diff --git a/.gitignore b/.gitignore index 3b24e4e90..b5d59005d 100644 --- a/.gitignore +++ b/.gitignore @@ -59,4 +59,3 @@ app/static.go prog/static.go vendor/github.com/ugorji/go/codec/codecgen/bin/* *.codecgen.go -*.generated.go diff --git a/app/api_topologies_test.go b/app/api_topologies_test.go index 371ad6774..6e6945eff 100644 --- a/app/api_topologies_test.go +++ b/app/api_topologies_test.go @@ -83,7 +83,7 @@ func TestAPITopologyAddsKubernetes(t *testing.T) { {ContainerID: "container2"}, }, }, - }).GetNode() + }).GetNode("") buf := &bytes.Buffer{} encoder := codec.NewEncoder(buf, &codec.MsgpackHandle{}) if err := encoder.Encode(rpt); err != nil { diff --git a/probe/host/tagger.go b/probe/host/tagger.go index dbdeefc8a..8b0b7ec46 100644 --- a/probe/host/tagger.go +++ b/probe/host/tagger.go @@ -31,7 +31,7 @@ func (t Tagger) Tag(r report.Report) (report.Report, error) { // Explicitly don't tag Endpoints and Addresses - These topologies include pseudo nodes, // and as such do their own host tagging - for _, topology := range []report.Topology{r.Process, r.Container, r.ContainerImage, r.Host, r.Overlay} { + for _, topology := range []report.Topology{r.Process, r.Container, r.ContainerImage, r.Host, r.Overlay, r.Pod} { for _, node := range topology.Nodes { topology.AddNode(node.WithLatests(metadata).WithParents(parents)) } diff --git a/probe/kubernetes/client.go b/probe/kubernetes/client.go index db8ba601b..7bcafa5b1 100644 --- a/probe/kubernetes/client.go +++ b/probe/kubernetes/client.go @@ -1,11 +1,14 @@ package kubernetes import ( + "io" + "strconv" "time" log "github.com/Sirupsen/logrus" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/client/cache" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" @@ -22,15 +25,19 @@ type Client interface { Stop() WalkPods(f func(Pod) error) error WalkServices(f func(Service) error) error + WalkNodes(f func(*api.Node) error) error + GetLogs(namespaceID, podID string) (io.ReadCloser, error) } type client struct { quit chan struct{} - client cache.Getter + client *unversioned.Client podReflector *cache.Reflector serviceReflector *cache.Reflector + nodeReflector *cache.Reflector podStore *cache.StoreToPodLister serviceStore *cache.StoreToServiceLister + nodeStore *cache.StoreToNodeLister } // runReflectorUntil is equivalent to cache.Reflector.RunUntil, but it also logs @@ -46,16 +53,16 @@ func runReflectorUntil(r *cache.Reflector, resyncPeriod time.Duration, stopCh <- // NewClient returns a usable Client. Don't forget to Stop it. func NewClient(addr string, resyncPeriod time.Duration) (Client, error) { - var config *unversioned.Config + var config *restclient.Config if addr != "" { - config = &unversioned.Config{Host: addr} + config = &restclient.Config{Host: addr} } else { // If no API server address was provided, assume we are running // inside a pod. Try to connect to the API server through its // Service environment variables, using the default Service // Account Token. var err error - if config, err = unversioned.InClusterConfig(); err != nil { + if config, err = restclient.InClusterConfig(); err != nil { return nil, err } } @@ -73,9 +80,14 @@ func NewClient(addr string, resyncPeriod time.Duration) (Client, error) { serviceStore := cache.NewStore(cache.MetaNamespaceKeyFunc) serviceReflector := cache.NewReflector(serviceListWatch, &api.Service{}, serviceStore, resyncPeriod) + nodeListWatch := cache.NewListWatchFromClient(c, "nodes", api.NamespaceAll, fields.Everything()) + nodeStore := cache.NewStore(cache.MetaNamespaceKeyFunc) + nodeReflector := cache.NewReflector(nodeListWatch, &api.Node{}, nodeStore, resyncPeriod) + quit := make(chan struct{}) runReflectorUntil(podReflector, resyncPeriod, quit) runReflectorUntil(serviceReflector, resyncPeriod, quit) + runReflectorUntil(nodeReflector, resyncPeriod, quit) return &client{ quit: quit, @@ -84,6 +96,8 @@ func NewClient(addr string, resyncPeriod time.Duration) (Client, error) { podStore: &cache.StoreToPodLister{Store: podStore}, serviceReflector: serviceReflector, serviceStore: &cache.StoreToServiceLister{Store: serviceStore}, + nodeReflector: nodeReflector, + nodeStore: &cache.StoreToNodeLister{Store: nodeStore}, }, nil } @@ -113,6 +127,31 @@ func (c *client) WalkServices(f func(Service) error) error { return nil } +func (c *client) WalkNodes(f func(*api.Node) error) error { + list, err := c.nodeStore.List() + if err != nil { + return err + } + for i := range list.Items { + if err := f(&(list.Items[i])); err != nil { + return err + } + } + return nil +} + +func (c *client) GetLogs(namespaceID, podID string) (io.ReadCloser, error) { + return c.client.RESTClient.Get(). + Namespace(namespaceID). + Name(podID). + Resource("pods"). + SubResource("log"). + Param("follow", strconv.FormatBool(true)). + Param("previous", strconv.FormatBool(false)). + Param("timestamps", strconv.FormatBool(true)). + Stream() +} + func (c *client) Stop() { close(c.quit) } diff --git a/probe/kubernetes/controls.go b/probe/kubernetes/controls.go new file mode 100644 index 000000000..ef1b9ad6f --- /dev/null +++ b/probe/kubernetes/controls.go @@ -0,0 +1,54 @@ +package kubernetes + +import ( + "io" + "io/ioutil" + + "github.com/weaveworks/scope/common/xfer" + "github.com/weaveworks/scope/probe/controls" + "github.com/weaveworks/scope/report" +) + +// Control IDs used by the kubernetes integration. +const ( + GetLogs = "kubernetes_get_logs" +) + +// GetLogs is the control to get the logs for a kubernetes pod +func (r *Reporter) GetLogs(req xfer.Request) xfer.Response { + namespaceID, podID, ok := report.ParsePodNodeID(req.NodeID) + if !ok { + return xfer.ResponseErrorf("Invalid ID: %s", req.NodeID) + } + + readCloser, err := r.client.GetLogs(namespaceID, podID) + if err != nil { + return xfer.ResponseError(err) + } + + readWriter := struct { + io.Reader + io.Writer + }{ + readCloser, + ioutil.Discard, + } + id, pipe, err := controls.NewPipeFromEnds(nil, readWriter, r.pipes, req.AppID) + if err != nil { + return xfer.ResponseError(err) + } + pipe.OnClose(func() { + readCloser.Close() + }) + return xfer.Response{ + Pipe: id, + } +} + +func (r *Reporter) registerControls() { + controls.Register(GetLogs, r.GetLogs) +} + +func (r *Reporter) deregisterControls() { + controls.Rm(GetLogs) +} diff --git a/probe/kubernetes/pod.go b/probe/kubernetes/pod.go index 0349ea87c..09a57f382 100644 --- a/probe/kubernetes/pod.go +++ b/probe/kubernetes/pod.go @@ -29,7 +29,8 @@ type Pod interface { Created() string AddServiceID(id string) Labels() labels.Labels - GetNode() report.Node + NodeName() string + GetNode(probeID string) report.Node } type pod struct { @@ -79,14 +80,19 @@ func (p *pod) State() string { return string(p.Status.Phase) } -func (p *pod) GetNode() report.Node { +func (p *pod) NodeName() string { + return p.Spec.NodeName +} + +func (p *pod) GetNode(probeID string) report.Node { n := report.MakeNodeWith(report.MakePodNodeID(p.Namespace(), p.Name()), map[string]string{ - PodID: p.ID(), - PodName: p.Name(), - Namespace: p.Namespace(), - PodCreated: p.Created(), - PodContainerIDs: strings.Join(p.ContainerIDs(), " "), - PodState: p.State(), + PodID: p.ID(), + PodName: p.Name(), + Namespace: p.Namespace(), + PodCreated: p.Created(), + PodContainerIDs: strings.Join(p.ContainerIDs(), " "), + PodState: p.State(), + report.ControlProbeID: probeID, }) if len(p.serviceIDs) > 0 { n = n.WithLatests(map[string]string{ServiceIDs: strings.Join(p.serviceIDs, " ")}) @@ -100,5 +106,7 @@ func (p *pod) GetNode() report.Node { Add(report.Service, report.MakeStringSet(report.MakeServiceNodeID(p.Namespace(), segments[1]))), ) } - return n.AddTable(PodLabelPrefix, p.ObjectMeta.Labels) + n = n.AddTable(PodLabelPrefix, p.ObjectMeta.Labels) + n = n.WithControls(GetLogs) + return n } diff --git a/probe/kubernetes/reporter.go b/probe/kubernetes/reporter.go index 56e7293e1..d27a68731 100644 --- a/probe/kubernetes/reporter.go +++ b/probe/kubernetes/reporter.go @@ -1,8 +1,14 @@ package kubernetes import ( + "io/ioutil" + "os" + "strings" + + "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/labels" + "github.com/weaveworks/scope/probe/controls" "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/report" ) @@ -34,14 +40,25 @@ var ( // Reporter generate Reports containing Container and ContainerImage topologies type Reporter struct { - client Client + client Client + pipes controls.PipeClient + probeID string } // NewReporter makes a new Reporter -func NewReporter(client Client) *Reporter { - return &Reporter{ - client: client, +func NewReporter(client Client, pipes controls.PipeClient, probeID string) *Reporter { + reporter := &Reporter{ + client: client, + pipes: pipes, + probeID: probeID, } + reporter.registerControls() + return reporter +} + +// Stop unregisters controls. +func (r *Reporter) Stop() { + r.deregisterControls() } // Name of this reporter, for metrics gathering @@ -79,6 +96,27 @@ func (r *Reporter) serviceTopology() (report.Topology, []Service, error) { return result, services, err } +// GetNodeName return the k8s node name for the current machine. +// It is exported for testing. +var GetNodeName = func(r *Reporter) (string, error) { + uuidBytes, err := ioutil.ReadFile("/sys/class/dmi/id/product_uuid") + if os.IsNotExist(err) { + uuidBytes, err = ioutil.ReadFile("/sys/hypervisor/uuid") + } + if err != nil { + return "", err + } + uuid := strings.Trim(string(uuidBytes), "\n") + nodeName := "" + err = r.client.WalkNodes(func(node *api.Node) error { + if node.Status.NodeInfo.SystemUUID == string(uuid) { + nodeName = node.ObjectMeta.Name + } + return nil + }) + return nodeName, err +} + func (r *Reporter) podTopology(services []Service) (report.Topology, report.Topology, error) { var ( pods = report.MakeTopology(). @@ -87,17 +125,30 @@ func (r *Reporter) podTopology(services []Service) (report.Topology, report.Topo containers = report.MakeTopology() selectors = map[string]labels.Selector{} ) + pods.Controls.AddControl(report.Control{ + ID: GetLogs, + Human: "Get logs", + Icon: "fa-desktop", + }) for _, service := range services { selectors[service.ID()] = service.Selector() } - err := r.client.WalkPods(func(p Pod) error { + + thisNodeName, err := GetNodeName(r) + if err != nil { + return pods, containers, err + } + err = r.client.WalkPods(func(p Pod) error { + if p.NodeName() != thisNodeName { + return nil + } for serviceID, selector := range selectors { if selector.Matches(p.Labels()) { p.AddServiceID(serviceID) } } nodeID := report.MakePodNodeID(p.Namespace(), p.Name()) - pods = pods.AddNode(p.GetNode()) + pods = pods.AddNode(p.GetNode(r.probeID)) for _, containerID := range p.ContainerIDs() { container := report.MakeNodeWith(report.MakeContainerNodeID(containerID), map[string]string{ diff --git a/probe/kubernetes/reporter_test.go b/probe/kubernetes/reporter_test.go index 3712cd4f6..101ba70d1 100644 --- a/probe/kubernetes/reporter_test.go +++ b/probe/kubernetes/reporter_test.go @@ -1,16 +1,22 @@ package kubernetes_test import ( + "fmt" + "io" + "io/ioutil" + "strings" "testing" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" + "github.com/weaveworks/scope/common/xfer" "github.com/weaveworks/scope/probe/kubernetes" "github.com/weaveworks/scope/report" ) var ( + nodeName = "nodename" podTypeMeta = unversioned.TypeMeta{ Kind: "Pod", APIVersion: "v1", @@ -30,6 +36,9 @@ var ( {ContainerID: "container2"}, }, }, + Spec: api.PodSpec{ + NodeName: nodeName, + }, } apiPod2 = api.Pod{ TypeMeta: podTypeMeta, @@ -46,6 +55,9 @@ var ( {ContainerID: "container4"}, }, }, + Spec: api.PodSpec{ + NodeName: nodeName, + }, } apiService1 = api.Service{ TypeMeta: unversioned.TypeMeta{ @@ -73,18 +85,23 @@ var ( }, }, } - pod1 = kubernetes.NewPod(&apiPod1) - pod2 = kubernetes.NewPod(&apiPod2) - service1 = kubernetes.NewService(&apiService1) - mockClientInstance = &mockClient{ + pod1 = kubernetes.NewPod(&apiPod1) + pod2 = kubernetes.NewPod(&apiPod2) + service1 = kubernetes.NewService(&apiService1) +) + +func newMockClient() *mockClient { + return &mockClient{ pods: []kubernetes.Pod{pod1, pod2}, services: []kubernetes.Service{service1}, + logs: map[string]io.ReadCloser{}, } -) +} type mockClient struct { pods []kubernetes.Pod services []kubernetes.Service + logs map[string]io.ReadCloser } func (c *mockClient) Stop() {} @@ -104,12 +121,41 @@ func (c *mockClient) WalkServices(f func(kubernetes.Service) error) error { } return nil } +func (*mockClient) WalkNodes(f func(*api.Node) error) error { + return nil +} +func (c *mockClient) GetLogs(namespaceID, podName string) (io.ReadCloser, error) { + r, ok := c.logs[report.MakePodNodeID(namespaceID, podName)] + if !ok { + return nil, fmt.Errorf("Not found") + } + return r, nil +} + +type mockPipeClient map[string]xfer.Pipe + +func (c mockPipeClient) PipeConnection(appID, id string, pipe xfer.Pipe) error { + c[id] = pipe + return nil +} + +func (c mockPipeClient) PipeClose(appID, id string) error { + err := c[id].Close() + delete(c, id) + return err +} func TestReporter(t *testing.T) { + oldGetNodeName := kubernetes.GetNodeName + defer func() { kubernetes.GetNodeName = oldGetNodeName }() + kubernetes.GetNodeName = func(*kubernetes.Reporter) (string, error) { + return nodeName, nil + } + pod1ID := report.MakePodNodeID("ping", "pong-a") pod2ID := report.MakePodNodeID("ping", "pong-b") serviceID := report.MakeServiceNodeID("ping", "pongservice") - rpt, _ := kubernetes.NewReporter(mockClientInstance).Report() + rpt, _ := kubernetes.NewReporter(newMockClient(), nil, "").Report() // Reporter should have added the following pods for _, pod := range []struct { @@ -197,3 +243,88 @@ func TestReporter(t *testing.T) { } } } + +type callbackReadCloser struct { + io.Reader + close func() error +} + +func (c *callbackReadCloser) Close() error { return c.close() } + +func TestReporterGetLogs(t *testing.T) { + oldGetNodeName := kubernetes.GetNodeName + defer func() { kubernetes.GetNodeName = oldGetNodeName }() + kubernetes.GetNodeName = func(*kubernetes.Reporter) (string, error) { + return nodeName, nil + } + + client := newMockClient() + pipes := mockPipeClient{} + reporter := kubernetes.NewReporter(client, pipes, "") + + // Should error on invalid IDs + { + resp := reporter.GetLogs(xfer.Request{ + NodeID: "invalidID", + Control: kubernetes.GetLogs, + }) + if want := "Invalid ID: invalidID"; resp.Error != want { + t.Errorf("Expected error on invalid ID: %q, got %q", want, resp.Error) + } + } + + // Should pass through errors from k8s (e.g if pod does not exist) + { + resp := reporter.GetLogs(xfer.Request{ + AppID: "appID", + NodeID: report.MakePodNodeID("not", "found"), + Control: kubernetes.GetLogs, + }) + if want := "Not found"; resp.Error != want { + t.Errorf("Expected error on invalid ID: %q, got %q", want, resp.Error) + } + } + + pod1ID := report.MakePodNodeID("ping", "pong-a") + pod1Request := xfer.Request{ + AppID: "appID", + NodeID: pod1ID, + Control: kubernetes.GetLogs, + } + + // Inject our logs content, and watch for it to be closed + closed := false + wantContents := "logs: ping/pong-a" + client.logs[pod1ID] = &callbackReadCloser{Reader: strings.NewReader(wantContents), close: func() error { + closed = true + return nil + }} + + // Should create a new pipe for the stream + resp := reporter.GetLogs(pod1Request) + if resp.Pipe == "" { + t.Errorf("Expected pipe id to be returned, but got %#v", resp) + } + pipe, ok := pipes[resp.Pipe] + if !ok { + t.Errorf("Expected pipe %q to have been created, but wasn't", resp.Pipe) + } + + // Should push logs from k8s client into the pipe + _, readWriter := pipe.Ends() + contents, err := ioutil.ReadAll(readWriter) + if err != nil { + t.Error(err) + } + if string(contents) != wantContents { + t.Errorf("Expected pipe to contain %q, but got %q", wantContents, string(contents)) + } + + // Should close the stream when the pipe closes + if err := pipe.Close(); err != nil { + t.Error(err) + } + if !closed { + t.Errorf("Expected pipe to close the underlying log stream") + } +} diff --git a/prog/probe.go b/prog/probe.go index 3138b16b4..fa6b082dc 100644 --- a/prog/probe.go +++ b/prog/probe.go @@ -144,7 +144,9 @@ func probeMain(flags probeFlags) { if flags.kubernetesEnabled { if client, err := kubernetes.NewClient(flags.kubernetesAPI, flags.kubernetesInterval); err == nil { defer client.Stop() - p.AddReporter(kubernetes.NewReporter(client)) + reporter := kubernetes.NewReporter(client, clients, probeID) + defer reporter.Stop() + p.AddReporter(reporter) } else { log.Errorf("Kubernetes: failed to start client: %v", err) log.Errorf("Kubernetes: make sure to run Scope inside a POD with a service account or provide a valid kubernetes.api url") diff --git a/render/detailed/node_test.go b/render/detailed/node_test.go index 39a9b489c..78c0e1a4a 100644 --- a/render/detailed/node_test.go +++ b/render/detailed/node_test.go @@ -34,6 +34,7 @@ func TestMakeDetailedHostNode(t *testing.T) { process1NodeSummary.Linkable = true process2NodeSummary := child(t, render.ProcessRenderer, fixture.ClientProcess2NodeID) process2NodeSummary.Linkable = true + podNodeSummary := child(t, render.PodRenderer, fixture.ClientPodNodeID) want := detailed.Node{ NodeSummary: detailed.NodeSummary{ ID: fixture.ClientHostNodeID, @@ -93,6 +94,12 @@ func TestMakeDetailedHostNode(t *testing.T) { }, Controls: []detailed.ControlInstance{}, Children: []detailed.NodeSummaryGroup{ + { + Label: "Pods", + TopologyID: "pods", + Columns: nil, + Nodes: []detailed.NodeSummary{podNodeSummary}, + }, { Label: "Containers", TopologyID: "containers", diff --git a/render/expected/expected.go b/render/expected/expected.go index 64f6ac561..095f80da6 100644 --- a/render/expected/expected.go +++ b/render/expected/expected.go @@ -218,7 +218,7 @@ var ( RenderedProcesses[fixture.ClientProcess2NodeID], RenderedContainers[fixture.ClientContainerNodeID], RenderedContainerImages[fixture.ClientContainerImageNodeID], - //RenderedPods[fixture.ClientPodNodeID], #1142 + RenderedPods[fixture.ClientPodNodeID], )), fixture.ServerHostNodeID: hostNode(fixture.ServerHostNodeID, render.OutgoingInternetID). @@ -229,7 +229,7 @@ var ( RenderedProcesses[fixture.NonContainerProcessNodeID], RenderedContainers[fixture.ServerContainerNodeID], RenderedContainerImages[fixture.ServerContainerImageNodeID], - //RenderedPods[fixture.ServerPodNodeID], #1142 + RenderedPods[fixture.ServerPodNodeID], )), // due to https://github.com/weaveworks/scope/issues/1323 we are dropping diff --git a/render/topologies.go b/render/topologies.go index 78292c977..904f2cde8 100644 --- a/render/topologies.go +++ b/render/topologies.go @@ -213,11 +213,10 @@ var HostRenderer = MakeReduce( MapX2Host, ContainerImageRenderer, ), - // Pods don't have a host id - #1142 - // MakeMap( - // MapX2Host, - // SelectPod, - // ), + MakeMap( + MapX2Host, + PodRenderer, + ), SelectHost, ) diff --git a/report/id.go b/report/id.go index 2919f71d2..2ffc0b2a3 100644 --- a/report/id.go +++ b/report/id.go @@ -166,6 +166,15 @@ func ParseAddressNodeID(addressNodeID string) (hostID, address string, ok bool) return fields[0], fields[1], true } +// ParsePodNodeID produces the namespace ID and pod ID from an pod node ID. +func ParsePodNodeID(podNodeID string) (namespaceID, podID string, ok bool) { + fields := strings.SplitN(podNodeID, ScopeDelim, 2) + if len(fields) != 2 { + return "", "", false + } + return fields[0], fields[1], true +} + // ExtractHostID extracts the host id from Node func ExtractHostID(m Node) string { hostNodeID, _ := m.Latest.Lookup(HostNodeID) diff --git a/vendor/k8s.io/kubernetes/pkg/OWNERS b/vendor/k8s.io/kubernetes/pkg/OWNERS new file mode 100644 index 000000000..b55c4799a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/OWNERS @@ -0,0 +1,7 @@ +assignees: + - bgrant0607 + - brendandburns + - dchen1107 + - lavalamp + - smarterclayton + - thockin diff --git a/vendor/k8s.io/kubernetes/pkg/admission/OWNERS b/vendor/k8s.io/kubernetes/pkg/admission/OWNERS new file mode 100644 index 000000000..27608d8f8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/admission/OWNERS @@ -0,0 +1,6 @@ +assignees: + - davidopp + - derekwaynecarr + - erictune + - lavalamp + - liggitt diff --git a/vendor/k8s.io/kubernetes/pkg/admission/attributes.go b/vendor/k8s.io/kubernetes/pkg/admission/attributes.go new file mode 100644 index 000000000..838ea100b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/admission/attributes.go @@ -0,0 +1,79 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 admission + +import ( + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/auth/user" + "k8s.io/kubernetes/pkg/runtime" +) + +type attributesRecord struct { + kind unversioned.GroupKind + namespace string + name string + resource unversioned.GroupResource + subresource string + operation Operation + object runtime.Object + userInfo user.Info +} + +func NewAttributesRecord(object runtime.Object, kind unversioned.GroupKind, namespace, name string, resource unversioned.GroupResource, subresource string, operation Operation, userInfo user.Info) Attributes { + return &attributesRecord{ + kind: kind, + namespace: namespace, + name: name, + resource: resource, + subresource: subresource, + operation: operation, + object: object, + userInfo: userInfo, + } +} + +func (record *attributesRecord) GetKind() unversioned.GroupKind { + return record.kind +} + +func (record *attributesRecord) GetNamespace() string { + return record.namespace +} + +func (record *attributesRecord) GetName() string { + return record.name +} + +func (record *attributesRecord) GetResource() unversioned.GroupResource { + return record.resource +} + +func (record *attributesRecord) GetSubresource() string { + return record.subresource +} + +func (record *attributesRecord) GetOperation() Operation { + return record.operation +} + +func (record *attributesRecord) GetObject() runtime.Object { + return record.object +} + +func (record *attributesRecord) GetUserInfo() user.Info { + return record.userInfo +} diff --git a/vendor/k8s.io/kubernetes/pkg/admission/chain.go b/vendor/k8s.io/kubernetes/pkg/admission/chain.go new file mode 100644 index 000000000..5301a7b72 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/admission/chain.go @@ -0,0 +1,64 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 admission + +import clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + +// chainAdmissionHandler is an instance of admission.Interface that performs admission control using a chain of admission handlers +type chainAdmissionHandler []Interface + +// NewFromPlugins returns an admission.Interface that will enforce admission control decisions of all +// the given plugins. +func NewFromPlugins(client clientset.Interface, pluginNames []string, configFilePath string) Interface { + plugins := []Interface{} + for _, pluginName := range pluginNames { + plugin := InitPlugin(pluginName, client, configFilePath) + if plugin != nil { + plugins = append(plugins, plugin) + } + } + return chainAdmissionHandler(plugins) +} + +// NewChainHandler creates a new chain handler from an array of handlers. Used for testing. +func NewChainHandler(handlers ...Interface) Interface { + return chainAdmissionHandler(handlers) +} + +// Admit performs an admission control check using a chain of handlers, and returns immediately on first error +func (admissionHandler chainAdmissionHandler) Admit(a Attributes) error { + for _, handler := range admissionHandler { + if !handler.Handles(a.GetOperation()) { + continue + } + err := handler.Admit(a) + if err != nil { + return err + } + } + return nil +} + +// Handles will return true if any of the handlers handles the given operation +func (admissionHandler chainAdmissionHandler) Handles(operation Operation) bool { + for _, handler := range admissionHandler { + if handler.Handles(operation) { + return true + } + } + return false +} diff --git a/vendor/k8s.io/kubernetes/pkg/admission/chain_test.go b/vendor/k8s.io/kubernetes/pkg/admission/chain_test.go new file mode 100644 index 000000000..1e8056786 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/admission/chain_test.go @@ -0,0 +1,154 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 admission + +import ( + "fmt" + "testing" + + "k8s.io/kubernetes/pkg/api/unversioned" +) + +type FakeHandler struct { + *Handler + name string + admit bool + admitCalled bool +} + +func (h *FakeHandler) Admit(a Attributes) (err error) { + h.admitCalled = true + if h.admit { + return nil + } + return fmt.Errorf("Don't admit") +} + +func makeHandler(name string, admit bool, ops ...Operation) Interface { + return &FakeHandler{ + name: name, + admit: admit, + Handler: NewHandler(ops...), + } +} + +func TestAdmit(t *testing.T) { + tests := []struct { + name string + operation Operation + chain chainAdmissionHandler + accept bool + calls map[string]bool + }{ + { + name: "all accept", + operation: Create, + chain: []Interface{ + makeHandler("a", true, Update, Delete, Create), + makeHandler("b", true, Delete, Create), + makeHandler("c", true, Create), + }, + calls: map[string]bool{"a": true, "b": true, "c": true}, + accept: true, + }, + { + name: "ignore handler", + operation: Create, + chain: []Interface{ + makeHandler("a", true, Update, Delete, Create), + makeHandler("b", false, Delete), + makeHandler("c", true, Create), + }, + calls: map[string]bool{"a": true, "c": true}, + accept: true, + }, + { + name: "ignore all", + operation: Connect, + chain: []Interface{ + makeHandler("a", true, Update, Delete, Create), + makeHandler("b", false, Delete), + makeHandler("c", true, Create), + }, + calls: map[string]bool{}, + accept: true, + }, + { + name: "reject one", + operation: Delete, + chain: []Interface{ + makeHandler("a", true, Update, Delete, Create), + makeHandler("b", false, Delete), + makeHandler("c", true, Create), + }, + calls: map[string]bool{"a": true, "b": true}, + accept: false, + }, + } + for _, test := range tests { + err := test.chain.Admit(NewAttributesRecord(nil, unversioned.GroupKind{}, "", "", unversioned.GroupResource{}, "", test.operation, nil)) + accepted := (err == nil) + if accepted != test.accept { + t.Errorf("%s: unexpected result of admit call: %v\n", test.name, accepted) + } + for _, h := range test.chain { + fake := h.(*FakeHandler) + _, shouldBeCalled := test.calls[fake.name] + if shouldBeCalled != fake.admitCalled { + t.Errorf("%s: handler %s not called as expected: %v", test.name, fake.name, fake.admitCalled) + continue + } + } + } +} + +func TestHandles(t *testing.T) { + chain := chainAdmissionHandler{ + makeHandler("a", true, Update, Delete, Create), + makeHandler("b", true, Delete, Create), + makeHandler("c", true, Create), + } + + tests := []struct { + name string + operation Operation + chain chainAdmissionHandler + expected bool + }{ + { + name: "all handle", + operation: Create, + expected: true, + }, + { + name: "none handle", + operation: Connect, + expected: false, + }, + { + name: "some handle", + operation: Delete, + expected: true, + }, + } + for _, test := range tests { + handles := chain.Handles(test.operation) + if handles != test.expected { + t.Errorf("Unexpected handles result. Expected: %v. Actual: %v", test.expected, handles) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/admission/errors.go b/vendor/k8s.io/kubernetes/pkg/admission/errors.go new file mode 100644 index 000000000..cdb53e482 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/admission/errors.go @@ -0,0 +1,66 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 admission + +import ( + apierrors "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" + utilerrors "k8s.io/kubernetes/pkg/util/errors" +) + +func extractResourceName(a Attributes) (name string, resource unversioned.GroupResource, err error) { + name = "Unknown" + resource = a.GetResource() + obj := a.GetObject() + if obj != nil { + accessor, err := meta.Accessor(obj) + if err != nil { + return "", unversioned.GroupResource{}, err + } + + // this is necessary because name object name generation has not occurred yet + if len(accessor.GetName()) > 0 { + name = accessor.GetName() + } else if len(accessor.GetGenerateName()) > 0 { + name = accessor.GetGenerateName() + } + } + return name, resource, nil +} + +// NewForbidden is a utility function to return a well-formatted admission control error response +func NewForbidden(a Attributes, internalError error) error { + // do not double wrap an error of same type + if apierrors.IsForbidden(internalError) { + return internalError + } + name, resource, err := extractResourceName(a) + if err != nil { + return apierrors.NewInternalError(utilerrors.NewAggregate([]error{internalError, err})) + } + return apierrors.NewForbidden(resource, name, internalError) +} + +// NewNotFound is a utility function to return a well-formatted admission control error response +func NewNotFound(a Attributes) error { + name, resource, err := extractResourceName(a) + if err != nil { + return apierrors.NewInternalError(err) + } + return apierrors.NewNotFound(resource, name) +} diff --git a/vendor/k8s.io/kubernetes/pkg/admission/handler.go b/vendor/k8s.io/kubernetes/pkg/admission/handler.go new file mode 100644 index 000000000..a0d26c469 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/admission/handler.go @@ -0,0 +1,44 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 admission + +import ( + "k8s.io/kubernetes/pkg/util/sets" +) + +// Handler is a base for admission control handlers that +// support a predefined set of operations +type Handler struct { + operations sets.String +} + +// Handles returns true for methods that this handler supports +func (h *Handler) Handles(operation Operation) bool { + return h.operations.Has(string(operation)) +} + +// NewHandler creates a new base handler that handles the passed +// in operations +func NewHandler(ops ...Operation) *Handler { + operations := sets.NewString() + for _, op := range ops { + operations.Insert(string(op)) + } + return &Handler{ + operations: operations, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/admission/interfaces.go b/vendor/k8s.io/kubernetes/pkg/admission/interfaces.go new file mode 100644 index 000000000..a19f5847b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/admission/interfaces.go @@ -0,0 +1,69 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 admission + +import ( + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/auth/user" + "k8s.io/kubernetes/pkg/runtime" +) + +// Attributes is an interface used by AdmissionController to get information about a request +// that is used to make an admission decision. +type Attributes interface { + // GetName returns the name of the object as presented in the request. On a CREATE operation, the client + // may omit name and rely on the server to generate the name. If that is the case, this method will return + // the empty string + GetName() string + // GetNamespace is the namespace associated with the request (if any) + GetNamespace() string + // GetResource is the name of the resource being requested. This is not the kind. For example: pods + GetResource() unversioned.GroupResource + // GetSubresource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind. + // For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" + // (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding". + GetSubresource() string + // GetOperation is the operation being performed + GetOperation() Operation + // GetObject is the object from the incoming request prior to default values being applied + GetObject() runtime.Object + // GetKind is the type of object being manipulated. For example: Pod + GetKind() unversioned.GroupKind + // GetUserInfo is information about the requesting user + GetUserInfo() user.Info +} + +// Interface is an abstract, pluggable interface for Admission Control decisions. +type Interface interface { + // Admit makes an admission decision based on the request attributes + Admit(a Attributes) (err error) + + // Handles returns true if this admission controller can handle the given operation + // where operation can be one of CREATE, UPDATE, DELETE, or CONNECT + Handles(operation Operation) bool +} + +// Operation is the type of resource operation being checked for admission control +type Operation string + +// Operation constants +const ( + Create Operation = "CREATE" + Update Operation = "UPDATE" + Delete Operation = "DELETE" + Connect Operation = "CONNECT" +) diff --git a/vendor/k8s.io/kubernetes/pkg/admission/plugins.go b/vendor/k8s.io/kubernetes/pkg/admission/plugins.go new file mode 100644 index 000000000..09fc449e1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/admission/plugins.go @@ -0,0 +1,111 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 admission + +import ( + "io" + "os" + "sort" + "sync" + + "github.com/golang/glog" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" +) + +// Factory is a function that returns an Interface for admission decisions. +// The config parameter provides an io.Reader handler to the factory in +// order to load specific configurations. If no configuration is provided +// the parameter is nil. +type Factory func(client clientset.Interface, config io.Reader) (Interface, error) + +// All registered admission options. +var ( + pluginsMutex sync.Mutex + plugins = make(map[string]Factory) +) + +// GetPlugins enumerates the names of all registered plugins. +func GetPlugins() []string { + pluginsMutex.Lock() + defer pluginsMutex.Unlock() + keys := []string{} + for k := range plugins { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +// RegisterPlugin registers a plugin Factory by name. This +// is expected to happen during app startup. +func RegisterPlugin(name string, plugin Factory) { + pluginsMutex.Lock() + defer pluginsMutex.Unlock() + _, found := plugins[name] + if found { + glog.Fatalf("Admission plugin %q was registered twice", name) + } + glog.V(1).Infof("Registered admission plugin %q", name) + plugins[name] = plugin +} + +// GetPlugin creates an instance of the named plugin, or nil if the name is not +// known. The error is returned only when the named provider was known but failed +// to initialize. The config parameter specifies the io.Reader handler of the +// configuration file for the cloud provider, or nil for no configuration. +func GetPlugin(name string, client clientset.Interface, config io.Reader) (Interface, error) { + pluginsMutex.Lock() + defer pluginsMutex.Unlock() + f, found := plugins[name] + if !found { + return nil, nil + } + return f(client, config) +} + +// InitPlugin creates an instance of the named interface. +func InitPlugin(name string, client clientset.Interface, configFilePath string) Interface { + var ( + config *os.File + err error + ) + + if name == "" { + glog.Info("No admission plugin specified.") + return nil + } + + if configFilePath != "" { + config, err = os.Open(configFilePath) + if err != nil { + glog.Fatalf("Couldn't open admission plugin configuration %s: %#v", + configFilePath, err) + } + + defer config.Close() + } + + plugin, err := GetPlugin(name, client, config) + if err != nil { + glog.Fatalf("Couldn't init admission plugin %q: %v", name, err) + } + if plugin == nil { + glog.Fatalf("Unknown admission plugin: %s", name) + } + + return plugin +} diff --git a/vendor/k8s.io/kubernetes/pkg/api/OWNERS b/vendor/k8s.io/kubernetes/pkg/api/OWNERS new file mode 100644 index 000000000..d28472e0f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/api/OWNERS @@ -0,0 +1,6 @@ +assignees: + - bgrant0607 + - erictune + - lavalamp + - smarterclayton + - thockin diff --git a/vendor/k8s.io/kubernetes/pkg/api/conversion.go b/vendor/k8s.io/kubernetes/pkg/api/conversion.go index 5599db346..896aef961 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/conversion.go +++ b/vendor/k8s.io/kubernetes/pkg/api/conversion.go @@ -41,19 +41,20 @@ func init() { Convert_unversioned_ListMeta_To_unversioned_ListMeta, Convert_intstr_IntOrString_To_intstr_IntOrString, Convert_unversioned_Time_To_unversioned_Time, + Convert_Slice_string_To_unversioned_Time, Convert_string_To_labels_Selector, Convert_string_To_fields_Selector, - Convert_bool_ref_To_bool, - Convert_bool_To_bool_ref, - Convert_string_ref_To_string, - Convert_string_To_string_ref, + Convert_Pointer_bool_To_bool, + Convert_bool_To_Pointer_bool, + Convert_Pointer_string_To_string, + Convert_string_To_Pointer_string, Convert_labels_Selector_To_string, Convert_fields_Selector_To_string, Convert_resource_Quantity_To_resource_Quantity, ) } -func Convert_string_ref_To_string(in **string, out *string, s conversion.Scope) error { +func Convert_Pointer_string_To_string(in **string, out *string, s conversion.Scope) error { if *in == nil { *out = "" return nil @@ -62,7 +63,7 @@ func Convert_string_ref_To_string(in **string, out *string, s conversion.Scope) return nil } -func Convert_string_To_string_ref(in *string, out **string, s conversion.Scope) error { +func Convert_string_To_Pointer_string(in *string, out **string, s conversion.Scope) error { if in == nil { stringVar := "" *out = &stringVar @@ -72,7 +73,7 @@ func Convert_string_To_string_ref(in *string, out **string, s conversion.Scope) return nil } -func Convert_bool_ref_To_bool(in **bool, out *bool, s conversion.Scope) error { +func Convert_Pointer_bool_To_bool(in **bool, out *bool, s conversion.Scope) error { if *in == nil { *out = false return nil @@ -81,7 +82,7 @@ func Convert_bool_ref_To_bool(in **bool, out *bool, s conversion.Scope) error { return nil } -func Convert_bool_To_bool_ref(in *bool, out **bool, s conversion.Scope) error { +func Convert_bool_To_Pointer_bool(in *bool, out **bool, s conversion.Scope) error { if in == nil { boolVar := false *out = &boolVar @@ -116,6 +117,16 @@ func Convert_unversioned_Time_To_unversioned_Time(in *unversioned.Time, out *unv *out = *in return nil } + +// Convert_Slice_string_To_unversioned_Time allows converting a URL query parameter value +func Convert_Slice_string_To_unversioned_Time(input *[]string, out *unversioned.Time, s conversion.Scope) error { + str := "" + if len(*input) > 0 { + str = (*input)[0] + } + return out.UnmarshalQueryParameter(str) +} + func Convert_string_To_labels_Selector(in *string, out *labels.Selector, s conversion.Scope) error { selector, err := labels.Parse(*in) if err != nil { diff --git a/vendor/k8s.io/kubernetes/pkg/api/copy_test.go b/vendor/k8s.io/kubernetes/pkg/api/copy_test.go index 194b5318a..af81d0273 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/copy_test.go +++ b/vendor/k8s.io/kubernetes/pkg/api/copy_test.go @@ -25,7 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" "github.com/google/gofuzz" ) @@ -54,7 +54,7 @@ func doDeepCopyTest(t *testing.T, kind unversioned.GroupVersionKind, f *fuzz.Fuz } if !reflect.DeepEqual(item, itemCopy) { - t.Errorf("\nexpected: %#v\n\ngot: %#v\n\ndiff: %v", item, itemCopy, util.ObjectGoPrintSideBySide(item, itemCopy)) + t.Errorf("\nexpected: %#v\n\ngot: %#v\n\ndiff: %v", item, itemCopy, diff.ObjectGoPrintSideBySide(item, itemCopy)) } } diff --git a/vendor/k8s.io/kubernetes/pkg/api/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/api/deep_copy_generated.go index a63ef92ca..0458e6bb8 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/deep_copy_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/api/deep_copy_generated.go @@ -1,3 +1,5 @@ +// +build !ignore_autogenerated + /* Copyright 2016 The Kubernetes Authors All rights reserved. @@ -25,8 +27,8 @@ import ( fields "k8s.io/kubernetes/pkg/fields" labels "k8s.io/kubernetes/pkg/labels" runtime "k8s.io/kubernetes/pkg/runtime" + types "k8s.io/kubernetes/pkg/types" intstr "k8s.io/kubernetes/pkg/util/intstr" - sets "k8s.io/kubernetes/pkg/util/sets" ) func init() { @@ -104,6 +106,7 @@ func init() { DeepCopy_api_NodeCondition, DeepCopy_api_NodeDaemonEndpoints, DeepCopy_api_NodeList, + DeepCopy_api_NodeProxyOptions, DeepCopy_api_NodeResources, DeepCopy_api_NodeSelector, DeepCopy_api_NodeSelectorRequirement, @@ -138,6 +141,7 @@ func init() { DeepCopy_api_PodTemplate, DeepCopy_api_PodTemplateList, DeepCopy_api_PodTemplateSpec, + DeepCopy_api_Preconditions, DeepCopy_api_PreferredSchedulingTerm, DeepCopy_api_Probe, DeepCopy_api_RBDVolumeSource, @@ -163,22 +167,13 @@ func init() { DeepCopy_api_ServiceAccountList, DeepCopy_api_ServiceList, DeepCopy_api_ServicePort, + DeepCopy_api_ServiceProxyOptions, DeepCopy_api_ServiceSpec, DeepCopy_api_ServiceStatus, DeepCopy_api_TCPSocketAction, DeepCopy_api_Volume, DeepCopy_api_VolumeMount, DeepCopy_api_VolumeSource, - DeepCopy_conversion_Meta, - DeepCopy_intstr_IntOrString, - DeepCopy_sets_Empty, - DeepCopy_unversioned_GroupKind, - DeepCopy_unversioned_GroupResource, - DeepCopy_unversioned_GroupVersion, - DeepCopy_unversioned_GroupVersionKind, - DeepCopy_unversioned_GroupVersionResource, - DeepCopy_unversioned_ListMeta, - DeepCopy_unversioned_TypeMeta, ); err != nil { // if one of the deep copy functions is malformed, detect it immediately. panic(err) @@ -214,7 +209,7 @@ func DeepCopy_api_AzureFileVolumeSource(in AzureFileVolumeSource, out *AzureFile } func DeepCopy_api_Binding(in Binding, out *Binding, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -288,7 +283,7 @@ func DeepCopy_api_ComponentCondition(in ComponentCondition, out *ComponentCondit } func DeepCopy_api_ComponentStatus(in ComponentStatus, out *ComponentStatus, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -309,10 +304,10 @@ func DeepCopy_api_ComponentStatus(in ComponentStatus, out *ComponentStatus, c *c } func DeepCopy_api_ComponentStatusList(in ComponentStatusList, out *ComponentStatusList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -330,7 +325,7 @@ func DeepCopy_api_ComponentStatusList(in ComponentStatusList, out *ComponentStat } func DeepCopy_api_ConfigMap(in ConfigMap, out *ConfigMap, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -357,10 +352,10 @@ func DeepCopy_api_ConfigMapKeySelector(in ConfigMapKeySelector, out *ConfigMapKe } func DeepCopy_api_ConfigMapList(in ConfigMapList, out *ConfigMapList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -494,14 +489,14 @@ func DeepCopy_api_Container(in Container, out *Container, c *conversion.Cloner) } func DeepCopy_api_ContainerImage(in ContainerImage, out *ContainerImage, c *conversion.Cloner) error { - if in.RepoTags != nil { - in, out := in.RepoTags, &out.RepoTags + if in.Names != nil { + in, out := in.Names, &out.Names *out = make([]string, len(in)) copy(*out, in) } else { - out.RepoTags = nil + out.Names = nil } - out.Size = in.Size + out.SizeBytes = in.SizeBytes return nil } @@ -546,10 +541,8 @@ func DeepCopy_api_ContainerState(in ContainerState, out *ContainerState, c *conv } func DeepCopy_api_ContainerStateRunning(in ContainerStateRunning, out *ContainerStateRunning, c *conversion.Cloner) error { - if newVal, err := c.DeepCopy(in.StartedAt); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.StartedAt, &out.StartedAt, c); err != nil { return err - } else { - out.StartedAt = newVal.(unversioned.Time) } return nil } @@ -559,15 +552,11 @@ func DeepCopy_api_ContainerStateTerminated(in ContainerStateTerminated, out *Con out.Signal = in.Signal out.Reason = in.Reason out.Message = in.Message - if newVal, err := c.DeepCopy(in.StartedAt); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.StartedAt, &out.StartedAt, c); err != nil { return err - } else { - out.StartedAt = newVal.(unversioned.Time) } - if newVal, err := c.DeepCopy(in.FinishedAt); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.FinishedAt, &out.FinishedAt, c); err != nil { return err - } else { - out.FinishedAt = newVal.(unversioned.Time) } out.ContainerID = in.ContainerID return nil @@ -596,12 +585,16 @@ func DeepCopy_api_ContainerStatus(in ContainerStatus, out *ContainerStatus, c *c } func DeepCopy_api_ConversionError(in ConversionError, out *ConversionError, c *conversion.Cloner) error { - if newVal, err := c.DeepCopy(in.In); err != nil { + if in.In == nil { + out.In = nil + } else if newVal, err := c.DeepCopy(in.In); err != nil { return err } else { out.In = newVal.(interface{}) } - if newVal, err := c.DeepCopy(in.Out); err != nil { + if in.Out == nil { + out.Out = nil + } else if newVal, err := c.DeepCopy(in.Out); err != nil { return err } else { out.Out = newVal.(interface{}) @@ -616,7 +609,7 @@ func DeepCopy_api_DaemonEndpoint(in DaemonEndpoint, out *DaemonEndpoint, c *conv } func DeepCopy_api_DeleteOptions(in DeleteOptions, out *DeleteOptions, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if in.GracePeriodSeconds != nil { @@ -626,6 +619,15 @@ func DeepCopy_api_DeleteOptions(in DeleteOptions, out *DeleteOptions, c *convers } else { out.GracePeriodSeconds = nil } + if in.Preconditions != nil { + in, out := in.Preconditions, &out.Preconditions + *out = new(Preconditions) + if err := DeepCopy_api_Preconditions(*in, *out, c); err != nil { + return err + } + } else { + out.Preconditions = nil + } return nil } @@ -716,7 +718,7 @@ func DeepCopy_api_EndpointSubset(in EndpointSubset, out *EndpointSubset, c *conv } func DeepCopy_api_Endpoints(in Endpoints, out *Endpoints, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -737,10 +739,10 @@ func DeepCopy_api_Endpoints(in Endpoints, out *Endpoints, c *conversion.Cloner) } func DeepCopy_api_EndpointsList(in EndpointsList, out *EndpointsList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -804,7 +806,7 @@ func DeepCopy_api_EnvVarSource(in EnvVarSource, out *EnvVarSource, c *conversion } func DeepCopy_api_Event(in Event, out *Event, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -818,15 +820,11 @@ func DeepCopy_api_Event(in Event, out *Event, c *conversion.Cloner) error { if err := DeepCopy_api_EventSource(in.Source, &out.Source, c); err != nil { return err } - if newVal, err := c.DeepCopy(in.FirstTimestamp); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.FirstTimestamp, &out.FirstTimestamp, c); err != nil { return err - } else { - out.FirstTimestamp = newVal.(unversioned.Time) } - if newVal, err := c.DeepCopy(in.LastTimestamp); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.LastTimestamp, &out.LastTimestamp, c); err != nil { return err - } else { - out.LastTimestamp = newVal.(unversioned.Time) } out.Count = in.Count out.Type = in.Type @@ -834,10 +832,10 @@ func DeepCopy_api_Event(in Event, out *Event, c *conversion.Cloner) error { } func DeepCopy_api_EventList(in EventList, out *EventList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -872,7 +870,7 @@ func DeepCopy_api_ExecAction(in ExecAction, out *ExecAction, c *conversion.Clone } func DeepCopy_api_ExportOptions(in ExportOptions, out *ExportOptions, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.Export = in.Export @@ -954,7 +952,7 @@ func DeepCopy_api_GlusterfsVolumeSource(in GlusterfsVolumeSource, out *Glusterfs func DeepCopy_api_HTTPGetAction(in HTTPGetAction, out *HTTPGetAction, c *conversion.Cloner) error { out.Path = in.Path - if err := DeepCopy_intstr_IntOrString(in.Port, &out.Port, c); err != nil { + if err := intstr.DeepCopy_intstr_IntOrString(in.Port, &out.Port, c); err != nil { return err } out.Host = in.Host @@ -1054,7 +1052,7 @@ func DeepCopy_api_Lifecycle(in Lifecycle, out *Lifecycle, c *conversion.Cloner) } func DeepCopy_api_LimitRange(in LimitRange, out *LimitRange, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -1072,11 +1070,11 @@ func DeepCopy_api_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conv in, out := in.Max, &out.Max *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.Max = nil @@ -1085,11 +1083,11 @@ func DeepCopy_api_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conv in, out := in.Min, &out.Min *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.Min = nil @@ -1098,11 +1096,11 @@ func DeepCopy_api_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conv in, out := in.Default, &out.Default *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.Default = nil @@ -1111,11 +1109,11 @@ func DeepCopy_api_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conv in, out := in.DefaultRequest, &out.DefaultRequest *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.DefaultRequest = nil @@ -1124,11 +1122,11 @@ func DeepCopy_api_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conv in, out := in.MaxLimitRequestRatio, &out.MaxLimitRequestRatio *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.MaxLimitRequestRatio = nil @@ -1137,10 +1135,10 @@ func DeepCopy_api_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conv } func DeepCopy_api_LimitRangeList(in LimitRangeList, out *LimitRangeList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -1173,10 +1171,10 @@ func DeepCopy_api_LimitRangeSpec(in LimitRangeSpec, out *LimitRangeSpec, c *conv } func DeepCopy_api_List(in List, out *List, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -1196,15 +1194,19 @@ func DeepCopy_api_List(in List, out *List, c *conversion.Cloner) error { } func DeepCopy_api_ListOptions(in ListOptions, out *ListOptions, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if newVal, err := c.DeepCopy(in.LabelSelector); err != nil { + if in.LabelSelector == nil { + out.LabelSelector = nil + } else if newVal, err := c.DeepCopy(in.LabelSelector); err != nil { return err } else { out.LabelSelector = newVal.(labels.Selector) } - if newVal, err := c.DeepCopy(in.FieldSelector); err != nil { + if in.FieldSelector == nil { + out.FieldSelector = nil + } else if newVal, err := c.DeepCopy(in.FieldSelector); err != nil { return err } else { out.FieldSelector = newVal.(fields.Selector) @@ -1255,7 +1257,7 @@ func DeepCopy_api_NFSVolumeSource(in NFSVolumeSource, out *NFSVolumeSource, c *c } func DeepCopy_api_Namespace(in Namespace, out *Namespace, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -1271,10 +1273,10 @@ func DeepCopy_api_Namespace(in Namespace, out *Namespace, c *conversion.Cloner) } func DeepCopy_api_NamespaceList(in NamespaceList, out *NamespaceList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -1310,7 +1312,7 @@ func DeepCopy_api_NamespaceStatus(in NamespaceStatus, out *NamespaceStatus, c *c } func DeepCopy_api_Node(in Node, out *Node, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -1358,15 +1360,11 @@ func DeepCopy_api_NodeAffinity(in NodeAffinity, out *NodeAffinity, c *conversion func DeepCopy_api_NodeCondition(in NodeCondition, out *NodeCondition, c *conversion.Cloner) error { out.Type = in.Type out.Status = in.Status - if newVal, err := c.DeepCopy(in.LastHeartbeatTime); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.LastHeartbeatTime, &out.LastHeartbeatTime, c); err != nil { return err - } else { - out.LastHeartbeatTime = newVal.(unversioned.Time) } - if newVal, err := c.DeepCopy(in.LastTransitionTime); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil { return err - } else { - out.LastTransitionTime = newVal.(unversioned.Time) } out.Reason = in.Reason out.Message = in.Message @@ -1381,10 +1379,10 @@ func DeepCopy_api_NodeDaemonEndpoints(in NodeDaemonEndpoints, out *NodeDaemonEnd } func DeepCopy_api_NodeList(in NodeList, out *NodeList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -1401,16 +1399,24 @@ func DeepCopy_api_NodeList(in NodeList, out *NodeList, c *conversion.Cloner) err return nil } +func DeepCopy_api_NodeProxyOptions(in NodeProxyOptions, out *NodeProxyOptions, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + out.Path = in.Path + return nil +} + func DeepCopy_api_NodeResources(in NodeResources, out *NodeResources, c *conversion.Cloner) error { if in.Capacity != nil { in, out := in.Capacity, &out.Capacity *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.Capacity = nil @@ -1474,11 +1480,11 @@ func DeepCopy_api_NodeStatus(in NodeStatus, out *NodeStatus, c *conversion.Clone in, out := in.Capacity, &out.Capacity *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.Capacity = nil @@ -1487,11 +1493,11 @@ func DeepCopy_api_NodeStatus(in NodeStatus, out *NodeStatus, c *conversion.Clone in, out := in.Allocatable, &out.Allocatable *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.Allocatable = nil @@ -1565,18 +1571,14 @@ func DeepCopy_api_ObjectMeta(in ObjectMeta, out *ObjectMeta, c *conversion.Clone out.UID = in.UID out.ResourceVersion = in.ResourceVersion out.Generation = in.Generation - if newVal, err := c.DeepCopy(in.CreationTimestamp); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.CreationTimestamp, &out.CreationTimestamp, c); err != nil { return err - } else { - out.CreationTimestamp = newVal.(unversioned.Time) } if in.DeletionTimestamp != nil { in, out := in.DeletionTimestamp, &out.DeletionTimestamp *out = new(unversioned.Time) - if newVal, err := c.DeepCopy(*in); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { return err - } else { - **out = newVal.(unversioned.Time) } } else { out.DeletionTimestamp = nil @@ -1621,7 +1623,7 @@ func DeepCopy_api_ObjectReference(in ObjectReference, out *ObjectReference, c *c } func DeepCopy_api_PersistentVolume(in PersistentVolume, out *PersistentVolume, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -1637,7 +1639,7 @@ func DeepCopy_api_PersistentVolume(in PersistentVolume, out *PersistentVolume, c } func DeepCopy_api_PersistentVolumeClaim(in PersistentVolumeClaim, out *PersistentVolumeClaim, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -1653,10 +1655,10 @@ func DeepCopy_api_PersistentVolumeClaim(in PersistentVolumeClaim, out *Persisten } func DeepCopy_api_PersistentVolumeClaimList(in PersistentVolumeClaimList, out *PersistentVolumeClaimList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -1705,11 +1707,11 @@ func DeepCopy_api_PersistentVolumeClaimStatus(in PersistentVolumeClaimStatus, ou in, out := in.Capacity, &out.Capacity *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.Capacity = nil @@ -1724,10 +1726,10 @@ func DeepCopy_api_PersistentVolumeClaimVolumeSource(in PersistentVolumeClaimVolu } func DeepCopy_api_PersistentVolumeList(in PersistentVolumeList, out *PersistentVolumeList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -1870,11 +1872,11 @@ func DeepCopy_api_PersistentVolumeSpec(in PersistentVolumeSpec, out *PersistentV in, out := in.Capacity, &out.Capacity *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.Capacity = nil @@ -1912,7 +1914,7 @@ func DeepCopy_api_PersistentVolumeStatus(in PersistentVolumeStatus, out *Persist } func DeepCopy_api_Pod(in Pod, out *Pod, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -1928,7 +1930,7 @@ func DeepCopy_api_Pod(in Pod, out *Pod, c *conversion.Cloner) error { } func DeepCopy_api_PodAttachOptions(in PodAttachOptions, out *PodAttachOptions, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.Stdin = in.Stdin @@ -1942,15 +1944,11 @@ func DeepCopy_api_PodAttachOptions(in PodAttachOptions, out *PodAttachOptions, c func DeepCopy_api_PodCondition(in PodCondition, out *PodCondition, c *conversion.Cloner) error { out.Type = in.Type out.Status = in.Status - if newVal, err := c.DeepCopy(in.LastProbeTime); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.LastProbeTime, &out.LastProbeTime, c); err != nil { return err - } else { - out.LastProbeTime = newVal.(unversioned.Time) } - if newVal, err := c.DeepCopy(in.LastTransitionTime); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil { return err - } else { - out.LastTransitionTime = newVal.(unversioned.Time) } out.Reason = in.Reason out.Message = in.Message @@ -1958,7 +1956,7 @@ func DeepCopy_api_PodCondition(in PodCondition, out *PodCondition, c *conversion } func DeepCopy_api_PodExecOptions(in PodExecOptions, out *PodExecOptions, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.Stdin = in.Stdin @@ -1977,10 +1975,10 @@ func DeepCopy_api_PodExecOptions(in PodExecOptions, out *PodExecOptions, c *conv } func DeepCopy_api_PodList(in PodList, out *PodList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -1998,7 +1996,7 @@ func DeepCopy_api_PodList(in PodList, out *PodList, c *conversion.Cloner) error } func DeepCopy_api_PodLogOptions(in PodLogOptions, out *PodLogOptions, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.Container = in.Container @@ -2014,10 +2012,8 @@ func DeepCopy_api_PodLogOptions(in PodLogOptions, out *PodLogOptions, c *convers if in.SinceTime != nil { in, out := in.SinceTime, &out.SinceTime *out = new(unversioned.Time) - if newVal, err := c.DeepCopy(*in); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { return err - } else { - **out = newVal.(unversioned.Time) } } else { out.SinceTime = nil @@ -2041,7 +2037,7 @@ func DeepCopy_api_PodLogOptions(in PodLogOptions, out *PodLogOptions, c *convers } func DeepCopy_api_PodProxyOptions(in PodProxyOptions, out *PodProxyOptions, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.Path = in.Path @@ -2185,10 +2181,8 @@ func DeepCopy_api_PodStatus(in PodStatus, out *PodStatus, c *conversion.Cloner) if in.StartTime != nil { in, out := in.StartTime, &out.StartTime *out = new(unversioned.Time) - if newVal, err := c.DeepCopy(*in); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { return err - } else { - **out = newVal.(unversioned.Time) } } else { out.StartTime = nil @@ -2208,7 +2202,7 @@ func DeepCopy_api_PodStatus(in PodStatus, out *PodStatus, c *conversion.Cloner) } func DeepCopy_api_PodStatusResult(in PodStatusResult, out *PodStatusResult, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -2221,7 +2215,7 @@ func DeepCopy_api_PodStatusResult(in PodStatusResult, out *PodStatusResult, c *c } func DeepCopy_api_PodTemplate(in PodTemplate, out *PodTemplate, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -2234,10 +2228,10 @@ func DeepCopy_api_PodTemplate(in PodTemplate, out *PodTemplate, c *conversion.Cl } func DeepCopy_api_PodTemplateList(in PodTemplateList, out *PodTemplateList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -2264,6 +2258,21 @@ func DeepCopy_api_PodTemplateSpec(in PodTemplateSpec, out *PodTemplateSpec, c *c return nil } +func DeepCopy_api_Preconditions(in Preconditions, out *Preconditions, c *conversion.Cloner) error { + if in.UID != nil { + in, out := in.UID, &out.UID + *out = new(types.UID) + if newVal, err := c.DeepCopy(*in); err != nil { + return err + } else { + **out = newVal.(types.UID) + } + } else { + out.UID = nil + } + return nil +} + func DeepCopy_api_PreferredSchedulingTerm(in PreferredSchedulingTerm, out *PreferredSchedulingTerm, c *conversion.Cloner) error { out.Weight = in.Weight if err := DeepCopy_api_NodeSelectorTerm(in.Preference, &out.Preference, c); err != nil { @@ -2311,7 +2320,7 @@ func DeepCopy_api_RBDVolumeSource(in RBDVolumeSource, out *RBDVolumeSource, c *c } func DeepCopy_api_RangeAllocation(in RangeAllocation, out *RangeAllocation, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -2329,7 +2338,7 @@ func DeepCopy_api_RangeAllocation(in RangeAllocation, out *RangeAllocation, c *c } func DeepCopy_api_ReplicationController(in ReplicationController, out *ReplicationController, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -2345,10 +2354,10 @@ func DeepCopy_api_ReplicationController(in ReplicationController, out *Replicati } func DeepCopy_api_ReplicationControllerList(in ReplicationControllerList, out *ReplicationControllerList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -2390,12 +2399,13 @@ func DeepCopy_api_ReplicationControllerSpec(in ReplicationControllerSpec, out *R func DeepCopy_api_ReplicationControllerStatus(in ReplicationControllerStatus, out *ReplicationControllerStatus, c *conversion.Cloner) error { out.Replicas = in.Replicas + out.FullyLabeledReplicas = in.FullyLabeledReplicas out.ObservedGeneration = in.ObservedGeneration return nil } func DeepCopy_api_ResourceQuota(in ResourceQuota, out *ResourceQuota, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -2411,10 +2421,10 @@ func DeepCopy_api_ResourceQuota(in ResourceQuota, out *ResourceQuota, c *convers } func DeepCopy_api_ResourceQuotaList(in ResourceQuotaList, out *ResourceQuotaList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -2436,15 +2446,24 @@ func DeepCopy_api_ResourceQuotaSpec(in ResourceQuotaSpec, out *ResourceQuotaSpec in, out := in.Hard, &out.Hard *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.Hard = nil } + if in.Scopes != nil { + in, out := in.Scopes, &out.Scopes + *out = make([]ResourceQuotaScope, len(in)) + for i := range in { + (*out)[i] = in[i] + } + } else { + out.Scopes = nil + } return nil } @@ -2453,11 +2472,11 @@ func DeepCopy_api_ResourceQuotaStatus(in ResourceQuotaStatus, out *ResourceQuota in, out := in.Hard, &out.Hard *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.Hard = nil @@ -2466,11 +2485,11 @@ func DeepCopy_api_ResourceQuotaStatus(in ResourceQuotaStatus, out *ResourceQuota in, out := in.Used, &out.Used *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.Used = nil @@ -2483,11 +2502,11 @@ func DeepCopy_api_ResourceRequirements(in ResourceRequirements, out *ResourceReq in, out := in.Limits, &out.Limits *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.Limits = nil @@ -2496,11 +2515,11 @@ func DeepCopy_api_ResourceRequirements(in ResourceRequirements, out *ResourceReq in, out := in.Requests, &out.Requests *out = make(ResourceList) for key, val := range in { - if newVal, err := c.DeepCopy(val); err != nil { + newVal := new(resource.Quantity) + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err - } else { - (*out)[key] = newVal.(resource.Quantity) } + (*out)[key] = *newVal } } else { out.Requests = nil @@ -2517,7 +2536,7 @@ func DeepCopy_api_SELinuxOptions(in SELinuxOptions, out *SELinuxOptions, c *conv } func DeepCopy_api_Secret(in Secret, out *Secret, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -2549,10 +2568,10 @@ func DeepCopy_api_SecretKeySelector(in SecretKeySelector, out *SecretKeySelector } func DeepCopy_api_SecretList(in SecretList, out *SecretList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -2625,7 +2644,7 @@ func DeepCopy_api_SecurityContext(in SecurityContext, out *SecurityContext, c *c } func DeepCopy_api_SerializedReference(in SerializedReference, out *SerializedReference, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectReference(in.Reference, &out.Reference, c); err != nil { @@ -2635,7 +2654,7 @@ func DeepCopy_api_SerializedReference(in SerializedReference, out *SerializedRef } func DeepCopy_api_Service(in Service, out *Service, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -2651,7 +2670,7 @@ func DeepCopy_api_Service(in Service, out *Service, c *conversion.Cloner) error } func DeepCopy_api_ServiceAccount(in ServiceAccount, out *ServiceAccount, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if err := DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { @@ -2683,10 +2702,10 @@ func DeepCopy_api_ServiceAccount(in ServiceAccount, out *ServiceAccount, c *conv } func DeepCopy_api_ServiceAccountList(in ServiceAccountList, out *ServiceAccountList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -2704,10 +2723,10 @@ func DeepCopy_api_ServiceAccountList(in ServiceAccountList, out *ServiceAccountL } func DeepCopy_api_ServiceList(in ServiceList, out *ServiceList, c *conversion.Cloner) error { - if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { @@ -2728,13 +2747,21 @@ func DeepCopy_api_ServicePort(in ServicePort, out *ServicePort, c *conversion.Cl out.Name = in.Name out.Protocol = in.Protocol out.Port = in.Port - if err := DeepCopy_intstr_IntOrString(in.TargetPort, &out.TargetPort, c); err != nil { + if err := intstr.DeepCopy_intstr_IntOrString(in.TargetPort, &out.TargetPort, c); err != nil { return err } out.NodePort = in.NodePort return nil } +func DeepCopy_api_ServiceProxyOptions(in ServiceProxyOptions, out *ServiceProxyOptions, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + out.Path = in.Path + return nil +} + func DeepCopy_api_ServiceSpec(in ServiceSpec, out *ServiceSpec, c *conversion.Cloner) error { out.Type = in.Type if in.Ports != nil { @@ -2778,7 +2805,7 @@ func DeepCopy_api_ServiceStatus(in ServiceStatus, out *ServiceStatus, c *convers } func DeepCopy_api_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *conversion.Cloner) error { - if err := DeepCopy_intstr_IntOrString(in.Port, &out.Port, c); err != nil { + if err := intstr.DeepCopy_intstr_IntOrString(in.Port, &out.Port, c); err != nil { return err } return nil @@ -2973,69 +3000,3 @@ func DeepCopy_api_VolumeSource(in VolumeSource, out *VolumeSource, c *conversion } return nil } - -func DeepCopy_conversion_Meta(in conversion.Meta, out *conversion.Meta, c *conversion.Cloner) error { - out.SrcVersion = in.SrcVersion - out.DestVersion = in.DestVersion - if newVal, err := c.DeepCopy(in.KeyNameMapping); err != nil { - return err - } else { - out.KeyNameMapping = newVal.(conversion.FieldMappingFunc) - } - return nil -} - -func DeepCopy_intstr_IntOrString(in intstr.IntOrString, out *intstr.IntOrString, c *conversion.Cloner) error { - out.Type = in.Type - out.IntVal = in.IntVal - out.StrVal = in.StrVal - return nil -} - -func DeepCopy_sets_Empty(in sets.Empty, out *sets.Empty, c *conversion.Cloner) error { - return nil -} - -func DeepCopy_unversioned_GroupKind(in unversioned.GroupKind, out *unversioned.GroupKind, c *conversion.Cloner) error { - out.Group = in.Group - out.Kind = in.Kind - return nil -} - -func DeepCopy_unversioned_GroupResource(in unversioned.GroupResource, out *unversioned.GroupResource, c *conversion.Cloner) error { - out.Group = in.Group - out.Resource = in.Resource - return nil -} - -func DeepCopy_unversioned_GroupVersion(in unversioned.GroupVersion, out *unversioned.GroupVersion, c *conversion.Cloner) error { - out.Group = in.Group - out.Version = in.Version - return nil -} - -func DeepCopy_unversioned_GroupVersionKind(in unversioned.GroupVersionKind, out *unversioned.GroupVersionKind, c *conversion.Cloner) error { - out.Group = in.Group - out.Version = in.Version - out.Kind = in.Kind - return nil -} - -func DeepCopy_unversioned_GroupVersionResource(in unversioned.GroupVersionResource, out *unversioned.GroupVersionResource, c *conversion.Cloner) error { - out.Group = in.Group - out.Version = in.Version - out.Resource = in.Resource - return nil -} - -func DeepCopy_unversioned_ListMeta(in unversioned.ListMeta, out *unversioned.ListMeta, c *conversion.Cloner) error { - out.SelfLink = in.SelfLink - out.ResourceVersion = in.ResourceVersion - return nil -} - -func DeepCopy_unversioned_TypeMeta(in unversioned.TypeMeta, out *unversioned.TypeMeta, c *conversion.Cloner) error { - out.Kind = in.Kind - out.APIVersion = in.APIVersion - return nil -} diff --git a/vendor/k8s.io/kubernetes/pkg/api/endpoints/util.go b/vendor/k8s.io/kubernetes/pkg/api/endpoints/util.go index 91bc57166..7758434a1 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/endpoints/util.go +++ b/vendor/k8s.io/kubernetes/pkg/api/endpoints/util.go @@ -28,6 +28,16 @@ import ( hashutil "k8s.io/kubernetes/pkg/util/hash" ) +const ( + // Its value is the json representation of map[string(IP)][HostRecord] + // example: '{"10.245.1.6":{"HostName":"my-webserver"}}' + PodHostnamesAnnotation = "endpoints.beta.kubernetes.io/hostnames-map" +) + +type HostRecord struct { + HostName string +} + // RepackSubsets takes a slice of EndpointSubset objects, expands it to the full // representation, and then repacks that into the canonical layout. This // ensures that code which operates on these objects can rely on the common diff --git a/vendor/k8s.io/kubernetes/pkg/api/errors/errors.go b/vendor/k8s.io/kubernetes/pkg/api/errors/errors.go index a78b7bc24..345ad0e04 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/errors/errors.go +++ b/vendor/k8s.io/kubernetes/pkg/api/errors/errors.go @@ -163,7 +163,7 @@ func NewConflict(qualifiedResource unversioned.GroupResource, name string, err e Kind: qualifiedResource.Resource, Name: name, }, - Message: fmt.Sprintf("%s %q cannot be updated: %v", qualifiedResource.String(), name, err), + Message: fmt.Sprintf("Operation cannot be fulfilled on %s %q: %v", qualifiedResource.String(), name, err), }} } diff --git a/vendor/k8s.io/kubernetes/pkg/api/errors/etcd/doc.go b/vendor/k8s.io/kubernetes/pkg/api/errors/storage/doc.go similarity index 97% rename from vendor/k8s.io/kubernetes/pkg/api/errors/etcd/doc.go rename to vendor/k8s.io/kubernetes/pkg/api/errors/storage/doc.go index 8cc0a832e..a2a550526 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/errors/etcd/doc.go +++ b/vendor/k8s.io/kubernetes/pkg/api/errors/storage/doc.go @@ -15,4 +15,4 @@ limitations under the License. */ // Package etcd provides conversion of etcd errors to API errors. -package etcd +package storage diff --git a/vendor/k8s.io/kubernetes/pkg/api/errors/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/api/errors/storage/storage.go similarity index 79% rename from vendor/k8s.io/kubernetes/pkg/api/errors/etcd/etcd.go rename to vendor/k8s.io/kubernetes/pkg/api/errors/storage/storage.go index 3e09aebaa..2a22b4f17 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/errors/etcd/etcd.go +++ b/vendor/k8s.io/kubernetes/pkg/api/errors/storage/storage.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package etcd +package storage import ( "k8s.io/kubernetes/pkg/api/errors" @@ -69,6 +69,10 @@ func InterpretUpdateError(err error, qualifiedResource unversioned.GroupResource return errors.NewConflict(qualifiedResource, name, err) case storage.IsUnreachable(err): return errors.NewServerTimeout(qualifiedResource, "update", 2) // TODO: make configurable or handled at a higher level + case storage.IsNotFound(err): + return errors.NewNotFound(qualifiedResource, name) + case storage.IsInternalError(err): + return errors.NewInternalError(err) default: return err } @@ -82,6 +86,22 @@ func InterpretDeleteError(err error, qualifiedResource unversioned.GroupResource return errors.NewNotFound(qualifiedResource, name) case storage.IsUnreachable(err): return errors.NewServerTimeout(qualifiedResource, "delete", 2) // TODO: make configurable or handled at a higher level + case storage.IsTestFailed(err), storage.IsNodeExist(err): + return errors.NewConflict(qualifiedResource, name, err) + case storage.IsInternalError(err): + return errors.NewInternalError(err) + default: + return err + } +} + +// InterpretWatchError converts a generic error on a watch +// operation into the appropriate API error. +func InterpretWatchError(err error, resource unversioned.GroupResource, name string) error { + switch { + case storage.IsInvalidError(err): + invalidError, _ := err.(storage.InvalidError) + return errors.NewInvalid(unversioned.GroupKind{Group: resource.Group, Kind: resource.Resource}, name, invalidError.Errs) default: return err } diff --git a/vendor/k8s.io/kubernetes/pkg/api/field_constants.go b/vendor/k8s.io/kubernetes/pkg/api/field_constants.go new file mode 100644 index 000000000..94a825caf --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/api/field_constants.go @@ -0,0 +1,38 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 api + +// Field path constants that are specific to the internal API +// representation. +const ( + NodeUnschedulableField = "spec.unschedulable" + ObjectNameField = "metadata.name" + PodHostField = "spec.nodeName" + PodStatusField = "status.phase" + SecretTypeField = "type" + + EventReasonField = "reason" + EventSourceField = "source" + EventTypeField = "type" + EventInvolvedKindField = "involvedObject.kind" + EventInvolvedNamespaceField = "involvedObject.namespace" + EventInvolvedNameField = "involvedObject.name" + EventInvolvedUIDField = "involvedObject.uid" + EventInvolvedAPIVersionField = "involvedObject.apiVersion" + EventInvolvedResourceVersionField = "involvedObject.resourceVersion" + EventInvolvedFieldPathField = "involvedObject.fieldPath" +) diff --git a/vendor/k8s.io/kubernetes/pkg/api/helpers.go b/vendor/k8s.io/kubernetes/pkg/api/helpers.go index b373b9eee..8d4fadc53 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/helpers.go +++ b/vendor/k8s.io/kubernetes/pkg/api/helpers.go @@ -30,6 +30,7 @@ import ( "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/util/sets" "github.com/davecgh/go-spew/spew" @@ -79,15 +80,101 @@ var Semantic = conversion.EqualitiesOrDie( }, ) -var standardResources = sets.NewString( +var standardResourceQuotaScopes = sets.NewString( + string(ResourceQuotaScopeTerminating), + string(ResourceQuotaScopeNotTerminating), + string(ResourceQuotaScopeBestEffort), + string(ResourceQuotaScopeNotBestEffort), +) + +// IsStandardResourceQuotaScope returns true if the scope is a standard value +func IsStandardResourceQuotaScope(str string) bool { + return standardResourceQuotaScopes.Has(str) +} + +var podObjectCountQuotaResources = sets.NewString( + string(ResourcePods), +) + +var podComputeQuotaResources = sets.NewString( string(ResourceCPU), string(ResourceMemory), + string(ResourceLimitsCPU), + string(ResourceLimitsMemory), + string(ResourceRequestsCPU), + string(ResourceRequestsMemory), +) + +// IsResourceQuotaScopeValidForResource returns true if the resource applies to the specified scope +func IsResourceQuotaScopeValidForResource(scope ResourceQuotaScope, resource string) bool { + switch scope { + case ResourceQuotaScopeTerminating, ResourceQuotaScopeNotTerminating, ResourceQuotaScopeNotBestEffort: + return podObjectCountQuotaResources.Has(resource) || podComputeQuotaResources.Has(resource) + case ResourceQuotaScopeBestEffort: + return podObjectCountQuotaResources.Has(resource) + default: + return true + } +} + +var standardContainerResources = sets.NewString( + string(ResourceCPU), + string(ResourceMemory), +) + +// IsStandardContainerResourceName returns true if the container can make a resource request +// for the specified resource +func IsStandardContainerResourceName(str string) bool { + return standardContainerResources.Has(str) +} + +var standardLimitRangeTypes = sets.NewString( + string(LimitTypePod), + string(LimitTypeContainer), +) + +// IsStandardLimitRangeType returns true if the type is Pod or Container +func IsStandardLimitRangeType(str string) bool { + return standardLimitRangeTypes.Has(str) +} + +var standardQuotaResources = sets.NewString( + string(ResourceCPU), + string(ResourceMemory), + string(ResourceRequestsCPU), + string(ResourceRequestsMemory), + string(ResourceLimitsCPU), + string(ResourceLimitsMemory), string(ResourcePods), string(ResourceQuotas), string(ResourceServices), string(ResourceReplicationControllers), string(ResourceSecrets), string(ResourcePersistentVolumeClaims), + string(ResourceConfigMaps), + string(ResourceServicesNodePorts), +) + +// IsStandardQuotaResourceName returns true if the resource is known to +// the quota tracking system +func IsStandardQuotaResourceName(str string) bool { + return standardQuotaResources.Has(str) +} + +var standardResources = sets.NewString( + string(ResourceCPU), + string(ResourceMemory), + string(ResourceRequestsCPU), + string(ResourceRequestsMemory), + string(ResourceLimitsCPU), + string(ResourceLimitsMemory), + string(ResourcePods), + string(ResourceQuotas), + string(ResourceServices), + string(ResourceReplicationControllers), + string(ResourceSecrets), + string(ResourceConfigMaps), + string(ResourcePersistentVolumeClaims), string(ResourceStorage), ) @@ -102,7 +189,9 @@ var integerResources = sets.NewString( string(ResourceServices), string(ResourceReplicationControllers), string(ResourceSecrets), + string(ResourceConfigMaps), string(ResourcePersistentVolumeClaims), + string(ResourceServicesNodePorts), ) // IsIntegerResourceName returns true if the resource is measured in integer values @@ -118,6 +207,19 @@ func NewDeleteOptions(grace int64) *DeleteOptions { return &DeleteOptions{GracePeriodSeconds: &grace} } +// NewPreconditionDeleteOptions returns a DeleteOptions with a UID precondition set. +func NewPreconditionDeleteOptions(uid string) *DeleteOptions { + u := types.UID(uid) + p := Preconditions{UID: &u} + return &DeleteOptions{Preconditions: &p} +} + +// NewUIDPreconditions returns a Preconditions with UID set. +func NewUIDPreconditions(uid string) *Preconditions { + u := types.UID(uid) + return &Preconditions{UID: &u} +} + // this function aims to check if the service's ClusterIP is set or not // the objective is not to perform validation here func IsServiceIPSet(service *Service) bool { @@ -256,13 +358,13 @@ func containsAccessMode(modes []PersistentVolumeAccessMode, mode PersistentVolum // ParseRFC3339 parses an RFC3339 date in either RFC3339Nano or RFC3339 format. func ParseRFC3339(s string, nowFn func() unversioned.Time) (unversioned.Time, error) { if t, timeErr := time.Parse(time.RFC3339Nano, s); timeErr == nil { - return unversioned.Time{t}, nil + return unversioned.Time{Time: t}, nil } t, err := time.Parse(time.RFC3339, s) if err != nil { return unversioned.Time{}, err } - return unversioned.Time{t}, nil + return unversioned.Time{Time: t}, nil } // NodeSelectorRequirementsAsSelector converts the []NodeSelectorRequirement api type into a struct that implements diff --git a/vendor/k8s.io/kubernetes/pkg/api/install/install.go b/vendor/k8s.io/kubernetes/pkg/api/install/install.go index 14cf55519..58d121ba8 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/install/install.go +++ b/vendor/k8s.io/kubernetes/pkg/api/install/install.go @@ -108,6 +108,8 @@ func newRESTMapper(externalVersions []unversioned.GroupVersion) meta.RESTMapper "PodExecOptions", "PodAttachOptions", "PodProxyOptions", + "NodeProxyOptions", + "ServiceProxyOptions", "ThirdPartyResource", "ThirdPartyResourceData", "ThirdPartyResourceList") diff --git a/vendor/k8s.io/kubernetes/pkg/api/install/install_test.go b/vendor/k8s.io/kubernetes/pkg/api/install/install_test.go index 0b1615350..07c7f5d51 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/install/install_test.go +++ b/vendor/k8s.io/kubernetes/pkg/api/install/install_test.go @@ -80,7 +80,7 @@ func TestRESTMapper(t *testing.T) { rcGVK := gv.WithKind("ReplicationController") podTemplateGVK := gv.WithKind("PodTemplate") - if gvk, err := registered.GroupOrDie(internal.GroupName).RESTMapper.KindFor(internal.SchemeGroupVersion.WithResource("replicationcontrollers")); err != nil || gvk != rcGVK { + if gvk, err := registered.RESTMapper().KindFor(internal.SchemeGroupVersion.WithResource("replicationcontrollers")); err != nil || gvk != rcGVK { t.Errorf("unexpected version mapping: %v %v", gvk, err) } diff --git a/vendor/k8s.io/kubernetes/pkg/api/mapper.go b/vendor/k8s.io/kubernetes/pkg/api/mapper.go index 054f74d5e..0216771ee 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/mapper.go +++ b/vendor/k8s.io/kubernetes/pkg/api/mapper.go @@ -43,10 +43,10 @@ func NewDefaultRESTMapper(defaultGroupVersions []unversioned.GroupVersion, inter for _, gv := range defaultGroupVersions { for kind, oType := range Scheme.KnownTypes(gv) { gvk := gv.WithKind(kind) - // TODO: Remove import path prefix check. - // We check the import path prefix because we currently stuff both "api" and "extensions" objects + // TODO: Remove import path check. + // We check the import path because we currently stuff both "api" and "extensions" objects // into the same group within Scheme since Scheme has no notion of groups yet. - if !strings.HasPrefix(oType.PkgPath(), importPathPrefix) || ignoredKinds.Has(kind) { + if !strings.Contains(oType.PkgPath(), importPathPrefix) || ignoredKinds.Has(kind) { continue } scope := meta.RESTScopeNamespace diff --git a/vendor/k8s.io/kubernetes/pkg/api/meta.go b/vendor/k8s.io/kubernetes/pkg/api/meta.go index ec84c3c8a..d5590f49d 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/meta.go +++ b/vendor/k8s.io/kubernetes/pkg/api/meta.go @@ -39,6 +39,7 @@ func HasObjectMetaSystemFieldValues(meta *ObjectMeta) bool { // ObjectMetaFor returns a pointer to a provided object's ObjectMeta. // TODO: allow runtime.Unknown to extract this object +// TODO: Remove this function and use meta.Accessor() instead. func ObjectMetaFor(obj runtime.Object) (*ObjectMeta, error) { v, err := conversion.EnforcePtr(obj) if err != nil { @@ -64,18 +65,26 @@ func ListMetaFor(obj runtime.Object) (*unversioned.ListMeta, error) { // Namespace implements meta.Object for any object with an ObjectMeta typed field. Allows // fast, direct access to metadata fields for API objects. -func (meta *ObjectMeta) GetNamespace() string { return meta.Namespace } -func (meta *ObjectMeta) SetNamespace(namespace string) { meta.Namespace = namespace } -func (meta *ObjectMeta) GetName() string { return meta.Name } -func (meta *ObjectMeta) SetName(name string) { meta.Name = name } -func (meta *ObjectMeta) GetGenerateName() string { return meta.GenerateName } -func (meta *ObjectMeta) SetGenerateName(generateName string) { meta.GenerateName = generateName } -func (meta *ObjectMeta) GetUID() types.UID { return meta.UID } -func (meta *ObjectMeta) SetUID(uid types.UID) { meta.UID = uid } -func (meta *ObjectMeta) GetResourceVersion() string { return meta.ResourceVersion } -func (meta *ObjectMeta) SetResourceVersion(version string) { meta.ResourceVersion = version } -func (meta *ObjectMeta) GetSelfLink() string { return meta.SelfLink } -func (meta *ObjectMeta) SetSelfLink(selfLink string) { meta.SelfLink = selfLink } +func (meta *ObjectMeta) GetNamespace() string { return meta.Namespace } +func (meta *ObjectMeta) SetNamespace(namespace string) { meta.Namespace = namespace } +func (meta *ObjectMeta) GetName() string { return meta.Name } +func (meta *ObjectMeta) SetName(name string) { meta.Name = name } +func (meta *ObjectMeta) GetGenerateName() string { return meta.GenerateName } +func (meta *ObjectMeta) SetGenerateName(generateName string) { meta.GenerateName = generateName } +func (meta *ObjectMeta) GetUID() types.UID { return meta.UID } +func (meta *ObjectMeta) SetUID(uid types.UID) { meta.UID = uid } +func (meta *ObjectMeta) GetResourceVersion() string { return meta.ResourceVersion } +func (meta *ObjectMeta) SetResourceVersion(version string) { meta.ResourceVersion = version } +func (meta *ObjectMeta) GetSelfLink() string { return meta.SelfLink } +func (meta *ObjectMeta) SetSelfLink(selfLink string) { meta.SelfLink = selfLink } +func (meta *ObjectMeta) GetCreationTimestamp() unversioned.Time { return meta.CreationTimestamp } +func (meta *ObjectMeta) SetCreationTimestamp(creationTimestamp unversioned.Time) { + meta.CreationTimestamp = creationTimestamp +} +func (meta *ObjectMeta) GetDeletionTimestamp() *unversioned.Time { return meta.DeletionTimestamp } +func (meta *ObjectMeta) SetDeletionTimestamp(deletionTimestamp *unversioned.Time) { + meta.DeletionTimestamp = deletionTimestamp +} func (meta *ObjectMeta) GetLabels() map[string]string { return meta.Labels } func (meta *ObjectMeta) SetLabels(labels map[string]string) { meta.Labels = labels } func (meta *ObjectMeta) GetAnnotations() map[string]string { return meta.Annotations } diff --git a/vendor/k8s.io/kubernetes/pkg/api/meta/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/api/meta/deep_copy_generated.go new file mode 100644 index 000000000..8fbea2823 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/api/meta/deep_copy_generated.go @@ -0,0 +1,154 @@ +// +build !ignore_autogenerated + +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package meta + +import ( + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + conversion "k8s.io/kubernetes/pkg/conversion" + runtime "k8s.io/kubernetes/pkg/runtime" +) + +func DeepCopy_meta_DefaultRESTMapper(in DefaultRESTMapper, out *DefaultRESTMapper, c *conversion.Cloner) error { + if in.defaultGroupVersions != nil { + in, out := in.defaultGroupVersions, &out.defaultGroupVersions + *out = make([]unversioned.GroupVersion, len(in)) + for i := range in { + if err := unversioned.DeepCopy_unversioned_GroupVersion(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.defaultGroupVersions = nil + } + if in.resourceToKind != nil { + in, out := in.resourceToKind, &out.resourceToKind + *out = make(map[unversioned.GroupVersionResource]unversioned.GroupVersionKind) + for range in { + // FIXME: Copying unassignable keys unsupported unversioned.GroupVersionResource + } + } else { + out.resourceToKind = nil + } + if in.kindToPluralResource != nil { + in, out := in.kindToPluralResource, &out.kindToPluralResource + *out = make(map[unversioned.GroupVersionKind]unversioned.GroupVersionResource) + for range in { + // FIXME: Copying unassignable keys unsupported unversioned.GroupVersionKind + } + } else { + out.kindToPluralResource = nil + } + if in.kindToScope != nil { + in, out := in.kindToScope, &out.kindToScope + *out = make(map[unversioned.GroupVersionKind]RESTScope) + for range in { + // FIXME: Copying unassignable keys unsupported unversioned.GroupVersionKind + } + } else { + out.kindToScope = nil + } + if in.singularToPlural != nil { + in, out := in.singularToPlural, &out.singularToPlural + *out = make(map[unversioned.GroupVersionResource]unversioned.GroupVersionResource) + for range in { + // FIXME: Copying unassignable keys unsupported unversioned.GroupVersionResource + } + } else { + out.singularToPlural = nil + } + if in.pluralToSingular != nil { + in, out := in.pluralToSingular, &out.pluralToSingular + *out = make(map[unversioned.GroupVersionResource]unversioned.GroupVersionResource) + for range in { + // FIXME: Copying unassignable keys unsupported unversioned.GroupVersionResource + } + } else { + out.pluralToSingular = nil + } + if in.interfacesFunc == nil { + out.interfacesFunc = nil + } else if newVal, err := c.DeepCopy(in.interfacesFunc); err != nil { + return err + } else { + out.interfacesFunc = newVal.(VersionInterfacesFunc) + } + if in.aliasToResource != nil { + in, out := in.aliasToResource, &out.aliasToResource + *out = make(map[string][]string) + for key, val := range in { + if newVal, err := c.DeepCopy(val); err != nil { + return err + } else { + (*out)[key] = newVal.([]string) + } + } + } else { + out.aliasToResource = nil + } + return nil +} + +func DeepCopy_meta_RESTMapping(in RESTMapping, out *RESTMapping, c *conversion.Cloner) error { + out.Resource = in.Resource + if err := unversioned.DeepCopy_unversioned_GroupVersionKind(in.GroupVersionKind, &out.GroupVersionKind, c); err != nil { + return err + } + if in.Scope == nil { + out.Scope = nil + } else if newVal, err := c.DeepCopy(in.Scope); err != nil { + return err + } else { + out.Scope = newVal.(RESTScope) + } + if in.ObjectConvertor == nil { + out.ObjectConvertor = nil + } else if newVal, err := c.DeepCopy(in.ObjectConvertor); err != nil { + return err + } else { + out.ObjectConvertor = newVal.(runtime.ObjectConvertor) + } + if in.MetadataAccessor == nil { + out.MetadataAccessor = nil + } else if newVal, err := c.DeepCopy(in.MetadataAccessor); err != nil { + return err + } else { + out.MetadataAccessor = newVal.(MetadataAccessor) + } + return nil +} + +func DeepCopy_meta_VersionInterfaces(in VersionInterfaces, out *VersionInterfaces, c *conversion.Cloner) error { + if in.ObjectConvertor == nil { + out.ObjectConvertor = nil + } else if newVal, err := c.DeepCopy(in.ObjectConvertor); err != nil { + return err + } else { + out.ObjectConvertor = newVal.(runtime.ObjectConvertor) + } + if in.MetadataAccessor == nil { + out.MetadataAccessor = nil + } else if newVal, err := c.DeepCopy(in.MetadataAccessor); err != nil { + return err + } else { + out.MetadataAccessor = newVal.(MetadataAccessor) + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/api/meta/help.go b/vendor/k8s.io/kubernetes/pkg/api/meta/help.go index 7d1570bc2..cdc07930f 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/meta/help.go +++ b/vendor/k8s.io/kubernetes/pkg/api/meta/help.go @@ -76,8 +76,9 @@ func ExtractList(obj runtime.Object) ([]runtime.Object, error) { switch { case item.Object != nil: list[i] = item.Object - case item.RawJSON != nil: - list[i] = &runtime.Unknown{RawJSON: item.RawJSON} + case item.Raw != nil: + // TODO: Set ContentEncoding and ContentType correctly. + list[i] = &runtime.Unknown{Raw: item.Raw} default: list[i] = nil } diff --git a/vendor/k8s.io/kubernetes/pkg/api/meta/help_test.go b/vendor/k8s.io/kubernetes/pkg/api/meta/help_test.go index 435784fdc..85ba2cbf6 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/meta/help_test.go +++ b/vendor/k8s.io/kubernetes/pkg/api/meta/help_test.go @@ -25,7 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" "github.com/google/gofuzz" ) @@ -114,8 +114,8 @@ func TestExtractListGeneric(t *testing.T) { func TestExtractListGenericV1(t *testing.T) { pl := &v1.List{ Items: []runtime.RawExtension{ - {RawJSON: []byte("foo")}, - {RawJSON: []byte("bar")}, + {Raw: []byte("foo")}, + {Raw: []byte("bar")}, {Object: &v1.Pod{ObjectMeta: v1.ObjectMeta{Name: "other"}}}, }, } @@ -224,7 +224,7 @@ func TestSetListToRuntimeObjectArray(t *testing.T) { } for i := range list { if e, a := list[i], pl.Items[i]; e != a { - t.Fatalf("%d: unmatched: %s", i, util.ObjectDiff(e, a)) + t.Fatalf("%d: unmatched: %s", i, diff.ObjectDiff(e, a)) } } } diff --git a/vendor/k8s.io/kubernetes/pkg/api/meta/interfaces.go b/vendor/k8s.io/kubernetes/pkg/api/meta/interfaces.go index f1402e7ad..8001d57e8 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/meta/interfaces.go +++ b/vendor/k8s.io/kubernetes/pkg/api/meta/interfaces.go @@ -49,6 +49,10 @@ type Object interface { SetResourceVersion(version string) GetSelfLink() string SetSelfLink(selfLink string) + GetCreationTimestamp() unversioned.Time + SetCreationTimestamp(timestamp unversioned.Time) + GetDeletionTimestamp() *unversioned.Time + SetDeletionTimestamp(timestamp *unversioned.Time) GetLabels() map[string]string SetLabels(labels map[string]string) GetAnnotations() map[string]string diff --git a/vendor/k8s.io/kubernetes/pkg/api/meta/meta.go b/vendor/k8s.io/kubernetes/pkg/api/meta/meta.go index bf11abd18..8a08ded26 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/meta/meta.go +++ b/vendor/k8s.io/kubernetes/pkg/api/meta/meta.go @@ -24,6 +24,8 @@ import ( "k8s.io/kubernetes/pkg/conversion" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/types" + + "github.com/golang/glog" ) // Accessor takes an arbitrary object pointer and returns meta.Interface. @@ -40,6 +42,8 @@ func Accessor(obj interface{}) (Object, error) { if oi, ok := obj.(Object); ok { return oi, nil } + + glog.V(4).Infof("Calling Accessor on non-internal object: %v", reflect.TypeOf(obj)) // legacy path for objects that do not implement Object and ObjectMetaAccessor via // reflection - very slow code path. v, err := conversion.EnforcePtr(obj) @@ -321,16 +325,18 @@ func (resourceAccessor) SetResourceVersion(obj runtime.Object, version string) e // genericAccessor contains pointers to strings that can modify an arbitrary // struct and implements the Accessor interface. type genericAccessor struct { - namespace *string - name *string - generateName *string - uid *types.UID - apiVersion *string - kind *string - resourceVersion *string - selfLink *string - labels *map[string]string - annotations *map[string]string + namespace *string + name *string + generateName *string + uid *types.UID + apiVersion *string + kind *string + resourceVersion *string + selfLink *string + creationTimestamp *unversioned.Time + deletionTimestamp **unversioned.Time + labels *map[string]string + annotations *map[string]string } func (a genericAccessor) GetNamespace() string { @@ -421,6 +427,22 @@ func (a genericAccessor) SetSelfLink(selfLink string) { *a.selfLink = selfLink } +func (a genericAccessor) GetCreationTimestamp() unversioned.Time { + return *a.creationTimestamp +} + +func (a genericAccessor) SetCreationTimestamp(timestamp unversioned.Time) { + *a.creationTimestamp = timestamp +} + +func (a genericAccessor) GetDeletionTimestamp() *unversioned.Time { + return *a.deletionTimestamp +} + +func (a genericAccessor) SetDeletionTimestamp(timestamp *unversioned.Time) { + *a.deletionTimestamp = timestamp +} + func (a genericAccessor) GetLabels() map[string]string { if a.labels == nil { return nil diff --git a/vendor/k8s.io/kubernetes/pkg/api/meta/multirestmapper.go b/vendor/k8s.io/kubernetes/pkg/api/meta/multirestmapper.go index 4d284e62a..b720f8fa2 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/meta/multirestmapper.go +++ b/vendor/k8s.io/kubernetes/pkg/api/meta/multirestmapper.go @@ -21,6 +21,8 @@ import ( "strings" "k8s.io/kubernetes/pkg/api/unversioned" + utilerrors "k8s.io/kubernetes/pkg/util/errors" + "k8s.io/kubernetes/pkg/util/sets" ) // MultiRESTMapper is a wrapper for multiple RESTMappers. @@ -50,72 +52,149 @@ func (m MultiRESTMapper) ResourceSingularizer(resource string) (singular string, } func (m MultiRESTMapper) ResourcesFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) { + allGVRs := []unversioned.GroupVersionResource{} for _, t := range m { gvrs, err := t.ResourcesFor(resource) // ignore "no match" errors, but any other error percolates back up - if !IsNoResourceMatchError(err) { - return gvrs, err + if IsNoResourceMatchError(err) { + continue + } + if err != nil { + return nil, err + } + + // walk the existing values to de-dup + for _, curr := range gvrs { + found := false + for _, existing := range allGVRs { + if curr == existing { + found = true + break + } + } + + if !found { + allGVRs = append(allGVRs, curr) + } } } - return nil, &NoResourceMatchError{PartialResource: resource} + + if len(allGVRs) == 0 { + return nil, &NoResourceMatchError{PartialResource: resource} + } + + return allGVRs, nil } -// KindsFor provides the Kind mappings for the REST resources. This implementation supports multiple REST schemas and returns -// the first match. func (m MultiRESTMapper) KindsFor(resource unversioned.GroupVersionResource) (gvk []unversioned.GroupVersionKind, err error) { + allGVKs := []unversioned.GroupVersionKind{} for _, t := range m { gvks, err := t.KindsFor(resource) // ignore "no match" errors, but any other error percolates back up - if !IsNoResourceMatchError(err) { - return gvks, err + if IsNoResourceMatchError(err) { + continue + } + if err != nil { + return nil, err + } + + // walk the existing values to de-dup + for _, curr := range gvks { + found := false + for _, existing := range allGVKs { + if curr == existing { + found = true + break + } + } + + if !found { + allGVKs = append(allGVKs, curr) + } } } - return nil, &NoResourceMatchError{PartialResource: resource} + + if len(allGVKs) == 0 { + return nil, &NoResourceMatchError{PartialResource: resource} + } + + return allGVKs, nil } func (m MultiRESTMapper) ResourceFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) { - for _, t := range m { - gvr, err := t.ResourceFor(resource) - // ignore "no match" errors, but any other error percolates back up - if !IsNoResourceMatchError(err) { - return gvr, err - } + resources, err := m.ResourcesFor(resource) + if err != nil { + return unversioned.GroupVersionResource{}, err } - return unversioned.GroupVersionResource{}, &NoResourceMatchError{PartialResource: resource} + if len(resources) == 1 { + return resources[0], nil + } + + return unversioned.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: resource, MatchingResources: resources} } -// KindsFor provides the Kind mapping for the REST resources. This implementation supports multiple REST schemas and returns -// the first match. func (m MultiRESTMapper) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) { - for _, t := range m { - gvk, err := t.KindFor(resource) - // ignore "no match" errors, but any other error percolates back up - if !IsNoResourceMatchError(err) { - return gvk, err - } + kinds, err := m.KindsFor(resource) + if err != nil { + return unversioned.GroupVersionKind{}, err } - return unversioned.GroupVersionKind{}, &NoResourceMatchError{PartialResource: resource} + if len(kinds) == 1 { + return kinds[0], nil + } + + return unversioned.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: resource, MatchingKinds: kinds} } // RESTMapping provides the REST mapping for the resource based on the // kind and version. This implementation supports multiple REST schemas and // return the first match. -func (m MultiRESTMapper) RESTMapping(gk unversioned.GroupKind, versions ...string) (mapping *RESTMapping, err error) { +func (m MultiRESTMapper) RESTMapping(gk unversioned.GroupKind, versions ...string) (*RESTMapping, error) { + allMappings := []*RESTMapping{} + errors := []error{} + for _, t := range m { - mapping, err = t.RESTMapping(gk, versions...) - if err == nil { - return + currMapping, err := t.RESTMapping(gk, versions...) + // ignore "no match" errors, but any other error percolates back up + if IsNoResourceMatchError(err) { + continue } + if err != nil { + errors = append(errors, err) + continue + } + + allMappings = append(allMappings, currMapping) } - return + + // if we got exactly one mapping, then use it even if other requested failed + if len(allMappings) == 1 { + return allMappings[0], nil + } + if len(allMappings) > 1 { + return nil, fmt.Errorf("multiple matches found for %v in %v", gk, versions) + } + if len(errors) > 0 { + return nil, utilerrors.NewAggregate(errors) + } + return nil, fmt.Errorf("no match found for %v in %v", gk, versions) } // AliasesForResource finds the first alias response for the provided mappers. -func (m MultiRESTMapper) AliasesForResource(alias string) (aliases []string, ok bool) { +func (m MultiRESTMapper) AliasesForResource(alias string) ([]string, bool) { + seenAliases := sets.NewString() + allAliases := []string{} + handled := false + for _, t := range m { - if aliases, ok = t.AliasesForResource(alias); ok { - return + if currAliases, currOk := t.AliasesForResource(alias); currOk { + for _, currAlias := range currAliases { + if !seenAliases.Has(currAlias) { + allAliases = append(allAliases, currAlias) + seenAliases.Insert(currAlias) + } + } + handled = true } } - return nil, false + return allAliases, handled } diff --git a/vendor/k8s.io/kubernetes/pkg/api/meta/multirestmapper_test.go b/vendor/k8s.io/kubernetes/pkg/api/meta/multirestmapper_test.go index c4d543115..1b3685e85 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/meta/multirestmapper_test.go +++ b/vendor/k8s.io/kubernetes/pkg/api/meta/multirestmapper_test.go @@ -24,7 +24,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" ) -func TestMultiRESTMapperResourceForErrorHandling(t *testing.T) { +func TestMultiRESTMapperResourceFor(t *testing.T) { tcs := []struct { name string @@ -49,7 +49,7 @@ func TestMultiRESTMapperResourceForErrorHandling(t *testing.T) { }, { name: "accept first failure", - mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{resourceFor: unversioned.GroupVersionResource{Resource: "unused"}}}, + mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{resourcesFor: []unversioned.GroupVersionResource{{Resource: "unused"}}}}, input: unversioned.GroupVersionResource{Resource: "foo"}, result: unversioned.GroupVersionResource{}, err: errors.New("fail on this"), @@ -61,13 +61,19 @@ func TestMultiRESTMapperResourceForErrorHandling(t *testing.T) { if e, a := tc.result, actualResult; e != a { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } - if e, a := tc.err.Error(), actualErr.Error(); e != a { - t.Errorf("%s: expected %v, got %v", tc.name, e, a) + switch { + case tc.err == nil && actualErr == nil: + case tc.err == nil: + t.Errorf("%s: unexpected error: %v", tc.name, actualErr) + case actualErr == nil: + t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) + case tc.err.Error() != actualErr.Error(): + t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) } } } -func TestMultiRESTMapperResourcesForErrorHandling(t *testing.T) { +func TestMultiRESTMapperResourcesFor(t *testing.T) { tcs := []struct { name string @@ -97,6 +103,24 @@ func TestMultiRESTMapperResourcesForErrorHandling(t *testing.T) { result: nil, err: errors.New("fail on this"), }, + { + name: "union and dedup", + mapper: MultiRESTMapper{ + fixedRESTMapper{resourcesFor: []unversioned.GroupVersionResource{{Resource: "dupe"}, {Resource: "first"}}}, + fixedRESTMapper{resourcesFor: []unversioned.GroupVersionResource{{Resource: "dupe"}, {Resource: "second"}}}, + }, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: []unversioned.GroupVersionResource{{Resource: "dupe"}, {Resource: "first"}, {Resource: "second"}}, + }, + { + name: "skip not and continue", + mapper: MultiRESTMapper{ + fixedRESTMapper{err: &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "IGNORE_THIS"}}}, + fixedRESTMapper{resourcesFor: []unversioned.GroupVersionResource{{Resource: "first"}, {Resource: "second"}}}, + }, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: []unversioned.GroupVersionResource{{Resource: "first"}, {Resource: "second"}}, + }, } for _, tc := range tcs { @@ -104,13 +128,19 @@ func TestMultiRESTMapperResourcesForErrorHandling(t *testing.T) { if e, a := tc.result, actualResult; !reflect.DeepEqual(e, a) { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } - if e, a := tc.err.Error(), actualErr.Error(); e != a { - t.Errorf("%s: expected %v, got %v", tc.name, e, a) + switch { + case tc.err == nil && actualErr == nil: + case tc.err == nil: + t.Errorf("%s: unexpected error: %v", tc.name, actualErr) + case actualErr == nil: + t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) + case tc.err.Error() != actualErr.Error(): + t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) } } } -func TestMultiRESTMapperKindsForErrorHandling(t *testing.T) { +func TestMultiRESTMapperKindsFor(t *testing.T) { tcs := []struct { name string @@ -140,6 +170,24 @@ func TestMultiRESTMapperKindsForErrorHandling(t *testing.T) { result: nil, err: errors.New("fail on this"), }, + { + name: "union and dedup", + mapper: MultiRESTMapper{ + fixedRESTMapper{kindsFor: []unversioned.GroupVersionKind{{Kind: "dupe"}, {Kind: "first"}}}, + fixedRESTMapper{kindsFor: []unversioned.GroupVersionKind{{Kind: "dupe"}, {Kind: "second"}}}, + }, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: []unversioned.GroupVersionKind{{Kind: "dupe"}, {Kind: "first"}, {Kind: "second"}}, + }, + { + name: "skip not and continue", + mapper: MultiRESTMapper{ + fixedRESTMapper{err: &NoResourceMatchError{PartialResource: unversioned.GroupVersionResource{Resource: "IGNORE_THIS"}}}, + fixedRESTMapper{kindsFor: []unversioned.GroupVersionKind{{Kind: "first"}, {Kind: "second"}}}, + }, + input: unversioned.GroupVersionResource{Resource: "foo"}, + result: []unversioned.GroupVersionKind{{Kind: "first"}, {Kind: "second"}}, + }, } for _, tc := range tcs { @@ -147,13 +195,19 @@ func TestMultiRESTMapperKindsForErrorHandling(t *testing.T) { if e, a := tc.result, actualResult; !reflect.DeepEqual(e, a) { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } - if e, a := tc.err.Error(), actualErr.Error(); e != a { - t.Errorf("%s: expected %v, got %v", tc.name, e, a) + switch { + case tc.err == nil && actualErr == nil: + case tc.err == nil: + t.Errorf("%s: unexpected error: %v", tc.name, actualErr) + case actualErr == nil: + t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) + case tc.err.Error() != actualErr.Error(): + t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) } } } -func TestMultiRESTMapperKindForErrorHandling(t *testing.T) { +func TestMultiRESTMapperKindFor(t *testing.T) { tcs := []struct { name string @@ -178,7 +232,7 @@ func TestMultiRESTMapperKindForErrorHandling(t *testing.T) { }, { name: "accept first failure", - mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{kindFor: unversioned.GroupVersionKind{Kind: "unused"}}}, + mapper: MultiRESTMapper{fixedRESTMapper{err: errors.New("fail on this")}, fixedRESTMapper{kindsFor: []unversioned.GroupVersionKind{{Kind: "unused"}}}}, input: unversioned.GroupVersionResource{Resource: "foo"}, result: unversioned.GroupVersionKind{}, err: errors.New("fail on this"), @@ -190,8 +244,14 @@ func TestMultiRESTMapperKindForErrorHandling(t *testing.T) { if e, a := tc.result, actualResult; e != a { t.Errorf("%s: expected %v, got %v", tc.name, e, a) } - if e, a := tc.err.Error(), actualErr.Error(); e != a { - t.Errorf("%s: expected %v, got %v", tc.name, e, a) + switch { + case tc.err == nil && actualErr == nil: + case tc.err == nil: + t.Errorf("%s: unexpected error: %v", tc.name, actualErr) + case actualErr == nil: + t.Errorf("%s: expected error: %v got nil", tc.name, tc.err) + case tc.err.Error() != actualErr.Error(): + t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) } } } diff --git a/vendor/k8s.io/kubernetes/pkg/api/meta/priority.go b/vendor/k8s.io/kubernetes/pkg/api/meta/priority.go new file mode 100644 index 000000000..24f38f78f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/api/meta/priority.go @@ -0,0 +1,173 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package meta + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api/unversioned" +) + +const ( + AnyGroup = "*" + AnyVersion = "*" + AnyResource = "*" + AnyKind = "*" +) + +// PriorityRESTMapper is a wrapper for automatically choosing a particular Resource or Kind +// when multiple matches are possible +type PriorityRESTMapper struct { + // Delegate is the RESTMapper to use to locate all the Kind and Resource matches + Delegate RESTMapper + + // ResourcePriority is a list of priority patterns to apply to matching resources. + // The list of all matching resources is narrowed based on the patterns until only one remains. + // A pattern with no matches is skipped. A pattern with more than one match uses its + // matches as the list to continue matching against. + ResourcePriority []unversioned.GroupVersionResource + + // KindPriority is a list of priority patterns to apply to matching kinds. + // The list of all matching kinds is narrowed based on the patterns until only one remains. + // A pattern with no matches is skipped. A pattern with more than one match uses its + // matches as the list to continue matching against. + KindPriority []unversioned.GroupVersionKind +} + +func (m PriorityRESTMapper) String() string { + return fmt.Sprintf("PriorityRESTMapper{\n\t%v\n\t%v\n\t%v\n}", m.ResourcePriority, m.KindPriority, m.Delegate) +} + +// ResourceFor finds all resources, then passes them through the ResourcePriority patterns to find a single matching hit. +func (m PriorityRESTMapper) ResourceFor(partiallySpecifiedResource unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) { + originalGVRs, err := m.Delegate.ResourcesFor(partiallySpecifiedResource) + if err != nil { + return unversioned.GroupVersionResource{}, err + } + if len(originalGVRs) == 1 { + return originalGVRs[0], nil + } + + remainingGVRs := append([]unversioned.GroupVersionResource{}, originalGVRs...) + for _, pattern := range m.ResourcePriority { + matchedGVRs := []unversioned.GroupVersionResource{} + for _, gvr := range remainingGVRs { + if resourceMatches(pattern, gvr) { + matchedGVRs = append(matchedGVRs, gvr) + } + } + + switch len(matchedGVRs) { + case 0: + // if you have no matches, then nothing matched this pattern just move to the next + continue + case 1: + // one match, return + return matchedGVRs[0], nil + default: + // more than one match, use the matched hits as the list moving to the next pattern. + // this way you can have a series of selection criteria + remainingGVRs = matchedGVRs + } + } + + return unversioned.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: partiallySpecifiedResource, MatchingResources: originalGVRs} +} + +// KindFor finds all kinds, then passes them through the KindPriority patterns to find a single matching hit. +func (m PriorityRESTMapper) KindFor(partiallySpecifiedResource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) { + originalGVKs, err := m.Delegate.KindsFor(partiallySpecifiedResource) + if err != nil { + return unversioned.GroupVersionKind{}, err + } + if len(originalGVKs) == 1 { + return originalGVKs[0], nil + } + + remainingGVKs := append([]unversioned.GroupVersionKind{}, originalGVKs...) + for _, pattern := range m.KindPriority { + matchedGVKs := []unversioned.GroupVersionKind{} + for _, gvr := range remainingGVKs { + if kindMatches(pattern, gvr) { + matchedGVKs = append(matchedGVKs, gvr) + } + } + + switch len(matchedGVKs) { + case 0: + // if you have no matches, then nothing matched this pattern just move to the next + continue + case 1: + // one match, return + return matchedGVKs[0], nil + default: + // more than one match, use the matched hits as the list moving to the next pattern. + // this way you can have a series of selection criteria + remainingGVKs = matchedGVKs + } + } + + return unversioned.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: partiallySpecifiedResource, MatchingKinds: originalGVKs} +} + +func resourceMatches(pattern unversioned.GroupVersionResource, resource unversioned.GroupVersionResource) bool { + if pattern.Group != AnyGroup && pattern.Group != resource.Group { + return false + } + if pattern.Version != AnyVersion && pattern.Version != resource.Version { + return false + } + if pattern.Resource != AnyResource && pattern.Resource != resource.Resource { + return false + } + + return true +} + +func kindMatches(pattern unversioned.GroupVersionKind, kind unversioned.GroupVersionKind) bool { + if pattern.Group != AnyGroup && pattern.Group != kind.Group { + return false + } + if pattern.Version != AnyVersion && pattern.Version != kind.Version { + return false + } + if pattern.Kind != AnyKind && pattern.Kind != kind.Kind { + return false + } + + return true +} + +func (m PriorityRESTMapper) RESTMapping(gk unversioned.GroupKind, versions ...string) (mapping *RESTMapping, err error) { + return m.Delegate.RESTMapping(gk, versions...) +} + +func (m PriorityRESTMapper) AliasesForResource(alias string) (aliases []string, ok bool) { + return m.Delegate.AliasesForResource(alias) +} + +func (m PriorityRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { + return m.Delegate.ResourceSingularizer(resource) +} + +func (m PriorityRESTMapper) ResourcesFor(partiallySpecifiedResource unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) { + return m.Delegate.ResourcesFor(partiallySpecifiedResource) +} + +func (m PriorityRESTMapper) KindsFor(partiallySpecifiedResource unversioned.GroupVersionResource) (gvk []unversioned.GroupVersionKind, err error) { + return m.Delegate.KindsFor(partiallySpecifiedResource) +} diff --git a/vendor/k8s.io/kubernetes/pkg/api/meta/priority_test.go b/vendor/k8s.io/kubernetes/pkg/api/meta/priority_test.go new file mode 100644 index 000000000..ea2d24b37 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/api/meta/priority_test.go @@ -0,0 +1,206 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package meta + +import ( + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api/unversioned" +) + +func TestPriorityRESTMapperResourceForErrorHandling(t *testing.T) { + tcs := []struct { + name string + + delegate RESTMapper + resourcePatterns []unversioned.GroupVersionResource + result unversioned.GroupVersionResource + err string + }{ + { + name: "single hit", + delegate: fixedRESTMapper{resourcesFor: []unversioned.GroupVersionResource{{Resource: "single-hit"}}}, + result: unversioned.GroupVersionResource{Resource: "single-hit"}, + }, + { + name: "ambiguous match", + delegate: fixedRESTMapper{resourcesFor: []unversioned.GroupVersionResource{ + {Group: "one", Version: "a", Resource: "first"}, + {Group: "two", Version: "b", Resource: "second"}, + }}, + err: "matches multiple resources", + }, + { + name: "group selection", + delegate: fixedRESTMapper{resourcesFor: []unversioned.GroupVersionResource{ + {Group: "one", Version: "a", Resource: "first"}, + {Group: "two", Version: "b", Resource: "second"}, + }}, + resourcePatterns: []unversioned.GroupVersionResource{ + {Group: "one", Version: AnyVersion, Resource: AnyResource}, + }, + result: unversioned.GroupVersionResource{Group: "one", Version: "a", Resource: "first"}, + }, + { + name: "empty match continues", + delegate: fixedRESTMapper{resourcesFor: []unversioned.GroupVersionResource{ + {Group: "one", Version: "a", Resource: "first"}, + {Group: "two", Version: "b", Resource: "second"}, + }}, + resourcePatterns: []unversioned.GroupVersionResource{ + {Group: "fail", Version: AnyVersion, Resource: AnyResource}, + {Group: "one", Version: AnyVersion, Resource: AnyResource}, + }, + result: unversioned.GroupVersionResource{Group: "one", Version: "a", Resource: "first"}, + }, + { + name: "group followed by version selection", + delegate: fixedRESTMapper{resourcesFor: []unversioned.GroupVersionResource{ + {Group: "one", Version: "a", Resource: "first"}, + {Group: "two", Version: "b", Resource: "second"}, + {Group: "one", Version: "c", Resource: "third"}, + }}, + resourcePatterns: []unversioned.GroupVersionResource{ + {Group: "one", Version: AnyVersion, Resource: AnyResource}, + {Group: AnyGroup, Version: "a", Resource: AnyResource}, + }, + result: unversioned.GroupVersionResource{Group: "one", Version: "a", Resource: "first"}, + }, + { + name: "resource selection", + delegate: fixedRESTMapper{resourcesFor: []unversioned.GroupVersionResource{ + {Group: "one", Version: "a", Resource: "first"}, + {Group: "one", Version: "a", Resource: "second"}, + }}, + resourcePatterns: []unversioned.GroupVersionResource{ + {Group: AnyGroup, Version: AnyVersion, Resource: "second"}, + }, + result: unversioned.GroupVersionResource{Group: "one", Version: "a", Resource: "second"}, + }, + } + + for _, tc := range tcs { + mapper := PriorityRESTMapper{Delegate: tc.delegate, ResourcePriority: tc.resourcePatterns} + + actualResult, actualErr := mapper.ResourceFor(unversioned.GroupVersionResource{}) + if e, a := tc.result, actualResult; e != a { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + if len(tc.err) == 0 && actualErr == nil { + continue + } + if len(tc.err) > 0 && actualErr == nil { + t.Errorf("%s: missing expected err: %v", tc.name, tc.err) + continue + } + if !strings.Contains(actualErr.Error(), tc.err) { + t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) + } + } +} + +func TestPriorityRESTMapperKindForErrorHandling(t *testing.T) { + tcs := []struct { + name string + + delegate RESTMapper + kindPatterns []unversioned.GroupVersionKind + result unversioned.GroupVersionKind + err string + }{ + { + name: "single hit", + delegate: fixedRESTMapper{kindsFor: []unversioned.GroupVersionKind{{Kind: "single-hit"}}}, + result: unversioned.GroupVersionKind{Kind: "single-hit"}, + }, + { + name: "ambiguous match", + delegate: fixedRESTMapper{kindsFor: []unversioned.GroupVersionKind{ + {Group: "one", Version: "a", Kind: "first"}, + {Group: "two", Version: "b", Kind: "second"}, + }}, + err: "matches multiple kinds", + }, + { + name: "group selection", + delegate: fixedRESTMapper{kindsFor: []unversioned.GroupVersionKind{ + {Group: "one", Version: "a", Kind: "first"}, + {Group: "two", Version: "b", Kind: "second"}, + }}, + kindPatterns: []unversioned.GroupVersionKind{ + {Group: "one", Version: AnyVersion, Kind: AnyKind}, + }, + result: unversioned.GroupVersionKind{Group: "one", Version: "a", Kind: "first"}, + }, + { + name: "empty match continues", + delegate: fixedRESTMapper{kindsFor: []unversioned.GroupVersionKind{ + {Group: "one", Version: "a", Kind: "first"}, + {Group: "two", Version: "b", Kind: "second"}, + }}, + kindPatterns: []unversioned.GroupVersionKind{ + {Group: "fail", Version: AnyVersion, Kind: AnyKind}, + {Group: "one", Version: AnyVersion, Kind: AnyKind}, + }, + result: unversioned.GroupVersionKind{Group: "one", Version: "a", Kind: "first"}, + }, + { + name: "group followed by version selection", + delegate: fixedRESTMapper{kindsFor: []unversioned.GroupVersionKind{ + {Group: "one", Version: "a", Kind: "first"}, + {Group: "two", Version: "b", Kind: "second"}, + {Group: "one", Version: "c", Kind: "third"}, + }}, + kindPatterns: []unversioned.GroupVersionKind{ + {Group: "one", Version: AnyVersion, Kind: AnyKind}, + {Group: AnyGroup, Version: "a", Kind: AnyKind}, + }, + result: unversioned.GroupVersionKind{Group: "one", Version: "a", Kind: "first"}, + }, + { + name: "kind selection", + delegate: fixedRESTMapper{kindsFor: []unversioned.GroupVersionKind{ + {Group: "one", Version: "a", Kind: "first"}, + {Group: "one", Version: "a", Kind: "second"}, + }}, + kindPatterns: []unversioned.GroupVersionKind{ + {Group: AnyGroup, Version: AnyVersion, Kind: "second"}, + }, + result: unversioned.GroupVersionKind{Group: "one", Version: "a", Kind: "second"}, + }, + } + + for _, tc := range tcs { + mapper := PriorityRESTMapper{Delegate: tc.delegate, KindPriority: tc.kindPatterns} + + actualResult, actualErr := mapper.KindFor(unversioned.GroupVersionResource{}) + if e, a := tc.result, actualResult; e != a { + t.Errorf("%s: expected %v, got %v", tc.name, e, a) + } + if len(tc.err) == 0 && actualErr == nil { + continue + } + if len(tc.err) > 0 && actualErr == nil { + t.Errorf("%s: missing expected err: %v", tc.name, tc.err) + continue + } + if !strings.Contains(actualErr.Error(), tc.err) { + t.Errorf("%s: expected %v, got %v", tc.name, tc.err, actualErr) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/api/meta/restmapper.go b/vendor/k8s.io/kubernetes/pkg/api/meta/restmapper.go index 7b1856bc4..4e07ab741 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/meta/restmapper.go +++ b/vendor/k8s.io/kubernetes/pkg/api/meta/restmapper.go @@ -24,7 +24,6 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util/sets" ) // Implements RESTScope interface @@ -70,7 +69,6 @@ var RESTScopeRoot = &restScope{ // // TODO: Only accept plural for some operations for increased control? // (`get pod bar` vs `get pods bar`) -// TODO these maps should be keyed based on GroupVersionKinds type DefaultRESTMapper struct { defaultGroupVersions []unversioned.GroupVersion @@ -81,6 +79,9 @@ type DefaultRESTMapper struct { pluralToSingular map[unversioned.GroupVersionResource]unversioned.GroupVersionResource interfacesFunc VersionInterfacesFunc + + // aliasToResource is used for mapping aliases to resources + aliasToResource map[string][]string } func (m *DefaultRESTMapper) String() string { @@ -104,6 +105,7 @@ func NewDefaultRESTMapper(defaultGroupVersions []unversioned.GroupVersion, f Ver kindToScope := make(map[unversioned.GroupVersionKind]RESTScope) singularToPlural := make(map[unversioned.GroupVersionResource]unversioned.GroupVersionResource) pluralToSingular := make(map[unversioned.GroupVersionResource]unversioned.GroupVersionResource) + aliasToResource := make(map[string][]string) // TODO: verify name mappings work correctly when versions differ return &DefaultRESTMapper{ @@ -113,6 +115,7 @@ func NewDefaultRESTMapper(defaultGroupVersions []unversioned.GroupVersion, f Ver defaultGroupVersions: defaultGroupVersions, singularToPlural: singularToPlural, pluralToSingular: pluralToSingular, + aliasToResource: aliasToResource, interfacesFunc: f, } } @@ -139,6 +142,8 @@ var unpluralizedSuffixes = []string{ } // KindToResource converts Kind to a resource name. +// Broken. This method only "sort of" works when used outside of this package. It assumes that Kinds and Resources match +// and they aren't guaranteed to do so. func KindToResource(kind unversioned.GroupVersionKind) ( /*plural*/ unversioned.GroupVersionResource /*singular*/, unversioned.GroupVersionResource) { kindName := kind.Kind if len(kindName) == 0 { @@ -195,7 +200,19 @@ func (m *DefaultRESTMapper) ResourceSingularizer(resourceType string) (string, e return singular.Resource, nil } -func (m *DefaultRESTMapper) ResourcesFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) { +// coerceResourceForMatching makes the resource lower case and converts internal versions to unspecified (legacy behavior) +func coerceResourceForMatching(resource unversioned.GroupVersionResource) unversioned.GroupVersionResource { + resource.Resource = strings.ToLower(resource.Resource) + if resource.Version == runtime.APIVersionInternal { + resource.Version = "" + } + + return resource +} + +func (m *DefaultRESTMapper) ResourcesFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) { + resource := coerceResourceForMatching(input) + hasResource := len(resource.Resource) > 0 hasGroup := len(resource.Group) > 0 hasVersion := len(resource.Version) > 0 @@ -272,10 +289,7 @@ func (m *DefaultRESTMapper) ResourceFor(resource unversioned.GroupVersionResourc } func (m *DefaultRESTMapper) KindsFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) { - resource := input.GroupVersion().WithResource(strings.ToLower(input.Resource)) - if resource.Version == runtime.APIVersionInternal { - resource.Version = "" - } + resource := coerceResourceForMatching(input) hasResource := len(resource.Resource) > 0 hasGroup := len(resource.Group) > 0 @@ -330,23 +344,8 @@ func (m *DefaultRESTMapper) KindFor(resource unversioned.GroupVersionResource) ( if err != nil { return unversioned.GroupVersionKind{}, err } - - // TODO for each group, choose the most preferred (first) version. This keeps us consistent with code today. - // eventually, we'll need a RESTMapper that is aware of what's available server-side and deconflicts that with - // user preferences - oneKindPerGroup := []unversioned.GroupVersionKind{} - groupsAdded := sets.String{} - for _, kind := range kinds { - if groupsAdded.Has(kind.Group) { - continue - } - - oneKindPerGroup = append(oneKindPerGroup, kind) - groupsAdded.Insert(kind.Group) - } - - if len(oneKindPerGroup) == 1 { - return oneKindPerGroup[0], nil + if len(kinds) == 1 { + return kinds[0], nil } return unversioned.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: resource, MatchingKinds: kinds} @@ -504,20 +503,17 @@ func (m *DefaultRESTMapper) RESTMapping(gk unversioned.GroupKind, versions ...st return retVal, nil } -// aliasToResource is used for mapping aliases to resources -var aliasToResource = map[string][]string{} - // AddResourceAlias maps aliases to resources func (m *DefaultRESTMapper) AddResourceAlias(alias string, resources ...string) { if len(resources) == 0 { return } - aliasToResource[alias] = resources + m.aliasToResource[alias] = resources } // AliasesForResource returns whether a resource has an alias or not func (m *DefaultRESTMapper) AliasesForResource(alias string) ([]string, bool) { - if res, ok := aliasToResource[alias]; ok { + if res, ok := m.aliasToResource[alias]; ok { return res, true } return nil, false diff --git a/vendor/k8s.io/kubernetes/pkg/api/pod/util.go b/vendor/k8s.io/kubernetes/pkg/api/pod/util.go index 8e70104aa..6b00c7e7d 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/pod/util.go +++ b/vendor/k8s.io/kubernetes/pkg/api/pod/util.go @@ -23,6 +23,18 @@ import ( "k8s.io/kubernetes/pkg/util/intstr" ) +const ( + // The annotation value is a string specifying the hostname to be used for the pod e.g 'my-webserver-1' + PodHostnameAnnotation = "pod.beta.kubernetes.io/hostname" + + // The annotation value is a string specifying the subdomain e.g. "my-web-service" + // If specified, on the the pod itself, ".my-web-service..svc." would resolve to + // the pod's IP. + // If there is a headless service named "my-web-service" in the same namespace as the pod, then, + // .my-web-service..svc." would be resolved by the cluster DNS Server. + PodSubdomainAnnotation = "pod.beta.kubernetes.io/subdomain" +) + // FindPort locates the container port for the given pod and portName. If the // targetPort is a number, use that. If the targetPort is a string, look that // string up in all named ports in all containers in the target pod. If no diff --git a/vendor/k8s.io/kubernetes/pkg/api/ref_test.go b/vendor/k8s.io/kubernetes/pkg/api/ref_test.go index 4f716672e..dbde7a01f 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/ref_test.go +++ b/vendor/k8s.io/kubernetes/pkg/api/ref_test.go @@ -36,6 +36,14 @@ type ExtensionAPIObject struct { func (obj *ExtensionAPIObject) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func TestGetReference(t *testing.T) { + + // when vendoring kube, if you don't force the set of registered versions (like this hack/test-go.sh does) + // then you run into trouble because the types aren't registered in the scheme by anything. This does the + // register manually to allow unit test execution + if _, err := Scheme.ObjectKind(&Pod{}); err != nil { + AddToScheme(Scheme) + } + table := map[string]struct { obj runtime.Object ref *ObjectReference diff --git a/vendor/k8s.io/kubernetes/pkg/api/register.go b/vendor/k8s.io/kubernetes/pkg/api/register.go index b01547b5a..5edc2d98a 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/register.go +++ b/vendor/k8s.io/kubernetes/pkg/api/register.go @@ -29,6 +29,9 @@ var Scheme = runtime.NewScheme() // Codecs provides access to encoding and decoding for the scheme var Codecs = serializer.NewCodecFactory(Scheme) +// StreamCodecs provides access to streaming encoding and decoding for the scheme +var StreamCodecs = serializer.NewStreamingCodecFactory(Scheme) + // GroupName is the group name use in this package const GroupName = "" @@ -66,8 +69,10 @@ func AddToScheme(scheme *runtime.Scheme) { &ReplicationController{}, &ServiceList{}, &Service{}, + &ServiceProxyOptions{}, &NodeList{}, &Node{}, + &NodeProxyOptions{}, &Endpoints{}, &EndpointsList{}, &Binding{}, @@ -133,6 +138,7 @@ func (obj *EndpointsList) GetObjectKind() unversioned.ObjectKind { r func (obj *Node) GetObjectMeta() meta.Object { return &obj.ObjectMeta } func (obj *Node) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *NodeList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } +func (obj *NodeProxyOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *Binding) GetObjectMeta() meta.Object { return &obj.ObjectMeta } func (obj *Binding) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *Event) GetObjectMeta() meta.Object { return &obj.ObjectMeta } @@ -166,6 +172,7 @@ func (obj *PodAttachOptions) GetObjectKind() unversioned.ObjectKind { r func (obj *PodLogOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *PodExecOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *PodProxyOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } +func (obj *ServiceProxyOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *ComponentStatus) GetObjectMeta() meta.Object { return &obj.ObjectMeta } func (obj *ComponentStatus) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *ComponentStatusList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/vendor/k8s.io/kubernetes/pkg/api/resource/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/api/resource/deep_copy_generated.go new file mode 100644 index 000000000..af6e3194d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/api/resource/deep_copy_generated.go @@ -0,0 +1,42 @@ +// +build !ignore_autogenerated + +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package resource + +import ( + conversion "k8s.io/kubernetes/pkg/conversion" + inf "speter.net/go/exp/math/dec/inf" +) + +func DeepCopy_resource_Quantity(in Quantity, out *Quantity, c *conversion.Cloner) error { + if in.Amount != nil { + in, out := in.Amount, &out.Amount + *out = new(inf.Dec) + if newVal, err := c.DeepCopy(*in); err != nil { + return err + } else { + **out = newVal.(inf.Dec) + } + } else { + out.Amount = nil + } + out.Format = in.Format + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/api/rest/delete.go b/vendor/k8s.io/kubernetes/pkg/api/rest/delete.go index c05f3446d..34965d52f 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/rest/delete.go +++ b/vendor/k8s.io/kubernetes/pkg/api/rest/delete.go @@ -17,9 +17,11 @@ limitations under the License. package rest import ( + "fmt" "time" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" ) @@ -28,7 +30,11 @@ import ( // API conventions. type RESTDeleteStrategy interface { runtime.ObjectTyper +} +// RESTGracefulDeleteStrategy must be implemented by the registry that supports +// graceful deletion. +type RESTGracefulDeleteStrategy interface { // CheckGracefulDelete should return true if the object can be gracefully deleted and set // any default values on the DeleteOptions. CheckGracefulDelete(obj runtime.Object, options *api.DeleteOptions) bool @@ -40,14 +46,18 @@ type RESTDeleteStrategy interface { // condition cannot be checked or the gracePeriodSeconds is invalid. The options argument may be updated with // default values if graceful is true. func BeforeDelete(strategy RESTDeleteStrategy, ctx api.Context, obj runtime.Object, options *api.DeleteOptions) (graceful, gracefulPending bool, err error) { - if strategy == nil { - return false, false, nil - } - objectMeta, _, kerr := objectMetaAndKind(strategy, obj) + objectMeta, gvk, kerr := objectMetaAndKind(strategy, obj) if kerr != nil { return false, false, kerr } - + // Checking the Preconditions here to fail early. They'll be enforced later on when we actually do the deletion, too. + if options.Preconditions != nil && options.Preconditions.UID != nil && *options.Preconditions.UID != objectMeta.UID { + return false, false, errors.NewConflict(unversioned.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, objectMeta.Name, fmt.Errorf("the UID in the precondition (%s) does not match the UID in record (%s). The object might have been deleted and then recreated", *options.Preconditions.UID, objectMeta.UID)) + } + gracefulStrategy, ok := strategy.(RESTGracefulDeleteStrategy) + if !ok { + return false, false, nil + } // if the object is already being deleted if objectMeta.DeletionTimestamp != nil { // if we are already being deleted, we may only shorten the deletion grace period @@ -73,7 +83,7 @@ func BeforeDelete(strategy RESTDeleteStrategy, ctx api.Context, obj runtime.Obje return false, true, nil } - if !strategy.CheckGracefulDelete(obj, options) { + if !gracefulStrategy.CheckGracefulDelete(obj, options) { return false, false, nil } now := unversioned.NewTime(unversioned.Now().Add(time.Second * time.Duration(*options.GracePeriodSeconds))) diff --git a/vendor/k8s.io/kubernetes/pkg/api/rest/rest.go b/vendor/k8s.io/kubernetes/pkg/api/rest/rest.go index 584670f77..4565730f2 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/rest/rest.go +++ b/vendor/k8s.io/kubernetes/pkg/api/rest/rest.go @@ -57,7 +57,7 @@ type Storage interface { // KindProvider specifies a different kind for its API than for its internal storage. This is necessary for external // objects that are not compiled into the api server. For such objects, there is no in-memory representation for -// the object, so they must be represented as generic objects (e.g. RawJSON), but when we present the object as part of +// the object, so they must be represented as generic objects (e.g. runtime.Unknown), but when we present the object as part of // API discovery we want to present the specific kind, not the generic internal representation. type KindProvider interface { Kind() string @@ -104,7 +104,7 @@ type GetterWithOptions interface { // value of the request path below the object will be included as the named // string in the serialization of the runtime object. E.g., returning "path" // will convert the trailing request scheme value to "path" in the map[string][]string - // passed to the convertor. + // passed to the converter. NewGetOptions() (runtime.Object, bool, string) } diff --git a/vendor/k8s.io/kubernetes/pkg/api/rest/resttest/resttest.go b/vendor/k8s.io/kubernetes/pkg/api/rest/resttest/resttest.go index fbf0bf87d..de1b6c689 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/rest/resttest/resttest.go +++ b/vendor/k8s.io/kubernetes/pkg/api/rest/resttest/resttest.go @@ -32,6 +32,7 @@ import ( "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/util/wait" ) @@ -154,12 +155,14 @@ func (t *Tester) TestUpdate(valid runtime.Object, setFn SetFunc, getFn GetFunc, t.testUpdateRejectsMismatchedNamespace(copyOrDie(valid), setFn) } t.testUpdateInvokesValidation(copyOrDie(valid), setFn, invalidUpdateFn...) + t.testUpdateWithWrongUID(copyOrDie(valid), setFn, getFn) } // Test deleting an object. func (t *Tester) TestDelete(valid runtime.Object, setFn SetFunc, getFn GetFunc, isNotFoundFn IsErrorFunc) { t.testDeleteNonExist(copyOrDie(valid)) t.testDeleteNoGraceful(copyOrDie(valid), setFn, getFn, isNotFoundFn) + t.testDeleteWithUID(copyOrDie(valid), setFn, getFn, isNotFoundFn) } // Test gracefully deleting an object. @@ -474,6 +477,26 @@ func (t *Tester) testUpdateInvokesValidation(obj runtime.Object, setFn SetFunc, } } +func (t *Tester) testUpdateWithWrongUID(obj runtime.Object, setFn SetFunc, getFn GetFunc) { + ctx := t.TestContext() + foo := copyOrDie(obj) + t.setObjectMeta(foo, "foo5") + objectMeta := t.getObjectMetaOrFail(foo) + objectMeta.UID = types.UID("UID0000") + if err := setFn(ctx, foo); err != nil { + t.Errorf("unexpected error: %v", err) + } + objectMeta.UID = types.UID("UID1111") + + obj, created, err := t.storage.(rest.Updater).Update(ctx, foo) + if created || obj != nil { + t.Errorf("expected nil object and no creation for object: %v", foo) + } + if err == nil || !errors.IsConflict(err) { + t.Errorf("unexpected error: %v", err) + } +} + func (t *Tester) testUpdateOnNotFound(obj runtime.Object) { t.setObjectMeta(obj, "foo") _, created, err := t.storage.(rest.Updater).Update(t.TestContext(), obj) @@ -557,6 +580,42 @@ func (t *Tester) testDeleteNonExist(obj runtime.Object) { } +// This test the fast-fail path. We test that the precondition gets verified +// again before deleting the object in tests of pkg/storage/etcd. +func (t *Tester) testDeleteWithUID(obj runtime.Object, setFn SetFunc, getFn GetFunc, isNotFoundFn IsErrorFunc) { + ctx := t.TestContext() + + foo := copyOrDie(obj) + t.setObjectMeta(foo, "foo1") + objectMeta := t.getObjectMetaOrFail(foo) + objectMeta.UID = types.UID("UID0000") + if err := setFn(ctx, foo); err != nil { + t.Errorf("unexpected error: %v", err) + } + obj, err := t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, api.NewPreconditionDeleteOptions("UID1111")) + if err == nil || !errors.IsConflict(err) { + t.Errorf("unexpected error: %v", err) + } + + obj, err = t.storage.(rest.GracefulDeleter).Delete(ctx, objectMeta.Name, api.NewPreconditionDeleteOptions("UID0000")) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if !t.returnDeletedObject { + if status, ok := obj.(*unversioned.Status); !ok { + t.Errorf("expected status of delete, got %v", status) + } else if status.Status != unversioned.StatusSuccess { + t.Errorf("expected success, got: %v", status.Status) + } + } + + _, err = getFn(ctx, foo) + if err == nil || !isNotFoundFn(err) { + t.Errorf("unexpected error: %v", err) + } +} + // ============================================================================= // Graceful Deletion tests. @@ -574,7 +633,7 @@ func (t *Tester) testDeleteGracefulHasDefault(obj runtime.Object, setFn SetFunc, t.Errorf("unexpected error: %v", err) } if _, err := getFn(ctx, foo); err != nil { - t.Fatalf("did not gracefully delete resource", err) + t.Fatalf("did not gracefully delete resource: %v", err) } object, err := t.storage.(rest.Getter).Get(ctx, objectMeta.Name) @@ -601,7 +660,7 @@ func (t *Tester) testDeleteGracefulWithValue(obj runtime.Object, setFn SetFunc, t.Errorf("unexpected error: %v", err) } if _, err := getFn(ctx, foo); err != nil { - t.Fatalf("did not gracefully delete resource", err) + t.Fatalf("did not gracefully delete resource: %v", err) } object, err := t.storage.(rest.Getter).Get(ctx, objectMeta.Name) @@ -628,7 +687,7 @@ func (t *Tester) testDeleteGracefulExtend(obj runtime.Object, setFn SetFunc, get t.Errorf("unexpected error: %v", err) } if _, err := getFn(ctx, foo); err != nil { - t.Fatalf("did not gracefully delete resource", err) + t.Fatalf("did not gracefully delete resource: %v", err) } // second delete duration is ignored @@ -660,7 +719,7 @@ func (t *Tester) testDeleteGracefulImmediate(obj runtime.Object, setFn SetFunc, t.Errorf("unexpected error: %v", err) } if _, err := getFn(ctx, foo); err != nil { - t.Fatalf("did not gracefully delete resource", err) + t.Fatalf("did not gracefully delete resource: %v", err) } // second delete is immediate, resource is deleted @@ -674,7 +733,7 @@ func (t *Tester) testDeleteGracefulImmediate(obj runtime.Object, setFn SetFunc, } objectMeta = t.getObjectMetaOrFail(out) // the second delete shouldn't update the object, so the objectMeta.DeletionGracePeriodSeconds should eqaul to the value set in the first delete. - if objectMeta.DeletionTimestamp == nil || objectMeta.DeletionGracePeriodSeconds == nil || *objectMeta.DeletionGracePeriodSeconds != expectedGrace { + if objectMeta.DeletionTimestamp == nil || objectMeta.DeletionGracePeriodSeconds == nil || *objectMeta.DeletionGracePeriodSeconds != 0 { t.Errorf("unexpected deleted meta: %#v", objectMeta) } } diff --git a/vendor/k8s.io/kubernetes/pkg/api/serialization_proto_test.go b/vendor/k8s.io/kubernetes/pkg/api/serialization_proto_test.go index d74c1865d..a22e9796c 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/serialization_proto_test.go +++ b/vendor/k8s.io/kubernetes/pkg/api/serialization_proto_test.go @@ -25,24 +25,27 @@ import ( "github.com/gogo/protobuf/proto" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" _ "k8s.io/kubernetes/pkg/apis/extensions" _ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/runtime/protobuf" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/runtime/serializer/protobuf" + "k8s.io/kubernetes/pkg/util/diff" ) func init() { - codecsToTest = append(codecsToTest, func(version string, item runtime.Object) (runtime.Codec, error) { - return protobuf.NewCodec(version, api.Scheme, api.Scheme, api.Scheme), nil + codecsToTest = append(codecsToTest, func(version unversioned.GroupVersion, item runtime.Object) (runtime.Codec, error) { + s := protobuf.NewSerializer(api.Scheme, runtime.ObjectTyperToTyper(api.Scheme), "application/arbitrary.content.type") + return api.Codecs.CodecForVersions(s, testapi.ExternalGroupVersions(), nil), nil }) } func TestProtobufRoundTrip(t *testing.T) { obj := &v1.Pod{} - apitesting.FuzzerFor(t, "v1", rand.NewSource(benchmarkSeed)).Fuzz(obj) + apitesting.FuzzerFor(t, v1.SchemeGroupVersion, rand.NewSource(benchmarkSeed)).Fuzz(obj) data, err := obj.Marshal() if err != nil { t.Fatal(err) @@ -53,10 +56,47 @@ func TestProtobufRoundTrip(t *testing.T) { } if !api.Semantic.Equalities.DeepEqual(out, obj) { t.Logf("marshal\n%s", hex.Dump(data)) - t.Fatalf("Unmarshal is unequal\n%s", util.ObjectGoPrintSideBySide(out, obj)) + t.Fatalf("Unmarshal is unequal\n%s", diff.ObjectGoPrintSideBySide(out, obj)) } } +// BenchmarkEncodeCodec measures the cost of performing a codec encode, which includes +// reflection (to clear APIVersion and Kind) +func BenchmarkEncodeCodecProtobuf(b *testing.B) { + items := benchmarkItems() + width := len(items) + s := protobuf.NewSerializer(nil, nil, "application/arbitrary.content.type") + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := runtime.Encode(s, &items[i%width]); err != nil { + b.Fatal(err) + } + } + b.StopTimer() +} + +// BenchmarkEncodeCodecFromInternalProtobuf measures the cost of performing a codec encode, +// including conversions and any type setting. This is a "full" encode. +func BenchmarkEncodeCodecFromInternalProtobuf(b *testing.B) { + items := benchmarkItems() + width := len(items) + encodable := make([]api.Pod, width) + for i := range items { + if err := api.Scheme.Convert(&items[i], &encodable[i]); err != nil { + b.Fatal(err) + } + } + s := protobuf.NewSerializer(nil, nil, "application/arbitrary.content.type") + codec := api.Codecs.EncoderForVersion(s, v1.SchemeGroupVersion) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := runtime.Encode(codec, &encodable[i%width]); err != nil { + b.Fatal(err) + } + } + b.StopTimer() +} + func BenchmarkEncodeProtobufGeneratedMarshal(b *testing.B) { items := benchmarkItems() width := len(items) diff --git a/vendor/k8s.io/kubernetes/pkg/api/serialization_test.go b/vendor/k8s.io/kubernetes/pkg/api/serialization_test.go index f95e9a277..974dd1e1b 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/serialization_test.go +++ b/vendor/k8s.io/kubernetes/pkg/api/serialization_test.go @@ -17,12 +17,15 @@ limitations under the License. package api_test import ( + "encoding/hex" "encoding/json" "math/rand" "reflect" + "strings" "testing" "github.com/davecgh/go-spew/spew" + proto "github.com/golang/protobuf/proto" flag "github.com/spf13/pflag" "github.com/ugorji/go/codec" @@ -33,7 +36,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" "k8s.io/kubernetes/pkg/util/sets" ) @@ -58,9 +61,16 @@ func fuzzInternalObject(t *testing.T, forVersion unversioned.GroupVersion, item return item } -func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) { - //t.Logf("codec: %#v", codec) +func dataAsString(data []byte) string { + dataString := string(data) + if !strings.HasPrefix(dataString, "{") { + dataString = "\n" + hex.Dump(data) + proto.NewBuffer(make([]byte, 0, 1024)).DebugPrint("decoded object", data) + } + return dataString +} +func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) { printer := spew.ConfigState{DisableMethods: true} name := reflect.TypeOf(item).Elem().Name() @@ -72,11 +82,12 @@ func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) { obj2, err := runtime.Decode(codec, data) if err != nil { - t.Errorf("0: %v: %v\nCodec: %v\nData: %s\nSource: %#v", name, err, codec, string(data), printer.Sprintf("%#v", item)) + t.Errorf("0: %v: %v\nCodec: %v\nData: %s\nSource: %#v", name, err, codec, dataAsString(data), printer.Sprintf("%#v", item)) + panic("failed") return } if !api.Semantic.DeepEqual(item, obj2) { - t.Errorf("\n1: %v: diff: %v\nCodec: %v\nSource:\n\n%#v\n\nEncoded:\n\n%s\n\nFinal:\n\n%#v", name, util.ObjectGoPrintDiff(item, obj2), codec, printer.Sprintf("%#v", item), string(data), printer.Sprintf("%#v", obj2)) + t.Errorf("\n1: %v: diff: %v\nCodec: %v\nSource:\n\n%#v\n\nEncoded:\n\n%s\n\nFinal:\n\n%#v", name, diff.ObjectGoPrintDiff(item, obj2), codec, printer.Sprintf("%#v", item), dataAsString(data), printer.Sprintf("%#v", obj2)) return } @@ -86,7 +97,7 @@ func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) { return } if !api.Semantic.DeepEqual(item, obj3) { - t.Errorf("3: %v: diff: %v\nCodec: %v", name, util.ObjectDiff(item, obj3), codec) + t.Errorf("3: %v: diff: %v\nCodec: %v", name, diff.ObjectDiff(item, obj3), codec) return } } @@ -118,9 +129,6 @@ func roundTripSame(t *testing.T, group testapi.TestGroup, item runtime.Object, e // For debugging problems func TestSpecificKind(t *testing.T) { - // api.Scheme.Log(t) - // defer api.Scheme.Log(nil) - kind := "DaemonSet" for i := 0; i < *fuzzIters; i++ { doRoundTripTest(testapi.Groups["extensions"], kind, t) @@ -131,9 +139,6 @@ func TestSpecificKind(t *testing.T) { } func TestList(t *testing.T) { - // api.Scheme.Log(t) - // defer api.Scheme.Log(nil) - kind := "List" item, err := api.Scheme.New(api.SchemeGroupVersion.WithKind(kind)) if err != nil { @@ -143,17 +148,21 @@ func TestList(t *testing.T) { roundTripSame(t, testapi.Default, item) } -var nonRoundTrippableTypes = sets.NewString("ExportOptions") +var nonRoundTrippableTypes = sets.NewString( + "ExportOptions", + // WatchEvent does not include kind and version and can only be deserialized + // implicitly (if the caller expects the specific object). The watch call defines + // the schema by content type, rather than via kind/version included in each + // object. + "WatchEvent", +) var nonInternalRoundTrippableTypes = sets.NewString("List", "ListOptions", "ExportOptions") var nonRoundTrippableTypesByVersion = map[string][]string{} func TestRoundTripTypes(t *testing.T) { - // api.Scheme.Log(t) - // defer api.Scheme.Log(nil) - for groupKey, group := range testapi.Groups { - for kind := range api.Scheme.KnownTypes(group.InternalGroupVersion()) { + for kind := range group.InternalTypes() { t.Logf("working on %v in %v", kind, groupKey) if nonRoundTrippableTypes.Has(kind) { continue @@ -180,7 +189,7 @@ func doRoundTripTest(group testapi.TestGroup, kind string, t *testing.T) { if api.Scheme.Recognizes(group.GroupVersion().WithKind(kind)) { roundTripSame(t, group, item, nonRoundTrippableTypesByVersion[kind]...) } - if !nonInternalRoundTrippableTypes.Has(kind) { + if !nonInternalRoundTrippableTypes.Has(kind) && api.Scheme.Recognizes(group.GroupVersion().WithKind(kind)) { roundTrip(t, group.Codec(), fuzzInternalObject(t, group.InternalGroupVersion(), item, rand.Int63())) } } @@ -210,7 +219,7 @@ func TestEncode_Ptr(t *testing.T) { t.Fatalf("Got wrong type") } if !api.Semantic.DeepEqual(obj2, pod) { - t.Errorf("\nExpected:\n\n %#v,\n\nGot:\n\n %#vDiff: %v\n\n", pod, obj2, util.ObjectDiff(obj2, pod)) + t.Errorf("\nExpected:\n\n %#v,\n\nGot:\n\n %#vDiff: %v\n\n", pod, obj2, diff.ObjectDiff(obj2, pod)) } } @@ -286,6 +295,26 @@ func BenchmarkEncodeCodec(b *testing.B) { b.StopTimer() } +// BenchmarkEncodeCodecFromInternal measures the cost of performing a codec encode, +// including conversions. +func BenchmarkEncodeCodecFromInternal(b *testing.B) { + items := benchmarkItems() + width := len(items) + encodable := make([]api.Pod, width) + for i := range items { + if err := api.Scheme.Convert(&items[i], &encodable[i]); err != nil { + b.Fatal(err) + } + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := runtime.Encode(testapi.Default.Codec(), &encodable[i%width]); err != nil { + b.Fatal(err) + } + } + b.StopTimer() +} + // BenchmarkEncodeJSONMarshal provides a baseline for regular JSON encode performance func BenchmarkEncodeJSONMarshal(b *testing.B) { items := benchmarkItems() diff --git a/vendor/k8s.io/kubernetes/pkg/api/service/annotations.go b/vendor/k8s.io/kubernetes/pkg/api/service/annotations.go new file mode 100644 index 000000000..9d57fa4c2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/api/service/annotations.go @@ -0,0 +1,28 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 service + +const ( + // AnnotationLoadBalancerSourceRangesKey is the key of the annotation on a service to set allowed ingress ranges on their LoadBalancers + // + // It should be a comma-separated list of CIDRs, e.g. `0.0.0.0/0` to + // allow full access (the default) or `18.0.0.0/8,56.0.0.0/8` to allow + // access only from the CIDRs currently allocated to MIT & the USPS. + // + // Not all cloud providers support this annotation, though AWS & GCE do. + AnnotationLoadBalancerSourceRangesKey = "service.beta.kubernetes.io/load-balancer-source-ranges" +) diff --git a/vendor/k8s.io/kubernetes/pkg/api/service/util.go b/vendor/k8s.io/kubernetes/pkg/api/service/util.go new file mode 100644 index 000000000..a77e5b9c7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/api/service/util.go @@ -0,0 +1,54 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 service + +import ( + "fmt" + "strings" + + netsets "k8s.io/kubernetes/pkg/util/net/sets" +) + +const ( + defaultLoadBalancerSourceRanges = "0.0.0.0/0" +) + +// IsAllowAll checks whether the netsets.IPNet allows traffic from 0.0.0.0/0 +func IsAllowAll(ipnets netsets.IPNet) bool { + for _, s := range ipnets.StringSlice() { + if s == "0.0.0.0/0" { + return true + } + } + return false +} + +// GetLoadBalancerSourceRanges verifies and parses the AnnotationLoadBalancerSourceRangesKey annotation from a service, +// extracting the source ranges to allow, and if not present returns a default (allow-all) value. +func GetLoadBalancerSourceRanges(annotations map[string]string) (netsets.IPNet, error) { + val := annotations[AnnotationLoadBalancerSourceRangesKey] + val = strings.TrimSpace(val) + if val == "" { + val = defaultLoadBalancerSourceRanges + } + specs := strings.Split(val, ",") + ipnets, err := netsets.ParseIPNets(specs...) + if err != nil { + return nil, fmt.Errorf("Service annotation %s:%s is not valid. Expecting a comma-separated list of source IP ranges. For example, 10.0.0.0/24,192.168.2.0/24", AnnotationLoadBalancerSourceRangesKey, val) + } + return ipnets, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/api/service/util_test.go b/vendor/k8s.io/kubernetes/pkg/api/service/util_test.go new file mode 100644 index 000000000..c77d4f259 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/api/service/util_test.go @@ -0,0 +1,92 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 service + +import ( + "testing" + + netsets "k8s.io/kubernetes/pkg/util/net/sets" +) + +func TestGetLoadBalancerSourceRanges(t *testing.T) { + checkError := func(v string) { + annotations := make(map[string]string) + annotations[AnnotationLoadBalancerSourceRangesKey] = v + _, err := GetLoadBalancerSourceRanges(annotations) + if err == nil { + t.Errorf("Expected error parsing: %q", v) + } + } + checkError("10.0.0.1/33") + checkError("foo.bar") + checkError("10.0.0.1/32,*") + checkError("10.0.0.1/32,") + checkError("10.0.0.1/32, ") + checkError("10.0.0.1") + + checkOK := func(v string) netsets.IPNet { + annotations := make(map[string]string) + annotations[AnnotationLoadBalancerSourceRangesKey] = v + cidrs, err := GetLoadBalancerSourceRanges(annotations) + if err != nil { + t.Errorf("Unexpected error parsing: %q", v) + } + return cidrs + } + cidrs := checkOK("192.168.0.1/32") + if len(cidrs) != 1 { + t.Errorf("Expected exactly one CIDR: %v", cidrs.StringSlice()) + } + cidrs = checkOK("192.168.0.1/32,192.168.0.1/32") + if len(cidrs) != 1 { + t.Errorf("Expected exactly one CIDR (after de-dup): %v", cidrs.StringSlice()) + } + cidrs = checkOK("192.168.0.1/32,192.168.0.2/32") + if len(cidrs) != 2 { + t.Errorf("Expected two CIDRs: %v", cidrs.StringSlice()) + } + cidrs = checkOK(" 192.168.0.1/32 , 192.168.0.2/32 ") + if len(cidrs) != 2 { + t.Errorf("Expected two CIDRs: %v", cidrs.StringSlice()) + } + cidrs = checkOK("") + if len(cidrs) != 1 { + t.Errorf("Expected exactly one CIDR: %v", cidrs.StringSlice()) + } + if !IsAllowAll(cidrs) { + t.Errorf("Expected default to be allow-all: %v", cidrs.StringSlice()) + } +} + +func TestAllowAll(t *testing.T) { + checkAllowAll := func(allowAll bool, cidrs ...string) { + ipnets, err := netsets.ParseIPNets(cidrs...) + if err != nil { + t.Errorf("Unexpected error parsing cidrs: %v", cidrs) + } + if allowAll != IsAllowAll(ipnets) { + t.Errorf("IsAllowAll did not return expected value for %v", cidrs) + } + } + checkAllowAll(false, "10.0.0.1/32") + checkAllowAll(false, "10.0.0.1/32", "10.0.0.2/32") + checkAllowAll(false, "10.0.0.1/32", "10.0.0.1/32") + + checkAllowAll(true, "0.0.0.0/0") + checkAllowAll(true, "192.168.0.0/0") + checkAllowAll(true, "192.168.0.1/32", "0.0.0.0/0") +} diff --git a/vendor/k8s.io/kubernetes/pkg/api/testapi/testapi.go b/vendor/k8s.io/kubernetes/pkg/api/testapi/testapi.go index d3fc91aae..389c83cca 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/testapi/testapi.go +++ b/vendor/k8s.io/kubernetes/pkg/api/testapi/testapi.go @@ -20,30 +20,38 @@ package testapi import ( "fmt" "os" + "reflect" "strings" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/apis/autoscaling" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/runtime" _ "k8s.io/kubernetes/pkg/api/install" + _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" + _ "k8s.io/kubernetes/pkg/apis/batch/install" _ "k8s.io/kubernetes/pkg/apis/componentconfig/install" _ "k8s.io/kubernetes/pkg/apis/extensions/install" _ "k8s.io/kubernetes/pkg/apis/metrics/install" ) var ( - Groups = make(map[string]TestGroup) - Default TestGroup - Extensions TestGroup + Groups = make(map[string]TestGroup) + Default TestGroup + Autoscaling TestGroup + Batch TestGroup + Extensions TestGroup ) type TestGroup struct { externalGroupVersion unversioned.GroupVersion internalGroupVersion unversioned.GroupVersion + internalTypes map[string]reflect.Type } func init() { @@ -56,9 +64,11 @@ func init() { panic(fmt.Sprintf("Error parsing groupversion %v: %v", gvString, err)) } + internalGroupVersion := unversioned.GroupVersion{Group: groupVersion.Group, Version: runtime.APIVersionInternal} Groups[groupVersion.Group] = TestGroup{ externalGroupVersion: groupVersion, - internalGroupVersion: unversioned.GroupVersion{Group: groupVersion.Group, Version: runtime.APIVersionInternal}, + internalGroupVersion: internalGroupVersion, + internalTypes: api.Scheme.KnownTypes(internalGroupVersion), } } } @@ -67,16 +77,55 @@ func init() { Groups[api.GroupName] = TestGroup{ externalGroupVersion: unversioned.GroupVersion{Group: api.GroupName, Version: registered.GroupOrDie(api.GroupName).GroupVersion.Version}, internalGroupVersion: api.SchemeGroupVersion, + internalTypes: api.Scheme.KnownTypes(api.SchemeGroupVersion), } } if _, ok := Groups[extensions.GroupName]; !ok { Groups[extensions.GroupName] = TestGroup{ externalGroupVersion: unversioned.GroupVersion{Group: extensions.GroupName, Version: registered.GroupOrDie(extensions.GroupName).GroupVersion.Version}, internalGroupVersion: extensions.SchemeGroupVersion, + internalTypes: api.Scheme.KnownTypes(extensions.SchemeGroupVersion), + } + } + if _, ok := Groups[autoscaling.GroupName]; !ok { + internalTypes := make(map[string]reflect.Type) + for k, t := range api.Scheme.KnownTypes(extensions.SchemeGroupVersion) { + if k == "Scale" { + continue + } + internalTypes[k] = t + } + Groups[autoscaling.GroupName] = TestGroup{ + externalGroupVersion: unversioned.GroupVersion{Group: autoscaling.GroupName, Version: registered.GroupOrDie(autoscaling.GroupName).GroupVersion.Version}, + internalGroupVersion: extensions.SchemeGroupVersion, + internalTypes: internalTypes, + } + } + if _, ok := Groups[autoscaling.GroupName+"IntraGroup"]; !ok { + internalTypes := make(map[string]reflect.Type) + for k, t := range api.Scheme.KnownTypes(extensions.SchemeGroupVersion) { + if k == "Scale" { + internalTypes[k] = t + break + } + } + Groups[autoscaling.GroupName] = TestGroup{ + externalGroupVersion: unversioned.GroupVersion{Group: autoscaling.GroupName, Version: registered.GroupOrDie(autoscaling.GroupName).GroupVersion.Version}, + internalGroupVersion: autoscaling.SchemeGroupVersion, + internalTypes: internalTypes, + } + } + if _, ok := Groups[batch.GroupName]; !ok { + Groups[batch.GroupName] = TestGroup{ + externalGroupVersion: unversioned.GroupVersion{Group: batch.GroupName, Version: registered.GroupOrDie(batch.GroupName).GroupVersion.Version}, + internalGroupVersion: extensions.SchemeGroupVersion, + internalTypes: api.Scheme.KnownTypes(extensions.SchemeGroupVersion), } } Default = Groups[api.GroupName] + Autoscaling = Groups[autoscaling.GroupName] + Batch = Groups[batch.GroupName] Extensions = Groups[extensions.GroupName] } @@ -95,6 +144,11 @@ func (g TestGroup) InternalGroupVersion() unversioned.GroupVersion { return g.internalGroupVersion } +// InternalTypes returns a map of internal API types' kind names to their Go types. +func (g TestGroup) InternalTypes() map[string]reflect.Type { + return g.internalTypes +} + // Codec returns the codec for the API version to test against, as set by the // KUBE_TEST_API env var. func (g TestGroup) Codec() runtime.Codec { @@ -178,7 +232,17 @@ func (g TestGroup) ResourcePath(resource, namespace, name string) string { } func (g TestGroup) RESTMapper() meta.RESTMapper { - return registered.GroupOrDie(g.externalGroupVersion.Group).RESTMapper + return registered.RESTMapper() +} + +// ExternalGroupVersions returns all external group versions allowed for the server. +func ExternalGroupVersions() []unversioned.GroupVersion { + versions := []unversioned.GroupVersion{} + for _, g := range Groups { + gv := g.GroupVersion() + versions = append(versions, *gv) + } + return versions } // Get codec based on runtime.Object @@ -208,6 +272,6 @@ func GetCodecForObject(obj runtime.Object) (runtime.Codec, error) { return nil, fmt.Errorf("unexpected kind: %v", kind) } -func NewTestGroup(external, internal unversioned.GroupVersion) TestGroup { - return TestGroup{external, internal} +func NewTestGroup(external, internal unversioned.GroupVersion, internalTypes map[string]reflect.Type) TestGroup { + return TestGroup{external, internal, internalTypes} } diff --git a/vendor/k8s.io/kubernetes/pkg/api/testapi/testapi_test.go b/vendor/k8s.io/kubernetes/pkg/api/testapi/testapi_test.go index 7b8bb02ba..aa049f91d 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/testapi/testapi_test.go +++ b/vendor/k8s.io/kubernetes/pkg/api/testapi/testapi_test.go @@ -100,9 +100,8 @@ func TestV1EncodeDecodeStatus(t *testing.T) { } } -func TestExperimentalEncodeDecodeStatus(t *testing.T) { - extensionCodec := Extensions.Codec() - encoded, err := runtime.Encode(extensionCodec, status) +func testEncodeDecodeStatus(t *testing.T, codec runtime.Codec) { + encoded, err := runtime.Encode(codec, status) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -116,7 +115,7 @@ func TestExperimentalEncodeDecodeStatus(t *testing.T) { if typeMeta.APIVersion != "v1" { t.Errorf("APIVersion is not set to \"\". Got %s", encoded) } - decoded, err := runtime.Decode(extensionCodec, encoded) + decoded, err := runtime.Decode(codec, encoded) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -124,3 +123,15 @@ func TestExperimentalEncodeDecodeStatus(t *testing.T) { t.Errorf("expected: %v, got: %v", status, decoded) } } + +func TestAutoscalingEncodeDecodeStatus(t *testing.T) { + testEncodeDecodeStatus(t, Autoscaling.Codec()) +} + +func TestBatchEncodeDecodeStatus(t *testing.T) { + testEncodeDecodeStatus(t, Batch.Codec()) +} + +func TestExperimentalEncodeDecodeStatus(t *testing.T) { + testEncodeDecodeStatus(t, Extensions.Codec()) +} diff --git a/vendor/k8s.io/kubernetes/pkg/api/testing/fuzzer.go b/vendor/k8s.io/kubernetes/pkg/api/testing/fuzzer.go index 4375d2661..72c3025c4 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/testing/fuzzer.go +++ b/vendor/k8s.io/kubernetes/pkg/api/testing/fuzzer.go @@ -162,6 +162,11 @@ func FuzzerFor(t *testing.T, version unversioned.GroupVersion, src rand.Source) parallelism := int(c.Rand.Int31()) j.Completions = &completions j.Parallelism = ¶llelism + if c.Rand.Int31()%2 == 0 { + j.ManualSelector = newBool(true) + } else { + j.ManualSelector = nil + } }, func(j *api.List, c fuzz.Continue) { c.FuzzNoCustom(j) // fuzz self without calling this function again @@ -175,7 +180,8 @@ func FuzzerFor(t *testing.T, version unversioned.GroupVersion, src rand.Source) if true { //c.RandBool() { *j = &runtime.Unknown{ // We do not set TypeMeta here because it is not carried through a round trip - RawJSON: []byte(`{"apiVersion":"unknown.group/unknown","kind":"Something","someKey":"someValue"}`), + Raw: []byte(`{"apiVersion":"unknown.group/unknown","kind":"Something","someKey":"someValue"}`), + ContentType: runtime.ContentTypeJSON, } } else { types := []runtime.Object{&api.Pod{}, &api.ReplicationController{}} @@ -386,24 +392,48 @@ func FuzzerFor(t *testing.T, version unversioned.GroupVersion, src rand.Source) c.FuzzNoCustom(s) s.Allocatable = s.Capacity }, - func(s *extensions.APIVersion, c fuzz.Continue) { - // We can't use c.RandString() here because it may generate empty - // string, which will cause tests failure. - s.APIGroup = "something" - }, func(s *extensions.HorizontalPodAutoscalerSpec, c fuzz.Continue) { c.FuzzNoCustom(s) // fuzz self without calling this function again minReplicas := int(c.Rand.Int31()) s.MinReplicas = &minReplicas s.CPUUtilization = &extensions.CPUTargetUtilization{TargetPercentage: int(int32(c.RandUint64()))} }, + func(s *extensions.SubresourceReference, c fuzz.Continue) { + c.FuzzNoCustom(s) // fuzz self without calling this function again + s.Subresource = "scale" + }, func(psp *extensions.PodSecurityPolicySpec, c fuzz.Continue) { c.FuzzNoCustom(psp) // fuzz self without calling this function again - userTypes := []extensions.RunAsUserStrategy{extensions.RunAsUserStrategyMustRunAsNonRoot, extensions.RunAsUserStrategyMustRunAs, extensions.RunAsUserStrategyRunAsAny} - psp.RunAsUser.Type = userTypes[c.Rand.Intn(len(userTypes))] - seLinuxTypes := []extensions.SELinuxContextStrategy{extensions.SELinuxStrategyRunAsAny, extensions.SELinuxStrategyMustRunAs} - psp.SELinuxContext.Type = seLinuxTypes[c.Rand.Intn(len(seLinuxTypes))] + runAsUserRules := []extensions.RunAsUserStrategy{extensions.RunAsUserStrategyMustRunAsNonRoot, extensions.RunAsUserStrategyMustRunAs, extensions.RunAsUserStrategyRunAsAny} + psp.RunAsUser.Rule = runAsUserRules[c.Rand.Intn(len(runAsUserRules))] + seLinuxRules := []extensions.SELinuxStrategy{extensions.SELinuxStrategyRunAsAny, extensions.SELinuxStrategyMustRunAs} + psp.SELinux.Rule = seLinuxRules[c.Rand.Intn(len(seLinuxRules))] + }, + func(s *extensions.Scale, c fuzz.Continue) { + c.FuzzNoCustom(s) // fuzz self without calling this function again + // TODO: Implement a fuzzer to generate valid keys, values and operators for + // selector requirements. + if s.Status.Selector != nil { + s.Status.Selector = &unversioned.LabelSelector{ + MatchLabels: map[string]string{ + "testlabelkey": "testlabelval", + }, + MatchExpressions: []unversioned.LabelSelectorRequirement{ + { + Key: "testkey", + Operator: unversioned.LabelSelectorOpIn, + Values: []string{"val1", "val2", "val3"}, + }, + }, + } + } }, ) return f } + +func newBool(val bool) *bool { + p := new(bool) + *p = val + return p +} diff --git a/vendor/k8s.io/kubernetes/pkg/api/types.generated.go b/vendor/k8s.io/kubernetes/pkg/api/types.generated.go index 5cd4a3883..214eda048 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/types.generated.go +++ b/vendor/k8s.io/kubernetes/pkg/api/types.generated.go @@ -26953,13 +26953,14 @@ func (x *ReplicationControllerStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [2]bool + var yyq2 [3]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[1] = x.ObservedGeneration != 0 + yyq2[1] = x.FullyLabeledReplicas != 0 + yyq2[2] = x.ObservedGeneration != 0 var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(2) + r.EncodeArrayStart(3) } else { yynn2 = 1 for _, b := range yyq2 { @@ -26996,7 +26997,7 @@ func (x *ReplicationControllerStatus) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym7 if false { } else { - r.EncodeInt(int64(x.ObservedGeneration)) + r.EncodeInt(int64(x.FullyLabeledReplicas)) } } else { r.EncodeInt(0) @@ -27004,11 +27005,36 @@ func (x *ReplicationControllerStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[1] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("observedGeneration")) + r.EncodeString(codecSelferC_UTF81234, string("fullyLabeledReplicas")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym8 := z.EncBinary() _ = yym8 if false { + } else { + r.EncodeInt(int64(x.FullyLabeledReplicas)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeInt(int64(x.ObservedGeneration)) + } + } else { + r.EncodeInt(0) + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("observedGeneration")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym11 := z.EncBinary() + _ = yym11 + if false { } else { r.EncodeInt(int64(x.ObservedGeneration)) } @@ -27081,6 +27107,12 @@ func (x *ReplicationControllerStatus) codecDecodeSelfFromMap(l int, d *codec1978 } else { x.Replicas = int(r.DecodeInt(codecSelferBitsize1234)) } + case "fullyLabeledReplicas": + if r.TryDecodeAsNil() { + x.FullyLabeledReplicas = 0 + } else { + x.FullyLabeledReplicas = int(r.DecodeInt(codecSelferBitsize1234)) + } case "observedGeneration": if r.TryDecodeAsNil() { x.ObservedGeneration = 0 @@ -27098,16 +27130,16 @@ func (x *ReplicationControllerStatus) codecDecodeSelfFromArray(l int, d *codec19 var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj6 int - var yyb6 bool - var yyhl6 bool = l >= 0 - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb6 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb6 { + if yyb7 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -27117,13 +27149,29 @@ func (x *ReplicationControllerStatus) codecDecodeSelfFromArray(l int, d *codec19 } else { x.Replicas = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb6 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb6 { + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.FullyLabeledReplicas = 0 + } else { + x.FullyLabeledReplicas = int(r.DecodeInt(codecSelferBitsize1234)) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -27134,17 +27182,17 @@ func (x *ReplicationControllerStatus) codecDecodeSelfFromArray(l int, d *codec19 x.ObservedGeneration = int64(r.DecodeInt(64)) } for { - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb6 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb6 { + if yyb7 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj6-1, "") + z.DecStructFieldNotFound(yyj7-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -33225,11 +33273,12 @@ func (x *NodeStatus) CodecEncodeSelf(e *codec1978.Encoder) { yyq2[4] = len(x.Addresses) != 0 yyq2[5] = true yyq2[6] = true + yyq2[7] = len(x.Images) != 0 var yynn2 int if yyr2 || yy2arr2 { r.EncodeArrayStart(8) } else { - yynn2 = 1 + yynn2 = 0 for _, b := range yyq2 { if b { yynn2++ @@ -33401,28 +33450,34 @@ func (x *NodeStatus) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if x.Images == nil { - r.EncodeNil() - } else { - yym29 := z.EncBinary() - _ = yym29 - if false { + if yyq2[7] { + if x.Images == nil { + r.EncodeNil() } else { - h.encSliceContainerImage(([]ContainerImage)(x.Images), e) + yym29 := z.EncBinary() + _ = yym29 + if false { + } else { + h.encSliceContainerImage(([]ContainerImage)(x.Images), e) + } } + } else { + r.EncodeNil() } } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("images")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.Images == nil { - r.EncodeNil() - } else { - yym30 := z.EncBinary() - _ = yym30 - if false { + if yyq2[7] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("images")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Images == nil { + r.EncodeNil() } else { - h.encSliceContainerImage(([]ContainerImage)(x.Images), e) + yym30 := z.EncBinary() + _ = yym30 + if false { + } else { + h.encSliceContainerImage(([]ContainerImage)(x.Images), e) + } } } } @@ -33754,7 +33809,7 @@ func (x *ContainerImage) CodecEncodeSelf(e *codec1978.Encoder) { var yyq2 [2]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[1] = x.Size != 0 + yyq2[1] = x.SizeBytes != 0 var yynn2 int if yyr2 || yy2arr2 { r.EncodeArrayStart(2) @@ -33770,28 +33825,28 @@ func (x *ContainerImage) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if x.RepoTags == nil { + if x.Names == nil { r.EncodeNil() } else { yym4 := z.EncBinary() _ = yym4 if false { } else { - z.F.EncSliceStringV(x.RepoTags, false, e) + z.F.EncSliceStringV(x.Names, false, e) } } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("repoTags")) + r.EncodeString(codecSelferC_UTF81234, string("names")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.RepoTags == nil { + if x.Names == nil { r.EncodeNil() } else { yym5 := z.EncBinary() _ = yym5 if false { } else { - z.F.EncSliceStringV(x.RepoTags, false, e) + z.F.EncSliceStringV(x.Names, false, e) } } } @@ -33802,7 +33857,7 @@ func (x *ContainerImage) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym7 if false { } else { - r.EncodeInt(int64(x.Size)) + r.EncodeInt(int64(x.SizeBytes)) } } else { r.EncodeInt(0) @@ -33810,13 +33865,13 @@ func (x *ContainerImage) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[1] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("size")) + r.EncodeString(codecSelferC_UTF81234, string("sizeBytes")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym8 := z.EncBinary() _ = yym8 if false { } else { - r.EncodeInt(int64(x.Size)) + r.EncodeInt(int64(x.SizeBytes)) } } } @@ -33881,11 +33936,11 @@ func (x *ContainerImage) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { yys3 := string(yys3Slc) z.DecSendContainerState(codecSelfer_containerMapValue1234) switch yys3 { - case "repoTags": + case "names": if r.TryDecodeAsNil() { - x.RepoTags = nil + x.Names = nil } else { - yyv4 := &x.RepoTags + yyv4 := &x.Names yym5 := z.DecBinary() _ = yym5 if false { @@ -33893,11 +33948,11 @@ func (x *ContainerImage) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { z.F.DecSliceStringX(yyv4, false, d) } } - case "size": + case "sizeBytes": if r.TryDecodeAsNil() { - x.Size = 0 + x.SizeBytes = 0 } else { - x.Size = int64(r.DecodeInt(64)) + x.SizeBytes = int64(r.DecodeInt(64)) } default: z.DecStructFieldNotFound(-1, yys3) @@ -33925,9 +33980,9 @@ func (x *ContainerImage) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.RepoTags = nil + x.Names = nil } else { - yyv8 := &x.RepoTags + yyv8 := &x.Names yym9 := z.DecBinary() _ = yym9 if false { @@ -33947,9 +34002,9 @@ func (x *ContainerImage) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.Size = 0 + x.SizeBytes = 0 } else { - x.Size = int64(r.DecodeInt(64)) + x.SizeBytes = int64(r.DecodeInt(64)) } for { yyj7++ @@ -36967,6 +37022,209 @@ func (x *Binding) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } +func (x *Preconditions) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [1]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.UID != nil + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(1) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + if x.UID == nil { + r.EncodeNil() + } else { + yy4 := *x.UID + yym5 := z.EncBinary() + _ = yym5 + if false { + } else if z.HasExtensions() && z.EncExt(yy4) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(yy4)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("uid")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.UID == nil { + r.EncodeNil() + } else { + yy6 := *x.UID + yym7 := z.EncBinary() + _ = yym7 + if false { + } else if z.HasExtensions() && z.EncExt(yy6) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(yy6)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *Preconditions) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *Preconditions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "uid": + if r.TryDecodeAsNil() { + if x.UID != nil { + x.UID = nil + } + } else { + if x.UID == nil { + x.UID = new(pkg1_types.UID) + } + yym5 := z.DecBinary() + _ = yym5 + if false { + } else if z.HasExtensions() && z.DecExt(x.UID) { + } else { + *((*string)(x.UID)) = r.DecodeString() + } + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *Preconditions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj6 int + var yyb6 bool + var yyhl6 bool = l >= 0 + yyj6++ + if yyhl6 { + yyb6 = yyj6 > l + } else { + yyb6 = r.CheckBreak() + } + if yyb6 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.UID != nil { + x.UID = nil + } + } else { + if x.UID == nil { + x.UID = new(pkg1_types.UID) + } + yym8 := z.DecBinary() + _ = yym8 + if false { + } else if z.HasExtensions() && z.DecExt(x.UID) { + } else { + *((*string)(x.UID)) = r.DecodeString() + } + } + for { + yyj6++ + if yyhl6 { + yyb6 = yyj6 > l + } else { + yyb6 = r.CheckBreak() + } + if yyb6 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj6-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -36981,16 +37239,18 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [3]bool + var yyq2 [4]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[1] = x.Kind != "" - yyq2[2] = x.APIVersion != "" + yyq2[0] = x.GracePeriodSeconds != nil + yyq2[1] = x.Preconditions != nil + yyq2[2] = x.Kind != "" + yyq2[3] = x.APIVersion != "" var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(3) + r.EncodeArrayStart(4) } else { - yynn2 = 1 + yynn2 = 0 for _, b := range yyq2 { if b { yynn2++ @@ -37001,55 +37261,59 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if x.GracePeriodSeconds == nil { - r.EncodeNil() - } else { - yy4 := *x.GracePeriodSeconds - yym5 := z.EncBinary() - _ = yym5 - if false { + if yyq2[0] { + if x.GracePeriodSeconds == nil { + r.EncodeNil() } else { - r.EncodeInt(int64(yy4)) + yy4 := *x.GracePeriodSeconds + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeInt(int64(yy4)) + } } + } else { + r.EncodeNil() } } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("gracePeriodSeconds")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.GracePeriodSeconds == nil { - r.EncodeNil() - } else { - yy6 := *x.GracePeriodSeconds - yym7 := z.EncBinary() - _ = yym7 - if false { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("gracePeriodSeconds")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.GracePeriodSeconds == nil { + r.EncodeNil() } else { - r.EncodeInt(int64(yy6)) + yy6 := *x.GracePeriodSeconds + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeInt(int64(yy6)) + } } } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[1] { - yym9 := z.EncBinary() - _ = yym9 - if false { + if x.Preconditions == nil { + r.EncodeNil() } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + x.Preconditions.CodecEncodeSelf(e) } } else { - r.EncodeString(codecSelferC_UTF81234, "") + r.EncodeNil() } } else { if yyq2[1] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("kind")) + r.EncodeString(codecSelferC_UTF81234, string("preconditions")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym10 := z.EncBinary() - _ = yym10 - if false { + if x.Preconditions == nil { + r.EncodeNil() } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + x.Preconditions.CodecEncodeSelf(e) } } } @@ -37060,7 +37324,7 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym12 if false { } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) } } else { r.EncodeString(codecSelferC_UTF81234, "") @@ -37068,11 +37332,36 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[2] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + r.EncodeString(codecSelferC_UTF81234, string("kind")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym13 := z.EncBinary() _ = yym13 if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym15 := z.EncBinary() + _ = yym15 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym16 := z.EncBinary() + _ = yym16 + if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) } @@ -37155,6 +37444,17 @@ func (x *DeleteOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { *((*int64)(x.GracePeriodSeconds)) = int64(r.DecodeInt(64)) } } + case "preconditions": + if r.TryDecodeAsNil() { + if x.Preconditions != nil { + x.Preconditions = nil + } + } else { + if x.Preconditions == nil { + x.Preconditions = new(Preconditions) + } + x.Preconditions.CodecDecodeSelf(d) + } case "kind": if r.TryDecodeAsNil() { x.Kind = "" @@ -37178,16 +37478,16 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj8 int - var yyb8 bool - var yyhl8 bool = l >= 0 - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + var yyj9 int + var yyb9 bool + var yyhl9 bool = l >= 0 + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -37200,20 +37500,41 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if x.GracePeriodSeconds == nil { x.GracePeriodSeconds = new(int64) } - yym10 := z.DecBinary() - _ = yym10 + yym11 := z.DecBinary() + _ = yym11 if false { } else { *((*int64)(x.GracePeriodSeconds)) = int64(r.DecodeInt(64)) } } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.Preconditions != nil { + x.Preconditions = nil + } + } else { + if x.Preconditions == nil { + x.Preconditions = new(Preconditions) + } + x.Preconditions.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -37223,13 +37544,13 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } else { x.Kind = string(r.DecodeString()) } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -37240,17 +37561,17 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { x.APIVersion = string(r.DecodeString()) } for { - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj8-1, "") + z.DecStructFieldNotFound(yyj9-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -39935,6 +40256,522 @@ func (x *PodProxyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } +func (x *NodeProxyOptions) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [3]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[1] = x.Kind != "" + yyq2[2] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(3) + } else { + yynn2 = 1 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Path)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("Path")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Path)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym11 := z.EncBinary() + _ = yym11 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *NodeProxyOptions) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *NodeProxyOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "Path": + if r.TryDecodeAsNil() { + x.Path = "" + } else { + x.Path = string(r.DecodeString()) + } + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *NodeProxyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Path = "" + } else { + x.Path = string(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj7-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *ServiceProxyOptions) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [3]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[1] = x.Kind != "" + yyq2[2] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(3) + } else { + yynn2 = 1 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Path)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("Path")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Path)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym11 := z.EncBinary() + _ = yym11 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *ServiceProxyOptions) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *ServiceProxyOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "Path": + if r.TryDecodeAsNil() { + x.Path = "" + } else { + x.Path = string(r.DecodeString()) + } + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *ServiceProxyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Path = "" + } else { + x.Path = string(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj7-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + func (x *ObjectReference) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -43671,6 +44508,32 @@ func (x *LimitRangeList) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } +func (x ResourceQuotaScope) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x)) + } +} + +func (x *ResourceQuotaScope) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + *((*string)(x)) = r.DecodeString() + } +} + func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -43685,13 +44548,14 @@ func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [1]bool + var yyq2 [2]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[0] = len(x.Hard) != 0 + yyq2[1] = len(x.Scopes) != 0 var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(1) + r.EncodeArrayStart(2) } else { yynn2 = 0 for _, b := range yyq2 { @@ -43725,6 +44589,39 @@ func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) { } } } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + if x.Scopes == nil { + r.EncodeNil() + } else { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + h.encSliceResourceQuotaScope(([]ResourceQuotaScope)(x.Scopes), e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("scopes")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Scopes == nil { + r.EncodeNil() + } else { + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + h.encSliceResourceQuotaScope(([]ResourceQuotaScope)(x.Scopes), e) + } + } + } + } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayEnd1234) } else { @@ -43793,6 +44690,18 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) yyv4 := &x.Hard yyv4.CodecDecodeSelf(d) } + case "scopes": + if r.TryDecodeAsNil() { + x.Scopes = nil + } else { + yyv5 := &x.Scopes + yym6 := z.DecBinary() + _ = yym6 + if false { + } else { + h.decSliceResourceQuotaScope((*[]ResourceQuotaScope)(yyv5), d) + } + } default: z.DecStructFieldNotFound(-1, yys3) } // end switch yys3 @@ -43804,16 +44713,16 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj5 int - var yyb5 bool - var yyhl5 bool = l >= 0 - yyj5++ - if yyhl5 { - yyb5 = yyj5 > l + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb5 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb5 { + if yyb7 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -43821,21 +44730,43 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder if r.TryDecodeAsNil() { x.Hard = nil } else { - yyv6 := &x.Hard - yyv6.CodecDecodeSelf(d) + yyv8 := &x.Hard + yyv8.CodecDecodeSelf(d) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Scopes = nil + } else { + yyv9 := &x.Scopes + yym10 := z.DecBinary() + _ = yym10 + if false { + } else { + h.decSliceResourceQuotaScope((*[]ResourceQuotaScope)(yyv9), d) + } } for { - yyj5++ - if yyhl5 { - yyb5 = yyj5 > l + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb5 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb5 { + if yyb7 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj5-1, "") + z.DecStructFieldNotFound(yyj7-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -50779,7 +51710,7 @@ func (x codecSelfer1234) decSliceReplicationController(v *[]ReplicationControlle yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 232) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 240) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] @@ -53252,6 +54183,116 @@ func (x codecSelfer1234) decSliceLimitRange(v *[]LimitRange, d *codec1978.Decode } } +func (x codecSelfer1234) encSliceResourceQuotaScope(v []ResourceQuotaScope, e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + r.EncodeArrayStart(len(v)) + for _, yyv1 := range v { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yyv1.CodecEncodeSelf(e) + } + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x codecSelfer1234) decSliceResourceQuotaScope(v *[]ResourceQuotaScope, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + + yyv1 := *v + yyh1, yyl1 := z.DecSliceHelperStart() + var yyc1 bool + _ = yyc1 + if yyl1 == 0 { + if yyv1 == nil { + yyv1 = []ResourceQuotaScope{} + yyc1 = true + } else if len(yyv1) != 0 { + yyv1 = yyv1[:0] + yyc1 = true + } + } else if yyl1 > 0 { + var yyrr1, yyrl1 int + var yyrt1 bool + _, _ = yyrl1, yyrt1 + yyrr1 = yyl1 // len(yyv1) + if yyl1 > cap(yyv1) { + + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 16) + if yyrt1 { + if yyrl1 <= cap(yyv1) { + yyv1 = yyv1[:yyrl1] + } else { + yyv1 = make([]ResourceQuotaScope, yyrl1) + } + } else { + yyv1 = make([]ResourceQuotaScope, yyrl1) + } + yyc1 = true + yyrr1 = len(yyv1) + } else if yyl1 != len(yyv1) { + yyv1 = yyv1[:yyl1] + yyc1 = true + } + yyj1 := 0 + for ; yyj1 < yyrr1; yyj1++ { + yyh1.ElemContainerState(yyj1) + if r.TryDecodeAsNil() { + yyv1[yyj1] = "" + } else { + yyv1[yyj1] = ResourceQuotaScope(r.DecodeString()) + } + + } + if yyrt1 { + for ; yyj1 < yyl1; yyj1++ { + yyv1 = append(yyv1, "") + yyh1.ElemContainerState(yyj1) + if r.TryDecodeAsNil() { + yyv1[yyj1] = "" + } else { + yyv1[yyj1] = ResourceQuotaScope(r.DecodeString()) + } + + } + } + + } else { + yyj1 := 0 + for ; !r.CheckBreak(); yyj1++ { + + if yyj1 >= len(yyv1) { + yyv1 = append(yyv1, "") // var yyz1 ResourceQuotaScope + yyc1 = true + } + yyh1.ElemContainerState(yyj1) + if yyj1 < len(yyv1) { + if r.TryDecodeAsNil() { + yyv1[yyj1] = "" + } else { + yyv1[yyj1] = ResourceQuotaScope(r.DecodeString()) + } + + } else { + z.DecSwallow() + } + + } + if yyj1 < len(yyv1) { + yyv1 = yyv1[:yyj1] + yyc1 = true + } else if yyj1 == 0 && yyv1 == nil { + yyv1 = []ResourceQuotaScope{} + yyc1 = true + } + } + yyh1.End() + if yyc1 { + *v = yyv1 + } +} + func (x codecSelfer1234) encSliceResourceQuota(v []ResourceQuota, e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -53291,7 +54332,7 @@ func (x codecSelfer1234) decSliceResourceQuota(v *[]ResourceQuota, d *codec1978. yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 216) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 240) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] diff --git a/vendor/k8s.io/kubernetes/pkg/api/types.go b/vendor/k8s.io/kubernetes/pkg/api/types.go index e945ea6a3..69c8a876a 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/types.go +++ b/vendor/k8s.io/kubernetes/pkg/api/types.go @@ -98,7 +98,7 @@ type ObjectMeta struct { ResourceVersion string `json:"resourceVersion,omitempty"` // A sequence number representing a specific generation of the desired state. - // Currently only implemented by replication controllers. + // Populated by the system. Read-only. Generation int64 `json:"generation,omitempty"` // CreationTimestamp is a timestamp representing the server time when this object was @@ -305,7 +305,7 @@ const ( // PersistentVolumeReclaimDelete means the volume will be deleted from Kubernetes on release from its claim. // The volume plugin must support Deletion. PersistentVolumeReclaimDelete PersistentVolumeReclaimPolicy = "Delete" - // PersistentVolumeReclaimRetain means the volume will left in its current phase (Released) for manual reclamation by the administrator. + // PersistentVolumeReclaimRetain means the volume will be left in its current phase (Released) for manual reclamation by the administrator. // The default policy is Retain. PersistentVolumeReclaimRetain PersistentVolumeReclaimPolicy = "Retain" ) @@ -429,7 +429,7 @@ const ( StorageMediumMemory StorageMedium = "Memory" // use memory (tmpfs) ) -// Protocol defines network protocols supported for things like conatiner ports. +// Protocol defines network protocols supported for things like container ports. type Protocol string const ( @@ -441,10 +441,10 @@ const ( // Represents a Persistent Disk resource in Google Compute Engine. // -// A GCE PD must exist and be formatted before mounting to a container. -// The disk must also be in the same GCE project and zone as the kubelet. -// A GCE PD can only be mounted as read/write once. -// GCE PDs support ownership management and SELinux relabeling. +// A GCE PD must exist before mounting to a container. The disk must +// also be in the same GCE project and zone as the kubelet. A GCE PD +// can only be mounted as read/write once or read-only many times. GCE +// PDs support ownership management and SELinux relabeling. type GCEPersistentDiskVolumeSource struct { // Unique name of the PD resource. Used to identify the disk in GCE PDName string `json:"pdName"` @@ -523,10 +523,10 @@ type FlexVolumeSource struct { // Represents a Persistent Disk resource in AWS. // -// An AWS EBS disk must exist and be formatted before mounting to a container. -// The disk must also be in the same AWS zone as the kubelet. -// A AWS EBS disk can only be mounted as read/write once. -// AWS EBS volumes support ownership management and SELinux relabeling. +// An AWS EBS disk must exist before mounting to a container. The disk +// must also be in the same AWS zone as the kubelet. A AWS EBS disk +// can only be mounted as read/write once. AWS EBS volumes support +// ownership management and SELinux relabeling. type AWSElasticBlockStoreVolumeSource struct { // Unique id of the persistent disk resource. Used to identify the disk in AWS VolumeID string `json:"volumeID"` @@ -623,10 +623,10 @@ type RBDVolumeSource struct { ReadOnly bool `json:"readOnly,omitempty"` } -// Represents a cinder volume resource in Openstack. -// A Cinder volume must exist and be formatted before mounting to a container. -// The volume must also be in the same region as the kubelet. -// Cinder volumes support ownership management and SELinux relabeling. +// Represents a cinder volume resource in Openstack. A Cinder volume +// must exist before mounting to a container. The volume must also be +// in the same region as the kubelet. Cinder volumes support ownership +// management and SELinux relabeling. type CinderVolumeSource struct { // Unique id of the volume used to identify the cinder volume VolumeID string `json:"volumeID"` @@ -742,7 +742,7 @@ type VolumeMount struct { Name string `json:"name"` // Optional: Defaults to false (read-write). ReadOnly bool `json:"readOnly,omitempty"` - // Required. + // Required. Must not contain ':'. MountPath string `json:"mountPath"` } @@ -1017,7 +1017,7 @@ type ContainerStatus struct { Name string `json:"name"` State ContainerState `json:"state,omitempty"` LastTerminationState ContainerState `json:"lastState,omitempty"` - // Ready specifies whether the conatiner has passed its readiness check. + // Ready specifies whether the container has passed its readiness check. Ready bool `json:"ready"` // Note that this is calculated from dead containers. But those containers are subject to // garbage collection. This value will get capped at 5 by GC. @@ -1380,6 +1380,9 @@ type ReplicationControllerStatus struct { // Replicas is the number of actual replicas. Replicas int `json:"replicas"` + // The number of pods that have labels matching the labels of the pod template of the replication controller. + FullyLabeledReplicas int `json:"fullyLabeledReplicas,omitempty"` + // ObservedGeneration is the most recent generation observed by the controller. ObservedGeneration int64 `json:"observedGeneration,omitempty"` } @@ -1526,8 +1529,10 @@ type ServicePort struct { // Optional: The target port on pods selected by this service. If this // is a string, it will be looked up as a named port in the target - // Pod's container ports. If this is not specified, the default value - // is the sames as the Port field (an identity map). + // Pod's container ports. If this is not specified, the value + // of the 'port' field is used (an identity map). + // This field is ignored for services with clusterIP=None, and should be + // omitted or set equal to the 'port' field. TargetPort intstr.IntOrString `json:"targetPort"` // The port on each node on which this service is exposed. @@ -1666,8 +1671,14 @@ type NodeSpec struct { // DaemonEndpoint contains information about a single Daemon endpoint. type DaemonEndpoint struct { + /* + The port tag was not properly in quotes in earlier releases, so it must be + uppercased for backwards compat (since it was falling back to var name of + 'Port'). + */ + // Port number of the given endpoint. - Port int `json:port` + Port int `json:"Port"` } // NodeDaemonEndpoints lists ports opened by daemons running on the Node. @@ -1713,15 +1724,15 @@ type NodeStatus struct { // Set of ids/uuids to uniquely identify the node. NodeInfo NodeSystemInfo `json:"nodeInfo,omitempty"` // List of container images on this node - Images []ContainerImage `json:"images",omitempty` + Images []ContainerImage `json:"images,omitempty"` } // Describe a container image type ContainerImage struct { // Names by which this image is known. - RepoTags []string `json:"repoTags"` + Names []string `json:"names"` // The size of the image in bytes. - Size int64 `json:"size,omitempty"` + SizeBytes int64 `json:"sizeBytes,omitempty"` } type NodePhase string @@ -1882,6 +1893,12 @@ type Binding struct { Target ObjectReference `json:"target"` } +// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. +type Preconditions struct { + // Specifies the target UID. + UID *types.UID `json:"uid,omitempty"` +} + // DeleteOptions may be provided when deleting an API object type DeleteOptions struct { unversioned.TypeMeta `json:",inline"` @@ -1889,7 +1906,11 @@ type DeleteOptions struct { // Optional duration in seconds before the object should be deleted. Value must be non-negative integer. // The value zero indicates delete immediately. If this value is nil, the default grace period for the // specified type will be used. - GracePeriodSeconds *int64 `json:"gracePeriodSeconds"` + GracePeriodSeconds *int64 `json:"gracePeriodSeconds,omitempty"` + + // Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be + // returned. + Preconditions *Preconditions `json:"preconditions,omitempty"` } // ExportOptions is the query options to the standard REST get call. @@ -1934,7 +1955,7 @@ type PodLogOptions struct { // Only one of sinceSeconds or sinceTime may be specified. SinceSeconds *int64 // An RFC3339 timestamp from which to show logs. If this value - // preceeds the time a pod was started, only logs since the pod start will be returned. + // precedes the time a pod was started, only logs since the pod start will be returned. // If this value is in the future, no logs will be returned. // Only one of sinceSeconds or sinceTime may be specified. SinceTime *unversioned.Time @@ -2002,6 +2023,26 @@ type PodProxyOptions struct { Path string } +// NodeProxyOptions is the query options to a Node's proxy call +type NodeProxyOptions struct { + unversioned.TypeMeta + + // Path is the URL path to use for the current proxy request + Path string +} + +// ServiceProxyOptions is the query options to a Service's proxy call. +type ServiceProxyOptions struct { + unversioned.TypeMeta + + // Path is the part of URLs that include service endpoints, suffixes, + // and parameters to use for the current proxy request to service. + // For example, the whole request URL is + // http://localhost/api/v1/namespaces/kube-system/services/elasticsearch-logging/_search?q=user:kimchy. + // Path is _search?q=user:kimchy. + Path string +} + // ObjectReference contains enough information to let you inspect or modify the referred object. type ObjectReference struct { Kind string `json:"kind,omitempty"` @@ -2165,14 +2206,43 @@ const ( ResourceQuotas ResourceName = "resourcequotas" // ResourceSecrets, number ResourceSecrets ResourceName = "secrets" + // ResourceConfigMaps, number + ResourceConfigMaps ResourceName = "configmaps" // ResourcePersistentVolumeClaims, number ResourcePersistentVolumeClaims ResourceName = "persistentvolumeclaims" + // ResourceServicesNodePorts, number + ResourceServicesNodePorts ResourceName = "services.nodeports" + // CPU request, in cores. (500m = .5 cores) + ResourceRequestsCPU ResourceName = "requests.cpu" + // Memory request, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceRequestsMemory ResourceName = "requests.memory" + // CPU limit, in cores. (500m = .5 cores) + ResourceLimitsCPU ResourceName = "limits.cpu" + // Memory limit, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceLimitsMemory ResourceName = "limits.memory" +) + +// A ResourceQuotaScope defines a filter that must match each object tracked by a quota +type ResourceQuotaScope string + +const ( + // Match all pod objects where spec.activeDeadlineSeconds + ResourceQuotaScopeTerminating ResourceQuotaScope = "Terminating" + // Match all pod objects where !spec.activeDeadlineSeconds + ResourceQuotaScopeNotTerminating ResourceQuotaScope = "NotTerminating" + // Match all pod objects that have best effort quality of service + ResourceQuotaScopeBestEffort ResourceQuotaScope = "BestEffort" + // Match all pod objects that do not have best effort quality of service + ResourceQuotaScopeNotBestEffort ResourceQuotaScope = "NotBestEffort" ) // ResourceQuotaSpec defines the desired hard limits to enforce for Quota type ResourceQuotaSpec struct { // Hard is the set of desired hard limits for each named resource Hard ResourceList `json:"hard,omitempty"` + // A collection of filters that must match each object tracked by a quota. + // If not specified, the quota matches all objects. + Scopes []ResourceQuotaScope `json:"scopes,omitempty"` } // ResourceQuotaStatus defines the enforced hard limits and observed use diff --git a/vendor/k8s.io/kubernetes/pkg/api/unversioned/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/api/unversioned/deep_copy_generated.go new file mode 100644 index 000000000..5c02ee7b4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/api/unversioned/deep_copy_generated.go @@ -0,0 +1,121 @@ +// +build !ignore_autogenerated + +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package unversioned + +import ( + conversion "k8s.io/kubernetes/pkg/conversion" + time "time" +) + +func DeepCopy_unversioned_Duration(in Duration, out *Duration, c *conversion.Cloner) error { + out.Duration = in.Duration + return nil +} + +func DeepCopy_unversioned_GroupKind(in GroupKind, out *GroupKind, c *conversion.Cloner) error { + out.Group = in.Group + out.Kind = in.Kind + return nil +} + +func DeepCopy_unversioned_GroupResource(in GroupResource, out *GroupResource, c *conversion.Cloner) error { + out.Group = in.Group + out.Resource = in.Resource + return nil +} + +func DeepCopy_unversioned_GroupVersion(in GroupVersion, out *GroupVersion, c *conversion.Cloner) error { + out.Group = in.Group + out.Version = in.Version + return nil +} + +func DeepCopy_unversioned_GroupVersionKind(in GroupVersionKind, out *GroupVersionKind, c *conversion.Cloner) error { + out.Group = in.Group + out.Version = in.Version + out.Kind = in.Kind + return nil +} + +func DeepCopy_unversioned_GroupVersionResource(in GroupVersionResource, out *GroupVersionResource, c *conversion.Cloner) error { + out.Group = in.Group + out.Version = in.Version + out.Resource = in.Resource + return nil +} + +func DeepCopy_unversioned_LabelSelector(in LabelSelector, out *LabelSelector, c *conversion.Cloner) error { + if in.MatchLabels != nil { + in, out := in.MatchLabels, &out.MatchLabels + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val + } + } else { + out.MatchLabels = nil + } + if in.MatchExpressions != nil { + in, out := in.MatchExpressions, &out.MatchExpressions + *out = make([]LabelSelectorRequirement, len(in)) + for i := range in { + if err := DeepCopy_unversioned_LabelSelectorRequirement(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.MatchExpressions = nil + } + return nil +} + +func DeepCopy_unversioned_LabelSelectorRequirement(in LabelSelectorRequirement, out *LabelSelectorRequirement, c *conversion.Cloner) error { + out.Key = in.Key + out.Operator = in.Operator + if in.Values != nil { + in, out := in.Values, &out.Values + *out = make([]string, len(in)) + copy(*out, in) + } else { + out.Values = nil + } + return nil +} + +func DeepCopy_unversioned_ListMeta(in ListMeta, out *ListMeta, c *conversion.Cloner) error { + out.SelfLink = in.SelfLink + out.ResourceVersion = in.ResourceVersion + return nil +} + +func DeepCopy_unversioned_Time(in Time, out *Time, c *conversion.Cloner) error { + if newVal, err := c.DeepCopy(in.Time); err != nil { + return err + } else { + out.Time = newVal.(time.Time) + } + return nil +} + +func DeepCopy_unversioned_TypeMeta(in TypeMeta, out *TypeMeta, c *conversion.Cloner) error { + out.Kind = in.Kind + out.APIVersion = in.APIVersion + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/api/unversioned/group_version.go b/vendor/k8s.io/kubernetes/pkg/api/unversioned/group_version.go index dc1dc9672..5d350432c 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/unversioned/group_version.go +++ b/vendor/k8s.io/kubernetes/pkg/api/unversioned/group_version.go @@ -22,6 +22,21 @@ import ( "strings" ) +// ParseResourceArg takes the common style of string which may be either `resource.group.com` or `resource.version.group.com` +// and parses it out into both possibilities. This code takes no responsibility for knowing which representation was intended +// but with a knowledge of all GroupVersions, calling code can take a very good guess. If there are only two segments, then +// `*GroupVersionResource` is nil. +// `resource.group.com` -> `group=com, version=group, resource=resource` and `group=group.com, resource=resource` +func ParseResourceArg(arg string) (*GroupVersionResource, GroupResource) { + var gvr *GroupVersionResource + s := strings.SplitN(arg, ".", 3) + if len(s) == 3 { + gvr = &GroupVersionResource{Group: s[2], Version: s[1], Resource: s[0]} + } + + return gvr, ParseGroupResource(arg) +} + // GroupResource specifies a Group and a Resource, but does not force a version. This is useful for identifying // concepts during lookup stages without having partially valid types // @@ -46,6 +61,17 @@ func (gr *GroupResource) String() string { return gr.Resource + "." + gr.Group } +// ParseGroupResource turns "resource.group" string into a GroupResource struct. Empty strings are allowed +// for each field. +func ParseGroupResource(gr string) GroupResource { + s := strings.SplitN(gr, ".", 2) + if len(s) == 1 { + return GroupResource{Resource: s[0]} + } + + return GroupResource{Group: s[1], Resource: s[0]} +} + // GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion // to avoid automatic coersion. It doesn't use a GroupVersion to avoid custom marshalling // diff --git a/vendor/k8s.io/kubernetes/pkg/api/unversioned/helpers.go b/vendor/k8s.io/kubernetes/pkg/api/unversioned/helpers.go index b26948b4d..b71297ec5 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/unversioned/helpers.go +++ b/vendor/k8s.io/kubernetes/pkg/api/unversioned/helpers.go @@ -25,6 +25,7 @@ import ( // LabelSelectorAsSelector converts the LabelSelector api type into a struct that implements // labels.Selector +// Note: This function should be kept in sync with the selector methods in pkg/labels/selector.go func LabelSelectorAsSelector(ps *LabelSelector) (labels.Selector, error) { if ps == nil { return labels.Nothing(), nil @@ -34,7 +35,7 @@ func LabelSelectorAsSelector(ps *LabelSelector) (labels.Selector, error) { } selector := labels.NewSelector() for k, v := range ps.MatchLabels { - r, err := labels.NewRequirement(k, labels.InOperator, sets.NewString(v)) + r, err := labels.NewRequirement(k, labels.EqualsOperator, sets.NewString(v)) if err != nil { return nil, err } @@ -63,6 +64,55 @@ func LabelSelectorAsSelector(ps *LabelSelector) (labels.Selector, error) { return selector, nil } +// ParseToLabelSelector parses a string representing a selector into a LabelSelector object. +// Note: This function should be kept in sync with the parser in pkg/labels/selector.go +func ParseToLabelSelector(selector string) (*LabelSelector, error) { + reqs, err := labels.ParseToRequirements(selector) + if err != nil { + return nil, fmt.Errorf("couldn't parse the selector string \"%s\": %v", selector, err) + } + + labelSelector := &LabelSelector{ + MatchLabels: map[string]string{}, + MatchExpressions: []LabelSelectorRequirement{}, + } + for _, req := range reqs { + var op LabelSelectorOperator + switch req.Operator() { + case labels.EqualsOperator, labels.DoubleEqualsOperator: + vals := req.Values() + if vals.Len() != 1 { + return nil, fmt.Errorf("equals operator must have exactly one value") + } + val, ok := vals.PopAny() + if !ok { + return nil, fmt.Errorf("equals operator has exactly one value but it cannot be retrieved") + } + labelSelector.MatchLabels[req.Key()] = val + continue + case labels.InOperator: + op = LabelSelectorOpIn + case labels.NotInOperator: + op = LabelSelectorOpNotIn + case labels.ExistsOperator: + op = LabelSelectorOpExists + case labels.DoesNotExistOperator: + op = LabelSelectorOpDoesNotExist + case labels.GreaterThanOperator, labels.LessThanOperator: + // Adding a separate case for these operators to indicate that this is deliberate + return nil, fmt.Errorf("%q isn't supported in label selectors", req.Operator()) + default: + return nil, fmt.Errorf("%q is not a valid label selector operator", req.Operator()) + } + labelSelector.MatchExpressions = append(labelSelector.MatchExpressions, LabelSelectorRequirement{ + Key: req.Key(), + Operator: op, + Values: req.Values().List(), + }) + } + return labelSelector, nil +} + // SetAsLabelSelector converts the labels.Set object into a LabelSelector api object. func SetAsLabelSelector(ls labels.Set) *LabelSelector { if ls == nil { @@ -92,3 +142,13 @@ func FormatLabelSelector(labelSelector *LabelSelector) string { } return l } + +func ExtractGroupVersions(l *APIGroupList) []string { + var groupVersions []string + for _, g := range l.Groups { + for _, gv := range g.Versions { + groupVersions = append(groupVersions, gv.GroupVersion) + } + } + return groupVersions +} diff --git a/vendor/k8s.io/kubernetes/pkg/api/unversioned/helpers_test.go b/vendor/k8s.io/kubernetes/pkg/api/unversioned/helpers_test.go index 334c78597..f803d4366 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/unversioned/helpers_test.go +++ b/vendor/k8s.io/kubernetes/pkg/api/unversioned/helpers_test.go @@ -46,7 +46,7 @@ func TestLabelSelectorAsSelector(t *testing.T) { {in: &LabelSelector{}, out: labels.Everything()}, { in: &LabelSelector{MatchLabels: matchLabels}, - out: mustParse("foo in (bar)"), + out: mustParse("foo=bar"), }, { in: &LabelSelector{MatchExpressions: matchExpressions}, @@ -54,7 +54,7 @@ func TestLabelSelectorAsSelector(t *testing.T) { }, { in: &LabelSelector{MatchLabels: matchLabels, MatchExpressions: matchExpressions}, - out: mustParse("foo in (bar),baz in (norf,qux)"), + out: mustParse("baz in (norf,qux),foo=bar"), }, { in: &LabelSelector{ diff --git a/vendor/k8s.io/kubernetes/pkg/api/unversioned/time.go b/vendor/k8s.io/kubernetes/pkg/api/unversioned/time.go index 4072833d9..df94bbe72 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/unversioned/time.go +++ b/vendor/k8s.io/kubernetes/pkg/api/unversioned/time.go @@ -28,8 +28,9 @@ import ( // of the factory methods that the time package offers. // // +protobuf.options.marshal=false +// +protobuf.as=Timestamp type Time struct { - time.Time `protobuf:"Timestamp,1,req,name=time"` + time.Time `protobuf:"-"` } // NewTime returns a wrapped instance of the provided time @@ -97,6 +98,27 @@ func (t *Time) UnmarshalJSON(b []byte) error { return nil } +// UnmarshalQueryParameter converts from a URL query parameter value to an object +func (t *Time) UnmarshalQueryParameter(str string) error { + if len(str) == 0 { + t.Time = time.Time{} + return nil + } + // Tolerate requests from older clients that used JSON serialization to build query params + if len(str) == 4 && str == "null" { + t.Time = time.Time{} + return nil + } + + pt, err := time.Parse(time.RFC3339, str) + if err != nil { + return err + } + + t.Time = pt.Local() + return nil +} + // MarshalJSON implements the json.Marshaler interface. func (t Time) MarshalJSON() ([]byte, error) { if t.IsZero() { @@ -107,6 +129,16 @@ func (t Time) MarshalJSON() ([]byte, error) { return json.Marshal(t.UTC().Format(time.RFC3339)) } +// MarshalQueryParameter converts to a URL query parameter value +func (t Time) MarshalQueryParameter() (string, error) { + if t.IsZero() { + // Encode unset/nil objects as an empty string + return "", nil + } + + return t.UTC().Format(time.RFC3339), nil +} + // Fuzz satisfies fuzz.Interface. func (t *Time) Fuzz(c fuzz.Continue) { if t == nil { diff --git a/vendor/k8s.io/kubernetes/pkg/api/unversioned/time_proto.go b/vendor/k8s.io/kubernetes/pkg/api/unversioned/time_proto.go index 6d6fe5d4e..5ca0edcdf 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/unversioned/time_proto.go +++ b/vendor/k8s.io/kubernetes/pkg/api/unversioned/time_proto.go @@ -22,15 +22,9 @@ import ( "time" ) -// ProtoTime is a struct that is equivalent to Time, but intended for +// Timestamp is a struct that is equivalent to Time, but intended for // protobuf marshalling/unmarshalling. It is generated into a serialization // that matches Time. Do not use in Go structs. -type ProtoTime struct { - // Represents the time of an event. - Timestamp Timestamp `json:"timestamp"` -} - -// Timestamp is a protobuf Timestamp compatible representation of time.Time type Timestamp struct { // Represents seconds of UTC time since Unix epoch // 1970-01-01T00:00:00Z. Must be from from 0001-01-01T00:00:00Z to @@ -39,20 +33,18 @@ type Timestamp struct { // Non-negative fractions of a second at nanosecond resolution. Negative // second values with fractions must still have non-negative nanos values // that count forward in time. Must be from 0 to 999,999,999 - // inclusive. + // inclusive. This field may be limited in precision depending on context. Nanos int32 `json:"nanos"` } -// ProtoTime returns the Time as a new ProtoTime value. -func (m *Time) ProtoTime() *ProtoTime { +// Timestamp returns the Time as a new Timestamp value. +func (m *Time) ProtoTime() *Timestamp { if m == nil { - return &ProtoTime{} + return &Timestamp{} } - return &ProtoTime{ - Timestamp: Timestamp{ - Seconds: m.Time.Unix(), - Nanos: int32(m.Time.Nanosecond()), - }, + return &Timestamp{ + Seconds: m.Time.Unix(), + Nanos: int32(m.Time.Nanosecond()), } } @@ -61,11 +53,11 @@ func (m *Time) Size() (n int) { return m.ProtoTime().Size() } // Reset implements the protobuf marshalling interface. func (m *Time) Unmarshal(data []byte) error { - p := ProtoTime{} + p := Timestamp{} if err := p.Unmarshal(data); err != nil { return err } - m.Time = time.Unix(p.Timestamp.Seconds, int64(p.Timestamp.Nanos)) + m.Time = time.Unix(p.Seconds, int64(p.Nanos)) return nil } diff --git a/vendor/k8s.io/kubernetes/pkg/api/unversioned/types.go b/vendor/k8s.io/kubernetes/pkg/api/unversioned/types.go index 715444931..3a3b4147d 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/unversioned/types.go +++ b/vendor/k8s.io/kubernetes/pkg/api/unversioned/types.go @@ -174,10 +174,10 @@ const ( // Status code 409 StatusReasonAlreadyExists StatusReason = "AlreadyExists" - // StatusReasonConflict means the requested update operation cannot be completed - // due to a conflict in the operation. The client may need to alter the request. - // Each resource may define custom details that indicate the nature of the - // conflict. + // StatusReasonConflict means the requested operation cannot be completed + // due to a conflict in the operation. The client may need to alter the + // request. Each resource may define custom details that indicate the + // nature of the conflict. // Status code 409 StatusReasonConflict StatusReason = "Conflict" @@ -308,6 +308,14 @@ type APIVersions struct { TypeMeta `json:",inline"` // versions are the api versions that are available. Versions []string `json:"versions"` + // a map of client CIDR to server address that is serving this group. + // This is to help clients reach servers in the most network-efficient way possible. + // Clients can use the appropriate server address as per the CIDR that they match. + // In case of multiple matches, clients should use the longest matching CIDR. + // The server returns only those CIDRs that it thinks that the client can match. + // For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. + // Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP. + ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs"` } // APIGroupList is a list of APIGroup, to allow clients to discover the API at @@ -329,6 +337,23 @@ type APIGroup struct { // preferredVersion is the version preferred by the API server, which // probably is the storage version. PreferredVersion GroupVersionForDiscovery `json:"preferredVersion,omitempty"` + // a map of client CIDR to server address that is serving this group. + // This is to help clients reach servers in the most network-efficient way possible. + // Clients can use the appropriate server address as per the CIDR that they match. + // In case of multiple matches, clients should use the longest matching CIDR. + // The server returns only those CIDRs that it thinks that the client can match. + // For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. + // Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP. + ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs"` +} + +// ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match. +type ServerAddressByClientCIDR struct { + // The CIDR with which clients can match their IP to figure out the server address that they should use. + ClientCIDR string `json:"clientCIDR"` + // Address of this server, suitable for a client that matches the above CIDR. + // This can be a hostname, hostname:port, IP or IP:port. + ServerAddress string `json:"serverAddress"` } // GroupVersion contains the "group/version" and "version" string of a version. diff --git a/vendor/k8s.io/kubernetes/pkg/api/unversioned/types_swagger_doc_generated.go b/vendor/k8s.io/kubernetes/pkg/api/unversioned/types_swagger_doc_generated.go index a65e88931..e0e74dad1 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/unversioned/types_swagger_doc_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/api/unversioned/types_swagger_doc_generated.go @@ -16,7 +16,7 @@ limitations under the License. package unversioned -// This file contains a collection of methods that can be used from go-resful to +// This file contains a collection of methods that can be used from go-restful to // generate Swagger API documentation for its models. Please read this PR for more // information on the implementation: https://github.com/emicklei/go-restful/pull/215 // @@ -28,10 +28,11 @@ package unversioned // AUTO-GENERATED FUNCTIONS START HERE var map_APIGroup = map[string]string{ - "": "APIGroup contains the name, the supported versions, and the preferred version of a group.", - "name": "name is the name of the group.", - "versions": "versions are the versions supported in this group.", - "preferredVersion": "preferredVersion is the version preferred by the API server, which probably is the storage version.", + "": "APIGroup contains the name, the supported versions, and the preferred version of a group.", + "name": "name is the name of the group.", + "versions": "versions are the versions supported in this group.", + "preferredVersion": "preferredVersion is the version preferred by the API server, which probably is the storage version.", + "serverAddressByClientCIDRs": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.", } func (APIGroup) SwaggerDoc() map[string]string { @@ -69,8 +70,9 @@ func (APIResourceList) SwaggerDoc() map[string]string { } var map_APIVersions = map[string]string{ - "": "APIVersions lists the versions that are available, to allow clients to discover the API at /api, which is the root path of the legacy v1 API.", - "versions": "versions are the api versions that are available.", + "": "APIVersions lists the versions that are available, to allow clients to discover the API at /api, which is the root path of the legacy v1 API.", + "versions": "versions are the api versions that are available.", + "serverAddressByClientCIDRs": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.", } func (APIVersions) SwaggerDoc() map[string]string { @@ -145,6 +147,16 @@ func (RootPaths) SwaggerDoc() map[string]string { return map_RootPaths } +var map_ServerAddressByClientCIDR = map[string]string{ + "": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.", + "clientCIDR": "The CIDR with which clients can match their IP to figure out the server address that they should use.", + "serverAddress": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port.", +} + +func (ServerAddressByClientCIDR) SwaggerDoc() map[string]string { + return map_ServerAddressByClientCIDR +} + var map_Status = map[string]string{ "": "Status is a return value for calls that don't return other objects.", "metadata": "Standard list metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds", diff --git a/vendor/k8s.io/kubernetes/pkg/api/unversioned/well_known_labels.go b/vendor/k8s.io/kubernetes/pkg/api/unversioned/well_known_labels.go index 0815e8083..6c163b784 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/unversioned/well_known_labels.go +++ b/vendor/k8s.io/kubernetes/pkg/api/unversioned/well_known_labels.go @@ -16,6 +16,7 @@ limitations under the License. package unversioned -const LabelZoneFailureDomain = "failure-domain.alpha.kubernetes.io/zone" -const LabelZoneRegion = "failure-domain.alpha.kubernetes.io/region" +const LabelHostname = "kubernetes.io/hostname" +const LabelZoneFailureDomain = "failure-domain.beta.kubernetes.io/zone" +const LabelZoneRegion = "failure-domain.beta.kubernetes.io/region" const LabelInstanceType = "beta.kubernetes.io/instance-type" diff --git a/vendor/k8s.io/kubernetes/pkg/api/v1/conversion.go b/vendor/k8s.io/kubernetes/pkg/api/v1/conversion.go index 1a4d36d43..56ca88f36 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/v1/conversion.go +++ b/vendor/k8s.io/kubernetes/pkg/api/v1/conversion.go @@ -87,7 +87,8 @@ func addConversionFuncs(scheme *runtime.Scheme) { "metadata.annotations", "status.phase", "status.podIP", - "spec.nodeName": + "spec.nodeName", + "spec.restartPolicy": return label, value, nil // This is for backwards compatibility with old v1 clients which send spec.host case "spec.host": @@ -321,7 +322,7 @@ func Convert_api_PodSpec_To_v1_PodSpec(in *api.PodSpec, out *PodSpec, s conversi return err } - // the host namespace fields have to be handled here for backward compatibilty + // the host namespace fields have to be handled here for backward compatibility // with v1.0.0 out.HostPID = in.SecurityContext.HostPID out.HostNetwork = in.SecurityContext.HostNetwork diff --git a/vendor/k8s.io/kubernetes/pkg/api/v1/conversion_generated.go b/vendor/k8s.io/kubernetes/pkg/api/v1/conversion_generated.go index 35b857bf9..6be8cf507 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/v1/conversion_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/api/v1/conversion_generated.go @@ -1,5 +1,7 @@ +// +build !ignore_autogenerated + /* -Copyright 2015 The Kubernetes Authors All rights reserved. +Copyright 2016 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,3290 +16,304 @@ See the License for the specific language governing permissions and limitations under the License. */ -// DO NOT EDIT. THIS FILE IS AUTO-GENERATED BY $KUBEROOT/hack/update-generated-conversions.sh +// This file was autogenerated by conversion-gen. Do not edit it manually! package v1 import ( - reflect "reflect" - api "k8s.io/kubernetes/pkg/api" resource "k8s.io/kubernetes/pkg/api/resource" unversioned "k8s.io/kubernetes/pkg/api/unversioned" conversion "k8s.io/kubernetes/pkg/conversion" runtime "k8s.io/kubernetes/pkg/runtime" + types "k8s.io/kubernetes/pkg/types" + reflect "reflect" ) -func autoConvert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in *api.AWSElasticBlockStoreVolumeSource, out *AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.AWSElasticBlockStoreVolumeSource))(in) +func init() { + if err := api.Scheme.AddGeneratedConversionFuncs( + Convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource, + Convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource, + Convert_v1_Affinity_To_api_Affinity, + Convert_api_Affinity_To_v1_Affinity, + Convert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource, + Convert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource, + Convert_v1_Binding_To_api_Binding, + Convert_api_Binding_To_v1_Binding, + Convert_v1_Capabilities_To_api_Capabilities, + Convert_api_Capabilities_To_v1_Capabilities, + Convert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource, + Convert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource, + Convert_v1_CinderVolumeSource_To_api_CinderVolumeSource, + Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource, + Convert_v1_ComponentCondition_To_api_ComponentCondition, + Convert_api_ComponentCondition_To_v1_ComponentCondition, + Convert_v1_ComponentStatus_To_api_ComponentStatus, + Convert_api_ComponentStatus_To_v1_ComponentStatus, + Convert_v1_ComponentStatusList_To_api_ComponentStatusList, + Convert_api_ComponentStatusList_To_v1_ComponentStatusList, + Convert_v1_ConfigMap_To_api_ConfigMap, + Convert_api_ConfigMap_To_v1_ConfigMap, + Convert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector, + Convert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector, + Convert_v1_ConfigMapList_To_api_ConfigMapList, + Convert_api_ConfigMapList_To_v1_ConfigMapList, + Convert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource, + Convert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource, + Convert_v1_Container_To_api_Container, + Convert_api_Container_To_v1_Container, + Convert_v1_ContainerImage_To_api_ContainerImage, + Convert_api_ContainerImage_To_v1_ContainerImage, + Convert_v1_ContainerPort_To_api_ContainerPort, + Convert_api_ContainerPort_To_v1_ContainerPort, + Convert_v1_ContainerState_To_api_ContainerState, + Convert_api_ContainerState_To_v1_ContainerState, + Convert_v1_ContainerStateRunning_To_api_ContainerStateRunning, + Convert_api_ContainerStateRunning_To_v1_ContainerStateRunning, + Convert_v1_ContainerStateTerminated_To_api_ContainerStateTerminated, + Convert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated, + Convert_v1_ContainerStateWaiting_To_api_ContainerStateWaiting, + Convert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting, + Convert_v1_ContainerStatus_To_api_ContainerStatus, + Convert_api_ContainerStatus_To_v1_ContainerStatus, + Convert_v1_DaemonEndpoint_To_api_DaemonEndpoint, + Convert_api_DaemonEndpoint_To_v1_DaemonEndpoint, + Convert_v1_DeleteOptions_To_api_DeleteOptions, + Convert_api_DeleteOptions_To_v1_DeleteOptions, + Convert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile, + Convert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile, + Convert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource, + Convert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource, + Convert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource, + Convert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource, + Convert_v1_EndpointAddress_To_api_EndpointAddress, + Convert_api_EndpointAddress_To_v1_EndpointAddress, + Convert_v1_EndpointPort_To_api_EndpointPort, + Convert_api_EndpointPort_To_v1_EndpointPort, + Convert_v1_EndpointSubset_To_api_EndpointSubset, + Convert_api_EndpointSubset_To_v1_EndpointSubset, + Convert_v1_Endpoints_To_api_Endpoints, + Convert_api_Endpoints_To_v1_Endpoints, + Convert_v1_EndpointsList_To_api_EndpointsList, + Convert_api_EndpointsList_To_v1_EndpointsList, + Convert_v1_EnvVar_To_api_EnvVar, + Convert_api_EnvVar_To_v1_EnvVar, + Convert_v1_EnvVarSource_To_api_EnvVarSource, + Convert_api_EnvVarSource_To_v1_EnvVarSource, + Convert_v1_Event_To_api_Event, + Convert_api_Event_To_v1_Event, + Convert_v1_EventList_To_api_EventList, + Convert_api_EventList_To_v1_EventList, + Convert_v1_EventSource_To_api_EventSource, + Convert_api_EventSource_To_v1_EventSource, + Convert_v1_ExecAction_To_api_ExecAction, + Convert_api_ExecAction_To_v1_ExecAction, + Convert_v1_ExportOptions_To_api_ExportOptions, + Convert_api_ExportOptions_To_v1_ExportOptions, + Convert_v1_FCVolumeSource_To_api_FCVolumeSource, + Convert_api_FCVolumeSource_To_v1_FCVolumeSource, + Convert_v1_FlexVolumeSource_To_api_FlexVolumeSource, + Convert_api_FlexVolumeSource_To_v1_FlexVolumeSource, + Convert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource, + Convert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource, + Convert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource, + Convert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource, + Convert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource, + Convert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource, + Convert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource, + Convert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource, + Convert_v1_HTTPGetAction_To_api_HTTPGetAction, + Convert_api_HTTPGetAction_To_v1_HTTPGetAction, + Convert_v1_HTTPHeader_To_api_HTTPHeader, + Convert_api_HTTPHeader_To_v1_HTTPHeader, + Convert_v1_Handler_To_api_Handler, + Convert_api_Handler_To_v1_Handler, + Convert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource, + Convert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource, + Convert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource, + Convert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource, + Convert_v1_KeyToPath_To_api_KeyToPath, + Convert_api_KeyToPath_To_v1_KeyToPath, + Convert_v1_Lifecycle_To_api_Lifecycle, + Convert_api_Lifecycle_To_v1_Lifecycle, + Convert_v1_LimitRange_To_api_LimitRange, + Convert_api_LimitRange_To_v1_LimitRange, + Convert_v1_LimitRangeItem_To_api_LimitRangeItem, + Convert_api_LimitRangeItem_To_v1_LimitRangeItem, + Convert_v1_LimitRangeList_To_api_LimitRangeList, + Convert_api_LimitRangeList_To_v1_LimitRangeList, + Convert_v1_LimitRangeSpec_To_api_LimitRangeSpec, + Convert_api_LimitRangeSpec_To_v1_LimitRangeSpec, + Convert_v1_List_To_api_List, + Convert_api_List_To_v1_List, + Convert_v1_ListOptions_To_api_ListOptions, + Convert_api_ListOptions_To_v1_ListOptions, + Convert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress, + Convert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress, + Convert_v1_LoadBalancerStatus_To_api_LoadBalancerStatus, + Convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus, + Convert_v1_LocalObjectReference_To_api_LocalObjectReference, + Convert_api_LocalObjectReference_To_v1_LocalObjectReference, + Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource, + Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource, + Convert_v1_Namespace_To_api_Namespace, + Convert_api_Namespace_To_v1_Namespace, + Convert_v1_NamespaceList_To_api_NamespaceList, + Convert_api_NamespaceList_To_v1_NamespaceList, + Convert_v1_NamespaceSpec_To_api_NamespaceSpec, + Convert_api_NamespaceSpec_To_v1_NamespaceSpec, + Convert_v1_NamespaceStatus_To_api_NamespaceStatus, + Convert_api_NamespaceStatus_To_v1_NamespaceStatus, + Convert_v1_Node_To_api_Node, + Convert_api_Node_To_v1_Node, + Convert_v1_NodeAddress_To_api_NodeAddress, + Convert_api_NodeAddress_To_v1_NodeAddress, + Convert_v1_NodeAffinity_To_api_NodeAffinity, + Convert_api_NodeAffinity_To_v1_NodeAffinity, + Convert_v1_NodeCondition_To_api_NodeCondition, + Convert_api_NodeCondition_To_v1_NodeCondition, + Convert_v1_NodeDaemonEndpoints_To_api_NodeDaemonEndpoints, + Convert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints, + Convert_v1_NodeList_To_api_NodeList, + Convert_api_NodeList_To_v1_NodeList, + Convert_v1_NodeProxyOptions_To_api_NodeProxyOptions, + Convert_api_NodeProxyOptions_To_v1_NodeProxyOptions, + Convert_v1_NodeSelector_To_api_NodeSelector, + Convert_api_NodeSelector_To_v1_NodeSelector, + Convert_v1_NodeSelectorRequirement_To_api_NodeSelectorRequirement, + Convert_api_NodeSelectorRequirement_To_v1_NodeSelectorRequirement, + Convert_v1_NodeSelectorTerm_To_api_NodeSelectorTerm, + Convert_api_NodeSelectorTerm_To_v1_NodeSelectorTerm, + Convert_v1_NodeSpec_To_api_NodeSpec, + Convert_api_NodeSpec_To_v1_NodeSpec, + Convert_v1_NodeStatus_To_api_NodeStatus, + Convert_api_NodeStatus_To_v1_NodeStatus, + Convert_v1_NodeSystemInfo_To_api_NodeSystemInfo, + Convert_api_NodeSystemInfo_To_v1_NodeSystemInfo, + Convert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector, + Convert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector, + Convert_v1_ObjectMeta_To_api_ObjectMeta, + Convert_api_ObjectMeta_To_v1_ObjectMeta, + Convert_v1_ObjectReference_To_api_ObjectReference, + Convert_api_ObjectReference_To_v1_ObjectReference, + Convert_v1_PersistentVolume_To_api_PersistentVolume, + Convert_api_PersistentVolume_To_v1_PersistentVolume, + Convert_v1_PersistentVolumeClaim_To_api_PersistentVolumeClaim, + Convert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim, + Convert_v1_PersistentVolumeClaimList_To_api_PersistentVolumeClaimList, + Convert_api_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList, + Convert_v1_PersistentVolumeClaimSpec_To_api_PersistentVolumeClaimSpec, + Convert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec, + Convert_v1_PersistentVolumeClaimStatus_To_api_PersistentVolumeClaimStatus, + Convert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus, + Convert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource, + Convert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource, + Convert_v1_PersistentVolumeList_To_api_PersistentVolumeList, + Convert_api_PersistentVolumeList_To_v1_PersistentVolumeList, + Convert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource, + Convert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource, + Convert_v1_PersistentVolumeSpec_To_api_PersistentVolumeSpec, + Convert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec, + Convert_v1_PersistentVolumeStatus_To_api_PersistentVolumeStatus, + Convert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus, + Convert_v1_Pod_To_api_Pod, + Convert_api_Pod_To_v1_Pod, + Convert_v1_PodAttachOptions_To_api_PodAttachOptions, + Convert_api_PodAttachOptions_To_v1_PodAttachOptions, + Convert_v1_PodCondition_To_api_PodCondition, + Convert_api_PodCondition_To_v1_PodCondition, + Convert_v1_PodExecOptions_To_api_PodExecOptions, + Convert_api_PodExecOptions_To_v1_PodExecOptions, + Convert_v1_PodList_To_api_PodList, + Convert_api_PodList_To_v1_PodList, + Convert_v1_PodLogOptions_To_api_PodLogOptions, + Convert_api_PodLogOptions_To_v1_PodLogOptions, + Convert_v1_PodProxyOptions_To_api_PodProxyOptions, + Convert_api_PodProxyOptions_To_v1_PodProxyOptions, + Convert_v1_PodSecurityContext_To_api_PodSecurityContext, + Convert_api_PodSecurityContext_To_v1_PodSecurityContext, + Convert_v1_PodSpec_To_api_PodSpec, + Convert_api_PodSpec_To_v1_PodSpec, + Convert_v1_PodStatus_To_api_PodStatus, + Convert_api_PodStatus_To_v1_PodStatus, + Convert_v1_PodStatusResult_To_api_PodStatusResult, + Convert_api_PodStatusResult_To_v1_PodStatusResult, + Convert_v1_PodTemplate_To_api_PodTemplate, + Convert_api_PodTemplate_To_v1_PodTemplate, + Convert_v1_PodTemplateList_To_api_PodTemplateList, + Convert_api_PodTemplateList_To_v1_PodTemplateList, + Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec, + Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec, + Convert_v1_Preconditions_To_api_Preconditions, + Convert_api_Preconditions_To_v1_Preconditions, + Convert_v1_PreferredSchedulingTerm_To_api_PreferredSchedulingTerm, + Convert_api_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm, + Convert_v1_Probe_To_api_Probe, + Convert_api_Probe_To_v1_Probe, + Convert_v1_RBDVolumeSource_To_api_RBDVolumeSource, + Convert_api_RBDVolumeSource_To_v1_RBDVolumeSource, + Convert_v1_RangeAllocation_To_api_RangeAllocation, + Convert_api_RangeAllocation_To_v1_RangeAllocation, + Convert_v1_ReplicationController_To_api_ReplicationController, + Convert_api_ReplicationController_To_v1_ReplicationController, + Convert_v1_ReplicationControllerList_To_api_ReplicationControllerList, + Convert_api_ReplicationControllerList_To_v1_ReplicationControllerList, + Convert_v1_ReplicationControllerSpec_To_api_ReplicationControllerSpec, + Convert_api_ReplicationControllerSpec_To_v1_ReplicationControllerSpec, + Convert_v1_ReplicationControllerStatus_To_api_ReplicationControllerStatus, + Convert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus, + Convert_v1_ResourceQuota_To_api_ResourceQuota, + Convert_api_ResourceQuota_To_v1_ResourceQuota, + Convert_v1_ResourceQuotaList_To_api_ResourceQuotaList, + Convert_api_ResourceQuotaList_To_v1_ResourceQuotaList, + Convert_v1_ResourceQuotaSpec_To_api_ResourceQuotaSpec, + Convert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec, + Convert_v1_ResourceQuotaStatus_To_api_ResourceQuotaStatus, + Convert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus, + Convert_v1_ResourceRequirements_To_api_ResourceRequirements, + Convert_api_ResourceRequirements_To_v1_ResourceRequirements, + Convert_v1_SELinuxOptions_To_api_SELinuxOptions, + Convert_api_SELinuxOptions_To_v1_SELinuxOptions, + Convert_v1_Secret_To_api_Secret, + Convert_api_Secret_To_v1_Secret, + Convert_v1_SecretKeySelector_To_api_SecretKeySelector, + Convert_api_SecretKeySelector_To_v1_SecretKeySelector, + Convert_v1_SecretList_To_api_SecretList, + Convert_api_SecretList_To_v1_SecretList, + Convert_v1_SecretVolumeSource_To_api_SecretVolumeSource, + Convert_api_SecretVolumeSource_To_v1_SecretVolumeSource, + Convert_v1_SecurityContext_To_api_SecurityContext, + Convert_api_SecurityContext_To_v1_SecurityContext, + Convert_v1_SerializedReference_To_api_SerializedReference, + Convert_api_SerializedReference_To_v1_SerializedReference, + Convert_v1_Service_To_api_Service, + Convert_api_Service_To_v1_Service, + Convert_v1_ServiceAccount_To_api_ServiceAccount, + Convert_api_ServiceAccount_To_v1_ServiceAccount, + Convert_v1_ServiceAccountList_To_api_ServiceAccountList, + Convert_api_ServiceAccountList_To_v1_ServiceAccountList, + Convert_v1_ServiceList_To_api_ServiceList, + Convert_api_ServiceList_To_v1_ServiceList, + Convert_v1_ServicePort_To_api_ServicePort, + Convert_api_ServicePort_To_v1_ServicePort, + Convert_v1_ServiceProxyOptions_To_api_ServiceProxyOptions, + Convert_api_ServiceProxyOptions_To_v1_ServiceProxyOptions, + Convert_v1_ServiceSpec_To_api_ServiceSpec, + Convert_api_ServiceSpec_To_v1_ServiceSpec, + Convert_v1_ServiceStatus_To_api_ServiceStatus, + Convert_api_ServiceStatus_To_v1_ServiceStatus, + Convert_v1_TCPSocketAction_To_api_TCPSocketAction, + Convert_api_TCPSocketAction_To_v1_TCPSocketAction, + Convert_v1_Volume_To_api_Volume, + Convert_api_Volume_To_v1_Volume, + Convert_v1_VolumeMount_To_api_VolumeMount, + Convert_api_VolumeMount_To_v1_VolumeMount, + Convert_v1_VolumeSource_To_api_VolumeSource, + Convert_api_VolumeSource_To_v1_VolumeSource, + ); err != nil { + // if one of the conversion functions is malformed, detect it immediately. + panic(err) } - out.VolumeID = in.VolumeID - out.FSType = in.FSType - out.Partition = int32(in.Partition) - out.ReadOnly = in.ReadOnly - return nil -} - -func Convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in *api.AWSElasticBlockStoreVolumeSource, out *AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { - return autoConvert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in, out, s) -} - -func autoConvert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in *api.AzureFileVolumeSource, out *AzureFileVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.AzureFileVolumeSource))(in) - } - out.SecretName = in.SecretName - out.ShareName = in.ShareName - out.ReadOnly = in.ReadOnly - return nil -} - -func Convert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in *api.AzureFileVolumeSource, out *AzureFileVolumeSource, s conversion.Scope) error { - return autoConvert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in, out, s) -} - -func autoConvert_api_Binding_To_v1_Binding(in *api.Binding, out *Binding, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.Binding))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_api_ObjectReference_To_v1_ObjectReference(&in.Target, &out.Target, s); err != nil { - return err - } - return nil -} - -func Convert_api_Binding_To_v1_Binding(in *api.Binding, out *Binding, s conversion.Scope) error { - return autoConvert_api_Binding_To_v1_Binding(in, out, s) -} - -func autoConvert_api_Capabilities_To_v1_Capabilities(in *api.Capabilities, out *Capabilities, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.Capabilities))(in) - } - if in.Add != nil { - out.Add = make([]Capability, len(in.Add)) - for i := range in.Add { - out.Add[i] = Capability(in.Add[i]) - } - } else { - out.Add = nil - } - if in.Drop != nil { - out.Drop = make([]Capability, len(in.Drop)) - for i := range in.Drop { - out.Drop[i] = Capability(in.Drop[i]) - } - } else { - out.Drop = nil - } - return nil -} - -func Convert_api_Capabilities_To_v1_Capabilities(in *api.Capabilities, out *Capabilities, s conversion.Scope) error { - return autoConvert_api_Capabilities_To_v1_Capabilities(in, out, s) -} - -func autoConvert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in *api.CephFSVolumeSource, out *CephFSVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.CephFSVolumeSource))(in) - } - if in.Monitors != nil { - out.Monitors = make([]string, len(in.Monitors)) - for i := range in.Monitors { - out.Monitors[i] = in.Monitors[i] - } - } else { - out.Monitors = nil - } - out.Path = in.Path - out.User = in.User - out.SecretFile = in.SecretFile - // unable to generate simple pointer conversion for api.LocalObjectReference -> v1.LocalObjectReference - if in.SecretRef != nil { - out.SecretRef = new(LocalObjectReference) - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(in.SecretRef, out.SecretRef, s); err != nil { - return err - } - } else { - out.SecretRef = nil - } - out.ReadOnly = in.ReadOnly - return nil -} - -func Convert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in *api.CephFSVolumeSource, out *CephFSVolumeSource, s conversion.Scope) error { - return autoConvert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in, out, s) -} - -func autoConvert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in *api.CinderVolumeSource, out *CinderVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.CinderVolumeSource))(in) - } - out.VolumeID = in.VolumeID - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - return nil -} - -func Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in *api.CinderVolumeSource, out *CinderVolumeSource, s conversion.Scope) error { - return autoConvert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in, out, s) -} - -func autoConvert_api_ComponentCondition_To_v1_ComponentCondition(in *api.ComponentCondition, out *ComponentCondition, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ComponentCondition))(in) - } - out.Type = ComponentConditionType(in.Type) - out.Status = ConditionStatus(in.Status) - out.Message = in.Message - out.Error = in.Error - return nil -} - -func Convert_api_ComponentCondition_To_v1_ComponentCondition(in *api.ComponentCondition, out *ComponentCondition, s conversion.Scope) error { - return autoConvert_api_ComponentCondition_To_v1_ComponentCondition(in, out, s) -} - -func autoConvert_api_ComponentStatus_To_v1_ComponentStatus(in *api.ComponentStatus, out *ComponentStatus, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ComponentStatus))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if in.Conditions != nil { - out.Conditions = make([]ComponentCondition, len(in.Conditions)) - for i := range in.Conditions { - if err := Convert_api_ComponentCondition_To_v1_ComponentCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil { - return err - } - } - } else { - out.Conditions = nil - } - return nil -} - -func Convert_api_ComponentStatus_To_v1_ComponentStatus(in *api.ComponentStatus, out *ComponentStatus, s conversion.Scope) error { - return autoConvert_api_ComponentStatus_To_v1_ComponentStatus(in, out, s) -} - -func autoConvert_api_ComponentStatusList_To_v1_ComponentStatusList(in *api.ComponentStatusList, out *ComponentStatusList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ComponentStatusList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]ComponentStatus, len(in.Items)) - for i := range in.Items { - if err := Convert_api_ComponentStatus_To_v1_ComponentStatus(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_ComponentStatusList_To_v1_ComponentStatusList(in *api.ComponentStatusList, out *ComponentStatusList, s conversion.Scope) error { - return autoConvert_api_ComponentStatusList_To_v1_ComponentStatusList(in, out, s) -} - -func autoConvert_api_ConfigMap_To_v1_ConfigMap(in *api.ConfigMap, out *ConfigMap, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ConfigMap))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if in.Data != nil { - out.Data = make(map[string]string) - for key, val := range in.Data { - out.Data[key] = val - } - } else { - out.Data = nil - } - return nil -} - -func Convert_api_ConfigMap_To_v1_ConfigMap(in *api.ConfigMap, out *ConfigMap, s conversion.Scope) error { - return autoConvert_api_ConfigMap_To_v1_ConfigMap(in, out, s) -} - -func autoConvert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in *api.ConfigMapKeySelector, out *ConfigMapKeySelector, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ConfigMapKeySelector))(in) - } - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Key = in.Key - return nil -} - -func Convert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in *api.ConfigMapKeySelector, out *ConfigMapKeySelector, s conversion.Scope) error { - return autoConvert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in, out, s) -} - -func autoConvert_api_ConfigMapList_To_v1_ConfigMapList(in *api.ConfigMapList, out *ConfigMapList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ConfigMapList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]ConfigMap, len(in.Items)) - for i := range in.Items { - if err := Convert_api_ConfigMap_To_v1_ConfigMap(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_ConfigMapList_To_v1_ConfigMapList(in *api.ConfigMapList, out *ConfigMapList, s conversion.Scope) error { - return autoConvert_api_ConfigMapList_To_v1_ConfigMapList(in, out, s) -} - -func autoConvert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in *api.ConfigMapVolumeSource, out *ConfigMapVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ConfigMapVolumeSource))(in) - } - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]KeyToPath, len(in.Items)) - for i := range in.Items { - if err := Convert_api_KeyToPath_To_v1_KeyToPath(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in *api.ConfigMapVolumeSource, out *ConfigMapVolumeSource, s conversion.Scope) error { - return autoConvert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in, out, s) -} - -func autoConvert_api_Container_To_v1_Container(in *api.Container, out *Container, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.Container))(in) - } - out.Name = in.Name - out.Image = in.Image - if in.Command != nil { - out.Command = make([]string, len(in.Command)) - for i := range in.Command { - out.Command[i] = in.Command[i] - } - } else { - out.Command = nil - } - if in.Args != nil { - out.Args = make([]string, len(in.Args)) - for i := range in.Args { - out.Args[i] = in.Args[i] - } - } else { - out.Args = nil - } - out.WorkingDir = in.WorkingDir - if in.Ports != nil { - out.Ports = make([]ContainerPort, len(in.Ports)) - for i := range in.Ports { - if err := Convert_api_ContainerPort_To_v1_ContainerPort(&in.Ports[i], &out.Ports[i], s); err != nil { - return err - } - } - } else { - out.Ports = nil - } - if in.Env != nil { - out.Env = make([]EnvVar, len(in.Env)) - for i := range in.Env { - if err := Convert_api_EnvVar_To_v1_EnvVar(&in.Env[i], &out.Env[i], s); err != nil { - return err - } - } - } else { - out.Env = nil - } - if err := Convert_api_ResourceRequirements_To_v1_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil { - return err - } - if in.VolumeMounts != nil { - out.VolumeMounts = make([]VolumeMount, len(in.VolumeMounts)) - for i := range in.VolumeMounts { - if err := Convert_api_VolumeMount_To_v1_VolumeMount(&in.VolumeMounts[i], &out.VolumeMounts[i], s); err != nil { - return err - } - } - } else { - out.VolumeMounts = nil - } - // unable to generate simple pointer conversion for api.Probe -> v1.Probe - if in.LivenessProbe != nil { - out.LivenessProbe = new(Probe) - if err := Convert_api_Probe_To_v1_Probe(in.LivenessProbe, out.LivenessProbe, s); err != nil { - return err - } - } else { - out.LivenessProbe = nil - } - // unable to generate simple pointer conversion for api.Probe -> v1.Probe - if in.ReadinessProbe != nil { - out.ReadinessProbe = new(Probe) - if err := Convert_api_Probe_To_v1_Probe(in.ReadinessProbe, out.ReadinessProbe, s); err != nil { - return err - } - } else { - out.ReadinessProbe = nil - } - // unable to generate simple pointer conversion for api.Lifecycle -> v1.Lifecycle - if in.Lifecycle != nil { - out.Lifecycle = new(Lifecycle) - if err := Convert_api_Lifecycle_To_v1_Lifecycle(in.Lifecycle, out.Lifecycle, s); err != nil { - return err - } - } else { - out.Lifecycle = nil - } - out.TerminationMessagePath = in.TerminationMessagePath - out.ImagePullPolicy = PullPolicy(in.ImagePullPolicy) - // unable to generate simple pointer conversion for api.SecurityContext -> v1.SecurityContext - if in.SecurityContext != nil { - out.SecurityContext = new(SecurityContext) - if err := Convert_api_SecurityContext_To_v1_SecurityContext(in.SecurityContext, out.SecurityContext, s); err != nil { - return err - } - } else { - out.SecurityContext = nil - } - out.Stdin = in.Stdin - out.StdinOnce = in.StdinOnce - out.TTY = in.TTY - return nil -} - -func Convert_api_Container_To_v1_Container(in *api.Container, out *Container, s conversion.Scope) error { - return autoConvert_api_Container_To_v1_Container(in, out, s) -} - -func autoConvert_api_ContainerImage_To_v1_ContainerImage(in *api.ContainerImage, out *ContainerImage, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ContainerImage))(in) - } - if in.RepoTags != nil { - out.RepoTags = make([]string, len(in.RepoTags)) - for i := range in.RepoTags { - out.RepoTags[i] = in.RepoTags[i] - } - } else { - out.RepoTags = nil - } - out.Size = in.Size - return nil -} - -func Convert_api_ContainerImage_To_v1_ContainerImage(in *api.ContainerImage, out *ContainerImage, s conversion.Scope) error { - return autoConvert_api_ContainerImage_To_v1_ContainerImage(in, out, s) -} - -func autoConvert_api_ContainerPort_To_v1_ContainerPort(in *api.ContainerPort, out *ContainerPort, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ContainerPort))(in) - } - out.Name = in.Name - out.HostPort = int32(in.HostPort) - out.ContainerPort = int32(in.ContainerPort) - out.Protocol = Protocol(in.Protocol) - out.HostIP = in.HostIP - return nil -} - -func Convert_api_ContainerPort_To_v1_ContainerPort(in *api.ContainerPort, out *ContainerPort, s conversion.Scope) error { - return autoConvert_api_ContainerPort_To_v1_ContainerPort(in, out, s) -} - -func autoConvert_api_ContainerState_To_v1_ContainerState(in *api.ContainerState, out *ContainerState, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ContainerState))(in) - } - // unable to generate simple pointer conversion for api.ContainerStateWaiting -> v1.ContainerStateWaiting - if in.Waiting != nil { - out.Waiting = new(ContainerStateWaiting) - if err := Convert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting(in.Waiting, out.Waiting, s); err != nil { - return err - } - } else { - out.Waiting = nil - } - // unable to generate simple pointer conversion for api.ContainerStateRunning -> v1.ContainerStateRunning - if in.Running != nil { - out.Running = new(ContainerStateRunning) - if err := Convert_api_ContainerStateRunning_To_v1_ContainerStateRunning(in.Running, out.Running, s); err != nil { - return err - } - } else { - out.Running = nil - } - // unable to generate simple pointer conversion for api.ContainerStateTerminated -> v1.ContainerStateTerminated - if in.Terminated != nil { - out.Terminated = new(ContainerStateTerminated) - if err := Convert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated(in.Terminated, out.Terminated, s); err != nil { - return err - } - } else { - out.Terminated = nil - } - return nil -} - -func Convert_api_ContainerState_To_v1_ContainerState(in *api.ContainerState, out *ContainerState, s conversion.Scope) error { - return autoConvert_api_ContainerState_To_v1_ContainerState(in, out, s) -} - -func autoConvert_api_ContainerStateRunning_To_v1_ContainerStateRunning(in *api.ContainerStateRunning, out *ContainerStateRunning, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ContainerStateRunning))(in) - } - if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.StartedAt, &out.StartedAt, s); err != nil { - return err - } - return nil -} - -func Convert_api_ContainerStateRunning_To_v1_ContainerStateRunning(in *api.ContainerStateRunning, out *ContainerStateRunning, s conversion.Scope) error { - return autoConvert_api_ContainerStateRunning_To_v1_ContainerStateRunning(in, out, s) -} - -func autoConvert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated(in *api.ContainerStateTerminated, out *ContainerStateTerminated, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ContainerStateTerminated))(in) - } - out.ExitCode = int32(in.ExitCode) - out.Signal = int32(in.Signal) - out.Reason = in.Reason - out.Message = in.Message - if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.StartedAt, &out.StartedAt, s); err != nil { - return err - } - if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.FinishedAt, &out.FinishedAt, s); err != nil { - return err - } - out.ContainerID = in.ContainerID - return nil -} - -func Convert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated(in *api.ContainerStateTerminated, out *ContainerStateTerminated, s conversion.Scope) error { - return autoConvert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated(in, out, s) -} - -func autoConvert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting(in *api.ContainerStateWaiting, out *ContainerStateWaiting, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ContainerStateWaiting))(in) - } - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -func Convert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting(in *api.ContainerStateWaiting, out *ContainerStateWaiting, s conversion.Scope) error { - return autoConvert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting(in, out, s) -} - -func autoConvert_api_ContainerStatus_To_v1_ContainerStatus(in *api.ContainerStatus, out *ContainerStatus, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ContainerStatus))(in) - } - out.Name = in.Name - if err := Convert_api_ContainerState_To_v1_ContainerState(&in.State, &out.State, s); err != nil { - return err - } - if err := Convert_api_ContainerState_To_v1_ContainerState(&in.LastTerminationState, &out.LastTerminationState, s); err != nil { - return err - } - out.Ready = in.Ready - out.RestartCount = int32(in.RestartCount) - out.Image = in.Image - out.ImageID = in.ImageID - out.ContainerID = in.ContainerID - return nil -} - -func Convert_api_ContainerStatus_To_v1_ContainerStatus(in *api.ContainerStatus, out *ContainerStatus, s conversion.Scope) error { - return autoConvert_api_ContainerStatus_To_v1_ContainerStatus(in, out, s) -} - -func autoConvert_api_DaemonEndpoint_To_v1_DaemonEndpoint(in *api.DaemonEndpoint, out *DaemonEndpoint, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.DaemonEndpoint))(in) - } - out.Port = int32(in.Port) - return nil -} - -func Convert_api_DaemonEndpoint_To_v1_DaemonEndpoint(in *api.DaemonEndpoint, out *DaemonEndpoint, s conversion.Scope) error { - return autoConvert_api_DaemonEndpoint_To_v1_DaemonEndpoint(in, out, s) -} - -func autoConvert_api_DeleteOptions_To_v1_DeleteOptions(in *api.DeleteOptions, out *DeleteOptions, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.DeleteOptions))(in) - } - if in.GracePeriodSeconds != nil { - out.GracePeriodSeconds = new(int64) - *out.GracePeriodSeconds = *in.GracePeriodSeconds - } else { - out.GracePeriodSeconds = nil - } - return nil -} - -func Convert_api_DeleteOptions_To_v1_DeleteOptions(in *api.DeleteOptions, out *DeleteOptions, s conversion.Scope) error { - return autoConvert_api_DeleteOptions_To_v1_DeleteOptions(in, out, s) -} - -func autoConvert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in *api.DownwardAPIVolumeFile, out *DownwardAPIVolumeFile, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.DownwardAPIVolumeFile))(in) - } - out.Path = in.Path - if err := Convert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(&in.FieldRef, &out.FieldRef, s); err != nil { - return err - } - return nil -} - -func Convert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in *api.DownwardAPIVolumeFile, out *DownwardAPIVolumeFile, s conversion.Scope) error { - return autoConvert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in, out, s) -} - -func autoConvert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in *api.DownwardAPIVolumeSource, out *DownwardAPIVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.DownwardAPIVolumeSource))(in) - } - if in.Items != nil { - out.Items = make([]DownwardAPIVolumeFile, len(in.Items)) - for i := range in.Items { - if err := Convert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in *api.DownwardAPIVolumeSource, out *DownwardAPIVolumeSource, s conversion.Scope) error { - return autoConvert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in, out, s) -} - -func autoConvert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in *api.EmptyDirVolumeSource, out *EmptyDirVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.EmptyDirVolumeSource))(in) - } - out.Medium = StorageMedium(in.Medium) - return nil -} - -func Convert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in *api.EmptyDirVolumeSource, out *EmptyDirVolumeSource, s conversion.Scope) error { - return autoConvert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in, out, s) -} - -func autoConvert_api_EndpointAddress_To_v1_EndpointAddress(in *api.EndpointAddress, out *EndpointAddress, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.EndpointAddress))(in) - } - out.IP = in.IP - // unable to generate simple pointer conversion for api.ObjectReference -> v1.ObjectReference - if in.TargetRef != nil { - out.TargetRef = new(ObjectReference) - if err := Convert_api_ObjectReference_To_v1_ObjectReference(in.TargetRef, out.TargetRef, s); err != nil { - return err - } - } else { - out.TargetRef = nil - } - return nil -} - -func Convert_api_EndpointAddress_To_v1_EndpointAddress(in *api.EndpointAddress, out *EndpointAddress, s conversion.Scope) error { - return autoConvert_api_EndpointAddress_To_v1_EndpointAddress(in, out, s) -} - -func autoConvert_api_EndpointPort_To_v1_EndpointPort(in *api.EndpointPort, out *EndpointPort, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.EndpointPort))(in) - } - out.Name = in.Name - out.Port = int32(in.Port) - out.Protocol = Protocol(in.Protocol) - return nil -} - -func Convert_api_EndpointPort_To_v1_EndpointPort(in *api.EndpointPort, out *EndpointPort, s conversion.Scope) error { - return autoConvert_api_EndpointPort_To_v1_EndpointPort(in, out, s) -} - -func autoConvert_api_EndpointSubset_To_v1_EndpointSubset(in *api.EndpointSubset, out *EndpointSubset, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.EndpointSubset))(in) - } - if in.Addresses != nil { - out.Addresses = make([]EndpointAddress, len(in.Addresses)) - for i := range in.Addresses { - if err := Convert_api_EndpointAddress_To_v1_EndpointAddress(&in.Addresses[i], &out.Addresses[i], s); err != nil { - return err - } - } - } else { - out.Addresses = nil - } - if in.NotReadyAddresses != nil { - out.NotReadyAddresses = make([]EndpointAddress, len(in.NotReadyAddresses)) - for i := range in.NotReadyAddresses { - if err := Convert_api_EndpointAddress_To_v1_EndpointAddress(&in.NotReadyAddresses[i], &out.NotReadyAddresses[i], s); err != nil { - return err - } - } - } else { - out.NotReadyAddresses = nil - } - if in.Ports != nil { - out.Ports = make([]EndpointPort, len(in.Ports)) - for i := range in.Ports { - if err := Convert_api_EndpointPort_To_v1_EndpointPort(&in.Ports[i], &out.Ports[i], s); err != nil { - return err - } - } - } else { - out.Ports = nil - } - return nil -} - -func Convert_api_EndpointSubset_To_v1_EndpointSubset(in *api.EndpointSubset, out *EndpointSubset, s conversion.Scope) error { - return autoConvert_api_EndpointSubset_To_v1_EndpointSubset(in, out, s) -} - -func autoConvert_api_Endpoints_To_v1_Endpoints(in *api.Endpoints, out *Endpoints, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.Endpoints))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if in.Subsets != nil { - out.Subsets = make([]EndpointSubset, len(in.Subsets)) - for i := range in.Subsets { - if err := Convert_api_EndpointSubset_To_v1_EndpointSubset(&in.Subsets[i], &out.Subsets[i], s); err != nil { - return err - } - } - } else { - out.Subsets = nil - } - return nil -} - -func Convert_api_Endpoints_To_v1_Endpoints(in *api.Endpoints, out *Endpoints, s conversion.Scope) error { - return autoConvert_api_Endpoints_To_v1_Endpoints(in, out, s) -} - -func autoConvert_api_EndpointsList_To_v1_EndpointsList(in *api.EndpointsList, out *EndpointsList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.EndpointsList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]Endpoints, len(in.Items)) - for i := range in.Items { - if err := Convert_api_Endpoints_To_v1_Endpoints(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_EndpointsList_To_v1_EndpointsList(in *api.EndpointsList, out *EndpointsList, s conversion.Scope) error { - return autoConvert_api_EndpointsList_To_v1_EndpointsList(in, out, s) -} - -func autoConvert_api_EnvVar_To_v1_EnvVar(in *api.EnvVar, out *EnvVar, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.EnvVar))(in) - } - out.Name = in.Name - out.Value = in.Value - // unable to generate simple pointer conversion for api.EnvVarSource -> v1.EnvVarSource - if in.ValueFrom != nil { - out.ValueFrom = new(EnvVarSource) - if err := Convert_api_EnvVarSource_To_v1_EnvVarSource(in.ValueFrom, out.ValueFrom, s); err != nil { - return err - } - } else { - out.ValueFrom = nil - } - return nil -} - -func Convert_api_EnvVar_To_v1_EnvVar(in *api.EnvVar, out *EnvVar, s conversion.Scope) error { - return autoConvert_api_EnvVar_To_v1_EnvVar(in, out, s) -} - -func autoConvert_api_EnvVarSource_To_v1_EnvVarSource(in *api.EnvVarSource, out *EnvVarSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.EnvVarSource))(in) - } - // unable to generate simple pointer conversion for api.ObjectFieldSelector -> v1.ObjectFieldSelector - if in.FieldRef != nil { - out.FieldRef = new(ObjectFieldSelector) - if err := Convert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(in.FieldRef, out.FieldRef, s); err != nil { - return err - } - } else { - out.FieldRef = nil - } - // unable to generate simple pointer conversion for api.ConfigMapKeySelector -> v1.ConfigMapKeySelector - if in.ConfigMapKeyRef != nil { - out.ConfigMapKeyRef = new(ConfigMapKeySelector) - if err := Convert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in.ConfigMapKeyRef, out.ConfigMapKeyRef, s); err != nil { - return err - } - } else { - out.ConfigMapKeyRef = nil - } - // unable to generate simple pointer conversion for api.SecretKeySelector -> v1.SecretKeySelector - if in.SecretKeyRef != nil { - out.SecretKeyRef = new(SecretKeySelector) - if err := Convert_api_SecretKeySelector_To_v1_SecretKeySelector(in.SecretKeyRef, out.SecretKeyRef, s); err != nil { - return err - } - } else { - out.SecretKeyRef = nil - } - return nil -} - -func Convert_api_EnvVarSource_To_v1_EnvVarSource(in *api.EnvVarSource, out *EnvVarSource, s conversion.Scope) error { - return autoConvert_api_EnvVarSource_To_v1_EnvVarSource(in, out, s) -} - -func autoConvert_api_Event_To_v1_Event(in *api.Event, out *Event, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.Event))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_api_ObjectReference_To_v1_ObjectReference(&in.InvolvedObject, &out.InvolvedObject, s); err != nil { - return err - } - out.Reason = in.Reason - out.Message = in.Message - if err := Convert_api_EventSource_To_v1_EventSource(&in.Source, &out.Source, s); err != nil { - return err - } - if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.FirstTimestamp, &out.FirstTimestamp, s); err != nil { - return err - } - if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastTimestamp, &out.LastTimestamp, s); err != nil { - return err - } - out.Count = int32(in.Count) - out.Type = in.Type - return nil -} - -func Convert_api_Event_To_v1_Event(in *api.Event, out *Event, s conversion.Scope) error { - return autoConvert_api_Event_To_v1_Event(in, out, s) -} - -func autoConvert_api_EventList_To_v1_EventList(in *api.EventList, out *EventList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.EventList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]Event, len(in.Items)) - for i := range in.Items { - if err := Convert_api_Event_To_v1_Event(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_EventList_To_v1_EventList(in *api.EventList, out *EventList, s conversion.Scope) error { - return autoConvert_api_EventList_To_v1_EventList(in, out, s) -} - -func autoConvert_api_EventSource_To_v1_EventSource(in *api.EventSource, out *EventSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.EventSource))(in) - } - out.Component = in.Component - out.Host = in.Host - return nil -} - -func Convert_api_EventSource_To_v1_EventSource(in *api.EventSource, out *EventSource, s conversion.Scope) error { - return autoConvert_api_EventSource_To_v1_EventSource(in, out, s) -} - -func autoConvert_api_ExecAction_To_v1_ExecAction(in *api.ExecAction, out *ExecAction, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ExecAction))(in) - } - if in.Command != nil { - out.Command = make([]string, len(in.Command)) - for i := range in.Command { - out.Command[i] = in.Command[i] - } - } else { - out.Command = nil - } - return nil -} - -func Convert_api_ExecAction_To_v1_ExecAction(in *api.ExecAction, out *ExecAction, s conversion.Scope) error { - return autoConvert_api_ExecAction_To_v1_ExecAction(in, out, s) -} - -func autoConvert_api_FCVolumeSource_To_v1_FCVolumeSource(in *api.FCVolumeSource, out *FCVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.FCVolumeSource))(in) - } - if in.TargetWWNs != nil { - out.TargetWWNs = make([]string, len(in.TargetWWNs)) - for i := range in.TargetWWNs { - out.TargetWWNs[i] = in.TargetWWNs[i] - } - } else { - out.TargetWWNs = nil - } - if in.Lun != nil { - out.Lun = new(int32) - *out.Lun = int32(*in.Lun) - } else { - out.Lun = nil - } - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - return nil -} - -func Convert_api_FCVolumeSource_To_v1_FCVolumeSource(in *api.FCVolumeSource, out *FCVolumeSource, s conversion.Scope) error { - return autoConvert_api_FCVolumeSource_To_v1_FCVolumeSource(in, out, s) -} - -func autoConvert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in *api.FlexVolumeSource, out *FlexVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.FlexVolumeSource))(in) - } - out.Driver = in.Driver - out.FSType = in.FSType - // unable to generate simple pointer conversion for api.LocalObjectReference -> v1.LocalObjectReference - if in.SecretRef != nil { - out.SecretRef = new(LocalObjectReference) - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(in.SecretRef, out.SecretRef, s); err != nil { - return err - } - } else { - out.SecretRef = nil - } - out.ReadOnly = in.ReadOnly - if in.Options != nil { - out.Options = make(map[string]string) - for key, val := range in.Options { - out.Options[key] = val - } - } else { - out.Options = nil - } - return nil -} - -func Convert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in *api.FlexVolumeSource, out *FlexVolumeSource, s conversion.Scope) error { - return autoConvert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in, out, s) -} - -func autoConvert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in *api.FlockerVolumeSource, out *FlockerVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.FlockerVolumeSource))(in) - } - out.DatasetName = in.DatasetName - return nil -} - -func Convert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in *api.FlockerVolumeSource, out *FlockerVolumeSource, s conversion.Scope) error { - return autoConvert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in, out, s) -} - -func autoConvert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in *api.GCEPersistentDiskVolumeSource, out *GCEPersistentDiskVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.GCEPersistentDiskVolumeSource))(in) - } - out.PDName = in.PDName - out.FSType = in.FSType - out.Partition = int32(in.Partition) - out.ReadOnly = in.ReadOnly - return nil -} - -func Convert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in *api.GCEPersistentDiskVolumeSource, out *GCEPersistentDiskVolumeSource, s conversion.Scope) error { - return autoConvert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in, out, s) -} - -func autoConvert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in *api.GitRepoVolumeSource, out *GitRepoVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.GitRepoVolumeSource))(in) - } - out.Repository = in.Repository - out.Revision = in.Revision - out.Directory = in.Directory - return nil -} - -func Convert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in *api.GitRepoVolumeSource, out *GitRepoVolumeSource, s conversion.Scope) error { - return autoConvert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in, out, s) -} - -func autoConvert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in *api.GlusterfsVolumeSource, out *GlusterfsVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.GlusterfsVolumeSource))(in) - } - out.EndpointsName = in.EndpointsName - out.Path = in.Path - out.ReadOnly = in.ReadOnly - return nil -} - -func Convert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in *api.GlusterfsVolumeSource, out *GlusterfsVolumeSource, s conversion.Scope) error { - return autoConvert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in, out, s) -} - -func autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction(in *api.HTTPGetAction, out *HTTPGetAction, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.HTTPGetAction))(in) - } - out.Path = in.Path - if err := api.Convert_intstr_IntOrString_To_intstr_IntOrString(&in.Port, &out.Port, s); err != nil { - return err - } - out.Host = in.Host - out.Scheme = URIScheme(in.Scheme) - if in.HTTPHeaders != nil { - out.HTTPHeaders = make([]HTTPHeader, len(in.HTTPHeaders)) - for i := range in.HTTPHeaders { - if err := Convert_api_HTTPHeader_To_v1_HTTPHeader(&in.HTTPHeaders[i], &out.HTTPHeaders[i], s); err != nil { - return err - } - } - } else { - out.HTTPHeaders = nil - } - return nil -} - -func Convert_api_HTTPGetAction_To_v1_HTTPGetAction(in *api.HTTPGetAction, out *HTTPGetAction, s conversion.Scope) error { - return autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction(in, out, s) -} - -func autoConvert_api_HTTPHeader_To_v1_HTTPHeader(in *api.HTTPHeader, out *HTTPHeader, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.HTTPHeader))(in) - } - out.Name = in.Name - out.Value = in.Value - return nil -} - -func Convert_api_HTTPHeader_To_v1_HTTPHeader(in *api.HTTPHeader, out *HTTPHeader, s conversion.Scope) error { - return autoConvert_api_HTTPHeader_To_v1_HTTPHeader(in, out, s) -} - -func autoConvert_api_Handler_To_v1_Handler(in *api.Handler, out *Handler, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.Handler))(in) - } - // unable to generate simple pointer conversion for api.ExecAction -> v1.ExecAction - if in.Exec != nil { - out.Exec = new(ExecAction) - if err := Convert_api_ExecAction_To_v1_ExecAction(in.Exec, out.Exec, s); err != nil { - return err - } - } else { - out.Exec = nil - } - // unable to generate simple pointer conversion for api.HTTPGetAction -> v1.HTTPGetAction - if in.HTTPGet != nil { - out.HTTPGet = new(HTTPGetAction) - if err := Convert_api_HTTPGetAction_To_v1_HTTPGetAction(in.HTTPGet, out.HTTPGet, s); err != nil { - return err - } - } else { - out.HTTPGet = nil - } - // unable to generate simple pointer conversion for api.TCPSocketAction -> v1.TCPSocketAction - if in.TCPSocket != nil { - out.TCPSocket = new(TCPSocketAction) - if err := Convert_api_TCPSocketAction_To_v1_TCPSocketAction(in.TCPSocket, out.TCPSocket, s); err != nil { - return err - } - } else { - out.TCPSocket = nil - } - return nil -} - -func Convert_api_Handler_To_v1_Handler(in *api.Handler, out *Handler, s conversion.Scope) error { - return autoConvert_api_Handler_To_v1_Handler(in, out, s) -} - -func autoConvert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in *api.HostPathVolumeSource, out *HostPathVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.HostPathVolumeSource))(in) - } - out.Path = in.Path - return nil -} - -func Convert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in *api.HostPathVolumeSource, out *HostPathVolumeSource, s conversion.Scope) error { - return autoConvert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in, out, s) -} - -func autoConvert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in *api.ISCSIVolumeSource, out *ISCSIVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ISCSIVolumeSource))(in) - } - out.TargetPortal = in.TargetPortal - out.IQN = in.IQN - out.Lun = int32(in.Lun) - out.ISCSIInterface = in.ISCSIInterface - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - return nil -} - -func Convert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in *api.ISCSIVolumeSource, out *ISCSIVolumeSource, s conversion.Scope) error { - return autoConvert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in, out, s) -} - -func autoConvert_api_KeyToPath_To_v1_KeyToPath(in *api.KeyToPath, out *KeyToPath, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.KeyToPath))(in) - } - out.Key = in.Key - out.Path = in.Path - return nil -} - -func Convert_api_KeyToPath_To_v1_KeyToPath(in *api.KeyToPath, out *KeyToPath, s conversion.Scope) error { - return autoConvert_api_KeyToPath_To_v1_KeyToPath(in, out, s) -} - -func autoConvert_api_Lifecycle_To_v1_Lifecycle(in *api.Lifecycle, out *Lifecycle, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.Lifecycle))(in) - } - // unable to generate simple pointer conversion for api.Handler -> v1.Handler - if in.PostStart != nil { - out.PostStart = new(Handler) - if err := Convert_api_Handler_To_v1_Handler(in.PostStart, out.PostStart, s); err != nil { - return err - } - } else { - out.PostStart = nil - } - // unable to generate simple pointer conversion for api.Handler -> v1.Handler - if in.PreStop != nil { - out.PreStop = new(Handler) - if err := Convert_api_Handler_To_v1_Handler(in.PreStop, out.PreStop, s); err != nil { - return err - } - } else { - out.PreStop = nil - } - return nil -} - -func Convert_api_Lifecycle_To_v1_Lifecycle(in *api.Lifecycle, out *Lifecycle, s conversion.Scope) error { - return autoConvert_api_Lifecycle_To_v1_Lifecycle(in, out, s) -} - -func autoConvert_api_LimitRange_To_v1_LimitRange(in *api.LimitRange, out *LimitRange, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.LimitRange))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_api_LimitRangeSpec_To_v1_LimitRangeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -func Convert_api_LimitRange_To_v1_LimitRange(in *api.LimitRange, out *LimitRange, s conversion.Scope) error { - return autoConvert_api_LimitRange_To_v1_LimitRange(in, out, s) -} - -func autoConvert_api_LimitRangeItem_To_v1_LimitRangeItem(in *api.LimitRangeItem, out *LimitRangeItem, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.LimitRangeItem))(in) - } - out.Type = LimitType(in.Type) - if in.Max != nil { - out.Max = make(ResourceList) - for key, val := range in.Max { - newVal := resource.Quantity{} - if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { - return err - } - out.Max[ResourceName(key)] = newVal - } - } else { - out.Max = nil - } - if in.Min != nil { - out.Min = make(ResourceList) - for key, val := range in.Min { - newVal := resource.Quantity{} - if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { - return err - } - out.Min[ResourceName(key)] = newVal - } - } else { - out.Min = nil - } - if in.Default != nil { - out.Default = make(ResourceList) - for key, val := range in.Default { - newVal := resource.Quantity{} - if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { - return err - } - out.Default[ResourceName(key)] = newVal - } - } else { - out.Default = nil - } - if in.DefaultRequest != nil { - out.DefaultRequest = make(ResourceList) - for key, val := range in.DefaultRequest { - newVal := resource.Quantity{} - if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { - return err - } - out.DefaultRequest[ResourceName(key)] = newVal - } - } else { - out.DefaultRequest = nil - } - if in.MaxLimitRequestRatio != nil { - out.MaxLimitRequestRatio = make(ResourceList) - for key, val := range in.MaxLimitRequestRatio { - newVal := resource.Quantity{} - if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { - return err - } - out.MaxLimitRequestRatio[ResourceName(key)] = newVal - } - } else { - out.MaxLimitRequestRatio = nil - } - return nil -} - -func Convert_api_LimitRangeItem_To_v1_LimitRangeItem(in *api.LimitRangeItem, out *LimitRangeItem, s conversion.Scope) error { - return autoConvert_api_LimitRangeItem_To_v1_LimitRangeItem(in, out, s) -} - -func autoConvert_api_LimitRangeList_To_v1_LimitRangeList(in *api.LimitRangeList, out *LimitRangeList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.LimitRangeList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]LimitRange, len(in.Items)) - for i := range in.Items { - if err := Convert_api_LimitRange_To_v1_LimitRange(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_LimitRangeList_To_v1_LimitRangeList(in *api.LimitRangeList, out *LimitRangeList, s conversion.Scope) error { - return autoConvert_api_LimitRangeList_To_v1_LimitRangeList(in, out, s) -} - -func autoConvert_api_LimitRangeSpec_To_v1_LimitRangeSpec(in *api.LimitRangeSpec, out *LimitRangeSpec, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.LimitRangeSpec))(in) - } - if in.Limits != nil { - out.Limits = make([]LimitRangeItem, len(in.Limits)) - for i := range in.Limits { - if err := Convert_api_LimitRangeItem_To_v1_LimitRangeItem(&in.Limits[i], &out.Limits[i], s); err != nil { - return err - } - } - } else { - out.Limits = nil - } - return nil -} - -func Convert_api_LimitRangeSpec_To_v1_LimitRangeSpec(in *api.LimitRangeSpec, out *LimitRangeSpec, s conversion.Scope) error { - return autoConvert_api_LimitRangeSpec_To_v1_LimitRangeSpec(in, out, s) -} - -func autoConvert_api_List_To_v1_List(in *api.List, out *List, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.List))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]runtime.RawExtension, len(in.Items)) - for i := range in.Items { - if err := s.Convert(&in.Items[i], &out.Items[i], 0); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_List_To_v1_List(in *api.List, out *List, s conversion.Scope) error { - return autoConvert_api_List_To_v1_List(in, out, s) -} - -func autoConvert_api_ListOptions_To_v1_ListOptions(in *api.ListOptions, out *ListOptions, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ListOptions))(in) - } - if err := api.Convert_labels_Selector_To_string(&in.LabelSelector, &out.LabelSelector, s); err != nil { - return err - } - if err := api.Convert_fields_Selector_To_string(&in.FieldSelector, &out.FieldSelector, s); err != nil { - return err - } - out.Watch = in.Watch - out.ResourceVersion = in.ResourceVersion - if in.TimeoutSeconds != nil { - out.TimeoutSeconds = new(int64) - *out.TimeoutSeconds = *in.TimeoutSeconds - } else { - out.TimeoutSeconds = nil - } - return nil -} - -func Convert_api_ListOptions_To_v1_ListOptions(in *api.ListOptions, out *ListOptions, s conversion.Scope) error { - return autoConvert_api_ListOptions_To_v1_ListOptions(in, out, s) -} - -func autoConvert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress(in *api.LoadBalancerIngress, out *LoadBalancerIngress, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.LoadBalancerIngress))(in) - } - out.IP = in.IP - out.Hostname = in.Hostname - return nil -} - -func Convert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress(in *api.LoadBalancerIngress, out *LoadBalancerIngress, s conversion.Scope) error { - return autoConvert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress(in, out, s) -} - -func autoConvert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus(in *api.LoadBalancerStatus, out *LoadBalancerStatus, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.LoadBalancerStatus))(in) - } - if in.Ingress != nil { - out.Ingress = make([]LoadBalancerIngress, len(in.Ingress)) - for i := range in.Ingress { - if err := Convert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress(&in.Ingress[i], &out.Ingress[i], s); err != nil { - return err - } - } - } else { - out.Ingress = nil - } - return nil -} - -func Convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus(in *api.LoadBalancerStatus, out *LoadBalancerStatus, s conversion.Scope) error { - return autoConvert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus(in, out, s) -} - -func autoConvert_api_LocalObjectReference_To_v1_LocalObjectReference(in *api.LocalObjectReference, out *LocalObjectReference, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.LocalObjectReference))(in) - } - out.Name = in.Name - return nil -} - -func Convert_api_LocalObjectReference_To_v1_LocalObjectReference(in *api.LocalObjectReference, out *LocalObjectReference, s conversion.Scope) error { - return autoConvert_api_LocalObjectReference_To_v1_LocalObjectReference(in, out, s) -} - -func autoConvert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in *api.NFSVolumeSource, out *NFSVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.NFSVolumeSource))(in) - } - out.Server = in.Server - out.Path = in.Path - out.ReadOnly = in.ReadOnly - return nil -} - -func Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in *api.NFSVolumeSource, out *NFSVolumeSource, s conversion.Scope) error { - return autoConvert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in, out, s) -} - -func autoConvert_api_Namespace_To_v1_Namespace(in *api.Namespace, out *Namespace, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.Namespace))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_api_NamespaceSpec_To_v1_NamespaceSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_NamespaceStatus_To_v1_NamespaceStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func Convert_api_Namespace_To_v1_Namespace(in *api.Namespace, out *Namespace, s conversion.Scope) error { - return autoConvert_api_Namespace_To_v1_Namespace(in, out, s) -} - -func autoConvert_api_NamespaceList_To_v1_NamespaceList(in *api.NamespaceList, out *NamespaceList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.NamespaceList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]Namespace, len(in.Items)) - for i := range in.Items { - if err := Convert_api_Namespace_To_v1_Namespace(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_NamespaceList_To_v1_NamespaceList(in *api.NamespaceList, out *NamespaceList, s conversion.Scope) error { - return autoConvert_api_NamespaceList_To_v1_NamespaceList(in, out, s) -} - -func autoConvert_api_NamespaceSpec_To_v1_NamespaceSpec(in *api.NamespaceSpec, out *NamespaceSpec, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.NamespaceSpec))(in) - } - if in.Finalizers != nil { - out.Finalizers = make([]FinalizerName, len(in.Finalizers)) - for i := range in.Finalizers { - out.Finalizers[i] = FinalizerName(in.Finalizers[i]) - } - } else { - out.Finalizers = nil - } - return nil -} - -func Convert_api_NamespaceSpec_To_v1_NamespaceSpec(in *api.NamespaceSpec, out *NamespaceSpec, s conversion.Scope) error { - return autoConvert_api_NamespaceSpec_To_v1_NamespaceSpec(in, out, s) -} - -func autoConvert_api_NamespaceStatus_To_v1_NamespaceStatus(in *api.NamespaceStatus, out *NamespaceStatus, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.NamespaceStatus))(in) - } - out.Phase = NamespacePhase(in.Phase) - return nil -} - -func Convert_api_NamespaceStatus_To_v1_NamespaceStatus(in *api.NamespaceStatus, out *NamespaceStatus, s conversion.Scope) error { - return autoConvert_api_NamespaceStatus_To_v1_NamespaceStatus(in, out, s) -} - -func autoConvert_api_Node_To_v1_Node(in *api.Node, out *Node, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.Node))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_api_NodeSpec_To_v1_NodeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_NodeStatus_To_v1_NodeStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func Convert_api_Node_To_v1_Node(in *api.Node, out *Node, s conversion.Scope) error { - return autoConvert_api_Node_To_v1_Node(in, out, s) -} - -func autoConvert_api_NodeAddress_To_v1_NodeAddress(in *api.NodeAddress, out *NodeAddress, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.NodeAddress))(in) - } - out.Type = NodeAddressType(in.Type) - out.Address = in.Address - return nil -} - -func Convert_api_NodeAddress_To_v1_NodeAddress(in *api.NodeAddress, out *NodeAddress, s conversion.Scope) error { - return autoConvert_api_NodeAddress_To_v1_NodeAddress(in, out, s) -} - -func autoConvert_api_NodeCondition_To_v1_NodeCondition(in *api.NodeCondition, out *NodeCondition, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.NodeCondition))(in) - } - out.Type = NodeConditionType(in.Type) - out.Status = ConditionStatus(in.Status) - if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastHeartbeatTime, &out.LastHeartbeatTime, s); err != nil { - return err - } - if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastTransitionTime, &out.LastTransitionTime, s); err != nil { - return err - } - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -func Convert_api_NodeCondition_To_v1_NodeCondition(in *api.NodeCondition, out *NodeCondition, s conversion.Scope) error { - return autoConvert_api_NodeCondition_To_v1_NodeCondition(in, out, s) -} - -func autoConvert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(in *api.NodeDaemonEndpoints, out *NodeDaemonEndpoints, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.NodeDaemonEndpoints))(in) - } - if err := Convert_api_DaemonEndpoint_To_v1_DaemonEndpoint(&in.KubeletEndpoint, &out.KubeletEndpoint, s); err != nil { - return err - } - return nil -} - -func Convert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(in *api.NodeDaemonEndpoints, out *NodeDaemonEndpoints, s conversion.Scope) error { - return autoConvert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(in, out, s) -} - -func autoConvert_api_NodeList_To_v1_NodeList(in *api.NodeList, out *NodeList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.NodeList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]Node, len(in.Items)) - for i := range in.Items { - if err := Convert_api_Node_To_v1_Node(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_NodeList_To_v1_NodeList(in *api.NodeList, out *NodeList, s conversion.Scope) error { - return autoConvert_api_NodeList_To_v1_NodeList(in, out, s) -} - -func autoConvert_api_NodeSpec_To_v1_NodeSpec(in *api.NodeSpec, out *NodeSpec, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.NodeSpec))(in) - } - out.PodCIDR = in.PodCIDR - out.ExternalID = in.ExternalID - out.ProviderID = in.ProviderID - out.Unschedulable = in.Unschedulable - return nil -} - -func Convert_api_NodeSpec_To_v1_NodeSpec(in *api.NodeSpec, out *NodeSpec, s conversion.Scope) error { - return autoConvert_api_NodeSpec_To_v1_NodeSpec(in, out, s) -} - -func autoConvert_api_NodeStatus_To_v1_NodeStatus(in *api.NodeStatus, out *NodeStatus, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.NodeStatus))(in) - } - if in.Capacity != nil { - out.Capacity = make(ResourceList) - for key, val := range in.Capacity { - newVal := resource.Quantity{} - if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { - return err - } - out.Capacity[ResourceName(key)] = newVal - } - } else { - out.Capacity = nil - } - if in.Allocatable != nil { - out.Allocatable = make(ResourceList) - for key, val := range in.Allocatable { - newVal := resource.Quantity{} - if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { - return err - } - out.Allocatable[ResourceName(key)] = newVal - } - } else { - out.Allocatable = nil - } - out.Phase = NodePhase(in.Phase) - if in.Conditions != nil { - out.Conditions = make([]NodeCondition, len(in.Conditions)) - for i := range in.Conditions { - if err := Convert_api_NodeCondition_To_v1_NodeCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil { - return err - } - } - } else { - out.Conditions = nil - } - if in.Addresses != nil { - out.Addresses = make([]NodeAddress, len(in.Addresses)) - for i := range in.Addresses { - if err := Convert_api_NodeAddress_To_v1_NodeAddress(&in.Addresses[i], &out.Addresses[i], s); err != nil { - return err - } - } - } else { - out.Addresses = nil - } - if err := Convert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(&in.DaemonEndpoints, &out.DaemonEndpoints, s); err != nil { - return err - } - if err := Convert_api_NodeSystemInfo_To_v1_NodeSystemInfo(&in.NodeInfo, &out.NodeInfo, s); err != nil { - return err - } - if in.Images != nil { - out.Images = make([]ContainerImage, len(in.Images)) - for i := range in.Images { - if err := Convert_api_ContainerImage_To_v1_ContainerImage(&in.Images[i], &out.Images[i], s); err != nil { - return err - } - } - } else { - out.Images = nil - } - return nil -} - -func Convert_api_NodeStatus_To_v1_NodeStatus(in *api.NodeStatus, out *NodeStatus, s conversion.Scope) error { - return autoConvert_api_NodeStatus_To_v1_NodeStatus(in, out, s) -} - -func autoConvert_api_NodeSystemInfo_To_v1_NodeSystemInfo(in *api.NodeSystemInfo, out *NodeSystemInfo, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.NodeSystemInfo))(in) - } - out.MachineID = in.MachineID - out.SystemUUID = in.SystemUUID - out.BootID = in.BootID - out.KernelVersion = in.KernelVersion - out.OSImage = in.OSImage - out.ContainerRuntimeVersion = in.ContainerRuntimeVersion - out.KubeletVersion = in.KubeletVersion - out.KubeProxyVersion = in.KubeProxyVersion - return nil -} - -func Convert_api_NodeSystemInfo_To_v1_NodeSystemInfo(in *api.NodeSystemInfo, out *NodeSystemInfo, s conversion.Scope) error { - return autoConvert_api_NodeSystemInfo_To_v1_NodeSystemInfo(in, out, s) -} - -func autoConvert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(in *api.ObjectFieldSelector, out *ObjectFieldSelector, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ObjectFieldSelector))(in) - } - out.APIVersion = in.APIVersion - out.FieldPath = in.FieldPath - return nil -} - -func Convert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(in *api.ObjectFieldSelector, out *ObjectFieldSelector, s conversion.Scope) error { - return autoConvert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(in, out, s) -} - -func autoConvert_api_ObjectMeta_To_v1_ObjectMeta(in *api.ObjectMeta, out *ObjectMeta, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ObjectMeta))(in) - } - out.Name = in.Name - out.GenerateName = in.GenerateName - out.Namespace = in.Namespace - out.SelfLink = in.SelfLink - out.UID = in.UID - out.ResourceVersion = in.ResourceVersion - out.Generation = in.Generation - if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.CreationTimestamp, &out.CreationTimestamp, s); err != nil { - return err - } - // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time - if in.DeletionTimestamp != nil { - out.DeletionTimestamp = new(unversioned.Time) - if err := api.Convert_unversioned_Time_To_unversioned_Time(in.DeletionTimestamp, out.DeletionTimestamp, s); err != nil { - return err - } - } else { - out.DeletionTimestamp = nil - } - if in.DeletionGracePeriodSeconds != nil { - out.DeletionGracePeriodSeconds = new(int64) - *out.DeletionGracePeriodSeconds = *in.DeletionGracePeriodSeconds - } else { - out.DeletionGracePeriodSeconds = nil - } - if in.Labels != nil { - out.Labels = make(map[string]string) - for key, val := range in.Labels { - out.Labels[key] = val - } - } else { - out.Labels = nil - } - if in.Annotations != nil { - out.Annotations = make(map[string]string) - for key, val := range in.Annotations { - out.Annotations[key] = val - } - } else { - out.Annotations = nil - } - return nil -} - -func Convert_api_ObjectMeta_To_v1_ObjectMeta(in *api.ObjectMeta, out *ObjectMeta, s conversion.Scope) error { - return autoConvert_api_ObjectMeta_To_v1_ObjectMeta(in, out, s) -} - -func autoConvert_api_ObjectReference_To_v1_ObjectReference(in *api.ObjectReference, out *ObjectReference, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ObjectReference))(in) - } - out.Kind = in.Kind - out.Namespace = in.Namespace - out.Name = in.Name - out.UID = in.UID - out.APIVersion = in.APIVersion - out.ResourceVersion = in.ResourceVersion - out.FieldPath = in.FieldPath - return nil -} - -func Convert_api_ObjectReference_To_v1_ObjectReference(in *api.ObjectReference, out *ObjectReference, s conversion.Scope) error { - return autoConvert_api_ObjectReference_To_v1_ObjectReference(in, out, s) -} - -func autoConvert_api_PersistentVolume_To_v1_PersistentVolume(in *api.PersistentVolume, out *PersistentVolume, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PersistentVolume))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func Convert_api_PersistentVolume_To_v1_PersistentVolume(in *api.PersistentVolume, out *PersistentVolume, s conversion.Scope) error { - return autoConvert_api_PersistentVolume_To_v1_PersistentVolume(in, out, s) -} - -func autoConvert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(in *api.PersistentVolumeClaim, out *PersistentVolumeClaim, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PersistentVolumeClaim))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func Convert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(in *api.PersistentVolumeClaim, out *PersistentVolumeClaim, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(in, out, s) -} - -func autoConvert_api_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList(in *api.PersistentVolumeClaimList, out *PersistentVolumeClaimList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PersistentVolumeClaimList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]PersistentVolumeClaim, len(in.Items)) - for i := range in.Items { - if err := Convert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList(in *api.PersistentVolumeClaimList, out *PersistentVolumeClaimList, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList(in, out, s) -} - -func autoConvert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(in *api.PersistentVolumeClaimSpec, out *PersistentVolumeClaimSpec, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PersistentVolumeClaimSpec))(in) - } - if in.AccessModes != nil { - out.AccessModes = make([]PersistentVolumeAccessMode, len(in.AccessModes)) - for i := range in.AccessModes { - out.AccessModes[i] = PersistentVolumeAccessMode(in.AccessModes[i]) - } - } else { - out.AccessModes = nil - } - if err := Convert_api_ResourceRequirements_To_v1_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil { - return err - } - out.VolumeName = in.VolumeName - return nil -} - -func Convert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(in *api.PersistentVolumeClaimSpec, out *PersistentVolumeClaimSpec, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(in, out, s) -} - -func autoConvert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(in *api.PersistentVolumeClaimStatus, out *PersistentVolumeClaimStatus, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PersistentVolumeClaimStatus))(in) - } - out.Phase = PersistentVolumeClaimPhase(in.Phase) - if in.AccessModes != nil { - out.AccessModes = make([]PersistentVolumeAccessMode, len(in.AccessModes)) - for i := range in.AccessModes { - out.AccessModes[i] = PersistentVolumeAccessMode(in.AccessModes[i]) - } - } else { - out.AccessModes = nil - } - if in.Capacity != nil { - out.Capacity = make(ResourceList) - for key, val := range in.Capacity { - newVal := resource.Quantity{} - if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { - return err - } - out.Capacity[ResourceName(key)] = newVal - } - } else { - out.Capacity = nil - } - return nil -} - -func Convert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(in *api.PersistentVolumeClaimStatus, out *PersistentVolumeClaimStatus, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(in, out, s) -} - -func autoConvert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in *api.PersistentVolumeClaimVolumeSource, out *PersistentVolumeClaimVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PersistentVolumeClaimVolumeSource))(in) - } - out.ClaimName = in.ClaimName - out.ReadOnly = in.ReadOnly - return nil -} - -func Convert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in *api.PersistentVolumeClaimVolumeSource, out *PersistentVolumeClaimVolumeSource, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in, out, s) -} - -func autoConvert_api_PersistentVolumeList_To_v1_PersistentVolumeList(in *api.PersistentVolumeList, out *PersistentVolumeList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PersistentVolumeList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]PersistentVolume, len(in.Items)) - for i := range in.Items { - if err := Convert_api_PersistentVolume_To_v1_PersistentVolume(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_PersistentVolumeList_To_v1_PersistentVolumeList(in *api.PersistentVolumeList, out *PersistentVolumeList, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeList_To_v1_PersistentVolumeList(in, out, s) -} - -func autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *api.PersistentVolumeSource, out *PersistentVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PersistentVolumeSource))(in) - } - // unable to generate simple pointer conversion for api.GCEPersistentDiskVolumeSource -> v1.GCEPersistentDiskVolumeSource - if in.GCEPersistentDisk != nil { - out.GCEPersistentDisk = new(GCEPersistentDiskVolumeSource) - if err := Convert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in.GCEPersistentDisk, out.GCEPersistentDisk, s); err != nil { - return err - } - } else { - out.GCEPersistentDisk = nil - } - // unable to generate simple pointer conversion for api.AWSElasticBlockStoreVolumeSource -> v1.AWSElasticBlockStoreVolumeSource - if in.AWSElasticBlockStore != nil { - out.AWSElasticBlockStore = new(AWSElasticBlockStoreVolumeSource) - if err := Convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in.AWSElasticBlockStore, out.AWSElasticBlockStore, s); err != nil { - return err - } - } else { - out.AWSElasticBlockStore = nil - } - // unable to generate simple pointer conversion for api.HostPathVolumeSource -> v1.HostPathVolumeSource - if in.HostPath != nil { - out.HostPath = new(HostPathVolumeSource) - if err := Convert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in.HostPath, out.HostPath, s); err != nil { - return err - } - } else { - out.HostPath = nil - } - // unable to generate simple pointer conversion for api.GlusterfsVolumeSource -> v1.GlusterfsVolumeSource - if in.Glusterfs != nil { - out.Glusterfs = new(GlusterfsVolumeSource) - if err := Convert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in.Glusterfs, out.Glusterfs, s); err != nil { - return err - } - } else { - out.Glusterfs = nil - } - // unable to generate simple pointer conversion for api.NFSVolumeSource -> v1.NFSVolumeSource - if in.NFS != nil { - out.NFS = new(NFSVolumeSource) - if err := Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in.NFS, out.NFS, s); err != nil { - return err - } - } else { - out.NFS = nil - } - // unable to generate simple pointer conversion for api.RBDVolumeSource -> v1.RBDVolumeSource - if in.RBD != nil { - out.RBD = new(RBDVolumeSource) - if err := Convert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in.RBD, out.RBD, s); err != nil { - return err - } - } else { - out.RBD = nil - } - // unable to generate simple pointer conversion for api.ISCSIVolumeSource -> v1.ISCSIVolumeSource - if in.ISCSI != nil { - out.ISCSI = new(ISCSIVolumeSource) - if err := Convert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in.ISCSI, out.ISCSI, s); err != nil { - return err - } - } else { - out.ISCSI = nil - } - // unable to generate simple pointer conversion for api.FlexVolumeSource -> v1.FlexVolumeSource - if in.FlexVolume != nil { - out.FlexVolume = new(FlexVolumeSource) - if err := Convert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in.FlexVolume, out.FlexVolume, s); err != nil { - return err - } - } else { - out.FlexVolume = nil - } - // unable to generate simple pointer conversion for api.CinderVolumeSource -> v1.CinderVolumeSource - if in.Cinder != nil { - out.Cinder = new(CinderVolumeSource) - if err := Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in.Cinder, out.Cinder, s); err != nil { - return err - } - } else { - out.Cinder = nil - } - // unable to generate simple pointer conversion for api.CephFSVolumeSource -> v1.CephFSVolumeSource - if in.CephFS != nil { - out.CephFS = new(CephFSVolumeSource) - if err := Convert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in.CephFS, out.CephFS, s); err != nil { - return err - } - } else { - out.CephFS = nil - } - // unable to generate simple pointer conversion for api.FCVolumeSource -> v1.FCVolumeSource - if in.FC != nil { - out.FC = new(FCVolumeSource) - if err := Convert_api_FCVolumeSource_To_v1_FCVolumeSource(in.FC, out.FC, s); err != nil { - return err - } - } else { - out.FC = nil - } - // unable to generate simple pointer conversion for api.FlockerVolumeSource -> v1.FlockerVolumeSource - if in.Flocker != nil { - out.Flocker = new(FlockerVolumeSource) - if err := Convert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in.Flocker, out.Flocker, s); err != nil { - return err - } - } else { - out.Flocker = nil - } - // unable to generate simple pointer conversion for api.AzureFileVolumeSource -> v1.AzureFileVolumeSource - if in.AzureFile != nil { - out.AzureFile = new(AzureFileVolumeSource) - if err := Convert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in.AzureFile, out.AzureFile, s); err != nil { - return err - } - } else { - out.AzureFile = nil - } - return nil -} - -func Convert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *api.PersistentVolumeSource, out *PersistentVolumeSource, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in, out, s) -} - -func autoConvert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in *api.PersistentVolumeSpec, out *PersistentVolumeSpec, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PersistentVolumeSpec))(in) - } - if in.Capacity != nil { - out.Capacity = make(ResourceList) - for key, val := range in.Capacity { - newVal := resource.Quantity{} - if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { - return err - } - out.Capacity[ResourceName(key)] = newVal - } - } else { - out.Capacity = nil - } - if err := Convert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(&in.PersistentVolumeSource, &out.PersistentVolumeSource, s); err != nil { - return err - } - if in.AccessModes != nil { - out.AccessModes = make([]PersistentVolumeAccessMode, len(in.AccessModes)) - for i := range in.AccessModes { - out.AccessModes[i] = PersistentVolumeAccessMode(in.AccessModes[i]) - } - } else { - out.AccessModes = nil - } - // unable to generate simple pointer conversion for api.ObjectReference -> v1.ObjectReference - if in.ClaimRef != nil { - out.ClaimRef = new(ObjectReference) - if err := Convert_api_ObjectReference_To_v1_ObjectReference(in.ClaimRef, out.ClaimRef, s); err != nil { - return err - } - } else { - out.ClaimRef = nil - } - out.PersistentVolumeReclaimPolicy = PersistentVolumeReclaimPolicy(in.PersistentVolumeReclaimPolicy) - return nil -} - -func Convert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in *api.PersistentVolumeSpec, out *PersistentVolumeSpec, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in, out, s) -} - -func autoConvert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(in *api.PersistentVolumeStatus, out *PersistentVolumeStatus, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PersistentVolumeStatus))(in) - } - out.Phase = PersistentVolumePhase(in.Phase) - out.Message = in.Message - out.Reason = in.Reason - return nil -} - -func Convert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(in *api.PersistentVolumeStatus, out *PersistentVolumeStatus, s conversion.Scope) error { - return autoConvert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(in, out, s) -} - -func autoConvert_api_Pod_To_v1_Pod(in *api.Pod, out *Pod, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.Pod))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_api_PodSpec_To_v1_PodSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_PodStatus_To_v1_PodStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func autoConvert_api_PodAttachOptions_To_v1_PodAttachOptions(in *api.PodAttachOptions, out *PodAttachOptions, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PodAttachOptions))(in) - } - out.Stdin = in.Stdin - out.Stdout = in.Stdout - out.Stderr = in.Stderr - out.TTY = in.TTY - out.Container = in.Container - return nil -} - -func Convert_api_PodAttachOptions_To_v1_PodAttachOptions(in *api.PodAttachOptions, out *PodAttachOptions, s conversion.Scope) error { - return autoConvert_api_PodAttachOptions_To_v1_PodAttachOptions(in, out, s) -} - -func autoConvert_api_PodCondition_To_v1_PodCondition(in *api.PodCondition, out *PodCondition, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PodCondition))(in) - } - out.Type = PodConditionType(in.Type) - out.Status = ConditionStatus(in.Status) - if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastProbeTime, &out.LastProbeTime, s); err != nil { - return err - } - if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastTransitionTime, &out.LastTransitionTime, s); err != nil { - return err - } - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -func Convert_api_PodCondition_To_v1_PodCondition(in *api.PodCondition, out *PodCondition, s conversion.Scope) error { - return autoConvert_api_PodCondition_To_v1_PodCondition(in, out, s) -} - -func autoConvert_api_PodExecOptions_To_v1_PodExecOptions(in *api.PodExecOptions, out *PodExecOptions, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PodExecOptions))(in) - } - out.Stdin = in.Stdin - out.Stdout = in.Stdout - out.Stderr = in.Stderr - out.TTY = in.TTY - out.Container = in.Container - if in.Command != nil { - out.Command = make([]string, len(in.Command)) - for i := range in.Command { - out.Command[i] = in.Command[i] - } - } else { - out.Command = nil - } - return nil -} - -func Convert_api_PodExecOptions_To_v1_PodExecOptions(in *api.PodExecOptions, out *PodExecOptions, s conversion.Scope) error { - return autoConvert_api_PodExecOptions_To_v1_PodExecOptions(in, out, s) -} - -func autoConvert_api_PodList_To_v1_PodList(in *api.PodList, out *PodList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PodList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]Pod, len(in.Items)) - for i := range in.Items { - if err := Convert_api_Pod_To_v1_Pod(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_PodList_To_v1_PodList(in *api.PodList, out *PodList, s conversion.Scope) error { - return autoConvert_api_PodList_To_v1_PodList(in, out, s) -} - -func autoConvert_api_PodLogOptions_To_v1_PodLogOptions(in *api.PodLogOptions, out *PodLogOptions, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PodLogOptions))(in) - } - out.Container = in.Container - out.Follow = in.Follow - out.Previous = in.Previous - if in.SinceSeconds != nil { - out.SinceSeconds = new(int64) - *out.SinceSeconds = *in.SinceSeconds - } else { - out.SinceSeconds = nil - } - // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time - if in.SinceTime != nil { - out.SinceTime = new(unversioned.Time) - if err := api.Convert_unversioned_Time_To_unversioned_Time(in.SinceTime, out.SinceTime, s); err != nil { - return err - } - } else { - out.SinceTime = nil - } - out.Timestamps = in.Timestamps - if in.TailLines != nil { - out.TailLines = new(int64) - *out.TailLines = *in.TailLines - } else { - out.TailLines = nil - } - if in.LimitBytes != nil { - out.LimitBytes = new(int64) - *out.LimitBytes = *in.LimitBytes - } else { - out.LimitBytes = nil - } - return nil -} - -func Convert_api_PodLogOptions_To_v1_PodLogOptions(in *api.PodLogOptions, out *PodLogOptions, s conversion.Scope) error { - return autoConvert_api_PodLogOptions_To_v1_PodLogOptions(in, out, s) -} - -func autoConvert_api_PodProxyOptions_To_v1_PodProxyOptions(in *api.PodProxyOptions, out *PodProxyOptions, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PodProxyOptions))(in) - } - out.Path = in.Path - return nil -} - -func Convert_api_PodProxyOptions_To_v1_PodProxyOptions(in *api.PodProxyOptions, out *PodProxyOptions, s conversion.Scope) error { - return autoConvert_api_PodProxyOptions_To_v1_PodProxyOptions(in, out, s) -} - -func autoConvert_api_PodSpec_To_v1_PodSpec(in *api.PodSpec, out *PodSpec, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PodSpec))(in) - } - if in.Volumes != nil { - out.Volumes = make([]Volume, len(in.Volumes)) - for i := range in.Volumes { - if err := Convert_api_Volume_To_v1_Volume(&in.Volumes[i], &out.Volumes[i], s); err != nil { - return err - } - } - } else { - out.Volumes = nil - } - if in.Containers != nil { - out.Containers = make([]Container, len(in.Containers)) - for i := range in.Containers { - if err := Convert_api_Container_To_v1_Container(&in.Containers[i], &out.Containers[i], s); err != nil { - return err - } - } - } else { - out.Containers = nil - } - out.RestartPolicy = RestartPolicy(in.RestartPolicy) - if in.TerminationGracePeriodSeconds != nil { - out.TerminationGracePeriodSeconds = new(int64) - *out.TerminationGracePeriodSeconds = *in.TerminationGracePeriodSeconds - } else { - out.TerminationGracePeriodSeconds = nil - } - if in.ActiveDeadlineSeconds != nil { - out.ActiveDeadlineSeconds = new(int64) - *out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds - } else { - out.ActiveDeadlineSeconds = nil - } - out.DNSPolicy = DNSPolicy(in.DNSPolicy) - if in.NodeSelector != nil { - out.NodeSelector = make(map[string]string) - for key, val := range in.NodeSelector { - out.NodeSelector[key] = val - } - } else { - out.NodeSelector = nil - } - out.ServiceAccountName = in.ServiceAccountName - out.NodeName = in.NodeName - // unable to generate simple pointer conversion for api.PodSecurityContext -> v1.PodSecurityContext - if in.SecurityContext != nil { - if err := s.Convert(&in.SecurityContext, &out.SecurityContext, 0); err != nil { - return err - } - } else { - out.SecurityContext = nil - } - if in.ImagePullSecrets != nil { - out.ImagePullSecrets = make([]LocalObjectReference, len(in.ImagePullSecrets)) - for i := range in.ImagePullSecrets { - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.ImagePullSecrets[i], &out.ImagePullSecrets[i], s); err != nil { - return err - } - } - } else { - out.ImagePullSecrets = nil - } - return nil -} - -func autoConvert_api_PodStatus_To_v1_PodStatus(in *api.PodStatus, out *PodStatus, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PodStatus))(in) - } - out.Phase = PodPhase(in.Phase) - if in.Conditions != nil { - out.Conditions = make([]PodCondition, len(in.Conditions)) - for i := range in.Conditions { - if err := Convert_api_PodCondition_To_v1_PodCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil { - return err - } - } - } else { - out.Conditions = nil - } - out.Message = in.Message - out.Reason = in.Reason - out.HostIP = in.HostIP - out.PodIP = in.PodIP - // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time - if in.StartTime != nil { - out.StartTime = new(unversioned.Time) - if err := api.Convert_unversioned_Time_To_unversioned_Time(in.StartTime, out.StartTime, s); err != nil { - return err - } - } else { - out.StartTime = nil - } - if in.ContainerStatuses != nil { - out.ContainerStatuses = make([]ContainerStatus, len(in.ContainerStatuses)) - for i := range in.ContainerStatuses { - if err := Convert_api_ContainerStatus_To_v1_ContainerStatus(&in.ContainerStatuses[i], &out.ContainerStatuses[i], s); err != nil { - return err - } - } - } else { - out.ContainerStatuses = nil - } - return nil -} - -func Convert_api_PodStatus_To_v1_PodStatus(in *api.PodStatus, out *PodStatus, s conversion.Scope) error { - return autoConvert_api_PodStatus_To_v1_PodStatus(in, out, s) -} - -func autoConvert_api_PodStatusResult_To_v1_PodStatusResult(in *api.PodStatusResult, out *PodStatusResult, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PodStatusResult))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_api_PodStatus_To_v1_PodStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func Convert_api_PodStatusResult_To_v1_PodStatusResult(in *api.PodStatusResult, out *PodStatusResult, s conversion.Scope) error { - return autoConvert_api_PodStatusResult_To_v1_PodStatusResult(in, out, s) -} - -func autoConvert_api_PodTemplate_To_v1_PodTemplate(in *api.PodTemplate, out *PodTemplate, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PodTemplate))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { - return err - } - return nil -} - -func Convert_api_PodTemplate_To_v1_PodTemplate(in *api.PodTemplate, out *PodTemplate, s conversion.Scope) error { - return autoConvert_api_PodTemplate_To_v1_PodTemplate(in, out, s) -} - -func autoConvert_api_PodTemplateList_To_v1_PodTemplateList(in *api.PodTemplateList, out *PodTemplateList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PodTemplateList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]PodTemplate, len(in.Items)) - for i := range in.Items { - if err := Convert_api_PodTemplate_To_v1_PodTemplate(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_PodTemplateList_To_v1_PodTemplateList(in *api.PodTemplateList, out *PodTemplateList, s conversion.Scope) error { - return autoConvert_api_PodTemplateList_To_v1_PodTemplateList(in, out, s) -} - -func autoConvert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in *api.PodTemplateSpec, out *PodTemplateSpec, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.PodTemplateSpec))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_api_PodSpec_To_v1_PodSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -func Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in *api.PodTemplateSpec, out *PodTemplateSpec, s conversion.Scope) error { - return autoConvert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in, out, s) -} - -func autoConvert_api_Probe_To_v1_Probe(in *api.Probe, out *Probe, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.Probe))(in) - } - if err := Convert_api_Handler_To_v1_Handler(&in.Handler, &out.Handler, s); err != nil { - return err - } - out.InitialDelaySeconds = int32(in.InitialDelaySeconds) - out.TimeoutSeconds = int32(in.TimeoutSeconds) - out.PeriodSeconds = int32(in.PeriodSeconds) - out.SuccessThreshold = int32(in.SuccessThreshold) - out.FailureThreshold = int32(in.FailureThreshold) - return nil -} - -func Convert_api_Probe_To_v1_Probe(in *api.Probe, out *Probe, s conversion.Scope) error { - return autoConvert_api_Probe_To_v1_Probe(in, out, s) -} - -func autoConvert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in *api.RBDVolumeSource, out *RBDVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.RBDVolumeSource))(in) - } - if in.CephMonitors != nil { - out.CephMonitors = make([]string, len(in.CephMonitors)) - for i := range in.CephMonitors { - out.CephMonitors[i] = in.CephMonitors[i] - } - } else { - out.CephMonitors = nil - } - out.RBDImage = in.RBDImage - out.FSType = in.FSType - out.RBDPool = in.RBDPool - out.RadosUser = in.RadosUser - out.Keyring = in.Keyring - // unable to generate simple pointer conversion for api.LocalObjectReference -> v1.LocalObjectReference - if in.SecretRef != nil { - out.SecretRef = new(LocalObjectReference) - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(in.SecretRef, out.SecretRef, s); err != nil { - return err - } - } else { - out.SecretRef = nil - } - out.ReadOnly = in.ReadOnly - return nil -} - -func Convert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in *api.RBDVolumeSource, out *RBDVolumeSource, s conversion.Scope) error { - return autoConvert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in, out, s) -} - -func autoConvert_api_RangeAllocation_To_v1_RangeAllocation(in *api.RangeAllocation, out *RangeAllocation, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.RangeAllocation))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - out.Range = in.Range - if err := conversion.ByteSliceCopy(&in.Data, &out.Data, s); err != nil { - return err - } - return nil -} - -func Convert_api_RangeAllocation_To_v1_RangeAllocation(in *api.RangeAllocation, out *RangeAllocation, s conversion.Scope) error { - return autoConvert_api_RangeAllocation_To_v1_RangeAllocation(in, out, s) -} - -func autoConvert_api_ReplicationController_To_v1_ReplicationController(in *api.ReplicationController, out *ReplicationController, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ReplicationController))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_api_ReplicationControllerSpec_To_v1_ReplicationControllerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func Convert_api_ReplicationController_To_v1_ReplicationController(in *api.ReplicationController, out *ReplicationController, s conversion.Scope) error { - return autoConvert_api_ReplicationController_To_v1_ReplicationController(in, out, s) -} - -func autoConvert_api_ReplicationControllerList_To_v1_ReplicationControllerList(in *api.ReplicationControllerList, out *ReplicationControllerList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ReplicationControllerList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]ReplicationController, len(in.Items)) - for i := range in.Items { - if err := Convert_api_ReplicationController_To_v1_ReplicationController(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_ReplicationControllerList_To_v1_ReplicationControllerList(in *api.ReplicationControllerList, out *ReplicationControllerList, s conversion.Scope) error { - return autoConvert_api_ReplicationControllerList_To_v1_ReplicationControllerList(in, out, s) -} - -func autoConvert_api_ReplicationControllerSpec_To_v1_ReplicationControllerSpec(in *api.ReplicationControllerSpec, out *ReplicationControllerSpec, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ReplicationControllerSpec))(in) - } - if err := s.Convert(&in.Replicas, &out.Replicas, 0); err != nil { - return err - } - if in.Selector != nil { - out.Selector = make(map[string]string) - for key, val := range in.Selector { - out.Selector[key] = val - } - } else { - out.Selector = nil - } - // unable to generate simple pointer conversion for api.PodTemplateSpec -> v1.PodTemplateSpec - if in.Template != nil { - out.Template = new(PodTemplateSpec) - if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in.Template, out.Template, s); err != nil { - return err - } - } else { - out.Template = nil - } - return nil -} - -func autoConvert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(in *api.ReplicationControllerStatus, out *ReplicationControllerStatus, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ReplicationControllerStatus))(in) - } - out.Replicas = int32(in.Replicas) - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -func Convert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(in *api.ReplicationControllerStatus, out *ReplicationControllerStatus, s conversion.Scope) error { - return autoConvert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(in, out, s) -} - -func autoConvert_api_ResourceQuota_To_v1_ResourceQuota(in *api.ResourceQuota, out *ResourceQuota, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ResourceQuota))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func Convert_api_ResourceQuota_To_v1_ResourceQuota(in *api.ResourceQuota, out *ResourceQuota, s conversion.Scope) error { - return autoConvert_api_ResourceQuota_To_v1_ResourceQuota(in, out, s) -} - -func autoConvert_api_ResourceQuotaList_To_v1_ResourceQuotaList(in *api.ResourceQuotaList, out *ResourceQuotaList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ResourceQuotaList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]ResourceQuota, len(in.Items)) - for i := range in.Items { - if err := Convert_api_ResourceQuota_To_v1_ResourceQuota(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_ResourceQuotaList_To_v1_ResourceQuotaList(in *api.ResourceQuotaList, out *ResourceQuotaList, s conversion.Scope) error { - return autoConvert_api_ResourceQuotaList_To_v1_ResourceQuotaList(in, out, s) -} - -func autoConvert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in *api.ResourceQuotaSpec, out *ResourceQuotaSpec, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ResourceQuotaSpec))(in) - } - if in.Hard != nil { - out.Hard = make(ResourceList) - for key, val := range in.Hard { - newVal := resource.Quantity{} - if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { - return err - } - out.Hard[ResourceName(key)] = newVal - } - } else { - out.Hard = nil - } - return nil -} - -func Convert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in *api.ResourceQuotaSpec, out *ResourceQuotaSpec, s conversion.Scope) error { - return autoConvert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in, out, s) -} - -func autoConvert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(in *api.ResourceQuotaStatus, out *ResourceQuotaStatus, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ResourceQuotaStatus))(in) - } - if in.Hard != nil { - out.Hard = make(ResourceList) - for key, val := range in.Hard { - newVal := resource.Quantity{} - if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { - return err - } - out.Hard[ResourceName(key)] = newVal - } - } else { - out.Hard = nil - } - if in.Used != nil { - out.Used = make(ResourceList) - for key, val := range in.Used { - newVal := resource.Quantity{} - if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { - return err - } - out.Used[ResourceName(key)] = newVal - } - } else { - out.Used = nil - } - return nil -} - -func Convert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(in *api.ResourceQuotaStatus, out *ResourceQuotaStatus, s conversion.Scope) error { - return autoConvert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(in, out, s) -} - -func autoConvert_api_ResourceRequirements_To_v1_ResourceRequirements(in *api.ResourceRequirements, out *ResourceRequirements, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ResourceRequirements))(in) - } - if in.Limits != nil { - out.Limits = make(ResourceList) - for key, val := range in.Limits { - newVal := resource.Quantity{} - if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { - return err - } - out.Limits[ResourceName(key)] = newVal - } - } else { - out.Limits = nil - } - if in.Requests != nil { - out.Requests = make(ResourceList) - for key, val := range in.Requests { - newVal := resource.Quantity{} - if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { - return err - } - out.Requests[ResourceName(key)] = newVal - } - } else { - out.Requests = nil - } - return nil -} - -func Convert_api_ResourceRequirements_To_v1_ResourceRequirements(in *api.ResourceRequirements, out *ResourceRequirements, s conversion.Scope) error { - return autoConvert_api_ResourceRequirements_To_v1_ResourceRequirements(in, out, s) -} - -func autoConvert_api_SELinuxOptions_To_v1_SELinuxOptions(in *api.SELinuxOptions, out *SELinuxOptions, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.SELinuxOptions))(in) - } - out.User = in.User - out.Role = in.Role - out.Type = in.Type - out.Level = in.Level - return nil -} - -func Convert_api_SELinuxOptions_To_v1_SELinuxOptions(in *api.SELinuxOptions, out *SELinuxOptions, s conversion.Scope) error { - return autoConvert_api_SELinuxOptions_To_v1_SELinuxOptions(in, out, s) -} - -func autoConvert_api_Secret_To_v1_Secret(in *api.Secret, out *Secret, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.Secret))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if in.Data != nil { - out.Data = make(map[string][]uint8) - for key, val := range in.Data { - newVal := []uint8{} - if err := conversion.ByteSliceCopy(&val, &newVal, s); err != nil { - return err - } - out.Data[key] = newVal - } - } else { - out.Data = nil - } - out.Type = SecretType(in.Type) - return nil -} - -func Convert_api_Secret_To_v1_Secret(in *api.Secret, out *Secret, s conversion.Scope) error { - return autoConvert_api_Secret_To_v1_Secret(in, out, s) -} - -func autoConvert_api_SecretKeySelector_To_v1_SecretKeySelector(in *api.SecretKeySelector, out *SecretKeySelector, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.SecretKeySelector))(in) - } - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { - return err - } - out.Key = in.Key - return nil -} - -func Convert_api_SecretKeySelector_To_v1_SecretKeySelector(in *api.SecretKeySelector, out *SecretKeySelector, s conversion.Scope) error { - return autoConvert_api_SecretKeySelector_To_v1_SecretKeySelector(in, out, s) -} - -func autoConvert_api_SecretList_To_v1_SecretList(in *api.SecretList, out *SecretList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.SecretList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]Secret, len(in.Items)) - for i := range in.Items { - if err := Convert_api_Secret_To_v1_Secret(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_SecretList_To_v1_SecretList(in *api.SecretList, out *SecretList, s conversion.Scope) error { - return autoConvert_api_SecretList_To_v1_SecretList(in, out, s) -} - -func autoConvert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in *api.SecretVolumeSource, out *SecretVolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.SecretVolumeSource))(in) - } - out.SecretName = in.SecretName - return nil -} - -func Convert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in *api.SecretVolumeSource, out *SecretVolumeSource, s conversion.Scope) error { - return autoConvert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in, out, s) -} - -func autoConvert_api_SecurityContext_To_v1_SecurityContext(in *api.SecurityContext, out *SecurityContext, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.SecurityContext))(in) - } - // unable to generate simple pointer conversion for api.Capabilities -> v1.Capabilities - if in.Capabilities != nil { - out.Capabilities = new(Capabilities) - if err := Convert_api_Capabilities_To_v1_Capabilities(in.Capabilities, out.Capabilities, s); err != nil { - return err - } - } else { - out.Capabilities = nil - } - if in.Privileged != nil { - out.Privileged = new(bool) - *out.Privileged = *in.Privileged - } else { - out.Privileged = nil - } - // unable to generate simple pointer conversion for api.SELinuxOptions -> v1.SELinuxOptions - if in.SELinuxOptions != nil { - out.SELinuxOptions = new(SELinuxOptions) - if err := Convert_api_SELinuxOptions_To_v1_SELinuxOptions(in.SELinuxOptions, out.SELinuxOptions, s); err != nil { - return err - } - } else { - out.SELinuxOptions = nil - } - if in.RunAsUser != nil { - out.RunAsUser = new(int64) - *out.RunAsUser = *in.RunAsUser - } else { - out.RunAsUser = nil - } - if in.RunAsNonRoot != nil { - out.RunAsNonRoot = new(bool) - *out.RunAsNonRoot = *in.RunAsNonRoot - } else { - out.RunAsNonRoot = nil - } - if in.ReadOnlyRootFilesystem != nil { - out.ReadOnlyRootFilesystem = new(bool) - *out.ReadOnlyRootFilesystem = *in.ReadOnlyRootFilesystem - } else { - out.ReadOnlyRootFilesystem = nil - } - return nil -} - -func Convert_api_SecurityContext_To_v1_SecurityContext(in *api.SecurityContext, out *SecurityContext, s conversion.Scope) error { - return autoConvert_api_SecurityContext_To_v1_SecurityContext(in, out, s) -} - -func autoConvert_api_SerializedReference_To_v1_SerializedReference(in *api.SerializedReference, out *SerializedReference, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.SerializedReference))(in) - } - if err := Convert_api_ObjectReference_To_v1_ObjectReference(&in.Reference, &out.Reference, s); err != nil { - return err - } - return nil -} - -func Convert_api_SerializedReference_To_v1_SerializedReference(in *api.SerializedReference, out *SerializedReference, s conversion.Scope) error { - return autoConvert_api_SerializedReference_To_v1_SerializedReference(in, out, s) -} - -func autoConvert_api_Service_To_v1_Service(in *api.Service, out *Service, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.Service))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_api_ServiceSpec_To_v1_ServiceSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_api_ServiceStatus_To_v1_ServiceStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -func Convert_api_Service_To_v1_Service(in *api.Service, out *Service, s conversion.Scope) error { - return autoConvert_api_Service_To_v1_Service(in, out, s) -} - -func autoConvert_api_ServiceAccount_To_v1_ServiceAccount(in *api.ServiceAccount, out *ServiceAccount, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ServiceAccount))(in) - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if in.Secrets != nil { - out.Secrets = make([]ObjectReference, len(in.Secrets)) - for i := range in.Secrets { - if err := Convert_api_ObjectReference_To_v1_ObjectReference(&in.Secrets[i], &out.Secrets[i], s); err != nil { - return err - } - } - } else { - out.Secrets = nil - } - if in.ImagePullSecrets != nil { - out.ImagePullSecrets = make([]LocalObjectReference, len(in.ImagePullSecrets)) - for i := range in.ImagePullSecrets { - if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.ImagePullSecrets[i], &out.ImagePullSecrets[i], s); err != nil { - return err - } - } - } else { - out.ImagePullSecrets = nil - } - return nil -} - -func Convert_api_ServiceAccount_To_v1_ServiceAccount(in *api.ServiceAccount, out *ServiceAccount, s conversion.Scope) error { - return autoConvert_api_ServiceAccount_To_v1_ServiceAccount(in, out, s) -} - -func autoConvert_api_ServiceAccountList_To_v1_ServiceAccountList(in *api.ServiceAccountList, out *ServiceAccountList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ServiceAccountList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]ServiceAccount, len(in.Items)) - for i := range in.Items { - if err := Convert_api_ServiceAccount_To_v1_ServiceAccount(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_ServiceAccountList_To_v1_ServiceAccountList(in *api.ServiceAccountList, out *ServiceAccountList, s conversion.Scope) error { - return autoConvert_api_ServiceAccountList_To_v1_ServiceAccountList(in, out, s) -} - -func autoConvert_api_ServiceList_To_v1_ServiceList(in *api.ServiceList, out *ServiceList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ServiceList))(in) - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]Service, len(in.Items)) - for i := range in.Items { - if err := Convert_api_Service_To_v1_Service(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_api_ServiceList_To_v1_ServiceList(in *api.ServiceList, out *ServiceList, s conversion.Scope) error { - return autoConvert_api_ServiceList_To_v1_ServiceList(in, out, s) -} - -func autoConvert_api_ServicePort_To_v1_ServicePort(in *api.ServicePort, out *ServicePort, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ServicePort))(in) - } - out.Name = in.Name - out.Protocol = Protocol(in.Protocol) - out.Port = int32(in.Port) - if err := api.Convert_intstr_IntOrString_To_intstr_IntOrString(&in.TargetPort, &out.TargetPort, s); err != nil { - return err - } - out.NodePort = int32(in.NodePort) - return nil -} - -func Convert_api_ServicePort_To_v1_ServicePort(in *api.ServicePort, out *ServicePort, s conversion.Scope) error { - return autoConvert_api_ServicePort_To_v1_ServicePort(in, out, s) -} - -func autoConvert_api_ServiceSpec_To_v1_ServiceSpec(in *api.ServiceSpec, out *ServiceSpec, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ServiceSpec))(in) - } - out.Type = ServiceType(in.Type) - if in.Ports != nil { - out.Ports = make([]ServicePort, len(in.Ports)) - for i := range in.Ports { - if err := Convert_api_ServicePort_To_v1_ServicePort(&in.Ports[i], &out.Ports[i], s); err != nil { - return err - } - } - } else { - out.Ports = nil - } - if in.Selector != nil { - out.Selector = make(map[string]string) - for key, val := range in.Selector { - out.Selector[key] = val - } - } else { - out.Selector = nil - } - out.ClusterIP = in.ClusterIP - if in.ExternalIPs != nil { - out.ExternalIPs = make([]string, len(in.ExternalIPs)) - for i := range in.ExternalIPs { - out.ExternalIPs[i] = in.ExternalIPs[i] - } - } else { - out.ExternalIPs = nil - } - out.LoadBalancerIP = in.LoadBalancerIP - out.SessionAffinity = ServiceAffinity(in.SessionAffinity) - return nil -} - -func autoConvert_api_ServiceStatus_To_v1_ServiceStatus(in *api.ServiceStatus, out *ServiceStatus, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.ServiceStatus))(in) - } - if err := Convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus(&in.LoadBalancer, &out.LoadBalancer, s); err != nil { - return err - } - return nil -} - -func Convert_api_ServiceStatus_To_v1_ServiceStatus(in *api.ServiceStatus, out *ServiceStatus, s conversion.Scope) error { - return autoConvert_api_ServiceStatus_To_v1_ServiceStatus(in, out, s) -} - -func autoConvert_api_TCPSocketAction_To_v1_TCPSocketAction(in *api.TCPSocketAction, out *TCPSocketAction, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.TCPSocketAction))(in) - } - if err := api.Convert_intstr_IntOrString_To_intstr_IntOrString(&in.Port, &out.Port, s); err != nil { - return err - } - return nil -} - -func Convert_api_TCPSocketAction_To_v1_TCPSocketAction(in *api.TCPSocketAction, out *TCPSocketAction, s conversion.Scope) error { - return autoConvert_api_TCPSocketAction_To_v1_TCPSocketAction(in, out, s) -} - -func autoConvert_api_Volume_To_v1_Volume(in *api.Volume, out *Volume, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.Volume))(in) - } - out.Name = in.Name - if err := Convert_api_VolumeSource_To_v1_VolumeSource(&in.VolumeSource, &out.VolumeSource, s); err != nil { - return err - } - return nil -} - -func Convert_api_Volume_To_v1_Volume(in *api.Volume, out *Volume, s conversion.Scope) error { - return autoConvert_api_Volume_To_v1_Volume(in, out, s) -} - -func autoConvert_api_VolumeMount_To_v1_VolumeMount(in *api.VolumeMount, out *VolumeMount, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.VolumeMount))(in) - } - out.Name = in.Name - out.ReadOnly = in.ReadOnly - out.MountPath = in.MountPath - return nil -} - -func Convert_api_VolumeMount_To_v1_VolumeMount(in *api.VolumeMount, out *VolumeMount, s conversion.Scope) error { - return autoConvert_api_VolumeMount_To_v1_VolumeMount(in, out, s) -} - -func autoConvert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *VolumeSource, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*api.VolumeSource))(in) - } - // unable to generate simple pointer conversion for api.HostPathVolumeSource -> v1.HostPathVolumeSource - if in.HostPath != nil { - out.HostPath = new(HostPathVolumeSource) - if err := Convert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in.HostPath, out.HostPath, s); err != nil { - return err - } - } else { - out.HostPath = nil - } - // unable to generate simple pointer conversion for api.EmptyDirVolumeSource -> v1.EmptyDirVolumeSource - if in.EmptyDir != nil { - out.EmptyDir = new(EmptyDirVolumeSource) - if err := Convert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in.EmptyDir, out.EmptyDir, s); err != nil { - return err - } - } else { - out.EmptyDir = nil - } - // unable to generate simple pointer conversion for api.GCEPersistentDiskVolumeSource -> v1.GCEPersistentDiskVolumeSource - if in.GCEPersistentDisk != nil { - out.GCEPersistentDisk = new(GCEPersistentDiskVolumeSource) - if err := Convert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in.GCEPersistentDisk, out.GCEPersistentDisk, s); err != nil { - return err - } - } else { - out.GCEPersistentDisk = nil - } - // unable to generate simple pointer conversion for api.AWSElasticBlockStoreVolumeSource -> v1.AWSElasticBlockStoreVolumeSource - if in.AWSElasticBlockStore != nil { - out.AWSElasticBlockStore = new(AWSElasticBlockStoreVolumeSource) - if err := Convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in.AWSElasticBlockStore, out.AWSElasticBlockStore, s); err != nil { - return err - } - } else { - out.AWSElasticBlockStore = nil - } - // unable to generate simple pointer conversion for api.GitRepoVolumeSource -> v1.GitRepoVolumeSource - if in.GitRepo != nil { - out.GitRepo = new(GitRepoVolumeSource) - if err := Convert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in.GitRepo, out.GitRepo, s); err != nil { - return err - } - } else { - out.GitRepo = nil - } - // unable to generate simple pointer conversion for api.SecretVolumeSource -> v1.SecretVolumeSource - if in.Secret != nil { - out.Secret = new(SecretVolumeSource) - if err := Convert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in.Secret, out.Secret, s); err != nil { - return err - } - } else { - out.Secret = nil - } - // unable to generate simple pointer conversion for api.NFSVolumeSource -> v1.NFSVolumeSource - if in.NFS != nil { - out.NFS = new(NFSVolumeSource) - if err := Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in.NFS, out.NFS, s); err != nil { - return err - } - } else { - out.NFS = nil - } - // unable to generate simple pointer conversion for api.ISCSIVolumeSource -> v1.ISCSIVolumeSource - if in.ISCSI != nil { - out.ISCSI = new(ISCSIVolumeSource) - if err := Convert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in.ISCSI, out.ISCSI, s); err != nil { - return err - } - } else { - out.ISCSI = nil - } - // unable to generate simple pointer conversion for api.GlusterfsVolumeSource -> v1.GlusterfsVolumeSource - if in.Glusterfs != nil { - out.Glusterfs = new(GlusterfsVolumeSource) - if err := Convert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in.Glusterfs, out.Glusterfs, s); err != nil { - return err - } - } else { - out.Glusterfs = nil - } - // unable to generate simple pointer conversion for api.PersistentVolumeClaimVolumeSource -> v1.PersistentVolumeClaimVolumeSource - if in.PersistentVolumeClaim != nil { - out.PersistentVolumeClaim = new(PersistentVolumeClaimVolumeSource) - if err := Convert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in.PersistentVolumeClaim, out.PersistentVolumeClaim, s); err != nil { - return err - } - } else { - out.PersistentVolumeClaim = nil - } - // unable to generate simple pointer conversion for api.RBDVolumeSource -> v1.RBDVolumeSource - if in.RBD != nil { - out.RBD = new(RBDVolumeSource) - if err := Convert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in.RBD, out.RBD, s); err != nil { - return err - } - } else { - out.RBD = nil - } - // unable to generate simple pointer conversion for api.FlexVolumeSource -> v1.FlexVolumeSource - if in.FlexVolume != nil { - out.FlexVolume = new(FlexVolumeSource) - if err := Convert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in.FlexVolume, out.FlexVolume, s); err != nil { - return err - } - } else { - out.FlexVolume = nil - } - // unable to generate simple pointer conversion for api.CinderVolumeSource -> v1.CinderVolumeSource - if in.Cinder != nil { - out.Cinder = new(CinderVolumeSource) - if err := Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in.Cinder, out.Cinder, s); err != nil { - return err - } - } else { - out.Cinder = nil - } - // unable to generate simple pointer conversion for api.CephFSVolumeSource -> v1.CephFSVolumeSource - if in.CephFS != nil { - out.CephFS = new(CephFSVolumeSource) - if err := Convert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in.CephFS, out.CephFS, s); err != nil { - return err - } - } else { - out.CephFS = nil - } - // unable to generate simple pointer conversion for api.FlockerVolumeSource -> v1.FlockerVolumeSource - if in.Flocker != nil { - out.Flocker = new(FlockerVolumeSource) - if err := Convert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in.Flocker, out.Flocker, s); err != nil { - return err - } - } else { - out.Flocker = nil - } - // unable to generate simple pointer conversion for api.DownwardAPIVolumeSource -> v1.DownwardAPIVolumeSource - if in.DownwardAPI != nil { - out.DownwardAPI = new(DownwardAPIVolumeSource) - if err := Convert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in.DownwardAPI, out.DownwardAPI, s); err != nil { - return err - } - } else { - out.DownwardAPI = nil - } - // unable to generate simple pointer conversion for api.FCVolumeSource -> v1.FCVolumeSource - if in.FC != nil { - out.FC = new(FCVolumeSource) - if err := Convert_api_FCVolumeSource_To_v1_FCVolumeSource(in.FC, out.FC, s); err != nil { - return err - } - } else { - out.FC = nil - } - // unable to generate simple pointer conversion for api.AzureFileVolumeSource -> v1.AzureFileVolumeSource - if in.AzureFile != nil { - out.AzureFile = new(AzureFileVolumeSource) - if err := Convert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in.AzureFile, out.AzureFile, s); err != nil { - return err - } - } else { - out.AzureFile = nil - } - // unable to generate simple pointer conversion for api.ConfigMapVolumeSource -> v1.ConfigMapVolumeSource - if in.ConfigMap != nil { - out.ConfigMap = new(ConfigMapVolumeSource) - if err := Convert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in.ConfigMap, out.ConfigMap, s); err != nil { - return err - } - } else { - out.ConfigMap = nil - } - return nil -} - -func Convert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *VolumeSource, s conversion.Scope) error { - return autoConvert_api_VolumeSource_To_v1_VolumeSource(in, out, s) -} - -func autoConvert_unversioned_ExportOptions_To_v1_ExportOptions(in *unversioned.ExportOptions, out *ExportOptions, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*unversioned.ExportOptions))(in) - } - out.Export = in.Export - out.Exact = in.Exact - return nil -} - -func Convert_unversioned_ExportOptions_To_v1_ExportOptions(in *unversioned.ExportOptions, out *ExportOptions, s conversion.Scope) error { - return autoConvert_unversioned_ExportOptions_To_v1_ExportOptions(in, out, s) } func autoConvert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource(in *AWSElasticBlockStoreVolumeSource, out *api.AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { @@ -3315,6 +331,61 @@ func Convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolu return autoConvert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource(in, out, s) } +func autoConvert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in *api.AWSElasticBlockStoreVolumeSource, out *AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.AWSElasticBlockStoreVolumeSource))(in) + } + out.VolumeID = in.VolumeID + out.FSType = in.FSType + out.Partition = int32(in.Partition) + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in *api.AWSElasticBlockStoreVolumeSource, out *AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { + return autoConvert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in, out, s) +} + +func autoConvert_v1_Affinity_To_api_Affinity(in *Affinity, out *api.Affinity, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*Affinity))(in) + } + if in.NodeAffinity != nil { + in, out := &in.NodeAffinity, &out.NodeAffinity + *out = new(api.NodeAffinity) + if err := Convert_v1_NodeAffinity_To_api_NodeAffinity(*in, *out, s); err != nil { + return err + } + } else { + out.NodeAffinity = nil + } + return nil +} + +func Convert_v1_Affinity_To_api_Affinity(in *Affinity, out *api.Affinity, s conversion.Scope) error { + return autoConvert_v1_Affinity_To_api_Affinity(in, out, s) +} + +func autoConvert_api_Affinity_To_v1_Affinity(in *api.Affinity, out *Affinity, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Affinity))(in) + } + if in.NodeAffinity != nil { + in, out := &in.NodeAffinity, &out.NodeAffinity + *out = new(NodeAffinity) + if err := Convert_api_NodeAffinity_To_v1_NodeAffinity(*in, *out, s); err != nil { + return err + } + } else { + out.NodeAffinity = nil + } + return nil +} + +func Convert_api_Affinity_To_v1_Affinity(in *api.Affinity, out *Affinity, s conversion.Scope) error { + return autoConvert_api_Affinity_To_v1_Affinity(in, out, s) +} + func autoConvert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource(in *AzureFileVolumeSource, out *api.AzureFileVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*AzureFileVolumeSource))(in) @@ -3329,10 +400,27 @@ func Convert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource(in *AzureFile return autoConvert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource(in, out, s) } +func autoConvert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in *api.AzureFileVolumeSource, out *AzureFileVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.AzureFileVolumeSource))(in) + } + out.SecretName = in.SecretName + out.ShareName = in.ShareName + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in *api.AzureFileVolumeSource, out *AzureFileVolumeSource, s conversion.Scope) error { + return autoConvert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in, out, s) +} + func autoConvert_v1_Binding_To_api_Binding(in *Binding, out *api.Binding, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Binding))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } @@ -3346,22 +434,44 @@ func Convert_v1_Binding_To_api_Binding(in *Binding, out *api.Binding, s conversi return autoConvert_v1_Binding_To_api_Binding(in, out, s) } +func autoConvert_api_Binding_To_v1_Binding(in *api.Binding, out *Binding, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Binding))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectReference_To_v1_ObjectReference(&in.Target, &out.Target, s); err != nil { + return err + } + return nil +} + +func Convert_api_Binding_To_v1_Binding(in *api.Binding, out *Binding, s conversion.Scope) error { + return autoConvert_api_Binding_To_v1_Binding(in, out, s) +} + func autoConvert_v1_Capabilities_To_api_Capabilities(in *Capabilities, out *api.Capabilities, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Capabilities))(in) } if in.Add != nil { - out.Add = make([]api.Capability, len(in.Add)) - for i := range in.Add { - out.Add[i] = api.Capability(in.Add[i]) + in, out := &in.Add, &out.Add + *out = make([]api.Capability, len(*in)) + for i := range *in { + (*out)[i] = api.Capability((*in)[i]) } } else { out.Add = nil } if in.Drop != nil { - out.Drop = make([]api.Capability, len(in.Drop)) - for i := range in.Drop { - out.Drop[i] = api.Capability(in.Drop[i]) + in, out := &in.Drop, &out.Drop + *out = make([]api.Capability, len(*in)) + for i := range *in { + (*out)[i] = api.Capability((*in)[i]) } } else { out.Drop = nil @@ -3373,25 +483,53 @@ func Convert_v1_Capabilities_To_api_Capabilities(in *Capabilities, out *api.Capa return autoConvert_v1_Capabilities_To_api_Capabilities(in, out, s) } +func autoConvert_api_Capabilities_To_v1_Capabilities(in *api.Capabilities, out *Capabilities, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Capabilities))(in) + } + if in.Add != nil { + in, out := &in.Add, &out.Add + *out = make([]Capability, len(*in)) + for i := range *in { + (*out)[i] = Capability((*in)[i]) + } + } else { + out.Add = nil + } + if in.Drop != nil { + in, out := &in.Drop, &out.Drop + *out = make([]Capability, len(*in)) + for i := range *in { + (*out)[i] = Capability((*in)[i]) + } + } else { + out.Drop = nil + } + return nil +} + +func Convert_api_Capabilities_To_v1_Capabilities(in *api.Capabilities, out *Capabilities, s conversion.Scope) error { + return autoConvert_api_Capabilities_To_v1_Capabilities(in, out, s) +} + func autoConvert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource(in *CephFSVolumeSource, out *api.CephFSVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*CephFSVolumeSource))(in) } if in.Monitors != nil { - out.Monitors = make([]string, len(in.Monitors)) - for i := range in.Monitors { - out.Monitors[i] = in.Monitors[i] - } + in, out := &in.Monitors, &out.Monitors + *out = make([]string, len(*in)) + copy(*out, *in) } else { out.Monitors = nil } out.Path = in.Path out.User = in.User out.SecretFile = in.SecretFile - // unable to generate simple pointer conversion for v1.LocalObjectReference -> api.LocalObjectReference if in.SecretRef != nil { - out.SecretRef = new(api.LocalObjectReference) - if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(in.SecretRef, out.SecretRef, s); err != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(api.LocalObjectReference) + if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(*in, *out, s); err != nil { return err } } else { @@ -3405,6 +543,37 @@ func Convert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource(in *CephFSVolumeSou return autoConvert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource(in, out, s) } +func autoConvert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in *api.CephFSVolumeSource, out *CephFSVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.CephFSVolumeSource))(in) + } + if in.Monitors != nil { + in, out := &in.Monitors, &out.Monitors + *out = make([]string, len(*in)) + copy(*out, *in) + } else { + out.Monitors = nil + } + out.Path = in.Path + out.User = in.User + out.SecretFile = in.SecretFile + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(LocalObjectReference) + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(*in, *out, s); err != nil { + return err + } + } else { + out.SecretRef = nil + } + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in *api.CephFSVolumeSource, out *CephFSVolumeSource, s conversion.Scope) error { + return autoConvert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in, out, s) +} + func autoConvert_v1_CinderVolumeSource_To_api_CinderVolumeSource(in *CinderVolumeSource, out *api.CinderVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*CinderVolumeSource))(in) @@ -3419,6 +588,20 @@ func Convert_v1_CinderVolumeSource_To_api_CinderVolumeSource(in *CinderVolumeSou return autoConvert_v1_CinderVolumeSource_To_api_CinderVolumeSource(in, out, s) } +func autoConvert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in *api.CinderVolumeSource, out *CinderVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.CinderVolumeSource))(in) + } + out.VolumeID = in.VolumeID + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in *api.CinderVolumeSource, out *CinderVolumeSource, s conversion.Scope) error { + return autoConvert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in, out, s) +} + func autoConvert_v1_ComponentCondition_To_api_ComponentCondition(in *ComponentCondition, out *api.ComponentCondition, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ComponentCondition))(in) @@ -3434,17 +617,36 @@ func Convert_v1_ComponentCondition_To_api_ComponentCondition(in *ComponentCondit return autoConvert_v1_ComponentCondition_To_api_ComponentCondition(in, out, s) } +func autoConvert_api_ComponentCondition_To_v1_ComponentCondition(in *api.ComponentCondition, out *ComponentCondition, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ComponentCondition))(in) + } + out.Type = ComponentConditionType(in.Type) + out.Status = ConditionStatus(in.Status) + out.Message = in.Message + out.Error = in.Error + return nil +} + +func Convert_api_ComponentCondition_To_v1_ComponentCondition(in *api.ComponentCondition, out *ComponentCondition, s conversion.Scope) error { + return autoConvert_api_ComponentCondition_To_v1_ComponentCondition(in, out, s) +} + func autoConvert_v1_ComponentStatus_To_api_ComponentStatus(in *ComponentStatus, out *api.ComponentStatus, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ComponentStatus))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } if in.Conditions != nil { - out.Conditions = make([]api.ComponentCondition, len(in.Conditions)) - for i := range in.Conditions { - if err := Convert_v1_ComponentCondition_To_api_ComponentCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]api.ComponentCondition, len(*in)) + for i := range *in { + if err := Convert_v1_ComponentCondition_To_api_ComponentCondition(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -3458,17 +660,49 @@ func Convert_v1_ComponentStatus_To_api_ComponentStatus(in *ComponentStatus, out return autoConvert_v1_ComponentStatus_To_api_ComponentStatus(in, out, s) } +func autoConvert_api_ComponentStatus_To_v1_ComponentStatus(in *api.ComponentStatus, out *ComponentStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ComponentStatus))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]ComponentCondition, len(*in)) + for i := range *in { + if err := Convert_api_ComponentCondition_To_v1_ComponentCondition(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Conditions = nil + } + return nil +} + +func Convert_api_ComponentStatus_To_v1_ComponentStatus(in *api.ComponentStatus, out *ComponentStatus, s conversion.Scope) error { + return autoConvert_api_ComponentStatus_To_v1_ComponentStatus(in, out, s) +} + func autoConvert_v1_ComponentStatusList_To_api_ComponentStatusList(in *ComponentStatusList, out *api.ComponentStatusList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ComponentStatusList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.ComponentStatus, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_ComponentStatus_To_api_ComponentStatus(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.ComponentStatus, len(*in)) + for i := range *in { + if err := Convert_v1_ComponentStatus_To_api_ComponentStatus(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -3482,17 +716,49 @@ func Convert_v1_ComponentStatusList_To_api_ComponentStatusList(in *ComponentStat return autoConvert_v1_ComponentStatusList_To_api_ComponentStatusList(in, out, s) } +func autoConvert_api_ComponentStatusList_To_v1_ComponentStatusList(in *api.ComponentStatusList, out *ComponentStatusList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ComponentStatusList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ComponentStatus, len(*in)) + for i := range *in { + if err := Convert_api_ComponentStatus_To_v1_ComponentStatus(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_ComponentStatusList_To_v1_ComponentStatusList(in *api.ComponentStatusList, out *ComponentStatusList, s conversion.Scope) error { + return autoConvert_api_ComponentStatusList_To_v1_ComponentStatusList(in, out, s) +} + func autoConvert_v1_ConfigMap_To_api_ConfigMap(in *ConfigMap, out *api.ConfigMap, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ConfigMap))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } if in.Data != nil { - out.Data = make(map[string]string) - for key, val := range in.Data { - out.Data[key] = val + in, out := &in.Data, &out.Data + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } else { out.Data = nil @@ -3504,6 +770,32 @@ func Convert_v1_ConfigMap_To_api_ConfigMap(in *ConfigMap, out *api.ConfigMap, s return autoConvert_v1_ConfigMap_To_api_ConfigMap(in, out, s) } +func autoConvert_api_ConfigMap_To_v1_ConfigMap(in *api.ConfigMap, out *ConfigMap, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ConfigMap))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if in.Data != nil { + in, out := &in.Data, &out.Data + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } else { + out.Data = nil + } + return nil +} + +func Convert_api_ConfigMap_To_v1_ConfigMap(in *api.ConfigMap, out *ConfigMap, s conversion.Scope) error { + return autoConvert_api_ConfigMap_To_v1_ConfigMap(in, out, s) +} + func autoConvert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector(in *ConfigMapKeySelector, out *api.ConfigMapKeySelector, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ConfigMapKeySelector))(in) @@ -3519,17 +811,36 @@ func Convert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector(in *ConfigMapKe return autoConvert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector(in, out, s) } +func autoConvert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in *api.ConfigMapKeySelector, out *ConfigMapKeySelector, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ConfigMapKeySelector))(in) + } + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Key = in.Key + return nil +} + +func Convert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in *api.ConfigMapKeySelector, out *ConfigMapKeySelector, s conversion.Scope) error { + return autoConvert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in, out, s) +} + func autoConvert_v1_ConfigMapList_To_api_ConfigMapList(in *ConfigMapList, out *api.ConfigMapList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ConfigMapList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.ConfigMap, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_ConfigMap_To_api_ConfigMap(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.ConfigMap, len(*in)) + for i := range *in { + if err := Convert_v1_ConfigMap_To_api_ConfigMap(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -3543,6 +854,34 @@ func Convert_v1_ConfigMapList_To_api_ConfigMapList(in *ConfigMapList, out *api.C return autoConvert_v1_ConfigMapList_To_api_ConfigMapList(in, out, s) } +func autoConvert_api_ConfigMapList_To_v1_ConfigMapList(in *api.ConfigMapList, out *ConfigMapList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ConfigMapList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ConfigMap, len(*in)) + for i := range *in { + if err := Convert_api_ConfigMap_To_v1_ConfigMap(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_ConfigMapList_To_v1_ConfigMapList(in *api.ConfigMapList, out *ConfigMapList, s conversion.Scope) error { + return autoConvert_api_ConfigMapList_To_v1_ConfigMapList(in, out, s) +} + func autoConvert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource(in *ConfigMapVolumeSource, out *api.ConfigMapVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ConfigMapVolumeSource))(in) @@ -3551,9 +890,10 @@ func autoConvert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource(in *Confi return err } if in.Items != nil { - out.Items = make([]api.KeyToPath, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_KeyToPath_To_api_KeyToPath(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.KeyToPath, len(*in)) + for i := range *in { + if err := Convert_v1_KeyToPath_To_api_KeyToPath(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -3567,6 +907,31 @@ func Convert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource(in *ConfigMap return autoConvert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource(in, out, s) } +func autoConvert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in *api.ConfigMapVolumeSource, out *ConfigMapVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ConfigMapVolumeSource))(in) + } + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]KeyToPath, len(*in)) + for i := range *in { + if err := Convert_api_KeyToPath_To_v1_KeyToPath(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in *api.ConfigMapVolumeSource, out *ConfigMapVolumeSource, s conversion.Scope) error { + return autoConvert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in, out, s) +} + func autoConvert_v1_Container_To_api_Container(in *Container, out *api.Container, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Container))(in) @@ -3574,26 +939,25 @@ func autoConvert_v1_Container_To_api_Container(in *Container, out *api.Container out.Name = in.Name out.Image = in.Image if in.Command != nil { - out.Command = make([]string, len(in.Command)) - for i := range in.Command { - out.Command[i] = in.Command[i] - } + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) } else { out.Command = nil } if in.Args != nil { - out.Args = make([]string, len(in.Args)) - for i := range in.Args { - out.Args[i] = in.Args[i] - } + in, out := &in.Args, &out.Args + *out = make([]string, len(*in)) + copy(*out, *in) } else { out.Args = nil } out.WorkingDir = in.WorkingDir if in.Ports != nil { - out.Ports = make([]api.ContainerPort, len(in.Ports)) - for i := range in.Ports { - if err := Convert_v1_ContainerPort_To_api_ContainerPort(&in.Ports[i], &out.Ports[i], s); err != nil { + in, out := &in.Ports, &out.Ports + *out = make([]api.ContainerPort, len(*in)) + for i := range *in { + if err := Convert_v1_ContainerPort_To_api_ContainerPort(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -3601,9 +965,10 @@ func autoConvert_v1_Container_To_api_Container(in *Container, out *api.Container out.Ports = nil } if in.Env != nil { - out.Env = make([]api.EnvVar, len(in.Env)) - for i := range in.Env { - if err := Convert_v1_EnvVar_To_api_EnvVar(&in.Env[i], &out.Env[i], s); err != nil { + in, out := &in.Env, &out.Env + *out = make([]api.EnvVar, len(*in)) + for i := range *in { + if err := Convert_v1_EnvVar_To_api_EnvVar(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -3614,37 +979,38 @@ func autoConvert_v1_Container_To_api_Container(in *Container, out *api.Container return err } if in.VolumeMounts != nil { - out.VolumeMounts = make([]api.VolumeMount, len(in.VolumeMounts)) - for i := range in.VolumeMounts { - if err := Convert_v1_VolumeMount_To_api_VolumeMount(&in.VolumeMounts[i], &out.VolumeMounts[i], s); err != nil { + in, out := &in.VolumeMounts, &out.VolumeMounts + *out = make([]api.VolumeMount, len(*in)) + for i := range *in { + if err := Convert_v1_VolumeMount_To_api_VolumeMount(&(*in)[i], &(*out)[i], s); err != nil { return err } } } else { out.VolumeMounts = nil } - // unable to generate simple pointer conversion for v1.Probe -> api.Probe if in.LivenessProbe != nil { - out.LivenessProbe = new(api.Probe) - if err := Convert_v1_Probe_To_api_Probe(in.LivenessProbe, out.LivenessProbe, s); err != nil { + in, out := &in.LivenessProbe, &out.LivenessProbe + *out = new(api.Probe) + if err := Convert_v1_Probe_To_api_Probe(*in, *out, s); err != nil { return err } } else { out.LivenessProbe = nil } - // unable to generate simple pointer conversion for v1.Probe -> api.Probe if in.ReadinessProbe != nil { - out.ReadinessProbe = new(api.Probe) - if err := Convert_v1_Probe_To_api_Probe(in.ReadinessProbe, out.ReadinessProbe, s); err != nil { + in, out := &in.ReadinessProbe, &out.ReadinessProbe + *out = new(api.Probe) + if err := Convert_v1_Probe_To_api_Probe(*in, *out, s); err != nil { return err } } else { out.ReadinessProbe = nil } - // unable to generate simple pointer conversion for v1.Lifecycle -> api.Lifecycle if in.Lifecycle != nil { - out.Lifecycle = new(api.Lifecycle) - if err := Convert_v1_Lifecycle_To_api_Lifecycle(in.Lifecycle, out.Lifecycle, s); err != nil { + in, out := &in.Lifecycle, &out.Lifecycle + *out = new(api.Lifecycle) + if err := Convert_v1_Lifecycle_To_api_Lifecycle(*in, *out, s); err != nil { return err } } else { @@ -3652,10 +1018,10 @@ func autoConvert_v1_Container_To_api_Container(in *Container, out *api.Container } out.TerminationMessagePath = in.TerminationMessagePath out.ImagePullPolicy = api.PullPolicy(in.ImagePullPolicy) - // unable to generate simple pointer conversion for v1.SecurityContext -> api.SecurityContext if in.SecurityContext != nil { - out.SecurityContext = new(api.SecurityContext) - if err := Convert_v1_SecurityContext_To_api_SecurityContext(in.SecurityContext, out.SecurityContext, s); err != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(api.SecurityContext) + if err := Convert_v1_SecurityContext_To_api_SecurityContext(*in, *out, s); err != nil { return err } } else { @@ -3671,19 +1037,123 @@ func Convert_v1_Container_To_api_Container(in *Container, out *api.Container, s return autoConvert_v1_Container_To_api_Container(in, out, s) } +func autoConvert_api_Container_To_v1_Container(in *api.Container, out *Container, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Container))(in) + } + out.Name = in.Name + out.Image = in.Image + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } else { + out.Command = nil + } + if in.Args != nil { + in, out := &in.Args, &out.Args + *out = make([]string, len(*in)) + copy(*out, *in) + } else { + out.Args = nil + } + out.WorkingDir = in.WorkingDir + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]ContainerPort, len(*in)) + for i := range *in { + if err := Convert_api_ContainerPort_To_v1_ContainerPort(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Ports = nil + } + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]EnvVar, len(*in)) + for i := range *in { + if err := Convert_api_EnvVar_To_v1_EnvVar(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Env = nil + } + if err := Convert_api_ResourceRequirements_To_v1_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil { + return err + } + if in.VolumeMounts != nil { + in, out := &in.VolumeMounts, &out.VolumeMounts + *out = make([]VolumeMount, len(*in)) + for i := range *in { + if err := Convert_api_VolumeMount_To_v1_VolumeMount(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.VolumeMounts = nil + } + if in.LivenessProbe != nil { + in, out := &in.LivenessProbe, &out.LivenessProbe + *out = new(Probe) + if err := Convert_api_Probe_To_v1_Probe(*in, *out, s); err != nil { + return err + } + } else { + out.LivenessProbe = nil + } + if in.ReadinessProbe != nil { + in, out := &in.ReadinessProbe, &out.ReadinessProbe + *out = new(Probe) + if err := Convert_api_Probe_To_v1_Probe(*in, *out, s); err != nil { + return err + } + } else { + out.ReadinessProbe = nil + } + if in.Lifecycle != nil { + in, out := &in.Lifecycle, &out.Lifecycle + *out = new(Lifecycle) + if err := Convert_api_Lifecycle_To_v1_Lifecycle(*in, *out, s); err != nil { + return err + } + } else { + out.Lifecycle = nil + } + out.TerminationMessagePath = in.TerminationMessagePath + out.ImagePullPolicy = PullPolicy(in.ImagePullPolicy) + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(SecurityContext) + if err := Convert_api_SecurityContext_To_v1_SecurityContext(*in, *out, s); err != nil { + return err + } + } else { + out.SecurityContext = nil + } + out.Stdin = in.Stdin + out.StdinOnce = in.StdinOnce + out.TTY = in.TTY + return nil +} + +func Convert_api_Container_To_v1_Container(in *api.Container, out *Container, s conversion.Scope) error { + return autoConvert_api_Container_To_v1_Container(in, out, s) +} + func autoConvert_v1_ContainerImage_To_api_ContainerImage(in *ContainerImage, out *api.ContainerImage, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ContainerImage))(in) } - if in.RepoTags != nil { - out.RepoTags = make([]string, len(in.RepoTags)) - for i := range in.RepoTags { - out.RepoTags[i] = in.RepoTags[i] - } + if in.Names != nil { + in, out := &in.Names, &out.Names + *out = make([]string, len(*in)) + copy(*out, *in) } else { - out.RepoTags = nil + out.Names = nil } - out.Size = in.Size + out.SizeBytes = in.SizeBytes return nil } @@ -3691,6 +1161,25 @@ func Convert_v1_ContainerImage_To_api_ContainerImage(in *ContainerImage, out *ap return autoConvert_v1_ContainerImage_To_api_ContainerImage(in, out, s) } +func autoConvert_api_ContainerImage_To_v1_ContainerImage(in *api.ContainerImage, out *ContainerImage, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ContainerImage))(in) + } + if in.Names != nil { + in, out := &in.Names, &out.Names + *out = make([]string, len(*in)) + copy(*out, *in) + } else { + out.Names = nil + } + out.SizeBytes = in.SizeBytes + return nil +} + +func Convert_api_ContainerImage_To_v1_ContainerImage(in *api.ContainerImage, out *ContainerImage, s conversion.Scope) error { + return autoConvert_api_ContainerImage_To_v1_ContainerImage(in, out, s) +} + func autoConvert_v1_ContainerPort_To_api_ContainerPort(in *ContainerPort, out *api.ContainerPort, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ContainerPort))(in) @@ -3707,32 +1196,48 @@ func Convert_v1_ContainerPort_To_api_ContainerPort(in *ContainerPort, out *api.C return autoConvert_v1_ContainerPort_To_api_ContainerPort(in, out, s) } +func autoConvert_api_ContainerPort_To_v1_ContainerPort(in *api.ContainerPort, out *ContainerPort, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ContainerPort))(in) + } + out.Name = in.Name + out.HostPort = int32(in.HostPort) + out.ContainerPort = int32(in.ContainerPort) + out.Protocol = Protocol(in.Protocol) + out.HostIP = in.HostIP + return nil +} + +func Convert_api_ContainerPort_To_v1_ContainerPort(in *api.ContainerPort, out *ContainerPort, s conversion.Scope) error { + return autoConvert_api_ContainerPort_To_v1_ContainerPort(in, out, s) +} + func autoConvert_v1_ContainerState_To_api_ContainerState(in *ContainerState, out *api.ContainerState, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ContainerState))(in) } - // unable to generate simple pointer conversion for v1.ContainerStateWaiting -> api.ContainerStateWaiting if in.Waiting != nil { - out.Waiting = new(api.ContainerStateWaiting) - if err := Convert_v1_ContainerStateWaiting_To_api_ContainerStateWaiting(in.Waiting, out.Waiting, s); err != nil { + in, out := &in.Waiting, &out.Waiting + *out = new(api.ContainerStateWaiting) + if err := Convert_v1_ContainerStateWaiting_To_api_ContainerStateWaiting(*in, *out, s); err != nil { return err } } else { out.Waiting = nil } - // unable to generate simple pointer conversion for v1.ContainerStateRunning -> api.ContainerStateRunning if in.Running != nil { - out.Running = new(api.ContainerStateRunning) - if err := Convert_v1_ContainerStateRunning_To_api_ContainerStateRunning(in.Running, out.Running, s); err != nil { + in, out := &in.Running, &out.Running + *out = new(api.ContainerStateRunning) + if err := Convert_v1_ContainerStateRunning_To_api_ContainerStateRunning(*in, *out, s); err != nil { return err } } else { out.Running = nil } - // unable to generate simple pointer conversion for v1.ContainerStateTerminated -> api.ContainerStateTerminated if in.Terminated != nil { - out.Terminated = new(api.ContainerStateTerminated) - if err := Convert_v1_ContainerStateTerminated_To_api_ContainerStateTerminated(in.Terminated, out.Terminated, s); err != nil { + in, out := &in.Terminated, &out.Terminated + *out = new(api.ContainerStateTerminated) + if err := Convert_v1_ContainerStateTerminated_To_api_ContainerStateTerminated(*in, *out, s); err != nil { return err } } else { @@ -3745,6 +1250,44 @@ func Convert_v1_ContainerState_To_api_ContainerState(in *ContainerState, out *ap return autoConvert_v1_ContainerState_To_api_ContainerState(in, out, s) } +func autoConvert_api_ContainerState_To_v1_ContainerState(in *api.ContainerState, out *ContainerState, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ContainerState))(in) + } + if in.Waiting != nil { + in, out := &in.Waiting, &out.Waiting + *out = new(ContainerStateWaiting) + if err := Convert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting(*in, *out, s); err != nil { + return err + } + } else { + out.Waiting = nil + } + if in.Running != nil { + in, out := &in.Running, &out.Running + *out = new(ContainerStateRunning) + if err := Convert_api_ContainerStateRunning_To_v1_ContainerStateRunning(*in, *out, s); err != nil { + return err + } + } else { + out.Running = nil + } + if in.Terminated != nil { + in, out := &in.Terminated, &out.Terminated + *out = new(ContainerStateTerminated) + if err := Convert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated(*in, *out, s); err != nil { + return err + } + } else { + out.Terminated = nil + } + return nil +} + +func Convert_api_ContainerState_To_v1_ContainerState(in *api.ContainerState, out *ContainerState, s conversion.Scope) error { + return autoConvert_api_ContainerState_To_v1_ContainerState(in, out, s) +} + func autoConvert_v1_ContainerStateRunning_To_api_ContainerStateRunning(in *ContainerStateRunning, out *api.ContainerStateRunning, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ContainerStateRunning))(in) @@ -3759,6 +1302,20 @@ func Convert_v1_ContainerStateRunning_To_api_ContainerStateRunning(in *Container return autoConvert_v1_ContainerStateRunning_To_api_ContainerStateRunning(in, out, s) } +func autoConvert_api_ContainerStateRunning_To_v1_ContainerStateRunning(in *api.ContainerStateRunning, out *ContainerStateRunning, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ContainerStateRunning))(in) + } + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.StartedAt, &out.StartedAt, s); err != nil { + return err + } + return nil +} + +func Convert_api_ContainerStateRunning_To_v1_ContainerStateRunning(in *api.ContainerStateRunning, out *ContainerStateRunning, s conversion.Scope) error { + return autoConvert_api_ContainerStateRunning_To_v1_ContainerStateRunning(in, out, s) +} + func autoConvert_v1_ContainerStateTerminated_To_api_ContainerStateTerminated(in *ContainerStateTerminated, out *api.ContainerStateTerminated, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ContainerStateTerminated))(in) @@ -3781,6 +1338,28 @@ func Convert_v1_ContainerStateTerminated_To_api_ContainerStateTerminated(in *Con return autoConvert_v1_ContainerStateTerminated_To_api_ContainerStateTerminated(in, out, s) } +func autoConvert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated(in *api.ContainerStateTerminated, out *ContainerStateTerminated, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ContainerStateTerminated))(in) + } + out.ExitCode = int32(in.ExitCode) + out.Signal = int32(in.Signal) + out.Reason = in.Reason + out.Message = in.Message + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.StartedAt, &out.StartedAt, s); err != nil { + return err + } + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.FinishedAt, &out.FinishedAt, s); err != nil { + return err + } + out.ContainerID = in.ContainerID + return nil +} + +func Convert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated(in *api.ContainerStateTerminated, out *ContainerStateTerminated, s conversion.Scope) error { + return autoConvert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated(in, out, s) +} + func autoConvert_v1_ContainerStateWaiting_To_api_ContainerStateWaiting(in *ContainerStateWaiting, out *api.ContainerStateWaiting, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ContainerStateWaiting))(in) @@ -3794,6 +1373,19 @@ func Convert_v1_ContainerStateWaiting_To_api_ContainerStateWaiting(in *Container return autoConvert_v1_ContainerStateWaiting_To_api_ContainerStateWaiting(in, out, s) } +func autoConvert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting(in *api.ContainerStateWaiting, out *ContainerStateWaiting, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ContainerStateWaiting))(in) + } + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +func Convert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting(in *api.ContainerStateWaiting, out *ContainerStateWaiting, s conversion.Scope) error { + return autoConvert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting(in, out, s) +} + func autoConvert_v1_ContainerStatus_To_api_ContainerStatus(in *ContainerStatus, out *api.ContainerStatus, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ContainerStatus))(in) @@ -3817,6 +1409,29 @@ func Convert_v1_ContainerStatus_To_api_ContainerStatus(in *ContainerStatus, out return autoConvert_v1_ContainerStatus_To_api_ContainerStatus(in, out, s) } +func autoConvert_api_ContainerStatus_To_v1_ContainerStatus(in *api.ContainerStatus, out *ContainerStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ContainerStatus))(in) + } + out.Name = in.Name + if err := Convert_api_ContainerState_To_v1_ContainerState(&in.State, &out.State, s); err != nil { + return err + } + if err := Convert_api_ContainerState_To_v1_ContainerState(&in.LastTerminationState, &out.LastTerminationState, s); err != nil { + return err + } + out.Ready = in.Ready + out.RestartCount = int32(in.RestartCount) + out.Image = in.Image + out.ImageID = in.ImageID + out.ContainerID = in.ContainerID + return nil +} + +func Convert_api_ContainerStatus_To_v1_ContainerStatus(in *api.ContainerStatus, out *ContainerStatus, s conversion.Scope) error { + return autoConvert_api_ContainerStatus_To_v1_ContainerStatus(in, out, s) +} + func autoConvert_v1_DaemonEndpoint_To_api_DaemonEndpoint(in *DaemonEndpoint, out *api.DaemonEndpoint, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*DaemonEndpoint))(in) @@ -3829,16 +1444,41 @@ func Convert_v1_DaemonEndpoint_To_api_DaemonEndpoint(in *DaemonEndpoint, out *ap return autoConvert_v1_DaemonEndpoint_To_api_DaemonEndpoint(in, out, s) } +func autoConvert_api_DaemonEndpoint_To_v1_DaemonEndpoint(in *api.DaemonEndpoint, out *DaemonEndpoint, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.DaemonEndpoint))(in) + } + out.Port = int32(in.Port) + return nil +} + +func Convert_api_DaemonEndpoint_To_v1_DaemonEndpoint(in *api.DaemonEndpoint, out *DaemonEndpoint, s conversion.Scope) error { + return autoConvert_api_DaemonEndpoint_To_v1_DaemonEndpoint(in, out, s) +} + func autoConvert_v1_DeleteOptions_To_api_DeleteOptions(in *DeleteOptions, out *api.DeleteOptions, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*DeleteOptions))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if in.GracePeriodSeconds != nil { - out.GracePeriodSeconds = new(int64) - *out.GracePeriodSeconds = *in.GracePeriodSeconds + in, out := &in.GracePeriodSeconds, &out.GracePeriodSeconds + *out = new(int64) + **out = **in } else { out.GracePeriodSeconds = nil } + if in.Preconditions != nil { + in, out := &in.Preconditions, &out.Preconditions + *out = new(api.Preconditions) + if err := Convert_v1_Preconditions_To_api_Preconditions(*in, *out, s); err != nil { + return err + } + } else { + out.Preconditions = nil + } return nil } @@ -3846,6 +1486,36 @@ func Convert_v1_DeleteOptions_To_api_DeleteOptions(in *DeleteOptions, out *api.D return autoConvert_v1_DeleteOptions_To_api_DeleteOptions(in, out, s) } +func autoConvert_api_DeleteOptions_To_v1_DeleteOptions(in *api.DeleteOptions, out *DeleteOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.DeleteOptions))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if in.GracePeriodSeconds != nil { + in, out := &in.GracePeriodSeconds, &out.GracePeriodSeconds + *out = new(int64) + **out = **in + } else { + out.GracePeriodSeconds = nil + } + if in.Preconditions != nil { + in, out := &in.Preconditions, &out.Preconditions + *out = new(Preconditions) + if err := Convert_api_Preconditions_To_v1_Preconditions(*in, *out, s); err != nil { + return err + } + } else { + out.Preconditions = nil + } + return nil +} + +func Convert_api_DeleteOptions_To_v1_DeleteOptions(in *api.DeleteOptions, out *DeleteOptions, s conversion.Scope) error { + return autoConvert_api_DeleteOptions_To_v1_DeleteOptions(in, out, s) +} + func autoConvert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile(in *DownwardAPIVolumeFile, out *api.DownwardAPIVolumeFile, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*DownwardAPIVolumeFile))(in) @@ -3861,14 +1531,30 @@ func Convert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile(in *DownwardA return autoConvert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile(in, out, s) } +func autoConvert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in *api.DownwardAPIVolumeFile, out *DownwardAPIVolumeFile, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.DownwardAPIVolumeFile))(in) + } + out.Path = in.Path + if err := Convert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(&in.FieldRef, &out.FieldRef, s); err != nil { + return err + } + return nil +} + +func Convert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in *api.DownwardAPIVolumeFile, out *DownwardAPIVolumeFile, s conversion.Scope) error { + return autoConvert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in, out, s) +} + func autoConvert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource(in *DownwardAPIVolumeSource, out *api.DownwardAPIVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*DownwardAPIVolumeSource))(in) } if in.Items != nil { - out.Items = make([]api.DownwardAPIVolumeFile, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.DownwardAPIVolumeFile, len(*in)) + for i := range *in { + if err := Convert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -3882,6 +1568,28 @@ func Convert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource(in *Downw return autoConvert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource(in, out, s) } +func autoConvert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in *api.DownwardAPIVolumeSource, out *DownwardAPIVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.DownwardAPIVolumeSource))(in) + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]DownwardAPIVolumeFile, len(*in)) + for i := range *in { + if err := Convert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in *api.DownwardAPIVolumeSource, out *DownwardAPIVolumeSource, s conversion.Scope) error { + return autoConvert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in, out, s) +} + func autoConvert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource(in *EmptyDirVolumeSource, out *api.EmptyDirVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*EmptyDirVolumeSource))(in) @@ -3894,15 +1602,27 @@ func Convert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource(in *EmptyDirVol return autoConvert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource(in, out, s) } +func autoConvert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in *api.EmptyDirVolumeSource, out *EmptyDirVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.EmptyDirVolumeSource))(in) + } + out.Medium = StorageMedium(in.Medium) + return nil +} + +func Convert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in *api.EmptyDirVolumeSource, out *EmptyDirVolumeSource, s conversion.Scope) error { + return autoConvert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in, out, s) +} + func autoConvert_v1_EndpointAddress_To_api_EndpointAddress(in *EndpointAddress, out *api.EndpointAddress, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*EndpointAddress))(in) } out.IP = in.IP - // unable to generate simple pointer conversion for v1.ObjectReference -> api.ObjectReference if in.TargetRef != nil { - out.TargetRef = new(api.ObjectReference) - if err := Convert_v1_ObjectReference_To_api_ObjectReference(in.TargetRef, out.TargetRef, s); err != nil { + in, out := &in.TargetRef, &out.TargetRef + *out = new(api.ObjectReference) + if err := Convert_v1_ObjectReference_To_api_ObjectReference(*in, *out, s); err != nil { return err } } else { @@ -3915,6 +1635,27 @@ func Convert_v1_EndpointAddress_To_api_EndpointAddress(in *EndpointAddress, out return autoConvert_v1_EndpointAddress_To_api_EndpointAddress(in, out, s) } +func autoConvert_api_EndpointAddress_To_v1_EndpointAddress(in *api.EndpointAddress, out *EndpointAddress, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.EndpointAddress))(in) + } + out.IP = in.IP + if in.TargetRef != nil { + in, out := &in.TargetRef, &out.TargetRef + *out = new(ObjectReference) + if err := Convert_api_ObjectReference_To_v1_ObjectReference(*in, *out, s); err != nil { + return err + } + } else { + out.TargetRef = nil + } + return nil +} + +func Convert_api_EndpointAddress_To_v1_EndpointAddress(in *api.EndpointAddress, out *EndpointAddress, s conversion.Scope) error { + return autoConvert_api_EndpointAddress_To_v1_EndpointAddress(in, out, s) +} + func autoConvert_v1_EndpointPort_To_api_EndpointPort(in *EndpointPort, out *api.EndpointPort, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*EndpointPort))(in) @@ -3929,14 +1670,29 @@ func Convert_v1_EndpointPort_To_api_EndpointPort(in *EndpointPort, out *api.Endp return autoConvert_v1_EndpointPort_To_api_EndpointPort(in, out, s) } +func autoConvert_api_EndpointPort_To_v1_EndpointPort(in *api.EndpointPort, out *EndpointPort, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.EndpointPort))(in) + } + out.Name = in.Name + out.Port = int32(in.Port) + out.Protocol = Protocol(in.Protocol) + return nil +} + +func Convert_api_EndpointPort_To_v1_EndpointPort(in *api.EndpointPort, out *EndpointPort, s conversion.Scope) error { + return autoConvert_api_EndpointPort_To_v1_EndpointPort(in, out, s) +} + func autoConvert_v1_EndpointSubset_To_api_EndpointSubset(in *EndpointSubset, out *api.EndpointSubset, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*EndpointSubset))(in) } if in.Addresses != nil { - out.Addresses = make([]api.EndpointAddress, len(in.Addresses)) - for i := range in.Addresses { - if err := Convert_v1_EndpointAddress_To_api_EndpointAddress(&in.Addresses[i], &out.Addresses[i], s); err != nil { + in, out := &in.Addresses, &out.Addresses + *out = make([]api.EndpointAddress, len(*in)) + for i := range *in { + if err := Convert_v1_EndpointAddress_To_api_EndpointAddress(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -3944,9 +1700,10 @@ func autoConvert_v1_EndpointSubset_To_api_EndpointSubset(in *EndpointSubset, out out.Addresses = nil } if in.NotReadyAddresses != nil { - out.NotReadyAddresses = make([]api.EndpointAddress, len(in.NotReadyAddresses)) - for i := range in.NotReadyAddresses { - if err := Convert_v1_EndpointAddress_To_api_EndpointAddress(&in.NotReadyAddresses[i], &out.NotReadyAddresses[i], s); err != nil { + in, out := &in.NotReadyAddresses, &out.NotReadyAddresses + *out = make([]api.EndpointAddress, len(*in)) + for i := range *in { + if err := Convert_v1_EndpointAddress_To_api_EndpointAddress(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -3954,9 +1711,10 @@ func autoConvert_v1_EndpointSubset_To_api_EndpointSubset(in *EndpointSubset, out out.NotReadyAddresses = nil } if in.Ports != nil { - out.Ports = make([]api.EndpointPort, len(in.Ports)) - for i := range in.Ports { - if err := Convert_v1_EndpointPort_To_api_EndpointPort(&in.Ports[i], &out.Ports[i], s); err != nil { + in, out := &in.Ports, &out.Ports + *out = make([]api.EndpointPort, len(*in)) + for i := range *in { + if err := Convert_v1_EndpointPort_To_api_EndpointPort(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -3970,17 +1728,65 @@ func Convert_v1_EndpointSubset_To_api_EndpointSubset(in *EndpointSubset, out *ap return autoConvert_v1_EndpointSubset_To_api_EndpointSubset(in, out, s) } +func autoConvert_api_EndpointSubset_To_v1_EndpointSubset(in *api.EndpointSubset, out *EndpointSubset, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.EndpointSubset))(in) + } + if in.Addresses != nil { + in, out := &in.Addresses, &out.Addresses + *out = make([]EndpointAddress, len(*in)) + for i := range *in { + if err := Convert_api_EndpointAddress_To_v1_EndpointAddress(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Addresses = nil + } + if in.NotReadyAddresses != nil { + in, out := &in.NotReadyAddresses, &out.NotReadyAddresses + *out = make([]EndpointAddress, len(*in)) + for i := range *in { + if err := Convert_api_EndpointAddress_To_v1_EndpointAddress(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.NotReadyAddresses = nil + } + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]EndpointPort, len(*in)) + for i := range *in { + if err := Convert_api_EndpointPort_To_v1_EndpointPort(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Ports = nil + } + return nil +} + +func Convert_api_EndpointSubset_To_v1_EndpointSubset(in *api.EndpointSubset, out *EndpointSubset, s conversion.Scope) error { + return autoConvert_api_EndpointSubset_To_v1_EndpointSubset(in, out, s) +} + func autoConvert_v1_Endpoints_To_api_Endpoints(in *Endpoints, out *api.Endpoints, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Endpoints))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } if in.Subsets != nil { - out.Subsets = make([]api.EndpointSubset, len(in.Subsets)) - for i := range in.Subsets { - if err := Convert_v1_EndpointSubset_To_api_EndpointSubset(&in.Subsets[i], &out.Subsets[i], s); err != nil { + in, out := &in.Subsets, &out.Subsets + *out = make([]api.EndpointSubset, len(*in)) + for i := range *in { + if err := Convert_v1_EndpointSubset_To_api_EndpointSubset(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -3994,17 +1800,49 @@ func Convert_v1_Endpoints_To_api_Endpoints(in *Endpoints, out *api.Endpoints, s return autoConvert_v1_Endpoints_To_api_Endpoints(in, out, s) } +func autoConvert_api_Endpoints_To_v1_Endpoints(in *api.Endpoints, out *Endpoints, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Endpoints))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if in.Subsets != nil { + in, out := &in.Subsets, &out.Subsets + *out = make([]EndpointSubset, len(*in)) + for i := range *in { + if err := Convert_api_EndpointSubset_To_v1_EndpointSubset(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Subsets = nil + } + return nil +} + +func Convert_api_Endpoints_To_v1_Endpoints(in *api.Endpoints, out *Endpoints, s conversion.Scope) error { + return autoConvert_api_Endpoints_To_v1_Endpoints(in, out, s) +} + func autoConvert_v1_EndpointsList_To_api_EndpointsList(in *EndpointsList, out *api.EndpointsList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*EndpointsList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.Endpoints, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_Endpoints_To_api_Endpoints(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.Endpoints, len(*in)) + for i := range *in { + if err := Convert_v1_Endpoints_To_api_Endpoints(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -4018,16 +1856,44 @@ func Convert_v1_EndpointsList_To_api_EndpointsList(in *EndpointsList, out *api.E return autoConvert_v1_EndpointsList_To_api_EndpointsList(in, out, s) } +func autoConvert_api_EndpointsList_To_v1_EndpointsList(in *api.EndpointsList, out *EndpointsList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.EndpointsList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Endpoints, len(*in)) + for i := range *in { + if err := Convert_api_Endpoints_To_v1_Endpoints(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_EndpointsList_To_v1_EndpointsList(in *api.EndpointsList, out *EndpointsList, s conversion.Scope) error { + return autoConvert_api_EndpointsList_To_v1_EndpointsList(in, out, s) +} + func autoConvert_v1_EnvVar_To_api_EnvVar(in *EnvVar, out *api.EnvVar, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*EnvVar))(in) } out.Name = in.Name out.Value = in.Value - // unable to generate simple pointer conversion for v1.EnvVarSource -> api.EnvVarSource if in.ValueFrom != nil { - out.ValueFrom = new(api.EnvVarSource) - if err := Convert_v1_EnvVarSource_To_api_EnvVarSource(in.ValueFrom, out.ValueFrom, s); err != nil { + in, out := &in.ValueFrom, &out.ValueFrom + *out = new(api.EnvVarSource) + if err := Convert_v1_EnvVarSource_To_api_EnvVarSource(*in, *out, s); err != nil { return err } } else { @@ -4040,32 +1906,54 @@ func Convert_v1_EnvVar_To_api_EnvVar(in *EnvVar, out *api.EnvVar, s conversion.S return autoConvert_v1_EnvVar_To_api_EnvVar(in, out, s) } +func autoConvert_api_EnvVar_To_v1_EnvVar(in *api.EnvVar, out *EnvVar, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.EnvVar))(in) + } + out.Name = in.Name + out.Value = in.Value + if in.ValueFrom != nil { + in, out := &in.ValueFrom, &out.ValueFrom + *out = new(EnvVarSource) + if err := Convert_api_EnvVarSource_To_v1_EnvVarSource(*in, *out, s); err != nil { + return err + } + } else { + out.ValueFrom = nil + } + return nil +} + +func Convert_api_EnvVar_To_v1_EnvVar(in *api.EnvVar, out *EnvVar, s conversion.Scope) error { + return autoConvert_api_EnvVar_To_v1_EnvVar(in, out, s) +} + func autoConvert_v1_EnvVarSource_To_api_EnvVarSource(in *EnvVarSource, out *api.EnvVarSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*EnvVarSource))(in) } - // unable to generate simple pointer conversion for v1.ObjectFieldSelector -> api.ObjectFieldSelector if in.FieldRef != nil { - out.FieldRef = new(api.ObjectFieldSelector) - if err := Convert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector(in.FieldRef, out.FieldRef, s); err != nil { + in, out := &in.FieldRef, &out.FieldRef + *out = new(api.ObjectFieldSelector) + if err := Convert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector(*in, *out, s); err != nil { return err } } else { out.FieldRef = nil } - // unable to generate simple pointer conversion for v1.ConfigMapKeySelector -> api.ConfigMapKeySelector if in.ConfigMapKeyRef != nil { - out.ConfigMapKeyRef = new(api.ConfigMapKeySelector) - if err := Convert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector(in.ConfigMapKeyRef, out.ConfigMapKeyRef, s); err != nil { + in, out := &in.ConfigMapKeyRef, &out.ConfigMapKeyRef + *out = new(api.ConfigMapKeySelector) + if err := Convert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector(*in, *out, s); err != nil { return err } } else { out.ConfigMapKeyRef = nil } - // unable to generate simple pointer conversion for v1.SecretKeySelector -> api.SecretKeySelector if in.SecretKeyRef != nil { - out.SecretKeyRef = new(api.SecretKeySelector) - if err := Convert_v1_SecretKeySelector_To_api_SecretKeySelector(in.SecretKeyRef, out.SecretKeyRef, s); err != nil { + in, out := &in.SecretKeyRef, &out.SecretKeyRef + *out = new(api.SecretKeySelector) + if err := Convert_v1_SecretKeySelector_To_api_SecretKeySelector(*in, *out, s); err != nil { return err } } else { @@ -4078,10 +1966,51 @@ func Convert_v1_EnvVarSource_To_api_EnvVarSource(in *EnvVarSource, out *api.EnvV return autoConvert_v1_EnvVarSource_To_api_EnvVarSource(in, out, s) } +func autoConvert_api_EnvVarSource_To_v1_EnvVarSource(in *api.EnvVarSource, out *EnvVarSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.EnvVarSource))(in) + } + if in.FieldRef != nil { + in, out := &in.FieldRef, &out.FieldRef + *out = new(ObjectFieldSelector) + if err := Convert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(*in, *out, s); err != nil { + return err + } + } else { + out.FieldRef = nil + } + if in.ConfigMapKeyRef != nil { + in, out := &in.ConfigMapKeyRef, &out.ConfigMapKeyRef + *out = new(ConfigMapKeySelector) + if err := Convert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(*in, *out, s); err != nil { + return err + } + } else { + out.ConfigMapKeyRef = nil + } + if in.SecretKeyRef != nil { + in, out := &in.SecretKeyRef, &out.SecretKeyRef + *out = new(SecretKeySelector) + if err := Convert_api_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { + return err + } + } else { + out.SecretKeyRef = nil + } + return nil +} + +func Convert_api_EnvVarSource_To_v1_EnvVarSource(in *api.EnvVarSource, out *EnvVarSource, s conversion.Scope) error { + return autoConvert_api_EnvVarSource_To_v1_EnvVarSource(in, out, s) +} + func autoConvert_v1_Event_To_api_Event(in *Event, out *api.Event, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Event))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } @@ -4108,17 +2037,54 @@ func Convert_v1_Event_To_api_Event(in *Event, out *api.Event, s conversion.Scope return autoConvert_v1_Event_To_api_Event(in, out, s) } +func autoConvert_api_Event_To_v1_Event(in *api.Event, out *Event, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Event))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectReference_To_v1_ObjectReference(&in.InvolvedObject, &out.InvolvedObject, s); err != nil { + return err + } + out.Reason = in.Reason + out.Message = in.Message + if err := Convert_api_EventSource_To_v1_EventSource(&in.Source, &out.Source, s); err != nil { + return err + } + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.FirstTimestamp, &out.FirstTimestamp, s); err != nil { + return err + } + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastTimestamp, &out.LastTimestamp, s); err != nil { + return err + } + out.Count = int32(in.Count) + out.Type = in.Type + return nil +} + +func Convert_api_Event_To_v1_Event(in *api.Event, out *Event, s conversion.Scope) error { + return autoConvert_api_Event_To_v1_Event(in, out, s) +} + func autoConvert_v1_EventList_To_api_EventList(in *EventList, out *api.EventList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*EventList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.Event, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_Event_To_api_Event(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.Event, len(*in)) + for i := range *in { + if err := Convert_v1_Event_To_api_Event(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -4132,6 +2098,34 @@ func Convert_v1_EventList_To_api_EventList(in *EventList, out *api.EventList, s return autoConvert_v1_EventList_To_api_EventList(in, out, s) } +func autoConvert_api_EventList_To_v1_EventList(in *api.EventList, out *EventList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.EventList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Event, len(*in)) + for i := range *in { + if err := Convert_api_Event_To_v1_Event(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_EventList_To_v1_EventList(in *api.EventList, out *EventList, s conversion.Scope) error { + return autoConvert_api_EventList_To_v1_EventList(in, out, s) +} + func autoConvert_v1_EventSource_To_api_EventSource(in *EventSource, out *api.EventSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*EventSource))(in) @@ -4145,15 +2139,27 @@ func Convert_v1_EventSource_To_api_EventSource(in *EventSource, out *api.EventSo return autoConvert_v1_EventSource_To_api_EventSource(in, out, s) } +func autoConvert_api_EventSource_To_v1_EventSource(in *api.EventSource, out *EventSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.EventSource))(in) + } + out.Component = in.Component + out.Host = in.Host + return nil +} + +func Convert_api_EventSource_To_v1_EventSource(in *api.EventSource, out *EventSource, s conversion.Scope) error { + return autoConvert_api_EventSource_To_v1_EventSource(in, out, s) +} + func autoConvert_v1_ExecAction_To_api_ExecAction(in *ExecAction, out *api.ExecAction, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ExecAction))(in) } if in.Command != nil { - out.Command = make([]string, len(in.Command)) - for i := range in.Command { - out.Command[i] = in.Command[i] - } + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) } else { out.Command = nil } @@ -4164,17 +2170,54 @@ func Convert_v1_ExecAction_To_api_ExecAction(in *ExecAction, out *api.ExecAction return autoConvert_v1_ExecAction_To_api_ExecAction(in, out, s) } -func autoConvert_v1_ExportOptions_To_unversioned_ExportOptions(in *ExportOptions, out *unversioned.ExportOptions, s conversion.Scope) error { +func autoConvert_api_ExecAction_To_v1_ExecAction(in *api.ExecAction, out *ExecAction, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ExecAction))(in) + } + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } else { + out.Command = nil + } + return nil +} + +func Convert_api_ExecAction_To_v1_ExecAction(in *api.ExecAction, out *ExecAction, s conversion.Scope) error { + return autoConvert_api_ExecAction_To_v1_ExecAction(in, out, s) +} + +func autoConvert_v1_ExportOptions_To_api_ExportOptions(in *ExportOptions, out *api.ExportOptions, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ExportOptions))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } out.Export = in.Export out.Exact = in.Exact return nil } -func Convert_v1_ExportOptions_To_unversioned_ExportOptions(in *ExportOptions, out *unversioned.ExportOptions, s conversion.Scope) error { - return autoConvert_v1_ExportOptions_To_unversioned_ExportOptions(in, out, s) +func Convert_v1_ExportOptions_To_api_ExportOptions(in *ExportOptions, out *api.ExportOptions, s conversion.Scope) error { + return autoConvert_v1_ExportOptions_To_api_ExportOptions(in, out, s) +} + +func autoConvert_api_ExportOptions_To_v1_ExportOptions(in *api.ExportOptions, out *ExportOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ExportOptions))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + out.Export = in.Export + out.Exact = in.Exact + return nil +} + +func Convert_api_ExportOptions_To_v1_ExportOptions(in *api.ExportOptions, out *ExportOptions, s conversion.Scope) error { + return autoConvert_api_ExportOptions_To_v1_ExportOptions(in, out, s) } func autoConvert_v1_FCVolumeSource_To_api_FCVolumeSource(in *FCVolumeSource, out *api.FCVolumeSource, s conversion.Scope) error { @@ -4182,16 +2225,16 @@ func autoConvert_v1_FCVolumeSource_To_api_FCVolumeSource(in *FCVolumeSource, out defaulting.(func(*FCVolumeSource))(in) } if in.TargetWWNs != nil { - out.TargetWWNs = make([]string, len(in.TargetWWNs)) - for i := range in.TargetWWNs { - out.TargetWWNs[i] = in.TargetWWNs[i] - } + in, out := &in.TargetWWNs, &out.TargetWWNs + *out = make([]string, len(*in)) + copy(*out, *in) } else { out.TargetWWNs = nil } if in.Lun != nil { - out.Lun = new(int) - *out.Lun = int(*in.Lun) + in, out := &in.Lun, &out.Lun + *out = new(int) + **out = int(**in) } else { out.Lun = nil } @@ -4204,16 +2247,43 @@ func Convert_v1_FCVolumeSource_To_api_FCVolumeSource(in *FCVolumeSource, out *ap return autoConvert_v1_FCVolumeSource_To_api_FCVolumeSource(in, out, s) } +func autoConvert_api_FCVolumeSource_To_v1_FCVolumeSource(in *api.FCVolumeSource, out *FCVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.FCVolumeSource))(in) + } + if in.TargetWWNs != nil { + in, out := &in.TargetWWNs, &out.TargetWWNs + *out = make([]string, len(*in)) + copy(*out, *in) + } else { + out.TargetWWNs = nil + } + if in.Lun != nil { + in, out := &in.Lun, &out.Lun + *out = new(int32) + **out = int32(**in) + } else { + out.Lun = nil + } + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_FCVolumeSource_To_v1_FCVolumeSource(in *api.FCVolumeSource, out *FCVolumeSource, s conversion.Scope) error { + return autoConvert_api_FCVolumeSource_To_v1_FCVolumeSource(in, out, s) +} + func autoConvert_v1_FlexVolumeSource_To_api_FlexVolumeSource(in *FlexVolumeSource, out *api.FlexVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*FlexVolumeSource))(in) } out.Driver = in.Driver out.FSType = in.FSType - // unable to generate simple pointer conversion for v1.LocalObjectReference -> api.LocalObjectReference if in.SecretRef != nil { - out.SecretRef = new(api.LocalObjectReference) - if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(in.SecretRef, out.SecretRef, s); err != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(api.LocalObjectReference) + if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(*in, *out, s); err != nil { return err } } else { @@ -4221,9 +2291,10 @@ func autoConvert_v1_FlexVolumeSource_To_api_FlexVolumeSource(in *FlexVolumeSourc } out.ReadOnly = in.ReadOnly if in.Options != nil { - out.Options = make(map[string]string) - for key, val := range in.Options { - out.Options[key] = val + in, out := &in.Options, &out.Options + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } else { out.Options = nil @@ -4235,6 +2306,38 @@ func Convert_v1_FlexVolumeSource_To_api_FlexVolumeSource(in *FlexVolumeSource, o return autoConvert_v1_FlexVolumeSource_To_api_FlexVolumeSource(in, out, s) } +func autoConvert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in *api.FlexVolumeSource, out *FlexVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.FlexVolumeSource))(in) + } + out.Driver = in.Driver + out.FSType = in.FSType + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(LocalObjectReference) + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(*in, *out, s); err != nil { + return err + } + } else { + out.SecretRef = nil + } + out.ReadOnly = in.ReadOnly + if in.Options != nil { + in, out := &in.Options, &out.Options + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } else { + out.Options = nil + } + return nil +} + +func Convert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in *api.FlexVolumeSource, out *FlexVolumeSource, s conversion.Scope) error { + return autoConvert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in, out, s) +} + func autoConvert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(in *FlockerVolumeSource, out *api.FlockerVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*FlockerVolumeSource))(in) @@ -4247,6 +2350,18 @@ func Convert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(in *FlockerVolume return autoConvert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(in, out, s) } +func autoConvert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in *api.FlockerVolumeSource, out *FlockerVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.FlockerVolumeSource))(in) + } + out.DatasetName = in.DatasetName + return nil +} + +func Convert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in *api.FlockerVolumeSource, out *FlockerVolumeSource, s conversion.Scope) error { + return autoConvert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in, out, s) +} + func autoConvert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource(in *GCEPersistentDiskVolumeSource, out *api.GCEPersistentDiskVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*GCEPersistentDiskVolumeSource))(in) @@ -4262,6 +2377,21 @@ func Convert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSour return autoConvert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource(in, out, s) } +func autoConvert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in *api.GCEPersistentDiskVolumeSource, out *GCEPersistentDiskVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.GCEPersistentDiskVolumeSource))(in) + } + out.PDName = in.PDName + out.FSType = in.FSType + out.Partition = int32(in.Partition) + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in *api.GCEPersistentDiskVolumeSource, out *GCEPersistentDiskVolumeSource, s conversion.Scope) error { + return autoConvert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in, out, s) +} + func autoConvert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource(in *GitRepoVolumeSource, out *api.GitRepoVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*GitRepoVolumeSource))(in) @@ -4276,6 +2406,20 @@ func Convert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource(in *GitRepoVolume return autoConvert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource(in, out, s) } +func autoConvert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in *api.GitRepoVolumeSource, out *GitRepoVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.GitRepoVolumeSource))(in) + } + out.Repository = in.Repository + out.Revision = in.Revision + out.Directory = in.Directory + return nil +} + +func Convert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in *api.GitRepoVolumeSource, out *GitRepoVolumeSource, s conversion.Scope) error { + return autoConvert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in, out, s) +} + func autoConvert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource(in *GlusterfsVolumeSource, out *api.GlusterfsVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*GlusterfsVolumeSource))(in) @@ -4290,6 +2434,20 @@ func Convert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource(in *Glusterfs return autoConvert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource(in, out, s) } +func autoConvert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in *api.GlusterfsVolumeSource, out *GlusterfsVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.GlusterfsVolumeSource))(in) + } + out.EndpointsName = in.EndpointsName + out.Path = in.Path + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in *api.GlusterfsVolumeSource, out *GlusterfsVolumeSource, s conversion.Scope) error { + return autoConvert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in, out, s) +} + func autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction(in *HTTPGetAction, out *api.HTTPGetAction, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*HTTPGetAction))(in) @@ -4301,9 +2459,10 @@ func autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction(in *HTTPGetAction, out *a out.Host = in.Host out.Scheme = api.URIScheme(in.Scheme) if in.HTTPHeaders != nil { - out.HTTPHeaders = make([]api.HTTPHeader, len(in.HTTPHeaders)) - for i := range in.HTTPHeaders { - if err := Convert_v1_HTTPHeader_To_api_HTTPHeader(&in.HTTPHeaders[i], &out.HTTPHeaders[i], s); err != nil { + in, out := &in.HTTPHeaders, &out.HTTPHeaders + *out = make([]api.HTTPHeader, len(*in)) + for i := range *in { + if err := Convert_v1_HTTPHeader_To_api_HTTPHeader(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -4317,6 +2476,34 @@ func Convert_v1_HTTPGetAction_To_api_HTTPGetAction(in *HTTPGetAction, out *api.H return autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction(in, out, s) } +func autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction(in *api.HTTPGetAction, out *HTTPGetAction, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.HTTPGetAction))(in) + } + out.Path = in.Path + if err := api.Convert_intstr_IntOrString_To_intstr_IntOrString(&in.Port, &out.Port, s); err != nil { + return err + } + out.Host = in.Host + out.Scheme = URIScheme(in.Scheme) + if in.HTTPHeaders != nil { + in, out := &in.HTTPHeaders, &out.HTTPHeaders + *out = make([]HTTPHeader, len(*in)) + for i := range *in { + if err := Convert_api_HTTPHeader_To_v1_HTTPHeader(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.HTTPHeaders = nil + } + return nil +} + +func Convert_api_HTTPGetAction_To_v1_HTTPGetAction(in *api.HTTPGetAction, out *HTTPGetAction, s conversion.Scope) error { + return autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction(in, out, s) +} + func autoConvert_v1_HTTPHeader_To_api_HTTPHeader(in *HTTPHeader, out *api.HTTPHeader, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*HTTPHeader))(in) @@ -4330,32 +2517,45 @@ func Convert_v1_HTTPHeader_To_api_HTTPHeader(in *HTTPHeader, out *api.HTTPHeader return autoConvert_v1_HTTPHeader_To_api_HTTPHeader(in, out, s) } +func autoConvert_api_HTTPHeader_To_v1_HTTPHeader(in *api.HTTPHeader, out *HTTPHeader, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.HTTPHeader))(in) + } + out.Name = in.Name + out.Value = in.Value + return nil +} + +func Convert_api_HTTPHeader_To_v1_HTTPHeader(in *api.HTTPHeader, out *HTTPHeader, s conversion.Scope) error { + return autoConvert_api_HTTPHeader_To_v1_HTTPHeader(in, out, s) +} + func autoConvert_v1_Handler_To_api_Handler(in *Handler, out *api.Handler, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Handler))(in) } - // unable to generate simple pointer conversion for v1.ExecAction -> api.ExecAction if in.Exec != nil { - out.Exec = new(api.ExecAction) - if err := Convert_v1_ExecAction_To_api_ExecAction(in.Exec, out.Exec, s); err != nil { + in, out := &in.Exec, &out.Exec + *out = new(api.ExecAction) + if err := Convert_v1_ExecAction_To_api_ExecAction(*in, *out, s); err != nil { return err } } else { out.Exec = nil } - // unable to generate simple pointer conversion for v1.HTTPGetAction -> api.HTTPGetAction if in.HTTPGet != nil { - out.HTTPGet = new(api.HTTPGetAction) - if err := Convert_v1_HTTPGetAction_To_api_HTTPGetAction(in.HTTPGet, out.HTTPGet, s); err != nil { + in, out := &in.HTTPGet, &out.HTTPGet + *out = new(api.HTTPGetAction) + if err := Convert_v1_HTTPGetAction_To_api_HTTPGetAction(*in, *out, s); err != nil { return err } } else { out.HTTPGet = nil } - // unable to generate simple pointer conversion for v1.TCPSocketAction -> api.TCPSocketAction if in.TCPSocket != nil { - out.TCPSocket = new(api.TCPSocketAction) - if err := Convert_v1_TCPSocketAction_To_api_TCPSocketAction(in.TCPSocket, out.TCPSocket, s); err != nil { + in, out := &in.TCPSocket, &out.TCPSocket + *out = new(api.TCPSocketAction) + if err := Convert_v1_TCPSocketAction_To_api_TCPSocketAction(*in, *out, s); err != nil { return err } } else { @@ -4368,6 +2568,44 @@ func Convert_v1_Handler_To_api_Handler(in *Handler, out *api.Handler, s conversi return autoConvert_v1_Handler_To_api_Handler(in, out, s) } +func autoConvert_api_Handler_To_v1_Handler(in *api.Handler, out *Handler, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Handler))(in) + } + if in.Exec != nil { + in, out := &in.Exec, &out.Exec + *out = new(ExecAction) + if err := Convert_api_ExecAction_To_v1_ExecAction(*in, *out, s); err != nil { + return err + } + } else { + out.Exec = nil + } + if in.HTTPGet != nil { + in, out := &in.HTTPGet, &out.HTTPGet + *out = new(HTTPGetAction) + if err := Convert_api_HTTPGetAction_To_v1_HTTPGetAction(*in, *out, s); err != nil { + return err + } + } else { + out.HTTPGet = nil + } + if in.TCPSocket != nil { + in, out := &in.TCPSocket, &out.TCPSocket + *out = new(TCPSocketAction) + if err := Convert_api_TCPSocketAction_To_v1_TCPSocketAction(*in, *out, s); err != nil { + return err + } + } else { + out.TCPSocket = nil + } + return nil +} + +func Convert_api_Handler_To_v1_Handler(in *api.Handler, out *Handler, s conversion.Scope) error { + return autoConvert_api_Handler_To_v1_Handler(in, out, s) +} + func autoConvert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource(in *HostPathVolumeSource, out *api.HostPathVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*HostPathVolumeSource))(in) @@ -4380,6 +2618,18 @@ func Convert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource(in *HostPathVol return autoConvert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource(in, out, s) } +func autoConvert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in *api.HostPathVolumeSource, out *HostPathVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.HostPathVolumeSource))(in) + } + out.Path = in.Path + return nil +} + +func Convert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in *api.HostPathVolumeSource, out *HostPathVolumeSource, s conversion.Scope) error { + return autoConvert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in, out, s) +} + func autoConvert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource(in *ISCSIVolumeSource, out *api.ISCSIVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ISCSIVolumeSource))(in) @@ -4397,6 +2647,23 @@ func Convert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource(in *ISCSIVolumeSource return autoConvert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource(in, out, s) } +func autoConvert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in *api.ISCSIVolumeSource, out *ISCSIVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ISCSIVolumeSource))(in) + } + out.TargetPortal = in.TargetPortal + out.IQN = in.IQN + out.Lun = int32(in.Lun) + out.ISCSIInterface = in.ISCSIInterface + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in *api.ISCSIVolumeSource, out *ISCSIVolumeSource, s conversion.Scope) error { + return autoConvert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in, out, s) +} + func autoConvert_v1_KeyToPath_To_api_KeyToPath(in *KeyToPath, out *api.KeyToPath, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*KeyToPath))(in) @@ -4410,23 +2677,36 @@ func Convert_v1_KeyToPath_To_api_KeyToPath(in *KeyToPath, out *api.KeyToPath, s return autoConvert_v1_KeyToPath_To_api_KeyToPath(in, out, s) } +func autoConvert_api_KeyToPath_To_v1_KeyToPath(in *api.KeyToPath, out *KeyToPath, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.KeyToPath))(in) + } + out.Key = in.Key + out.Path = in.Path + return nil +} + +func Convert_api_KeyToPath_To_v1_KeyToPath(in *api.KeyToPath, out *KeyToPath, s conversion.Scope) error { + return autoConvert_api_KeyToPath_To_v1_KeyToPath(in, out, s) +} + func autoConvert_v1_Lifecycle_To_api_Lifecycle(in *Lifecycle, out *api.Lifecycle, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Lifecycle))(in) } - // unable to generate simple pointer conversion for v1.Handler -> api.Handler if in.PostStart != nil { - out.PostStart = new(api.Handler) - if err := Convert_v1_Handler_To_api_Handler(in.PostStart, out.PostStart, s); err != nil { + in, out := &in.PostStart, &out.PostStart + *out = new(api.Handler) + if err := Convert_v1_Handler_To_api_Handler(*in, *out, s); err != nil { return err } } else { out.PostStart = nil } - // unable to generate simple pointer conversion for v1.Handler -> api.Handler if in.PreStop != nil { - out.PreStop = new(api.Handler) - if err := Convert_v1_Handler_To_api_Handler(in.PreStop, out.PreStop, s); err != nil { + in, out := &in.PreStop, &out.PreStop + *out = new(api.Handler) + if err := Convert_v1_Handler_To_api_Handler(*in, *out, s); err != nil { return err } } else { @@ -4439,10 +2719,42 @@ func Convert_v1_Lifecycle_To_api_Lifecycle(in *Lifecycle, out *api.Lifecycle, s return autoConvert_v1_Lifecycle_To_api_Lifecycle(in, out, s) } +func autoConvert_api_Lifecycle_To_v1_Lifecycle(in *api.Lifecycle, out *Lifecycle, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Lifecycle))(in) + } + if in.PostStart != nil { + in, out := &in.PostStart, &out.PostStart + *out = new(Handler) + if err := Convert_api_Handler_To_v1_Handler(*in, *out, s); err != nil { + return err + } + } else { + out.PostStart = nil + } + if in.PreStop != nil { + in, out := &in.PreStop, &out.PreStop + *out = new(Handler) + if err := Convert_api_Handler_To_v1_Handler(*in, *out, s); err != nil { + return err + } + } else { + out.PreStop = nil + } + return nil +} + +func Convert_api_Lifecycle_To_v1_Lifecycle(in *api.Lifecycle, out *Lifecycle, s conversion.Scope) error { + return autoConvert_api_Lifecycle_To_v1_Lifecycle(in, out, s) +} + func autoConvert_v1_LimitRange_To_api_LimitRange(in *LimitRange, out *api.LimitRange, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*LimitRange))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } @@ -4456,24 +2768,44 @@ func Convert_v1_LimitRange_To_api_LimitRange(in *LimitRange, out *api.LimitRange return autoConvert_v1_LimitRange_To_api_LimitRange(in, out, s) } +func autoConvert_api_LimitRange_To_v1_LimitRange(in *api.LimitRange, out *LimitRange, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.LimitRange))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_LimitRangeSpec_To_v1_LimitRangeSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + return nil +} + +func Convert_api_LimitRange_To_v1_LimitRange(in *api.LimitRange, out *LimitRange, s conversion.Scope) error { + return autoConvert_api_LimitRange_To_v1_LimitRange(in, out, s) +} + func autoConvert_v1_LimitRangeItem_To_api_LimitRangeItem(in *LimitRangeItem, out *api.LimitRangeItem, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*LimitRangeItem))(in) } out.Type = api.LimitType(in.Type) - if err := s.Convert(&in.Max, &out.Max, 0); err != nil { + if err := Convert_v1_ResourceList_To_api_ResourceList(&in.Max, &out.Max, s); err != nil { return err } - if err := s.Convert(&in.Min, &out.Min, 0); err != nil { + if err := Convert_v1_ResourceList_To_api_ResourceList(&in.Min, &out.Min, s); err != nil { return err } - if err := s.Convert(&in.Default, &out.Default, 0); err != nil { + if err := Convert_v1_ResourceList_To_api_ResourceList(&in.Default, &out.Default, s); err != nil { return err } - if err := s.Convert(&in.DefaultRequest, &out.DefaultRequest, 0); err != nil { + if err := Convert_v1_ResourceList_To_api_ResourceList(&in.DefaultRequest, &out.DefaultRequest, s); err != nil { return err } - if err := s.Convert(&in.MaxLimitRequestRatio, &out.MaxLimitRequestRatio, 0); err != nil { + if err := Convert_v1_ResourceList_To_api_ResourceList(&in.MaxLimitRequestRatio, &out.MaxLimitRequestRatio, s); err != nil { return err } return nil @@ -4483,17 +2815,98 @@ func Convert_v1_LimitRangeItem_To_api_LimitRangeItem(in *LimitRangeItem, out *ap return autoConvert_v1_LimitRangeItem_To_api_LimitRangeItem(in, out, s) } +func autoConvert_api_LimitRangeItem_To_v1_LimitRangeItem(in *api.LimitRangeItem, out *LimitRangeItem, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.LimitRangeItem))(in) + } + out.Type = LimitType(in.Type) + if in.Max != nil { + in, out := &in.Max, &out.Max + *out = make(ResourceList, len(*in)) + for key, val := range *in { + newVal := new(resource.Quantity) + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, newVal, s); err != nil { + return err + } + (*out)[ResourceName(key)] = *newVal + } + } else { + out.Max = nil + } + if in.Min != nil { + in, out := &in.Min, &out.Min + *out = make(ResourceList, len(*in)) + for key, val := range *in { + newVal := new(resource.Quantity) + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, newVal, s); err != nil { + return err + } + (*out)[ResourceName(key)] = *newVal + } + } else { + out.Min = nil + } + if in.Default != nil { + in, out := &in.Default, &out.Default + *out = make(ResourceList, len(*in)) + for key, val := range *in { + newVal := new(resource.Quantity) + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, newVal, s); err != nil { + return err + } + (*out)[ResourceName(key)] = *newVal + } + } else { + out.Default = nil + } + if in.DefaultRequest != nil { + in, out := &in.DefaultRequest, &out.DefaultRequest + *out = make(ResourceList, len(*in)) + for key, val := range *in { + newVal := new(resource.Quantity) + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, newVal, s); err != nil { + return err + } + (*out)[ResourceName(key)] = *newVal + } + } else { + out.DefaultRequest = nil + } + if in.MaxLimitRequestRatio != nil { + in, out := &in.MaxLimitRequestRatio, &out.MaxLimitRequestRatio + *out = make(ResourceList, len(*in)) + for key, val := range *in { + newVal := new(resource.Quantity) + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, newVal, s); err != nil { + return err + } + (*out)[ResourceName(key)] = *newVal + } + } else { + out.MaxLimitRequestRatio = nil + } + return nil +} + +func Convert_api_LimitRangeItem_To_v1_LimitRangeItem(in *api.LimitRangeItem, out *LimitRangeItem, s conversion.Scope) error { + return autoConvert_api_LimitRangeItem_To_v1_LimitRangeItem(in, out, s) +} + func autoConvert_v1_LimitRangeList_To_api_LimitRangeList(in *LimitRangeList, out *api.LimitRangeList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*LimitRangeList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.LimitRange, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_LimitRange_To_api_LimitRange(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.LimitRange, len(*in)) + for i := range *in { + if err := Convert_v1_LimitRange_To_api_LimitRange(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -4507,14 +2920,43 @@ func Convert_v1_LimitRangeList_To_api_LimitRangeList(in *LimitRangeList, out *ap return autoConvert_v1_LimitRangeList_To_api_LimitRangeList(in, out, s) } +func autoConvert_api_LimitRangeList_To_v1_LimitRangeList(in *api.LimitRangeList, out *LimitRangeList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.LimitRangeList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LimitRange, len(*in)) + for i := range *in { + if err := Convert_api_LimitRange_To_v1_LimitRange(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_LimitRangeList_To_v1_LimitRangeList(in *api.LimitRangeList, out *LimitRangeList, s conversion.Scope) error { + return autoConvert_api_LimitRangeList_To_v1_LimitRangeList(in, out, s) +} + func autoConvert_v1_LimitRangeSpec_To_api_LimitRangeSpec(in *LimitRangeSpec, out *api.LimitRangeSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*LimitRangeSpec))(in) } if in.Limits != nil { - out.Limits = make([]api.LimitRangeItem, len(in.Limits)) - for i := range in.Limits { - if err := Convert_v1_LimitRangeItem_To_api_LimitRangeItem(&in.Limits[i], &out.Limits[i], s); err != nil { + in, out := &in.Limits, &out.Limits + *out = make([]api.LimitRangeItem, len(*in)) + for i := range *in { + if err := Convert_v1_LimitRangeItem_To_api_LimitRangeItem(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -4528,17 +2970,43 @@ func Convert_v1_LimitRangeSpec_To_api_LimitRangeSpec(in *LimitRangeSpec, out *ap return autoConvert_v1_LimitRangeSpec_To_api_LimitRangeSpec(in, out, s) } +func autoConvert_api_LimitRangeSpec_To_v1_LimitRangeSpec(in *api.LimitRangeSpec, out *LimitRangeSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.LimitRangeSpec))(in) + } + if in.Limits != nil { + in, out := &in.Limits, &out.Limits + *out = make([]LimitRangeItem, len(*in)) + for i := range *in { + if err := Convert_api_LimitRangeItem_To_v1_LimitRangeItem(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Limits = nil + } + return nil +} + +func Convert_api_LimitRangeSpec_To_v1_LimitRangeSpec(in *api.LimitRangeSpec, out *LimitRangeSpec, s conversion.Scope) error { + return autoConvert_api_LimitRangeSpec_To_v1_LimitRangeSpec(in, out, s) +} + func autoConvert_v1_List_To_api_List(in *List, out *api.List, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*List))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]runtime.Object, len(in.Items)) - for i := range in.Items { - if err := s.Convert(&in.Items[i], &out.Items[i], 0); err != nil { + in, out := &in.Items, &out.Items + *out = make([]runtime.Object, len(*in)) + for i := range *in { + if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -4552,10 +3020,41 @@ func Convert_v1_List_To_api_List(in *List, out *api.List, s conversion.Scope) er return autoConvert_v1_List_To_api_List(in, out, s) } +func autoConvert_api_List_To_v1_List(in *api.List, out *List, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.List))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]runtime.RawExtension, len(*in)) + for i := range *in { + if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_List_To_v1_List(in *api.List, out *List, s conversion.Scope) error { + return autoConvert_api_List_To_v1_List(in, out, s) +} + func autoConvert_v1_ListOptions_To_api_ListOptions(in *ListOptions, out *api.ListOptions, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ListOptions))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_string_To_labels_Selector(&in.LabelSelector, &out.LabelSelector, s); err != nil { return err } @@ -4565,8 +3064,9 @@ func autoConvert_v1_ListOptions_To_api_ListOptions(in *ListOptions, out *api.Lis out.Watch = in.Watch out.ResourceVersion = in.ResourceVersion if in.TimeoutSeconds != nil { - out.TimeoutSeconds = new(int64) - *out.TimeoutSeconds = *in.TimeoutSeconds + in, out := &in.TimeoutSeconds, &out.TimeoutSeconds + *out = new(int64) + **out = **in } else { out.TimeoutSeconds = nil } @@ -4577,6 +3077,35 @@ func Convert_v1_ListOptions_To_api_ListOptions(in *ListOptions, out *api.ListOpt return autoConvert_v1_ListOptions_To_api_ListOptions(in, out, s) } +func autoConvert_api_ListOptions_To_v1_ListOptions(in *api.ListOptions, out *ListOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ListOptions))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_labels_Selector_To_string(&in.LabelSelector, &out.LabelSelector, s); err != nil { + return err + } + if err := api.Convert_fields_Selector_To_string(&in.FieldSelector, &out.FieldSelector, s); err != nil { + return err + } + out.Watch = in.Watch + out.ResourceVersion = in.ResourceVersion + if in.TimeoutSeconds != nil { + in, out := &in.TimeoutSeconds, &out.TimeoutSeconds + *out = new(int64) + **out = **in + } else { + out.TimeoutSeconds = nil + } + return nil +} + +func Convert_api_ListOptions_To_v1_ListOptions(in *api.ListOptions, out *ListOptions, s conversion.Scope) error { + return autoConvert_api_ListOptions_To_v1_ListOptions(in, out, s) +} + func autoConvert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress(in *LoadBalancerIngress, out *api.LoadBalancerIngress, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*LoadBalancerIngress))(in) @@ -4590,14 +3119,28 @@ func Convert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress(in *LoadBalancerI return autoConvert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress(in, out, s) } +func autoConvert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress(in *api.LoadBalancerIngress, out *LoadBalancerIngress, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.LoadBalancerIngress))(in) + } + out.IP = in.IP + out.Hostname = in.Hostname + return nil +} + +func Convert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress(in *api.LoadBalancerIngress, out *LoadBalancerIngress, s conversion.Scope) error { + return autoConvert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress(in, out, s) +} + func autoConvert_v1_LoadBalancerStatus_To_api_LoadBalancerStatus(in *LoadBalancerStatus, out *api.LoadBalancerStatus, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*LoadBalancerStatus))(in) } if in.Ingress != nil { - out.Ingress = make([]api.LoadBalancerIngress, len(in.Ingress)) - for i := range in.Ingress { - if err := Convert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress(&in.Ingress[i], &out.Ingress[i], s); err != nil { + in, out := &in.Ingress, &out.Ingress + *out = make([]api.LoadBalancerIngress, len(*in)) + for i := range *in { + if err := Convert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -4611,6 +3154,28 @@ func Convert_v1_LoadBalancerStatus_To_api_LoadBalancerStatus(in *LoadBalancerSta return autoConvert_v1_LoadBalancerStatus_To_api_LoadBalancerStatus(in, out, s) } +func autoConvert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus(in *api.LoadBalancerStatus, out *LoadBalancerStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.LoadBalancerStatus))(in) + } + if in.Ingress != nil { + in, out := &in.Ingress, &out.Ingress + *out = make([]LoadBalancerIngress, len(*in)) + for i := range *in { + if err := Convert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Ingress = nil + } + return nil +} + +func Convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus(in *api.LoadBalancerStatus, out *LoadBalancerStatus, s conversion.Scope) error { + return autoConvert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus(in, out, s) +} + func autoConvert_v1_LocalObjectReference_To_api_LocalObjectReference(in *LocalObjectReference, out *api.LocalObjectReference, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*LocalObjectReference))(in) @@ -4623,6 +3188,18 @@ func Convert_v1_LocalObjectReference_To_api_LocalObjectReference(in *LocalObject return autoConvert_v1_LocalObjectReference_To_api_LocalObjectReference(in, out, s) } +func autoConvert_api_LocalObjectReference_To_v1_LocalObjectReference(in *api.LocalObjectReference, out *LocalObjectReference, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.LocalObjectReference))(in) + } + out.Name = in.Name + return nil +} + +func Convert_api_LocalObjectReference_To_v1_LocalObjectReference(in *api.LocalObjectReference, out *LocalObjectReference, s conversion.Scope) error { + return autoConvert_api_LocalObjectReference_To_v1_LocalObjectReference(in, out, s) +} + func autoConvert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in *NFSVolumeSource, out *api.NFSVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*NFSVolumeSource))(in) @@ -4637,10 +3214,27 @@ func Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in *NFSVolumeSource, out return autoConvert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in, out, s) } +func autoConvert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in *api.NFSVolumeSource, out *NFSVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NFSVolumeSource))(in) + } + out.Server = in.Server + out.Path = in.Path + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in *api.NFSVolumeSource, out *NFSVolumeSource, s conversion.Scope) error { + return autoConvert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in, out, s) +} + func autoConvert_v1_Namespace_To_api_Namespace(in *Namespace, out *api.Namespace, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Namespace))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } @@ -4657,17 +3251,44 @@ func Convert_v1_Namespace_To_api_Namespace(in *Namespace, out *api.Namespace, s return autoConvert_v1_Namespace_To_api_Namespace(in, out, s) } +func autoConvert_api_Namespace_To_v1_Namespace(in *api.Namespace, out *Namespace, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Namespace))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_NamespaceSpec_To_v1_NamespaceSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_api_NamespaceStatus_To_v1_NamespaceStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_api_Namespace_To_v1_Namespace(in *api.Namespace, out *Namespace, s conversion.Scope) error { + return autoConvert_api_Namespace_To_v1_Namespace(in, out, s) +} + func autoConvert_v1_NamespaceList_To_api_NamespaceList(in *NamespaceList, out *api.NamespaceList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*NamespaceList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.Namespace, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_Namespace_To_api_Namespace(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.Namespace, len(*in)) + for i := range *in { + if err := Convert_v1_Namespace_To_api_Namespace(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -4681,14 +3302,43 @@ func Convert_v1_NamespaceList_To_api_NamespaceList(in *NamespaceList, out *api.N return autoConvert_v1_NamespaceList_To_api_NamespaceList(in, out, s) } +func autoConvert_api_NamespaceList_To_v1_NamespaceList(in *api.NamespaceList, out *NamespaceList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NamespaceList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Namespace, len(*in)) + for i := range *in { + if err := Convert_api_Namespace_To_v1_Namespace(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_NamespaceList_To_v1_NamespaceList(in *api.NamespaceList, out *NamespaceList, s conversion.Scope) error { + return autoConvert_api_NamespaceList_To_v1_NamespaceList(in, out, s) +} + func autoConvert_v1_NamespaceSpec_To_api_NamespaceSpec(in *NamespaceSpec, out *api.NamespaceSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*NamespaceSpec))(in) } if in.Finalizers != nil { - out.Finalizers = make([]api.FinalizerName, len(in.Finalizers)) - for i := range in.Finalizers { - out.Finalizers[i] = api.FinalizerName(in.Finalizers[i]) + in, out := &in.Finalizers, &out.Finalizers + *out = make([]api.FinalizerName, len(*in)) + for i := range *in { + (*out)[i] = api.FinalizerName((*in)[i]) } } else { out.Finalizers = nil @@ -4700,6 +3350,26 @@ func Convert_v1_NamespaceSpec_To_api_NamespaceSpec(in *NamespaceSpec, out *api.N return autoConvert_v1_NamespaceSpec_To_api_NamespaceSpec(in, out, s) } +func autoConvert_api_NamespaceSpec_To_v1_NamespaceSpec(in *api.NamespaceSpec, out *NamespaceSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NamespaceSpec))(in) + } + if in.Finalizers != nil { + in, out := &in.Finalizers, &out.Finalizers + *out = make([]FinalizerName, len(*in)) + for i := range *in { + (*out)[i] = FinalizerName((*in)[i]) + } + } else { + out.Finalizers = nil + } + return nil +} + +func Convert_api_NamespaceSpec_To_v1_NamespaceSpec(in *api.NamespaceSpec, out *NamespaceSpec, s conversion.Scope) error { + return autoConvert_api_NamespaceSpec_To_v1_NamespaceSpec(in, out, s) +} + func autoConvert_v1_NamespaceStatus_To_api_NamespaceStatus(in *NamespaceStatus, out *api.NamespaceStatus, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*NamespaceStatus))(in) @@ -4712,10 +3382,25 @@ func Convert_v1_NamespaceStatus_To_api_NamespaceStatus(in *NamespaceStatus, out return autoConvert_v1_NamespaceStatus_To_api_NamespaceStatus(in, out, s) } +func autoConvert_api_NamespaceStatus_To_v1_NamespaceStatus(in *api.NamespaceStatus, out *NamespaceStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NamespaceStatus))(in) + } + out.Phase = NamespacePhase(in.Phase) + return nil +} + +func Convert_api_NamespaceStatus_To_v1_NamespaceStatus(in *api.NamespaceStatus, out *NamespaceStatus, s conversion.Scope) error { + return autoConvert_api_NamespaceStatus_To_v1_NamespaceStatus(in, out, s) +} + func autoConvert_v1_Node_To_api_Node(in *Node, out *api.Node, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Node))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } @@ -4732,6 +3417,29 @@ func Convert_v1_Node_To_api_Node(in *Node, out *api.Node, s conversion.Scope) er return autoConvert_v1_Node_To_api_Node(in, out, s) } +func autoConvert_api_Node_To_v1_Node(in *api.Node, out *Node, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Node))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_NodeSpec_To_v1_NodeSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_api_NodeStatus_To_v1_NodeStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_api_Node_To_v1_Node(in *api.Node, out *Node, s conversion.Scope) error { + return autoConvert_api_Node_To_v1_Node(in, out, s) +} + func autoConvert_v1_NodeAddress_To_api_NodeAddress(in *NodeAddress, out *api.NodeAddress, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*NodeAddress))(in) @@ -4745,6 +3453,81 @@ func Convert_v1_NodeAddress_To_api_NodeAddress(in *NodeAddress, out *api.NodeAdd return autoConvert_v1_NodeAddress_To_api_NodeAddress(in, out, s) } +func autoConvert_api_NodeAddress_To_v1_NodeAddress(in *api.NodeAddress, out *NodeAddress, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NodeAddress))(in) + } + out.Type = NodeAddressType(in.Type) + out.Address = in.Address + return nil +} + +func Convert_api_NodeAddress_To_v1_NodeAddress(in *api.NodeAddress, out *NodeAddress, s conversion.Scope) error { + return autoConvert_api_NodeAddress_To_v1_NodeAddress(in, out, s) +} + +func autoConvert_v1_NodeAffinity_To_api_NodeAffinity(in *NodeAffinity, out *api.NodeAffinity, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*NodeAffinity))(in) + } + if in.RequiredDuringSchedulingIgnoredDuringExecution != nil { + in, out := &in.RequiredDuringSchedulingIgnoredDuringExecution, &out.RequiredDuringSchedulingIgnoredDuringExecution + *out = new(api.NodeSelector) + if err := Convert_v1_NodeSelector_To_api_NodeSelector(*in, *out, s); err != nil { + return err + } + } else { + out.RequiredDuringSchedulingIgnoredDuringExecution = nil + } + if in.PreferredDuringSchedulingIgnoredDuringExecution != nil { + in, out := &in.PreferredDuringSchedulingIgnoredDuringExecution, &out.PreferredDuringSchedulingIgnoredDuringExecution + *out = make([]api.PreferredSchedulingTerm, len(*in)) + for i := range *in { + if err := Convert_v1_PreferredSchedulingTerm_To_api_PreferredSchedulingTerm(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.PreferredDuringSchedulingIgnoredDuringExecution = nil + } + return nil +} + +func Convert_v1_NodeAffinity_To_api_NodeAffinity(in *NodeAffinity, out *api.NodeAffinity, s conversion.Scope) error { + return autoConvert_v1_NodeAffinity_To_api_NodeAffinity(in, out, s) +} + +func autoConvert_api_NodeAffinity_To_v1_NodeAffinity(in *api.NodeAffinity, out *NodeAffinity, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NodeAffinity))(in) + } + if in.RequiredDuringSchedulingIgnoredDuringExecution != nil { + in, out := &in.RequiredDuringSchedulingIgnoredDuringExecution, &out.RequiredDuringSchedulingIgnoredDuringExecution + *out = new(NodeSelector) + if err := Convert_api_NodeSelector_To_v1_NodeSelector(*in, *out, s); err != nil { + return err + } + } else { + out.RequiredDuringSchedulingIgnoredDuringExecution = nil + } + if in.PreferredDuringSchedulingIgnoredDuringExecution != nil { + in, out := &in.PreferredDuringSchedulingIgnoredDuringExecution, &out.PreferredDuringSchedulingIgnoredDuringExecution + *out = make([]PreferredSchedulingTerm, len(*in)) + for i := range *in { + if err := Convert_api_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.PreferredDuringSchedulingIgnoredDuringExecution = nil + } + return nil +} + +func Convert_api_NodeAffinity_To_v1_NodeAffinity(in *api.NodeAffinity, out *NodeAffinity, s conversion.Scope) error { + return autoConvert_api_NodeAffinity_To_v1_NodeAffinity(in, out, s) +} + func autoConvert_v1_NodeCondition_To_api_NodeCondition(in *NodeCondition, out *api.NodeCondition, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*NodeCondition))(in) @@ -4766,6 +3549,27 @@ func Convert_v1_NodeCondition_To_api_NodeCondition(in *NodeCondition, out *api.N return autoConvert_v1_NodeCondition_To_api_NodeCondition(in, out, s) } +func autoConvert_api_NodeCondition_To_v1_NodeCondition(in *api.NodeCondition, out *NodeCondition, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NodeCondition))(in) + } + out.Type = NodeConditionType(in.Type) + out.Status = ConditionStatus(in.Status) + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastHeartbeatTime, &out.LastHeartbeatTime, s); err != nil { + return err + } + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastTransitionTime, &out.LastTransitionTime, s); err != nil { + return err + } + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +func Convert_api_NodeCondition_To_v1_NodeCondition(in *api.NodeCondition, out *NodeCondition, s conversion.Scope) error { + return autoConvert_api_NodeCondition_To_v1_NodeCondition(in, out, s) +} + func autoConvert_v1_NodeDaemonEndpoints_To_api_NodeDaemonEndpoints(in *NodeDaemonEndpoints, out *api.NodeDaemonEndpoints, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*NodeDaemonEndpoints))(in) @@ -4780,17 +3584,35 @@ func Convert_v1_NodeDaemonEndpoints_To_api_NodeDaemonEndpoints(in *NodeDaemonEnd return autoConvert_v1_NodeDaemonEndpoints_To_api_NodeDaemonEndpoints(in, out, s) } +func autoConvert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(in *api.NodeDaemonEndpoints, out *NodeDaemonEndpoints, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NodeDaemonEndpoints))(in) + } + if err := Convert_api_DaemonEndpoint_To_v1_DaemonEndpoint(&in.KubeletEndpoint, &out.KubeletEndpoint, s); err != nil { + return err + } + return nil +} + +func Convert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(in *api.NodeDaemonEndpoints, out *NodeDaemonEndpoints, s conversion.Scope) error { + return autoConvert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(in, out, s) +} + func autoConvert_v1_NodeList_To_api_NodeList(in *NodeList, out *api.NodeList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*NodeList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.Node, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_Node_To_api_Node(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.Node, len(*in)) + for i := range *in { + if err := Convert_v1_Node_To_api_Node(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -4804,6 +3626,192 @@ func Convert_v1_NodeList_To_api_NodeList(in *NodeList, out *api.NodeList, s conv return autoConvert_v1_NodeList_To_api_NodeList(in, out, s) } +func autoConvert_api_NodeList_To_v1_NodeList(in *api.NodeList, out *NodeList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NodeList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Node, len(*in)) + for i := range *in { + if err := Convert_api_Node_To_v1_Node(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_NodeList_To_v1_NodeList(in *api.NodeList, out *NodeList, s conversion.Scope) error { + return autoConvert_api_NodeList_To_v1_NodeList(in, out, s) +} + +func autoConvert_v1_NodeProxyOptions_To_api_NodeProxyOptions(in *NodeProxyOptions, out *api.NodeProxyOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*NodeProxyOptions))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + out.Path = in.Path + return nil +} + +func Convert_v1_NodeProxyOptions_To_api_NodeProxyOptions(in *NodeProxyOptions, out *api.NodeProxyOptions, s conversion.Scope) error { + return autoConvert_v1_NodeProxyOptions_To_api_NodeProxyOptions(in, out, s) +} + +func autoConvert_api_NodeProxyOptions_To_v1_NodeProxyOptions(in *api.NodeProxyOptions, out *NodeProxyOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NodeProxyOptions))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + out.Path = in.Path + return nil +} + +func Convert_api_NodeProxyOptions_To_v1_NodeProxyOptions(in *api.NodeProxyOptions, out *NodeProxyOptions, s conversion.Scope) error { + return autoConvert_api_NodeProxyOptions_To_v1_NodeProxyOptions(in, out, s) +} + +func autoConvert_v1_NodeSelector_To_api_NodeSelector(in *NodeSelector, out *api.NodeSelector, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*NodeSelector))(in) + } + if in.NodeSelectorTerms != nil { + in, out := &in.NodeSelectorTerms, &out.NodeSelectorTerms + *out = make([]api.NodeSelectorTerm, len(*in)) + for i := range *in { + if err := Convert_v1_NodeSelectorTerm_To_api_NodeSelectorTerm(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.NodeSelectorTerms = nil + } + return nil +} + +func Convert_v1_NodeSelector_To_api_NodeSelector(in *NodeSelector, out *api.NodeSelector, s conversion.Scope) error { + return autoConvert_v1_NodeSelector_To_api_NodeSelector(in, out, s) +} + +func autoConvert_api_NodeSelector_To_v1_NodeSelector(in *api.NodeSelector, out *NodeSelector, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NodeSelector))(in) + } + if in.NodeSelectorTerms != nil { + in, out := &in.NodeSelectorTerms, &out.NodeSelectorTerms + *out = make([]NodeSelectorTerm, len(*in)) + for i := range *in { + if err := Convert_api_NodeSelectorTerm_To_v1_NodeSelectorTerm(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.NodeSelectorTerms = nil + } + return nil +} + +func Convert_api_NodeSelector_To_v1_NodeSelector(in *api.NodeSelector, out *NodeSelector, s conversion.Scope) error { + return autoConvert_api_NodeSelector_To_v1_NodeSelector(in, out, s) +} + +func autoConvert_v1_NodeSelectorRequirement_To_api_NodeSelectorRequirement(in *NodeSelectorRequirement, out *api.NodeSelectorRequirement, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*NodeSelectorRequirement))(in) + } + out.Key = in.Key + out.Operator = api.NodeSelectorOperator(in.Operator) + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = make([]string, len(*in)) + copy(*out, *in) + } else { + out.Values = nil + } + return nil +} + +func Convert_v1_NodeSelectorRequirement_To_api_NodeSelectorRequirement(in *NodeSelectorRequirement, out *api.NodeSelectorRequirement, s conversion.Scope) error { + return autoConvert_v1_NodeSelectorRequirement_To_api_NodeSelectorRequirement(in, out, s) +} + +func autoConvert_api_NodeSelectorRequirement_To_v1_NodeSelectorRequirement(in *api.NodeSelectorRequirement, out *NodeSelectorRequirement, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NodeSelectorRequirement))(in) + } + out.Key = in.Key + out.Operator = NodeSelectorOperator(in.Operator) + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = make([]string, len(*in)) + copy(*out, *in) + } else { + out.Values = nil + } + return nil +} + +func Convert_api_NodeSelectorRequirement_To_v1_NodeSelectorRequirement(in *api.NodeSelectorRequirement, out *NodeSelectorRequirement, s conversion.Scope) error { + return autoConvert_api_NodeSelectorRequirement_To_v1_NodeSelectorRequirement(in, out, s) +} + +func autoConvert_v1_NodeSelectorTerm_To_api_NodeSelectorTerm(in *NodeSelectorTerm, out *api.NodeSelectorTerm, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*NodeSelectorTerm))(in) + } + if in.MatchExpressions != nil { + in, out := &in.MatchExpressions, &out.MatchExpressions + *out = make([]api.NodeSelectorRequirement, len(*in)) + for i := range *in { + if err := Convert_v1_NodeSelectorRequirement_To_api_NodeSelectorRequirement(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.MatchExpressions = nil + } + return nil +} + +func Convert_v1_NodeSelectorTerm_To_api_NodeSelectorTerm(in *NodeSelectorTerm, out *api.NodeSelectorTerm, s conversion.Scope) error { + return autoConvert_v1_NodeSelectorTerm_To_api_NodeSelectorTerm(in, out, s) +} + +func autoConvert_api_NodeSelectorTerm_To_v1_NodeSelectorTerm(in *api.NodeSelectorTerm, out *NodeSelectorTerm, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NodeSelectorTerm))(in) + } + if in.MatchExpressions != nil { + in, out := &in.MatchExpressions, &out.MatchExpressions + *out = make([]NodeSelectorRequirement, len(*in)) + for i := range *in { + if err := Convert_api_NodeSelectorRequirement_To_v1_NodeSelectorRequirement(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.MatchExpressions = nil + } + return nil +} + +func Convert_api_NodeSelectorTerm_To_v1_NodeSelectorTerm(in *api.NodeSelectorTerm, out *NodeSelectorTerm, s conversion.Scope) error { + return autoConvert_api_NodeSelectorTerm_To_v1_NodeSelectorTerm(in, out, s) +} + func autoConvert_v1_NodeSpec_To_api_NodeSpec(in *NodeSpec, out *api.NodeSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*NodeSpec))(in) @@ -4819,21 +3827,37 @@ func Convert_v1_NodeSpec_To_api_NodeSpec(in *NodeSpec, out *api.NodeSpec, s conv return autoConvert_v1_NodeSpec_To_api_NodeSpec(in, out, s) } +func autoConvert_api_NodeSpec_To_v1_NodeSpec(in *api.NodeSpec, out *NodeSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NodeSpec))(in) + } + out.PodCIDR = in.PodCIDR + out.ExternalID = in.ExternalID + out.ProviderID = in.ProviderID + out.Unschedulable = in.Unschedulable + return nil +} + +func Convert_api_NodeSpec_To_v1_NodeSpec(in *api.NodeSpec, out *NodeSpec, s conversion.Scope) error { + return autoConvert_api_NodeSpec_To_v1_NodeSpec(in, out, s) +} + func autoConvert_v1_NodeStatus_To_api_NodeStatus(in *NodeStatus, out *api.NodeStatus, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*NodeStatus))(in) } - if err := s.Convert(&in.Capacity, &out.Capacity, 0); err != nil { + if err := Convert_v1_ResourceList_To_api_ResourceList(&in.Capacity, &out.Capacity, s); err != nil { return err } - if err := s.Convert(&in.Allocatable, &out.Allocatable, 0); err != nil { + if err := Convert_v1_ResourceList_To_api_ResourceList(&in.Allocatable, &out.Allocatable, s); err != nil { return err } out.Phase = api.NodePhase(in.Phase) if in.Conditions != nil { - out.Conditions = make([]api.NodeCondition, len(in.Conditions)) - for i := range in.Conditions { - if err := Convert_v1_NodeCondition_To_api_NodeCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]api.NodeCondition, len(*in)) + for i := range *in { + if err := Convert_v1_NodeCondition_To_api_NodeCondition(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -4841,9 +3865,10 @@ func autoConvert_v1_NodeStatus_To_api_NodeStatus(in *NodeStatus, out *api.NodeSt out.Conditions = nil } if in.Addresses != nil { - out.Addresses = make([]api.NodeAddress, len(in.Addresses)) - for i := range in.Addresses { - if err := Convert_v1_NodeAddress_To_api_NodeAddress(&in.Addresses[i], &out.Addresses[i], s); err != nil { + in, out := &in.Addresses, &out.Addresses + *out = make([]api.NodeAddress, len(*in)) + for i := range *in { + if err := Convert_v1_NodeAddress_To_api_NodeAddress(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -4857,9 +3882,10 @@ func autoConvert_v1_NodeStatus_To_api_NodeStatus(in *NodeStatus, out *api.NodeSt return err } if in.Images != nil { - out.Images = make([]api.ContainerImage, len(in.Images)) - for i := range in.Images { - if err := Convert_v1_ContainerImage_To_api_ContainerImage(&in.Images[i], &out.Images[i], s); err != nil { + in, out := &in.Images, &out.Images + *out = make([]api.ContainerImage, len(*in)) + for i := range *in { + if err := Convert_v1_ContainerImage_To_api_ContainerImage(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -4873,6 +3899,83 @@ func Convert_v1_NodeStatus_To_api_NodeStatus(in *NodeStatus, out *api.NodeStatus return autoConvert_v1_NodeStatus_To_api_NodeStatus(in, out, s) } +func autoConvert_api_NodeStatus_To_v1_NodeStatus(in *api.NodeStatus, out *NodeStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NodeStatus))(in) + } + if in.Capacity != nil { + in, out := &in.Capacity, &out.Capacity + *out = make(ResourceList, len(*in)) + for key, val := range *in { + newVal := new(resource.Quantity) + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, newVal, s); err != nil { + return err + } + (*out)[ResourceName(key)] = *newVal + } + } else { + out.Capacity = nil + } + if in.Allocatable != nil { + in, out := &in.Allocatable, &out.Allocatable + *out = make(ResourceList, len(*in)) + for key, val := range *in { + newVal := new(resource.Quantity) + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, newVal, s); err != nil { + return err + } + (*out)[ResourceName(key)] = *newVal + } + } else { + out.Allocatable = nil + } + out.Phase = NodePhase(in.Phase) + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]NodeCondition, len(*in)) + for i := range *in { + if err := Convert_api_NodeCondition_To_v1_NodeCondition(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Conditions = nil + } + if in.Addresses != nil { + in, out := &in.Addresses, &out.Addresses + *out = make([]NodeAddress, len(*in)) + for i := range *in { + if err := Convert_api_NodeAddress_To_v1_NodeAddress(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Addresses = nil + } + if err := Convert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints(&in.DaemonEndpoints, &out.DaemonEndpoints, s); err != nil { + return err + } + if err := Convert_api_NodeSystemInfo_To_v1_NodeSystemInfo(&in.NodeInfo, &out.NodeInfo, s); err != nil { + return err + } + if in.Images != nil { + in, out := &in.Images, &out.Images + *out = make([]ContainerImage, len(*in)) + for i := range *in { + if err := Convert_api_ContainerImage_To_v1_ContainerImage(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Images = nil + } + return nil +} + +func Convert_api_NodeStatus_To_v1_NodeStatus(in *api.NodeStatus, out *NodeStatus, s conversion.Scope) error { + return autoConvert_api_NodeStatus_To_v1_NodeStatus(in, out, s) +} + func autoConvert_v1_NodeSystemInfo_To_api_NodeSystemInfo(in *NodeSystemInfo, out *api.NodeSystemInfo, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*NodeSystemInfo))(in) @@ -4892,6 +3995,25 @@ func Convert_v1_NodeSystemInfo_To_api_NodeSystemInfo(in *NodeSystemInfo, out *ap return autoConvert_v1_NodeSystemInfo_To_api_NodeSystemInfo(in, out, s) } +func autoConvert_api_NodeSystemInfo_To_v1_NodeSystemInfo(in *api.NodeSystemInfo, out *NodeSystemInfo, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NodeSystemInfo))(in) + } + out.MachineID = in.MachineID + out.SystemUUID = in.SystemUUID + out.BootID = in.BootID + out.KernelVersion = in.KernelVersion + out.OSImage = in.OSImage + out.ContainerRuntimeVersion = in.ContainerRuntimeVersion + out.KubeletVersion = in.KubeletVersion + out.KubeProxyVersion = in.KubeProxyVersion + return nil +} + +func Convert_api_NodeSystemInfo_To_v1_NodeSystemInfo(in *api.NodeSystemInfo, out *NodeSystemInfo, s conversion.Scope) error { + return autoConvert_api_NodeSystemInfo_To_v1_NodeSystemInfo(in, out, s) +} + func autoConvert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector(in *ObjectFieldSelector, out *api.ObjectFieldSelector, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ObjectFieldSelector))(in) @@ -4905,6 +4027,19 @@ func Convert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector(in *ObjectFieldSe return autoConvert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector(in, out, s) } +func autoConvert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(in *api.ObjectFieldSelector, out *ObjectFieldSelector, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ObjectFieldSelector))(in) + } + out.APIVersion = in.APIVersion + out.FieldPath = in.FieldPath + return nil +} + +func Convert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(in *api.ObjectFieldSelector, out *ObjectFieldSelector, s conversion.Scope) error { + return autoConvert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(in, out, s) +} + func autoConvert_v1_ObjectMeta_To_api_ObjectMeta(in *ObjectMeta, out *api.ObjectMeta, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ObjectMeta))(in) @@ -4919,33 +4054,36 @@ func autoConvert_v1_ObjectMeta_To_api_ObjectMeta(in *ObjectMeta, out *api.Object if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.CreationTimestamp, &out.CreationTimestamp, s); err != nil { return err } - // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time if in.DeletionTimestamp != nil { - out.DeletionTimestamp = new(unversioned.Time) - if err := api.Convert_unversioned_Time_To_unversioned_Time(in.DeletionTimestamp, out.DeletionTimestamp, s); err != nil { + in, out := &in.DeletionTimestamp, &out.DeletionTimestamp + *out = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(*in, *out, s); err != nil { return err } } else { out.DeletionTimestamp = nil } if in.DeletionGracePeriodSeconds != nil { - out.DeletionGracePeriodSeconds = new(int64) - *out.DeletionGracePeriodSeconds = *in.DeletionGracePeriodSeconds + in, out := &in.DeletionGracePeriodSeconds, &out.DeletionGracePeriodSeconds + *out = new(int64) + **out = **in } else { out.DeletionGracePeriodSeconds = nil } if in.Labels != nil { - out.Labels = make(map[string]string) - for key, val := range in.Labels { - out.Labels[key] = val + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } else { out.Labels = nil } if in.Annotations != nil { - out.Annotations = make(map[string]string) - for key, val := range in.Annotations { - out.Annotations[key] = val + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } else { out.Annotations = nil @@ -4957,6 +4095,61 @@ func Convert_v1_ObjectMeta_To_api_ObjectMeta(in *ObjectMeta, out *api.ObjectMeta return autoConvert_v1_ObjectMeta_To_api_ObjectMeta(in, out, s) } +func autoConvert_api_ObjectMeta_To_v1_ObjectMeta(in *api.ObjectMeta, out *ObjectMeta, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ObjectMeta))(in) + } + out.Name = in.Name + out.GenerateName = in.GenerateName + out.Namespace = in.Namespace + out.SelfLink = in.SelfLink + out.UID = in.UID + out.ResourceVersion = in.ResourceVersion + out.Generation = in.Generation + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.CreationTimestamp, &out.CreationTimestamp, s); err != nil { + return err + } + if in.DeletionTimestamp != nil { + in, out := &in.DeletionTimestamp, &out.DeletionTimestamp + *out = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(*in, *out, s); err != nil { + return err + } + } else { + out.DeletionTimestamp = nil + } + if in.DeletionGracePeriodSeconds != nil { + in, out := &in.DeletionGracePeriodSeconds, &out.DeletionGracePeriodSeconds + *out = new(int64) + **out = **in + } else { + out.DeletionGracePeriodSeconds = nil + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } else { + out.Labels = nil + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } else { + out.Annotations = nil + } + return nil +} + +func Convert_api_ObjectMeta_To_v1_ObjectMeta(in *api.ObjectMeta, out *ObjectMeta, s conversion.Scope) error { + return autoConvert_api_ObjectMeta_To_v1_ObjectMeta(in, out, s) +} + func autoConvert_v1_ObjectReference_To_api_ObjectReference(in *ObjectReference, out *api.ObjectReference, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ObjectReference))(in) @@ -4975,10 +4168,31 @@ func Convert_v1_ObjectReference_To_api_ObjectReference(in *ObjectReference, out return autoConvert_v1_ObjectReference_To_api_ObjectReference(in, out, s) } +func autoConvert_api_ObjectReference_To_v1_ObjectReference(in *api.ObjectReference, out *ObjectReference, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ObjectReference))(in) + } + out.Kind = in.Kind + out.Namespace = in.Namespace + out.Name = in.Name + out.UID = in.UID + out.APIVersion = in.APIVersion + out.ResourceVersion = in.ResourceVersion + out.FieldPath = in.FieldPath + return nil +} + +func Convert_api_ObjectReference_To_v1_ObjectReference(in *api.ObjectReference, out *ObjectReference, s conversion.Scope) error { + return autoConvert_api_ObjectReference_To_v1_ObjectReference(in, out, s) +} + func autoConvert_v1_PersistentVolume_To_api_PersistentVolume(in *PersistentVolume, out *api.PersistentVolume, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PersistentVolume))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } @@ -4995,10 +4209,36 @@ func Convert_v1_PersistentVolume_To_api_PersistentVolume(in *PersistentVolume, o return autoConvert_v1_PersistentVolume_To_api_PersistentVolume(in, out, s) } +func autoConvert_api_PersistentVolume_To_v1_PersistentVolume(in *api.PersistentVolume, out *PersistentVolume, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PersistentVolume))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_api_PersistentVolume_To_v1_PersistentVolume(in *api.PersistentVolume, out *PersistentVolume, s conversion.Scope) error { + return autoConvert_api_PersistentVolume_To_v1_PersistentVolume(in, out, s) +} + func autoConvert_v1_PersistentVolumeClaim_To_api_PersistentVolumeClaim(in *PersistentVolumeClaim, out *api.PersistentVolumeClaim, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PersistentVolumeClaim))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } @@ -5015,17 +4255,44 @@ func Convert_v1_PersistentVolumeClaim_To_api_PersistentVolumeClaim(in *Persisten return autoConvert_v1_PersistentVolumeClaim_To_api_PersistentVolumeClaim(in, out, s) } +func autoConvert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(in *api.PersistentVolumeClaim, out *PersistentVolumeClaim, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PersistentVolumeClaim))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(in *api.PersistentVolumeClaim, out *PersistentVolumeClaim, s conversion.Scope) error { + return autoConvert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(in, out, s) +} + func autoConvert_v1_PersistentVolumeClaimList_To_api_PersistentVolumeClaimList(in *PersistentVolumeClaimList, out *api.PersistentVolumeClaimList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PersistentVolumeClaimList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.PersistentVolumeClaim, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_PersistentVolumeClaim_To_api_PersistentVolumeClaim(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.PersistentVolumeClaim, len(*in)) + for i := range *in { + if err := Convert_v1_PersistentVolumeClaim_To_api_PersistentVolumeClaim(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -5039,14 +4306,43 @@ func Convert_v1_PersistentVolumeClaimList_To_api_PersistentVolumeClaimList(in *P return autoConvert_v1_PersistentVolumeClaimList_To_api_PersistentVolumeClaimList(in, out, s) } +func autoConvert_api_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList(in *api.PersistentVolumeClaimList, out *PersistentVolumeClaimList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PersistentVolumeClaimList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PersistentVolumeClaim, len(*in)) + for i := range *in { + if err := Convert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList(in *api.PersistentVolumeClaimList, out *PersistentVolumeClaimList, s conversion.Scope) error { + return autoConvert_api_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList(in, out, s) +} + func autoConvert_v1_PersistentVolumeClaimSpec_To_api_PersistentVolumeClaimSpec(in *PersistentVolumeClaimSpec, out *api.PersistentVolumeClaimSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PersistentVolumeClaimSpec))(in) } if in.AccessModes != nil { - out.AccessModes = make([]api.PersistentVolumeAccessMode, len(in.AccessModes)) - for i := range in.AccessModes { - out.AccessModes[i] = api.PersistentVolumeAccessMode(in.AccessModes[i]) + in, out := &in.AccessModes, &out.AccessModes + *out = make([]api.PersistentVolumeAccessMode, len(*in)) + for i := range *in { + (*out)[i] = api.PersistentVolumeAccessMode((*in)[i]) } } else { out.AccessModes = nil @@ -5062,20 +4358,45 @@ func Convert_v1_PersistentVolumeClaimSpec_To_api_PersistentVolumeClaimSpec(in *P return autoConvert_v1_PersistentVolumeClaimSpec_To_api_PersistentVolumeClaimSpec(in, out, s) } +func autoConvert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(in *api.PersistentVolumeClaimSpec, out *PersistentVolumeClaimSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PersistentVolumeClaimSpec))(in) + } + if in.AccessModes != nil { + in, out := &in.AccessModes, &out.AccessModes + *out = make([]PersistentVolumeAccessMode, len(*in)) + for i := range *in { + (*out)[i] = PersistentVolumeAccessMode((*in)[i]) + } + } else { + out.AccessModes = nil + } + if err := Convert_api_ResourceRequirements_To_v1_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil { + return err + } + out.VolumeName = in.VolumeName + return nil +} + +func Convert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(in *api.PersistentVolumeClaimSpec, out *PersistentVolumeClaimSpec, s conversion.Scope) error { + return autoConvert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(in, out, s) +} + func autoConvert_v1_PersistentVolumeClaimStatus_To_api_PersistentVolumeClaimStatus(in *PersistentVolumeClaimStatus, out *api.PersistentVolumeClaimStatus, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PersistentVolumeClaimStatus))(in) } out.Phase = api.PersistentVolumeClaimPhase(in.Phase) if in.AccessModes != nil { - out.AccessModes = make([]api.PersistentVolumeAccessMode, len(in.AccessModes)) - for i := range in.AccessModes { - out.AccessModes[i] = api.PersistentVolumeAccessMode(in.AccessModes[i]) + in, out := &in.AccessModes, &out.AccessModes + *out = make([]api.PersistentVolumeAccessMode, len(*in)) + for i := range *in { + (*out)[i] = api.PersistentVolumeAccessMode((*in)[i]) } } else { out.AccessModes = nil } - if err := s.Convert(&in.Capacity, &out.Capacity, 0); err != nil { + if err := Convert_v1_ResourceList_To_api_ResourceList(&in.Capacity, &out.Capacity, s); err != nil { return err } return nil @@ -5085,6 +4406,40 @@ func Convert_v1_PersistentVolumeClaimStatus_To_api_PersistentVolumeClaimStatus(i return autoConvert_v1_PersistentVolumeClaimStatus_To_api_PersistentVolumeClaimStatus(in, out, s) } +func autoConvert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(in *api.PersistentVolumeClaimStatus, out *PersistentVolumeClaimStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PersistentVolumeClaimStatus))(in) + } + out.Phase = PersistentVolumeClaimPhase(in.Phase) + if in.AccessModes != nil { + in, out := &in.AccessModes, &out.AccessModes + *out = make([]PersistentVolumeAccessMode, len(*in)) + for i := range *in { + (*out)[i] = PersistentVolumeAccessMode((*in)[i]) + } + } else { + out.AccessModes = nil + } + if in.Capacity != nil { + in, out := &in.Capacity, &out.Capacity + *out = make(ResourceList, len(*in)) + for key, val := range *in { + newVal := new(resource.Quantity) + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, newVal, s); err != nil { + return err + } + (*out)[ResourceName(key)] = *newVal + } + } else { + out.Capacity = nil + } + return nil +} + +func Convert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(in *api.PersistentVolumeClaimStatus, out *PersistentVolumeClaimStatus, s conversion.Scope) error { + return autoConvert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(in, out, s) +} + func autoConvert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource(in *PersistentVolumeClaimVolumeSource, out *api.PersistentVolumeClaimVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PersistentVolumeClaimVolumeSource))(in) @@ -5098,17 +4453,34 @@ func Convert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVo return autoConvert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource(in, out, s) } +func autoConvert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in *api.PersistentVolumeClaimVolumeSource, out *PersistentVolumeClaimVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PersistentVolumeClaimVolumeSource))(in) + } + out.ClaimName = in.ClaimName + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in *api.PersistentVolumeClaimVolumeSource, out *PersistentVolumeClaimVolumeSource, s conversion.Scope) error { + return autoConvert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in, out, s) +} + func autoConvert_v1_PersistentVolumeList_To_api_PersistentVolumeList(in *PersistentVolumeList, out *api.PersistentVolumeList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PersistentVolumeList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.PersistentVolume, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_PersistentVolume_To_api_PersistentVolume(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.PersistentVolume, len(*in)) + for i := range *in { + if err := Convert_v1_PersistentVolume_To_api_PersistentVolume(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -5122,122 +4494,150 @@ func Convert_v1_PersistentVolumeList_To_api_PersistentVolumeList(in *PersistentV return autoConvert_v1_PersistentVolumeList_To_api_PersistentVolumeList(in, out, s) } +func autoConvert_api_PersistentVolumeList_To_v1_PersistentVolumeList(in *api.PersistentVolumeList, out *PersistentVolumeList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PersistentVolumeList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PersistentVolume, len(*in)) + for i := range *in { + if err := Convert_api_PersistentVolume_To_v1_PersistentVolume(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_PersistentVolumeList_To_v1_PersistentVolumeList(in *api.PersistentVolumeList, out *PersistentVolumeList, s conversion.Scope) error { + return autoConvert_api_PersistentVolumeList_To_v1_PersistentVolumeList(in, out, s) +} + func autoConvert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(in *PersistentVolumeSource, out *api.PersistentVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PersistentVolumeSource))(in) } - // unable to generate simple pointer conversion for v1.GCEPersistentDiskVolumeSource -> api.GCEPersistentDiskVolumeSource if in.GCEPersistentDisk != nil { - out.GCEPersistentDisk = new(api.GCEPersistentDiskVolumeSource) - if err := Convert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource(in.GCEPersistentDisk, out.GCEPersistentDisk, s); err != nil { + in, out := &in.GCEPersistentDisk, &out.GCEPersistentDisk + *out = new(api.GCEPersistentDiskVolumeSource) + if err := Convert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource(*in, *out, s); err != nil { return err } } else { out.GCEPersistentDisk = nil } - // unable to generate simple pointer conversion for v1.AWSElasticBlockStoreVolumeSource -> api.AWSElasticBlockStoreVolumeSource if in.AWSElasticBlockStore != nil { - out.AWSElasticBlockStore = new(api.AWSElasticBlockStoreVolumeSource) - if err := Convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource(in.AWSElasticBlockStore, out.AWSElasticBlockStore, s); err != nil { + in, out := &in.AWSElasticBlockStore, &out.AWSElasticBlockStore + *out = new(api.AWSElasticBlockStoreVolumeSource) + if err := Convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource(*in, *out, s); err != nil { return err } } else { out.AWSElasticBlockStore = nil } - // unable to generate simple pointer conversion for v1.HostPathVolumeSource -> api.HostPathVolumeSource if in.HostPath != nil { - out.HostPath = new(api.HostPathVolumeSource) - if err := Convert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource(in.HostPath, out.HostPath, s); err != nil { + in, out := &in.HostPath, &out.HostPath + *out = new(api.HostPathVolumeSource) + if err := Convert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource(*in, *out, s); err != nil { return err } } else { out.HostPath = nil } - // unable to generate simple pointer conversion for v1.GlusterfsVolumeSource -> api.GlusterfsVolumeSource if in.Glusterfs != nil { - out.Glusterfs = new(api.GlusterfsVolumeSource) - if err := Convert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource(in.Glusterfs, out.Glusterfs, s); err != nil { + in, out := &in.Glusterfs, &out.Glusterfs + *out = new(api.GlusterfsVolumeSource) + if err := Convert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource(*in, *out, s); err != nil { return err } } else { out.Glusterfs = nil } - // unable to generate simple pointer conversion for v1.NFSVolumeSource -> api.NFSVolumeSource if in.NFS != nil { - out.NFS = new(api.NFSVolumeSource) - if err := Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in.NFS, out.NFS, s); err != nil { + in, out := &in.NFS, &out.NFS + *out = new(api.NFSVolumeSource) + if err := Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource(*in, *out, s); err != nil { return err } } else { out.NFS = nil } - // unable to generate simple pointer conversion for v1.RBDVolumeSource -> api.RBDVolumeSource if in.RBD != nil { - out.RBD = new(api.RBDVolumeSource) - if err := Convert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in.RBD, out.RBD, s); err != nil { + in, out := &in.RBD, &out.RBD + *out = new(api.RBDVolumeSource) + if err := Convert_v1_RBDVolumeSource_To_api_RBDVolumeSource(*in, *out, s); err != nil { return err } } else { out.RBD = nil } - // unable to generate simple pointer conversion for v1.ISCSIVolumeSource -> api.ISCSIVolumeSource if in.ISCSI != nil { - out.ISCSI = new(api.ISCSIVolumeSource) - if err := Convert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource(in.ISCSI, out.ISCSI, s); err != nil { + in, out := &in.ISCSI, &out.ISCSI + *out = new(api.ISCSIVolumeSource) + if err := Convert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource(*in, *out, s); err != nil { return err } } else { out.ISCSI = nil } - // unable to generate simple pointer conversion for v1.CinderVolumeSource -> api.CinderVolumeSource if in.Cinder != nil { - out.Cinder = new(api.CinderVolumeSource) - if err := Convert_v1_CinderVolumeSource_To_api_CinderVolumeSource(in.Cinder, out.Cinder, s); err != nil { + in, out := &in.Cinder, &out.Cinder + *out = new(api.CinderVolumeSource) + if err := Convert_v1_CinderVolumeSource_To_api_CinderVolumeSource(*in, *out, s); err != nil { return err } } else { out.Cinder = nil } - // unable to generate simple pointer conversion for v1.CephFSVolumeSource -> api.CephFSVolumeSource if in.CephFS != nil { - out.CephFS = new(api.CephFSVolumeSource) - if err := Convert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource(in.CephFS, out.CephFS, s); err != nil { + in, out := &in.CephFS, &out.CephFS + *out = new(api.CephFSVolumeSource) + if err := Convert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource(*in, *out, s); err != nil { return err } } else { out.CephFS = nil } - // unable to generate simple pointer conversion for v1.FCVolumeSource -> api.FCVolumeSource if in.FC != nil { - out.FC = new(api.FCVolumeSource) - if err := Convert_v1_FCVolumeSource_To_api_FCVolumeSource(in.FC, out.FC, s); err != nil { + in, out := &in.FC, &out.FC + *out = new(api.FCVolumeSource) + if err := Convert_v1_FCVolumeSource_To_api_FCVolumeSource(*in, *out, s); err != nil { return err } } else { out.FC = nil } - // unable to generate simple pointer conversion for v1.FlockerVolumeSource -> api.FlockerVolumeSource if in.Flocker != nil { - out.Flocker = new(api.FlockerVolumeSource) - if err := Convert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(in.Flocker, out.Flocker, s); err != nil { + in, out := &in.Flocker, &out.Flocker + *out = new(api.FlockerVolumeSource) + if err := Convert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(*in, *out, s); err != nil { return err } } else { out.Flocker = nil } - // unable to generate simple pointer conversion for v1.FlexVolumeSource -> api.FlexVolumeSource if in.FlexVolume != nil { - out.FlexVolume = new(api.FlexVolumeSource) - if err := Convert_v1_FlexVolumeSource_To_api_FlexVolumeSource(in.FlexVolume, out.FlexVolume, s); err != nil { + in, out := &in.FlexVolume, &out.FlexVolume + *out = new(api.FlexVolumeSource) + if err := Convert_v1_FlexVolumeSource_To_api_FlexVolumeSource(*in, *out, s); err != nil { return err } } else { out.FlexVolume = nil } - // unable to generate simple pointer conversion for v1.AzureFileVolumeSource -> api.AzureFileVolumeSource if in.AzureFile != nil { - out.AzureFile = new(api.AzureFileVolumeSource) - if err := Convert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource(in.AzureFile, out.AzureFile, s); err != nil { + in, out := &in.AzureFile, &out.AzureFile + *out = new(api.AzureFileVolumeSource) + if err := Convert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource(*in, *out, s); err != nil { return err } } else { @@ -5250,28 +4650,157 @@ func Convert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(in *Persist return autoConvert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(in, out, s) } +func autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *api.PersistentVolumeSource, out *PersistentVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PersistentVolumeSource))(in) + } + if in.GCEPersistentDisk != nil { + in, out := &in.GCEPersistentDisk, &out.GCEPersistentDisk + *out = new(GCEPersistentDiskVolumeSource) + if err := Convert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.GCEPersistentDisk = nil + } + if in.AWSElasticBlockStore != nil { + in, out := &in.AWSElasticBlockStore, &out.AWSElasticBlockStore + *out = new(AWSElasticBlockStoreVolumeSource) + if err := Convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.AWSElasticBlockStore = nil + } + if in.HostPath != nil { + in, out := &in.HostPath, &out.HostPath + *out = new(HostPathVolumeSource) + if err := Convert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.HostPath = nil + } + if in.Glusterfs != nil { + in, out := &in.Glusterfs, &out.Glusterfs + *out = new(GlusterfsVolumeSource) + if err := Convert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.Glusterfs = nil + } + if in.NFS != nil { + in, out := &in.NFS, &out.NFS + *out = new(NFSVolumeSource) + if err := Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.NFS = nil + } + if in.RBD != nil { + in, out := &in.RBD, &out.RBD + *out = new(RBDVolumeSource) + if err := Convert_api_RBDVolumeSource_To_v1_RBDVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.RBD = nil + } + if in.ISCSI != nil { + in, out := &in.ISCSI, &out.ISCSI + *out = new(ISCSIVolumeSource) + if err := Convert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.ISCSI = nil + } + if in.FlexVolume != nil { + in, out := &in.FlexVolume, &out.FlexVolume + *out = new(FlexVolumeSource) + if err := Convert_api_FlexVolumeSource_To_v1_FlexVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.FlexVolume = nil + } + if in.Cinder != nil { + in, out := &in.Cinder, &out.Cinder + *out = new(CinderVolumeSource) + if err := Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.Cinder = nil + } + if in.CephFS != nil { + in, out := &in.CephFS, &out.CephFS + *out = new(CephFSVolumeSource) + if err := Convert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.CephFS = nil + } + if in.FC != nil { + in, out := &in.FC, &out.FC + *out = new(FCVolumeSource) + if err := Convert_api_FCVolumeSource_To_v1_FCVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.FC = nil + } + if in.Flocker != nil { + in, out := &in.Flocker, &out.Flocker + *out = new(FlockerVolumeSource) + if err := Convert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.Flocker = nil + } + if in.AzureFile != nil { + in, out := &in.AzureFile, &out.AzureFile + *out = new(AzureFileVolumeSource) + if err := Convert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.AzureFile = nil + } + return nil +} + +func Convert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in *api.PersistentVolumeSource, out *PersistentVolumeSource, s conversion.Scope) error { + return autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(in, out, s) +} + func autoConvert_v1_PersistentVolumeSpec_To_api_PersistentVolumeSpec(in *PersistentVolumeSpec, out *api.PersistentVolumeSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PersistentVolumeSpec))(in) } - if err := s.Convert(&in.Capacity, &out.Capacity, 0); err != nil { + if err := Convert_v1_ResourceList_To_api_ResourceList(&in.Capacity, &out.Capacity, s); err != nil { return err } if err := Convert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource(&in.PersistentVolumeSource, &out.PersistentVolumeSource, s); err != nil { return err } if in.AccessModes != nil { - out.AccessModes = make([]api.PersistentVolumeAccessMode, len(in.AccessModes)) - for i := range in.AccessModes { - out.AccessModes[i] = api.PersistentVolumeAccessMode(in.AccessModes[i]) + in, out := &in.AccessModes, &out.AccessModes + *out = make([]api.PersistentVolumeAccessMode, len(*in)) + for i := range *in { + (*out)[i] = api.PersistentVolumeAccessMode((*in)[i]) } } else { out.AccessModes = nil } - // unable to generate simple pointer conversion for v1.ObjectReference -> api.ObjectReference if in.ClaimRef != nil { - out.ClaimRef = new(api.ObjectReference) - if err := Convert_v1_ObjectReference_To_api_ObjectReference(in.ClaimRef, out.ClaimRef, s); err != nil { + in, out := &in.ClaimRef, &out.ClaimRef + *out = new(api.ObjectReference) + if err := Convert_v1_ObjectReference_To_api_ObjectReference(*in, *out, s); err != nil { return err } } else { @@ -5285,6 +4814,52 @@ func Convert_v1_PersistentVolumeSpec_To_api_PersistentVolumeSpec(in *PersistentV return autoConvert_v1_PersistentVolumeSpec_To_api_PersistentVolumeSpec(in, out, s) } +func autoConvert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in *api.PersistentVolumeSpec, out *PersistentVolumeSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PersistentVolumeSpec))(in) + } + if in.Capacity != nil { + in, out := &in.Capacity, &out.Capacity + *out = make(ResourceList, len(*in)) + for key, val := range *in { + newVal := new(resource.Quantity) + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, newVal, s); err != nil { + return err + } + (*out)[ResourceName(key)] = *newVal + } + } else { + out.Capacity = nil + } + if err := Convert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource(&in.PersistentVolumeSource, &out.PersistentVolumeSource, s); err != nil { + return err + } + if in.AccessModes != nil { + in, out := &in.AccessModes, &out.AccessModes + *out = make([]PersistentVolumeAccessMode, len(*in)) + for i := range *in { + (*out)[i] = PersistentVolumeAccessMode((*in)[i]) + } + } else { + out.AccessModes = nil + } + if in.ClaimRef != nil { + in, out := &in.ClaimRef, &out.ClaimRef + *out = new(ObjectReference) + if err := Convert_api_ObjectReference_To_v1_ObjectReference(*in, *out, s); err != nil { + return err + } + } else { + out.ClaimRef = nil + } + out.PersistentVolumeReclaimPolicy = PersistentVolumeReclaimPolicy(in.PersistentVolumeReclaimPolicy) + return nil +} + +func Convert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in *api.PersistentVolumeSpec, out *PersistentVolumeSpec, s conversion.Scope) error { + return autoConvert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in, out, s) +} + func autoConvert_v1_PersistentVolumeStatus_To_api_PersistentVolumeStatus(in *PersistentVolumeStatus, out *api.PersistentVolumeStatus, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PersistentVolumeStatus))(in) @@ -5299,10 +4874,27 @@ func Convert_v1_PersistentVolumeStatus_To_api_PersistentVolumeStatus(in *Persist return autoConvert_v1_PersistentVolumeStatus_To_api_PersistentVolumeStatus(in, out, s) } +func autoConvert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(in *api.PersistentVolumeStatus, out *PersistentVolumeStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PersistentVolumeStatus))(in) + } + out.Phase = PersistentVolumePhase(in.Phase) + out.Message = in.Message + out.Reason = in.Reason + return nil +} + +func Convert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(in *api.PersistentVolumeStatus, out *PersistentVolumeStatus, s conversion.Scope) error { + return autoConvert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus(in, out, s) +} + func autoConvert_v1_Pod_To_api_Pod(in *Pod, out *api.Pod, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Pod))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } @@ -5315,10 +4907,32 @@ func autoConvert_v1_Pod_To_api_Pod(in *Pod, out *api.Pod, s conversion.Scope) er return nil } +func autoConvert_api_Pod_To_v1_Pod(in *api.Pod, out *Pod, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Pod))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_PodSpec_To_v1_PodSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_api_PodStatus_To_v1_PodStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + func autoConvert_v1_PodAttachOptions_To_api_PodAttachOptions(in *PodAttachOptions, out *api.PodAttachOptions, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PodAttachOptions))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } out.Stdin = in.Stdin out.Stdout = in.Stdout out.Stderr = in.Stderr @@ -5331,6 +4945,25 @@ func Convert_v1_PodAttachOptions_To_api_PodAttachOptions(in *PodAttachOptions, o return autoConvert_v1_PodAttachOptions_To_api_PodAttachOptions(in, out, s) } +func autoConvert_api_PodAttachOptions_To_v1_PodAttachOptions(in *api.PodAttachOptions, out *PodAttachOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PodAttachOptions))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + out.Stdin = in.Stdin + out.Stdout = in.Stdout + out.Stderr = in.Stderr + out.TTY = in.TTY + out.Container = in.Container + return nil +} + +func Convert_api_PodAttachOptions_To_v1_PodAttachOptions(in *api.PodAttachOptions, out *PodAttachOptions, s conversion.Scope) error { + return autoConvert_api_PodAttachOptions_To_v1_PodAttachOptions(in, out, s) +} + func autoConvert_v1_PodCondition_To_api_PodCondition(in *PodCondition, out *api.PodCondition, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PodCondition))(in) @@ -5352,20 +4985,43 @@ func Convert_v1_PodCondition_To_api_PodCondition(in *PodCondition, out *api.PodC return autoConvert_v1_PodCondition_To_api_PodCondition(in, out, s) } +func autoConvert_api_PodCondition_To_v1_PodCondition(in *api.PodCondition, out *PodCondition, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PodCondition))(in) + } + out.Type = PodConditionType(in.Type) + out.Status = ConditionStatus(in.Status) + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastProbeTime, &out.LastProbeTime, s); err != nil { + return err + } + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastTransitionTime, &out.LastTransitionTime, s); err != nil { + return err + } + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +func Convert_api_PodCondition_To_v1_PodCondition(in *api.PodCondition, out *PodCondition, s conversion.Scope) error { + return autoConvert_api_PodCondition_To_v1_PodCondition(in, out, s) +} + func autoConvert_v1_PodExecOptions_To_api_PodExecOptions(in *PodExecOptions, out *api.PodExecOptions, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PodExecOptions))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } out.Stdin = in.Stdin out.Stdout = in.Stdout out.Stderr = in.Stderr out.TTY = in.TTY out.Container = in.Container if in.Command != nil { - out.Command = make([]string, len(in.Command)) - for i := range in.Command { - out.Command[i] = in.Command[i] - } + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) } else { out.Command = nil } @@ -5376,17 +5032,47 @@ func Convert_v1_PodExecOptions_To_api_PodExecOptions(in *PodExecOptions, out *ap return autoConvert_v1_PodExecOptions_To_api_PodExecOptions(in, out, s) } +func autoConvert_api_PodExecOptions_To_v1_PodExecOptions(in *api.PodExecOptions, out *PodExecOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PodExecOptions))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + out.Stdin = in.Stdin + out.Stdout = in.Stdout + out.Stderr = in.Stderr + out.TTY = in.TTY + out.Container = in.Container + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } else { + out.Command = nil + } + return nil +} + +func Convert_api_PodExecOptions_To_v1_PodExecOptions(in *api.PodExecOptions, out *PodExecOptions, s conversion.Scope) error { + return autoConvert_api_PodExecOptions_To_v1_PodExecOptions(in, out, s) +} + func autoConvert_v1_PodList_To_api_PodList(in *PodList, out *api.PodList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PodList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.Pod, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_Pod_To_api_Pod(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.Pod, len(*in)) + for i := range *in { + if err := Convert_v1_Pod_To_api_Pod(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -5400,23 +5086,55 @@ func Convert_v1_PodList_To_api_PodList(in *PodList, out *api.PodList, s conversi return autoConvert_v1_PodList_To_api_PodList(in, out, s) } +func autoConvert_api_PodList_To_v1_PodList(in *api.PodList, out *PodList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PodList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Pod, len(*in)) + for i := range *in { + if err := Convert_api_Pod_To_v1_Pod(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_PodList_To_v1_PodList(in *api.PodList, out *PodList, s conversion.Scope) error { + return autoConvert_api_PodList_To_v1_PodList(in, out, s) +} + func autoConvert_v1_PodLogOptions_To_api_PodLogOptions(in *PodLogOptions, out *api.PodLogOptions, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PodLogOptions))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } out.Container = in.Container out.Follow = in.Follow out.Previous = in.Previous if in.SinceSeconds != nil { - out.SinceSeconds = new(int64) - *out.SinceSeconds = *in.SinceSeconds + in, out := &in.SinceSeconds, &out.SinceSeconds + *out = new(int64) + **out = **in } else { out.SinceSeconds = nil } - // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time if in.SinceTime != nil { - out.SinceTime = new(unversioned.Time) - if err := api.Convert_unversioned_Time_To_unversioned_Time(in.SinceTime, out.SinceTime, s); err != nil { + in, out := &in.SinceTime, &out.SinceTime + *out = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(*in, *out, s); err != nil { return err } } else { @@ -5424,14 +5142,16 @@ func autoConvert_v1_PodLogOptions_To_api_PodLogOptions(in *PodLogOptions, out *a } out.Timestamps = in.Timestamps if in.TailLines != nil { - out.TailLines = new(int64) - *out.TailLines = *in.TailLines + in, out := &in.TailLines, &out.TailLines + *out = new(int64) + **out = **in } else { out.TailLines = nil } if in.LimitBytes != nil { - out.LimitBytes = new(int64) - *out.LimitBytes = *in.LimitBytes + in, out := &in.LimitBytes, &out.LimitBytes + *out = new(int64) + **out = **in } else { out.LimitBytes = nil } @@ -5442,10 +5162,61 @@ func Convert_v1_PodLogOptions_To_api_PodLogOptions(in *PodLogOptions, out *api.P return autoConvert_v1_PodLogOptions_To_api_PodLogOptions(in, out, s) } +func autoConvert_api_PodLogOptions_To_v1_PodLogOptions(in *api.PodLogOptions, out *PodLogOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PodLogOptions))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + out.Container = in.Container + out.Follow = in.Follow + out.Previous = in.Previous + if in.SinceSeconds != nil { + in, out := &in.SinceSeconds, &out.SinceSeconds + *out = new(int64) + **out = **in + } else { + out.SinceSeconds = nil + } + if in.SinceTime != nil { + in, out := &in.SinceTime, &out.SinceTime + *out = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(*in, *out, s); err != nil { + return err + } + } else { + out.SinceTime = nil + } + out.Timestamps = in.Timestamps + if in.TailLines != nil { + in, out := &in.TailLines, &out.TailLines + *out = new(int64) + **out = **in + } else { + out.TailLines = nil + } + if in.LimitBytes != nil { + in, out := &in.LimitBytes, &out.LimitBytes + *out = new(int64) + **out = **in + } else { + out.LimitBytes = nil + } + return nil +} + +func Convert_api_PodLogOptions_To_v1_PodLogOptions(in *api.PodLogOptions, out *PodLogOptions, s conversion.Scope) error { + return autoConvert_api_PodLogOptions_To_v1_PodLogOptions(in, out, s) +} + func autoConvert_v1_PodProxyOptions_To_api_PodProxyOptions(in *PodProxyOptions, out *api.PodProxyOptions, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PodProxyOptions))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } out.Path = in.Path return nil } @@ -5454,14 +5225,74 @@ func Convert_v1_PodProxyOptions_To_api_PodProxyOptions(in *PodProxyOptions, out return autoConvert_v1_PodProxyOptions_To_api_PodProxyOptions(in, out, s) } -func autoConvert_v1_PodSpec_To_api_PodSpec(in *PodSpec, out *api.PodSpec, s conversion.Scope) error { +func autoConvert_api_PodProxyOptions_To_v1_PodProxyOptions(in *api.PodProxyOptions, out *PodProxyOptions, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*PodSpec))(in) + defaulting.(func(*api.PodProxyOptions))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + out.Path = in.Path + return nil +} + +func Convert_api_PodProxyOptions_To_v1_PodProxyOptions(in *api.PodProxyOptions, out *PodProxyOptions, s conversion.Scope) error { + return autoConvert_api_PodProxyOptions_To_v1_PodProxyOptions(in, out, s) +} + +func autoConvert_v1_PodSecurityContext_To_api_PodSecurityContext(in *PodSecurityContext, out *api.PodSecurityContext, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*PodSecurityContext))(in) + } + if in.SELinuxOptions != nil { + in, out := &in.SELinuxOptions, &out.SELinuxOptions + *out = new(api.SELinuxOptions) + if err := Convert_v1_SELinuxOptions_To_api_SELinuxOptions(*in, *out, s); err != nil { + return err + } + } else { + out.SELinuxOptions = nil + } + if in.RunAsUser != nil { + in, out := &in.RunAsUser, &out.RunAsUser + *out = new(int64) + **out = **in + } else { + out.RunAsUser = nil + } + if in.RunAsNonRoot != nil { + in, out := &in.RunAsNonRoot, &out.RunAsNonRoot + *out = new(bool) + **out = **in + } else { + out.RunAsNonRoot = nil + } + if in.SupplementalGroups != nil { + in, out := &in.SupplementalGroups, &out.SupplementalGroups + *out = make([]int64, len(*in)) + copy(*out, *in) + } else { + out.SupplementalGroups = nil + } + if in.FSGroup != nil { + in, out := &in.FSGroup, &out.FSGroup + *out = new(int64) + **out = **in + } else { + out.FSGroup = nil + } + return nil +} + +func autoConvert_api_PodSpec_To_v1_PodSpec(in *api.PodSpec, out *PodSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PodSpec))(in) } if in.Volumes != nil { - out.Volumes = make([]api.Volume, len(in.Volumes)) - for i := range in.Volumes { - if err := Convert_v1_Volume_To_api_Volume(&in.Volumes[i], &out.Volumes[i], s); err != nil { + in, out := &in.Volumes, &out.Volumes + *out = make([]Volume, len(*in)) + for i := range *in { + if err := Convert_api_Volume_To_v1_Volume(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -5469,55 +5300,57 @@ func autoConvert_v1_PodSpec_To_api_PodSpec(in *PodSpec, out *api.PodSpec, s conv out.Volumes = nil } if in.Containers != nil { - out.Containers = make([]api.Container, len(in.Containers)) - for i := range in.Containers { - if err := Convert_v1_Container_To_api_Container(&in.Containers[i], &out.Containers[i], s); err != nil { + in, out := &in.Containers, &out.Containers + *out = make([]Container, len(*in)) + for i := range *in { + if err := Convert_api_Container_To_v1_Container(&(*in)[i], &(*out)[i], s); err != nil { return err } } } else { out.Containers = nil } - out.RestartPolicy = api.RestartPolicy(in.RestartPolicy) + out.RestartPolicy = RestartPolicy(in.RestartPolicy) if in.TerminationGracePeriodSeconds != nil { - out.TerminationGracePeriodSeconds = new(int64) - *out.TerminationGracePeriodSeconds = *in.TerminationGracePeriodSeconds + in, out := &in.TerminationGracePeriodSeconds, &out.TerminationGracePeriodSeconds + *out = new(int64) + **out = **in } else { out.TerminationGracePeriodSeconds = nil } if in.ActiveDeadlineSeconds != nil { - out.ActiveDeadlineSeconds = new(int64) - *out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds + in, out := &in.ActiveDeadlineSeconds, &out.ActiveDeadlineSeconds + *out = new(int64) + **out = **in } else { out.ActiveDeadlineSeconds = nil } - out.DNSPolicy = api.DNSPolicy(in.DNSPolicy) + out.DNSPolicy = DNSPolicy(in.DNSPolicy) if in.NodeSelector != nil { - out.NodeSelector = make(map[string]string) - for key, val := range in.NodeSelector { - out.NodeSelector[key] = val + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } else { out.NodeSelector = nil } out.ServiceAccountName = in.ServiceAccountName - // in.DeprecatedServiceAccount has no peer in out out.NodeName = in.NodeName - // in.HostNetwork has no peer in out - // in.HostPID has no peer in out - // in.HostIPC has no peer in out - // unable to generate simple pointer conversion for v1.PodSecurityContext -> api.PodSecurityContext if in.SecurityContext != nil { - if err := s.Convert(&in.SecurityContext, &out.SecurityContext, 0); err != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(PodSecurityContext) + if err := Convert_api_PodSecurityContext_To_v1_PodSecurityContext(*in, *out, s); err != nil { return err } } else { out.SecurityContext = nil } if in.ImagePullSecrets != nil { - out.ImagePullSecrets = make([]api.LocalObjectReference, len(in.ImagePullSecrets)) - for i := range in.ImagePullSecrets { - if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(&in.ImagePullSecrets[i], &out.ImagePullSecrets[i], s); err != nil { + in, out := &in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]LocalObjectReference, len(*in)) + for i := range *in { + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -5533,9 +5366,10 @@ func autoConvert_v1_PodStatus_To_api_PodStatus(in *PodStatus, out *api.PodStatus } out.Phase = api.PodPhase(in.Phase) if in.Conditions != nil { - out.Conditions = make([]api.PodCondition, len(in.Conditions)) - for i := range in.Conditions { - if err := Convert_v1_PodCondition_To_api_PodCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]api.PodCondition, len(*in)) + for i := range *in { + if err := Convert_v1_PodCondition_To_api_PodCondition(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -5546,19 +5380,20 @@ func autoConvert_v1_PodStatus_To_api_PodStatus(in *PodStatus, out *api.PodStatus out.Reason = in.Reason out.HostIP = in.HostIP out.PodIP = in.PodIP - // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time if in.StartTime != nil { - out.StartTime = new(unversioned.Time) - if err := api.Convert_unversioned_Time_To_unversioned_Time(in.StartTime, out.StartTime, s); err != nil { + in, out := &in.StartTime, &out.StartTime + *out = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(*in, *out, s); err != nil { return err } } else { out.StartTime = nil } if in.ContainerStatuses != nil { - out.ContainerStatuses = make([]api.ContainerStatus, len(in.ContainerStatuses)) - for i := range in.ContainerStatuses { - if err := Convert_v1_ContainerStatus_To_api_ContainerStatus(&in.ContainerStatuses[i], &out.ContainerStatuses[i], s); err != nil { + in, out := &in.ContainerStatuses, &out.ContainerStatuses + *out = make([]api.ContainerStatus, len(*in)) + for i := range *in { + if err := Convert_v1_ContainerStatus_To_api_ContainerStatus(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -5572,10 +5407,60 @@ func Convert_v1_PodStatus_To_api_PodStatus(in *PodStatus, out *api.PodStatus, s return autoConvert_v1_PodStatus_To_api_PodStatus(in, out, s) } +func autoConvert_api_PodStatus_To_v1_PodStatus(in *api.PodStatus, out *PodStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PodStatus))(in) + } + out.Phase = PodPhase(in.Phase) + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]PodCondition, len(*in)) + for i := range *in { + if err := Convert_api_PodCondition_To_v1_PodCondition(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Conditions = nil + } + out.Message = in.Message + out.Reason = in.Reason + out.HostIP = in.HostIP + out.PodIP = in.PodIP + if in.StartTime != nil { + in, out := &in.StartTime, &out.StartTime + *out = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(*in, *out, s); err != nil { + return err + } + } else { + out.StartTime = nil + } + if in.ContainerStatuses != nil { + in, out := &in.ContainerStatuses, &out.ContainerStatuses + *out = make([]ContainerStatus, len(*in)) + for i := range *in { + if err := Convert_api_ContainerStatus_To_v1_ContainerStatus(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.ContainerStatuses = nil + } + return nil +} + +func Convert_api_PodStatus_To_v1_PodStatus(in *api.PodStatus, out *PodStatus, s conversion.Scope) error { + return autoConvert_api_PodStatus_To_v1_PodStatus(in, out, s) +} + func autoConvert_v1_PodStatusResult_To_api_PodStatusResult(in *PodStatusResult, out *api.PodStatusResult, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PodStatusResult))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } @@ -5589,10 +5474,33 @@ func Convert_v1_PodStatusResult_To_api_PodStatusResult(in *PodStatusResult, out return autoConvert_v1_PodStatusResult_To_api_PodStatusResult(in, out, s) } +func autoConvert_api_PodStatusResult_To_v1_PodStatusResult(in *api.PodStatusResult, out *PodStatusResult, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PodStatusResult))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_PodStatus_To_v1_PodStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_api_PodStatusResult_To_v1_PodStatusResult(in *api.PodStatusResult, out *PodStatusResult, s conversion.Scope) error { + return autoConvert_api_PodStatusResult_To_v1_PodStatusResult(in, out, s) +} + func autoConvert_v1_PodTemplate_To_api_PodTemplate(in *PodTemplate, out *api.PodTemplate, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PodTemplate))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } @@ -5606,17 +5514,41 @@ func Convert_v1_PodTemplate_To_api_PodTemplate(in *PodTemplate, out *api.PodTemp return autoConvert_v1_PodTemplate_To_api_PodTemplate(in, out, s) } +func autoConvert_api_PodTemplate_To_v1_PodTemplate(in *api.PodTemplate, out *PodTemplate, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PodTemplate))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + return nil +} + +func Convert_api_PodTemplate_To_v1_PodTemplate(in *api.PodTemplate, out *PodTemplate, s conversion.Scope) error { + return autoConvert_api_PodTemplate_To_v1_PodTemplate(in, out, s) +} + func autoConvert_v1_PodTemplateList_To_api_PodTemplateList(in *PodTemplateList, out *api.PodTemplateList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PodTemplateList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.PodTemplate, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_PodTemplate_To_api_PodTemplate(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.PodTemplate, len(*in)) + for i := range *in { + if err := Convert_v1_PodTemplate_To_api_PodTemplate(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -5630,6 +5562,34 @@ func Convert_v1_PodTemplateList_To_api_PodTemplateList(in *PodTemplateList, out return autoConvert_v1_PodTemplateList_To_api_PodTemplateList(in, out, s) } +func autoConvert_api_PodTemplateList_To_v1_PodTemplateList(in *api.PodTemplateList, out *PodTemplateList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PodTemplateList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PodTemplate, len(*in)) + for i := range *in { + if err := Convert_api_PodTemplate_To_v1_PodTemplate(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_PodTemplateList_To_v1_PodTemplateList(in *api.PodTemplateList, out *PodTemplateList, s conversion.Scope) error { + return autoConvert_api_PodTemplateList_To_v1_PodTemplateList(in, out, s) +} + func autoConvert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in *PodTemplateSpec, out *api.PodTemplateSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PodTemplateSpec))(in) @@ -5647,6 +5607,89 @@ func Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in *PodTemplateSpec, out return autoConvert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in, out, s) } +func autoConvert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in *api.PodTemplateSpec, out *PodTemplateSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PodTemplateSpec))(in) + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_PodSpec_To_v1_PodSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + return nil +} + +func Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in *api.PodTemplateSpec, out *PodTemplateSpec, s conversion.Scope) error { + return autoConvert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in, out, s) +} + +func autoConvert_v1_Preconditions_To_api_Preconditions(in *Preconditions, out *api.Preconditions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*Preconditions))(in) + } + if in.UID != nil { + in, out := &in.UID, &out.UID + *out = new(types.UID) + **out = **in + } else { + out.UID = nil + } + return nil +} + +func Convert_v1_Preconditions_To_api_Preconditions(in *Preconditions, out *api.Preconditions, s conversion.Scope) error { + return autoConvert_v1_Preconditions_To_api_Preconditions(in, out, s) +} + +func autoConvert_api_Preconditions_To_v1_Preconditions(in *api.Preconditions, out *Preconditions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Preconditions))(in) + } + if in.UID != nil { + in, out := &in.UID, &out.UID + *out = new(types.UID) + **out = **in + } else { + out.UID = nil + } + return nil +} + +func Convert_api_Preconditions_To_v1_Preconditions(in *api.Preconditions, out *Preconditions, s conversion.Scope) error { + return autoConvert_api_Preconditions_To_v1_Preconditions(in, out, s) +} + +func autoConvert_v1_PreferredSchedulingTerm_To_api_PreferredSchedulingTerm(in *PreferredSchedulingTerm, out *api.PreferredSchedulingTerm, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*PreferredSchedulingTerm))(in) + } + out.Weight = int(in.Weight) + if err := Convert_v1_NodeSelectorTerm_To_api_NodeSelectorTerm(&in.Preference, &out.Preference, s); err != nil { + return err + } + return nil +} + +func Convert_v1_PreferredSchedulingTerm_To_api_PreferredSchedulingTerm(in *PreferredSchedulingTerm, out *api.PreferredSchedulingTerm, s conversion.Scope) error { + return autoConvert_v1_PreferredSchedulingTerm_To_api_PreferredSchedulingTerm(in, out, s) +} + +func autoConvert_api_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm(in *api.PreferredSchedulingTerm, out *PreferredSchedulingTerm, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PreferredSchedulingTerm))(in) + } + out.Weight = int32(in.Weight) + if err := Convert_api_NodeSelectorTerm_To_v1_NodeSelectorTerm(&in.Preference, &out.Preference, s); err != nil { + return err + } + return nil +} + +func Convert_api_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm(in *api.PreferredSchedulingTerm, out *PreferredSchedulingTerm, s conversion.Scope) error { + return autoConvert_api_PreferredSchedulingTerm_To_v1_PreferredSchedulingTerm(in, out, s) +} + func autoConvert_v1_Probe_To_api_Probe(in *Probe, out *api.Probe, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Probe))(in) @@ -5666,15 +5709,33 @@ func Convert_v1_Probe_To_api_Probe(in *Probe, out *api.Probe, s conversion.Scope return autoConvert_v1_Probe_To_api_Probe(in, out, s) } +func autoConvert_api_Probe_To_v1_Probe(in *api.Probe, out *Probe, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Probe))(in) + } + if err := Convert_api_Handler_To_v1_Handler(&in.Handler, &out.Handler, s); err != nil { + return err + } + out.InitialDelaySeconds = int32(in.InitialDelaySeconds) + out.TimeoutSeconds = int32(in.TimeoutSeconds) + out.PeriodSeconds = int32(in.PeriodSeconds) + out.SuccessThreshold = int32(in.SuccessThreshold) + out.FailureThreshold = int32(in.FailureThreshold) + return nil +} + +func Convert_api_Probe_To_v1_Probe(in *api.Probe, out *Probe, s conversion.Scope) error { + return autoConvert_api_Probe_To_v1_Probe(in, out, s) +} + func autoConvert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in *RBDVolumeSource, out *api.RBDVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*RBDVolumeSource))(in) } if in.CephMonitors != nil { - out.CephMonitors = make([]string, len(in.CephMonitors)) - for i := range in.CephMonitors { - out.CephMonitors[i] = in.CephMonitors[i] - } + in, out := &in.CephMonitors, &out.CephMonitors + *out = make([]string, len(*in)) + copy(*out, *in) } else { out.CephMonitors = nil } @@ -5683,10 +5744,10 @@ func autoConvert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in *RBDVolumeSource, out.RBDPool = in.RBDPool out.RadosUser = in.RadosUser out.Keyring = in.Keyring - // unable to generate simple pointer conversion for v1.LocalObjectReference -> api.LocalObjectReference if in.SecretRef != nil { - out.SecretRef = new(api.LocalObjectReference) - if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(in.SecretRef, out.SecretRef, s); err != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(api.LocalObjectReference) + if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(*in, *out, s); err != nil { return err } } else { @@ -5700,15 +5761,51 @@ func Convert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in *RBDVolumeSource, out return autoConvert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in, out, s) } +func autoConvert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in *api.RBDVolumeSource, out *RBDVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.RBDVolumeSource))(in) + } + if in.CephMonitors != nil { + in, out := &in.CephMonitors, &out.CephMonitors + *out = make([]string, len(*in)) + copy(*out, *in) + } else { + out.CephMonitors = nil + } + out.RBDImage = in.RBDImage + out.FSType = in.FSType + out.RBDPool = in.RBDPool + out.RadosUser = in.RadosUser + out.Keyring = in.Keyring + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(LocalObjectReference) + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(*in, *out, s); err != nil { + return err + } + } else { + out.SecretRef = nil + } + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in *api.RBDVolumeSource, out *RBDVolumeSource, s conversion.Scope) error { + return autoConvert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in, out, s) +} + func autoConvert_v1_RangeAllocation_To_api_RangeAllocation(in *RangeAllocation, out *api.RangeAllocation, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*RangeAllocation))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } out.Range = in.Range - if err := conversion.ByteSliceCopy(&in.Data, &out.Data, s); err != nil { + if err := conversion.Convert_Slice_byte_To_Slice_byte(&in.Data, &out.Data, s); err != nil { return err } return nil @@ -5718,10 +5815,34 @@ func Convert_v1_RangeAllocation_To_api_RangeAllocation(in *RangeAllocation, out return autoConvert_v1_RangeAllocation_To_api_RangeAllocation(in, out, s) } +func autoConvert_api_RangeAllocation_To_v1_RangeAllocation(in *api.RangeAllocation, out *RangeAllocation, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.RangeAllocation))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + out.Range = in.Range + if err := conversion.Convert_Slice_byte_To_Slice_byte(&in.Data, &out.Data, s); err != nil { + return err + } + return nil +} + +func Convert_api_RangeAllocation_To_v1_RangeAllocation(in *api.RangeAllocation, out *RangeAllocation, s conversion.Scope) error { + return autoConvert_api_RangeAllocation_To_v1_RangeAllocation(in, out, s) +} + func autoConvert_v1_ReplicationController_To_api_ReplicationController(in *ReplicationController, out *api.ReplicationController, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ReplicationController))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } @@ -5738,17 +5859,44 @@ func Convert_v1_ReplicationController_To_api_ReplicationController(in *Replicati return autoConvert_v1_ReplicationController_To_api_ReplicationController(in, out, s) } +func autoConvert_api_ReplicationController_To_v1_ReplicationController(in *api.ReplicationController, out *ReplicationController, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ReplicationController))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_ReplicationControllerSpec_To_v1_ReplicationControllerSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_api_ReplicationController_To_v1_ReplicationController(in *api.ReplicationController, out *ReplicationController, s conversion.Scope) error { + return autoConvert_api_ReplicationController_To_v1_ReplicationController(in, out, s) +} + func autoConvert_v1_ReplicationControllerList_To_api_ReplicationControllerList(in *ReplicationControllerList, out *api.ReplicationControllerList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ReplicationControllerList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.ReplicationController, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_ReplicationController_To_api_ReplicationController(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.ReplicationController, len(*in)) + for i := range *in { + if err := Convert_v1_ReplicationController_To_api_ReplicationController(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -5762,36 +5910,40 @@ func Convert_v1_ReplicationControllerList_To_api_ReplicationControllerList(in *R return autoConvert_v1_ReplicationControllerList_To_api_ReplicationControllerList(in, out, s) } -func autoConvert_v1_ReplicationControllerSpec_To_api_ReplicationControllerSpec(in *ReplicationControllerSpec, out *api.ReplicationControllerSpec, s conversion.Scope) error { +func autoConvert_api_ReplicationControllerList_To_v1_ReplicationControllerList(in *api.ReplicationControllerList, out *ReplicationControllerList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*ReplicationControllerSpec))(in) + defaulting.(func(*api.ReplicationControllerList))(in) } - // in.Replicas has no peer in out - if in.Selector != nil { - out.Selector = make(map[string]string) - for key, val := range in.Selector { - out.Selector[key] = val + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ReplicationController, len(*in)) + for i := range *in { + if err := Convert_api_ReplicationController_To_v1_ReplicationController(&(*in)[i], &(*out)[i], s); err != nil { + return err + } } } else { - out.Selector = nil - } - // unable to generate simple pointer conversion for v1.PodTemplateSpec -> api.PodTemplateSpec - if in.Template != nil { - out.Template = new(api.PodTemplateSpec) - if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in.Template, out.Template, s); err != nil { - return err - } - } else { - out.Template = nil + out.Items = nil } return nil } +func Convert_api_ReplicationControllerList_To_v1_ReplicationControllerList(in *api.ReplicationControllerList, out *ReplicationControllerList, s conversion.Scope) error { + return autoConvert_api_ReplicationControllerList_To_v1_ReplicationControllerList(in, out, s) +} + func autoConvert_v1_ReplicationControllerStatus_To_api_ReplicationControllerStatus(in *ReplicationControllerStatus, out *api.ReplicationControllerStatus, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ReplicationControllerStatus))(in) } out.Replicas = int(in.Replicas) + out.FullyLabeledReplicas = int(in.FullyLabeledReplicas) out.ObservedGeneration = in.ObservedGeneration return nil } @@ -5800,10 +5952,27 @@ func Convert_v1_ReplicationControllerStatus_To_api_ReplicationControllerStatus(i return autoConvert_v1_ReplicationControllerStatus_To_api_ReplicationControllerStatus(in, out, s) } +func autoConvert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(in *api.ReplicationControllerStatus, out *ReplicationControllerStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ReplicationControllerStatus))(in) + } + out.Replicas = int32(in.Replicas) + out.FullyLabeledReplicas = int32(in.FullyLabeledReplicas) + out.ObservedGeneration = in.ObservedGeneration + return nil +} + +func Convert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(in *api.ReplicationControllerStatus, out *ReplicationControllerStatus, s conversion.Scope) error { + return autoConvert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus(in, out, s) +} + func autoConvert_v1_ResourceQuota_To_api_ResourceQuota(in *ResourceQuota, out *api.ResourceQuota, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ResourceQuota))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } @@ -5820,17 +5989,44 @@ func Convert_v1_ResourceQuota_To_api_ResourceQuota(in *ResourceQuota, out *api.R return autoConvert_v1_ResourceQuota_To_api_ResourceQuota(in, out, s) } +func autoConvert_api_ResourceQuota_To_v1_ResourceQuota(in *api.ResourceQuota, out *ResourceQuota, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ResourceQuota))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_api_ResourceQuota_To_v1_ResourceQuota(in *api.ResourceQuota, out *ResourceQuota, s conversion.Scope) error { + return autoConvert_api_ResourceQuota_To_v1_ResourceQuota(in, out, s) +} + func autoConvert_v1_ResourceQuotaList_To_api_ResourceQuotaList(in *ResourceQuotaList, out *api.ResourceQuotaList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ResourceQuotaList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.ResourceQuota, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_ResourceQuota_To_api_ResourceQuota(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.ResourceQuota, len(*in)) + for i := range *in { + if err := Convert_v1_ResourceQuota_To_api_ResourceQuota(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -5844,13 +6040,50 @@ func Convert_v1_ResourceQuotaList_To_api_ResourceQuotaList(in *ResourceQuotaList return autoConvert_v1_ResourceQuotaList_To_api_ResourceQuotaList(in, out, s) } +func autoConvert_api_ResourceQuotaList_To_v1_ResourceQuotaList(in *api.ResourceQuotaList, out *ResourceQuotaList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ResourceQuotaList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ResourceQuota, len(*in)) + for i := range *in { + if err := Convert_api_ResourceQuota_To_v1_ResourceQuota(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_ResourceQuotaList_To_v1_ResourceQuotaList(in *api.ResourceQuotaList, out *ResourceQuotaList, s conversion.Scope) error { + return autoConvert_api_ResourceQuotaList_To_v1_ResourceQuotaList(in, out, s) +} + func autoConvert_v1_ResourceQuotaSpec_To_api_ResourceQuotaSpec(in *ResourceQuotaSpec, out *api.ResourceQuotaSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ResourceQuotaSpec))(in) } - if err := s.Convert(&in.Hard, &out.Hard, 0); err != nil { + if err := Convert_v1_ResourceList_To_api_ResourceList(&in.Hard, &out.Hard, s); err != nil { return err } + if in.Scopes != nil { + in, out := &in.Scopes, &out.Scopes + *out = make([]api.ResourceQuotaScope, len(*in)) + for i := range *in { + (*out)[i] = api.ResourceQuotaScope((*in)[i]) + } + } else { + out.Scopes = nil + } return nil } @@ -5858,14 +6091,47 @@ func Convert_v1_ResourceQuotaSpec_To_api_ResourceQuotaSpec(in *ResourceQuotaSpec return autoConvert_v1_ResourceQuotaSpec_To_api_ResourceQuotaSpec(in, out, s) } +func autoConvert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in *api.ResourceQuotaSpec, out *ResourceQuotaSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ResourceQuotaSpec))(in) + } + if in.Hard != nil { + in, out := &in.Hard, &out.Hard + *out = make(ResourceList, len(*in)) + for key, val := range *in { + newVal := new(resource.Quantity) + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, newVal, s); err != nil { + return err + } + (*out)[ResourceName(key)] = *newVal + } + } else { + out.Hard = nil + } + if in.Scopes != nil { + in, out := &in.Scopes, &out.Scopes + *out = make([]ResourceQuotaScope, len(*in)) + for i := range *in { + (*out)[i] = ResourceQuotaScope((*in)[i]) + } + } else { + out.Scopes = nil + } + return nil +} + +func Convert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in *api.ResourceQuotaSpec, out *ResourceQuotaSpec, s conversion.Scope) error { + return autoConvert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in, out, s) +} + func autoConvert_v1_ResourceQuotaStatus_To_api_ResourceQuotaStatus(in *ResourceQuotaStatus, out *api.ResourceQuotaStatus, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ResourceQuotaStatus))(in) } - if err := s.Convert(&in.Hard, &out.Hard, 0); err != nil { + if err := Convert_v1_ResourceList_To_api_ResourceList(&in.Hard, &out.Hard, s); err != nil { return err } - if err := s.Convert(&in.Used, &out.Used, 0); err != nil { + if err := Convert_v1_ResourceList_To_api_ResourceList(&in.Used, &out.Used, s); err != nil { return err } return nil @@ -5875,14 +6141,51 @@ func Convert_v1_ResourceQuotaStatus_To_api_ResourceQuotaStatus(in *ResourceQuota return autoConvert_v1_ResourceQuotaStatus_To_api_ResourceQuotaStatus(in, out, s) } +func autoConvert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(in *api.ResourceQuotaStatus, out *ResourceQuotaStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ResourceQuotaStatus))(in) + } + if in.Hard != nil { + in, out := &in.Hard, &out.Hard + *out = make(ResourceList, len(*in)) + for key, val := range *in { + newVal := new(resource.Quantity) + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, newVal, s); err != nil { + return err + } + (*out)[ResourceName(key)] = *newVal + } + } else { + out.Hard = nil + } + if in.Used != nil { + in, out := &in.Used, &out.Used + *out = make(ResourceList, len(*in)) + for key, val := range *in { + newVal := new(resource.Quantity) + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, newVal, s); err != nil { + return err + } + (*out)[ResourceName(key)] = *newVal + } + } else { + out.Used = nil + } + return nil +} + +func Convert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(in *api.ResourceQuotaStatus, out *ResourceQuotaStatus, s conversion.Scope) error { + return autoConvert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(in, out, s) +} + func autoConvert_v1_ResourceRequirements_To_api_ResourceRequirements(in *ResourceRequirements, out *api.ResourceRequirements, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ResourceRequirements))(in) } - if err := s.Convert(&in.Limits, &out.Limits, 0); err != nil { + if err := Convert_v1_ResourceList_To_api_ResourceList(&in.Limits, &out.Limits, s); err != nil { return err } - if err := s.Convert(&in.Requests, &out.Requests, 0); err != nil { + if err := Convert_v1_ResourceList_To_api_ResourceList(&in.Requests, &out.Requests, s); err != nil { return err } return nil @@ -5892,6 +6195,43 @@ func Convert_v1_ResourceRequirements_To_api_ResourceRequirements(in *ResourceReq return autoConvert_v1_ResourceRequirements_To_api_ResourceRequirements(in, out, s) } +func autoConvert_api_ResourceRequirements_To_v1_ResourceRequirements(in *api.ResourceRequirements, out *ResourceRequirements, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ResourceRequirements))(in) + } + if in.Limits != nil { + in, out := &in.Limits, &out.Limits + *out = make(ResourceList, len(*in)) + for key, val := range *in { + newVal := new(resource.Quantity) + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, newVal, s); err != nil { + return err + } + (*out)[ResourceName(key)] = *newVal + } + } else { + out.Limits = nil + } + if in.Requests != nil { + in, out := &in.Requests, &out.Requests + *out = make(ResourceList, len(*in)) + for key, val := range *in { + newVal := new(resource.Quantity) + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, newVal, s); err != nil { + return err + } + (*out)[ResourceName(key)] = *newVal + } + } else { + out.Requests = nil + } + return nil +} + +func Convert_api_ResourceRequirements_To_v1_ResourceRequirements(in *api.ResourceRequirements, out *ResourceRequirements, s conversion.Scope) error { + return autoConvert_api_ResourceRequirements_To_v1_ResourceRequirements(in, out, s) +} + func autoConvert_v1_SELinuxOptions_To_api_SELinuxOptions(in *SELinuxOptions, out *api.SELinuxOptions, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*SELinuxOptions))(in) @@ -5907,21 +6247,40 @@ func Convert_v1_SELinuxOptions_To_api_SELinuxOptions(in *SELinuxOptions, out *ap return autoConvert_v1_SELinuxOptions_To_api_SELinuxOptions(in, out, s) } +func autoConvert_api_SELinuxOptions_To_v1_SELinuxOptions(in *api.SELinuxOptions, out *SELinuxOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.SELinuxOptions))(in) + } + out.User = in.User + out.Role = in.Role + out.Type = in.Type + out.Level = in.Level + return nil +} + +func Convert_api_SELinuxOptions_To_v1_SELinuxOptions(in *api.SELinuxOptions, out *SELinuxOptions, s conversion.Scope) error { + return autoConvert_api_SELinuxOptions_To_v1_SELinuxOptions(in, out, s) +} + func autoConvert_v1_Secret_To_api_Secret(in *Secret, out *api.Secret, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Secret))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } if in.Data != nil { - out.Data = make(map[string][]uint8) - for key, val := range in.Data { - newVal := []uint8{} - if err := conversion.ByteSliceCopy(&val, &newVal, s); err != nil { + in, out := &in.Data, &out.Data + *out = make(map[string][]byte, len(*in)) + for key, val := range *in { + newVal := new([]byte) + if err := conversion.Convert_Slice_byte_To_Slice_byte(&val, newVal, s); err != nil { return err } - out.Data[key] = newVal + (*out)[key] = *newVal } } else { out.Data = nil @@ -5934,6 +6293,37 @@ func Convert_v1_Secret_To_api_Secret(in *Secret, out *api.Secret, s conversion.S return autoConvert_v1_Secret_To_api_Secret(in, out, s) } +func autoConvert_api_Secret_To_v1_Secret(in *api.Secret, out *Secret, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Secret))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if in.Data != nil { + in, out := &in.Data, &out.Data + *out = make(map[string][]byte, len(*in)) + for key, val := range *in { + newVal := new([]byte) + if err := conversion.Convert_Slice_byte_To_Slice_byte(&val, newVal, s); err != nil { + return err + } + (*out)[key] = *newVal + } + } else { + out.Data = nil + } + out.Type = SecretType(in.Type) + return nil +} + +func Convert_api_Secret_To_v1_Secret(in *api.Secret, out *Secret, s conversion.Scope) error { + return autoConvert_api_Secret_To_v1_Secret(in, out, s) +} + func autoConvert_v1_SecretKeySelector_To_api_SecretKeySelector(in *SecretKeySelector, out *api.SecretKeySelector, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*SecretKeySelector))(in) @@ -5949,17 +6339,36 @@ func Convert_v1_SecretKeySelector_To_api_SecretKeySelector(in *SecretKeySelector return autoConvert_v1_SecretKeySelector_To_api_SecretKeySelector(in, out, s) } +func autoConvert_api_SecretKeySelector_To_v1_SecretKeySelector(in *api.SecretKeySelector, out *SecretKeySelector, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.SecretKeySelector))(in) + } + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Key = in.Key + return nil +} + +func Convert_api_SecretKeySelector_To_v1_SecretKeySelector(in *api.SecretKeySelector, out *SecretKeySelector, s conversion.Scope) error { + return autoConvert_api_SecretKeySelector_To_v1_SecretKeySelector(in, out, s) +} + func autoConvert_v1_SecretList_To_api_SecretList(in *SecretList, out *api.SecretList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*SecretList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.Secret, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_Secret_To_api_Secret(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.Secret, len(*in)) + for i := range *in { + if err := Convert_v1_Secret_To_api_Secret(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -5973,6 +6382,34 @@ func Convert_v1_SecretList_To_api_SecretList(in *SecretList, out *api.SecretList return autoConvert_v1_SecretList_To_api_SecretList(in, out, s) } +func autoConvert_api_SecretList_To_v1_SecretList(in *api.SecretList, out *SecretList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.SecretList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Secret, len(*in)) + for i := range *in { + if err := Convert_api_Secret_To_v1_Secret(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_SecretList_To_v1_SecretList(in *api.SecretList, out *SecretList, s conversion.Scope) error { + return autoConvert_api_SecretList_To_v1_SecretList(in, out, s) +} + func autoConvert_v1_SecretVolumeSource_To_api_SecretVolumeSource(in *SecretVolumeSource, out *api.SecretVolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*SecretVolumeSource))(in) @@ -5985,49 +6422,65 @@ func Convert_v1_SecretVolumeSource_To_api_SecretVolumeSource(in *SecretVolumeSou return autoConvert_v1_SecretVolumeSource_To_api_SecretVolumeSource(in, out, s) } +func autoConvert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in *api.SecretVolumeSource, out *SecretVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.SecretVolumeSource))(in) + } + out.SecretName = in.SecretName + return nil +} + +func Convert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in *api.SecretVolumeSource, out *SecretVolumeSource, s conversion.Scope) error { + return autoConvert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in, out, s) +} + func autoConvert_v1_SecurityContext_To_api_SecurityContext(in *SecurityContext, out *api.SecurityContext, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*SecurityContext))(in) } - // unable to generate simple pointer conversion for v1.Capabilities -> api.Capabilities if in.Capabilities != nil { - out.Capabilities = new(api.Capabilities) - if err := Convert_v1_Capabilities_To_api_Capabilities(in.Capabilities, out.Capabilities, s); err != nil { + in, out := &in.Capabilities, &out.Capabilities + *out = new(api.Capabilities) + if err := Convert_v1_Capabilities_To_api_Capabilities(*in, *out, s); err != nil { return err } } else { out.Capabilities = nil } if in.Privileged != nil { - out.Privileged = new(bool) - *out.Privileged = *in.Privileged + in, out := &in.Privileged, &out.Privileged + *out = new(bool) + **out = **in } else { out.Privileged = nil } - // unable to generate simple pointer conversion for v1.SELinuxOptions -> api.SELinuxOptions if in.SELinuxOptions != nil { - out.SELinuxOptions = new(api.SELinuxOptions) - if err := Convert_v1_SELinuxOptions_To_api_SELinuxOptions(in.SELinuxOptions, out.SELinuxOptions, s); err != nil { + in, out := &in.SELinuxOptions, &out.SELinuxOptions + *out = new(api.SELinuxOptions) + if err := Convert_v1_SELinuxOptions_To_api_SELinuxOptions(*in, *out, s); err != nil { return err } } else { out.SELinuxOptions = nil } if in.RunAsUser != nil { - out.RunAsUser = new(int64) - *out.RunAsUser = *in.RunAsUser + in, out := &in.RunAsUser, &out.RunAsUser + *out = new(int64) + **out = **in } else { out.RunAsUser = nil } if in.RunAsNonRoot != nil { - out.RunAsNonRoot = new(bool) - *out.RunAsNonRoot = *in.RunAsNonRoot + in, out := &in.RunAsNonRoot, &out.RunAsNonRoot + *out = new(bool) + **out = **in } else { out.RunAsNonRoot = nil } if in.ReadOnlyRootFilesystem != nil { - out.ReadOnlyRootFilesystem = new(bool) - *out.ReadOnlyRootFilesystem = *in.ReadOnlyRootFilesystem + in, out := &in.ReadOnlyRootFilesystem, &out.ReadOnlyRootFilesystem + *out = new(bool) + **out = **in } else { out.ReadOnlyRootFilesystem = nil } @@ -6038,10 +6491,70 @@ func Convert_v1_SecurityContext_To_api_SecurityContext(in *SecurityContext, out return autoConvert_v1_SecurityContext_To_api_SecurityContext(in, out, s) } +func autoConvert_api_SecurityContext_To_v1_SecurityContext(in *api.SecurityContext, out *SecurityContext, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.SecurityContext))(in) + } + if in.Capabilities != nil { + in, out := &in.Capabilities, &out.Capabilities + *out = new(Capabilities) + if err := Convert_api_Capabilities_To_v1_Capabilities(*in, *out, s); err != nil { + return err + } + } else { + out.Capabilities = nil + } + if in.Privileged != nil { + in, out := &in.Privileged, &out.Privileged + *out = new(bool) + **out = **in + } else { + out.Privileged = nil + } + if in.SELinuxOptions != nil { + in, out := &in.SELinuxOptions, &out.SELinuxOptions + *out = new(SELinuxOptions) + if err := Convert_api_SELinuxOptions_To_v1_SELinuxOptions(*in, *out, s); err != nil { + return err + } + } else { + out.SELinuxOptions = nil + } + if in.RunAsUser != nil { + in, out := &in.RunAsUser, &out.RunAsUser + *out = new(int64) + **out = **in + } else { + out.RunAsUser = nil + } + if in.RunAsNonRoot != nil { + in, out := &in.RunAsNonRoot, &out.RunAsNonRoot + *out = new(bool) + **out = **in + } else { + out.RunAsNonRoot = nil + } + if in.ReadOnlyRootFilesystem != nil { + in, out := &in.ReadOnlyRootFilesystem, &out.ReadOnlyRootFilesystem + *out = new(bool) + **out = **in + } else { + out.ReadOnlyRootFilesystem = nil + } + return nil +} + +func Convert_api_SecurityContext_To_v1_SecurityContext(in *api.SecurityContext, out *SecurityContext, s conversion.Scope) error { + return autoConvert_api_SecurityContext_To_v1_SecurityContext(in, out, s) +} + func autoConvert_v1_SerializedReference_To_api_SerializedReference(in *SerializedReference, out *api.SerializedReference, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*SerializedReference))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectReference_To_api_ObjectReference(&in.Reference, &out.Reference, s); err != nil { return err } @@ -6052,10 +6565,30 @@ func Convert_v1_SerializedReference_To_api_SerializedReference(in *SerializedRef return autoConvert_v1_SerializedReference_To_api_SerializedReference(in, out, s) } +func autoConvert_api_SerializedReference_To_v1_SerializedReference(in *api.SerializedReference, out *SerializedReference, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.SerializedReference))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectReference_To_v1_ObjectReference(&in.Reference, &out.Reference, s); err != nil { + return err + } + return nil +} + +func Convert_api_SerializedReference_To_v1_SerializedReference(in *api.SerializedReference, out *SerializedReference, s conversion.Scope) error { + return autoConvert_api_SerializedReference_To_v1_SerializedReference(in, out, s) +} + func autoConvert_v1_Service_To_api_Service(in *Service, out *api.Service, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Service))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } @@ -6072,17 +6605,44 @@ func Convert_v1_Service_To_api_Service(in *Service, out *api.Service, s conversi return autoConvert_v1_Service_To_api_Service(in, out, s) } +func autoConvert_api_Service_To_v1_Service(in *api.Service, out *Service, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Service))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_ServiceSpec_To_v1_ServiceSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_api_ServiceStatus_To_v1_ServiceStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_api_Service_To_v1_Service(in *api.Service, out *Service, s conversion.Scope) error { + return autoConvert_api_Service_To_v1_Service(in, out, s) +} + func autoConvert_v1_ServiceAccount_To_api_ServiceAccount(in *ServiceAccount, out *api.ServiceAccount, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ServiceAccount))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } if in.Secrets != nil { - out.Secrets = make([]api.ObjectReference, len(in.Secrets)) - for i := range in.Secrets { - if err := Convert_v1_ObjectReference_To_api_ObjectReference(&in.Secrets[i], &out.Secrets[i], s); err != nil { + in, out := &in.Secrets, &out.Secrets + *out = make([]api.ObjectReference, len(*in)) + for i := range *in { + if err := Convert_v1_ObjectReference_To_api_ObjectReference(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -6090,9 +6650,10 @@ func autoConvert_v1_ServiceAccount_To_api_ServiceAccount(in *ServiceAccount, out out.Secrets = nil } if in.ImagePullSecrets != nil { - out.ImagePullSecrets = make([]api.LocalObjectReference, len(in.ImagePullSecrets)) - for i := range in.ImagePullSecrets { - if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(&in.ImagePullSecrets[i], &out.ImagePullSecrets[i], s); err != nil { + in, out := &in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]api.LocalObjectReference, len(*in)) + for i := range *in { + if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -6106,17 +6667,60 @@ func Convert_v1_ServiceAccount_To_api_ServiceAccount(in *ServiceAccount, out *ap return autoConvert_v1_ServiceAccount_To_api_ServiceAccount(in, out, s) } +func autoConvert_api_ServiceAccount_To_v1_ServiceAccount(in *api.ServiceAccount, out *ServiceAccount, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ServiceAccount))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if in.Secrets != nil { + in, out := &in.Secrets, &out.Secrets + *out = make([]ObjectReference, len(*in)) + for i := range *in { + if err := Convert_api_ObjectReference_To_v1_ObjectReference(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Secrets = nil + } + if in.ImagePullSecrets != nil { + in, out := &in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]LocalObjectReference, len(*in)) + for i := range *in { + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.ImagePullSecrets = nil + } + return nil +} + +func Convert_api_ServiceAccount_To_v1_ServiceAccount(in *api.ServiceAccount, out *ServiceAccount, s conversion.Scope) error { + return autoConvert_api_ServiceAccount_To_v1_ServiceAccount(in, out, s) +} + func autoConvert_v1_ServiceAccountList_To_api_ServiceAccountList(in *ServiceAccountList, out *api.ServiceAccountList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ServiceAccountList))(in) } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.ServiceAccount, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_ServiceAccount_To_api_ServiceAccount(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]api.ServiceAccount, len(*in)) + for i := range *in { + if err := Convert_v1_ServiceAccount_To_api_ServiceAccount(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -6130,17 +6734,49 @@ func Convert_v1_ServiceAccountList_To_api_ServiceAccountList(in *ServiceAccountL return autoConvert_v1_ServiceAccountList_To_api_ServiceAccountList(in, out, s) } -func autoConvert_v1_ServiceList_To_api_ServiceList(in *ServiceList, out *api.ServiceList, s conversion.Scope) error { +func autoConvert_api_ServiceAccountList_To_v1_ServiceAccountList(in *api.ServiceAccountList, out *ServiceAccountList, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*ServiceList))(in) + defaulting.(func(*api.ServiceAccountList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err } if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { return err } if in.Items != nil { - out.Items = make([]api.Service, len(in.Items)) - for i := range in.Items { - if err := Convert_v1_Service_To_api_Service(&in.Items[i], &out.Items[i], s); err != nil { + in, out := &in.Items, &out.Items + *out = make([]ServiceAccount, len(*in)) + for i := range *in { + if err := Convert_api_ServiceAccount_To_v1_ServiceAccount(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_ServiceAccountList_To_v1_ServiceAccountList(in *api.ServiceAccountList, out *ServiceAccountList, s conversion.Scope) error { + return autoConvert_api_ServiceAccountList_To_v1_ServiceAccountList(in, out, s) +} + +func autoConvert_v1_ServiceList_To_api_ServiceList(in *ServiceList, out *api.ServiceList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*ServiceList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]api.Service, len(*in)) + for i := range *in { + if err := Convert_v1_Service_To_api_Service(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -6154,6 +6790,34 @@ func Convert_v1_ServiceList_To_api_ServiceList(in *ServiceList, out *api.Service return autoConvert_v1_ServiceList_To_api_ServiceList(in, out, s) } +func autoConvert_api_ServiceList_To_v1_ServiceList(in *api.ServiceList, out *ServiceList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ServiceList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Service, len(*in)) + for i := range *in { + if err := Convert_api_Service_To_v1_Service(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_ServiceList_To_v1_ServiceList(in *api.ServiceList, out *ServiceList, s conversion.Scope) error { + return autoConvert_api_ServiceList_To_v1_ServiceList(in, out, s) +} + func autoConvert_v1_ServicePort_To_api_ServicePort(in *ServicePort, out *api.ServicePort, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ServicePort))(in) @@ -6172,14 +6836,63 @@ func Convert_v1_ServicePort_To_api_ServicePort(in *ServicePort, out *api.Service return autoConvert_v1_ServicePort_To_api_ServicePort(in, out, s) } +func autoConvert_api_ServicePort_To_v1_ServicePort(in *api.ServicePort, out *ServicePort, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ServicePort))(in) + } + out.Name = in.Name + out.Protocol = Protocol(in.Protocol) + out.Port = int32(in.Port) + if err := api.Convert_intstr_IntOrString_To_intstr_IntOrString(&in.TargetPort, &out.TargetPort, s); err != nil { + return err + } + out.NodePort = int32(in.NodePort) + return nil +} + +func Convert_api_ServicePort_To_v1_ServicePort(in *api.ServicePort, out *ServicePort, s conversion.Scope) error { + return autoConvert_api_ServicePort_To_v1_ServicePort(in, out, s) +} + +func autoConvert_v1_ServiceProxyOptions_To_api_ServiceProxyOptions(in *ServiceProxyOptions, out *api.ServiceProxyOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*ServiceProxyOptions))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + out.Path = in.Path + return nil +} + +func Convert_v1_ServiceProxyOptions_To_api_ServiceProxyOptions(in *ServiceProxyOptions, out *api.ServiceProxyOptions, s conversion.Scope) error { + return autoConvert_v1_ServiceProxyOptions_To_api_ServiceProxyOptions(in, out, s) +} + +func autoConvert_api_ServiceProxyOptions_To_v1_ServiceProxyOptions(in *api.ServiceProxyOptions, out *ServiceProxyOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ServiceProxyOptions))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + out.Path = in.Path + return nil +} + +func Convert_api_ServiceProxyOptions_To_v1_ServiceProxyOptions(in *api.ServiceProxyOptions, out *ServiceProxyOptions, s conversion.Scope) error { + return autoConvert_api_ServiceProxyOptions_To_v1_ServiceProxyOptions(in, out, s) +} + func autoConvert_v1_ServiceSpec_To_api_ServiceSpec(in *ServiceSpec, out *api.ServiceSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ServiceSpec))(in) } if in.Ports != nil { - out.Ports = make([]api.ServicePort, len(in.Ports)) - for i := range in.Ports { - if err := Convert_v1_ServicePort_To_api_ServicePort(&in.Ports[i], &out.Ports[i], s); err != nil { + in, out := &in.Ports, &out.Ports + *out = make([]api.ServicePort, len(*in)) + for i := range *in { + if err := Convert_v1_ServicePort_To_api_ServicePort(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -6187,9 +6900,10 @@ func autoConvert_v1_ServiceSpec_To_api_ServiceSpec(in *ServiceSpec, out *api.Ser out.Ports = nil } if in.Selector != nil { - out.Selector = make(map[string]string) - for key, val := range in.Selector { - out.Selector[key] = val + in, out := &in.Selector, &out.Selector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val } } else { out.Selector = nil @@ -6197,19 +6911,55 @@ func autoConvert_v1_ServiceSpec_To_api_ServiceSpec(in *ServiceSpec, out *api.Ser out.ClusterIP = in.ClusterIP out.Type = api.ServiceType(in.Type) if in.ExternalIPs != nil { - out.ExternalIPs = make([]string, len(in.ExternalIPs)) - for i := range in.ExternalIPs { - out.ExternalIPs[i] = in.ExternalIPs[i] - } + in, out := &in.ExternalIPs, &out.ExternalIPs + *out = make([]string, len(*in)) + copy(*out, *in) } else { out.ExternalIPs = nil } - // in.DeprecatedPublicIPs has no peer in out out.SessionAffinity = api.ServiceAffinity(in.SessionAffinity) out.LoadBalancerIP = in.LoadBalancerIP return nil } +func autoConvert_api_ServiceSpec_To_v1_ServiceSpec(in *api.ServiceSpec, out *ServiceSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ServiceSpec))(in) + } + out.Type = ServiceType(in.Type) + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]ServicePort, len(*in)) + for i := range *in { + if err := Convert_api_ServicePort_To_v1_ServicePort(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Ports = nil + } + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } else { + out.Selector = nil + } + out.ClusterIP = in.ClusterIP + if in.ExternalIPs != nil { + in, out := &in.ExternalIPs, &out.ExternalIPs + *out = make([]string, len(*in)) + copy(*out, *in) + } else { + out.ExternalIPs = nil + } + out.LoadBalancerIP = in.LoadBalancerIP + out.SessionAffinity = ServiceAffinity(in.SessionAffinity) + return nil +} + func autoConvert_v1_ServiceStatus_To_api_ServiceStatus(in *ServiceStatus, out *api.ServiceStatus, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*ServiceStatus))(in) @@ -6224,6 +6974,20 @@ func Convert_v1_ServiceStatus_To_api_ServiceStatus(in *ServiceStatus, out *api.S return autoConvert_v1_ServiceStatus_To_api_ServiceStatus(in, out, s) } +func autoConvert_api_ServiceStatus_To_v1_ServiceStatus(in *api.ServiceStatus, out *ServiceStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ServiceStatus))(in) + } + if err := Convert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus(&in.LoadBalancer, &out.LoadBalancer, s); err != nil { + return err + } + return nil +} + +func Convert_api_ServiceStatus_To_v1_ServiceStatus(in *api.ServiceStatus, out *ServiceStatus, s conversion.Scope) error { + return autoConvert_api_ServiceStatus_To_v1_ServiceStatus(in, out, s) +} + func autoConvert_v1_TCPSocketAction_To_api_TCPSocketAction(in *TCPSocketAction, out *api.TCPSocketAction, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*TCPSocketAction))(in) @@ -6238,6 +7002,20 @@ func Convert_v1_TCPSocketAction_To_api_TCPSocketAction(in *TCPSocketAction, out return autoConvert_v1_TCPSocketAction_To_api_TCPSocketAction(in, out, s) } +func autoConvert_api_TCPSocketAction_To_v1_TCPSocketAction(in *api.TCPSocketAction, out *TCPSocketAction, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.TCPSocketAction))(in) + } + if err := api.Convert_intstr_IntOrString_To_intstr_IntOrString(&in.Port, &out.Port, s); err != nil { + return err + } + return nil +} + +func Convert_api_TCPSocketAction_To_v1_TCPSocketAction(in *api.TCPSocketAction, out *TCPSocketAction, s conversion.Scope) error { + return autoConvert_api_TCPSocketAction_To_v1_TCPSocketAction(in, out, s) +} + func autoConvert_v1_Volume_To_api_Volume(in *Volume, out *api.Volume, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*Volume))(in) @@ -6253,6 +7031,21 @@ func Convert_v1_Volume_To_api_Volume(in *Volume, out *api.Volume, s conversion.S return autoConvert_v1_Volume_To_api_Volume(in, out, s) } +func autoConvert_api_Volume_To_v1_Volume(in *api.Volume, out *Volume, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Volume))(in) + } + out.Name = in.Name + if err := Convert_api_VolumeSource_To_v1_VolumeSource(&in.VolumeSource, &out.VolumeSource, s); err != nil { + return err + } + return nil +} + +func Convert_api_Volume_To_v1_Volume(in *api.Volume, out *Volume, s conversion.Scope) error { + return autoConvert_api_Volume_To_v1_Volume(in, out, s) +} + func autoConvert_v1_VolumeMount_To_api_VolumeMount(in *VolumeMount, out *api.VolumeMount, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*VolumeMount))(in) @@ -6267,176 +7060,190 @@ func Convert_v1_VolumeMount_To_api_VolumeMount(in *VolumeMount, out *api.VolumeM return autoConvert_v1_VolumeMount_To_api_VolumeMount(in, out, s) } +func autoConvert_api_VolumeMount_To_v1_VolumeMount(in *api.VolumeMount, out *VolumeMount, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.VolumeMount))(in) + } + out.Name = in.Name + out.ReadOnly = in.ReadOnly + out.MountPath = in.MountPath + return nil +} + +func Convert_api_VolumeMount_To_v1_VolumeMount(in *api.VolumeMount, out *VolumeMount, s conversion.Scope) error { + return autoConvert_api_VolumeMount_To_v1_VolumeMount(in, out, s) +} + func autoConvert_v1_VolumeSource_To_api_VolumeSource(in *VolumeSource, out *api.VolumeSource, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*VolumeSource))(in) } - // unable to generate simple pointer conversion for v1.HostPathVolumeSource -> api.HostPathVolumeSource if in.HostPath != nil { - out.HostPath = new(api.HostPathVolumeSource) - if err := Convert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource(in.HostPath, out.HostPath, s); err != nil { + in, out := &in.HostPath, &out.HostPath + *out = new(api.HostPathVolumeSource) + if err := Convert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource(*in, *out, s); err != nil { return err } } else { out.HostPath = nil } - // unable to generate simple pointer conversion for v1.EmptyDirVolumeSource -> api.EmptyDirVolumeSource if in.EmptyDir != nil { - out.EmptyDir = new(api.EmptyDirVolumeSource) - if err := Convert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource(in.EmptyDir, out.EmptyDir, s); err != nil { + in, out := &in.EmptyDir, &out.EmptyDir + *out = new(api.EmptyDirVolumeSource) + if err := Convert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource(*in, *out, s); err != nil { return err } } else { out.EmptyDir = nil } - // unable to generate simple pointer conversion for v1.GCEPersistentDiskVolumeSource -> api.GCEPersistentDiskVolumeSource if in.GCEPersistentDisk != nil { - out.GCEPersistentDisk = new(api.GCEPersistentDiskVolumeSource) - if err := Convert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource(in.GCEPersistentDisk, out.GCEPersistentDisk, s); err != nil { + in, out := &in.GCEPersistentDisk, &out.GCEPersistentDisk + *out = new(api.GCEPersistentDiskVolumeSource) + if err := Convert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource(*in, *out, s); err != nil { return err } } else { out.GCEPersistentDisk = nil } - // unable to generate simple pointer conversion for v1.AWSElasticBlockStoreVolumeSource -> api.AWSElasticBlockStoreVolumeSource if in.AWSElasticBlockStore != nil { - out.AWSElasticBlockStore = new(api.AWSElasticBlockStoreVolumeSource) - if err := Convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource(in.AWSElasticBlockStore, out.AWSElasticBlockStore, s); err != nil { + in, out := &in.AWSElasticBlockStore, &out.AWSElasticBlockStore + *out = new(api.AWSElasticBlockStoreVolumeSource) + if err := Convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource(*in, *out, s); err != nil { return err } } else { out.AWSElasticBlockStore = nil } - // unable to generate simple pointer conversion for v1.GitRepoVolumeSource -> api.GitRepoVolumeSource if in.GitRepo != nil { - out.GitRepo = new(api.GitRepoVolumeSource) - if err := Convert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource(in.GitRepo, out.GitRepo, s); err != nil { + in, out := &in.GitRepo, &out.GitRepo + *out = new(api.GitRepoVolumeSource) + if err := Convert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource(*in, *out, s); err != nil { return err } } else { out.GitRepo = nil } - // unable to generate simple pointer conversion for v1.SecretVolumeSource -> api.SecretVolumeSource if in.Secret != nil { - out.Secret = new(api.SecretVolumeSource) - if err := Convert_v1_SecretVolumeSource_To_api_SecretVolumeSource(in.Secret, out.Secret, s); err != nil { + in, out := &in.Secret, &out.Secret + *out = new(api.SecretVolumeSource) + if err := Convert_v1_SecretVolumeSource_To_api_SecretVolumeSource(*in, *out, s); err != nil { return err } } else { out.Secret = nil } - // unable to generate simple pointer conversion for v1.NFSVolumeSource -> api.NFSVolumeSource if in.NFS != nil { - out.NFS = new(api.NFSVolumeSource) - if err := Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in.NFS, out.NFS, s); err != nil { + in, out := &in.NFS, &out.NFS + *out = new(api.NFSVolumeSource) + if err := Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource(*in, *out, s); err != nil { return err } } else { out.NFS = nil } - // unable to generate simple pointer conversion for v1.ISCSIVolumeSource -> api.ISCSIVolumeSource if in.ISCSI != nil { - out.ISCSI = new(api.ISCSIVolumeSource) - if err := Convert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource(in.ISCSI, out.ISCSI, s); err != nil { + in, out := &in.ISCSI, &out.ISCSI + *out = new(api.ISCSIVolumeSource) + if err := Convert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource(*in, *out, s); err != nil { return err } } else { out.ISCSI = nil } - // unable to generate simple pointer conversion for v1.GlusterfsVolumeSource -> api.GlusterfsVolumeSource if in.Glusterfs != nil { - out.Glusterfs = new(api.GlusterfsVolumeSource) - if err := Convert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource(in.Glusterfs, out.Glusterfs, s); err != nil { + in, out := &in.Glusterfs, &out.Glusterfs + *out = new(api.GlusterfsVolumeSource) + if err := Convert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource(*in, *out, s); err != nil { return err } } else { out.Glusterfs = nil } - // unable to generate simple pointer conversion for v1.PersistentVolumeClaimVolumeSource -> api.PersistentVolumeClaimVolumeSource if in.PersistentVolumeClaim != nil { - out.PersistentVolumeClaim = new(api.PersistentVolumeClaimVolumeSource) - if err := Convert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource(in.PersistentVolumeClaim, out.PersistentVolumeClaim, s); err != nil { + in, out := &in.PersistentVolumeClaim, &out.PersistentVolumeClaim + *out = new(api.PersistentVolumeClaimVolumeSource) + if err := Convert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource(*in, *out, s); err != nil { return err } } else { out.PersistentVolumeClaim = nil } - // unable to generate simple pointer conversion for v1.RBDVolumeSource -> api.RBDVolumeSource if in.RBD != nil { - out.RBD = new(api.RBDVolumeSource) - if err := Convert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in.RBD, out.RBD, s); err != nil { + in, out := &in.RBD, &out.RBD + *out = new(api.RBDVolumeSource) + if err := Convert_v1_RBDVolumeSource_To_api_RBDVolumeSource(*in, *out, s); err != nil { return err } } else { out.RBD = nil } - // unable to generate simple pointer conversion for v1.FlexVolumeSource -> api.FlexVolumeSource if in.FlexVolume != nil { - out.FlexVolume = new(api.FlexVolumeSource) - if err := Convert_v1_FlexVolumeSource_To_api_FlexVolumeSource(in.FlexVolume, out.FlexVolume, s); err != nil { + in, out := &in.FlexVolume, &out.FlexVolume + *out = new(api.FlexVolumeSource) + if err := Convert_v1_FlexVolumeSource_To_api_FlexVolumeSource(*in, *out, s); err != nil { return err } } else { out.FlexVolume = nil } - // unable to generate simple pointer conversion for v1.CinderVolumeSource -> api.CinderVolumeSource if in.Cinder != nil { - out.Cinder = new(api.CinderVolumeSource) - if err := Convert_v1_CinderVolumeSource_To_api_CinderVolumeSource(in.Cinder, out.Cinder, s); err != nil { + in, out := &in.Cinder, &out.Cinder + *out = new(api.CinderVolumeSource) + if err := Convert_v1_CinderVolumeSource_To_api_CinderVolumeSource(*in, *out, s); err != nil { return err } } else { out.Cinder = nil } - // unable to generate simple pointer conversion for v1.CephFSVolumeSource -> api.CephFSVolumeSource if in.CephFS != nil { - out.CephFS = new(api.CephFSVolumeSource) - if err := Convert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource(in.CephFS, out.CephFS, s); err != nil { + in, out := &in.CephFS, &out.CephFS + *out = new(api.CephFSVolumeSource) + if err := Convert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource(*in, *out, s); err != nil { return err } } else { out.CephFS = nil } - // unable to generate simple pointer conversion for v1.FlockerVolumeSource -> api.FlockerVolumeSource if in.Flocker != nil { - out.Flocker = new(api.FlockerVolumeSource) - if err := Convert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(in.Flocker, out.Flocker, s); err != nil { + in, out := &in.Flocker, &out.Flocker + *out = new(api.FlockerVolumeSource) + if err := Convert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(*in, *out, s); err != nil { return err } } else { out.Flocker = nil } - // unable to generate simple pointer conversion for v1.DownwardAPIVolumeSource -> api.DownwardAPIVolumeSource if in.DownwardAPI != nil { - out.DownwardAPI = new(api.DownwardAPIVolumeSource) - if err := Convert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource(in.DownwardAPI, out.DownwardAPI, s); err != nil { + in, out := &in.DownwardAPI, &out.DownwardAPI + *out = new(api.DownwardAPIVolumeSource) + if err := Convert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource(*in, *out, s); err != nil { return err } } else { out.DownwardAPI = nil } - // unable to generate simple pointer conversion for v1.FCVolumeSource -> api.FCVolumeSource if in.FC != nil { - out.FC = new(api.FCVolumeSource) - if err := Convert_v1_FCVolumeSource_To_api_FCVolumeSource(in.FC, out.FC, s); err != nil { + in, out := &in.FC, &out.FC + *out = new(api.FCVolumeSource) + if err := Convert_v1_FCVolumeSource_To_api_FCVolumeSource(*in, *out, s); err != nil { return err } } else { out.FC = nil } - // unable to generate simple pointer conversion for v1.AzureFileVolumeSource -> api.AzureFileVolumeSource if in.AzureFile != nil { - out.AzureFile = new(api.AzureFileVolumeSource) - if err := Convert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource(in.AzureFile, out.AzureFile, s); err != nil { + in, out := &in.AzureFile, &out.AzureFile + *out = new(api.AzureFileVolumeSource) + if err := Convert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource(*in, *out, s); err != nil { return err } } else { out.AzureFile = nil } - // unable to generate simple pointer conversion for v1.ConfigMapVolumeSource -> api.ConfigMapVolumeSource if in.ConfigMap != nil { - out.ConfigMap = new(api.ConfigMapVolumeSource) - if err := Convert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource(in.ConfigMap, out.ConfigMap, s); err != nil { + in, out := &in.ConfigMap, &out.ConfigMap + *out = new(api.ConfigMapVolumeSource) + if err := Convert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource(*in, *out, s); err != nil { return err } } else { @@ -6449,269 +7256,184 @@ func Convert_v1_VolumeSource_To_api_VolumeSource(in *VolumeSource, out *api.Volu return autoConvert_v1_VolumeSource_To_api_VolumeSource(in, out, s) } -func init() { - err := api.Scheme.AddGeneratedConversionFuncs( - autoConvert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource, - autoConvert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource, - autoConvert_api_Binding_To_v1_Binding, - autoConvert_api_Capabilities_To_v1_Capabilities, - autoConvert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource, - autoConvert_api_CinderVolumeSource_To_v1_CinderVolumeSource, - autoConvert_api_ComponentCondition_To_v1_ComponentCondition, - autoConvert_api_ComponentStatusList_To_v1_ComponentStatusList, - autoConvert_api_ComponentStatus_To_v1_ComponentStatus, - autoConvert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector, - autoConvert_api_ConfigMapList_To_v1_ConfigMapList, - autoConvert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource, - autoConvert_api_ConfigMap_To_v1_ConfigMap, - autoConvert_api_ContainerImage_To_v1_ContainerImage, - autoConvert_api_ContainerPort_To_v1_ContainerPort, - autoConvert_api_ContainerStateRunning_To_v1_ContainerStateRunning, - autoConvert_api_ContainerStateTerminated_To_v1_ContainerStateTerminated, - autoConvert_api_ContainerStateWaiting_To_v1_ContainerStateWaiting, - autoConvert_api_ContainerState_To_v1_ContainerState, - autoConvert_api_ContainerStatus_To_v1_ContainerStatus, - autoConvert_api_Container_To_v1_Container, - autoConvert_api_DaemonEndpoint_To_v1_DaemonEndpoint, - autoConvert_api_DeleteOptions_To_v1_DeleteOptions, - autoConvert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile, - autoConvert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource, - autoConvert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource, - autoConvert_api_EndpointAddress_To_v1_EndpointAddress, - autoConvert_api_EndpointPort_To_v1_EndpointPort, - autoConvert_api_EndpointSubset_To_v1_EndpointSubset, - autoConvert_api_EndpointsList_To_v1_EndpointsList, - autoConvert_api_Endpoints_To_v1_Endpoints, - autoConvert_api_EnvVarSource_To_v1_EnvVarSource, - autoConvert_api_EnvVar_To_v1_EnvVar, - autoConvert_api_EventList_To_v1_EventList, - autoConvert_api_EventSource_To_v1_EventSource, - autoConvert_api_Event_To_v1_Event, - autoConvert_api_ExecAction_To_v1_ExecAction, - autoConvert_api_FCVolumeSource_To_v1_FCVolumeSource, - autoConvert_api_FlexVolumeSource_To_v1_FlexVolumeSource, - autoConvert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource, - autoConvert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource, - autoConvert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource, - autoConvert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource, - autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction, - autoConvert_api_HTTPHeader_To_v1_HTTPHeader, - autoConvert_api_Handler_To_v1_Handler, - autoConvert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource, - autoConvert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource, - autoConvert_api_KeyToPath_To_v1_KeyToPath, - autoConvert_api_Lifecycle_To_v1_Lifecycle, - autoConvert_api_LimitRangeItem_To_v1_LimitRangeItem, - autoConvert_api_LimitRangeList_To_v1_LimitRangeList, - autoConvert_api_LimitRangeSpec_To_v1_LimitRangeSpec, - autoConvert_api_LimitRange_To_v1_LimitRange, - autoConvert_api_ListOptions_To_v1_ListOptions, - autoConvert_api_List_To_v1_List, - autoConvert_api_LoadBalancerIngress_To_v1_LoadBalancerIngress, - autoConvert_api_LoadBalancerStatus_To_v1_LoadBalancerStatus, - autoConvert_api_LocalObjectReference_To_v1_LocalObjectReference, - autoConvert_api_NFSVolumeSource_To_v1_NFSVolumeSource, - autoConvert_api_NamespaceList_To_v1_NamespaceList, - autoConvert_api_NamespaceSpec_To_v1_NamespaceSpec, - autoConvert_api_NamespaceStatus_To_v1_NamespaceStatus, - autoConvert_api_Namespace_To_v1_Namespace, - autoConvert_api_NodeAddress_To_v1_NodeAddress, - autoConvert_api_NodeCondition_To_v1_NodeCondition, - autoConvert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints, - autoConvert_api_NodeList_To_v1_NodeList, - autoConvert_api_NodeSpec_To_v1_NodeSpec, - autoConvert_api_NodeStatus_To_v1_NodeStatus, - autoConvert_api_NodeSystemInfo_To_v1_NodeSystemInfo, - autoConvert_api_Node_To_v1_Node, - autoConvert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector, - autoConvert_api_ObjectMeta_To_v1_ObjectMeta, - autoConvert_api_ObjectReference_To_v1_ObjectReference, - autoConvert_api_PersistentVolumeClaimList_To_v1_PersistentVolumeClaimList, - autoConvert_api_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec, - autoConvert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus, - autoConvert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource, - autoConvert_api_PersistentVolumeClaim_To_v1_PersistentVolumeClaim, - autoConvert_api_PersistentVolumeList_To_v1_PersistentVolumeList, - autoConvert_api_PersistentVolumeSource_To_v1_PersistentVolumeSource, - autoConvert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec, - autoConvert_api_PersistentVolumeStatus_To_v1_PersistentVolumeStatus, - autoConvert_api_PersistentVolume_To_v1_PersistentVolume, - autoConvert_api_PodAttachOptions_To_v1_PodAttachOptions, - autoConvert_api_PodCondition_To_v1_PodCondition, - autoConvert_api_PodExecOptions_To_v1_PodExecOptions, - autoConvert_api_PodList_To_v1_PodList, - autoConvert_api_PodLogOptions_To_v1_PodLogOptions, - autoConvert_api_PodProxyOptions_To_v1_PodProxyOptions, - autoConvert_api_PodSpec_To_v1_PodSpec, - autoConvert_api_PodStatusResult_To_v1_PodStatusResult, - autoConvert_api_PodStatus_To_v1_PodStatus, - autoConvert_api_PodTemplateList_To_v1_PodTemplateList, - autoConvert_api_PodTemplateSpec_To_v1_PodTemplateSpec, - autoConvert_api_PodTemplate_To_v1_PodTemplate, - autoConvert_api_Pod_To_v1_Pod, - autoConvert_api_Probe_To_v1_Probe, - autoConvert_api_RBDVolumeSource_To_v1_RBDVolumeSource, - autoConvert_api_RangeAllocation_To_v1_RangeAllocation, - autoConvert_api_ReplicationControllerList_To_v1_ReplicationControllerList, - autoConvert_api_ReplicationControllerSpec_To_v1_ReplicationControllerSpec, - autoConvert_api_ReplicationControllerStatus_To_v1_ReplicationControllerStatus, - autoConvert_api_ReplicationController_To_v1_ReplicationController, - autoConvert_api_ResourceQuotaList_To_v1_ResourceQuotaList, - autoConvert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec, - autoConvert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus, - autoConvert_api_ResourceQuota_To_v1_ResourceQuota, - autoConvert_api_ResourceRequirements_To_v1_ResourceRequirements, - autoConvert_api_SELinuxOptions_To_v1_SELinuxOptions, - autoConvert_api_SecretKeySelector_To_v1_SecretKeySelector, - autoConvert_api_SecretList_To_v1_SecretList, - autoConvert_api_SecretVolumeSource_To_v1_SecretVolumeSource, - autoConvert_api_Secret_To_v1_Secret, - autoConvert_api_SecurityContext_To_v1_SecurityContext, - autoConvert_api_SerializedReference_To_v1_SerializedReference, - autoConvert_api_ServiceAccountList_To_v1_ServiceAccountList, - autoConvert_api_ServiceAccount_To_v1_ServiceAccount, - autoConvert_api_ServiceList_To_v1_ServiceList, - autoConvert_api_ServicePort_To_v1_ServicePort, - autoConvert_api_ServiceSpec_To_v1_ServiceSpec, - autoConvert_api_ServiceStatus_To_v1_ServiceStatus, - autoConvert_api_Service_To_v1_Service, - autoConvert_api_TCPSocketAction_To_v1_TCPSocketAction, - autoConvert_api_VolumeMount_To_v1_VolumeMount, - autoConvert_api_VolumeSource_To_v1_VolumeSource, - autoConvert_api_Volume_To_v1_Volume, - autoConvert_unversioned_ExportOptions_To_v1_ExportOptions, - autoConvert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource, - autoConvert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource, - autoConvert_v1_Binding_To_api_Binding, - autoConvert_v1_Capabilities_To_api_Capabilities, - autoConvert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource, - autoConvert_v1_CinderVolumeSource_To_api_CinderVolumeSource, - autoConvert_v1_ComponentCondition_To_api_ComponentCondition, - autoConvert_v1_ComponentStatusList_To_api_ComponentStatusList, - autoConvert_v1_ComponentStatus_To_api_ComponentStatus, - autoConvert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector, - autoConvert_v1_ConfigMapList_To_api_ConfigMapList, - autoConvert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource, - autoConvert_v1_ConfigMap_To_api_ConfigMap, - autoConvert_v1_ContainerImage_To_api_ContainerImage, - autoConvert_v1_ContainerPort_To_api_ContainerPort, - autoConvert_v1_ContainerStateRunning_To_api_ContainerStateRunning, - autoConvert_v1_ContainerStateTerminated_To_api_ContainerStateTerminated, - autoConvert_v1_ContainerStateWaiting_To_api_ContainerStateWaiting, - autoConvert_v1_ContainerState_To_api_ContainerState, - autoConvert_v1_ContainerStatus_To_api_ContainerStatus, - autoConvert_v1_Container_To_api_Container, - autoConvert_v1_DaemonEndpoint_To_api_DaemonEndpoint, - autoConvert_v1_DeleteOptions_To_api_DeleteOptions, - autoConvert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile, - autoConvert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource, - autoConvert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource, - autoConvert_v1_EndpointAddress_To_api_EndpointAddress, - autoConvert_v1_EndpointPort_To_api_EndpointPort, - autoConvert_v1_EndpointSubset_To_api_EndpointSubset, - autoConvert_v1_EndpointsList_To_api_EndpointsList, - autoConvert_v1_Endpoints_To_api_Endpoints, - autoConvert_v1_EnvVarSource_To_api_EnvVarSource, - autoConvert_v1_EnvVar_To_api_EnvVar, - autoConvert_v1_EventList_To_api_EventList, - autoConvert_v1_EventSource_To_api_EventSource, - autoConvert_v1_Event_To_api_Event, - autoConvert_v1_ExecAction_To_api_ExecAction, - autoConvert_v1_ExportOptions_To_unversioned_ExportOptions, - autoConvert_v1_FCVolumeSource_To_api_FCVolumeSource, - autoConvert_v1_FlexVolumeSource_To_api_FlexVolumeSource, - autoConvert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource, - autoConvert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource, - autoConvert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource, - autoConvert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource, - autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction, - autoConvert_v1_HTTPHeader_To_api_HTTPHeader, - autoConvert_v1_Handler_To_api_Handler, - autoConvert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource, - autoConvert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource, - autoConvert_v1_KeyToPath_To_api_KeyToPath, - autoConvert_v1_Lifecycle_To_api_Lifecycle, - autoConvert_v1_LimitRangeItem_To_api_LimitRangeItem, - autoConvert_v1_LimitRangeList_To_api_LimitRangeList, - autoConvert_v1_LimitRangeSpec_To_api_LimitRangeSpec, - autoConvert_v1_LimitRange_To_api_LimitRange, - autoConvert_v1_ListOptions_To_api_ListOptions, - autoConvert_v1_List_To_api_List, - autoConvert_v1_LoadBalancerIngress_To_api_LoadBalancerIngress, - autoConvert_v1_LoadBalancerStatus_To_api_LoadBalancerStatus, - autoConvert_v1_LocalObjectReference_To_api_LocalObjectReference, - autoConvert_v1_NFSVolumeSource_To_api_NFSVolumeSource, - autoConvert_v1_NamespaceList_To_api_NamespaceList, - autoConvert_v1_NamespaceSpec_To_api_NamespaceSpec, - autoConvert_v1_NamespaceStatus_To_api_NamespaceStatus, - autoConvert_v1_Namespace_To_api_Namespace, - autoConvert_v1_NodeAddress_To_api_NodeAddress, - autoConvert_v1_NodeCondition_To_api_NodeCondition, - autoConvert_v1_NodeDaemonEndpoints_To_api_NodeDaemonEndpoints, - autoConvert_v1_NodeList_To_api_NodeList, - autoConvert_v1_NodeSpec_To_api_NodeSpec, - autoConvert_v1_NodeStatus_To_api_NodeStatus, - autoConvert_v1_NodeSystemInfo_To_api_NodeSystemInfo, - autoConvert_v1_Node_To_api_Node, - autoConvert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector, - autoConvert_v1_ObjectMeta_To_api_ObjectMeta, - autoConvert_v1_ObjectReference_To_api_ObjectReference, - autoConvert_v1_PersistentVolumeClaimList_To_api_PersistentVolumeClaimList, - autoConvert_v1_PersistentVolumeClaimSpec_To_api_PersistentVolumeClaimSpec, - autoConvert_v1_PersistentVolumeClaimStatus_To_api_PersistentVolumeClaimStatus, - autoConvert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource, - autoConvert_v1_PersistentVolumeClaim_To_api_PersistentVolumeClaim, - autoConvert_v1_PersistentVolumeList_To_api_PersistentVolumeList, - autoConvert_v1_PersistentVolumeSource_To_api_PersistentVolumeSource, - autoConvert_v1_PersistentVolumeSpec_To_api_PersistentVolumeSpec, - autoConvert_v1_PersistentVolumeStatus_To_api_PersistentVolumeStatus, - autoConvert_v1_PersistentVolume_To_api_PersistentVolume, - autoConvert_v1_PodAttachOptions_To_api_PodAttachOptions, - autoConvert_v1_PodCondition_To_api_PodCondition, - autoConvert_v1_PodExecOptions_To_api_PodExecOptions, - autoConvert_v1_PodList_To_api_PodList, - autoConvert_v1_PodLogOptions_To_api_PodLogOptions, - autoConvert_v1_PodProxyOptions_To_api_PodProxyOptions, - autoConvert_v1_PodSpec_To_api_PodSpec, - autoConvert_v1_PodStatusResult_To_api_PodStatusResult, - autoConvert_v1_PodStatus_To_api_PodStatus, - autoConvert_v1_PodTemplateList_To_api_PodTemplateList, - autoConvert_v1_PodTemplateSpec_To_api_PodTemplateSpec, - autoConvert_v1_PodTemplate_To_api_PodTemplate, - autoConvert_v1_Pod_To_api_Pod, - autoConvert_v1_Probe_To_api_Probe, - autoConvert_v1_RBDVolumeSource_To_api_RBDVolumeSource, - autoConvert_v1_RangeAllocation_To_api_RangeAllocation, - autoConvert_v1_ReplicationControllerList_To_api_ReplicationControllerList, - autoConvert_v1_ReplicationControllerSpec_To_api_ReplicationControllerSpec, - autoConvert_v1_ReplicationControllerStatus_To_api_ReplicationControllerStatus, - autoConvert_v1_ReplicationController_To_api_ReplicationController, - autoConvert_v1_ResourceQuotaList_To_api_ResourceQuotaList, - autoConvert_v1_ResourceQuotaSpec_To_api_ResourceQuotaSpec, - autoConvert_v1_ResourceQuotaStatus_To_api_ResourceQuotaStatus, - autoConvert_v1_ResourceQuota_To_api_ResourceQuota, - autoConvert_v1_ResourceRequirements_To_api_ResourceRequirements, - autoConvert_v1_SELinuxOptions_To_api_SELinuxOptions, - autoConvert_v1_SecretKeySelector_To_api_SecretKeySelector, - autoConvert_v1_SecretList_To_api_SecretList, - autoConvert_v1_SecretVolumeSource_To_api_SecretVolumeSource, - autoConvert_v1_Secret_To_api_Secret, - autoConvert_v1_SecurityContext_To_api_SecurityContext, - autoConvert_v1_SerializedReference_To_api_SerializedReference, - autoConvert_v1_ServiceAccountList_To_api_ServiceAccountList, - autoConvert_v1_ServiceAccount_To_api_ServiceAccount, - autoConvert_v1_ServiceList_To_api_ServiceList, - autoConvert_v1_ServicePort_To_api_ServicePort, - autoConvert_v1_ServiceSpec_To_api_ServiceSpec, - autoConvert_v1_ServiceStatus_To_api_ServiceStatus, - autoConvert_v1_Service_To_api_Service, - autoConvert_v1_TCPSocketAction_To_api_TCPSocketAction, - autoConvert_v1_VolumeMount_To_api_VolumeMount, - autoConvert_v1_VolumeSource_To_api_VolumeSource, - autoConvert_v1_Volume_To_api_Volume, - ) - if err != nil { - // If one of the conversion functions is malformed, detect it immediately. - panic(err) +func autoConvert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *VolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.VolumeSource))(in) } + if in.HostPath != nil { + in, out := &in.HostPath, &out.HostPath + *out = new(HostPathVolumeSource) + if err := Convert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.HostPath = nil + } + if in.EmptyDir != nil { + in, out := &in.EmptyDir, &out.EmptyDir + *out = new(EmptyDirVolumeSource) + if err := Convert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.EmptyDir = nil + } + if in.GCEPersistentDisk != nil { + in, out := &in.GCEPersistentDisk, &out.GCEPersistentDisk + *out = new(GCEPersistentDiskVolumeSource) + if err := Convert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.GCEPersistentDisk = nil + } + if in.AWSElasticBlockStore != nil { + in, out := &in.AWSElasticBlockStore, &out.AWSElasticBlockStore + *out = new(AWSElasticBlockStoreVolumeSource) + if err := Convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.AWSElasticBlockStore = nil + } + if in.GitRepo != nil { + in, out := &in.GitRepo, &out.GitRepo + *out = new(GitRepoVolumeSource) + if err := Convert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.GitRepo = nil + } + if in.Secret != nil { + in, out := &in.Secret, &out.Secret + *out = new(SecretVolumeSource) + if err := Convert_api_SecretVolumeSource_To_v1_SecretVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.Secret = nil + } + if in.NFS != nil { + in, out := &in.NFS, &out.NFS + *out = new(NFSVolumeSource) + if err := Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.NFS = nil + } + if in.ISCSI != nil { + in, out := &in.ISCSI, &out.ISCSI + *out = new(ISCSIVolumeSource) + if err := Convert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.ISCSI = nil + } + if in.Glusterfs != nil { + in, out := &in.Glusterfs, &out.Glusterfs + *out = new(GlusterfsVolumeSource) + if err := Convert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.Glusterfs = nil + } + if in.PersistentVolumeClaim != nil { + in, out := &in.PersistentVolumeClaim, &out.PersistentVolumeClaim + *out = new(PersistentVolumeClaimVolumeSource) + if err := Convert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.PersistentVolumeClaim = nil + } + if in.RBD != nil { + in, out := &in.RBD, &out.RBD + *out = new(RBDVolumeSource) + if err := Convert_api_RBDVolumeSource_To_v1_RBDVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.RBD = nil + } + if in.FlexVolume != nil { + in, out := &in.FlexVolume, &out.FlexVolume + *out = new(FlexVolumeSource) + if err := Convert_api_FlexVolumeSource_To_v1_FlexVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.FlexVolume = nil + } + if in.Cinder != nil { + in, out := &in.Cinder, &out.Cinder + *out = new(CinderVolumeSource) + if err := Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.Cinder = nil + } + if in.CephFS != nil { + in, out := &in.CephFS, &out.CephFS + *out = new(CephFSVolumeSource) + if err := Convert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.CephFS = nil + } + if in.Flocker != nil { + in, out := &in.Flocker, &out.Flocker + *out = new(FlockerVolumeSource) + if err := Convert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.Flocker = nil + } + if in.DownwardAPI != nil { + in, out := &in.DownwardAPI, &out.DownwardAPI + *out = new(DownwardAPIVolumeSource) + if err := Convert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.DownwardAPI = nil + } + if in.FC != nil { + in, out := &in.FC, &out.FC + *out = new(FCVolumeSource) + if err := Convert_api_FCVolumeSource_To_v1_FCVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.FC = nil + } + if in.AzureFile != nil { + in, out := &in.AzureFile, &out.AzureFile + *out = new(AzureFileVolumeSource) + if err := Convert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.AzureFile = nil + } + if in.ConfigMap != nil { + in, out := &in.ConfigMap, &out.ConfigMap + *out = new(ConfigMapVolumeSource) + if err := Convert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(*in, *out, s); err != nil { + return err + } + } else { + out.ConfigMap = nil + } + return nil +} + +func Convert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *VolumeSource, s conversion.Scope) error { + return autoConvert_api_VolumeSource_To_v1_VolumeSource(in, out, s) } diff --git a/vendor/k8s.io/kubernetes/pkg/api/v1/conversion_test.go b/vendor/k8s.io/kubernetes/pkg/api/v1/conversion_test.go index 8cc2f0474..ac0b4e682 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/v1/conversion_test.go +++ b/vendor/k8s.io/kubernetes/pkg/api/v1/conversion_test.go @@ -17,13 +17,105 @@ limitations under the License. package v1_test import ( + "net/url" + "reflect" "testing" + "time" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/unversioned" versioned "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/diff" ) +func TestPodLogOptions(t *testing.T) { + sinceSeconds := int64(1) + sinceTime := unversioned.NewTime(time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC).Local()) + tailLines := int64(2) + limitBytes := int64(3) + + versionedLogOptions := &versioned.PodLogOptions{ + Container: "mycontainer", + Follow: true, + Previous: true, + SinceSeconds: &sinceSeconds, + SinceTime: &sinceTime, + Timestamps: true, + TailLines: &tailLines, + LimitBytes: &limitBytes, + } + unversionedLogOptions := &api.PodLogOptions{ + Container: "mycontainer", + Follow: true, + Previous: true, + SinceSeconds: &sinceSeconds, + SinceTime: &sinceTime, + Timestamps: true, + TailLines: &tailLines, + LimitBytes: &limitBytes, + } + expectedParameters := url.Values{ + "container": {"mycontainer"}, + "follow": {"true"}, + "previous": {"true"}, + "sinceSeconds": {"1"}, + "sinceTime": {"2000-01-01T12:34:56Z"}, + "timestamps": {"true"}, + "tailLines": {"2"}, + "limitBytes": {"3"}, + } + + codec := runtime.NewParameterCodec(api.Scheme) + + // unversioned -> query params + { + actualParameters, err := codec.EncodeParameters(unversionedLogOptions, versioned.SchemeGroupVersion) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(actualParameters, expectedParameters) { + t.Fatalf("Expected\n%#v\ngot\n%#v", expectedParameters, actualParameters) + } + } + + // versioned -> query params + { + actualParameters, err := codec.EncodeParameters(versionedLogOptions, versioned.SchemeGroupVersion) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(actualParameters, expectedParameters) { + t.Fatalf("Expected\n%#v\ngot\n%#v", expectedParameters, actualParameters) + } + } + + // query params -> versioned + { + convertedLogOptions := &versioned.PodLogOptions{} + err := codec.DecodeParameters(expectedParameters, versioned.SchemeGroupVersion, convertedLogOptions) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(convertedLogOptions, versionedLogOptions) { + t.Fatalf("Unexpected deserialization:\n%s", diff.ObjectGoPrintSideBySide(versionedLogOptions, convertedLogOptions)) + } + } + + // query params -> unversioned + { + convertedLogOptions := &api.PodLogOptions{} + err := codec.DecodeParameters(expectedParameters, versioned.SchemeGroupVersion, convertedLogOptions) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(convertedLogOptions, unversionedLogOptions) { + t.Fatalf("Unexpected deserialization:\n%s", diff.ObjectGoPrintSideBySide(unversionedLogOptions, convertedLogOptions)) + } + } +} + // TestPodSpecConversion tests that ServiceAccount is an alias for // ServiceAccountName. func TestPodSpecConversion(t *testing.T) { diff --git a/vendor/k8s.io/kubernetes/pkg/api/v1/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/api/v1/deep_copy_generated.go index c74acad29..1597f9f3e 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/v1/deep_copy_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/api/v1/deep_copy_generated.go @@ -1,5 +1,7 @@ +// +build !ignore_autogenerated + /* -Copyright 2015 The Kubernetes Authors All rights reserved. +Copyright 2016 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,58 +16,168 @@ See the License for the specific language governing permissions and limitations under the License. */ -// DO NOT EDIT. THIS FILE IS AUTO-GENERATED BY $KUBEROOT/hack/update-generated-deep-copies.sh. +// This file was autogenerated by deepcopy-gen. Do not edit it manually! package v1 import ( - time "time" - api "k8s.io/kubernetes/pkg/api" resource "k8s.io/kubernetes/pkg/api/resource" unversioned "k8s.io/kubernetes/pkg/api/unversioned" conversion "k8s.io/kubernetes/pkg/conversion" runtime "k8s.io/kubernetes/pkg/runtime" + types "k8s.io/kubernetes/pkg/types" intstr "k8s.io/kubernetes/pkg/util/intstr" - inf "speter.net/go/exp/math/dec/inf" ) -func deepCopy_resource_Quantity(in resource.Quantity, out *resource.Quantity, c *conversion.Cloner) error { - if in.Amount != nil { - if newVal, err := c.DeepCopy(in.Amount); err != nil { - return err - } else { - out.Amount = newVal.(*inf.Dec) - } - } else { - out.Amount = nil +func init() { + if err := api.Scheme.AddGeneratedDeepCopyFuncs( + DeepCopy_v1_AWSElasticBlockStoreVolumeSource, + DeepCopy_v1_Affinity, + DeepCopy_v1_AzureFileVolumeSource, + DeepCopy_v1_Binding, + DeepCopy_v1_Capabilities, + DeepCopy_v1_CephFSVolumeSource, + DeepCopy_v1_CinderVolumeSource, + DeepCopy_v1_ComponentCondition, + DeepCopy_v1_ComponentStatus, + DeepCopy_v1_ComponentStatusList, + DeepCopy_v1_ConfigMap, + DeepCopy_v1_ConfigMapKeySelector, + DeepCopy_v1_ConfigMapList, + DeepCopy_v1_ConfigMapVolumeSource, + DeepCopy_v1_Container, + DeepCopy_v1_ContainerImage, + DeepCopy_v1_ContainerPort, + DeepCopy_v1_ContainerState, + DeepCopy_v1_ContainerStateRunning, + DeepCopy_v1_ContainerStateTerminated, + DeepCopy_v1_ContainerStateWaiting, + DeepCopy_v1_ContainerStatus, + DeepCopy_v1_DaemonEndpoint, + DeepCopy_v1_DeleteOptions, + DeepCopy_v1_DownwardAPIVolumeFile, + DeepCopy_v1_DownwardAPIVolumeSource, + DeepCopy_v1_EmptyDirVolumeSource, + DeepCopy_v1_EndpointAddress, + DeepCopy_v1_EndpointPort, + DeepCopy_v1_EndpointSubset, + DeepCopy_v1_Endpoints, + DeepCopy_v1_EndpointsList, + DeepCopy_v1_EnvVar, + DeepCopy_v1_EnvVarSource, + DeepCopy_v1_Event, + DeepCopy_v1_EventList, + DeepCopy_v1_EventSource, + DeepCopy_v1_ExecAction, + DeepCopy_v1_ExportOptions, + DeepCopy_v1_FCVolumeSource, + DeepCopy_v1_FlexVolumeSource, + DeepCopy_v1_FlockerVolumeSource, + DeepCopy_v1_GCEPersistentDiskVolumeSource, + DeepCopy_v1_GitRepoVolumeSource, + DeepCopy_v1_GlusterfsVolumeSource, + DeepCopy_v1_HTTPGetAction, + DeepCopy_v1_HTTPHeader, + DeepCopy_v1_Handler, + DeepCopy_v1_HostPathVolumeSource, + DeepCopy_v1_ISCSIVolumeSource, + DeepCopy_v1_KeyToPath, + DeepCopy_v1_Lifecycle, + DeepCopy_v1_LimitRange, + DeepCopy_v1_LimitRangeItem, + DeepCopy_v1_LimitRangeList, + DeepCopy_v1_LimitRangeSpec, + DeepCopy_v1_List, + DeepCopy_v1_ListOptions, + DeepCopy_v1_LoadBalancerIngress, + DeepCopy_v1_LoadBalancerStatus, + DeepCopy_v1_LocalObjectReference, + DeepCopy_v1_NFSVolumeSource, + DeepCopy_v1_Namespace, + DeepCopy_v1_NamespaceList, + DeepCopy_v1_NamespaceSpec, + DeepCopy_v1_NamespaceStatus, + DeepCopy_v1_Node, + DeepCopy_v1_NodeAddress, + DeepCopy_v1_NodeAffinity, + DeepCopy_v1_NodeCondition, + DeepCopy_v1_NodeDaemonEndpoints, + DeepCopy_v1_NodeList, + DeepCopy_v1_NodeProxyOptions, + DeepCopy_v1_NodeSelector, + DeepCopy_v1_NodeSelectorRequirement, + DeepCopy_v1_NodeSelectorTerm, + DeepCopy_v1_NodeSpec, + DeepCopy_v1_NodeStatus, + DeepCopy_v1_NodeSystemInfo, + DeepCopy_v1_ObjectFieldSelector, + DeepCopy_v1_ObjectMeta, + DeepCopy_v1_ObjectReference, + DeepCopy_v1_PersistentVolume, + DeepCopy_v1_PersistentVolumeClaim, + DeepCopy_v1_PersistentVolumeClaimList, + DeepCopy_v1_PersistentVolumeClaimSpec, + DeepCopy_v1_PersistentVolumeClaimStatus, + DeepCopy_v1_PersistentVolumeClaimVolumeSource, + DeepCopy_v1_PersistentVolumeList, + DeepCopy_v1_PersistentVolumeSource, + DeepCopy_v1_PersistentVolumeSpec, + DeepCopy_v1_PersistentVolumeStatus, + DeepCopy_v1_Pod, + DeepCopy_v1_PodAttachOptions, + DeepCopy_v1_PodCondition, + DeepCopy_v1_PodExecOptions, + DeepCopy_v1_PodList, + DeepCopy_v1_PodLogOptions, + DeepCopy_v1_PodProxyOptions, + DeepCopy_v1_PodSecurityContext, + DeepCopy_v1_PodSpec, + DeepCopy_v1_PodStatus, + DeepCopy_v1_PodStatusResult, + DeepCopy_v1_PodTemplate, + DeepCopy_v1_PodTemplateList, + DeepCopy_v1_PodTemplateSpec, + DeepCopy_v1_Preconditions, + DeepCopy_v1_PreferredSchedulingTerm, + DeepCopy_v1_Probe, + DeepCopy_v1_RBDVolumeSource, + DeepCopy_v1_RangeAllocation, + DeepCopy_v1_ReplicationController, + DeepCopy_v1_ReplicationControllerList, + DeepCopy_v1_ReplicationControllerSpec, + DeepCopy_v1_ReplicationControllerStatus, + DeepCopy_v1_ResourceQuota, + DeepCopy_v1_ResourceQuotaList, + DeepCopy_v1_ResourceQuotaSpec, + DeepCopy_v1_ResourceQuotaStatus, + DeepCopy_v1_ResourceRequirements, + DeepCopy_v1_SELinuxOptions, + DeepCopy_v1_Secret, + DeepCopy_v1_SecretKeySelector, + DeepCopy_v1_SecretList, + DeepCopy_v1_SecretVolumeSource, + DeepCopy_v1_SecurityContext, + DeepCopy_v1_SerializedReference, + DeepCopy_v1_Service, + DeepCopy_v1_ServiceAccount, + DeepCopy_v1_ServiceAccountList, + DeepCopy_v1_ServiceList, + DeepCopy_v1_ServicePort, + DeepCopy_v1_ServiceProxyOptions, + DeepCopy_v1_ServiceSpec, + DeepCopy_v1_ServiceStatus, + DeepCopy_v1_TCPSocketAction, + DeepCopy_v1_Volume, + DeepCopy_v1_VolumeMount, + DeepCopy_v1_VolumeSource, + ); err != nil { + // if one of the deep copy functions is malformed, detect it immediately. + panic(err) } - out.Format = in.Format - return nil } -func deepCopy_unversioned_ListMeta(in unversioned.ListMeta, out *unversioned.ListMeta, c *conversion.Cloner) error { - out.SelfLink = in.SelfLink - out.ResourceVersion = in.ResourceVersion - return nil -} - -func deepCopy_unversioned_Time(in unversioned.Time, out *unversioned.Time, c *conversion.Cloner) error { - if newVal, err := c.DeepCopy(in.Time); err != nil { - return err - } else { - out.Time = newVal.(time.Time) - } - return nil -} - -func deepCopy_unversioned_TypeMeta(in unversioned.TypeMeta, out *unversioned.TypeMeta, c *conversion.Cloner) error { - out.Kind = in.Kind - out.APIVersion = in.APIVersion - return nil -} - -func deepCopy_v1_AWSElasticBlockStoreVolumeSource(in AWSElasticBlockStoreVolumeSource, out *AWSElasticBlockStoreVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_AWSElasticBlockStoreVolumeSource(in AWSElasticBlockStoreVolumeSource, out *AWSElasticBlockStoreVolumeSource, c *conversion.Cloner) error { out.VolumeID = in.VolumeID out.FSType = in.FSType out.Partition = in.Partition @@ -73,39 +185,54 @@ func deepCopy_v1_AWSElasticBlockStoreVolumeSource(in AWSElasticBlockStoreVolumeS return nil } -func deepCopy_v1_AzureFileVolumeSource(in AzureFileVolumeSource, out *AzureFileVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_Affinity(in Affinity, out *Affinity, c *conversion.Cloner) error { + if in.NodeAffinity != nil { + in, out := in.NodeAffinity, &out.NodeAffinity + *out = new(NodeAffinity) + if err := DeepCopy_v1_NodeAffinity(*in, *out, c); err != nil { + return err + } + } else { + out.NodeAffinity = nil + } + return nil +} + +func DeepCopy_v1_AzureFileVolumeSource(in AzureFileVolumeSource, out *AzureFileVolumeSource, c *conversion.Cloner) error { out.SecretName = in.SecretName out.ShareName = in.ShareName out.ReadOnly = in.ReadOnly return nil } -func deepCopy_v1_Binding(in Binding, out *Binding, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_Binding(in Binding, out *Binding, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectReference(in.Target, &out.Target, c); err != nil { + if err := DeepCopy_v1_ObjectReference(in.Target, &out.Target, c); err != nil { return err } return nil } -func deepCopy_v1_Capabilities(in Capabilities, out *Capabilities, c *conversion.Cloner) error { +func DeepCopy_v1_Capabilities(in Capabilities, out *Capabilities, c *conversion.Cloner) error { if in.Add != nil { - out.Add = make([]Capability, len(in.Add)) - for i := range in.Add { - out.Add[i] = in.Add[i] + in, out := in.Add, &out.Add + *out = make([]Capability, len(in)) + for i := range in { + (*out)[i] = in[i] } } else { out.Add = nil } if in.Drop != nil { - out.Drop = make([]Capability, len(in.Drop)) - for i := range in.Drop { - out.Drop[i] = in.Drop[i] + in, out := in.Drop, &out.Drop + *out = make([]Capability, len(in)) + for i := range in { + (*out)[i] = in[i] } } else { out.Drop = nil @@ -113,12 +240,11 @@ func deepCopy_v1_Capabilities(in Capabilities, out *Capabilities, c *conversion. return nil } -func deepCopy_v1_CephFSVolumeSource(in CephFSVolumeSource, out *CephFSVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_CephFSVolumeSource(in CephFSVolumeSource, out *CephFSVolumeSource, c *conversion.Cloner) error { if in.Monitors != nil { - out.Monitors = make([]string, len(in.Monitors)) - for i := range in.Monitors { - out.Monitors[i] = in.Monitors[i] - } + in, out := in.Monitors, &out.Monitors + *out = make([]string, len(in)) + copy(*out, in) } else { out.Monitors = nil } @@ -126,8 +252,9 @@ func deepCopy_v1_CephFSVolumeSource(in CephFSVolumeSource, out *CephFSVolumeSour out.User = in.User out.SecretFile = in.SecretFile if in.SecretRef != nil { - out.SecretRef = new(LocalObjectReference) - if err := deepCopy_v1_LocalObjectReference(*in.SecretRef, out.SecretRef, c); err != nil { + in, out := in.SecretRef, &out.SecretRef + *out = new(LocalObjectReference) + if err := DeepCopy_v1_LocalObjectReference(*in, *out, c); err != nil { return err } } else { @@ -137,14 +264,14 @@ func deepCopy_v1_CephFSVolumeSource(in CephFSVolumeSource, out *CephFSVolumeSour return nil } -func deepCopy_v1_CinderVolumeSource(in CinderVolumeSource, out *CinderVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_CinderVolumeSource(in CinderVolumeSource, out *CinderVolumeSource, c *conversion.Cloner) error { out.VolumeID = in.VolumeID out.FSType = in.FSType out.ReadOnly = in.ReadOnly return nil } -func deepCopy_v1_ComponentCondition(in ComponentCondition, out *ComponentCondition, c *conversion.Cloner) error { +func DeepCopy_v1_ComponentCondition(in ComponentCondition, out *ComponentCondition, c *conversion.Cloner) error { out.Type = in.Type out.Status = in.Status out.Message = in.Message @@ -152,17 +279,18 @@ func deepCopy_v1_ComponentCondition(in ComponentCondition, out *ComponentConditi return nil } -func deepCopy_v1_ComponentStatus(in ComponentStatus, out *ComponentStatus, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_ComponentStatus(in ComponentStatus, out *ComponentStatus, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } if in.Conditions != nil { - out.Conditions = make([]ComponentCondition, len(in.Conditions)) - for i := range in.Conditions { - if err := deepCopy_v1_ComponentCondition(in.Conditions[i], &out.Conditions[i], c); err != nil { + in, out := in.Conditions, &out.Conditions + *out = make([]ComponentCondition, len(in)) + for i := range in { + if err := DeepCopy_v1_ComponentCondition(in[i], &(*out)[i], c); err != nil { return err } } @@ -172,17 +300,18 @@ func deepCopy_v1_ComponentStatus(in ComponentStatus, out *ComponentStatus, c *co return nil } -func deepCopy_v1_ComponentStatusList(in ComponentStatusList, out *ComponentStatusList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_ComponentStatusList(in ComponentStatusList, out *ComponentStatusList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]ComponentStatus, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_ComponentStatus(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]ComponentStatus, len(in)) + for i := range in { + if err := DeepCopy_v1_ComponentStatus(in[i], &(*out)[i], c); err != nil { return err } } @@ -192,17 +321,18 @@ func deepCopy_v1_ComponentStatusList(in ComponentStatusList, out *ComponentStatu return nil } -func deepCopy_v1_ConfigMap(in ConfigMap, out *ConfigMap, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_ConfigMap(in ConfigMap, out *ConfigMap, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } if in.Data != nil { - out.Data = make(map[string]string) - for key, val := range in.Data { - out.Data[key] = val + in, out := in.Data, &out.Data + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val } } else { out.Data = nil @@ -210,25 +340,26 @@ func deepCopy_v1_ConfigMap(in ConfigMap, out *ConfigMap, c *conversion.Cloner) e return nil } -func deepCopy_v1_ConfigMapKeySelector(in ConfigMapKeySelector, out *ConfigMapKeySelector, c *conversion.Cloner) error { - if err := deepCopy_v1_LocalObjectReference(in.LocalObjectReference, &out.LocalObjectReference, c); err != nil { +func DeepCopy_v1_ConfigMapKeySelector(in ConfigMapKeySelector, out *ConfigMapKeySelector, c *conversion.Cloner) error { + if err := DeepCopy_v1_LocalObjectReference(in.LocalObjectReference, &out.LocalObjectReference, c); err != nil { return err } out.Key = in.Key return nil } -func deepCopy_v1_ConfigMapList(in ConfigMapList, out *ConfigMapList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_ConfigMapList(in ConfigMapList, out *ConfigMapList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]ConfigMap, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_ConfigMap(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]ConfigMap, len(in)) + for i := range in { + if err := DeepCopy_v1_ConfigMap(in[i], &(*out)[i], c); err != nil { return err } } @@ -238,14 +369,15 @@ func deepCopy_v1_ConfigMapList(in ConfigMapList, out *ConfigMapList, c *conversi return nil } -func deepCopy_v1_ConfigMapVolumeSource(in ConfigMapVolumeSource, out *ConfigMapVolumeSource, c *conversion.Cloner) error { - if err := deepCopy_v1_LocalObjectReference(in.LocalObjectReference, &out.LocalObjectReference, c); err != nil { +func DeepCopy_v1_ConfigMapVolumeSource(in ConfigMapVolumeSource, out *ConfigMapVolumeSource, c *conversion.Cloner) error { + if err := DeepCopy_v1_LocalObjectReference(in.LocalObjectReference, &out.LocalObjectReference, c); err != nil { return err } if in.Items != nil { - out.Items = make([]KeyToPath, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_KeyToPath(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]KeyToPath, len(in)) + for i := range in { + if err := DeepCopy_v1_KeyToPath(in[i], &(*out)[i], c); err != nil { return err } } @@ -255,30 +387,29 @@ func deepCopy_v1_ConfigMapVolumeSource(in ConfigMapVolumeSource, out *ConfigMapV return nil } -func deepCopy_v1_Container(in Container, out *Container, c *conversion.Cloner) error { +func DeepCopy_v1_Container(in Container, out *Container, c *conversion.Cloner) error { out.Name = in.Name out.Image = in.Image if in.Command != nil { - out.Command = make([]string, len(in.Command)) - for i := range in.Command { - out.Command[i] = in.Command[i] - } + in, out := in.Command, &out.Command + *out = make([]string, len(in)) + copy(*out, in) } else { out.Command = nil } if in.Args != nil { - out.Args = make([]string, len(in.Args)) - for i := range in.Args { - out.Args[i] = in.Args[i] - } + in, out := in.Args, &out.Args + *out = make([]string, len(in)) + copy(*out, in) } else { out.Args = nil } out.WorkingDir = in.WorkingDir if in.Ports != nil { - out.Ports = make([]ContainerPort, len(in.Ports)) - for i := range in.Ports { - if err := deepCopy_v1_ContainerPort(in.Ports[i], &out.Ports[i], c); err != nil { + in, out := in.Ports, &out.Ports + *out = make([]ContainerPort, len(in)) + for i := range in { + if err := DeepCopy_v1_ContainerPort(in[i], &(*out)[i], c); err != nil { return err } } @@ -286,22 +417,24 @@ func deepCopy_v1_Container(in Container, out *Container, c *conversion.Cloner) e out.Ports = nil } if in.Env != nil { - out.Env = make([]EnvVar, len(in.Env)) - for i := range in.Env { - if err := deepCopy_v1_EnvVar(in.Env[i], &out.Env[i], c); err != nil { + in, out := in.Env, &out.Env + *out = make([]EnvVar, len(in)) + for i := range in { + if err := DeepCopy_v1_EnvVar(in[i], &(*out)[i], c); err != nil { return err } } } else { out.Env = nil } - if err := deepCopy_v1_ResourceRequirements(in.Resources, &out.Resources, c); err != nil { + if err := DeepCopy_v1_ResourceRequirements(in.Resources, &out.Resources, c); err != nil { return err } if in.VolumeMounts != nil { - out.VolumeMounts = make([]VolumeMount, len(in.VolumeMounts)) - for i := range in.VolumeMounts { - if err := deepCopy_v1_VolumeMount(in.VolumeMounts[i], &out.VolumeMounts[i], c); err != nil { + in, out := in.VolumeMounts, &out.VolumeMounts + *out = make([]VolumeMount, len(in)) + for i := range in { + if err := DeepCopy_v1_VolumeMount(in[i], &(*out)[i], c); err != nil { return err } } @@ -309,24 +442,27 @@ func deepCopy_v1_Container(in Container, out *Container, c *conversion.Cloner) e out.VolumeMounts = nil } if in.LivenessProbe != nil { - out.LivenessProbe = new(Probe) - if err := deepCopy_v1_Probe(*in.LivenessProbe, out.LivenessProbe, c); err != nil { + in, out := in.LivenessProbe, &out.LivenessProbe + *out = new(Probe) + if err := DeepCopy_v1_Probe(*in, *out, c); err != nil { return err } } else { out.LivenessProbe = nil } if in.ReadinessProbe != nil { - out.ReadinessProbe = new(Probe) - if err := deepCopy_v1_Probe(*in.ReadinessProbe, out.ReadinessProbe, c); err != nil { + in, out := in.ReadinessProbe, &out.ReadinessProbe + *out = new(Probe) + if err := DeepCopy_v1_Probe(*in, *out, c); err != nil { return err } } else { out.ReadinessProbe = nil } if in.Lifecycle != nil { - out.Lifecycle = new(Lifecycle) - if err := deepCopy_v1_Lifecycle(*in.Lifecycle, out.Lifecycle, c); err != nil { + in, out := in.Lifecycle, &out.Lifecycle + *out = new(Lifecycle) + if err := DeepCopy_v1_Lifecycle(*in, *out, c); err != nil { return err } } else { @@ -335,8 +471,9 @@ func deepCopy_v1_Container(in Container, out *Container, c *conversion.Cloner) e out.TerminationMessagePath = in.TerminationMessagePath out.ImagePullPolicy = in.ImagePullPolicy if in.SecurityContext != nil { - out.SecurityContext = new(SecurityContext) - if err := deepCopy_v1_SecurityContext(*in.SecurityContext, out.SecurityContext, c); err != nil { + in, out := in.SecurityContext, &out.SecurityContext + *out = new(SecurityContext) + if err := DeepCopy_v1_SecurityContext(*in, *out, c); err != nil { return err } } else { @@ -348,20 +485,19 @@ func deepCopy_v1_Container(in Container, out *Container, c *conversion.Cloner) e return nil } -func deepCopy_v1_ContainerImage(in ContainerImage, out *ContainerImage, c *conversion.Cloner) error { - if in.RepoTags != nil { - out.RepoTags = make([]string, len(in.RepoTags)) - for i := range in.RepoTags { - out.RepoTags[i] = in.RepoTags[i] - } +func DeepCopy_v1_ContainerImage(in ContainerImage, out *ContainerImage, c *conversion.Cloner) error { + if in.Names != nil { + in, out := in.Names, &out.Names + *out = make([]string, len(in)) + copy(*out, in) } else { - out.RepoTags = nil + out.Names = nil } - out.Size = in.Size + out.SizeBytes = in.SizeBytes return nil } -func deepCopy_v1_ContainerPort(in ContainerPort, out *ContainerPort, c *conversion.Cloner) error { +func DeepCopy_v1_ContainerPort(in ContainerPort, out *ContainerPort, c *conversion.Cloner) error { out.Name = in.Name out.HostPort = in.HostPort out.ContainerPort = in.ContainerPort @@ -370,26 +506,29 @@ func deepCopy_v1_ContainerPort(in ContainerPort, out *ContainerPort, c *conversi return nil } -func deepCopy_v1_ContainerState(in ContainerState, out *ContainerState, c *conversion.Cloner) error { +func DeepCopy_v1_ContainerState(in ContainerState, out *ContainerState, c *conversion.Cloner) error { if in.Waiting != nil { - out.Waiting = new(ContainerStateWaiting) - if err := deepCopy_v1_ContainerStateWaiting(*in.Waiting, out.Waiting, c); err != nil { + in, out := in.Waiting, &out.Waiting + *out = new(ContainerStateWaiting) + if err := DeepCopy_v1_ContainerStateWaiting(*in, *out, c); err != nil { return err } } else { out.Waiting = nil } if in.Running != nil { - out.Running = new(ContainerStateRunning) - if err := deepCopy_v1_ContainerStateRunning(*in.Running, out.Running, c); err != nil { + in, out := in.Running, &out.Running + *out = new(ContainerStateRunning) + if err := DeepCopy_v1_ContainerStateRunning(*in, *out, c); err != nil { return err } } else { out.Running = nil } if in.Terminated != nil { - out.Terminated = new(ContainerStateTerminated) - if err := deepCopy_v1_ContainerStateTerminated(*in.Terminated, out.Terminated, c); err != nil { + in, out := in.Terminated, &out.Terminated + *out = new(ContainerStateTerminated) + if err := DeepCopy_v1_ContainerStateTerminated(*in, *out, c); err != nil { return err } } else { @@ -398,40 +537,40 @@ func deepCopy_v1_ContainerState(in ContainerState, out *ContainerState, c *conve return nil } -func deepCopy_v1_ContainerStateRunning(in ContainerStateRunning, out *ContainerStateRunning, c *conversion.Cloner) error { - if err := deepCopy_unversioned_Time(in.StartedAt, &out.StartedAt, c); err != nil { +func DeepCopy_v1_ContainerStateRunning(in ContainerStateRunning, out *ContainerStateRunning, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_Time(in.StartedAt, &out.StartedAt, c); err != nil { return err } return nil } -func deepCopy_v1_ContainerStateTerminated(in ContainerStateTerminated, out *ContainerStateTerminated, c *conversion.Cloner) error { +func DeepCopy_v1_ContainerStateTerminated(in ContainerStateTerminated, out *ContainerStateTerminated, c *conversion.Cloner) error { out.ExitCode = in.ExitCode out.Signal = in.Signal out.Reason = in.Reason out.Message = in.Message - if err := deepCopy_unversioned_Time(in.StartedAt, &out.StartedAt, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.StartedAt, &out.StartedAt, c); err != nil { return err } - if err := deepCopy_unversioned_Time(in.FinishedAt, &out.FinishedAt, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.FinishedAt, &out.FinishedAt, c); err != nil { return err } out.ContainerID = in.ContainerID return nil } -func deepCopy_v1_ContainerStateWaiting(in ContainerStateWaiting, out *ContainerStateWaiting, c *conversion.Cloner) error { +func DeepCopy_v1_ContainerStateWaiting(in ContainerStateWaiting, out *ContainerStateWaiting, c *conversion.Cloner) error { out.Reason = in.Reason out.Message = in.Message return nil } -func deepCopy_v1_ContainerStatus(in ContainerStatus, out *ContainerStatus, c *conversion.Cloner) error { +func DeepCopy_v1_ContainerStatus(in ContainerStatus, out *ContainerStatus, c *conversion.Cloner) error { out.Name = in.Name - if err := deepCopy_v1_ContainerState(in.State, &out.State, c); err != nil { + if err := DeepCopy_v1_ContainerState(in.State, &out.State, c); err != nil { return err } - if err := deepCopy_v1_ContainerState(in.LastTerminationState, &out.LastTerminationState, c); err != nil { + if err := DeepCopy_v1_ContainerState(in.LastTerminationState, &out.LastTerminationState, c); err != nil { return err } out.Ready = in.Ready @@ -442,37 +581,48 @@ func deepCopy_v1_ContainerStatus(in ContainerStatus, out *ContainerStatus, c *co return nil } -func deepCopy_v1_DaemonEndpoint(in DaemonEndpoint, out *DaemonEndpoint, c *conversion.Cloner) error { +func DeepCopy_v1_DaemonEndpoint(in DaemonEndpoint, out *DaemonEndpoint, c *conversion.Cloner) error { out.Port = in.Port return nil } -func deepCopy_v1_DeleteOptions(in DeleteOptions, out *DeleteOptions, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_DeleteOptions(in DeleteOptions, out *DeleteOptions, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } if in.GracePeriodSeconds != nil { - out.GracePeriodSeconds = new(int64) - *out.GracePeriodSeconds = *in.GracePeriodSeconds + in, out := in.GracePeriodSeconds, &out.GracePeriodSeconds + *out = new(int64) + **out = *in } else { out.GracePeriodSeconds = nil } + if in.Preconditions != nil { + in, out := in.Preconditions, &out.Preconditions + *out = new(Preconditions) + if err := DeepCopy_v1_Preconditions(*in, *out, c); err != nil { + return err + } + } else { + out.Preconditions = nil + } return nil } -func deepCopy_v1_DownwardAPIVolumeFile(in DownwardAPIVolumeFile, out *DownwardAPIVolumeFile, c *conversion.Cloner) error { +func DeepCopy_v1_DownwardAPIVolumeFile(in DownwardAPIVolumeFile, out *DownwardAPIVolumeFile, c *conversion.Cloner) error { out.Path = in.Path - if err := deepCopy_v1_ObjectFieldSelector(in.FieldRef, &out.FieldRef, c); err != nil { + if err := DeepCopy_v1_ObjectFieldSelector(in.FieldRef, &out.FieldRef, c); err != nil { return err } return nil } -func deepCopy_v1_DownwardAPIVolumeSource(in DownwardAPIVolumeSource, out *DownwardAPIVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_DownwardAPIVolumeSource(in DownwardAPIVolumeSource, out *DownwardAPIVolumeSource, c *conversion.Cloner) error { if in.Items != nil { - out.Items = make([]DownwardAPIVolumeFile, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_DownwardAPIVolumeFile(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]DownwardAPIVolumeFile, len(in)) + for i := range in { + if err := DeepCopy_v1_DownwardAPIVolumeFile(in[i], &(*out)[i], c); err != nil { return err } } @@ -482,16 +632,17 @@ func deepCopy_v1_DownwardAPIVolumeSource(in DownwardAPIVolumeSource, out *Downwa return nil } -func deepCopy_v1_EmptyDirVolumeSource(in EmptyDirVolumeSource, out *EmptyDirVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_EmptyDirVolumeSource(in EmptyDirVolumeSource, out *EmptyDirVolumeSource, c *conversion.Cloner) error { out.Medium = in.Medium return nil } -func deepCopy_v1_EndpointAddress(in EndpointAddress, out *EndpointAddress, c *conversion.Cloner) error { +func DeepCopy_v1_EndpointAddress(in EndpointAddress, out *EndpointAddress, c *conversion.Cloner) error { out.IP = in.IP if in.TargetRef != nil { - out.TargetRef = new(ObjectReference) - if err := deepCopy_v1_ObjectReference(*in.TargetRef, out.TargetRef, c); err != nil { + in, out := in.TargetRef, &out.TargetRef + *out = new(ObjectReference) + if err := DeepCopy_v1_ObjectReference(*in, *out, c); err != nil { return err } } else { @@ -500,18 +651,19 @@ func deepCopy_v1_EndpointAddress(in EndpointAddress, out *EndpointAddress, c *co return nil } -func deepCopy_v1_EndpointPort(in EndpointPort, out *EndpointPort, c *conversion.Cloner) error { +func DeepCopy_v1_EndpointPort(in EndpointPort, out *EndpointPort, c *conversion.Cloner) error { out.Name = in.Name out.Port = in.Port out.Protocol = in.Protocol return nil } -func deepCopy_v1_EndpointSubset(in EndpointSubset, out *EndpointSubset, c *conversion.Cloner) error { +func DeepCopy_v1_EndpointSubset(in EndpointSubset, out *EndpointSubset, c *conversion.Cloner) error { if in.Addresses != nil { - out.Addresses = make([]EndpointAddress, len(in.Addresses)) - for i := range in.Addresses { - if err := deepCopy_v1_EndpointAddress(in.Addresses[i], &out.Addresses[i], c); err != nil { + in, out := in.Addresses, &out.Addresses + *out = make([]EndpointAddress, len(in)) + for i := range in { + if err := DeepCopy_v1_EndpointAddress(in[i], &(*out)[i], c); err != nil { return err } } @@ -519,9 +671,10 @@ func deepCopy_v1_EndpointSubset(in EndpointSubset, out *EndpointSubset, c *conve out.Addresses = nil } if in.NotReadyAddresses != nil { - out.NotReadyAddresses = make([]EndpointAddress, len(in.NotReadyAddresses)) - for i := range in.NotReadyAddresses { - if err := deepCopy_v1_EndpointAddress(in.NotReadyAddresses[i], &out.NotReadyAddresses[i], c); err != nil { + in, out := in.NotReadyAddresses, &out.NotReadyAddresses + *out = make([]EndpointAddress, len(in)) + for i := range in { + if err := DeepCopy_v1_EndpointAddress(in[i], &(*out)[i], c); err != nil { return err } } @@ -529,9 +682,10 @@ func deepCopy_v1_EndpointSubset(in EndpointSubset, out *EndpointSubset, c *conve out.NotReadyAddresses = nil } if in.Ports != nil { - out.Ports = make([]EndpointPort, len(in.Ports)) - for i := range in.Ports { - if err := deepCopy_v1_EndpointPort(in.Ports[i], &out.Ports[i], c); err != nil { + in, out := in.Ports, &out.Ports + *out = make([]EndpointPort, len(in)) + for i := range in { + if err := DeepCopy_v1_EndpointPort(in[i], &(*out)[i], c); err != nil { return err } } @@ -541,17 +695,18 @@ func deepCopy_v1_EndpointSubset(in EndpointSubset, out *EndpointSubset, c *conve return nil } -func deepCopy_v1_Endpoints(in Endpoints, out *Endpoints, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_Endpoints(in Endpoints, out *Endpoints, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } if in.Subsets != nil { - out.Subsets = make([]EndpointSubset, len(in.Subsets)) - for i := range in.Subsets { - if err := deepCopy_v1_EndpointSubset(in.Subsets[i], &out.Subsets[i], c); err != nil { + in, out := in.Subsets, &out.Subsets + *out = make([]EndpointSubset, len(in)) + for i := range in { + if err := DeepCopy_v1_EndpointSubset(in[i], &(*out)[i], c); err != nil { return err } } @@ -561,17 +716,18 @@ func deepCopy_v1_Endpoints(in Endpoints, out *Endpoints, c *conversion.Cloner) e return nil } -func deepCopy_v1_EndpointsList(in EndpointsList, out *EndpointsList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_EndpointsList(in EndpointsList, out *EndpointsList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]Endpoints, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_Endpoints(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]Endpoints, len(in)) + for i := range in { + if err := DeepCopy_v1_Endpoints(in[i], &(*out)[i], c); err != nil { return err } } @@ -581,12 +737,13 @@ func deepCopy_v1_EndpointsList(in EndpointsList, out *EndpointsList, c *conversi return nil } -func deepCopy_v1_EnvVar(in EnvVar, out *EnvVar, c *conversion.Cloner) error { +func DeepCopy_v1_EnvVar(in EnvVar, out *EnvVar, c *conversion.Cloner) error { out.Name = in.Name out.Value = in.Value if in.ValueFrom != nil { - out.ValueFrom = new(EnvVarSource) - if err := deepCopy_v1_EnvVarSource(*in.ValueFrom, out.ValueFrom, c); err != nil { + in, out := in.ValueFrom, &out.ValueFrom + *out = new(EnvVarSource) + if err := DeepCopy_v1_EnvVarSource(*in, *out, c); err != nil { return err } } else { @@ -595,26 +752,29 @@ func deepCopy_v1_EnvVar(in EnvVar, out *EnvVar, c *conversion.Cloner) error { return nil } -func deepCopy_v1_EnvVarSource(in EnvVarSource, out *EnvVarSource, c *conversion.Cloner) error { +func DeepCopy_v1_EnvVarSource(in EnvVarSource, out *EnvVarSource, c *conversion.Cloner) error { if in.FieldRef != nil { - out.FieldRef = new(ObjectFieldSelector) - if err := deepCopy_v1_ObjectFieldSelector(*in.FieldRef, out.FieldRef, c); err != nil { + in, out := in.FieldRef, &out.FieldRef + *out = new(ObjectFieldSelector) + if err := DeepCopy_v1_ObjectFieldSelector(*in, *out, c); err != nil { return err } } else { out.FieldRef = nil } if in.ConfigMapKeyRef != nil { - out.ConfigMapKeyRef = new(ConfigMapKeySelector) - if err := deepCopy_v1_ConfigMapKeySelector(*in.ConfigMapKeyRef, out.ConfigMapKeyRef, c); err != nil { + in, out := in.ConfigMapKeyRef, &out.ConfigMapKeyRef + *out = new(ConfigMapKeySelector) + if err := DeepCopy_v1_ConfigMapKeySelector(*in, *out, c); err != nil { return err } } else { out.ConfigMapKeyRef = nil } if in.SecretKeyRef != nil { - out.SecretKeyRef = new(SecretKeySelector) - if err := deepCopy_v1_SecretKeySelector(*in.SecretKeyRef, out.SecretKeyRef, c); err != nil { + in, out := in.SecretKeyRef, &out.SecretKeyRef + *out = new(SecretKeySelector) + if err := DeepCopy_v1_SecretKeySelector(*in, *out, c); err != nil { return err } } else { @@ -623,25 +783,25 @@ func deepCopy_v1_EnvVarSource(in EnvVarSource, out *EnvVarSource, c *conversion. return nil } -func deepCopy_v1_Event(in Event, out *Event, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_Event(in Event, out *Event, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectReference(in.InvolvedObject, &out.InvolvedObject, c); err != nil { + if err := DeepCopy_v1_ObjectReference(in.InvolvedObject, &out.InvolvedObject, c); err != nil { return err } out.Reason = in.Reason out.Message = in.Message - if err := deepCopy_v1_EventSource(in.Source, &out.Source, c); err != nil { + if err := DeepCopy_v1_EventSource(in.Source, &out.Source, c); err != nil { return err } - if err := deepCopy_unversioned_Time(in.FirstTimestamp, &out.FirstTimestamp, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.FirstTimestamp, &out.FirstTimestamp, c); err != nil { return err } - if err := deepCopy_unversioned_Time(in.LastTimestamp, &out.LastTimestamp, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.LastTimestamp, &out.LastTimestamp, c); err != nil { return err } out.Count = in.Count @@ -649,17 +809,18 @@ func deepCopy_v1_Event(in Event, out *Event, c *conversion.Cloner) error { return nil } -func deepCopy_v1_EventList(in EventList, out *EventList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_EventList(in EventList, out *EventList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]Event, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_Event(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]Event, len(in)) + for i := range in { + if err := DeepCopy_v1_Event(in[i], &(*out)[i], c); err != nil { return err } } @@ -669,26 +830,25 @@ func deepCopy_v1_EventList(in EventList, out *EventList, c *conversion.Cloner) e return nil } -func deepCopy_v1_EventSource(in EventSource, out *EventSource, c *conversion.Cloner) error { +func DeepCopy_v1_EventSource(in EventSource, out *EventSource, c *conversion.Cloner) error { out.Component = in.Component out.Host = in.Host return nil } -func deepCopy_v1_ExecAction(in ExecAction, out *ExecAction, c *conversion.Cloner) error { +func DeepCopy_v1_ExecAction(in ExecAction, out *ExecAction, c *conversion.Cloner) error { if in.Command != nil { - out.Command = make([]string, len(in.Command)) - for i := range in.Command { - out.Command[i] = in.Command[i] - } + in, out := in.Command, &out.Command + *out = make([]string, len(in)) + copy(*out, in) } else { out.Command = nil } return nil } -func deepCopy_v1_ExportOptions(in ExportOptions, out *ExportOptions, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_ExportOptions(in ExportOptions, out *ExportOptions, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.Export = in.Export @@ -696,18 +856,18 @@ func deepCopy_v1_ExportOptions(in ExportOptions, out *ExportOptions, c *conversi return nil } -func deepCopy_v1_FCVolumeSource(in FCVolumeSource, out *FCVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_FCVolumeSource(in FCVolumeSource, out *FCVolumeSource, c *conversion.Cloner) error { if in.TargetWWNs != nil { - out.TargetWWNs = make([]string, len(in.TargetWWNs)) - for i := range in.TargetWWNs { - out.TargetWWNs[i] = in.TargetWWNs[i] - } + in, out := in.TargetWWNs, &out.TargetWWNs + *out = make([]string, len(in)) + copy(*out, in) } else { out.TargetWWNs = nil } if in.Lun != nil { - out.Lun = new(int32) - *out.Lun = *in.Lun + in, out := in.Lun, &out.Lun + *out = new(int32) + **out = *in } else { out.Lun = nil } @@ -716,12 +876,13 @@ func deepCopy_v1_FCVolumeSource(in FCVolumeSource, out *FCVolumeSource, c *conve return nil } -func deepCopy_v1_FlexVolumeSource(in FlexVolumeSource, out *FlexVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_FlexVolumeSource(in FlexVolumeSource, out *FlexVolumeSource, c *conversion.Cloner) error { out.Driver = in.Driver out.FSType = in.FSType if in.SecretRef != nil { - out.SecretRef = new(LocalObjectReference) - if err := deepCopy_v1_LocalObjectReference(*in.SecretRef, out.SecretRef, c); err != nil { + in, out := in.SecretRef, &out.SecretRef + *out = new(LocalObjectReference) + if err := DeepCopy_v1_LocalObjectReference(*in, *out, c); err != nil { return err } } else { @@ -729,9 +890,10 @@ func deepCopy_v1_FlexVolumeSource(in FlexVolumeSource, out *FlexVolumeSource, c } out.ReadOnly = in.ReadOnly if in.Options != nil { - out.Options = make(map[string]string) - for key, val := range in.Options { - out.Options[key] = val + in, out := in.Options, &out.Options + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val } } else { out.Options = nil @@ -739,12 +901,12 @@ func deepCopy_v1_FlexVolumeSource(in FlexVolumeSource, out *FlexVolumeSource, c return nil } -func deepCopy_v1_FlockerVolumeSource(in FlockerVolumeSource, out *FlockerVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_FlockerVolumeSource(in FlockerVolumeSource, out *FlockerVolumeSource, c *conversion.Cloner) error { out.DatasetName = in.DatasetName return nil } -func deepCopy_v1_GCEPersistentDiskVolumeSource(in GCEPersistentDiskVolumeSource, out *GCEPersistentDiskVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_GCEPersistentDiskVolumeSource(in GCEPersistentDiskVolumeSource, out *GCEPersistentDiskVolumeSource, c *conversion.Cloner) error { out.PDName = in.PDName out.FSType = in.FSType out.Partition = in.Partition @@ -752,31 +914,32 @@ func deepCopy_v1_GCEPersistentDiskVolumeSource(in GCEPersistentDiskVolumeSource, return nil } -func deepCopy_v1_GitRepoVolumeSource(in GitRepoVolumeSource, out *GitRepoVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_GitRepoVolumeSource(in GitRepoVolumeSource, out *GitRepoVolumeSource, c *conversion.Cloner) error { out.Repository = in.Repository out.Revision = in.Revision out.Directory = in.Directory return nil } -func deepCopy_v1_GlusterfsVolumeSource(in GlusterfsVolumeSource, out *GlusterfsVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_GlusterfsVolumeSource(in GlusterfsVolumeSource, out *GlusterfsVolumeSource, c *conversion.Cloner) error { out.EndpointsName = in.EndpointsName out.Path = in.Path out.ReadOnly = in.ReadOnly return nil } -func deepCopy_v1_HTTPGetAction(in HTTPGetAction, out *HTTPGetAction, c *conversion.Cloner) error { +func DeepCopy_v1_HTTPGetAction(in HTTPGetAction, out *HTTPGetAction, c *conversion.Cloner) error { out.Path = in.Path - if err := deepCopy_intstr_IntOrString(in.Port, &out.Port, c); err != nil { + if err := intstr.DeepCopy_intstr_IntOrString(in.Port, &out.Port, c); err != nil { return err } out.Host = in.Host out.Scheme = in.Scheme if in.HTTPHeaders != nil { - out.HTTPHeaders = make([]HTTPHeader, len(in.HTTPHeaders)) - for i := range in.HTTPHeaders { - if err := deepCopy_v1_HTTPHeader(in.HTTPHeaders[i], &out.HTTPHeaders[i], c); err != nil { + in, out := in.HTTPHeaders, &out.HTTPHeaders + *out = make([]HTTPHeader, len(in)) + for i := range in { + if err := DeepCopy_v1_HTTPHeader(in[i], &(*out)[i], c); err != nil { return err } } @@ -786,32 +949,35 @@ func deepCopy_v1_HTTPGetAction(in HTTPGetAction, out *HTTPGetAction, c *conversi return nil } -func deepCopy_v1_HTTPHeader(in HTTPHeader, out *HTTPHeader, c *conversion.Cloner) error { +func DeepCopy_v1_HTTPHeader(in HTTPHeader, out *HTTPHeader, c *conversion.Cloner) error { out.Name = in.Name out.Value = in.Value return nil } -func deepCopy_v1_Handler(in Handler, out *Handler, c *conversion.Cloner) error { +func DeepCopy_v1_Handler(in Handler, out *Handler, c *conversion.Cloner) error { if in.Exec != nil { - out.Exec = new(ExecAction) - if err := deepCopy_v1_ExecAction(*in.Exec, out.Exec, c); err != nil { + in, out := in.Exec, &out.Exec + *out = new(ExecAction) + if err := DeepCopy_v1_ExecAction(*in, *out, c); err != nil { return err } } else { out.Exec = nil } if in.HTTPGet != nil { - out.HTTPGet = new(HTTPGetAction) - if err := deepCopy_v1_HTTPGetAction(*in.HTTPGet, out.HTTPGet, c); err != nil { + in, out := in.HTTPGet, &out.HTTPGet + *out = new(HTTPGetAction) + if err := DeepCopy_v1_HTTPGetAction(*in, *out, c); err != nil { return err } } else { out.HTTPGet = nil } if in.TCPSocket != nil { - out.TCPSocket = new(TCPSocketAction) - if err := deepCopy_v1_TCPSocketAction(*in.TCPSocket, out.TCPSocket, c); err != nil { + in, out := in.TCPSocket, &out.TCPSocket + *out = new(TCPSocketAction) + if err := DeepCopy_v1_TCPSocketAction(*in, *out, c); err != nil { return err } } else { @@ -820,12 +986,12 @@ func deepCopy_v1_Handler(in Handler, out *Handler, c *conversion.Cloner) error { return nil } -func deepCopy_v1_HostPathVolumeSource(in HostPathVolumeSource, out *HostPathVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_HostPathVolumeSource(in HostPathVolumeSource, out *HostPathVolumeSource, c *conversion.Cloner) error { out.Path = in.Path return nil } -func deepCopy_v1_ISCSIVolumeSource(in ISCSIVolumeSource, out *ISCSIVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_ISCSIVolumeSource(in ISCSIVolumeSource, out *ISCSIVolumeSource, c *conversion.Cloner) error { out.TargetPortal = in.TargetPortal out.IQN = in.IQN out.Lun = in.Lun @@ -835,24 +1001,26 @@ func deepCopy_v1_ISCSIVolumeSource(in ISCSIVolumeSource, out *ISCSIVolumeSource, return nil } -func deepCopy_v1_KeyToPath(in KeyToPath, out *KeyToPath, c *conversion.Cloner) error { +func DeepCopy_v1_KeyToPath(in KeyToPath, out *KeyToPath, c *conversion.Cloner) error { out.Key = in.Key out.Path = in.Path return nil } -func deepCopy_v1_Lifecycle(in Lifecycle, out *Lifecycle, c *conversion.Cloner) error { +func DeepCopy_v1_Lifecycle(in Lifecycle, out *Lifecycle, c *conversion.Cloner) error { if in.PostStart != nil { - out.PostStart = new(Handler) - if err := deepCopy_v1_Handler(*in.PostStart, out.PostStart, c); err != nil { + in, out := in.PostStart, &out.PostStart + *out = new(Handler) + if err := DeepCopy_v1_Handler(*in, *out, c); err != nil { return err } } else { out.PostStart = nil } if in.PreStop != nil { - out.PreStop = new(Handler) - if err := deepCopy_v1_Handler(*in.PreStop, out.PreStop, c); err != nil { + in, out := in.PreStop, &out.PreStop + *out = new(Handler) + if err := DeepCopy_v1_Handler(*in, *out, c); err != nil { return err } } else { @@ -861,77 +1029,82 @@ func deepCopy_v1_Lifecycle(in Lifecycle, out *Lifecycle, c *conversion.Cloner) e return nil } -func deepCopy_v1_LimitRange(in LimitRange, out *LimitRange, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_LimitRange(in LimitRange, out *LimitRange, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1_LimitRangeSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1_LimitRangeSpec(in.Spec, &out.Spec, c); err != nil { return err } return nil } -func deepCopy_v1_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conversion.Cloner) error { +func DeepCopy_v1_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conversion.Cloner) error { out.Type = in.Type if in.Max != nil { - out.Max = make(ResourceList) - for key, val := range in.Max { + in, out := in.Max, &out.Max + *out = make(ResourceList) + for key, val := range in { newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err } - out.Max[key] = *newVal + (*out)[key] = *newVal } } else { out.Max = nil } if in.Min != nil { - out.Min = make(ResourceList) - for key, val := range in.Min { + in, out := in.Min, &out.Min + *out = make(ResourceList) + for key, val := range in { newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err } - out.Min[key] = *newVal + (*out)[key] = *newVal } } else { out.Min = nil } if in.Default != nil { - out.Default = make(ResourceList) - for key, val := range in.Default { + in, out := in.Default, &out.Default + *out = make(ResourceList) + for key, val := range in { newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err } - out.Default[key] = *newVal + (*out)[key] = *newVal } } else { out.Default = nil } if in.DefaultRequest != nil { - out.DefaultRequest = make(ResourceList) - for key, val := range in.DefaultRequest { + in, out := in.DefaultRequest, &out.DefaultRequest + *out = make(ResourceList) + for key, val := range in { newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err } - out.DefaultRequest[key] = *newVal + (*out)[key] = *newVal } } else { out.DefaultRequest = nil } if in.MaxLimitRequestRatio != nil { - out.MaxLimitRequestRatio = make(ResourceList) - for key, val := range in.MaxLimitRequestRatio { + in, out := in.MaxLimitRequestRatio, &out.MaxLimitRequestRatio + *out = make(ResourceList) + for key, val := range in { newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err } - out.MaxLimitRequestRatio[key] = *newVal + (*out)[key] = *newVal } } else { out.MaxLimitRequestRatio = nil @@ -939,17 +1112,18 @@ func deepCopy_v1_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conve return nil } -func deepCopy_v1_LimitRangeList(in LimitRangeList, out *LimitRangeList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_LimitRangeList(in LimitRangeList, out *LimitRangeList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]LimitRange, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_LimitRange(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]LimitRange, len(in)) + for i := range in { + if err := DeepCopy_v1_LimitRange(in[i], &(*out)[i], c); err != nil { return err } } @@ -959,11 +1133,12 @@ func deepCopy_v1_LimitRangeList(in LimitRangeList, out *LimitRangeList, c *conve return nil } -func deepCopy_v1_LimitRangeSpec(in LimitRangeSpec, out *LimitRangeSpec, c *conversion.Cloner) error { +func DeepCopy_v1_LimitRangeSpec(in LimitRangeSpec, out *LimitRangeSpec, c *conversion.Cloner) error { if in.Limits != nil { - out.Limits = make([]LimitRangeItem, len(in.Limits)) - for i := range in.Limits { - if err := deepCopy_v1_LimitRangeItem(in.Limits[i], &out.Limits[i], c); err != nil { + in, out := in.Limits, &out.Limits + *out = make([]LimitRangeItem, len(in)) + for i := range in { + if err := DeepCopy_v1_LimitRangeItem(in[i], &(*out)[i], c); err != nil { return err } } @@ -973,17 +1148,18 @@ func deepCopy_v1_LimitRangeSpec(in LimitRangeSpec, out *LimitRangeSpec, c *conve return nil } -func deepCopy_v1_List(in List, out *List, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_List(in List, out *List, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]runtime.RawExtension, len(in.Items)) - for i := range in.Items { - if err := deepCopy_runtime_RawExtension(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]runtime.RawExtension, len(in)) + for i := range in { + if err := runtime.DeepCopy_runtime_RawExtension(in[i], &(*out)[i], c); err != nil { return err } } @@ -993,8 +1169,8 @@ func deepCopy_v1_List(in List, out *List, c *conversion.Cloner) error { return nil } -func deepCopy_v1_ListOptions(in ListOptions, out *ListOptions, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_ListOptions(in ListOptions, out *ListOptions, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.LabelSelector = in.LabelSelector @@ -1002,25 +1178,27 @@ func deepCopy_v1_ListOptions(in ListOptions, out *ListOptions, c *conversion.Clo out.Watch = in.Watch out.ResourceVersion = in.ResourceVersion if in.TimeoutSeconds != nil { - out.TimeoutSeconds = new(int64) - *out.TimeoutSeconds = *in.TimeoutSeconds + in, out := in.TimeoutSeconds, &out.TimeoutSeconds + *out = new(int64) + **out = *in } else { out.TimeoutSeconds = nil } return nil } -func deepCopy_v1_LoadBalancerIngress(in LoadBalancerIngress, out *LoadBalancerIngress, c *conversion.Cloner) error { +func DeepCopy_v1_LoadBalancerIngress(in LoadBalancerIngress, out *LoadBalancerIngress, c *conversion.Cloner) error { out.IP = in.IP out.Hostname = in.Hostname return nil } -func deepCopy_v1_LoadBalancerStatus(in LoadBalancerStatus, out *LoadBalancerStatus, c *conversion.Cloner) error { +func DeepCopy_v1_LoadBalancerStatus(in LoadBalancerStatus, out *LoadBalancerStatus, c *conversion.Cloner) error { if in.Ingress != nil { - out.Ingress = make([]LoadBalancerIngress, len(in.Ingress)) - for i := range in.Ingress { - if err := deepCopy_v1_LoadBalancerIngress(in.Ingress[i], &out.Ingress[i], c); err != nil { + in, out := in.Ingress, &out.Ingress + *out = make([]LoadBalancerIngress, len(in)) + for i := range in { + if err := DeepCopy_v1_LoadBalancerIngress(in[i], &(*out)[i], c); err != nil { return err } } @@ -1030,45 +1208,46 @@ func deepCopy_v1_LoadBalancerStatus(in LoadBalancerStatus, out *LoadBalancerStat return nil } -func deepCopy_v1_LocalObjectReference(in LocalObjectReference, out *LocalObjectReference, c *conversion.Cloner) error { +func DeepCopy_v1_LocalObjectReference(in LocalObjectReference, out *LocalObjectReference, c *conversion.Cloner) error { out.Name = in.Name return nil } -func deepCopy_v1_NFSVolumeSource(in NFSVolumeSource, out *NFSVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_NFSVolumeSource(in NFSVolumeSource, out *NFSVolumeSource, c *conversion.Cloner) error { out.Server = in.Server out.Path = in.Path out.ReadOnly = in.ReadOnly return nil } -func deepCopy_v1_Namespace(in Namespace, out *Namespace, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_Namespace(in Namespace, out *Namespace, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1_NamespaceSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1_NamespaceSpec(in.Spec, &out.Spec, c); err != nil { return err } - if err := deepCopy_v1_NamespaceStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1_NamespaceStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1_NamespaceList(in NamespaceList, out *NamespaceList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_NamespaceList(in NamespaceList, out *NamespaceList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]Namespace, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_Namespace(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]Namespace, len(in)) + for i := range in { + if err := DeepCopy_v1_Namespace(in[i], &(*out)[i], c); err != nil { return err } } @@ -1078,11 +1257,12 @@ func deepCopy_v1_NamespaceList(in NamespaceList, out *NamespaceList, c *conversi return nil } -func deepCopy_v1_NamespaceSpec(in NamespaceSpec, out *NamespaceSpec, c *conversion.Cloner) error { +func DeepCopy_v1_NamespaceSpec(in NamespaceSpec, out *NamespaceSpec, c *conversion.Cloner) error { if in.Finalizers != nil { - out.Finalizers = make([]FinalizerName, len(in.Finalizers)) - for i := range in.Finalizers { - out.Finalizers[i] = in.Finalizers[i] + in, out := in.Finalizers, &out.Finalizers + *out = make([]FinalizerName, len(in)) + for i := range in { + (*out)[i] = in[i] } } else { out.Finalizers = nil @@ -1090,40 +1270,64 @@ func deepCopy_v1_NamespaceSpec(in NamespaceSpec, out *NamespaceSpec, c *conversi return nil } -func deepCopy_v1_NamespaceStatus(in NamespaceStatus, out *NamespaceStatus, c *conversion.Cloner) error { +func DeepCopy_v1_NamespaceStatus(in NamespaceStatus, out *NamespaceStatus, c *conversion.Cloner) error { out.Phase = in.Phase return nil } -func deepCopy_v1_Node(in Node, out *Node, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_Node(in Node, out *Node, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1_NodeSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1_NodeSpec(in.Spec, &out.Spec, c); err != nil { return err } - if err := deepCopy_v1_NodeStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1_NodeStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1_NodeAddress(in NodeAddress, out *NodeAddress, c *conversion.Cloner) error { +func DeepCopy_v1_NodeAddress(in NodeAddress, out *NodeAddress, c *conversion.Cloner) error { out.Type = in.Type out.Address = in.Address return nil } -func deepCopy_v1_NodeCondition(in NodeCondition, out *NodeCondition, c *conversion.Cloner) error { +func DeepCopy_v1_NodeAffinity(in NodeAffinity, out *NodeAffinity, c *conversion.Cloner) error { + if in.RequiredDuringSchedulingIgnoredDuringExecution != nil { + in, out := in.RequiredDuringSchedulingIgnoredDuringExecution, &out.RequiredDuringSchedulingIgnoredDuringExecution + *out = new(NodeSelector) + if err := DeepCopy_v1_NodeSelector(*in, *out, c); err != nil { + return err + } + } else { + out.RequiredDuringSchedulingIgnoredDuringExecution = nil + } + if in.PreferredDuringSchedulingIgnoredDuringExecution != nil { + in, out := in.PreferredDuringSchedulingIgnoredDuringExecution, &out.PreferredDuringSchedulingIgnoredDuringExecution + *out = make([]PreferredSchedulingTerm, len(in)) + for i := range in { + if err := DeepCopy_v1_PreferredSchedulingTerm(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.PreferredDuringSchedulingIgnoredDuringExecution = nil + } + return nil +} + +func DeepCopy_v1_NodeCondition(in NodeCondition, out *NodeCondition, c *conversion.Cloner) error { out.Type = in.Type out.Status = in.Status - if err := deepCopy_unversioned_Time(in.LastHeartbeatTime, &out.LastHeartbeatTime, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.LastHeartbeatTime, &out.LastHeartbeatTime, c); err != nil { return err } - if err := deepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil { return err } out.Reason = in.Reason @@ -1131,24 +1335,25 @@ func deepCopy_v1_NodeCondition(in NodeCondition, out *NodeCondition, c *conversi return nil } -func deepCopy_v1_NodeDaemonEndpoints(in NodeDaemonEndpoints, out *NodeDaemonEndpoints, c *conversion.Cloner) error { - if err := deepCopy_v1_DaemonEndpoint(in.KubeletEndpoint, &out.KubeletEndpoint, c); err != nil { +func DeepCopy_v1_NodeDaemonEndpoints(in NodeDaemonEndpoints, out *NodeDaemonEndpoints, c *conversion.Cloner) error { + if err := DeepCopy_v1_DaemonEndpoint(in.KubeletEndpoint, &out.KubeletEndpoint, c); err != nil { return err } return nil } -func deepCopy_v1_NodeList(in NodeList, out *NodeList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_NodeList(in NodeList, out *NodeList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]Node, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_Node(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]Node, len(in)) + for i := range in { + if err := DeepCopy_v1_Node(in[i], &(*out)[i], c); err != nil { return err } } @@ -1158,7 +1363,58 @@ func deepCopy_v1_NodeList(in NodeList, out *NodeList, c *conversion.Cloner) erro return nil } -func deepCopy_v1_NodeSpec(in NodeSpec, out *NodeSpec, c *conversion.Cloner) error { +func DeepCopy_v1_NodeProxyOptions(in NodeProxyOptions, out *NodeProxyOptions, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + out.Path = in.Path + return nil +} + +func DeepCopy_v1_NodeSelector(in NodeSelector, out *NodeSelector, c *conversion.Cloner) error { + if in.NodeSelectorTerms != nil { + in, out := in.NodeSelectorTerms, &out.NodeSelectorTerms + *out = make([]NodeSelectorTerm, len(in)) + for i := range in { + if err := DeepCopy_v1_NodeSelectorTerm(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.NodeSelectorTerms = nil + } + return nil +} + +func DeepCopy_v1_NodeSelectorRequirement(in NodeSelectorRequirement, out *NodeSelectorRequirement, c *conversion.Cloner) error { + out.Key = in.Key + out.Operator = in.Operator + if in.Values != nil { + in, out := in.Values, &out.Values + *out = make([]string, len(in)) + copy(*out, in) + } else { + out.Values = nil + } + return nil +} + +func DeepCopy_v1_NodeSelectorTerm(in NodeSelectorTerm, out *NodeSelectorTerm, c *conversion.Cloner) error { + if in.MatchExpressions != nil { + in, out := in.MatchExpressions, &out.MatchExpressions + *out = make([]NodeSelectorRequirement, len(in)) + for i := range in { + if err := DeepCopy_v1_NodeSelectorRequirement(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.MatchExpressions = nil + } + return nil +} + +func DeepCopy_v1_NodeSpec(in NodeSpec, out *NodeSpec, c *conversion.Cloner) error { out.PodCIDR = in.PodCIDR out.ExternalID = in.ExternalID out.ProviderID = in.ProviderID @@ -1166,36 +1422,39 @@ func deepCopy_v1_NodeSpec(in NodeSpec, out *NodeSpec, c *conversion.Cloner) erro return nil } -func deepCopy_v1_NodeStatus(in NodeStatus, out *NodeStatus, c *conversion.Cloner) error { +func DeepCopy_v1_NodeStatus(in NodeStatus, out *NodeStatus, c *conversion.Cloner) error { if in.Capacity != nil { - out.Capacity = make(ResourceList) - for key, val := range in.Capacity { + in, out := in.Capacity, &out.Capacity + *out = make(ResourceList) + for key, val := range in { newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err } - out.Capacity[key] = *newVal + (*out)[key] = *newVal } } else { out.Capacity = nil } if in.Allocatable != nil { - out.Allocatable = make(ResourceList) - for key, val := range in.Allocatable { + in, out := in.Allocatable, &out.Allocatable + *out = make(ResourceList) + for key, val := range in { newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err } - out.Allocatable[key] = *newVal + (*out)[key] = *newVal } } else { out.Allocatable = nil } out.Phase = in.Phase if in.Conditions != nil { - out.Conditions = make([]NodeCondition, len(in.Conditions)) - for i := range in.Conditions { - if err := deepCopy_v1_NodeCondition(in.Conditions[i], &out.Conditions[i], c); err != nil { + in, out := in.Conditions, &out.Conditions + *out = make([]NodeCondition, len(in)) + for i := range in { + if err := DeepCopy_v1_NodeCondition(in[i], &(*out)[i], c); err != nil { return err } } @@ -1203,25 +1462,27 @@ func deepCopy_v1_NodeStatus(in NodeStatus, out *NodeStatus, c *conversion.Cloner out.Conditions = nil } if in.Addresses != nil { - out.Addresses = make([]NodeAddress, len(in.Addresses)) - for i := range in.Addresses { - if err := deepCopy_v1_NodeAddress(in.Addresses[i], &out.Addresses[i], c); err != nil { + in, out := in.Addresses, &out.Addresses + *out = make([]NodeAddress, len(in)) + for i := range in { + if err := DeepCopy_v1_NodeAddress(in[i], &(*out)[i], c); err != nil { return err } } } else { out.Addresses = nil } - if err := deepCopy_v1_NodeDaemonEndpoints(in.DaemonEndpoints, &out.DaemonEndpoints, c); err != nil { + if err := DeepCopy_v1_NodeDaemonEndpoints(in.DaemonEndpoints, &out.DaemonEndpoints, c); err != nil { return err } - if err := deepCopy_v1_NodeSystemInfo(in.NodeInfo, &out.NodeInfo, c); err != nil { + if err := DeepCopy_v1_NodeSystemInfo(in.NodeInfo, &out.NodeInfo, c); err != nil { return err } if in.Images != nil { - out.Images = make([]ContainerImage, len(in.Images)) - for i := range in.Images { - if err := deepCopy_v1_ContainerImage(in.Images[i], &out.Images[i], c); err != nil { + in, out := in.Images, &out.Images + *out = make([]ContainerImage, len(in)) + for i := range in { + if err := DeepCopy_v1_ContainerImage(in[i], &(*out)[i], c); err != nil { return err } } @@ -1231,7 +1492,7 @@ func deepCopy_v1_NodeStatus(in NodeStatus, out *NodeStatus, c *conversion.Cloner return nil } -func deepCopy_v1_NodeSystemInfo(in NodeSystemInfo, out *NodeSystemInfo, c *conversion.Cloner) error { +func DeepCopy_v1_NodeSystemInfo(in NodeSystemInfo, out *NodeSystemInfo, c *conversion.Cloner) error { out.MachineID = in.MachineID out.SystemUUID = in.SystemUUID out.BootID = in.BootID @@ -1243,13 +1504,13 @@ func deepCopy_v1_NodeSystemInfo(in NodeSystemInfo, out *NodeSystemInfo, c *conve return nil } -func deepCopy_v1_ObjectFieldSelector(in ObjectFieldSelector, out *ObjectFieldSelector, c *conversion.Cloner) error { +func DeepCopy_v1_ObjectFieldSelector(in ObjectFieldSelector, out *ObjectFieldSelector, c *conversion.Cloner) error { out.APIVersion = in.APIVersion out.FieldPath = in.FieldPath return nil } -func deepCopy_v1_ObjectMeta(in ObjectMeta, out *ObjectMeta, c *conversion.Cloner) error { +func DeepCopy_v1_ObjectMeta(in ObjectMeta, out *ObjectMeta, c *conversion.Cloner) error { out.Name = in.Name out.GenerateName = in.GenerateName out.Namespace = in.Namespace @@ -1257,35 +1518,39 @@ func deepCopy_v1_ObjectMeta(in ObjectMeta, out *ObjectMeta, c *conversion.Cloner out.UID = in.UID out.ResourceVersion = in.ResourceVersion out.Generation = in.Generation - if err := deepCopy_unversioned_Time(in.CreationTimestamp, &out.CreationTimestamp, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.CreationTimestamp, &out.CreationTimestamp, c); err != nil { return err } if in.DeletionTimestamp != nil { - out.DeletionTimestamp = new(unversioned.Time) - if err := deepCopy_unversioned_Time(*in.DeletionTimestamp, out.DeletionTimestamp, c); err != nil { + in, out := in.DeletionTimestamp, &out.DeletionTimestamp + *out = new(unversioned.Time) + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { return err } } else { out.DeletionTimestamp = nil } if in.DeletionGracePeriodSeconds != nil { - out.DeletionGracePeriodSeconds = new(int64) - *out.DeletionGracePeriodSeconds = *in.DeletionGracePeriodSeconds + in, out := in.DeletionGracePeriodSeconds, &out.DeletionGracePeriodSeconds + *out = new(int64) + **out = *in } else { out.DeletionGracePeriodSeconds = nil } if in.Labels != nil { - out.Labels = make(map[string]string) - for key, val := range in.Labels { - out.Labels[key] = val + in, out := in.Labels, &out.Labels + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val } } else { out.Labels = nil } if in.Annotations != nil { - out.Annotations = make(map[string]string) - for key, val := range in.Annotations { - out.Annotations[key] = val + in, out := in.Annotations, &out.Annotations + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val } } else { out.Annotations = nil @@ -1293,7 +1558,7 @@ func deepCopy_v1_ObjectMeta(in ObjectMeta, out *ObjectMeta, c *conversion.Cloner return nil } -func deepCopy_v1_ObjectReference(in ObjectReference, out *ObjectReference, c *conversion.Cloner) error { +func DeepCopy_v1_ObjectReference(in ObjectReference, out *ObjectReference, c *conversion.Cloner) error { out.Kind = in.Kind out.Namespace = in.Namespace out.Name = in.Name @@ -1304,49 +1569,50 @@ func deepCopy_v1_ObjectReference(in ObjectReference, out *ObjectReference, c *co return nil } -func deepCopy_v1_PersistentVolume(in PersistentVolume, out *PersistentVolume, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_PersistentVolume(in PersistentVolume, out *PersistentVolume, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1_PersistentVolumeSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1_PersistentVolumeSpec(in.Spec, &out.Spec, c); err != nil { return err } - if err := deepCopy_v1_PersistentVolumeStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1_PersistentVolumeStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1_PersistentVolumeClaim(in PersistentVolumeClaim, out *PersistentVolumeClaim, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_PersistentVolumeClaim(in PersistentVolumeClaim, out *PersistentVolumeClaim, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1_PersistentVolumeClaimSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1_PersistentVolumeClaimSpec(in.Spec, &out.Spec, c); err != nil { return err } - if err := deepCopy_v1_PersistentVolumeClaimStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1_PersistentVolumeClaimStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1_PersistentVolumeClaimList(in PersistentVolumeClaimList, out *PersistentVolumeClaimList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_PersistentVolumeClaimList(in PersistentVolumeClaimList, out *PersistentVolumeClaimList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]PersistentVolumeClaim, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_PersistentVolumeClaim(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]PersistentVolumeClaim, len(in)) + for i := range in { + if err := DeepCopy_v1_PersistentVolumeClaim(in[i], &(*out)[i], c); err != nil { return err } } @@ -1356,40 +1622,43 @@ func deepCopy_v1_PersistentVolumeClaimList(in PersistentVolumeClaimList, out *Pe return nil } -func deepCopy_v1_PersistentVolumeClaimSpec(in PersistentVolumeClaimSpec, out *PersistentVolumeClaimSpec, c *conversion.Cloner) error { +func DeepCopy_v1_PersistentVolumeClaimSpec(in PersistentVolumeClaimSpec, out *PersistentVolumeClaimSpec, c *conversion.Cloner) error { if in.AccessModes != nil { - out.AccessModes = make([]PersistentVolumeAccessMode, len(in.AccessModes)) - for i := range in.AccessModes { - out.AccessModes[i] = in.AccessModes[i] + in, out := in.AccessModes, &out.AccessModes + *out = make([]PersistentVolumeAccessMode, len(in)) + for i := range in { + (*out)[i] = in[i] } } else { out.AccessModes = nil } - if err := deepCopy_v1_ResourceRequirements(in.Resources, &out.Resources, c); err != nil { + if err := DeepCopy_v1_ResourceRequirements(in.Resources, &out.Resources, c); err != nil { return err } out.VolumeName = in.VolumeName return nil } -func deepCopy_v1_PersistentVolumeClaimStatus(in PersistentVolumeClaimStatus, out *PersistentVolumeClaimStatus, c *conversion.Cloner) error { +func DeepCopy_v1_PersistentVolumeClaimStatus(in PersistentVolumeClaimStatus, out *PersistentVolumeClaimStatus, c *conversion.Cloner) error { out.Phase = in.Phase if in.AccessModes != nil { - out.AccessModes = make([]PersistentVolumeAccessMode, len(in.AccessModes)) - for i := range in.AccessModes { - out.AccessModes[i] = in.AccessModes[i] + in, out := in.AccessModes, &out.AccessModes + *out = make([]PersistentVolumeAccessMode, len(in)) + for i := range in { + (*out)[i] = in[i] } } else { out.AccessModes = nil } if in.Capacity != nil { - out.Capacity = make(ResourceList) - for key, val := range in.Capacity { + in, out := in.Capacity, &out.Capacity + *out = make(ResourceList) + for key, val := range in { newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err } - out.Capacity[key] = *newVal + (*out)[key] = *newVal } } else { out.Capacity = nil @@ -1397,23 +1666,24 @@ func deepCopy_v1_PersistentVolumeClaimStatus(in PersistentVolumeClaimStatus, out return nil } -func deepCopy_v1_PersistentVolumeClaimVolumeSource(in PersistentVolumeClaimVolumeSource, out *PersistentVolumeClaimVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_PersistentVolumeClaimVolumeSource(in PersistentVolumeClaimVolumeSource, out *PersistentVolumeClaimVolumeSource, c *conversion.Cloner) error { out.ClaimName = in.ClaimName out.ReadOnly = in.ReadOnly return nil } -func deepCopy_v1_PersistentVolumeList(in PersistentVolumeList, out *PersistentVolumeList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_PersistentVolumeList(in PersistentVolumeList, out *PersistentVolumeList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]PersistentVolume, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_PersistentVolume(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]PersistentVolume, len(in)) + for i := range in { + if err := DeepCopy_v1_PersistentVolume(in[i], &(*out)[i], c); err != nil { return err } } @@ -1423,106 +1693,119 @@ func deepCopy_v1_PersistentVolumeList(in PersistentVolumeList, out *PersistentVo return nil } -func deepCopy_v1_PersistentVolumeSource(in PersistentVolumeSource, out *PersistentVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_PersistentVolumeSource(in PersistentVolumeSource, out *PersistentVolumeSource, c *conversion.Cloner) error { if in.GCEPersistentDisk != nil { - out.GCEPersistentDisk = new(GCEPersistentDiskVolumeSource) - if err := deepCopy_v1_GCEPersistentDiskVolumeSource(*in.GCEPersistentDisk, out.GCEPersistentDisk, c); err != nil { + in, out := in.GCEPersistentDisk, &out.GCEPersistentDisk + *out = new(GCEPersistentDiskVolumeSource) + if err := DeepCopy_v1_GCEPersistentDiskVolumeSource(*in, *out, c); err != nil { return err } } else { out.GCEPersistentDisk = nil } if in.AWSElasticBlockStore != nil { - out.AWSElasticBlockStore = new(AWSElasticBlockStoreVolumeSource) - if err := deepCopy_v1_AWSElasticBlockStoreVolumeSource(*in.AWSElasticBlockStore, out.AWSElasticBlockStore, c); err != nil { + in, out := in.AWSElasticBlockStore, &out.AWSElasticBlockStore + *out = new(AWSElasticBlockStoreVolumeSource) + if err := DeepCopy_v1_AWSElasticBlockStoreVolumeSource(*in, *out, c); err != nil { return err } } else { out.AWSElasticBlockStore = nil } if in.HostPath != nil { - out.HostPath = new(HostPathVolumeSource) - if err := deepCopy_v1_HostPathVolumeSource(*in.HostPath, out.HostPath, c); err != nil { + in, out := in.HostPath, &out.HostPath + *out = new(HostPathVolumeSource) + if err := DeepCopy_v1_HostPathVolumeSource(*in, *out, c); err != nil { return err } } else { out.HostPath = nil } if in.Glusterfs != nil { - out.Glusterfs = new(GlusterfsVolumeSource) - if err := deepCopy_v1_GlusterfsVolumeSource(*in.Glusterfs, out.Glusterfs, c); err != nil { + in, out := in.Glusterfs, &out.Glusterfs + *out = new(GlusterfsVolumeSource) + if err := DeepCopy_v1_GlusterfsVolumeSource(*in, *out, c); err != nil { return err } } else { out.Glusterfs = nil } if in.NFS != nil { - out.NFS = new(NFSVolumeSource) - if err := deepCopy_v1_NFSVolumeSource(*in.NFS, out.NFS, c); err != nil { + in, out := in.NFS, &out.NFS + *out = new(NFSVolumeSource) + if err := DeepCopy_v1_NFSVolumeSource(*in, *out, c); err != nil { return err } } else { out.NFS = nil } if in.RBD != nil { - out.RBD = new(RBDVolumeSource) - if err := deepCopy_v1_RBDVolumeSource(*in.RBD, out.RBD, c); err != nil { + in, out := in.RBD, &out.RBD + *out = new(RBDVolumeSource) + if err := DeepCopy_v1_RBDVolumeSource(*in, *out, c); err != nil { return err } } else { out.RBD = nil } if in.ISCSI != nil { - out.ISCSI = new(ISCSIVolumeSource) - if err := deepCopy_v1_ISCSIVolumeSource(*in.ISCSI, out.ISCSI, c); err != nil { + in, out := in.ISCSI, &out.ISCSI + *out = new(ISCSIVolumeSource) + if err := DeepCopy_v1_ISCSIVolumeSource(*in, *out, c); err != nil { return err } } else { out.ISCSI = nil } if in.Cinder != nil { - out.Cinder = new(CinderVolumeSource) - if err := deepCopy_v1_CinderVolumeSource(*in.Cinder, out.Cinder, c); err != nil { + in, out := in.Cinder, &out.Cinder + *out = new(CinderVolumeSource) + if err := DeepCopy_v1_CinderVolumeSource(*in, *out, c); err != nil { return err } } else { out.Cinder = nil } if in.CephFS != nil { - out.CephFS = new(CephFSVolumeSource) - if err := deepCopy_v1_CephFSVolumeSource(*in.CephFS, out.CephFS, c); err != nil { + in, out := in.CephFS, &out.CephFS + *out = new(CephFSVolumeSource) + if err := DeepCopy_v1_CephFSVolumeSource(*in, *out, c); err != nil { return err } } else { out.CephFS = nil } if in.FC != nil { - out.FC = new(FCVolumeSource) - if err := deepCopy_v1_FCVolumeSource(*in.FC, out.FC, c); err != nil { + in, out := in.FC, &out.FC + *out = new(FCVolumeSource) + if err := DeepCopy_v1_FCVolumeSource(*in, *out, c); err != nil { return err } } else { out.FC = nil } if in.Flocker != nil { - out.Flocker = new(FlockerVolumeSource) - if err := deepCopy_v1_FlockerVolumeSource(*in.Flocker, out.Flocker, c); err != nil { + in, out := in.Flocker, &out.Flocker + *out = new(FlockerVolumeSource) + if err := DeepCopy_v1_FlockerVolumeSource(*in, *out, c); err != nil { return err } } else { out.Flocker = nil } if in.FlexVolume != nil { - out.FlexVolume = new(FlexVolumeSource) - if err := deepCopy_v1_FlexVolumeSource(*in.FlexVolume, out.FlexVolume, c); err != nil { + in, out := in.FlexVolume, &out.FlexVolume + *out = new(FlexVolumeSource) + if err := DeepCopy_v1_FlexVolumeSource(*in, *out, c); err != nil { return err } } else { out.FlexVolume = nil } if in.AzureFile != nil { - out.AzureFile = new(AzureFileVolumeSource) - if err := deepCopy_v1_AzureFileVolumeSource(*in.AzureFile, out.AzureFile, c); err != nil { + in, out := in.AzureFile, &out.AzureFile + *out = new(AzureFileVolumeSource) + if err := DeepCopy_v1_AzureFileVolumeSource(*in, *out, c); err != nil { return err } } else { @@ -1531,33 +1814,36 @@ func deepCopy_v1_PersistentVolumeSource(in PersistentVolumeSource, out *Persiste return nil } -func deepCopy_v1_PersistentVolumeSpec(in PersistentVolumeSpec, out *PersistentVolumeSpec, c *conversion.Cloner) error { +func DeepCopy_v1_PersistentVolumeSpec(in PersistentVolumeSpec, out *PersistentVolumeSpec, c *conversion.Cloner) error { if in.Capacity != nil { - out.Capacity = make(ResourceList) - for key, val := range in.Capacity { + in, out := in.Capacity, &out.Capacity + *out = make(ResourceList) + for key, val := range in { newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err } - out.Capacity[key] = *newVal + (*out)[key] = *newVal } } else { out.Capacity = nil } - if err := deepCopy_v1_PersistentVolumeSource(in.PersistentVolumeSource, &out.PersistentVolumeSource, c); err != nil { + if err := DeepCopy_v1_PersistentVolumeSource(in.PersistentVolumeSource, &out.PersistentVolumeSource, c); err != nil { return err } if in.AccessModes != nil { - out.AccessModes = make([]PersistentVolumeAccessMode, len(in.AccessModes)) - for i := range in.AccessModes { - out.AccessModes[i] = in.AccessModes[i] + in, out := in.AccessModes, &out.AccessModes + *out = make([]PersistentVolumeAccessMode, len(in)) + for i := range in { + (*out)[i] = in[i] } } else { out.AccessModes = nil } if in.ClaimRef != nil { - out.ClaimRef = new(ObjectReference) - if err := deepCopy_v1_ObjectReference(*in.ClaimRef, out.ClaimRef, c); err != nil { + in, out := in.ClaimRef, &out.ClaimRef + *out = new(ObjectReference) + if err := DeepCopy_v1_ObjectReference(*in, *out, c); err != nil { return err } } else { @@ -1567,31 +1853,31 @@ func deepCopy_v1_PersistentVolumeSpec(in PersistentVolumeSpec, out *PersistentVo return nil } -func deepCopy_v1_PersistentVolumeStatus(in PersistentVolumeStatus, out *PersistentVolumeStatus, c *conversion.Cloner) error { +func DeepCopy_v1_PersistentVolumeStatus(in PersistentVolumeStatus, out *PersistentVolumeStatus, c *conversion.Cloner) error { out.Phase = in.Phase out.Message = in.Message out.Reason = in.Reason return nil } -func deepCopy_v1_Pod(in Pod, out *Pod, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_Pod(in Pod, out *Pod, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1_PodSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1_PodSpec(in.Spec, &out.Spec, c); err != nil { return err } - if err := deepCopy_v1_PodStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1_PodStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1_PodAttachOptions(in PodAttachOptions, out *PodAttachOptions, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_PodAttachOptions(in PodAttachOptions, out *PodAttachOptions, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.Stdin = in.Stdin @@ -1602,13 +1888,13 @@ func deepCopy_v1_PodAttachOptions(in PodAttachOptions, out *PodAttachOptions, c return nil } -func deepCopy_v1_PodCondition(in PodCondition, out *PodCondition, c *conversion.Cloner) error { +func DeepCopy_v1_PodCondition(in PodCondition, out *PodCondition, c *conversion.Cloner) error { out.Type = in.Type out.Status = in.Status - if err := deepCopy_unversioned_Time(in.LastProbeTime, &out.LastProbeTime, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.LastProbeTime, &out.LastProbeTime, c); err != nil { return err } - if err := deepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil { return err } out.Reason = in.Reason @@ -1616,8 +1902,8 @@ func deepCopy_v1_PodCondition(in PodCondition, out *PodCondition, c *conversion. return nil } -func deepCopy_v1_PodExecOptions(in PodExecOptions, out *PodExecOptions, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_PodExecOptions(in PodExecOptions, out *PodExecOptions, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.Stdin = in.Stdin @@ -1626,27 +1912,27 @@ func deepCopy_v1_PodExecOptions(in PodExecOptions, out *PodExecOptions, c *conve out.TTY = in.TTY out.Container = in.Container if in.Command != nil { - out.Command = make([]string, len(in.Command)) - for i := range in.Command { - out.Command[i] = in.Command[i] - } + in, out := in.Command, &out.Command + *out = make([]string, len(in)) + copy(*out, in) } else { out.Command = nil } return nil } -func deepCopy_v1_PodList(in PodList, out *PodList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_PodList(in PodList, out *PodList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]Pod, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_Pod(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]Pod, len(in)) + for i := range in { + if err := DeepCopy_v1_Pod(in[i], &(*out)[i], c); err != nil { return err } } @@ -1656,22 +1942,24 @@ func deepCopy_v1_PodList(in PodList, out *PodList, c *conversion.Cloner) error { return nil } -func deepCopy_v1_PodLogOptions(in PodLogOptions, out *PodLogOptions, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_PodLogOptions(in PodLogOptions, out *PodLogOptions, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.Container = in.Container out.Follow = in.Follow out.Previous = in.Previous if in.SinceSeconds != nil { - out.SinceSeconds = new(int64) - *out.SinceSeconds = *in.SinceSeconds + in, out := in.SinceSeconds, &out.SinceSeconds + *out = new(int64) + **out = *in } else { out.SinceSeconds = nil } if in.SinceTime != nil { - out.SinceTime = new(unversioned.Time) - if err := deepCopy_unversioned_Time(*in.SinceTime, out.SinceTime, c); err != nil { + in, out := in.SinceTime, &out.SinceTime + *out = new(unversioned.Time) + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { return err } } else { @@ -1679,71 +1967,77 @@ func deepCopy_v1_PodLogOptions(in PodLogOptions, out *PodLogOptions, c *conversi } out.Timestamps = in.Timestamps if in.TailLines != nil { - out.TailLines = new(int64) - *out.TailLines = *in.TailLines + in, out := in.TailLines, &out.TailLines + *out = new(int64) + **out = *in } else { out.TailLines = nil } if in.LimitBytes != nil { - out.LimitBytes = new(int64) - *out.LimitBytes = *in.LimitBytes + in, out := in.LimitBytes, &out.LimitBytes + *out = new(int64) + **out = *in } else { out.LimitBytes = nil } return nil } -func deepCopy_v1_PodProxyOptions(in PodProxyOptions, out *PodProxyOptions, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_PodProxyOptions(in PodProxyOptions, out *PodProxyOptions, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.Path = in.Path return nil } -func deepCopy_v1_PodSecurityContext(in PodSecurityContext, out *PodSecurityContext, c *conversion.Cloner) error { +func DeepCopy_v1_PodSecurityContext(in PodSecurityContext, out *PodSecurityContext, c *conversion.Cloner) error { if in.SELinuxOptions != nil { - out.SELinuxOptions = new(SELinuxOptions) - if err := deepCopy_v1_SELinuxOptions(*in.SELinuxOptions, out.SELinuxOptions, c); err != nil { + in, out := in.SELinuxOptions, &out.SELinuxOptions + *out = new(SELinuxOptions) + if err := DeepCopy_v1_SELinuxOptions(*in, *out, c); err != nil { return err } } else { out.SELinuxOptions = nil } if in.RunAsUser != nil { - out.RunAsUser = new(int64) - *out.RunAsUser = *in.RunAsUser + in, out := in.RunAsUser, &out.RunAsUser + *out = new(int64) + **out = *in } else { out.RunAsUser = nil } if in.RunAsNonRoot != nil { - out.RunAsNonRoot = new(bool) - *out.RunAsNonRoot = *in.RunAsNonRoot + in, out := in.RunAsNonRoot, &out.RunAsNonRoot + *out = new(bool) + **out = *in } else { out.RunAsNonRoot = nil } if in.SupplementalGroups != nil { - out.SupplementalGroups = make([]int64, len(in.SupplementalGroups)) - for i := range in.SupplementalGroups { - out.SupplementalGroups[i] = in.SupplementalGroups[i] - } + in, out := in.SupplementalGroups, &out.SupplementalGroups + *out = make([]int64, len(in)) + copy(*out, in) } else { out.SupplementalGroups = nil } if in.FSGroup != nil { - out.FSGroup = new(int64) - *out.FSGroup = *in.FSGroup + in, out := in.FSGroup, &out.FSGroup + *out = new(int64) + **out = *in } else { out.FSGroup = nil } return nil } -func deepCopy_v1_PodSpec(in PodSpec, out *PodSpec, c *conversion.Cloner) error { +func DeepCopy_v1_PodSpec(in PodSpec, out *PodSpec, c *conversion.Cloner) error { if in.Volumes != nil { - out.Volumes = make([]Volume, len(in.Volumes)) - for i := range in.Volumes { - if err := deepCopy_v1_Volume(in.Volumes[i], &out.Volumes[i], c); err != nil { + in, out := in.Volumes, &out.Volumes + *out = make([]Volume, len(in)) + for i := range in { + if err := DeepCopy_v1_Volume(in[i], &(*out)[i], c); err != nil { return err } } @@ -1751,9 +2045,10 @@ func deepCopy_v1_PodSpec(in PodSpec, out *PodSpec, c *conversion.Cloner) error { out.Volumes = nil } if in.Containers != nil { - out.Containers = make([]Container, len(in.Containers)) - for i := range in.Containers { - if err := deepCopy_v1_Container(in.Containers[i], &out.Containers[i], c); err != nil { + in, out := in.Containers, &out.Containers + *out = make([]Container, len(in)) + for i := range in { + if err := DeepCopy_v1_Container(in[i], &(*out)[i], c); err != nil { return err } } @@ -1762,22 +2057,25 @@ func deepCopy_v1_PodSpec(in PodSpec, out *PodSpec, c *conversion.Cloner) error { } out.RestartPolicy = in.RestartPolicy if in.TerminationGracePeriodSeconds != nil { - out.TerminationGracePeriodSeconds = new(int64) - *out.TerminationGracePeriodSeconds = *in.TerminationGracePeriodSeconds + in, out := in.TerminationGracePeriodSeconds, &out.TerminationGracePeriodSeconds + *out = new(int64) + **out = *in } else { out.TerminationGracePeriodSeconds = nil } if in.ActiveDeadlineSeconds != nil { - out.ActiveDeadlineSeconds = new(int64) - *out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds + in, out := in.ActiveDeadlineSeconds, &out.ActiveDeadlineSeconds + *out = new(int64) + **out = *in } else { out.ActiveDeadlineSeconds = nil } out.DNSPolicy = in.DNSPolicy if in.NodeSelector != nil { - out.NodeSelector = make(map[string]string) - for key, val := range in.NodeSelector { - out.NodeSelector[key] = val + in, out := in.NodeSelector, &out.NodeSelector + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val } } else { out.NodeSelector = nil @@ -1789,17 +2087,19 @@ func deepCopy_v1_PodSpec(in PodSpec, out *PodSpec, c *conversion.Cloner) error { out.HostPID = in.HostPID out.HostIPC = in.HostIPC if in.SecurityContext != nil { - out.SecurityContext = new(PodSecurityContext) - if err := deepCopy_v1_PodSecurityContext(*in.SecurityContext, out.SecurityContext, c); err != nil { + in, out := in.SecurityContext, &out.SecurityContext + *out = new(PodSecurityContext) + if err := DeepCopy_v1_PodSecurityContext(*in, *out, c); err != nil { return err } } else { out.SecurityContext = nil } if in.ImagePullSecrets != nil { - out.ImagePullSecrets = make([]LocalObjectReference, len(in.ImagePullSecrets)) - for i := range in.ImagePullSecrets { - if err := deepCopy_v1_LocalObjectReference(in.ImagePullSecrets[i], &out.ImagePullSecrets[i], c); err != nil { + in, out := in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]LocalObjectReference, len(in)) + for i := range in { + if err := DeepCopy_v1_LocalObjectReference(in[i], &(*out)[i], c); err != nil { return err } } @@ -1809,12 +2109,13 @@ func deepCopy_v1_PodSpec(in PodSpec, out *PodSpec, c *conversion.Cloner) error { return nil } -func deepCopy_v1_PodStatus(in PodStatus, out *PodStatus, c *conversion.Cloner) error { +func DeepCopy_v1_PodStatus(in PodStatus, out *PodStatus, c *conversion.Cloner) error { out.Phase = in.Phase if in.Conditions != nil { - out.Conditions = make([]PodCondition, len(in.Conditions)) - for i := range in.Conditions { - if err := deepCopy_v1_PodCondition(in.Conditions[i], &out.Conditions[i], c); err != nil { + in, out := in.Conditions, &out.Conditions + *out = make([]PodCondition, len(in)) + for i := range in { + if err := DeepCopy_v1_PodCondition(in[i], &(*out)[i], c); err != nil { return err } } @@ -1826,17 +2127,19 @@ func deepCopy_v1_PodStatus(in PodStatus, out *PodStatus, c *conversion.Cloner) e out.HostIP = in.HostIP out.PodIP = in.PodIP if in.StartTime != nil { - out.StartTime = new(unversioned.Time) - if err := deepCopy_unversioned_Time(*in.StartTime, out.StartTime, c); err != nil { + in, out := in.StartTime, &out.StartTime + *out = new(unversioned.Time) + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { return err } } else { out.StartTime = nil } if in.ContainerStatuses != nil { - out.ContainerStatuses = make([]ContainerStatus, len(in.ContainerStatuses)) - for i := range in.ContainerStatuses { - if err := deepCopy_v1_ContainerStatus(in.ContainerStatuses[i], &out.ContainerStatuses[i], c); err != nil { + in, out := in.ContainerStatuses, &out.ContainerStatuses + *out = make([]ContainerStatus, len(in)) + for i := range in { + if err := DeepCopy_v1_ContainerStatus(in[i], &(*out)[i], c); err != nil { return err } } @@ -1846,43 +2149,44 @@ func deepCopy_v1_PodStatus(in PodStatus, out *PodStatus, c *conversion.Cloner) e return nil } -func deepCopy_v1_PodStatusResult(in PodStatusResult, out *PodStatusResult, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_PodStatusResult(in PodStatusResult, out *PodStatusResult, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1_PodStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1_PodStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1_PodTemplate(in PodTemplate, out *PodTemplate, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_PodTemplate(in PodTemplate, out *PodTemplate, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil { + if err := DeepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil { return err } return nil } -func deepCopy_v1_PodTemplateList(in PodTemplateList, out *PodTemplateList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_PodTemplateList(in PodTemplateList, out *PodTemplateList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]PodTemplate, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_PodTemplate(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]PodTemplate, len(in)) + for i := range in { + if err := DeepCopy_v1_PodTemplate(in[i], &(*out)[i], c); err != nil { return err } } @@ -1892,18 +2196,41 @@ func deepCopy_v1_PodTemplateList(in PodTemplateList, out *PodTemplateList, c *co return nil } -func deepCopy_v1_PodTemplateSpec(in PodTemplateSpec, out *PodTemplateSpec, c *conversion.Cloner) error { - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { +func DeepCopy_v1_PodTemplateSpec(in PodTemplateSpec, out *PodTemplateSpec, c *conversion.Cloner) error { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1_PodSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1_PodSpec(in.Spec, &out.Spec, c); err != nil { return err } return nil } -func deepCopy_v1_Probe(in Probe, out *Probe, c *conversion.Cloner) error { - if err := deepCopy_v1_Handler(in.Handler, &out.Handler, c); err != nil { +func DeepCopy_v1_Preconditions(in Preconditions, out *Preconditions, c *conversion.Cloner) error { + if in.UID != nil { + in, out := in.UID, &out.UID + *out = new(types.UID) + if newVal, err := c.DeepCopy(*in); err != nil { + return err + } else { + **out = newVal.(types.UID) + } + } else { + out.UID = nil + } + return nil +} + +func DeepCopy_v1_PreferredSchedulingTerm(in PreferredSchedulingTerm, out *PreferredSchedulingTerm, c *conversion.Cloner) error { + out.Weight = in.Weight + if err := DeepCopy_v1_NodeSelectorTerm(in.Preference, &out.Preference, c); err != nil { + return err + } + return nil +} + +func DeepCopy_v1_Probe(in Probe, out *Probe, c *conversion.Cloner) error { + if err := DeepCopy_v1_Handler(in.Handler, &out.Handler, c); err != nil { return err } out.InitialDelaySeconds = in.InitialDelaySeconds @@ -1914,12 +2241,11 @@ func deepCopy_v1_Probe(in Probe, out *Probe, c *conversion.Cloner) error { return nil } -func deepCopy_v1_RBDVolumeSource(in RBDVolumeSource, out *RBDVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_RBDVolumeSource(in RBDVolumeSource, out *RBDVolumeSource, c *conversion.Cloner) error { if in.CephMonitors != nil { - out.CephMonitors = make([]string, len(in.CephMonitors)) - for i := range in.CephMonitors { - out.CephMonitors[i] = in.CephMonitors[i] - } + in, out := in.CephMonitors, &out.CephMonitors + *out = make([]string, len(in)) + copy(*out, in) } else { out.CephMonitors = nil } @@ -1929,8 +2255,9 @@ func deepCopy_v1_RBDVolumeSource(in RBDVolumeSource, out *RBDVolumeSource, c *co out.RadosUser = in.RadosUser out.Keyring = in.Keyring if in.SecretRef != nil { - out.SecretRef = new(LocalObjectReference) - if err := deepCopy_v1_LocalObjectReference(*in.SecretRef, out.SecretRef, c); err != nil { + in, out := in.SecretRef, &out.SecretRef + *out = new(LocalObjectReference) + if err := DeepCopy_v1_LocalObjectReference(*in, *out, c); err != nil { return err } } else { @@ -1940,52 +2267,52 @@ func deepCopy_v1_RBDVolumeSource(in RBDVolumeSource, out *RBDVolumeSource, c *co return nil } -func deepCopy_v1_RangeAllocation(in RangeAllocation, out *RangeAllocation, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_RangeAllocation(in RangeAllocation, out *RangeAllocation, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } out.Range = in.Range if in.Data != nil { - out.Data = make([]uint8, len(in.Data)) - for i := range in.Data { - out.Data[i] = in.Data[i] - } + in, out := in.Data, &out.Data + *out = make([]byte, len(in)) + copy(*out, in) } else { out.Data = nil } return nil } -func deepCopy_v1_ReplicationController(in ReplicationController, out *ReplicationController, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_ReplicationController(in ReplicationController, out *ReplicationController, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1_ReplicationControllerSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1_ReplicationControllerSpec(in.Spec, &out.Spec, c); err != nil { return err } - if err := deepCopy_v1_ReplicationControllerStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1_ReplicationControllerStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1_ReplicationControllerList(in ReplicationControllerList, out *ReplicationControllerList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_ReplicationControllerList(in ReplicationControllerList, out *ReplicationControllerList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]ReplicationController, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_ReplicationController(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]ReplicationController, len(in)) + for i := range in { + if err := DeepCopy_v1_ReplicationController(in[i], &(*out)[i], c); err != nil { return err } } @@ -1995,24 +2322,27 @@ func deepCopy_v1_ReplicationControllerList(in ReplicationControllerList, out *Re return nil } -func deepCopy_v1_ReplicationControllerSpec(in ReplicationControllerSpec, out *ReplicationControllerSpec, c *conversion.Cloner) error { +func DeepCopy_v1_ReplicationControllerSpec(in ReplicationControllerSpec, out *ReplicationControllerSpec, c *conversion.Cloner) error { if in.Replicas != nil { - out.Replicas = new(int32) - *out.Replicas = *in.Replicas + in, out := in.Replicas, &out.Replicas + *out = new(int32) + **out = *in } else { out.Replicas = nil } if in.Selector != nil { - out.Selector = make(map[string]string) - for key, val := range in.Selector { - out.Selector[key] = val + in, out := in.Selector, &out.Selector + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val } } else { out.Selector = nil } if in.Template != nil { - out.Template = new(PodTemplateSpec) - if err := deepCopy_v1_PodTemplateSpec(*in.Template, out.Template, c); err != nil { + in, out := in.Template, &out.Template + *out = new(PodTemplateSpec) + if err := DeepCopy_v1_PodTemplateSpec(*in, *out, c); err != nil { return err } } else { @@ -2021,39 +2351,41 @@ func deepCopy_v1_ReplicationControllerSpec(in ReplicationControllerSpec, out *Re return nil } -func deepCopy_v1_ReplicationControllerStatus(in ReplicationControllerStatus, out *ReplicationControllerStatus, c *conversion.Cloner) error { +func DeepCopy_v1_ReplicationControllerStatus(in ReplicationControllerStatus, out *ReplicationControllerStatus, c *conversion.Cloner) error { out.Replicas = in.Replicas + out.FullyLabeledReplicas = in.FullyLabeledReplicas out.ObservedGeneration = in.ObservedGeneration return nil } -func deepCopy_v1_ResourceQuota(in ResourceQuota, out *ResourceQuota, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_ResourceQuota(in ResourceQuota, out *ResourceQuota, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1_ResourceQuotaSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1_ResourceQuotaSpec(in.Spec, &out.Spec, c); err != nil { return err } - if err := deepCopy_v1_ResourceQuotaStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1_ResourceQuotaStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1_ResourceQuotaList(in ResourceQuotaList, out *ResourceQuotaList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_ResourceQuotaList(in ResourceQuotaList, out *ResourceQuotaList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]ResourceQuota, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_ResourceQuota(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]ResourceQuota, len(in)) + for i := range in { + if err := DeepCopy_v1_ResourceQuota(in[i], &(*out)[i], c); err != nil { return err } } @@ -2063,43 +2395,55 @@ func deepCopy_v1_ResourceQuotaList(in ResourceQuotaList, out *ResourceQuotaList, return nil } -func deepCopy_v1_ResourceQuotaSpec(in ResourceQuotaSpec, out *ResourceQuotaSpec, c *conversion.Cloner) error { +func DeepCopy_v1_ResourceQuotaSpec(in ResourceQuotaSpec, out *ResourceQuotaSpec, c *conversion.Cloner) error { if in.Hard != nil { - out.Hard = make(ResourceList) - for key, val := range in.Hard { + in, out := in.Hard, &out.Hard + *out = make(ResourceList) + for key, val := range in { newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err } - out.Hard[key] = *newVal + (*out)[key] = *newVal } } else { out.Hard = nil } + if in.Scopes != nil { + in, out := in.Scopes, &out.Scopes + *out = make([]ResourceQuotaScope, len(in)) + for i := range in { + (*out)[i] = in[i] + } + } else { + out.Scopes = nil + } return nil } -func deepCopy_v1_ResourceQuotaStatus(in ResourceQuotaStatus, out *ResourceQuotaStatus, c *conversion.Cloner) error { +func DeepCopy_v1_ResourceQuotaStatus(in ResourceQuotaStatus, out *ResourceQuotaStatus, c *conversion.Cloner) error { if in.Hard != nil { - out.Hard = make(ResourceList) - for key, val := range in.Hard { + in, out := in.Hard, &out.Hard + *out = make(ResourceList) + for key, val := range in { newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err } - out.Hard[key] = *newVal + (*out)[key] = *newVal } } else { out.Hard = nil } if in.Used != nil { - out.Used = make(ResourceList) - for key, val := range in.Used { + in, out := in.Used, &out.Used + *out = make(ResourceList) + for key, val := range in { newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err } - out.Used[key] = *newVal + (*out)[key] = *newVal } } else { out.Used = nil @@ -2107,27 +2451,29 @@ func deepCopy_v1_ResourceQuotaStatus(in ResourceQuotaStatus, out *ResourceQuotaS return nil } -func deepCopy_v1_ResourceRequirements(in ResourceRequirements, out *ResourceRequirements, c *conversion.Cloner) error { +func DeepCopy_v1_ResourceRequirements(in ResourceRequirements, out *ResourceRequirements, c *conversion.Cloner) error { if in.Limits != nil { - out.Limits = make(ResourceList) - for key, val := range in.Limits { + in, out := in.Limits, &out.Limits + *out = make(ResourceList) + for key, val := range in { newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err } - out.Limits[key] = *newVal + (*out)[key] = *newVal } } else { out.Limits = nil } if in.Requests != nil { - out.Requests = make(ResourceList) - for key, val := range in.Requests { + in, out := in.Requests, &out.Requests + *out = make(ResourceList) + for key, val := range in { newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { + if err := resource.DeepCopy_resource_Quantity(val, newVal, c); err != nil { return err } - out.Requests[key] = *newVal + (*out)[key] = *newVal } } else { out.Requests = nil @@ -2135,7 +2481,7 @@ func deepCopy_v1_ResourceRequirements(in ResourceRequirements, out *ResourceRequ return nil } -func deepCopy_v1_SELinuxOptions(in SELinuxOptions, out *SELinuxOptions, c *conversion.Cloner) error { +func DeepCopy_v1_SELinuxOptions(in SELinuxOptions, out *SELinuxOptions, c *conversion.Cloner) error { out.User = in.User out.Role = in.Role out.Type = in.Type @@ -2143,20 +2489,21 @@ func deepCopy_v1_SELinuxOptions(in SELinuxOptions, out *SELinuxOptions, c *conve return nil } -func deepCopy_v1_Secret(in Secret, out *Secret, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_Secret(in Secret, out *Secret, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } if in.Data != nil { - out.Data = make(map[string][]uint8) - for key, val := range in.Data { + in, out := in.Data, &out.Data + *out = make(map[string][]byte) + for key, val := range in { if newVal, err := c.DeepCopy(val); err != nil { return err } else { - out.Data[key] = newVal.([]uint8) + (*out)[key] = newVal.([]byte) } } } else { @@ -2166,25 +2513,26 @@ func deepCopy_v1_Secret(in Secret, out *Secret, c *conversion.Cloner) error { return nil } -func deepCopy_v1_SecretKeySelector(in SecretKeySelector, out *SecretKeySelector, c *conversion.Cloner) error { - if err := deepCopy_v1_LocalObjectReference(in.LocalObjectReference, &out.LocalObjectReference, c); err != nil { +func DeepCopy_v1_SecretKeySelector(in SecretKeySelector, out *SecretKeySelector, c *conversion.Cloner) error { + if err := DeepCopy_v1_LocalObjectReference(in.LocalObjectReference, &out.LocalObjectReference, c); err != nil { return err } out.Key = in.Key return nil } -func deepCopy_v1_SecretList(in SecretList, out *SecretList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_SecretList(in SecretList, out *SecretList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]Secret, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_Secret(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]Secret, len(in)) + for i := range in { + if err := DeepCopy_v1_Secret(in[i], &(*out)[i], c); err != nil { return err } } @@ -2194,92 +2542,99 @@ func deepCopy_v1_SecretList(in SecretList, out *SecretList, c *conversion.Cloner return nil } -func deepCopy_v1_SecretVolumeSource(in SecretVolumeSource, out *SecretVolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_SecretVolumeSource(in SecretVolumeSource, out *SecretVolumeSource, c *conversion.Cloner) error { out.SecretName = in.SecretName return nil } -func deepCopy_v1_SecurityContext(in SecurityContext, out *SecurityContext, c *conversion.Cloner) error { +func DeepCopy_v1_SecurityContext(in SecurityContext, out *SecurityContext, c *conversion.Cloner) error { if in.Capabilities != nil { - out.Capabilities = new(Capabilities) - if err := deepCopy_v1_Capabilities(*in.Capabilities, out.Capabilities, c); err != nil { + in, out := in.Capabilities, &out.Capabilities + *out = new(Capabilities) + if err := DeepCopy_v1_Capabilities(*in, *out, c); err != nil { return err } } else { out.Capabilities = nil } if in.Privileged != nil { - out.Privileged = new(bool) - *out.Privileged = *in.Privileged + in, out := in.Privileged, &out.Privileged + *out = new(bool) + **out = *in } else { out.Privileged = nil } if in.SELinuxOptions != nil { - out.SELinuxOptions = new(SELinuxOptions) - if err := deepCopy_v1_SELinuxOptions(*in.SELinuxOptions, out.SELinuxOptions, c); err != nil { + in, out := in.SELinuxOptions, &out.SELinuxOptions + *out = new(SELinuxOptions) + if err := DeepCopy_v1_SELinuxOptions(*in, *out, c); err != nil { return err } } else { out.SELinuxOptions = nil } if in.RunAsUser != nil { - out.RunAsUser = new(int64) - *out.RunAsUser = *in.RunAsUser + in, out := in.RunAsUser, &out.RunAsUser + *out = new(int64) + **out = *in } else { out.RunAsUser = nil } if in.RunAsNonRoot != nil { - out.RunAsNonRoot = new(bool) - *out.RunAsNonRoot = *in.RunAsNonRoot + in, out := in.RunAsNonRoot, &out.RunAsNonRoot + *out = new(bool) + **out = *in } else { out.RunAsNonRoot = nil } if in.ReadOnlyRootFilesystem != nil { - out.ReadOnlyRootFilesystem = new(bool) - *out.ReadOnlyRootFilesystem = *in.ReadOnlyRootFilesystem + in, out := in.ReadOnlyRootFilesystem, &out.ReadOnlyRootFilesystem + *out = new(bool) + **out = *in } else { out.ReadOnlyRootFilesystem = nil } return nil } -func deepCopy_v1_SerializedReference(in SerializedReference, out *SerializedReference, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_SerializedReference(in SerializedReference, out *SerializedReference, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectReference(in.Reference, &out.Reference, c); err != nil { + if err := DeepCopy_v1_ObjectReference(in.Reference, &out.Reference, c); err != nil { return err } return nil } -func deepCopy_v1_Service(in Service, out *Service, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_Service(in Service, out *Service, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1_ServiceSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1_ServiceSpec(in.Spec, &out.Spec, c); err != nil { return err } - if err := deepCopy_v1_ServiceStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1_ServiceStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1_ServiceAccount(in ServiceAccount, out *ServiceAccount, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_ServiceAccount(in ServiceAccount, out *ServiceAccount, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } if in.Secrets != nil { - out.Secrets = make([]ObjectReference, len(in.Secrets)) - for i := range in.Secrets { - if err := deepCopy_v1_ObjectReference(in.Secrets[i], &out.Secrets[i], c); err != nil { + in, out := in.Secrets, &out.Secrets + *out = make([]ObjectReference, len(in)) + for i := range in { + if err := DeepCopy_v1_ObjectReference(in[i], &(*out)[i], c); err != nil { return err } } @@ -2287,9 +2642,10 @@ func deepCopy_v1_ServiceAccount(in ServiceAccount, out *ServiceAccount, c *conve out.Secrets = nil } if in.ImagePullSecrets != nil { - out.ImagePullSecrets = make([]LocalObjectReference, len(in.ImagePullSecrets)) - for i := range in.ImagePullSecrets { - if err := deepCopy_v1_LocalObjectReference(in.ImagePullSecrets[i], &out.ImagePullSecrets[i], c); err != nil { + in, out := in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]LocalObjectReference, len(in)) + for i := range in { + if err := DeepCopy_v1_LocalObjectReference(in[i], &(*out)[i], c); err != nil { return err } } @@ -2299,17 +2655,18 @@ func deepCopy_v1_ServiceAccount(in ServiceAccount, out *ServiceAccount, c *conve return nil } -func deepCopy_v1_ServiceAccountList(in ServiceAccountList, out *ServiceAccountList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_ServiceAccountList(in ServiceAccountList, out *ServiceAccountList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]ServiceAccount, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_ServiceAccount(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]ServiceAccount, len(in)) + for i := range in { + if err := DeepCopy_v1_ServiceAccount(in[i], &(*out)[i], c); err != nil { return err } } @@ -2319,17 +2676,18 @@ func deepCopy_v1_ServiceAccountList(in ServiceAccountList, out *ServiceAccountLi return nil } -func deepCopy_v1_ServiceList(in ServiceList, out *ServiceList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1_ServiceList(in ServiceList, out *ServiceList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]Service, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_Service(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]Service, len(in)) + for i := range in { + if err := DeepCopy_v1_Service(in[i], &(*out)[i], c); err != nil { return err } } @@ -2339,22 +2697,31 @@ func deepCopy_v1_ServiceList(in ServiceList, out *ServiceList, c *conversion.Clo return nil } -func deepCopy_v1_ServicePort(in ServicePort, out *ServicePort, c *conversion.Cloner) error { +func DeepCopy_v1_ServicePort(in ServicePort, out *ServicePort, c *conversion.Cloner) error { out.Name = in.Name out.Protocol = in.Protocol out.Port = in.Port - if err := deepCopy_intstr_IntOrString(in.TargetPort, &out.TargetPort, c); err != nil { + if err := intstr.DeepCopy_intstr_IntOrString(in.TargetPort, &out.TargetPort, c); err != nil { return err } out.NodePort = in.NodePort return nil } -func deepCopy_v1_ServiceSpec(in ServiceSpec, out *ServiceSpec, c *conversion.Cloner) error { +func DeepCopy_v1_ServiceProxyOptions(in ServiceProxyOptions, out *ServiceProxyOptions, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + out.Path = in.Path + return nil +} + +func DeepCopy_v1_ServiceSpec(in ServiceSpec, out *ServiceSpec, c *conversion.Cloner) error { if in.Ports != nil { - out.Ports = make([]ServicePort, len(in.Ports)) - for i := range in.Ports { - if err := deepCopy_v1_ServicePort(in.Ports[i], &out.Ports[i], c); err != nil { + in, out := in.Ports, &out.Ports + *out = make([]ServicePort, len(in)) + for i := range in { + if err := DeepCopy_v1_ServicePort(in[i], &(*out)[i], c); err != nil { return err } } @@ -2362,9 +2729,10 @@ func deepCopy_v1_ServiceSpec(in ServiceSpec, out *ServiceSpec, c *conversion.Clo out.Ports = nil } if in.Selector != nil { - out.Selector = make(map[string]string) - for key, val := range in.Selector { - out.Selector[key] = val + in, out := in.Selector, &out.Selector + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val } } else { out.Selector = nil @@ -2372,18 +2740,16 @@ func deepCopy_v1_ServiceSpec(in ServiceSpec, out *ServiceSpec, c *conversion.Clo out.ClusterIP = in.ClusterIP out.Type = in.Type if in.ExternalIPs != nil { - out.ExternalIPs = make([]string, len(in.ExternalIPs)) - for i := range in.ExternalIPs { - out.ExternalIPs[i] = in.ExternalIPs[i] - } + in, out := in.ExternalIPs, &out.ExternalIPs + *out = make([]string, len(in)) + copy(*out, in) } else { out.ExternalIPs = nil } if in.DeprecatedPublicIPs != nil { - out.DeprecatedPublicIPs = make([]string, len(in.DeprecatedPublicIPs)) - for i := range in.DeprecatedPublicIPs { - out.DeprecatedPublicIPs[i] = in.DeprecatedPublicIPs[i] - } + in, out := in.DeprecatedPublicIPs, &out.DeprecatedPublicIPs + *out = make([]string, len(in)) + copy(*out, in) } else { out.DeprecatedPublicIPs = nil } @@ -2392,183 +2758,202 @@ func deepCopy_v1_ServiceSpec(in ServiceSpec, out *ServiceSpec, c *conversion.Clo return nil } -func deepCopy_v1_ServiceStatus(in ServiceStatus, out *ServiceStatus, c *conversion.Cloner) error { - if err := deepCopy_v1_LoadBalancerStatus(in.LoadBalancer, &out.LoadBalancer, c); err != nil { +func DeepCopy_v1_ServiceStatus(in ServiceStatus, out *ServiceStatus, c *conversion.Cloner) error { + if err := DeepCopy_v1_LoadBalancerStatus(in.LoadBalancer, &out.LoadBalancer, c); err != nil { return err } return nil } -func deepCopy_v1_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *conversion.Cloner) error { - if err := deepCopy_intstr_IntOrString(in.Port, &out.Port, c); err != nil { +func DeepCopy_v1_TCPSocketAction(in TCPSocketAction, out *TCPSocketAction, c *conversion.Cloner) error { + if err := intstr.DeepCopy_intstr_IntOrString(in.Port, &out.Port, c); err != nil { return err } return nil } -func deepCopy_v1_Volume(in Volume, out *Volume, c *conversion.Cloner) error { +func DeepCopy_v1_Volume(in Volume, out *Volume, c *conversion.Cloner) error { out.Name = in.Name - if err := deepCopy_v1_VolumeSource(in.VolumeSource, &out.VolumeSource, c); err != nil { + if err := DeepCopy_v1_VolumeSource(in.VolumeSource, &out.VolumeSource, c); err != nil { return err } return nil } -func deepCopy_v1_VolumeMount(in VolumeMount, out *VolumeMount, c *conversion.Cloner) error { +func DeepCopy_v1_VolumeMount(in VolumeMount, out *VolumeMount, c *conversion.Cloner) error { out.Name = in.Name out.ReadOnly = in.ReadOnly out.MountPath = in.MountPath return nil } -func deepCopy_v1_VolumeSource(in VolumeSource, out *VolumeSource, c *conversion.Cloner) error { +func DeepCopy_v1_VolumeSource(in VolumeSource, out *VolumeSource, c *conversion.Cloner) error { if in.HostPath != nil { - out.HostPath = new(HostPathVolumeSource) - if err := deepCopy_v1_HostPathVolumeSource(*in.HostPath, out.HostPath, c); err != nil { + in, out := in.HostPath, &out.HostPath + *out = new(HostPathVolumeSource) + if err := DeepCopy_v1_HostPathVolumeSource(*in, *out, c); err != nil { return err } } else { out.HostPath = nil } if in.EmptyDir != nil { - out.EmptyDir = new(EmptyDirVolumeSource) - if err := deepCopy_v1_EmptyDirVolumeSource(*in.EmptyDir, out.EmptyDir, c); err != nil { + in, out := in.EmptyDir, &out.EmptyDir + *out = new(EmptyDirVolumeSource) + if err := DeepCopy_v1_EmptyDirVolumeSource(*in, *out, c); err != nil { return err } } else { out.EmptyDir = nil } if in.GCEPersistentDisk != nil { - out.GCEPersistentDisk = new(GCEPersistentDiskVolumeSource) - if err := deepCopy_v1_GCEPersistentDiskVolumeSource(*in.GCEPersistentDisk, out.GCEPersistentDisk, c); err != nil { + in, out := in.GCEPersistentDisk, &out.GCEPersistentDisk + *out = new(GCEPersistentDiskVolumeSource) + if err := DeepCopy_v1_GCEPersistentDiskVolumeSource(*in, *out, c); err != nil { return err } } else { out.GCEPersistentDisk = nil } if in.AWSElasticBlockStore != nil { - out.AWSElasticBlockStore = new(AWSElasticBlockStoreVolumeSource) - if err := deepCopy_v1_AWSElasticBlockStoreVolumeSource(*in.AWSElasticBlockStore, out.AWSElasticBlockStore, c); err != nil { + in, out := in.AWSElasticBlockStore, &out.AWSElasticBlockStore + *out = new(AWSElasticBlockStoreVolumeSource) + if err := DeepCopy_v1_AWSElasticBlockStoreVolumeSource(*in, *out, c); err != nil { return err } } else { out.AWSElasticBlockStore = nil } if in.GitRepo != nil { - out.GitRepo = new(GitRepoVolumeSource) - if err := deepCopy_v1_GitRepoVolumeSource(*in.GitRepo, out.GitRepo, c); err != nil { + in, out := in.GitRepo, &out.GitRepo + *out = new(GitRepoVolumeSource) + if err := DeepCopy_v1_GitRepoVolumeSource(*in, *out, c); err != nil { return err } } else { out.GitRepo = nil } if in.Secret != nil { - out.Secret = new(SecretVolumeSource) - if err := deepCopy_v1_SecretVolumeSource(*in.Secret, out.Secret, c); err != nil { + in, out := in.Secret, &out.Secret + *out = new(SecretVolumeSource) + if err := DeepCopy_v1_SecretVolumeSource(*in, *out, c); err != nil { return err } } else { out.Secret = nil } if in.NFS != nil { - out.NFS = new(NFSVolumeSource) - if err := deepCopy_v1_NFSVolumeSource(*in.NFS, out.NFS, c); err != nil { + in, out := in.NFS, &out.NFS + *out = new(NFSVolumeSource) + if err := DeepCopy_v1_NFSVolumeSource(*in, *out, c); err != nil { return err } } else { out.NFS = nil } if in.ISCSI != nil { - out.ISCSI = new(ISCSIVolumeSource) - if err := deepCopy_v1_ISCSIVolumeSource(*in.ISCSI, out.ISCSI, c); err != nil { + in, out := in.ISCSI, &out.ISCSI + *out = new(ISCSIVolumeSource) + if err := DeepCopy_v1_ISCSIVolumeSource(*in, *out, c); err != nil { return err } } else { out.ISCSI = nil } if in.Glusterfs != nil { - out.Glusterfs = new(GlusterfsVolumeSource) - if err := deepCopy_v1_GlusterfsVolumeSource(*in.Glusterfs, out.Glusterfs, c); err != nil { + in, out := in.Glusterfs, &out.Glusterfs + *out = new(GlusterfsVolumeSource) + if err := DeepCopy_v1_GlusterfsVolumeSource(*in, *out, c); err != nil { return err } } else { out.Glusterfs = nil } if in.PersistentVolumeClaim != nil { - out.PersistentVolumeClaim = new(PersistentVolumeClaimVolumeSource) - if err := deepCopy_v1_PersistentVolumeClaimVolumeSource(*in.PersistentVolumeClaim, out.PersistentVolumeClaim, c); err != nil { + in, out := in.PersistentVolumeClaim, &out.PersistentVolumeClaim + *out = new(PersistentVolumeClaimVolumeSource) + if err := DeepCopy_v1_PersistentVolumeClaimVolumeSource(*in, *out, c); err != nil { return err } } else { out.PersistentVolumeClaim = nil } if in.RBD != nil { - out.RBD = new(RBDVolumeSource) - if err := deepCopy_v1_RBDVolumeSource(*in.RBD, out.RBD, c); err != nil { + in, out := in.RBD, &out.RBD + *out = new(RBDVolumeSource) + if err := DeepCopy_v1_RBDVolumeSource(*in, *out, c); err != nil { return err } } else { out.RBD = nil } if in.FlexVolume != nil { - out.FlexVolume = new(FlexVolumeSource) - if err := deepCopy_v1_FlexVolumeSource(*in.FlexVolume, out.FlexVolume, c); err != nil { + in, out := in.FlexVolume, &out.FlexVolume + *out = new(FlexVolumeSource) + if err := DeepCopy_v1_FlexVolumeSource(*in, *out, c); err != nil { return err } } else { out.FlexVolume = nil } if in.Cinder != nil { - out.Cinder = new(CinderVolumeSource) - if err := deepCopy_v1_CinderVolumeSource(*in.Cinder, out.Cinder, c); err != nil { + in, out := in.Cinder, &out.Cinder + *out = new(CinderVolumeSource) + if err := DeepCopy_v1_CinderVolumeSource(*in, *out, c); err != nil { return err } } else { out.Cinder = nil } if in.CephFS != nil { - out.CephFS = new(CephFSVolumeSource) - if err := deepCopy_v1_CephFSVolumeSource(*in.CephFS, out.CephFS, c); err != nil { + in, out := in.CephFS, &out.CephFS + *out = new(CephFSVolumeSource) + if err := DeepCopy_v1_CephFSVolumeSource(*in, *out, c); err != nil { return err } } else { out.CephFS = nil } if in.Flocker != nil { - out.Flocker = new(FlockerVolumeSource) - if err := deepCopy_v1_FlockerVolumeSource(*in.Flocker, out.Flocker, c); err != nil { + in, out := in.Flocker, &out.Flocker + *out = new(FlockerVolumeSource) + if err := DeepCopy_v1_FlockerVolumeSource(*in, *out, c); err != nil { return err } } else { out.Flocker = nil } if in.DownwardAPI != nil { - out.DownwardAPI = new(DownwardAPIVolumeSource) - if err := deepCopy_v1_DownwardAPIVolumeSource(*in.DownwardAPI, out.DownwardAPI, c); err != nil { + in, out := in.DownwardAPI, &out.DownwardAPI + *out = new(DownwardAPIVolumeSource) + if err := DeepCopy_v1_DownwardAPIVolumeSource(*in, *out, c); err != nil { return err } } else { out.DownwardAPI = nil } if in.FC != nil { - out.FC = new(FCVolumeSource) - if err := deepCopy_v1_FCVolumeSource(*in.FC, out.FC, c); err != nil { + in, out := in.FC, &out.FC + *out = new(FCVolumeSource) + if err := DeepCopy_v1_FCVolumeSource(*in, *out, c); err != nil { return err } } else { out.FC = nil } if in.AzureFile != nil { - out.AzureFile = new(AzureFileVolumeSource) - if err := deepCopy_v1_AzureFileVolumeSource(*in.AzureFile, out.AzureFile, c); err != nil { + in, out := in.AzureFile, &out.AzureFile + *out = new(AzureFileVolumeSource) + if err := DeepCopy_v1_AzureFileVolumeSource(*in, *out, c); err != nil { return err } } else { out.AzureFile = nil } if in.ConfigMap != nil { - out.ConfigMap = new(ConfigMapVolumeSource) - if err := deepCopy_v1_ConfigMapVolumeSource(*in.ConfigMap, out.ConfigMap, c); err != nil { + in, out := in.ConfigMap, &out.ConfigMap + *out = new(ConfigMapVolumeSource) + if err := DeepCopy_v1_ConfigMapVolumeSource(*in, *out, c); err != nil { return err } } else { @@ -2576,174 +2961,3 @@ func deepCopy_v1_VolumeSource(in VolumeSource, out *VolumeSource, c *conversion. } return nil } - -func deepCopy_runtime_RawExtension(in runtime.RawExtension, out *runtime.RawExtension, c *conversion.Cloner) error { - if in.RawJSON != nil { - out.RawJSON = make([]uint8, len(in.RawJSON)) - for i := range in.RawJSON { - out.RawJSON[i] = in.RawJSON[i] - } - } else { - out.RawJSON = nil - } - if newVal, err := c.DeepCopy(in.Object); err != nil { - return err - } else if newVal == nil { - out.Object = nil - } else { - out.Object = newVal.(runtime.Object) - } - return nil -} - -func deepCopy_intstr_IntOrString(in intstr.IntOrString, out *intstr.IntOrString, c *conversion.Cloner) error { - out.Type = in.Type - out.IntVal = in.IntVal - out.StrVal = in.StrVal - return nil -} - -func init() { - err := api.Scheme.AddGeneratedDeepCopyFuncs( - deepCopy_resource_Quantity, - deepCopy_unversioned_ListMeta, - deepCopy_unversioned_Time, - deepCopy_unversioned_TypeMeta, - deepCopy_v1_AWSElasticBlockStoreVolumeSource, - deepCopy_v1_AzureFileVolumeSource, - deepCopy_v1_Binding, - deepCopy_v1_Capabilities, - deepCopy_v1_CephFSVolumeSource, - deepCopy_v1_CinderVolumeSource, - deepCopy_v1_ComponentCondition, - deepCopy_v1_ComponentStatus, - deepCopy_v1_ComponentStatusList, - deepCopy_v1_ConfigMap, - deepCopy_v1_ConfigMapKeySelector, - deepCopy_v1_ConfigMapList, - deepCopy_v1_ConfigMapVolumeSource, - deepCopy_v1_Container, - deepCopy_v1_ContainerImage, - deepCopy_v1_ContainerPort, - deepCopy_v1_ContainerState, - deepCopy_v1_ContainerStateRunning, - deepCopy_v1_ContainerStateTerminated, - deepCopy_v1_ContainerStateWaiting, - deepCopy_v1_ContainerStatus, - deepCopy_v1_DaemonEndpoint, - deepCopy_v1_DeleteOptions, - deepCopy_v1_DownwardAPIVolumeFile, - deepCopy_v1_DownwardAPIVolumeSource, - deepCopy_v1_EmptyDirVolumeSource, - deepCopy_v1_EndpointAddress, - deepCopy_v1_EndpointPort, - deepCopy_v1_EndpointSubset, - deepCopy_v1_Endpoints, - deepCopy_v1_EndpointsList, - deepCopy_v1_EnvVar, - deepCopy_v1_EnvVarSource, - deepCopy_v1_Event, - deepCopy_v1_EventList, - deepCopy_v1_EventSource, - deepCopy_v1_ExecAction, - deepCopy_v1_ExportOptions, - deepCopy_v1_FCVolumeSource, - deepCopy_v1_FlexVolumeSource, - deepCopy_v1_FlockerVolumeSource, - deepCopy_v1_GCEPersistentDiskVolumeSource, - deepCopy_v1_GitRepoVolumeSource, - deepCopy_v1_GlusterfsVolumeSource, - deepCopy_v1_HTTPGetAction, - deepCopy_v1_HTTPHeader, - deepCopy_v1_Handler, - deepCopy_v1_HostPathVolumeSource, - deepCopy_v1_ISCSIVolumeSource, - deepCopy_v1_KeyToPath, - deepCopy_v1_Lifecycle, - deepCopy_v1_LimitRange, - deepCopy_v1_LimitRangeItem, - deepCopy_v1_LimitRangeList, - deepCopy_v1_LimitRangeSpec, - deepCopy_v1_List, - deepCopy_v1_ListOptions, - deepCopy_v1_LoadBalancerIngress, - deepCopy_v1_LoadBalancerStatus, - deepCopy_v1_LocalObjectReference, - deepCopy_v1_NFSVolumeSource, - deepCopy_v1_Namespace, - deepCopy_v1_NamespaceList, - deepCopy_v1_NamespaceSpec, - deepCopy_v1_NamespaceStatus, - deepCopy_v1_Node, - deepCopy_v1_NodeAddress, - deepCopy_v1_NodeCondition, - deepCopy_v1_NodeDaemonEndpoints, - deepCopy_v1_NodeList, - deepCopy_v1_NodeSpec, - deepCopy_v1_NodeStatus, - deepCopy_v1_NodeSystemInfo, - deepCopy_v1_ObjectFieldSelector, - deepCopy_v1_ObjectMeta, - deepCopy_v1_ObjectReference, - deepCopy_v1_PersistentVolume, - deepCopy_v1_PersistentVolumeClaim, - deepCopy_v1_PersistentVolumeClaimList, - deepCopy_v1_PersistentVolumeClaimSpec, - deepCopy_v1_PersistentVolumeClaimStatus, - deepCopy_v1_PersistentVolumeClaimVolumeSource, - deepCopy_v1_PersistentVolumeList, - deepCopy_v1_PersistentVolumeSource, - deepCopy_v1_PersistentVolumeSpec, - deepCopy_v1_PersistentVolumeStatus, - deepCopy_v1_Pod, - deepCopy_v1_PodAttachOptions, - deepCopy_v1_PodCondition, - deepCopy_v1_PodExecOptions, - deepCopy_v1_PodList, - deepCopy_v1_PodLogOptions, - deepCopy_v1_PodProxyOptions, - deepCopy_v1_PodSecurityContext, - deepCopy_v1_PodSpec, - deepCopy_v1_PodStatus, - deepCopy_v1_PodStatusResult, - deepCopy_v1_PodTemplate, - deepCopy_v1_PodTemplateList, - deepCopy_v1_PodTemplateSpec, - deepCopy_v1_Probe, - deepCopy_v1_RBDVolumeSource, - deepCopy_v1_RangeAllocation, - deepCopy_v1_ReplicationController, - deepCopy_v1_ReplicationControllerList, - deepCopy_v1_ReplicationControllerSpec, - deepCopy_v1_ReplicationControllerStatus, - deepCopy_v1_ResourceQuota, - deepCopy_v1_ResourceQuotaList, - deepCopy_v1_ResourceQuotaSpec, - deepCopy_v1_ResourceQuotaStatus, - deepCopy_v1_ResourceRequirements, - deepCopy_v1_SELinuxOptions, - deepCopy_v1_Secret, - deepCopy_v1_SecretKeySelector, - deepCopy_v1_SecretList, - deepCopy_v1_SecretVolumeSource, - deepCopy_v1_SecurityContext, - deepCopy_v1_SerializedReference, - deepCopy_v1_Service, - deepCopy_v1_ServiceAccount, - deepCopy_v1_ServiceAccountList, - deepCopy_v1_ServiceList, - deepCopy_v1_ServicePort, - deepCopy_v1_ServiceSpec, - deepCopy_v1_ServiceStatus, - deepCopy_v1_TCPSocketAction, - deepCopy_v1_Volume, - deepCopy_v1_VolumeMount, - deepCopy_v1_VolumeSource, - deepCopy_runtime_RawExtension, - deepCopy_intstr_IntOrString, - ) - if err != nil { - // if one of the deep copy functions is malformed, detect it immediately. - panic(err) - } -} diff --git a/vendor/k8s.io/kubernetes/pkg/api/v1/register.go b/vendor/k8s.io/kubernetes/pkg/api/v1/register.go index 96f407c40..1a8342c63 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/v1/register.go +++ b/vendor/k8s.io/kubernetes/pkg/api/v1/register.go @@ -19,6 +19,7 @@ package v1 import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" + versionedwatch "k8s.io/kubernetes/pkg/watch/versioned" ) // GroupName is the group name use in this package @@ -45,11 +46,13 @@ func addKnownTypes(scheme *runtime.Scheme) { &ReplicationController{}, &ReplicationControllerList{}, &Service{}, + &ServiceProxyOptions{}, &ServiceList{}, &Endpoints{}, &EndpointsList{}, &Node{}, &NodeList{}, + &NodeProxyOptions{}, &Binding{}, &Event{}, &EventList{}, @@ -85,6 +88,9 @@ func addKnownTypes(scheme *runtime.Scheme) { // Add common types scheme.AddKnownTypes(SchemeGroupVersion, &unversioned.Status{}) + + // Add the watch version that applies + versionedwatch.AddToGroupVersion(scheme, SchemeGroupVersion) } func (obj *Pod) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } @@ -100,6 +106,7 @@ func (obj *Endpoints) GetObjectKind() unversioned.ObjectKind { r func (obj *EndpointsList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *Node) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *NodeList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } +func (obj *NodeProxyOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *Binding) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *Event) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *EventList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } @@ -124,6 +131,7 @@ func (obj *PodAttachOptions) GetObjectKind() unversioned.ObjectKind { r func (obj *PodLogOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *PodExecOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *PodProxyOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } +func (obj *ServiceProxyOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *ComponentStatus) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *ComponentStatusList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *SerializedReference) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/vendor/k8s.io/kubernetes/pkg/api/v1/types.generated.go b/vendor/k8s.io/kubernetes/pkg/api/v1/types.generated.go index 64344ff42..504870417 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/v1/types.generated.go +++ b/vendor/k8s.io/kubernetes/pkg/api/v1/types.generated.go @@ -22412,7 +22412,7 @@ func (x *PreferredSchedulingTerm) codecDecodeSelfFromMap(l int, d *codec1978.Dec if r.TryDecodeAsNil() { x.Weight = 0 } else { - x.Weight = int(r.DecodeInt(codecSelferBitsize1234)) + x.Weight = int32(r.DecodeInt(32)) } case "preference": if r.TryDecodeAsNil() { @@ -22449,7 +22449,7 @@ func (x *PreferredSchedulingTerm) codecDecodeSelfFromArray(l int, d *codec1978.D if r.TryDecodeAsNil() { x.Weight = 0 } else { - x.Weight = int(r.DecodeInt(codecSelferBitsize1234)) + x.Weight = int32(r.DecodeInt(32)) } yyj6++ if yyhl6 { @@ -26683,13 +26683,14 @@ func (x *ReplicationControllerStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [2]bool + var yyq2 [3]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[1] = x.ObservedGeneration != 0 + yyq2[1] = x.FullyLabeledReplicas != 0 + yyq2[2] = x.ObservedGeneration != 0 var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(2) + r.EncodeArrayStart(3) } else { yynn2 = 1 for _, b := range yyq2 { @@ -26726,7 +26727,7 @@ func (x *ReplicationControllerStatus) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym7 if false { } else { - r.EncodeInt(int64(x.ObservedGeneration)) + r.EncodeInt(int64(x.FullyLabeledReplicas)) } } else { r.EncodeInt(0) @@ -26734,11 +26735,36 @@ func (x *ReplicationControllerStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[1] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("observedGeneration")) + r.EncodeString(codecSelferC_UTF81234, string("fullyLabeledReplicas")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym8 := z.EncBinary() _ = yym8 if false { + } else { + r.EncodeInt(int64(x.FullyLabeledReplicas)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeInt(int64(x.ObservedGeneration)) + } + } else { + r.EncodeInt(0) + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("observedGeneration")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym11 := z.EncBinary() + _ = yym11 + if false { } else { r.EncodeInt(int64(x.ObservedGeneration)) } @@ -26811,6 +26837,12 @@ func (x *ReplicationControllerStatus) codecDecodeSelfFromMap(l int, d *codec1978 } else { x.Replicas = int32(r.DecodeInt(32)) } + case "fullyLabeledReplicas": + if r.TryDecodeAsNil() { + x.FullyLabeledReplicas = 0 + } else { + x.FullyLabeledReplicas = int32(r.DecodeInt(32)) + } case "observedGeneration": if r.TryDecodeAsNil() { x.ObservedGeneration = 0 @@ -26828,16 +26860,16 @@ func (x *ReplicationControllerStatus) codecDecodeSelfFromArray(l int, d *codec19 var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj6 int - var yyb6 bool - var yyhl6 bool = l >= 0 - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb6 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb6 { + if yyb7 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -26847,13 +26879,29 @@ func (x *ReplicationControllerStatus) codecDecodeSelfFromArray(l int, d *codec19 } else { x.Replicas = int32(r.DecodeInt(32)) } - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb6 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb6 { + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.FullyLabeledReplicas = 0 + } else { + x.FullyLabeledReplicas = int32(r.DecodeInt(32)) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -26864,17 +26912,17 @@ func (x *ReplicationControllerStatus) codecDecodeSelfFromArray(l int, d *codec19 x.ObservedGeneration = int64(r.DecodeInt(64)) } for { - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb6 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb6 { + if yyb7 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj6-1, "") + z.DecStructFieldNotFound(yyj7-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -33107,11 +33155,12 @@ func (x *NodeStatus) CodecEncodeSelf(e *codec1978.Encoder) { yyq2[4] = len(x.Addresses) != 0 yyq2[5] = true yyq2[6] = true + yyq2[7] = len(x.Images) != 0 var yynn2 int if yyr2 || yy2arr2 { r.EncodeArrayStart(8) } else { - yynn2 = 1 + yynn2 = 0 for _, b := range yyq2 { if b { yynn2++ @@ -33283,28 +33332,34 @@ func (x *NodeStatus) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if x.Images == nil { - r.EncodeNil() - } else { - yym29 := z.EncBinary() - _ = yym29 - if false { + if yyq2[7] { + if x.Images == nil { + r.EncodeNil() } else { - h.encSliceContainerImage(([]ContainerImage)(x.Images), e) + yym29 := z.EncBinary() + _ = yym29 + if false { + } else { + h.encSliceContainerImage(([]ContainerImage)(x.Images), e) + } } + } else { + r.EncodeNil() } } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("images")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.Images == nil { - r.EncodeNil() - } else { - yym30 := z.EncBinary() - _ = yym30 - if false { + if yyq2[7] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("images")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Images == nil { + r.EncodeNil() } else { - h.encSliceContainerImage(([]ContainerImage)(x.Images), e) + yym30 := z.EncBinary() + _ = yym30 + if false { + } else { + h.encSliceContainerImage(([]ContainerImage)(x.Images), e) + } } } } @@ -33636,7 +33691,7 @@ func (x *ContainerImage) CodecEncodeSelf(e *codec1978.Encoder) { var yyq2 [2]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[1] = x.Size != 0 + yyq2[1] = x.SizeBytes != 0 var yynn2 int if yyr2 || yy2arr2 { r.EncodeArrayStart(2) @@ -33652,28 +33707,28 @@ func (x *ContainerImage) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if x.RepoTags == nil { + if x.Names == nil { r.EncodeNil() } else { yym4 := z.EncBinary() _ = yym4 if false { } else { - z.F.EncSliceStringV(x.RepoTags, false, e) + z.F.EncSliceStringV(x.Names, false, e) } } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("repoTags")) + r.EncodeString(codecSelferC_UTF81234, string("names")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.RepoTags == nil { + if x.Names == nil { r.EncodeNil() } else { yym5 := z.EncBinary() _ = yym5 if false { } else { - z.F.EncSliceStringV(x.RepoTags, false, e) + z.F.EncSliceStringV(x.Names, false, e) } } } @@ -33684,7 +33739,7 @@ func (x *ContainerImage) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym7 if false { } else { - r.EncodeInt(int64(x.Size)) + r.EncodeInt(int64(x.SizeBytes)) } } else { r.EncodeInt(0) @@ -33692,13 +33747,13 @@ func (x *ContainerImage) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[1] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("size")) + r.EncodeString(codecSelferC_UTF81234, string("sizeBytes")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym8 := z.EncBinary() _ = yym8 if false { } else { - r.EncodeInt(int64(x.Size)) + r.EncodeInt(int64(x.SizeBytes)) } } } @@ -33763,11 +33818,11 @@ func (x *ContainerImage) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { yys3 := string(yys3Slc) z.DecSendContainerState(codecSelfer_containerMapValue1234) switch yys3 { - case "repoTags": + case "names": if r.TryDecodeAsNil() { - x.RepoTags = nil + x.Names = nil } else { - yyv4 := &x.RepoTags + yyv4 := &x.Names yym5 := z.DecBinary() _ = yym5 if false { @@ -33775,11 +33830,11 @@ func (x *ContainerImage) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { z.F.DecSliceStringX(yyv4, false, d) } } - case "size": + case "sizeBytes": if r.TryDecodeAsNil() { - x.Size = 0 + x.SizeBytes = 0 } else { - x.Size = int64(r.DecodeInt(64)) + x.SizeBytes = int64(r.DecodeInt(64)) } default: z.DecStructFieldNotFound(-1, yys3) @@ -33807,9 +33862,9 @@ func (x *ContainerImage) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.RepoTags = nil + x.Names = nil } else { - yyv8 := &x.RepoTags + yyv8 := &x.Names yym9 := z.DecBinary() _ = yym9 if false { @@ -33829,9 +33884,9 @@ func (x *ContainerImage) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.Size = 0 + x.SizeBytes = 0 } else { - x.Size = int64(r.DecodeInt(64)) + x.SizeBytes = int64(r.DecodeInt(64)) } for { yyj7++ @@ -36687,6 +36742,209 @@ func (x *Binding) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } +func (x *Preconditions) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [1]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.UID != nil + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(1) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + if x.UID == nil { + r.EncodeNil() + } else { + yy4 := *x.UID + yym5 := z.EncBinary() + _ = yym5 + if false { + } else if z.HasExtensions() && z.EncExt(yy4) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(yy4)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("uid")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.UID == nil { + r.EncodeNil() + } else { + yy6 := *x.UID + yym7 := z.EncBinary() + _ = yym7 + if false { + } else if z.HasExtensions() && z.EncExt(yy6) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(yy6)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *Preconditions) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *Preconditions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "uid": + if r.TryDecodeAsNil() { + if x.UID != nil { + x.UID = nil + } + } else { + if x.UID == nil { + x.UID = new(pkg1_types.UID) + } + yym5 := z.DecBinary() + _ = yym5 + if false { + } else if z.HasExtensions() && z.DecExt(x.UID) { + } else { + *((*string)(x.UID)) = r.DecodeString() + } + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *Preconditions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj6 int + var yyb6 bool + var yyhl6 bool = l >= 0 + yyj6++ + if yyhl6 { + yyb6 = yyj6 > l + } else { + yyb6 = r.CheckBreak() + } + if yyb6 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.UID != nil { + x.UID = nil + } + } else { + if x.UID == nil { + x.UID = new(pkg1_types.UID) + } + yym8 := z.DecBinary() + _ = yym8 + if false { + } else if z.HasExtensions() && z.DecExt(x.UID) { + } else { + *((*string)(x.UID)) = r.DecodeString() + } + } + for { + yyj6++ + if yyhl6 { + yyb6 = yyj6 > l + } else { + yyb6 = r.CheckBreak() + } + if yyb6 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj6-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -36701,16 +36959,18 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [3]bool + var yyq2 [4]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[1] = x.Kind != "" - yyq2[2] = x.APIVersion != "" + yyq2[0] = x.GracePeriodSeconds != nil + yyq2[1] = x.Preconditions != nil + yyq2[2] = x.Kind != "" + yyq2[3] = x.APIVersion != "" var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(3) + r.EncodeArrayStart(4) } else { - yynn2 = 1 + yynn2 = 0 for _, b := range yyq2 { if b { yynn2++ @@ -36721,55 +36981,59 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if x.GracePeriodSeconds == nil { - r.EncodeNil() - } else { - yy4 := *x.GracePeriodSeconds - yym5 := z.EncBinary() - _ = yym5 - if false { + if yyq2[0] { + if x.GracePeriodSeconds == nil { + r.EncodeNil() } else { - r.EncodeInt(int64(yy4)) + yy4 := *x.GracePeriodSeconds + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeInt(int64(yy4)) + } } + } else { + r.EncodeNil() } } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("gracePeriodSeconds")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.GracePeriodSeconds == nil { - r.EncodeNil() - } else { - yy6 := *x.GracePeriodSeconds - yym7 := z.EncBinary() - _ = yym7 - if false { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("gracePeriodSeconds")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.GracePeriodSeconds == nil { + r.EncodeNil() } else { - r.EncodeInt(int64(yy6)) + yy6 := *x.GracePeriodSeconds + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeInt(int64(yy6)) + } } } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[1] { - yym9 := z.EncBinary() - _ = yym9 - if false { + if x.Preconditions == nil { + r.EncodeNil() } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + x.Preconditions.CodecEncodeSelf(e) } } else { - r.EncodeString(codecSelferC_UTF81234, "") + r.EncodeNil() } } else { if yyq2[1] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("kind")) + r.EncodeString(codecSelferC_UTF81234, string("preconditions")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym10 := z.EncBinary() - _ = yym10 - if false { + if x.Preconditions == nil { + r.EncodeNil() } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + x.Preconditions.CodecEncodeSelf(e) } } } @@ -36780,7 +37044,7 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym12 if false { } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) } } else { r.EncodeString(codecSelferC_UTF81234, "") @@ -36788,11 +37052,36 @@ func (x *DeleteOptions) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[2] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + r.EncodeString(codecSelferC_UTF81234, string("kind")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym13 := z.EncBinary() _ = yym13 if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym15 := z.EncBinary() + _ = yym15 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym16 := z.EncBinary() + _ = yym16 + if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) } @@ -36875,6 +37164,17 @@ func (x *DeleteOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { *((*int64)(x.GracePeriodSeconds)) = int64(r.DecodeInt(64)) } } + case "preconditions": + if r.TryDecodeAsNil() { + if x.Preconditions != nil { + x.Preconditions = nil + } + } else { + if x.Preconditions == nil { + x.Preconditions = new(Preconditions) + } + x.Preconditions.CodecDecodeSelf(d) + } case "kind": if r.TryDecodeAsNil() { x.Kind = "" @@ -36898,16 +37198,16 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj8 int - var yyb8 bool - var yyhl8 bool = l >= 0 - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + var yyj9 int + var yyb9 bool + var yyhl9 bool = l >= 0 + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -36920,20 +37220,41 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if x.GracePeriodSeconds == nil { x.GracePeriodSeconds = new(int64) } - yym10 := z.DecBinary() - _ = yym10 + yym11 := z.DecBinary() + _ = yym11 if false { } else { *((*int64)(x.GracePeriodSeconds)) = int64(r.DecodeInt(64)) } } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.Preconditions != nil { + x.Preconditions = nil + } + } else { + if x.Preconditions == nil { + x.Preconditions = new(Preconditions) + } + x.Preconditions.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -36943,13 +37264,13 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } else { x.Kind = string(r.DecodeString()) } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -36960,17 +37281,17 @@ func (x *DeleteOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { x.APIVersion = string(r.DecodeString()) } for { - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj8-1, "") + z.DecStructFieldNotFound(yyj9-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -39740,6 +40061,536 @@ func (x *PodProxyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } +func (x *NodeProxyOptions) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [3]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.Path != "" + yyq2[1] = x.Kind != "" + yyq2[2] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(3) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Path)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("path")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Path)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym11 := z.EncBinary() + _ = yym11 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *NodeProxyOptions) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *NodeProxyOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "path": + if r.TryDecodeAsNil() { + x.Path = "" + } else { + x.Path = string(r.DecodeString()) + } + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *NodeProxyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Path = "" + } else { + x.Path = string(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj7-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *ServiceProxyOptions) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [3]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.Path != "" + yyq2[1] = x.Kind != "" + yyq2[2] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(3) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Path)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("path")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Path)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym11 := z.EncBinary() + _ = yym11 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *ServiceProxyOptions) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *ServiceProxyOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "path": + if r.TryDecodeAsNil() { + x.Path = "" + } else { + x.Path = string(r.DecodeString()) + } + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *ServiceProxyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Path = "" + } else { + x.Path = string(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj7-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + func (x *ObjectReference) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -43469,6 +44320,32 @@ func (x *LimitRangeList) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } +func (x ResourceQuotaScope) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x)) + } +} + +func (x *ResourceQuotaScope) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + *((*string)(x)) = r.DecodeString() + } +} + func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -43483,13 +44360,14 @@ func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [1]bool + var yyq2 [2]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[0] = len(x.Hard) != 0 + yyq2[1] = len(x.Scopes) != 0 var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(1) + r.EncodeArrayStart(2) } else { yynn2 = 0 for _, b := range yyq2 { @@ -43523,6 +44401,39 @@ func (x *ResourceQuotaSpec) CodecEncodeSelf(e *codec1978.Encoder) { } } } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + if x.Scopes == nil { + r.EncodeNil() + } else { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + h.encSliceResourceQuotaScope(([]ResourceQuotaScope)(x.Scopes), e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("scopes")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Scopes == nil { + r.EncodeNil() + } else { + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + h.encSliceResourceQuotaScope(([]ResourceQuotaScope)(x.Scopes), e) + } + } + } + } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayEnd1234) } else { @@ -43591,6 +44502,18 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) yyv4 := &x.Hard yyv4.CodecDecodeSelf(d) } + case "scopes": + if r.TryDecodeAsNil() { + x.Scopes = nil + } else { + yyv5 := &x.Scopes + yym6 := z.DecBinary() + _ = yym6 + if false { + } else { + h.decSliceResourceQuotaScope((*[]ResourceQuotaScope)(yyv5), d) + } + } default: z.DecStructFieldNotFound(-1, yys3) } // end switch yys3 @@ -43602,16 +44525,16 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj5 int - var yyb5 bool - var yyhl5 bool = l >= 0 - yyj5++ - if yyhl5 { - yyb5 = yyj5 > l + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb5 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb5 { + if yyb7 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -43619,21 +44542,43 @@ func (x *ResourceQuotaSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder if r.TryDecodeAsNil() { x.Hard = nil } else { - yyv6 := &x.Hard - yyv6.CodecDecodeSelf(d) + yyv8 := &x.Hard + yyv8.CodecDecodeSelf(d) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Scopes = nil + } else { + yyv9 := &x.Scopes + yym10 := z.DecBinary() + _ = yym10 + if false { + } else { + h.decSliceResourceQuotaScope((*[]ResourceQuotaScope)(yyv9), d) + } } for { - yyj5++ - if yyhl5 { - yyb5 = yyj5 > l + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb5 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb5 { + if yyb7 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj5-1, "") + z.DecStructFieldNotFound(yyj7-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -53296,6 +54241,116 @@ func (x codecSelfer1234) decSliceLimitRange(v *[]LimitRange, d *codec1978.Decode } } +func (x codecSelfer1234) encSliceResourceQuotaScope(v []ResourceQuotaScope, e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + r.EncodeArrayStart(len(v)) + for _, yyv1 := range v { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yyv1.CodecEncodeSelf(e) + } + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x codecSelfer1234) decSliceResourceQuotaScope(v *[]ResourceQuotaScope, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + + yyv1 := *v + yyh1, yyl1 := z.DecSliceHelperStart() + var yyc1 bool + _ = yyc1 + if yyl1 == 0 { + if yyv1 == nil { + yyv1 = []ResourceQuotaScope{} + yyc1 = true + } else if len(yyv1) != 0 { + yyv1 = yyv1[:0] + yyc1 = true + } + } else if yyl1 > 0 { + var yyrr1, yyrl1 int + var yyrt1 bool + _, _ = yyrl1, yyrt1 + yyrr1 = yyl1 // len(yyv1) + if yyl1 > cap(yyv1) { + + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 16) + if yyrt1 { + if yyrl1 <= cap(yyv1) { + yyv1 = yyv1[:yyrl1] + } else { + yyv1 = make([]ResourceQuotaScope, yyrl1) + } + } else { + yyv1 = make([]ResourceQuotaScope, yyrl1) + } + yyc1 = true + yyrr1 = len(yyv1) + } else if yyl1 != len(yyv1) { + yyv1 = yyv1[:yyl1] + yyc1 = true + } + yyj1 := 0 + for ; yyj1 < yyrr1; yyj1++ { + yyh1.ElemContainerState(yyj1) + if r.TryDecodeAsNil() { + yyv1[yyj1] = "" + } else { + yyv1[yyj1] = ResourceQuotaScope(r.DecodeString()) + } + + } + if yyrt1 { + for ; yyj1 < yyl1; yyj1++ { + yyv1 = append(yyv1, "") + yyh1.ElemContainerState(yyj1) + if r.TryDecodeAsNil() { + yyv1[yyj1] = "" + } else { + yyv1[yyj1] = ResourceQuotaScope(r.DecodeString()) + } + + } + } + + } else { + yyj1 := 0 + for ; !r.CheckBreak(); yyj1++ { + + if yyj1 >= len(yyv1) { + yyv1 = append(yyv1, "") // var yyz1 ResourceQuotaScope + yyc1 = true + } + yyh1.ElemContainerState(yyj1) + if yyj1 < len(yyv1) { + if r.TryDecodeAsNil() { + yyv1[yyj1] = "" + } else { + yyv1[yyj1] = ResourceQuotaScope(r.DecodeString()) + } + + } else { + z.DecSwallow() + } + + } + if yyj1 < len(yyv1) { + yyv1 = yyv1[:yyj1] + yyc1 = true + } else if yyj1 == 0 && yyv1 == nil { + yyv1 = []ResourceQuotaScope{} + yyc1 = true + } + } + yyh1.End() + if yyc1 { + *v = yyv1 + } +} + func (x codecSelfer1234) encSliceResourceQuota(v []ResourceQuota, e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -53335,7 +54390,7 @@ func (x codecSelfer1234) decSliceResourceQuota(v *[]ResourceQuota, d *codec1978. yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 216) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 240) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] diff --git a/vendor/k8s.io/kubernetes/pkg/api/v1/types.go b/vendor/k8s.io/kubernetes/pkg/api/v1/types.go index 63b201561..517d5e641 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/v1/types.go +++ b/vendor/k8s.io/kubernetes/pkg/api/v1/types.go @@ -128,9 +128,7 @@ type ObjectMeta struct { ResourceVersion string `json:"resourceVersion,omitempty"` // A sequence number representing a specific generation of the desired state. - // Currently only implemented by replication controllers. - // Populated by the system. - // Read-only. + // Populated by the system. Read-only. Generation int64 `json:"generation,omitempty"` // CreationTimestamp is a timestamp representing the server time when this object was @@ -382,7 +380,7 @@ const ( // PersistentVolumeReclaimDelete means the volume will be deleted from Kubernetes on release from its claim. // The volume plugin must support Deletion. PersistentVolumeReclaimDelete PersistentVolumeReclaimPolicy = "Delete" - // PersistentVolumeReclaimRetain means the volume will left in its current phase (Released) for manual reclamation by the administrator. + // PersistentVolumeReclaimRetain means the volume will be left in its current phase (Released) for manual reclamation by the administrator. // The default policy is Retain. PersistentVolumeReclaimRetain PersistentVolumeReclaimPolicy = "Retain" ) @@ -630,7 +628,7 @@ const ( StorageMediumMemory StorageMedium = "Memory" // use memory (tmpfs) ) -// Protocol defines network protocols supported for things like conatiner ports. +// Protocol defines network protocols supported for things like container ports. type Protocol string const ( @@ -642,10 +640,10 @@ const ( // Represents a Persistent Disk resource in Google Compute Engine. // -// A GCE PD must exist and be formatted before mounting to a container. -// The disk must also be in the same GCE project and zone as the kubelet. -// A GCE PD can only be mounted as read/write once. -// GCE PDs support ownership management and SELinux relabeling. +// A GCE PD must exist before mounting to a container. The disk must +// also be in the same GCE project and zone as the kubelet. A GCE PD +// can only be mounted as read/write once or read-only many times. GCE +// PDs support ownership management and SELinux relabeling. type GCEPersistentDiskVolumeSource struct { // Unique name of the PD resource in GCE. Used to identify the disk in GCE. // More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#gcepersistentdisk @@ -688,10 +686,10 @@ type FlexVolumeSource struct { // Represents a Persistent Disk resource in AWS. // -// An AWS EBS disk must exist and be formatted before mounting to a container. -// The disk must also be in the same AWS zone as the kubelet. -// An AWS EBS disk can only be mounted as read/write once. -// AWS EBS volumes support ownership management and SELinux relabeling. +// An AWS EBS disk must exist before mounting to a container. The disk +// must also be in the same AWS zone as the kubelet. An AWS EBS disk +// can only be mounted as read/write once. AWS EBS volumes support +// ownership management and SELinux relabeling. type AWSElasticBlockStoreVolumeSource struct { // Unique ID of the persistent disk resource in AWS (Amazon EBS volume). // More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#awselasticblockstore @@ -868,7 +866,8 @@ type VolumeMount struct { // Mounted read-only if true, read-write otherwise (false or unspecified). // Defaults to false. ReadOnly bool `json:"readOnly,omitempty"` - // Path within the container at which the volume should be mounted. + // Path within the container at which the volume should be mounted. Must + // not contain ':'. MountPath string `json:"mountPath"` } @@ -1420,7 +1419,7 @@ type NodeAffinity struct { // (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). type PreferredSchedulingTerm struct { // Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. - Weight int `json:"weight"` + Weight int32 `json:"weight"` // A node selector term, associated with the corresponding weight. Preference NodeSelectorTerm `json:"preference"` } @@ -1680,6 +1679,9 @@ type ReplicationControllerStatus struct { // More info: http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md#what-is-a-replication-controller Replicas int32 `json:"replicas"` + // The number of pods that have labels matching the labels of the pod template of the replication controller. + FullyLabeledReplicas int32 `json:"fullyLabeledReplicas,omitempty"` + // ObservedGeneration reflects the generation of the most recently observed replication controller. ObservedGeneration int64 `json:"observedGeneration,omitempty"` } @@ -1814,6 +1816,7 @@ type ServiceSpec struct { // API for compatibility until at least 8/20/2016. It will be removed from // any new API revisions. If both deprecatedPublicIPs *and* externalIPs are // set, deprecatedPublicIPs is used. + // +genconversion=false DeprecatedPublicIPs []string `json:"deprecatedPublicIPs,omitempty"` // Supports "ClientIP" and "None". Used to maintain session affinity. @@ -1831,7 +1834,7 @@ type ServiceSpec struct { LoadBalancerIP string `json:"loadBalancerIP,omitempty"` } -// ServicePort conatins information on service's port. +// ServicePort contains information on service's port. type ServicePort struct { // The name of this port within the service. This must be a DNS_LABEL. // All ports within a ServiceSpec must have unique names. This maps to @@ -1850,8 +1853,9 @@ type ServicePort struct { // Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. // If this is a string, it will be looked up as a named port in the // target Pod's container ports. If this is not specified, the value - // of Port is used (an identity map). - // Defaults to the service port. + // of the 'port' field is used (an identity map). + // This field is ignored for services with clusterIP=None, and should be + // omitted or set equal to the 'port' field. // More info: http://releases.k8s.io/HEAD/docs/user-guide/services.md#defining-a-service TargetPort intstr.IntOrString `json:"targetPort,omitempty"` @@ -2044,8 +2048,14 @@ type NodeSpec struct { // DaemonEndpoint contains information about a single Daemon endpoint. type DaemonEndpoint struct { + /* + The port tag was not properly in quotes in earlier releases, so it must be + uppercased for backwards compat (since it was falling back to var name of + 'Port'). + */ + // Port number of the given endpoint. - Port int32 `json:port` + Port int32 `json:"Port"` } // NodeDaemonEndpoints lists ports opened by daemons running on the Node. @@ -2098,16 +2108,16 @@ type NodeStatus struct { // More info: http://releases.k8s.io/HEAD/docs/admin/node.md#node-info NodeInfo NodeSystemInfo `json:"nodeInfo,omitempty"` // List of container images on this node - Images []ContainerImage `json:"images",omitempty` + Images []ContainerImage `json:"images,omitempty"` } // Describe a container image type ContainerImage struct { // Names by which this image is known. // e.g. ["gcr.io/google_containers/hyperkube:v1.0.7", "dockerhub.io/google_containers/hyperkube:v1.0.7"] - RepoTags []string `json:"repoTags"` + Names []string `json:"names"` // The size of the image in bytes. - Size int64 `json:"size,omitempty"` + SizeBytes int64 `json:"sizeBytes,omitempty"` } type NodePhase string @@ -2289,6 +2299,12 @@ type Binding struct { Target ObjectReference `json:"target"` } +// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. +type Preconditions struct { + // Specifies the target UID. + UID *types.UID `json:"uid,omitempty"` +} + // DeleteOptions may be provided when deleting an API object type DeleteOptions struct { unversioned.TypeMeta `json:",inline"` @@ -2297,7 +2313,11 @@ type DeleteOptions struct { // The value zero indicates delete immediately. If this value is nil, the default grace period for the // specified type will be used. // Defaults to a per object value if not specified. zero means delete immediately. - GracePeriodSeconds *int64 `json:"gracePeriodSeconds"` + GracePeriodSeconds *int64 `json:"gracePeriodSeconds,omitempty"` + + // Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be + // returned. + Preconditions *Preconditions `json:"preconditions,omitempty"` } // ExportOptions is the query options to the standard REST get call. @@ -2346,7 +2366,7 @@ type PodLogOptions struct { // Only one of sinceSeconds or sinceTime may be specified. SinceSeconds *int64 `json:"sinceSeconds,omitempty"` // An RFC3339 timestamp from which to show logs. If this value - // preceeds the time a pod was started, only logs since the pod start will be returned. + // precedes the time a pod was started, only logs since the pod start will be returned. // If this value is in the future, no logs will be returned. // Only one of sinceSeconds or sinceTime may be specified. SinceTime *unversioned.Time `json:"sinceTime,omitempty"` @@ -2431,6 +2451,26 @@ type PodProxyOptions struct { Path string `json:"path,omitempty"` } +// NodeProxyOptions is the query options to a Node's proxy call. +type NodeProxyOptions struct { + unversioned.TypeMeta `json:",inline"` + + // Path is the URL path to use for the current proxy request to node. + Path string `json:"path,omitempty"` +} + +// ServiceProxyOptions is the query options to a Service's proxy call. +type ServiceProxyOptions struct { + unversioned.TypeMeta `json:",inline"` + + // Path is the part of URLs that include service endpoints, suffixes, + // and parameters to use for the current proxy request to service. + // For example, the whole request URL is + // http://localhost/api/v1/namespaces/kube-system/services/elasticsearch-logging/_search?q=user:kimchy. + // Path is _search?q=user:kimchy. + Path string `json:"path,omitempty"` +} + // ObjectReference contains enough information to let you inspect or modify the referred object. type ObjectReference struct { // Kind of the referent. @@ -2624,8 +2664,34 @@ const ( ResourceQuotas ResourceName = "resourcequotas" // ResourceSecrets, number ResourceSecrets ResourceName = "secrets" + // ResourceConfigMaps, number + ResourceConfigMaps ResourceName = "configmaps" // ResourcePersistentVolumeClaims, number ResourcePersistentVolumeClaims ResourceName = "persistentvolumeclaims" + // ResourceServicesNodePorts, number + ResourceServicesNodePorts ResourceName = "services.nodeports" + // CPU request, in cores. (500m = .5 cores) + ResourceCPURequest ResourceName = "cpu.request" + // CPU limit, in cores. (500m = .5 cores) + ResourceCPULimit ResourceName = "cpu.limit" + // Memory request, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceMemoryRequest ResourceName = "memory.request" + // Memory limit, in bytes. (500Gi = 500GiB = 500 * 1024 * 1024 * 1024) + ResourceMemoryLimit ResourceName = "memory.limit" +) + +// A ResourceQuotaScope defines a filter that must match each object tracked by a quota +type ResourceQuotaScope string + +const ( + // Match all pod objects where spec.activeDeadlineSeconds + ResourceQuotaScopeTerminating ResourceQuotaScope = "Terminating" + // Match all pod objects where !spec.activeDeadlineSeconds + ResourceQuotaScopeNotTerminating ResourceQuotaScope = "NotTerminating" + // Match all pod objects that have best effort quality of service + ResourceQuotaScopeBestEffort ResourceQuotaScope = "BestEffort" + // Match all pod objects that do not have best effort quality of service + ResourceQuotaScopeNotBestEffort ResourceQuotaScope = "NotBestEffort" ) // ResourceQuotaSpec defines the desired hard limits to enforce for Quota. @@ -2633,6 +2699,9 @@ type ResourceQuotaSpec struct { // Hard is the set of desired hard limits for each named resource. // More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota Hard ResourceList `json:"hard,omitempty"` + // A collection of filters that must match each object tracked by a quota. + // If not specified, the quota matches all objects. + Scopes []ResourceQuotaScope `json:"scopes,omitempty"` } // ResourceQuotaStatus defines the enforced hard limits and observed use. diff --git a/vendor/k8s.io/kubernetes/pkg/api/v1/types_swagger_doc_generated.go b/vendor/k8s.io/kubernetes/pkg/api/v1/types_swagger_doc_generated.go index 2ca06ac0f..b36490982 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/v1/types_swagger_doc_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/api/v1/types_swagger_doc_generated.go @@ -16,7 +16,7 @@ limitations under the License. package v1 -// This file contains a collection of methods that can be used from go-resful to +// This file contains a collection of methods that can be used from go-restful to // generate Swagger API documentation for its models. Please read this PR for more // information on the implementation: https://github.com/emicklei/go-restful/pull/215 // @@ -28,7 +28,7 @@ package v1 // AUTO-GENERATED FUNCTIONS START HERE var map_AWSElasticBlockStoreVolumeSource = map[string]string{ - "": "Represents a Persistent Disk resource in AWS.\n\nAn AWS EBS disk must exist and be formatted before mounting to a container. The disk must also be in the same AWS zone as the kubelet. An AWS EBS disk can only be mounted as read/write once. AWS EBS volumes support ownership management and SELinux relabeling.", + "": "Represents a Persistent Disk resource in AWS.\n\nAn AWS EBS disk must exist before mounting to a container. The disk must also be in the same AWS zone as the kubelet. An AWS EBS disk can only be mounted as read/write once. AWS EBS volumes support ownership management and SELinux relabeling.", "volumeID": "Unique ID of the persistent disk resource in AWS (Amazon EBS volume). More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#awselasticblockstore", "fsType": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#awselasticblockstore", "partition": "The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as \"1\". Similarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty).", @@ -201,9 +201,9 @@ func (Container) SwaggerDoc() map[string]string { } var map_ContainerImage = map[string]string{ - "": "Describe a container image", - "repoTags": "Names by which this image is known. e.g. [\"gcr.io/google_containers/hyperkube:v1.0.7\", \"dockerhub.io/google_containers/hyperkube:v1.0.7\"]", - "size": "The size of the image in bytes.", + "": "Describe a container image", + "names": "Names by which this image is known. e.g. [\"gcr.io/google_containers/hyperkube:v1.0.7\", \"dockerhub.io/google_containers/hyperkube:v1.0.7\"]", + "sizeBytes": "The size of the image in bytes.", } func (ContainerImage) SwaggerDoc() map[string]string { @@ -296,6 +296,7 @@ func (DaemonEndpoint) SwaggerDoc() map[string]string { var map_DeleteOptions = map[string]string{ "": "DeleteOptions may be provided when deleting an API object", "gracePeriodSeconds": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + "preconditions": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.", } func (DeleteOptions) SwaggerDoc() map[string]string { @@ -495,7 +496,7 @@ func (FlockerVolumeSource) SwaggerDoc() map[string]string { } var map_GCEPersistentDiskVolumeSource = map[string]string{ - "": "Represents a Persistent Disk resource in Google Compute Engine.\n\nA GCE PD must exist and be formatted before mounting to a container. The disk must also be in the same GCE project and zone as the kubelet. A GCE PD can only be mounted as read/write once. GCE PDs support ownership management and SELinux relabeling.", + "": "Represents a Persistent Disk resource in Google Compute Engine.\n\nA GCE PD must exist before mounting to a container. The disk must also be in the same GCE project and zone as the kubelet. A GCE PD can only be mounted as read/write once or read-only many times. GCE PDs support ownership management and SELinux relabeling.", "pdName": "Unique name of the PD resource in GCE. Used to identify the disk in GCE. More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#gcepersistentdisk", "fsType": "Filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#gcepersistentdisk", "partition": "The partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as \"1\". Similarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty). More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#gcepersistentdisk", @@ -813,6 +814,15 @@ func (NodeList) SwaggerDoc() map[string]string { return map_NodeList } +var map_NodeProxyOptions = map[string]string{ + "": "NodeProxyOptions is the query options to a Node's proxy call.", + "path": "Path is the URL path to use for the current proxy request to node.", +} + +func (NodeProxyOptions) SwaggerDoc() map[string]string { + return map_NodeProxyOptions +} + var map_NodeSelector = map[string]string{ "": "A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.", "nodeSelectorTerms": "Required. A list of node selector terms. The terms are ORed.", @@ -904,7 +914,7 @@ var map_ObjectMeta = map[string]string{ "selfLink": "SelfLink is a URL representing this object. Populated by the system. Read-only.", "uid": "UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations.\n\nPopulated by the system. Read-only. More info: http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#uids", "resourceVersion": "An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources.\n\nPopulated by the system. Read-only. Value must be treated as opaque by clients and . More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#concurrency-control-and-consistency", - "generation": "A sequence number representing a specific generation of the desired state. Currently only implemented by replication controllers. Populated by the system. Read-only.", + "generation": "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", "creationTimestamp": "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata", "deletionTimestamp": "DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource will be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field. Once set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. Once the resource is deleted in the API, the Kubelet will send a hard termination signal to the container. If not set, graceful deletion of the object has not been requested.\n\nPopulated by the system when a graceful deletion is requested. Read-only. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata", "deletionGracePeriodSeconds": "Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only.", @@ -1117,7 +1127,7 @@ var map_PodLogOptions = map[string]string{ "follow": "Follow the log stream of the pod. Defaults to false.", "previous": "Return previous terminated container logs. Defaults to false.", "sinceSeconds": "A relative time in seconds before the current time from which to show logs. If this value precedes the time a pod was started, only logs since the pod start will be returned. If this value is in the future, no logs will be returned. Only one of sinceSeconds or sinceTime may be specified.", - "sinceTime": "An RFC3339 timestamp from which to show logs. If this value preceeds the time a pod was started, only logs since the pod start will be returned. If this value is in the future, no logs will be returned. Only one of sinceSeconds or sinceTime may be specified.", + "sinceTime": "An RFC3339 timestamp from which to show logs. If this value precedes the time a pod was started, only logs since the pod start will be returned. If this value is in the future, no logs will be returned. Only one of sinceSeconds or sinceTime may be specified.", "timestamps": "If true, add an RFC3339 or RFC3339Nano timestamp at the beginning of every line of log output. Defaults to false.", "tailLines": "If set, the number of lines from the end of the logs to show. If not specified, logs are shown from the creation of the container or sinceSeconds or sinceTime", "limitBytes": "If set, the number of bytes to read from the server before terminating the log output. This may not display a complete final line of logging, and may return slightly more or slightly less than the specified limit.", @@ -1228,6 +1238,15 @@ func (PodTemplateSpec) SwaggerDoc() map[string]string { return map_PodTemplateSpec } +var map_Preconditions = map[string]string{ + "": "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + "uid": "Specifies the target UID.", +} + +func (Preconditions) SwaggerDoc() map[string]string { + return map_Preconditions +} + var map_PreferredSchedulingTerm = map[string]string{ "": "An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).", "weight": "Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100.", @@ -1311,9 +1330,10 @@ func (ReplicationControllerSpec) SwaggerDoc() map[string]string { } var map_ReplicationControllerStatus = map[string]string{ - "": "ReplicationControllerStatus represents the current status of a replication controller.", - "replicas": "Replicas is the most recently oberved number of replicas. More info: http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md#what-is-a-replication-controller", - "observedGeneration": "ObservedGeneration reflects the generation of the most recently observed replication controller.", + "": "ReplicationControllerStatus represents the current status of a replication controller.", + "replicas": "Replicas is the most recently oberved number of replicas. More info: http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md#what-is-a-replication-controller", + "fullyLabeledReplicas": "The number of pods that have labels matching the labels of the pod template of the replication controller.", + "observedGeneration": "ObservedGeneration reflects the generation of the most recently observed replication controller.", } func (ReplicationControllerStatus) SwaggerDoc() map[string]string { @@ -1342,8 +1362,9 @@ func (ResourceQuotaList) SwaggerDoc() map[string]string { } var map_ResourceQuotaSpec = map[string]string{ - "": "ResourceQuotaSpec defines the desired hard limits to enforce for Quota.", - "hard": "Hard is the set of desired hard limits for each named resource. More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota", + "": "ResourceQuotaSpec defines the desired hard limits to enforce for Quota.", + "hard": "Hard is the set of desired hard limits for each named resource. More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota", + "scopes": "A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.", } func (ResourceQuotaSpec) SwaggerDoc() map[string]string { @@ -1487,11 +1508,11 @@ func (ServiceList) SwaggerDoc() map[string]string { } var map_ServicePort = map[string]string{ - "": "ServicePort conatins information on service's port.", + "": "ServicePort contains information on service's port.", "name": "The name of this port within the service. This must be a DNS_LABEL. All ports within a ServiceSpec must have unique names. This maps to the 'Name' field in EndpointPort objects. Optional if only one ServicePort is defined on this service.", "protocol": "The IP protocol for this port. Supports \"TCP\" and \"UDP\". Default is TCP.", "port": "The port that will be exposed by this service.", - "targetPort": "Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a string, it will be looked up as a named port in the target Pod's container ports. If this is not specified, the value of Port is used (an identity map). Defaults to the service port. More info: http://releases.k8s.io/HEAD/docs/user-guide/services.md#defining-a-service", + "targetPort": "Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a string, it will be looked up as a named port in the target Pod's container ports. If this is not specified, the value of the 'port' field is used (an identity map). This field is ignored for services with clusterIP=None, and should be omitted or set equal to the 'port' field. More info: http://releases.k8s.io/HEAD/docs/user-guide/services.md#defining-a-service", "nodePort": "The port on each node on which this service is exposed when type=NodePort or LoadBalancer. Usually assigned by the system. If specified, it will be allocated to the service if unused or else creation of the service will fail. Default is to auto-allocate a port if the ServiceType of this Service requires one. More info: http://releases.k8s.io/HEAD/docs/user-guide/services.md#type--nodeport", } @@ -1499,6 +1520,15 @@ func (ServicePort) SwaggerDoc() map[string]string { return map_ServicePort } +var map_ServiceProxyOptions = map[string]string{ + "": "ServiceProxyOptions is the query options to a Service's proxy call.", + "path": "Path is the part of URLs that include service endpoints, suffixes, and parameters to use for the current proxy request to service. For example, the whole request URL is http://localhost/api/v1/namespaces/kube-system/services/elasticsearch-logging/_search?q=user:kimchy. Path is _search?q=user:kimchy.", +} + +func (ServiceProxyOptions) SwaggerDoc() map[string]string { + return map_ServiceProxyOptions +} + var map_ServiceSpec = map[string]string{ "": "ServiceSpec describes the attributes that a user creates on a service.", "ports": "The list of ports that are exposed by this service. More info: http://releases.k8s.io/HEAD/docs/user-guide/services.md#virtual-ips-and-service-proxies", @@ -1546,7 +1576,7 @@ var map_VolumeMount = map[string]string{ "": "VolumeMount describes a mounting of a Volume within a container.", "name": "This must match the Name of a Volume.", "readOnly": "Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.", - "mountPath": "Path within the container at which the volume should be mounted.", + "mountPath": "Path within the container at which the volume should be mounted. Must not contain ':'.", } func (VolumeMount) SwaggerDoc() map[string]string { diff --git a/vendor/k8s.io/kubernetes/pkg/api/validation/schema.go b/vendor/k8s.io/kubernetes/pkg/api/validation/schema.go index 24c1e2619..713b96ac6 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/validation/schema.go +++ b/vendor/k8s.io/kubernetes/pkg/api/validation/schema.go @@ -44,6 +44,14 @@ func NewInvalidTypeError(expected reflect.Kind, observed reflect.Kind, fieldName return &InvalidTypeError{expected, observed, fieldName} } +// TypeNotFoundError is returned when specified type +// can not found in schema +type TypeNotFoundError string + +func (tnfe TypeNotFoundError) Error() string { + return fmt.Sprintf("couldn't find type: %s", string(tnfe)) +} + // Schema is an interface that knows how to validate an API object serialized to a byte array. type Schema interface { ValidateBytes(data []byte) error @@ -164,7 +172,7 @@ func (s *SwaggerSchema) ValidateObject(obj interface{}, fieldName, typeName stri models := s.api.Models model, ok := models.At(typeName) if !ok { - return append(allErrs, fmt.Errorf("couldn't find type: %s", typeName)) + return append(allErrs, TypeNotFoundError(typeName)) } properties := model.Properties if len(properties.List) == 0 { @@ -274,7 +282,10 @@ func (s *SwaggerSchema) validateField(value interface{}, fieldName, fieldType st if _, ok := value.(bool); !ok { return append(allErrs, NewInvalidTypeError(reflect.Bool, reflect.TypeOf(value).Kind(), fieldName)) } + // API servers before release 1.3 produce swagger spec with `type: "any"` as the fallback type, while newer servers produce spec with `type: "object"`. + // We have both here so that kubectl can work with both old and new api servers. case "any": + case "object": default: return append(allErrs, fmt.Errorf("unexpected type: %v", fieldType)) } diff --git a/vendor/k8s.io/kubernetes/pkg/api/validation/schema_test.go b/vendor/k8s.io/kubernetes/pkg/api/validation/schema_test.go index be6fc83e7..695435f89 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/validation/schema_test.go +++ b/vendor/k8s.io/kubernetes/pkg/api/validation/schema_test.go @@ -19,26 +19,33 @@ package validation import ( "io/ioutil" "math/rand" + "strings" "testing" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" "k8s.io/kubernetes/pkg/runtime" + + "github.com/ghodss/yaml" ) -func readPod(filename string) (string, error) { +func readPod(filename string) ([]byte, error) { data, err := ioutil.ReadFile("testdata/" + testapi.Default.GroupVersion().Version + "/" + filename) if err != nil { - return "", err + return nil, err } - return string(data), nil + return data, nil +} + +func readSwaggerFile() ([]byte, error) { + // TODO this path is broken + pathToSwaggerSpec := "../../../api/swagger-spec/" + testapi.Default.GroupVersion().Version + ".json" + return ioutil.ReadFile(pathToSwaggerSpec) } func loadSchemaForTest() (Schema, error) { - // TODO this path is broken - pathToSwaggerSpec := "../../../api/swagger-spec/" + testapi.Default.GroupVersion().Version + ".json" - data, err := ioutil.ReadFile(pathToSwaggerSpec) + data, err := readSwaggerFile() if err != nil { return nil, err } @@ -98,9 +105,9 @@ func TestInvalid(t *testing.T) { for _, test := range tests { pod, err := readPod(test) if err != nil { - t.Errorf("could not read file: %s", pod) + t.Errorf("could not read file: %s, err: %v", test, err) } - err = schema.ValidateBytes([]byte(pod)) + err = schema.ValidateBytes(pod) if err == nil { t.Errorf("unexpected non-error, err: %s for pod: %s", err, pod) } @@ -118,9 +125,9 @@ func TestValid(t *testing.T) { for _, test := range tests { pod, err := readPod(test) if err != nil { - t.Errorf("could not read file: %s", test) + t.Errorf("could not read file: %s, err: %v", test, err) } - err = schema.ValidateBytes([]byte(pod)) + err = schema.ValidateBytes(pod) if err != nil { t.Errorf("unexpected error %s, for pod %s", err, pod) } @@ -154,3 +161,39 @@ func TestVersionRegex(t *testing.T) { } } } + +// Tests that validation works fine when spec contains "type": "object" instead of "type": "any" +func TestTypeObject(t *testing.T) { + data, err := readSwaggerFile() + if err != nil { + t.Errorf("failed to read swagger file: %v", err) + } + // Replace type: "any" in the spec by type: "object" and verify that the validation still passes. + newData := strings.Replace(string(data), `"type": "any"`, `"type": "object"`, -1) + schema, err := NewSwaggerSchemaFromBytes([]byte(newData)) + if err != nil { + t.Errorf("Failed to load: %v", err) + } + tests := []string{ + "validPod.yaml", + } + for _, test := range tests { + podBytes, err := readPod(test) + if err != nil { + t.Errorf("could not read file: %s, err: %v", test, err) + } + // Verify that pod has at least one label (labels are type "any") + var pod api.Pod + err = yaml.Unmarshal(podBytes, &pod) + if err != nil { + t.Errorf("error in unmarshalling pod: %v", err) + } + if len(pod.Labels) == 0 { + t.Errorf("invalid test input: the pod should have at least one label") + } + err = schema.ValidateBytes(podBytes) + if err != nil { + t.Errorf("unexpected error %s, for pod %s", err, string(podBytes)) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/api/validation/validation.go b/vendor/k8s.io/kubernetes/pkg/api/validation/validation.go index 755e36bf9..47b48afc4 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/validation/validation.go +++ b/vendor/k8s.io/kubernetes/pkg/api/validation/validation.go @@ -27,16 +27,18 @@ import ( "regexp" "strings" + "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/endpoints" + utilpod "k8s.io/kubernetes/pkg/api/pod" "k8s.io/kubernetes/pkg/api/resource" + apiservice "k8s.io/kubernetes/pkg/api/service" "k8s.io/kubernetes/pkg/capabilities" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/util/intstr" "k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/validation" "k8s.io/kubernetes/pkg/util/validation/field" - - "github.com/golang/glog" ) // TODO: delete this global variable when we enable the validation of common @@ -44,6 +46,7 @@ import ( var RepairMalformedUpdates bool = true const isNegativeErrorMsg string = `must be greater than or equal to 0` +const isInvalidQuotaResource string = `must be a standard resource for quota` const fieldImmutableErrorMsg string = `field is immutable` const cIdentifierErrorMsg string = `must be a C identifier (matching regex ` + validation.CIdentifierFmt + `): e.g. "my_name" or "MyName"` const isNotIntegerErrorMsg string = `must be an integer` @@ -84,6 +87,20 @@ func ValidateLabels(labels map[string]string, fldPath *field.Path) field.ErrorLi return allErrs } +// ValidateHasLabel requires that api.ObjectMeta has a Label with key and expectedValue +func ValidateHasLabel(meta api.ObjectMeta, fldPath *field.Path, key, expectedValue string) field.ErrorList { + allErrs := field.ErrorList{} + actualValue, found := meta.Labels[key] + if !found { + allErrs = append(allErrs, field.Required(fldPath.Child("labels"), key+"="+expectedValue)) + return allErrs + } + if actualValue != expectedValue { + allErrs = append(allErrs, field.Invalid(fldPath.Child("labels"), meta.Labels, "expected "+key+"="+expectedValue)) + } + return allErrs +} + // ValidateAnnotations validates that a set of annotations are correctly defined. func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} @@ -97,10 +114,34 @@ func ValidateAnnotations(annotations map[string]string, fldPath *field.Path) fie if totalSize > (int64)(totalAnnotationSizeLimitB) { allErrs = append(allErrs, field.TooLong(fldPath, "", totalAnnotationSizeLimitB)) } + return allErrs +} +func ValidatePodSpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} if annotations[api.AffinityAnnotationKey] != "" { allErrs = append(allErrs, ValidateAffinityInPodAnnotations(annotations, fldPath)...) } + + if hostname, exists := annotations[utilpod.PodHostnameAnnotation]; exists && !validation.IsDNS1123Label(hostname) { + allErrs = append(allErrs, field.Invalid(fldPath, utilpod.PodHostnameAnnotation, DNS1123LabelErrorMsg)) + } + + if subdomain, exists := annotations[utilpod.PodSubdomainAnnotation]; exists && !validation.IsDNS1123Label(subdomain) { + allErrs = append(allErrs, field.Invalid(fldPath, utilpod.PodSubdomainAnnotation, DNS1123LabelErrorMsg)) + } + + return allErrs +} + +func ValidateEndpointsSpecificAnnotations(annotations map[string]string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + hostnamesMap, exists := annotations[endpoints.PodHostnamesAnnotation] + if exists && !isValidHostnamesMap(hostnamesMap) { + allErrs = append(allErrs, field.Invalid(fldPath, endpoints.PodHostnamesAnnotation, + `must be a valid json representation of map[string(IP)][HostRecord] e.g. "{"10.245.1.6":{"HostName":"my-webserver"}}"`)) + } + return allErrs } @@ -1066,6 +1107,7 @@ func validateSecretKeySelector(s *api.SecretKeySelector, fldPath *field.Path) fi func validateVolumeMounts(mounts []api.VolumeMount, volumes sets.String, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} + mountpoints := sets.NewString() for i, mnt := range mounts { idxPath := fldPath.Index(i) @@ -1076,7 +1118,13 @@ func validateVolumeMounts(mounts []api.VolumeMount, volumes sets.String, fldPath } if len(mnt.MountPath) == 0 { allErrs = append(allErrs, field.Required(idxPath.Child("mountPath"), "")) + } else if strings.Contains(mnt.MountPath, ":") { + allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must not contain ':'")) } + if mountpoints.Has(mnt.MountPath) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must be unique")) + } + mountpoints.Insert(mnt.MountPath) } return allErrs } @@ -1338,7 +1386,9 @@ func validateImagePullSecrets(imagePullSecrets []api.LocalObjectReference, fldPa // ValidatePod tests if required fields in the pod are set. func ValidatePod(pod *api.Pod) field.ErrorList { - allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, field.NewPath("metadata")) + fldPath := field.NewPath("metadata") + allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, fldPath) + allErrs = append(allErrs, ValidatePodSpecificAnnotations(pod.ObjectMeta.Annotations, fldPath.Child("annotations"))...) allErrs = append(allErrs, ValidatePodSpec(&pod.Spec, field.NewPath("spec"))...) return allErrs } @@ -1502,8 +1552,9 @@ func ValidatePodSecurityContext(securityContext *api.PodSecurityContext, spec *a // ValidatePodUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields // that cannot be changed. func ValidatePodUpdate(newPod, oldPod *api.Pod) field.ErrorList { - allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, field.NewPath("metadata")) - + fldPath := field.NewPath("metadata") + allErrs := ValidateObjectMetaUpdate(&newPod.ObjectMeta, &oldPod.ObjectMeta, fldPath) + allErrs = append(allErrs, ValidatePodSpecificAnnotations(newPod.ObjectMeta.Annotations, fldPath.Child("annotations"))...) specPath := field.NewPath("spec") if len(newPod.Spec.Containers) != len(oldPod.Spec.Containers) { //TODO: Pinpoint the specific container that causes the invalid error after we have strategic merge diff @@ -1721,6 +1772,12 @@ func ValidateService(service *api.Service) field.ErrorList { nodePorts[key] = true } + _, err := apiservice.GetLoadBalancerSourceRanges(service.Annotations) + if err != nil { + v := service.Annotations[apiservice.AnnotationLoadBalancerSourceRangesKey] + allErrs = append(allErrs, field.Invalid(field.NewPath("metadata", "annotations").Key(apiservice.AnnotationLoadBalancerSourceRangesKey), v, "must be a comma separated list of CIDRs e.g. 192.168.0.0/16,10.0.0.0/8")) + } + return allErrs } @@ -1756,11 +1813,14 @@ func validateServicePort(sp *api.ServicePort, requireName, isHeadlessService boo allErrs = append(allErrs, field.Invalid(fldPath.Child("targetPort"), sp.TargetPort, PortNameErrorMsg)) } - if isHeadlessService { - if sp.TargetPort.Type == intstr.String || (sp.TargetPort.Type == intstr.Int && sp.Port != sp.TargetPort.IntValue()) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("port"), sp.Port, "must be equal to targetPort when clusterIP = None")) - } - } + // in the v1 API, targetPorts on headless services were tolerated. + // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. + // + // if isHeadlessService { + // if sp.TargetPort.Type == intstr.String || (sp.TargetPort.Type == intstr.Int && sp.Port != sp.TargetPort.IntValue()) { + // allErrs = append(allErrs, field.Invalid(fldPath.Child("targetPort"), sp.TargetPort, "must be equal to the value of 'port' when clusterIP = None")) + // } + // } return allErrs } @@ -1803,6 +1863,7 @@ func ValidateReplicationControllerStatusUpdate(controller, oldController *api.Re allErrs := ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, field.NewPath("metadata")) statusPath := field.NewPath("status") allErrs = append(allErrs, ValidateNonnegativeField(int64(controller.Status.Replicas), statusPath.Child("replicas"))...) + allErrs = append(allErrs, ValidateNonnegativeField(int64(controller.Status.FullyLabeledReplicas), statusPath.Child("fullyLabeledReplicas"))...) allErrs = append(allErrs, ValidateNonnegativeField(int64(controller.Status.ObservedGeneration), statusPath.Child("observedGeneration"))...) return allErrs } @@ -1857,6 +1918,7 @@ func ValidatePodTemplateSpec(spec *api.PodTemplateSpec, fldPath *field.Path) fie allErrs := field.ErrorList{} allErrs = append(allErrs, ValidateLabels(spec.Labels, fldPath.Child("labels"))...) allErrs = append(allErrs, ValidateAnnotations(spec.Annotations, fldPath.Child("annotations"))...) + allErrs = append(allErrs, ValidatePodSpecificAnnotations(spec.Annotations, fldPath.Child("annotations"))...) allErrs = append(allErrs, ValidatePodSpec(&spec.Spec, fldPath.Child("spec"))...) return allErrs } @@ -1954,6 +2016,57 @@ func validateResourceName(value string, fldPath *field.Path) field.ErrorList { return field.ErrorList{} } +// Validate container resource name +// Refer to docs/design/resources.md for more details. +func validateContainerResourceName(value string, fldPath *field.Path) field.ErrorList { + allErrs := validateResourceName(value, fldPath) + if len(strings.Split(value, "/")) == 1 { + if !api.IsStandardContainerResourceName(value) { + return append(allErrs, field.Invalid(fldPath, value, "must be a standard resource for containers")) + } + } + return field.ErrorList{} +} + +// Validate resource names that can go in a resource quota +// Refer to docs/design/resources.md for more details. +func validateResourceQuotaResourceName(value string, fldPath *field.Path) field.ErrorList { + allErrs := validateResourceName(value, fldPath) + if len(strings.Split(value, "/")) == 1 { + if !api.IsStandardQuotaResourceName(value) { + return append(allErrs, field.Invalid(fldPath, value, isInvalidQuotaResource)) + } + } + return field.ErrorList{} +} + +// Validate limit range types +func validateLimitRangeTypeName(value string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if !validation.IsQualifiedName(value) { + return append(allErrs, field.Invalid(fldPath, value, qualifiedNameErrorMsg)) + } + + if len(strings.Split(value, "/")) == 1 { + if !api.IsStandardLimitRangeType(value) { + return append(allErrs, field.Invalid(fldPath, value, "must be a standard limit type or fully qualified")) + } + } + + return allErrs +} + +// Validate limit range resource name +// limit types (other than Pod/Container) could contain storage not just cpu or memory +func validateLimitRangeResourceName(limitType api.LimitType, value string, fldPath *field.Path) field.ErrorList { + switch limitType { + case api.LimitTypePod, api.LimitTypeContainer: + return validateContainerResourceName(value, fldPath) + default: + return validateResourceName(value, fldPath) + } +} + // ValidateLimitRange tests if required fields in the LimitRange are set. func ValidateLimitRange(limitRange *api.LimitRange) field.ErrorList { allErrs := ValidateObjectMeta(&limitRange.ObjectMeta, true, ValidateLimitRangeName, field.NewPath("metadata")) @@ -1964,6 +2077,8 @@ func ValidateLimitRange(limitRange *api.LimitRange) field.ErrorList { for i := range limitRange.Spec.Limits { idxPath := fldPath.Index(i) limit := &limitRange.Spec.Limits[i] + allErrs = append(allErrs, validateLimitRangeTypeName(string(limit.Type), idxPath.Child("type"))...) + _, found := limitTypeSet[limit.Type] if found { allErrs = append(allErrs, field.Duplicate(idxPath.Child("type"), limit.Type)) @@ -1978,12 +2093,12 @@ func ValidateLimitRange(limitRange *api.LimitRange) field.ErrorList { maxLimitRequestRatios := map[string]resource.Quantity{} for k, q := range limit.Max { - allErrs = append(allErrs, validateResourceName(string(k), idxPath.Child("max").Key(string(k)))...) + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("max").Key(string(k)))...) keys.Insert(string(k)) max[string(k)] = q } for k, q := range limit.Min { - allErrs = append(allErrs, validateResourceName(string(k), idxPath.Child("min").Key(string(k)))...) + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("min").Key(string(k)))...) keys.Insert(string(k)) min[string(k)] = q } @@ -1997,19 +2112,19 @@ func ValidateLimitRange(limitRange *api.LimitRange) field.ErrorList { } } else { for k, q := range limit.Default { - allErrs = append(allErrs, validateResourceName(string(k), idxPath.Child("default").Key(string(k)))...) + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("default").Key(string(k)))...) keys.Insert(string(k)) defaults[string(k)] = q } for k, q := range limit.DefaultRequest { - allErrs = append(allErrs, validateResourceName(string(k), idxPath.Child("defaultRequest").Key(string(k)))...) + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("defaultRequest").Key(string(k)))...) keys.Insert(string(k)) defaultRequests[string(k)] = q } } for k, q := range limit.MaxLimitRequestRatio { - allErrs = append(allErrs, validateResourceName(string(k), idxPath.Child("maxLimitRequestRatio").Key(string(k)))...) + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("maxLimitRequestRatio").Key(string(k)))...) keys.Insert(string(k)) maxLimitRequestRatios[string(k)] = q } @@ -2232,7 +2347,7 @@ func ValidateResourceRequirements(requirements *api.ResourceRequirements, fldPat for resourceName, quantity := range requirements.Limits { fldPath := limPath.Key(string(resourceName)) // Validate resource name. - allErrs = append(allErrs, validateResourceName(string(resourceName), fldPath)...) + allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...) if api.IsStandardResourceName(string(resourceName)) { allErrs = append(allErrs, validateBasicResource(quantity, fldPath.Key(string(resourceName)))...) } @@ -2248,7 +2363,7 @@ func ValidateResourceRequirements(requirements *api.ResourceRequirements, fldPat for resourceName, quantity := range requirements.Requests { fldPath := reqPath.Key(string(resourceName)) // Validate resource name. - allErrs = append(allErrs, validateResourceName(string(resourceName), fldPath)...) + allErrs = append(allErrs, validateContainerResourceName(string(resourceName), fldPath)...) if api.IsStandardResourceName(string(resourceName)) { allErrs = append(allErrs, validateBasicResource(quantity, fldPath.Key(string(resourceName)))...) } @@ -2256,6 +2371,41 @@ func ValidateResourceRequirements(requirements *api.ResourceRequirements, fldPat return allErrs } +// validateResourceQuotaScopes ensures that each enumerated hard resource constraint is valid for set of scopes +func validateResourceQuotaScopes(resourceQuota *api.ResourceQuota) field.ErrorList { + allErrs := field.ErrorList{} + if len(resourceQuota.Spec.Scopes) == 0 { + return allErrs + } + hardLimits := sets.NewString() + for k := range resourceQuota.Spec.Hard { + hardLimits.Insert(string(k)) + } + fldPath := field.NewPath("spec", "scopes") + scopeSet := sets.NewString() + for _, scope := range resourceQuota.Spec.Scopes { + if !api.IsStandardResourceQuotaScope(string(scope)) { + allErrs = append(allErrs, field.Invalid(fldPath, resourceQuota.Spec.Scopes, "unsupported scope")) + } + for _, k := range hardLimits.List() { + if api.IsStandardQuotaResourceName(k) && !api.IsResourceQuotaScopeValidForResource(scope, k) { + allErrs = append(allErrs, field.Invalid(fldPath, resourceQuota.Spec.Scopes, "unsupported scope applied to resource")) + } + } + scopeSet.Insert(string(scope)) + } + invalidScopePairs := []sets.String{ + sets.NewString(string(api.ResourceQuotaScopeBestEffort), string(api.ResourceQuotaScopeNotBestEffort)), + sets.NewString(string(api.ResourceQuotaScopeTerminating), string(api.ResourceQuotaScopeNotTerminating)), + } + for _, invalidScopePair := range invalidScopePairs { + if scopeSet.HasAll(invalidScopePair.List()...) { + allErrs = append(allErrs, field.Invalid(fldPath, resourceQuota.Spec.Scopes, "conflicting scopes")) + } + } + return allErrs +} + // ValidateResourceQuota tests if required fields in the ResourceQuota are set. func ValidateResourceQuota(resourceQuota *api.ResourceQuota) field.ErrorList { allErrs := ValidateObjectMeta(&resourceQuota.ObjectMeta, true, ValidateResourceQuotaName, field.NewPath("metadata")) @@ -2263,21 +2413,24 @@ func ValidateResourceQuota(resourceQuota *api.ResourceQuota) field.ErrorList { fldPath := field.NewPath("spec", "hard") for k, v := range resourceQuota.Spec.Hard { resPath := fldPath.Key(string(k)) - allErrs = append(allErrs, validateResourceName(string(k), resPath)...) + allErrs = append(allErrs, validateResourceQuotaResourceName(string(k), resPath)...) allErrs = append(allErrs, validateResourceQuantityValue(string(k), v, resPath)...) } + allErrs = append(allErrs, validateResourceQuotaScopes(resourceQuota)...) + fldPath = field.NewPath("status", "hard") for k, v := range resourceQuota.Status.Hard { resPath := fldPath.Key(string(k)) - allErrs = append(allErrs, validateResourceName(string(k), resPath)...) + allErrs = append(allErrs, validateResourceQuotaResourceName(string(k), resPath)...) allErrs = append(allErrs, validateResourceQuantityValue(string(k), v, resPath)...) } fldPath = field.NewPath("status", "used") for k, v := range resourceQuota.Status.Used { resPath := fldPath.Key(string(k)) - allErrs = append(allErrs, validateResourceName(string(k), resPath)...) + allErrs = append(allErrs, validateResourceQuotaResourceName(string(k), resPath)...) allErrs = append(allErrs, validateResourceQuantityValue(string(k), v, resPath)...) } + return allErrs } @@ -2300,9 +2453,25 @@ func ValidateResourceQuotaUpdate(newResourceQuota, oldResourceQuota *api.Resourc fldPath := field.NewPath("spec", "hard") for k, v := range newResourceQuota.Spec.Hard { resPath := fldPath.Key(string(k)) - allErrs = append(allErrs, validateResourceName(string(k), resPath)...) + allErrs = append(allErrs, validateResourceQuotaResourceName(string(k), resPath)...) allErrs = append(allErrs, validateResourceQuantityValue(string(k), v, resPath)...) } + + // ensure scopes cannot change, and that resources are still valid for scope + fldPath = field.NewPath("spec", "scopes") + oldScopes := sets.NewString() + newScopes := sets.NewString() + for _, scope := range newResourceQuota.Spec.Scopes { + newScopes.Insert(string(scope)) + } + for _, scope := range oldResourceQuota.Spec.Scopes { + oldScopes.Insert(string(scope)) + } + if !oldScopes.Equal(newScopes) { + allErrs = append(allErrs, field.Invalid(fldPath, newResourceQuota.Spec.Scopes, "field is immutable")) + } + allErrs = append(allErrs, validateResourceQuotaScopes(newResourceQuota)...) + newResourceQuota.Status = oldResourceQuota.Status return allErrs } @@ -2317,13 +2486,13 @@ func ValidateResourceQuotaStatusUpdate(newResourceQuota, oldResourceQuota *api.R fldPath := field.NewPath("status", "hard") for k, v := range newResourceQuota.Status.Hard { resPath := fldPath.Key(string(k)) - allErrs = append(allErrs, validateResourceName(string(k), resPath)...) + allErrs = append(allErrs, validateResourceQuotaResourceName(string(k), resPath)...) allErrs = append(allErrs, validateResourceQuantityValue(string(k), v, resPath)...) } fldPath = field.NewPath("status", "used") for k, v := range newResourceQuota.Status.Used { resPath := fldPath.Key(string(k)) - allErrs = append(allErrs, validateResourceName(string(k), resPath)...) + allErrs = append(allErrs, validateResourceQuotaResourceName(string(k), resPath)...) allErrs = append(allErrs, validateResourceQuantityValue(string(k), v, resPath)...) } newResourceQuota.Spec = oldResourceQuota.Spec @@ -2398,6 +2567,7 @@ func ValidateNamespaceFinalizeUpdate(newNamespace, oldNamespace *api.Namespace) // ValidateEndpoints tests if required fields are set. func ValidateEndpoints(endpoints *api.Endpoints) field.ErrorList { allErrs := ValidateObjectMeta(&endpoints.ObjectMeta, true, ValidateEndpointsName, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateEndpointsSpecificAnnotations(endpoints.Annotations, field.NewPath("annotations"))...) allErrs = append(allErrs, validateEndpointSubsets(endpoints.Subsets, field.NewPath("subsets"))...) return allErrs } @@ -2481,6 +2651,7 @@ func validateEndpointPort(port *api.EndpointPort, requireName bool, fldPath *fie func ValidateEndpointsUpdate(newEndpoints, oldEndpoints *api.Endpoints) field.ErrorList { allErrs := ValidateObjectMetaUpdate(&newEndpoints.ObjectMeta, &oldEndpoints.ObjectMeta, field.NewPath("metadata")) allErrs = append(allErrs, validateEndpointSubsets(newEndpoints.Subsets, field.NewPath("subsets"))...) + allErrs = append(allErrs, ValidateEndpointsSpecificAnnotations(newEndpoints.Annotations, field.NewPath("annotations"))...) return allErrs } @@ -2546,3 +2717,24 @@ func ValidateLoadBalancerStatus(status *api.LoadBalancerStatus, fldPath *field.P } return allErrs } + +func isValidHostnamesMap(serializedPodHostNames string) bool { + if len(serializedPodHostNames) == 0 { + return false + } + podHostNames := map[string]endpoints.HostRecord{} + err := json.Unmarshal([]byte(serializedPodHostNames), &podHostNames) + if err != nil { + return false + } + + for ip, hostRecord := range podHostNames { + if !validation.IsDNS1123Label(hostRecord.HostName) { + return false + } + if net.ParseIP(ip) == nil { + return false + } + } + return true +} diff --git a/vendor/k8s.io/kubernetes/pkg/api/validation/validation_test.go b/vendor/k8s.io/kubernetes/pkg/api/validation/validation_test.go index 5f93808c0..ac7d5b246 100644 --- a/vendor/k8s.io/kubernetes/pkg/api/validation/validation_test.go +++ b/vendor/k8s.io/kubernetes/pkg/api/validation/validation_test.go @@ -25,6 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/service" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/capabilities" @@ -562,7 +563,7 @@ func TestValidateVolumes(t *testing.T) { {Name: "glusterfs", VolumeSource: api.VolumeSource{Glusterfs: &api.GlusterfsVolumeSource{EndpointsName: "host1", Path: "path", ReadOnly: false}}}, {Name: "flocker", VolumeSource: api.VolumeSource{Flocker: &api.FlockerVolumeSource{DatasetName: "datasetName"}}}, {Name: "rbd", VolumeSource: api.VolumeSource{RBD: &api.RBDVolumeSource{CephMonitors: []string{"foo"}, RBDImage: "bar", FSType: "ext4"}}}, - {Name: "cinder", VolumeSource: api.VolumeSource{Cinder: &api.CinderVolumeSource{"29ea5088-4f60-4757-962e-dba678767887", "ext4", false}}}, + {Name: "cinder", VolumeSource: api.VolumeSource{Cinder: &api.CinderVolumeSource{VolumeID: "29ea5088-4f60-4757-962e-dba678767887", FSType: "ext4", ReadOnly: false}}}, {Name: "cephfs", VolumeSource: api.VolumeSource{CephFS: &api.CephFSVolumeSource{Monitors: []string{"foo"}}}}, {Name: "downwardapi", VolumeSource: api.VolumeSource{DownwardAPI: &api.DownwardAPIVolumeSource{Items: []api.DownwardAPIVolumeFile{ {Path: "labels", FieldRef: api.ObjectFieldSelector{ @@ -590,9 +591,9 @@ func TestValidateVolumes(t *testing.T) { APIVersion: "v1", FieldPath: "metadata.labels"}}, }}}}, - {Name: "fc", VolumeSource: api.VolumeSource{FC: &api.FCVolumeSource{[]string{"some_wwn"}, &lun, "ext4", false}}}, + {Name: "fc", VolumeSource: api.VolumeSource{FC: &api.FCVolumeSource{TargetWWNs: []string{"some_wwn"}, Lun: &lun, FSType: "ext4", ReadOnly: false}}}, {Name: "flexvolume", VolumeSource: api.VolumeSource{FlexVolume: &api.FlexVolumeSource{Driver: "kubernetes.io/blue", FSType: "ext4"}}}, - {Name: "azure", VolumeSource: api.VolumeSource{AzureFile: &api.AzureFileVolumeSource{"key", "share", false}}}, + {Name: "azure", VolumeSource: api.VolumeSource{AzureFile: &api.AzureFileVolumeSource{SecretName: "key", ShareName: "share", ReadOnly: false}}}, } names, errs := validateVolumes(successCase, field.NewPath("field")) if len(errs) != 0 { @@ -638,11 +639,11 @@ func TestValidateVolumes(t *testing.T) { APIVersion: "v1", FieldPath: "metadata.labels"}}}, }} - zeroWWN := api.VolumeSource{FC: &api.FCVolumeSource{[]string{}, &lun, "ext4", false}} - emptyLun := api.VolumeSource{FC: &api.FCVolumeSource{[]string{"wwn"}, nil, "ext4", false}} + zeroWWN := api.VolumeSource{FC: &api.FCVolumeSource{TargetWWNs: []string{}, Lun: &lun, FSType: "ext4", ReadOnly: false}} + emptyLun := api.VolumeSource{FC: &api.FCVolumeSource{TargetWWNs: []string{"wwn"}, Lun: nil, FSType: "ext4", ReadOnly: false}} slashInName := api.VolumeSource{Flocker: &api.FlockerVolumeSource{DatasetName: "foo/bar"}} - emptyAzureSecret := api.VolumeSource{AzureFile: &api.AzureFileVolumeSource{"", "share", false}} - emptyAzureShare := api.VolumeSource{AzureFile: &api.AzureFileVolumeSource{"name", "", false}} + emptyAzureSecret := api.VolumeSource{AzureFile: &api.AzureFileVolumeSource{SecretName: "", ShareName: "share", ReadOnly: false}} + emptyAzureShare := api.VolumeSource{AzureFile: &api.AzureFileVolumeSource{SecretName: "name", ShareName: "", ReadOnly: false}} errorCases := map[string]struct { V []api.Volume T field.ErrorType @@ -1123,17 +1124,19 @@ func TestValidateVolumeMounts(t *testing.T) { successCase := []api.VolumeMount{ {Name: "abc", MountPath: "/foo"}, - {Name: "123", MountPath: "/foo"}, - {Name: "abc-123", MountPath: "/bar"}, + {Name: "123", MountPath: "/bar"}, + {Name: "abc-123", MountPath: "/baz"}, } if errs := validateVolumeMounts(successCase, volumes, field.NewPath("field")); len(errs) != 0 { t.Errorf("expected success: %v", errs) } errorCases := map[string][]api.VolumeMount{ - "empty name": {{Name: "", MountPath: "/foo"}}, - "name not found": {{Name: "", MountPath: "/foo"}}, - "empty mountpath": {{Name: "abc", MountPath: ""}}, + "empty name": {{Name: "", MountPath: "/foo"}}, + "name not found": {{Name: "", MountPath: "/foo"}}, + "empty mountpath": {{Name: "abc", MountPath: ""}}, + "colon mountpath": {{Name: "abc", MountPath: "foo:bar"}}, + "mountpath collision": {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}}, } for k, v := range errorCases { if errs := validateVolumeMounts(v, volumes, field.NewPath("field")); len(errs) == 0 { @@ -2620,22 +2623,28 @@ func TestValidateService(t *testing.T) { numErrs: 0, }, { - name: "invalid port headless", + name: "invalid port headless 1", tweakSvc: func(s *api.Service) { s.Spec.Ports[0].Port = 11722 s.Spec.Ports[0].TargetPort = intstr.FromInt(11721) s.Spec.ClusterIP = api.ClusterIPNone }, - numErrs: 1, + // in the v1 API, targetPorts on headless services were tolerated. + // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. + // numErrs: 1, + numErrs: 0, }, { - name: "invalid port headless", + name: "invalid port headless 2", tweakSvc: func(s *api.Service) { s.Spec.Ports[0].Port = 11722 s.Spec.Ports[0].TargetPort = intstr.FromString("target") s.Spec.ClusterIP = api.ClusterIPNone }, - numErrs: 1, + // in the v1 API, targetPorts on headless services were tolerated. + // once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility. + // numErrs: 1, + numErrs: 0, }, { name: "invalid publicIPs localhost", @@ -2871,6 +2880,34 @@ func TestValidateService(t *testing.T) { }, numErrs: 1, }, + { + name: "valid LoadBalancer source range annotation", + tweakSvc: func(s *api.Service) { + s.Annotations[service.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/8, 5.6.7.8/16" + }, + numErrs: 0, + }, + { + name: "empty LoadBalancer source range annotation", + tweakSvc: func(s *api.Service) { + s.Annotations[service.AnnotationLoadBalancerSourceRangesKey] = "" + }, + numErrs: 0, + }, + { + name: "invalid LoadBalancer source range annotation (hostname)", + tweakSvc: func(s *api.Service) { + s.Annotations[service.AnnotationLoadBalancerSourceRangesKey] = "foo.bar" + }, + numErrs: 1, + }, + { + name: "invalid LoadBalancer source range annotation (invalid CIDR)", + tweakSvc: func(s *api.Service) { + s.Annotations[service.AnnotationLoadBalancerSourceRangesKey] = "1.2.3.4/33" + }, + numErrs: 1, + }, } for _, tc := range testCases { @@ -3828,6 +3865,14 @@ func getResourceList(cpu, memory string) api.ResourceList { return res } +func getStorageResourceList(storage string) api.ResourceList { + res := api.ResourceList{} + if storage != "" { + res[api.ResourceStorage] = resource.MustParse(storage) + } + return res +} + func TestValidateLimitRange(t *testing.T) { successCases := []struct { name string @@ -3869,6 +3914,36 @@ func TestValidateLimitRange(t *testing.T) { }, }, }, + { + name: "thirdparty-fields-all-valid-standard-container-resources", + spec: api.LimitRangeSpec{ + Limits: []api.LimitRangeItem{ + { + Type: "thirdparty.com/foo", + Max: getResourceList("100m", "10000T"), + Min: getResourceList("5m", "100Mi"), + Default: getResourceList("50m", "500Mi"), + DefaultRequest: getResourceList("10m", "200Mi"), + MaxLimitRequestRatio: getResourceList("10", ""), + }, + }, + }, + }, + { + name: "thirdparty-fields-all-valid-storage-resources", + spec: api.LimitRangeSpec{ + Limits: []api.LimitRangeItem{ + { + Type: "thirdparty.com/foo", + Max: getStorageResourceList("10000T"), + Min: getStorageResourceList("100Mi"), + Default: getStorageResourceList("500Mi"), + DefaultRequest: getStorageResourceList("200Mi"), + MaxLimitRequestRatio: getStorageResourceList(""), + }, + }, + }, + }, } for _, successCase := range successCases { @@ -4016,6 +4091,21 @@ func TestValidateLimitRange(t *testing.T) { }}, "ratio 10 is greater than max/min = 4.000000", }, + "invalid non standard limit type": { + api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ + Limits: []api.LimitRangeItem{ + { + Type: "foo", + Max: getStorageResourceList("10000T"), + Min: getStorageResourceList("100Mi"), + Default: getStorageResourceList("500Mi"), + DefaultRequest: getStorageResourceList("200Mi"), + MaxLimitRequestRatio: getStorageResourceList(""), + }, + }, + }}, + "must be a standard limit type or fully qualified", + }, } for k, v := range errorCases { @@ -4038,10 +4128,52 @@ func TestValidateResourceQuota(t *testing.T) { Hard: api.ResourceList{ api.ResourceCPU: resource.MustParse("100"), api.ResourceMemory: resource.MustParse("10000"), + api.ResourceRequestsCPU: resource.MustParse("100"), + api.ResourceRequestsMemory: resource.MustParse("10000"), + api.ResourceLimitsCPU: resource.MustParse("100"), + api.ResourceLimitsMemory: resource.MustParse("10000"), api.ResourcePods: resource.MustParse("10"), api.ResourceServices: resource.MustParse("0"), api.ResourceReplicationControllers: resource.MustParse("10"), api.ResourceQuotas: resource.MustParse("10"), + api.ResourceConfigMaps: resource.MustParse("10"), + api.ResourceSecrets: resource.MustParse("10"), + }, + } + + terminatingSpec := api.ResourceQuotaSpec{ + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100"), + api.ResourceLimitsCPU: resource.MustParse("200"), + }, + Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeTerminating}, + } + + nonTerminatingSpec := api.ResourceQuotaSpec{ + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100"), + }, + Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeNotTerminating}, + } + + bestEffortSpec := api.ResourceQuotaSpec{ + Hard: api.ResourceList{ + api.ResourcePods: resource.MustParse("100"), + }, + Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeBestEffort}, + } + + nonBestEffortSpec := api.ResourceQuotaSpec{ + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100"), + }, + Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeNotBestEffort}, + } + + // storage is not yet supported as a quota tracked resource + invalidQuotaResourceSpec := api.ResourceQuotaSpec{ + Hard: api.ResourceList{ + api.ResourceStorage: resource.MustParse("10"), }, } @@ -4053,6 +4185,8 @@ func TestValidateResourceQuota(t *testing.T) { api.ResourceServices: resource.MustParse("-10"), api.ResourceReplicationControllers: resource.MustParse("-10"), api.ResourceQuotas: resource.MustParse("-10"), + api.ResourceConfigMaps: resource.MustParse("-10"), + api.ResourceSecrets: resource.MustParse("-10"), }, } @@ -4071,6 +4205,27 @@ func TestValidateResourceQuota(t *testing.T) { }, } + invalidTerminatingScopePairsSpec := api.ResourceQuotaSpec{ + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100"), + }, + Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeTerminating, api.ResourceQuotaScopeNotTerminating}, + } + + invalidBestEffortScopePairsSpec := api.ResourceQuotaSpec{ + Hard: api.ResourceList{ + api.ResourcePods: resource.MustParse("100"), + }, + Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScopeBestEffort, api.ResourceQuotaScopeNotBestEffort}, + } + + invalidScopeNameSpec := api.ResourceQuotaSpec{ + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100"), + }, + Scopes: []api.ResourceQuotaScope{api.ResourceQuotaScope("foo")}, + } + successCases := []api.ResourceQuota{ { ObjectMeta: api.ObjectMeta{ @@ -4086,6 +4241,34 @@ func TestValidateResourceQuota(t *testing.T) { }, Spec: fractionalComputeSpec, }, + { + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: "foo", + }, + Spec: terminatingSpec, + }, + { + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: "foo", + }, + Spec: nonTerminatingSpec, + }, + { + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: "foo", + }, + Spec: bestEffortSpec, + }, + { + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: "foo", + }, + Spec: nonBestEffortSpec, + }, } for _, successCase := range successCases { @@ -4122,6 +4305,22 @@ func TestValidateResourceQuota(t *testing.T) { api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: fractionalPodSpec}, isNotIntegerErrorMsg, }, + "invalid-quota-resource": { + api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidQuotaResourceSpec}, + isInvalidQuotaResource, + }, + "invalid-quota-terminating-pair": { + api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidTerminatingScopePairsSpec}, + "conflicting scopes", + }, + "invalid-quota-besteffort-pair": { + api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidBestEffortScopePairsSpec}, + "conflicting scopes", + }, + "invalid-quota-scope-name": { + api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: invalidScopeNameSpec}, + "unsupported scope", + }, } for k, v := range errorCases { errs := ValidateResourceQuota(&v.R) @@ -5107,3 +5306,40 @@ func TestValidateConfigMapUpdate(t *testing.T) { } } } + +func TestValidateHasLabel(t *testing.T) { + successCase := api.ObjectMeta{ + Name: "123", + Namespace: "ns", + Labels: map[string]string{ + "other": "blah", + "foo": "bar", + }, + } + if errs := ValidateHasLabel(successCase, field.NewPath("field"), "foo", "bar"); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + + missingCase := api.ObjectMeta{ + Name: "123", + Namespace: "ns", + Labels: map[string]string{ + "other": "blah", + }, + } + if errs := ValidateHasLabel(missingCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 { + t.Errorf("expected failure") + } + + wrongValueCase := api.ObjectMeta{ + Name: "123", + Namespace: "ns", + Labels: map[string]string{ + "other": "blah", + "foo": "notbar", + }, + } + if errs := ValidateHasLabel(wrongValueCase, field.NewPath("field"), "foo", "bar"); len(errs) == 0 { + t.Errorf("expected failure") + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apimachinery/registered/registered.go b/vendor/k8s.io/kubernetes/pkg/apimachinery/registered/registered.go index 6baef8f91..c1e1511be 100644 --- a/vendor/k8s.io/kubernetes/pkg/apimachinery/registered/registered.go +++ b/vendor/k8s.io/kubernetes/pkg/apimachinery/registered/registered.go @@ -24,14 +24,21 @@ import ( "strings" "github.com/golang/glog" + + "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apimachinery" + "k8s.io/kubernetes/pkg/util/sets" ) var ( // registeredGroupVersions stores all API group versions for which RegisterGroup is called. registeredVersions = map[unversioned.GroupVersion]struct{}{} + // thirdPartyGroupVersions are API versions which are dynamically + // registered (and unregistered) via API calls to the apiserver + thirdPartyGroupVersions []unversioned.GroupVersion + // enabledVersions represents all enabled API versions. It should be a // subset of registeredVersions. Please call EnableVersions() to add // enabled versions. @@ -43,8 +50,8 @@ var ( // envRequestedVersions represents the versions requested via the // KUBE_API_VERSIONS environment variable. The install package of each group // checks this list before add their versions to the latest package and - // Scheme. - envRequestedVersions = map[unversioned.GroupVersion]struct{}{} + // Scheme. This list is small and order matters, so represent as a slice + envRequestedVersions = []unversioned.GroupVersion{} ) func init() { @@ -58,7 +65,7 @@ func init() { glog.Fatalf("invalid api version: %s in KUBE_API_VERSIONS: %s.", version, os.Getenv("KUBE_API_VERSIONS")) } - envRequestedVersions[gv] = struct{}{} + envRequestedVersions = append(envRequestedVersions, gv) } } } @@ -104,8 +111,12 @@ func IsAllowedVersion(v unversioned.GroupVersion) bool { if len(envRequestedVersions) == 0 { return true } - _, found := envRequestedVersions[v] - return found + for _, envGV := range envRequestedVersions { + if v == envGV { + return true + } + } + return false } // IsEnabledVersion returns if a version is enabled. @@ -151,6 +162,51 @@ func IsRegistered(group string) bool { return found } +// IsRegisteredVersion returns if a version is registered. +func IsRegisteredVersion(v unversioned.GroupVersion) bool { + _, found := registeredVersions[v] + return found +} + +// IsThirdPartyAPIGroupVersion returns true if the api version is a user-registered group/version. +func IsThirdPartyAPIGroupVersion(gv unversioned.GroupVersion) bool { + for ix := range thirdPartyGroupVersions { + if thirdPartyGroupVersions[ix] == gv { + return true + } + } + return false +} + +// AddThirdPartyAPIGroupVersions sets the list of third party versions, +// registers them in the API machinery and enables them. +// Skips GroupVersions that are already registered. +// Returns the list of GroupVersions that were skipped. +func AddThirdPartyAPIGroupVersions(gvs ...unversioned.GroupVersion) []unversioned.GroupVersion { + filteredGVs := []unversioned.GroupVersion{} + skippedGVs := []unversioned.GroupVersion{} + for ix := range gvs { + if !IsRegisteredVersion(gvs[ix]) { + filteredGVs = append(filteredGVs, gvs[ix]) + } else { + glog.V(3).Infof("Skipping %s, because its already registered", gvs[ix].String()) + skippedGVs = append(skippedGVs, gvs[ix]) + } + } + if len(filteredGVs) == 0 { + return skippedGVs + } + RegisterVersions(filteredGVs) + EnableVersions(filteredGVs...) + next := make([]unversioned.GroupVersion, len(gvs)) + for ix := range filteredGVs { + next[ix] = filteredGVs[ix] + } + thirdPartyGroupVersions = next + + return skippedGVs +} + // TODO: This is an expedient function, because we don't check if a Group is // supported throughout the code base. We will abandon this function and // checking the error returned by the Group() function. @@ -167,6 +223,84 @@ func GroupOrDie(group string) *apimachinery.GroupMeta { return &groupMetaCopy } +// RESTMapper returns a union RESTMapper of all known types with priorities chosen in the following order: +// 1. if KUBE_API_VERSIONS is specified, then KUBE_API_VERSIONS in order, OR +// 1. legacy kube group preferred version, extensions preferred version, metrics perferred version, legacy +// kube any version, extensions any version, metrics any version, all other groups alphabetical preferred version, +// all other groups alphabetical. +func RESTMapper(versionPatterns ...unversioned.GroupVersion) meta.RESTMapper { + unionMapper := meta.MultiRESTMapper{} + unionedGroups := sets.NewString() + for enabledVersion := range enabledVersions { + if !unionedGroups.Has(enabledVersion.Group) { + unionedGroups.Insert(enabledVersion.Group) + groupMeta := groupMetaMap[enabledVersion.Group] + unionMapper = append(unionMapper, groupMeta.RESTMapper) + } + } + + if len(versionPatterns) != 0 { + resourcePriority := []unversioned.GroupVersionResource{} + kindPriority := []unversioned.GroupVersionKind{} + for _, versionPriority := range versionPatterns { + resourcePriority = append(resourcePriority, versionPriority.WithResource(meta.AnyResource)) + kindPriority = append(kindPriority, versionPriority.WithKind(meta.AnyKind)) + } + + return meta.PriorityRESTMapper{Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority} + } + + if len(envRequestedVersions) != 0 { + resourcePriority := []unversioned.GroupVersionResource{} + kindPriority := []unversioned.GroupVersionKind{} + + for _, versionPriority := range envRequestedVersions { + resourcePriority = append(resourcePriority, versionPriority.WithResource(meta.AnyResource)) + kindPriority = append(kindPriority, versionPriority.WithKind(meta.AnyKind)) + } + + return meta.PriorityRESTMapper{Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority} + } + + prioritizedGroups := []string{"", "extensions", "metrics"} + resourcePriority, kindPriority := prioritiesForGroups(prioritizedGroups...) + + prioritizedGroupsSet := sets.NewString(prioritizedGroups...) + remainingGroups := sets.String{} + for enabledVersion := range enabledVersions { + if !prioritizedGroupsSet.Has(enabledVersion.Group) { + remainingGroups.Insert(enabledVersion.Group) + } + } + + remainingResourcePriority, remainingKindPriority := prioritiesForGroups(remainingGroups.List()...) + resourcePriority = append(resourcePriority, remainingResourcePriority...) + kindPriority = append(kindPriority, remainingKindPriority...) + + return meta.PriorityRESTMapper{Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority} +} + +// prioritiesForGroups returns the resource and kind priorities for a PriorityRESTMapper, preferring the preferred version of each group first, +// then any non-preferred version of the group second. +func prioritiesForGroups(groups ...string) ([]unversioned.GroupVersionResource, []unversioned.GroupVersionKind) { + resourcePriority := []unversioned.GroupVersionResource{} + kindPriority := []unversioned.GroupVersionKind{} + + for _, group := range groups { + availableVersions := EnabledVersionsForGroup(group) + if len(availableVersions) > 0 { + resourcePriority = append(resourcePriority, availableVersions[0].WithResource(meta.AnyResource)) + kindPriority = append(kindPriority, availableVersions[0].WithKind(meta.AnyKind)) + } + } + for _, group := range groups { + resourcePriority = append(resourcePriority, unversioned.GroupVersionResource{Group: group, Version: meta.AnyVersion, Resource: meta.AnyResource}) + kindPriority = append(kindPriority, unversioned.GroupVersionKind{Group: group, Version: meta.AnyVersion, Kind: meta.AnyKind}) + } + + return resourcePriority, kindPriority +} + // AllPreferredGroupVersions returns the preferred versions of all registered // groups in the form of "group1/version1,group2/version2,..." func AllPreferredGroupVersions() string { @@ -185,7 +319,7 @@ func AllPreferredGroupVersions() string { // the KUBE_API_VERSIONS environment variable, but not enabled. func ValidateEnvRequestedVersions() []unversioned.GroupVersion { var missingVersions []unversioned.GroupVersion - for v := range envRequestedVersions { + for _, v := range envRequestedVersions { if _, found := enabledVersions[v]; !found { missingVersions = append(missingVersions, v) } diff --git a/vendor/k8s.io/kubernetes/pkg/apimachinery/registered/registered_test.go b/vendor/k8s.io/kubernetes/pkg/apimachinery/registered/registered_test.go index e7466fcc2..001b65e92 100644 --- a/vendor/k8s.io/kubernetes/pkg/apimachinery/registered/registered_test.go +++ b/vendor/k8s.io/kubernetes/pkg/apimachinery/registered/registered_test.go @@ -31,13 +31,13 @@ func TestAllPreferredGroupVersions(t *testing.T) { { groupMetas: []apimachinery.GroupMeta{ { - GroupVersion: unversioned.GroupVersion{"group1", "v1"}, + GroupVersion: unversioned.GroupVersion{Group: "group1", Version: "v1"}, }, { - GroupVersion: unversioned.GroupVersion{"group2", "v2"}, + GroupVersion: unversioned.GroupVersion{Group: "group2", Version: "v2"}, }, { - GroupVersion: unversioned.GroupVersion{"", "v1"}, + GroupVersion: unversioned.GroupVersion{Group: "", Version: "v1"}, }, }, expect: "group1/v1,group2/v2,v1", @@ -45,7 +45,7 @@ func TestAllPreferredGroupVersions(t *testing.T) { { groupMetas: []apimachinery.GroupMeta{ { - GroupVersion: unversioned.GroupVersion{"", "v1"}, + GroupVersion: unversioned.GroupVersion{Group: "", Version: "v1"}, }, }, expect: "v1", diff --git a/vendor/k8s.io/kubernetes/pkg/apis/OWNERS b/vendor/k8s.io/kubernetes/pkg/apis/OWNERS new file mode 100644 index 000000000..d28472e0f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/OWNERS @@ -0,0 +1,6 @@ +assignees: + - bgrant0607 + - erictune + - lavalamp + - smarterclayton + - thockin diff --git a/vendor/k8s.io/kubernetes/pkg/apis/abac/latest/latest.go b/vendor/k8s.io/kubernetes/pkg/apis/abac/latest/latest.go new file mode 100644 index 000000000..2618fc4fc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/abac/latest/latest.go @@ -0,0 +1,23 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 latest + +import ( + _ "k8s.io/kubernetes/pkg/apis/abac" + _ "k8s.io/kubernetes/pkg/apis/abac/v0" + _ "k8s.io/kubernetes/pkg/apis/abac/v1beta1" +) diff --git a/vendor/k8s.io/kubernetes/pkg/apis/abac/register.go b/vendor/k8s.io/kubernetes/pkg/apis/abac/register.go new file mode 100644 index 000000000..c555d5aa9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/abac/register.go @@ -0,0 +1,40 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 abac + +import ( + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/runtime/serializer" +) + +// Group is the API group for abac +const Group = "abac.authorization.kubernetes.io" + +// Scheme is the default instance of runtime.Scheme to which types in the abac API group are registered. +var Scheme = runtime.NewScheme() + +// Codecs provides access to encoding and decoding for the scheme +var Codecs = serializer.NewCodecFactory(Scheme) + +func init() { + Scheme.AddKnownTypes(unversioned.GroupVersion{Group: Group, Version: runtime.APIVersionInternal}, + &Policy{}, + ) +} + +func (obj *Policy) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/vendor/k8s.io/kubernetes/pkg/apis/abac/types.go b/vendor/k8s.io/kubernetes/pkg/apis/abac/types.go new file mode 100644 index 000000000..024c7ee24 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/abac/types.go @@ -0,0 +1,70 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 abac + +import "k8s.io/kubernetes/pkg/api/unversioned" + +// Policy contains a single ABAC policy rule +type Policy struct { + unversioned.TypeMeta + + // Spec describes the policy rule + Spec PolicySpec +} + +// PolicySpec contains the attributes for a policy rule +type PolicySpec struct { + + // User is the username this rule applies to. + // Either user or group is required to match the request. + // "*" matches all users. + User string + + // Group is the group this rule applies to. + // Either user or group is required to match the request. + // "*" matches all groups. + Group string + + // Readonly matches readonly requests when true, and all requests when false + Readonly bool + + // APIGroup is the name of an API group. APIGroup, Resource, and Namespace are required to match resource requests. + // "*" matches all API groups + APIGroup string + + // Resource is the name of a resource. APIGroup, Resource, and Namespace are required to match resource requests. + // "*" matches all resources + Resource string + + // Namespace is the name of a namespace. APIGroup, Resource, and Namespace are required to match resource requests. + // "*" matches all namespaces (including unnamespaced requests) + Namespace string + + // NonResourcePath matches non-resource request paths. + // "*" matches all paths + // "/foo/*" matches all subpaths of foo + NonResourcePath string + + // TODO: "expires" string in RFC3339 format. + + // TODO: want a way to allow some users to restart containers of a pod but + // not delete or modify it. + + // TODO: want a way to allow a controller to create a pod based only on a + // certain podTemplates. + +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/abac/v0/conversion.go b/vendor/k8s.io/kubernetes/pkg/apis/abac/v0/conversion.go new file mode 100644 index 000000000..c0fda4bd5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/abac/v0/conversion.go @@ -0,0 +1,58 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 v0 + +import ( + api "k8s.io/kubernetes/pkg/apis/abac" + "k8s.io/kubernetes/pkg/conversion" +) + +func init() { + api.Scheme.AddConversionFuncs( + func(in *Policy, out *api.Policy, s conversion.Scope) error { + // Begin by copying all fields + out.Spec.User = in.User + out.Spec.Group = in.Group + out.Spec.Namespace = in.Namespace + out.Spec.Resource = in.Resource + out.Spec.Readonly = in.Readonly + + // In v0, unspecified user and group matches all subjects + if len(in.User) == 0 && len(in.Group) == 0 { + out.Spec.User = "*" + } + + // In v0, leaving namespace empty matches all namespaces + if len(in.Namespace) == 0 { + out.Spec.Namespace = "*" + } + // In v0, leaving resource empty matches all resources + if len(in.Resource) == 0 { + out.Spec.Resource = "*" + } + // Any rule in v0 should match all API groups + out.Spec.APIGroup = "*" + + // In v0, leaving namespace and resource blank allows non-resource paths + if len(in.Namespace) == 0 && len(in.Resource) == 0 { + out.Spec.NonResourcePath = "*" + } + + return nil + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/abac/v0/conversion_test.go b/vendor/k8s.io/kubernetes/pkg/apis/abac/v0/conversion_test.go new file mode 100644 index 000000000..ffdbd398d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/abac/v0/conversion_test.go @@ -0,0 +1,77 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 v0_test + +import ( + "reflect" + "testing" + + api "k8s.io/kubernetes/pkg/apis/abac" + "k8s.io/kubernetes/pkg/apis/abac/v0" +) + +func TestConversion(t *testing.T) { + testcases := map[string]struct { + old *v0.Policy + expected *api.Policy + }{ + // a completely empty policy rule allows everything to all users + "empty": { + old: &v0.Policy{}, + expected: &api.Policy{Spec: api.PolicySpec{User: "*", Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}}, + }, + + // specifying a user is preserved + "user": { + old: &v0.Policy{User: "bob"}, + expected: &api.Policy{Spec: api.PolicySpec{User: "bob", Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}}, + }, + + // specifying a group is preserved (and no longer matches all users) + "group": { + old: &v0.Policy{Group: "mygroup"}, + expected: &api.Policy{Spec: api.PolicySpec{Group: "mygroup", Readonly: false, NonResourcePath: "*", Namespace: "*", Resource: "*", APIGroup: "*"}}, + }, + + // specifying a namespace removes the * match on non-resource path + "namespace": { + old: &v0.Policy{Namespace: "myns"}, + expected: &api.Policy{Spec: api.PolicySpec{User: "*", Readonly: false, NonResourcePath: "", Namespace: "myns", Resource: "*", APIGroup: "*"}}, + }, + + // specifying a resource removes the * match on non-resource path + "resource": { + old: &v0.Policy{Resource: "myresource"}, + expected: &api.Policy{Spec: api.PolicySpec{User: "*", Readonly: false, NonResourcePath: "", Namespace: "*", Resource: "myresource", APIGroup: "*"}}, + }, + + // specifying a namespace+resource removes the * match on non-resource path + "namespace+resource": { + old: &v0.Policy{Namespace: "myns", Resource: "myresource"}, + expected: &api.Policy{Spec: api.PolicySpec{User: "*", Readonly: false, NonResourcePath: "", Namespace: "myns", Resource: "myresource", APIGroup: "*"}}, + }, + } + for k, tc := range testcases { + internal := &api.Policy{} + if err := api.Scheme.Convert(tc.old, internal); err != nil { + t.Errorf("%s: unexpected error: %v", k, err) + } + if !reflect.DeepEqual(internal, tc.expected) { + t.Errorf("%s: expected\n\t%#v, got \n\t%#v", k, tc.expected, internal) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/abac/v0/register.go b/vendor/k8s.io/kubernetes/pkg/apis/abac/v0/register.go new file mode 100644 index 000000000..d5338045a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/abac/v0/register.go @@ -0,0 +1,33 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 v0 + +import ( + "k8s.io/kubernetes/pkg/api/unversioned" + api "k8s.io/kubernetes/pkg/apis/abac" +) + +// GroupVersion is the API group and version for abac v0 +var GroupVersion = unversioned.GroupVersion{Group: api.Group, Version: "v0"} + +func init() { + api.Scheme.AddKnownTypes(GroupVersion, + &Policy{}, + ) +} + +func (obj *Policy) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/vendor/k8s.io/kubernetes/pkg/apis/abac/v0/types.go b/vendor/k8s.io/kubernetes/pkg/apis/abac/v0/types.go new file mode 100644 index 000000000..58bb569f4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/abac/v0/types.go @@ -0,0 +1,45 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 v0 + +import "k8s.io/kubernetes/pkg/api/unversioned" + +// Policy contains a single ABAC policy rule +type Policy struct { + unversioned.TypeMeta `json:",inline"` + + // User is the username this rule applies to. + // Either user or group is required to match the request. + // "*" matches all users. + User string `json:"user,omitempty"` + + // Group is the group this rule applies to. + // Either user or group is required to match the request. + // "*" matches all groups. + Group string `json:"group,omitempty"` + + // Readonly matches readonly requests when true, and all requests when false + Readonly bool `json:"readonly,omitempty"` + + // Resource is the name of a resource + // "*" matches all resources + Resource string `json:"resource,omitempty"` + + // Namespace is the name of a namespace + // "*" matches all namespaces (including unnamespaced requests) + Namespace string `json:"namespace,omitempty"` +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/abac/v1beta1/register.go b/vendor/k8s.io/kubernetes/pkg/apis/abac/v1beta1/register.go new file mode 100644 index 000000000..95fd6b3ef --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/abac/v1beta1/register.go @@ -0,0 +1,33 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + "k8s.io/kubernetes/pkg/api/unversioned" + api "k8s.io/kubernetes/pkg/apis/abac" +) + +// GroupVersion is the API group and version for abac v1beta1 +var GroupVersion = unversioned.GroupVersion{Group: api.Group, Version: "v1beta1"} + +func init() { + api.Scheme.AddKnownTypes(GroupVersion, + &Policy{}, + ) +} + +func (obj *Policy) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/vendor/k8s.io/kubernetes/pkg/apis/abac/v1beta1/types.go b/vendor/k8s.io/kubernetes/pkg/apis/abac/v1beta1/types.go new file mode 100644 index 000000000..7ce61ac4a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/abac/v1beta1/types.go @@ -0,0 +1,60 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import "k8s.io/kubernetes/pkg/api/unversioned" + +// Policy contains a single ABAC policy rule +type Policy struct { + unversioned.TypeMeta `json:",inline"` + + // Spec describes the policy rule + Spec PolicySpec `json:"spec"` +} + +// PolicySpec contains the attributes for a policy rule +type PolicySpec struct { + // User is the username this rule applies to. + // Either user or group is required to match the request. + // "*" matches all users. + User string `json:"user,omitempty"` + + // Group is the group this rule applies to. + // Either user or group is required to match the request. + // "*" matches all groups. + Group string `json:"group,omitempty"` + + // Readonly matches readonly requests when true, and all requests when false + Readonly bool `json:"readonly,omitempty"` + + // APIGroup is the name of an API group. APIGroup, Resource, and Namespace are required to match resource requests. + // "*" matches all API groups + APIGroup string `json:"apiGroup,omitempty"` + + // Resource is the name of a resource. APIGroup, Resource, and Namespace are required to match resource requests. + // "*" matches all resources + Resource string `json:"resource,omitempty"` + + // Namespace is the name of a namespace. APIGroup, Resource, and Namespace are required to match resource requests. + // "*" matches all namespaces (including unnamespaced requests) + Namespace string `json:"namespace,omitempty"` + + // NonResourcePath matches non-resource request paths. + // "*" matches all paths + // "/foo/*" matches all subpaths of foo + NonResourcePath string `json:"nonResourcePath,omitempty"` +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/authorization/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/authorization/deep_copy_generated.go index 034a94d1e..7f671dfee 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/authorization/deep_copy_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/authorization/deep_copy_generated.go @@ -1,5 +1,7 @@ +// +build !ignore_autogenerated + /* -Copyright 2015 The Kubernetes Authors All rights reserved. +Copyright 2016 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,16 +16,142 @@ See the License for the specific language governing permissions and limitations under the License. */ -// DO NOT EDIT. THIS FILE IS AUTO-GENERATED BY $KUBEROOT/hack/update-generated-deep-copies.sh. +// This file was autogenerated by deepcopy-gen. Do not edit it manually! package authorization -import api "k8s.io/kubernetes/pkg/api" +import ( + api "k8s.io/kubernetes/pkg/api" + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + conversion "k8s.io/kubernetes/pkg/conversion" +) func init() { - err := api.Scheme.AddGeneratedDeepCopyFuncs() - if err != nil { + if err := api.Scheme.AddGeneratedDeepCopyFuncs( + DeepCopy_authorization_LocalSubjectAccessReview, + DeepCopy_authorization_NonResourceAttributes, + DeepCopy_authorization_ResourceAttributes, + DeepCopy_authorization_SelfSubjectAccessReview, + DeepCopy_authorization_SelfSubjectAccessReviewSpec, + DeepCopy_authorization_SubjectAccessReview, + DeepCopy_authorization_SubjectAccessReviewSpec, + DeepCopy_authorization_SubjectAccessReviewStatus, + ); err != nil { // if one of the deep copy functions is malformed, detect it immediately. panic(err) } } + +func DeepCopy_authorization_LocalSubjectAccessReview(in LocalSubjectAccessReview, out *LocalSubjectAccessReview, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := DeepCopy_authorization_SubjectAccessReviewSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_authorization_SubjectAccessReviewStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_authorization_NonResourceAttributes(in NonResourceAttributes, out *NonResourceAttributes, c *conversion.Cloner) error { + out.Path = in.Path + out.Verb = in.Verb + return nil +} + +func DeepCopy_authorization_ResourceAttributes(in ResourceAttributes, out *ResourceAttributes, c *conversion.Cloner) error { + out.Namespace = in.Namespace + out.Verb = in.Verb + out.Group = in.Group + out.Version = in.Version + out.Resource = in.Resource + out.Subresource = in.Subresource + out.Name = in.Name + return nil +} + +func DeepCopy_authorization_SelfSubjectAccessReview(in SelfSubjectAccessReview, out *SelfSubjectAccessReview, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := DeepCopy_authorization_SelfSubjectAccessReviewSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_authorization_SubjectAccessReviewStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_authorization_SelfSubjectAccessReviewSpec(in SelfSubjectAccessReviewSpec, out *SelfSubjectAccessReviewSpec, c *conversion.Cloner) error { + if in.ResourceAttributes != nil { + in, out := in.ResourceAttributes, &out.ResourceAttributes + *out = new(ResourceAttributes) + if err := DeepCopy_authorization_ResourceAttributes(*in, *out, c); err != nil { + return err + } + } else { + out.ResourceAttributes = nil + } + if in.NonResourceAttributes != nil { + in, out := in.NonResourceAttributes, &out.NonResourceAttributes + *out = new(NonResourceAttributes) + if err := DeepCopy_authorization_NonResourceAttributes(*in, *out, c); err != nil { + return err + } + } else { + out.NonResourceAttributes = nil + } + return nil +} + +func DeepCopy_authorization_SubjectAccessReview(in SubjectAccessReview, out *SubjectAccessReview, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := DeepCopy_authorization_SubjectAccessReviewSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_authorization_SubjectAccessReviewStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_authorization_SubjectAccessReviewSpec(in SubjectAccessReviewSpec, out *SubjectAccessReviewSpec, c *conversion.Cloner) error { + if in.ResourceAttributes != nil { + in, out := in.ResourceAttributes, &out.ResourceAttributes + *out = new(ResourceAttributes) + if err := DeepCopy_authorization_ResourceAttributes(*in, *out, c); err != nil { + return err + } + } else { + out.ResourceAttributes = nil + } + if in.NonResourceAttributes != nil { + in, out := in.NonResourceAttributes, &out.NonResourceAttributes + *out = new(NonResourceAttributes) + if err := DeepCopy_authorization_NonResourceAttributes(*in, *out, c); err != nil { + return err + } + } else { + out.NonResourceAttributes = nil + } + out.User = in.User + if in.Groups != nil { + in, out := in.Groups, &out.Groups + *out = make([]string, len(in)) + copy(*out, in) + } else { + out.Groups = nil + } + return nil +} + +func DeepCopy_authorization_SubjectAccessReviewStatus(in SubjectAccessReviewStatus, out *SubjectAccessReviewStatus, c *conversion.Cloner) error { + out.Allowed = in.Allowed + out.Reason = in.Reason + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/authorization/v1beta1/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/authorization/v1beta1/deep_copy_generated.go index ccf024c8b..5c4ad6fd9 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/authorization/v1beta1/deep_copy_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/authorization/v1beta1/deep_copy_generated.go @@ -1,5 +1,7 @@ +// +build !ignore_autogenerated + /* -Copyright 2015 The Kubernetes Authors All rights reserved. +Copyright 2016 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,16 +16,142 @@ See the License for the specific language governing permissions and limitations under the License. */ -// DO NOT EDIT. THIS FILE IS AUTO-GENERATED BY $KUBEROOT/hack/update-generated-deep-copies.sh. +// This file was autogenerated by deepcopy-gen. Do not edit it manually! package v1beta1 -import api "k8s.io/kubernetes/pkg/api" +import ( + api "k8s.io/kubernetes/pkg/api" + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + conversion "k8s.io/kubernetes/pkg/conversion" +) func init() { - err := api.Scheme.AddGeneratedDeepCopyFuncs() - if err != nil { + if err := api.Scheme.AddGeneratedDeepCopyFuncs( + DeepCopy_v1beta1_LocalSubjectAccessReview, + DeepCopy_v1beta1_NonResourceAttributes, + DeepCopy_v1beta1_ResourceAttributes, + DeepCopy_v1beta1_SelfSubjectAccessReview, + DeepCopy_v1beta1_SelfSubjectAccessReviewSpec, + DeepCopy_v1beta1_SubjectAccessReview, + DeepCopy_v1beta1_SubjectAccessReviewSpec, + DeepCopy_v1beta1_SubjectAccessReviewStatus, + ); err != nil { // if one of the deep copy functions is malformed, detect it immediately. panic(err) } } + +func DeepCopy_v1beta1_LocalSubjectAccessReview(in LocalSubjectAccessReview, out *LocalSubjectAccessReview, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := DeepCopy_v1beta1_SubjectAccessReviewSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_v1beta1_SubjectAccessReviewStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_v1beta1_NonResourceAttributes(in NonResourceAttributes, out *NonResourceAttributes, c *conversion.Cloner) error { + out.Path = in.Path + out.Verb = in.Verb + return nil +} + +func DeepCopy_v1beta1_ResourceAttributes(in ResourceAttributes, out *ResourceAttributes, c *conversion.Cloner) error { + out.Namespace = in.Namespace + out.Verb = in.Verb + out.Group = in.Group + out.Version = in.Version + out.Resource = in.Resource + out.Subresource = in.Subresource + out.Name = in.Name + return nil +} + +func DeepCopy_v1beta1_SelfSubjectAccessReview(in SelfSubjectAccessReview, out *SelfSubjectAccessReview, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := DeepCopy_v1beta1_SelfSubjectAccessReviewSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_v1beta1_SubjectAccessReviewStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_v1beta1_SelfSubjectAccessReviewSpec(in SelfSubjectAccessReviewSpec, out *SelfSubjectAccessReviewSpec, c *conversion.Cloner) error { + if in.ResourceAttributes != nil { + in, out := in.ResourceAttributes, &out.ResourceAttributes + *out = new(ResourceAttributes) + if err := DeepCopy_v1beta1_ResourceAttributes(*in, *out, c); err != nil { + return err + } + } else { + out.ResourceAttributes = nil + } + if in.NonResourceAttributes != nil { + in, out := in.NonResourceAttributes, &out.NonResourceAttributes + *out = new(NonResourceAttributes) + if err := DeepCopy_v1beta1_NonResourceAttributes(*in, *out, c); err != nil { + return err + } + } else { + out.NonResourceAttributes = nil + } + return nil +} + +func DeepCopy_v1beta1_SubjectAccessReview(in SubjectAccessReview, out *SubjectAccessReview, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := DeepCopy_v1beta1_SubjectAccessReviewSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_v1beta1_SubjectAccessReviewStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_v1beta1_SubjectAccessReviewSpec(in SubjectAccessReviewSpec, out *SubjectAccessReviewSpec, c *conversion.Cloner) error { + if in.ResourceAttributes != nil { + in, out := in.ResourceAttributes, &out.ResourceAttributes + *out = new(ResourceAttributes) + if err := DeepCopy_v1beta1_ResourceAttributes(*in, *out, c); err != nil { + return err + } + } else { + out.ResourceAttributes = nil + } + if in.NonResourceAttributes != nil { + in, out := in.NonResourceAttributes, &out.NonResourceAttributes + *out = new(NonResourceAttributes) + if err := DeepCopy_v1beta1_NonResourceAttributes(*in, *out, c); err != nil { + return err + } + } else { + out.NonResourceAttributes = nil + } + out.User = in.User + if in.Groups != nil { + in, out := in.Groups, &out.Groups + *out = make([]string, len(in)) + copy(*out, in) + } else { + out.Groups = nil + } + return nil +} + +func DeepCopy_v1beta1_SubjectAccessReviewStatus(in SubjectAccessReviewStatus, out *SubjectAccessReviewStatus, c *conversion.Cloner) error { + out.Allowed = in.Allowed + out.Reason = in.Reason + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/authorization/v1beta1/types_swagger_doc_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/authorization/v1beta1/types_swagger_doc_generated.go index 681e962ed..d9910ef14 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/authorization/v1beta1/types_swagger_doc_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/authorization/v1beta1/types_swagger_doc_generated.go @@ -16,7 +16,7 @@ limitations under the License. package v1beta1 -// This file contains a collection of methods that can be used from go-resful to +// This file contains a collection of methods that can be used from go-restful to // generate Swagger API documentation for its models. Please read this PR for more // information on the implementation: https://github.com/emicklei/go-restful/pull/215 // diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/deep_copy_generated.go new file mode 100644 index 000000000..938850f36 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/deep_copy_generated.go @@ -0,0 +1,65 @@ +// +build !ignore_autogenerated + +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package autoscaling + +import ( + api "k8s.io/kubernetes/pkg/api" + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + conversion "k8s.io/kubernetes/pkg/conversion" +) + +func init() { + if err := api.Scheme.AddGeneratedDeepCopyFuncs( + DeepCopy_autoscaling_Scale, + DeepCopy_autoscaling_ScaleSpec, + DeepCopy_autoscaling_ScaleStatus, + ); err != nil { + // if one of the deep copy functions is malformed, detect it immediately. + panic(err) + } +} + +func DeepCopy_autoscaling_Scale(in Scale, out *Scale, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api.DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := DeepCopy_autoscaling_ScaleSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_autoscaling_ScaleStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_autoscaling_ScaleSpec(in ScaleSpec, out *ScaleSpec, c *conversion.Cloner) error { + out.Replicas = in.Replicas + return nil +} + +func DeepCopy_autoscaling_ScaleStatus(in ScaleStatus, out *ScaleStatus, c *conversion.Cloner) error { + out.Replicas = in.Replicas + out.Selector = in.Selector + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/install/install.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/install/install.go new file mode 100644 index 000000000..6e226a066 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/install/install.go @@ -0,0 +1,129 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 install installs the experimental API group, making it available as +// an option to all of the API encoding/decoding machinery. +package install + +import ( + "fmt" + + "github.com/golang/glog" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apimachinery" + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/apis/autoscaling" + "k8s.io/kubernetes/pkg/apis/autoscaling/v1" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/sets" +) + +const importPrefix = "k8s.io/kubernetes/pkg/apis/autoscaling" + +var accessor = meta.NewAccessor() + +// availableVersions lists all known external versions for this group from most preferred to least preferred +var availableVersions = []unversioned.GroupVersion{v1.SchemeGroupVersion} + +func init() { + registered.RegisterVersions(availableVersions) + externalVersions := []unversioned.GroupVersion{} + for _, v := range availableVersions { + if registered.IsAllowedVersion(v) { + externalVersions = append(externalVersions, v) + } + } + if len(externalVersions) == 0 { + glog.V(4).Infof("No version is registered for group %v", autoscaling.GroupName) + return + } + + if err := registered.EnableVersions(externalVersions...); err != nil { + glog.V(4).Infof("%v", err) + return + } + if err := enableVersions(externalVersions); err != nil { + glog.V(4).Infof("%v", err) + return + } +} + +// TODO: enableVersions should be centralized rather than spread in each API +// group. +// We can combine registered.RegisterVersions, registered.EnableVersions and +// registered.RegisterGroup once we have moved enableVersions there. +func enableVersions(externalVersions []unversioned.GroupVersion) error { + addVersionsToScheme(externalVersions...) + preferredExternalVersion := externalVersions[0] + + groupMeta := apimachinery.GroupMeta{ + GroupVersion: preferredExternalVersion, + GroupVersions: externalVersions, + RESTMapper: newRESTMapper(externalVersions), + SelfLinker: runtime.SelfLinker(accessor), + InterfacesFor: interfacesFor, + } + + if err := registered.RegisterGroup(groupMeta); err != nil { + return err + } + api.RegisterRESTMapper(groupMeta.RESTMapper) + return nil +} + +func newRESTMapper(externalVersions []unversioned.GroupVersion) meta.RESTMapper { + // the list of kinds that are scoped at the root of the api hierarchy + // if a kind is not enumerated here, it is assumed to have a namespace scope + rootScoped := sets.NewString() + + ignoredKinds := sets.NewString() + + return api.NewDefaultRESTMapper(externalVersions, interfacesFor, importPrefix, ignoredKinds, rootScoped) +} + +// interfacesFor returns the default Codec and ResourceVersioner for a given version +// string, or an error if the version is not known. +func interfacesFor(version unversioned.GroupVersion) (*meta.VersionInterfaces, error) { + switch version { + case v1.SchemeGroupVersion: + return &meta.VersionInterfaces{ + ObjectConvertor: api.Scheme, + MetadataAccessor: accessor, + }, nil + default: + g, _ := registered.Group(autoscaling.GroupName) + return nil, fmt.Errorf("unsupported storage version: %s (valid: %v)", version, g.GroupVersions) + } +} + +func addVersionsToScheme(externalVersions ...unversioned.GroupVersion) { + // add the internal version to Scheme + autoscaling.AddToScheme(api.Scheme) + // add the enabled external versions to Scheme + for _, v := range externalVersions { + if !registered.IsEnabledVersion(v) { + glog.Errorf("Version %s is not enabled, so it will not be added to the Scheme.", v) + continue + } + switch v { + case v1.SchemeGroupVersion: + v1.AddToScheme(api.Scheme) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/register.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/register.go new file mode 100644 index 000000000..dfc86f24d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/register.go @@ -0,0 +1,57 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 autoscaling + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/runtime" +) + +// GroupName is the group name use in this package +const GroupName = "autoscaling" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = unversioned.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind +func Kind(kind string) unversioned.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns back a Group qualified GroupResource +func Resource(resource string) unversioned.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +func AddToScheme(scheme *runtime.Scheme) { + // Add the API to Scheme. + addKnownTypes(scheme) +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) { + scheme.AddKnownTypes(SchemeGroupVersion, + &Scale{}, + &extensions.HorizontalPodAutoscaler{}, + &extensions.HorizontalPodAutoscalerList{}, + &api.ListOptions{}, + ) +} + +func (obj *Scale) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/types.generated.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/types.generated.go new file mode 100644 index 000000000..7302ebca6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/types.generated.go @@ -0,0 +1,794 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// ************************************************************ +// DO NOT EDIT. +// THIS FILE IS AUTO-GENERATED BY codecgen. +// ************************************************************ + +package autoscaling + +import ( + "errors" + "fmt" + codec1978 "github.com/ugorji/go/codec" + pkg2_api "k8s.io/kubernetes/pkg/api" + pkg1_unversioned "k8s.io/kubernetes/pkg/api/unversioned" + pkg3_types "k8s.io/kubernetes/pkg/types" + "reflect" + "runtime" + time "time" +) + +const ( + // ----- content types ---- + codecSelferC_UTF81234 = 1 + codecSelferC_RAW1234 = 0 + // ----- value types used ---- + codecSelferValueTypeArray1234 = 10 + codecSelferValueTypeMap1234 = 9 + // ----- containerStateValues ---- + codecSelfer_containerMapKey1234 = 2 + codecSelfer_containerMapValue1234 = 3 + codecSelfer_containerMapEnd1234 = 4 + codecSelfer_containerArrayElem1234 = 6 + codecSelfer_containerArrayEnd1234 = 7 +) + +var ( + codecSelferBitsize1234 = uint8(reflect.TypeOf(uint(0)).Bits()) + codecSelferOnlyMapOrArrayEncodeToStructErr1234 = errors.New(`only encoded map or array can be decoded into a struct`) +) + +type codecSelfer1234 struct{} + +func init() { + if codec1978.GenVersion != 5 { + _, file, _, _ := runtime.Caller(0) + err := fmt.Errorf("codecgen version mismatch: current: %v, need %v. Re-generate file: %v", + 5, codec1978.GenVersion, file) + panic(err) + } + if false { // reference the types, but skip this branch at build/run time + var v0 pkg2_api.ObjectMeta + var v1 pkg1_unversioned.TypeMeta + var v2 pkg3_types.UID + var v3 time.Time + _, _, _, _ = v0, v1, v2, v3 + } +} + +func (x *Scale) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [5]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = true + yyq2[1] = true + yyq2[2] = true + yyq2[3] = x.Kind != "" + yyq2[4] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(5) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + yy4 := &x.ObjectMeta + yy4.CodecEncodeSelf(e) + } else { + r.EncodeNil() + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("metadata")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy6 := &x.ObjectMeta + yy6.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + yy9 := &x.Spec + yy9.CodecEncodeSelf(e) + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("spec")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy11 := &x.Spec + yy11.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yy14 := &x.Status + yy14.CodecEncodeSelf(e) + } else { + r.EncodeNil() + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("status")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy16 := &x.Status + yy16.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym19 := z.EncBinary() + _ = yym19 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym20 := z.EncBinary() + _ = yym20 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[4] { + yym22 := z.EncBinary() + _ = yym22 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym23 := z.EncBinary() + _ = yym23 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *Scale) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *Scale) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "metadata": + if r.TryDecodeAsNil() { + x.ObjectMeta = pkg2_api.ObjectMeta{} + } else { + yyv4 := &x.ObjectMeta + yyv4.CodecDecodeSelf(d) + } + case "spec": + if r.TryDecodeAsNil() { + x.Spec = ScaleSpec{} + } else { + yyv5 := &x.Spec + yyv5.CodecDecodeSelf(d) + } + case "status": + if r.TryDecodeAsNil() { + x.Status = ScaleStatus{} + } else { + yyv6 := &x.Status + yyv6.CodecDecodeSelf(d) + } + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *Scale) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj9 int + var yyb9 bool + var yyhl9 bool = l >= 0 + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ObjectMeta = pkg2_api.ObjectMeta{} + } else { + yyv10 := &x.ObjectMeta + yyv10.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Spec = ScaleSpec{} + } else { + yyv11 := &x.Spec + yyv11.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Status = ScaleStatus{} + } else { + yyv12 := &x.Status + yyv12.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj9-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *ScaleSpec) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [1]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.Replicas != 0 + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(1) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeInt(int64(x.Replicas)) + } + } else { + r.EncodeInt(0) + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("replicas")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeInt(int64(x.Replicas)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *ScaleSpec) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *ScaleSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "replicas": + if r.TryDecodeAsNil() { + x.Replicas = 0 + } else { + x.Replicas = int(r.DecodeInt(codecSelferBitsize1234)) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *ScaleSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj5 int + var yyb5 bool + var yyhl5 bool = l >= 0 + yyj5++ + if yyhl5 { + yyb5 = yyj5 > l + } else { + yyb5 = r.CheckBreak() + } + if yyb5 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Replicas = 0 + } else { + x.Replicas = int(r.DecodeInt(codecSelferBitsize1234)) + } + for { + yyj5++ + if yyhl5 { + yyb5 = yyj5 > l + } else { + yyb5 = r.CheckBreak() + } + if yyb5 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj5-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *ScaleStatus) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [2]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[1] = x.Selector != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(2) + } else { + yynn2 = 1 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeInt(int64(x.Replicas)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("replicas")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeInt(int64(x.Replicas)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Selector)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("selector")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Selector)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *ScaleStatus) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *ScaleStatus) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "replicas": + if r.TryDecodeAsNil() { + x.Replicas = 0 + } else { + x.Replicas = int(r.DecodeInt(codecSelferBitsize1234)) + } + case "selector": + if r.TryDecodeAsNil() { + x.Selector = "" + } else { + x.Selector = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *ScaleStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj6 int + var yyb6 bool + var yyhl6 bool = l >= 0 + yyj6++ + if yyhl6 { + yyb6 = yyj6 > l + } else { + yyb6 = r.CheckBreak() + } + if yyb6 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Replicas = 0 + } else { + x.Replicas = int(r.DecodeInt(codecSelferBitsize1234)) + } + yyj6++ + if yyhl6 { + yyb6 = yyj6 > l + } else { + yyb6 = r.CheckBreak() + } + if yyb6 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Selector = "" + } else { + x.Selector = string(r.DecodeString()) + } + for { + yyj6++ + if yyhl6 { + yyb6 = yyj6 > l + } else { + yyb6 = r.CheckBreak() + } + if yyb6 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj6-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/types.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/types.go new file mode 100644 index 000000000..a521feefb --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/types.go @@ -0,0 +1,53 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 autoscaling + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" +) + +// Scale represents a scaling request for a resource. +type Scale struct { + unversioned.TypeMeta `json:",inline"` + // Standard object metadata; More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata. + api.ObjectMeta `json:"metadata,omitempty"` + + // defines the behavior of the scale. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status. + Spec ScaleSpec `json:"spec,omitempty"` + + // current status of the scale. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status. Read-only. + Status ScaleStatus `json:"status,omitempty"` +} + +// ScaleSpec describes the attributes of a scale subresource. +type ScaleSpec struct { + // desired number of instances for the scaled object. + Replicas int `json:"replicas,omitempty"` +} + +// ScaleStatus represents the current status of a scale subresource. +type ScaleStatus struct { + // actual number of observed instances of the scaled object. + Replicas int `json:"replicas"` + + // label query over pods that should match the replicas count. This is same + // as the label selector but in the string format to avoid introspection + // by clients. The string will be in the same format as the query-param syntax. + // More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors + Selector string `json:"selector,omitempty"` +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/conversion.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/conversion.go new file mode 100644 index 000000000..286ce3fe8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/conversion.go @@ -0,0 +1,101 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "reflect" + + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/conversion" + "k8s.io/kubernetes/pkg/runtime" +) + +func addConversionFuncs(scheme *runtime.Scheme) { + // Add non-generated conversion functions + err := scheme.AddConversionFuncs( + Convert_extensions_SubresourceReference_To_v1_CrossVersionObjectReference, + Convert_v1_CrossVersionObjectReference_To_extensions_SubresourceReference, + Convert_extensions_HorizontalPodAutoscalerSpec_To_v1_HorizontalPodAutoscalerSpec, + Convert_v1_HorizontalPodAutoscalerSpec_To_extensions_HorizontalPodAutoscalerSpec, + ) + if err != nil { + // If one of the conversion functions is malformed, detect it immediately. + panic(err) + } +} + +func Convert_extensions_SubresourceReference_To_v1_CrossVersionObjectReference(in *extensions.SubresourceReference, out *CrossVersionObjectReference, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*extensions.SubresourceReference))(in) + } + out.Kind = in.Kind + out.Name = in.Name + out.APIVersion = in.APIVersion + return nil +} + +func Convert_v1_CrossVersionObjectReference_To_extensions_SubresourceReference(in *CrossVersionObjectReference, out *extensions.SubresourceReference, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*CrossVersionObjectReference))(in) + } + out.Kind = in.Kind + out.Name = in.Name + out.APIVersion = in.APIVersion + out.Subresource = "scale" + return nil +} + +func Convert_extensions_HorizontalPodAutoscalerSpec_To_v1_HorizontalPodAutoscalerSpec(in *extensions.HorizontalPodAutoscalerSpec, out *HorizontalPodAutoscalerSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*extensions.HorizontalPodAutoscalerSpec))(in) + } + if err := Convert_extensions_SubresourceReference_To_v1_CrossVersionObjectReference(&in.ScaleRef, &out.ScaleTargetRef, s); err != nil { + return err + } + if in.MinReplicas != nil { + out.MinReplicas = new(int32) + *out.MinReplicas = int32(*in.MinReplicas) + } else { + out.MinReplicas = nil + } + out.MaxReplicas = int32(in.MaxReplicas) + if in.CPUUtilization != nil { + out.TargetCPUUtilizationPercentage = new(int32) + *out.TargetCPUUtilizationPercentage = int32(in.CPUUtilization.TargetPercentage) + } + return nil +} + +func Convert_v1_HorizontalPodAutoscalerSpec_To_extensions_HorizontalPodAutoscalerSpec(in *HorizontalPodAutoscalerSpec, out *extensions.HorizontalPodAutoscalerSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*HorizontalPodAutoscalerSpec))(in) + } + if err := Convert_v1_CrossVersionObjectReference_To_extensions_SubresourceReference(&in.ScaleTargetRef, &out.ScaleRef, s); err != nil { + return err + } + if in.MinReplicas != nil { + out.MinReplicas = new(int) + *out.MinReplicas = int(*in.MinReplicas) + } else { + out.MinReplicas = nil + } + out.MaxReplicas = int(in.MaxReplicas) + if in.TargetCPUUtilizationPercentage != nil { + out.CPUUtilization = &extensions.CPUTargetUtilization{TargetPercentage: int(*in.TargetCPUUtilizationPercentage)} + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/conversion_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/conversion_generated.go new file mode 100644 index 000000000..f55d38fd9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/conversion_generated.go @@ -0,0 +1,455 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// DO NOT EDIT. THIS FILE IS AUTO-GENERATED BY $KUBEROOT/hack/update-generated-conversions.sh + +package v1 + +import ( + reflect "reflect" + + api "k8s.io/kubernetes/pkg/api" + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + v1 "k8s.io/kubernetes/pkg/api/v1" + autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + conversion "k8s.io/kubernetes/pkg/conversion" +) + +func autoConvert_api_ObjectMeta_To_v1_ObjectMeta(in *api.ObjectMeta, out *v1.ObjectMeta, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ObjectMeta))(in) + } + out.Name = in.Name + out.GenerateName = in.GenerateName + out.Namespace = in.Namespace + out.SelfLink = in.SelfLink + out.UID = in.UID + out.ResourceVersion = in.ResourceVersion + out.Generation = in.Generation + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.CreationTimestamp, &out.CreationTimestamp, s); err != nil { + return err + } + // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time + if in.DeletionTimestamp != nil { + out.DeletionTimestamp = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(in.DeletionTimestamp, out.DeletionTimestamp, s); err != nil { + return err + } + } else { + out.DeletionTimestamp = nil + } + if in.DeletionGracePeriodSeconds != nil { + out.DeletionGracePeriodSeconds = new(int64) + *out.DeletionGracePeriodSeconds = *in.DeletionGracePeriodSeconds + } else { + out.DeletionGracePeriodSeconds = nil + } + if in.Labels != nil { + out.Labels = make(map[string]string) + for key, val := range in.Labels { + out.Labels[key] = val + } + } else { + out.Labels = nil + } + if in.Annotations != nil { + out.Annotations = make(map[string]string) + for key, val := range in.Annotations { + out.Annotations[key] = val + } + } else { + out.Annotations = nil + } + return nil +} + +func Convert_api_ObjectMeta_To_v1_ObjectMeta(in *api.ObjectMeta, out *v1.ObjectMeta, s conversion.Scope) error { + return autoConvert_api_ObjectMeta_To_v1_ObjectMeta(in, out, s) +} + +func autoConvert_v1_ObjectMeta_To_api_ObjectMeta(in *v1.ObjectMeta, out *api.ObjectMeta, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.ObjectMeta))(in) + } + out.Name = in.Name + out.GenerateName = in.GenerateName + out.Namespace = in.Namespace + out.SelfLink = in.SelfLink + out.UID = in.UID + out.ResourceVersion = in.ResourceVersion + out.Generation = in.Generation + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.CreationTimestamp, &out.CreationTimestamp, s); err != nil { + return err + } + // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time + if in.DeletionTimestamp != nil { + out.DeletionTimestamp = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(in.DeletionTimestamp, out.DeletionTimestamp, s); err != nil { + return err + } + } else { + out.DeletionTimestamp = nil + } + if in.DeletionGracePeriodSeconds != nil { + out.DeletionGracePeriodSeconds = new(int64) + *out.DeletionGracePeriodSeconds = *in.DeletionGracePeriodSeconds + } else { + out.DeletionGracePeriodSeconds = nil + } + if in.Labels != nil { + out.Labels = make(map[string]string) + for key, val := range in.Labels { + out.Labels[key] = val + } + } else { + out.Labels = nil + } + if in.Annotations != nil { + out.Annotations = make(map[string]string) + for key, val := range in.Annotations { + out.Annotations[key] = val + } + } else { + out.Annotations = nil + } + return nil +} + +func Convert_v1_ObjectMeta_To_api_ObjectMeta(in *v1.ObjectMeta, out *api.ObjectMeta, s conversion.Scope) error { + return autoConvert_v1_ObjectMeta_To_api_ObjectMeta(in, out, s) +} + +func autoConvert_autoscaling_Scale_To_v1_Scale(in *autoscaling.Scale, out *Scale, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*autoscaling.Scale))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_autoscaling_ScaleSpec_To_v1_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_autoscaling_ScaleStatus_To_v1_ScaleStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_autoscaling_Scale_To_v1_Scale(in *autoscaling.Scale, out *Scale, s conversion.Scope) error { + return autoConvert_autoscaling_Scale_To_v1_Scale(in, out, s) +} + +func autoConvert_autoscaling_ScaleSpec_To_v1_ScaleSpec(in *autoscaling.ScaleSpec, out *ScaleSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*autoscaling.ScaleSpec))(in) + } + out.Replicas = int32(in.Replicas) + return nil +} + +func Convert_autoscaling_ScaleSpec_To_v1_ScaleSpec(in *autoscaling.ScaleSpec, out *ScaleSpec, s conversion.Scope) error { + return autoConvert_autoscaling_ScaleSpec_To_v1_ScaleSpec(in, out, s) +} + +func autoConvert_autoscaling_ScaleStatus_To_v1_ScaleStatus(in *autoscaling.ScaleStatus, out *ScaleStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*autoscaling.ScaleStatus))(in) + } + out.Replicas = int32(in.Replicas) + out.Selector = in.Selector + return nil +} + +func Convert_autoscaling_ScaleStatus_To_v1_ScaleStatus(in *autoscaling.ScaleStatus, out *ScaleStatus, s conversion.Scope) error { + return autoConvert_autoscaling_ScaleStatus_To_v1_ScaleStatus(in, out, s) +} + +func autoConvert_v1_HorizontalPodAutoscaler_To_extensions_HorizontalPodAutoscaler(in *HorizontalPodAutoscaler, out *extensions.HorizontalPodAutoscaler, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*HorizontalPodAutoscaler))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_v1_HorizontalPodAutoscalerSpec_To_extensions_HorizontalPodAutoscalerSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_HorizontalPodAutoscalerStatus_To_extensions_HorizontalPodAutoscalerStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_v1_HorizontalPodAutoscaler_To_extensions_HorizontalPodAutoscaler(in *HorizontalPodAutoscaler, out *extensions.HorizontalPodAutoscaler, s conversion.Scope) error { + return autoConvert_v1_HorizontalPodAutoscaler_To_extensions_HorizontalPodAutoscaler(in, out, s) +} + +func autoConvert_v1_HorizontalPodAutoscalerList_To_extensions_HorizontalPodAutoscalerList(in *HorizontalPodAutoscalerList, out *extensions.HorizontalPodAutoscalerList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*HorizontalPodAutoscalerList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + out.Items = make([]extensions.HorizontalPodAutoscaler, len(in.Items)) + for i := range in.Items { + if err := Convert_v1_HorizontalPodAutoscaler_To_extensions_HorizontalPodAutoscaler(&in.Items[i], &out.Items[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_v1_HorizontalPodAutoscalerList_To_extensions_HorizontalPodAutoscalerList(in *HorizontalPodAutoscalerList, out *extensions.HorizontalPodAutoscalerList, s conversion.Scope) error { + return autoConvert_v1_HorizontalPodAutoscalerList_To_extensions_HorizontalPodAutoscalerList(in, out, s) +} + +func autoConvert_v1_HorizontalPodAutoscalerSpec_To_extensions_HorizontalPodAutoscalerSpec(in *HorizontalPodAutoscalerSpec, out *extensions.HorizontalPodAutoscalerSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*HorizontalPodAutoscalerSpec))(in) + } + // in.ScaleTargetRef has no peer in out + if in.MinReplicas != nil { + out.MinReplicas = new(int) + *out.MinReplicas = int(*in.MinReplicas) + } else { + out.MinReplicas = nil + } + out.MaxReplicas = int(in.MaxReplicas) + // in.TargetCPUUtilizationPercentage has no peer in out + return nil +} + +func autoConvert_v1_HorizontalPodAutoscalerStatus_To_extensions_HorizontalPodAutoscalerStatus(in *HorizontalPodAutoscalerStatus, out *extensions.HorizontalPodAutoscalerStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*HorizontalPodAutoscalerStatus))(in) + } + if in.ObservedGeneration != nil { + out.ObservedGeneration = new(int64) + *out.ObservedGeneration = *in.ObservedGeneration + } else { + out.ObservedGeneration = nil + } + // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time + if in.LastScaleTime != nil { + out.LastScaleTime = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(in.LastScaleTime, out.LastScaleTime, s); err != nil { + return err + } + } else { + out.LastScaleTime = nil + } + out.CurrentReplicas = int(in.CurrentReplicas) + out.DesiredReplicas = int(in.DesiredReplicas) + if in.CurrentCPUUtilizationPercentage != nil { + out.CurrentCPUUtilizationPercentage = new(int) + *out.CurrentCPUUtilizationPercentage = int(*in.CurrentCPUUtilizationPercentage) + } else { + out.CurrentCPUUtilizationPercentage = nil + } + return nil +} + +func Convert_v1_HorizontalPodAutoscalerStatus_To_extensions_HorizontalPodAutoscalerStatus(in *HorizontalPodAutoscalerStatus, out *extensions.HorizontalPodAutoscalerStatus, s conversion.Scope) error { + return autoConvert_v1_HorizontalPodAutoscalerStatus_To_extensions_HorizontalPodAutoscalerStatus(in, out, s) +} + +func autoConvert_v1_Scale_To_autoscaling_Scale(in *Scale, out *autoscaling.Scale, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*Scale))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_v1_ScaleSpec_To_autoscaling_ScaleSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_ScaleStatus_To_autoscaling_ScaleStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_v1_Scale_To_autoscaling_Scale(in *Scale, out *autoscaling.Scale, s conversion.Scope) error { + return autoConvert_v1_Scale_To_autoscaling_Scale(in, out, s) +} + +func autoConvert_v1_ScaleSpec_To_autoscaling_ScaleSpec(in *ScaleSpec, out *autoscaling.ScaleSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*ScaleSpec))(in) + } + out.Replicas = int(in.Replicas) + return nil +} + +func Convert_v1_ScaleSpec_To_autoscaling_ScaleSpec(in *ScaleSpec, out *autoscaling.ScaleSpec, s conversion.Scope) error { + return autoConvert_v1_ScaleSpec_To_autoscaling_ScaleSpec(in, out, s) +} + +func autoConvert_v1_ScaleStatus_To_autoscaling_ScaleStatus(in *ScaleStatus, out *autoscaling.ScaleStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*ScaleStatus))(in) + } + out.Replicas = int(in.Replicas) + out.Selector = in.Selector + return nil +} + +func Convert_v1_ScaleStatus_To_autoscaling_ScaleStatus(in *ScaleStatus, out *autoscaling.ScaleStatus, s conversion.Scope) error { + return autoConvert_v1_ScaleStatus_To_autoscaling_ScaleStatus(in, out, s) +} + +func autoConvert_extensions_HorizontalPodAutoscaler_To_v1_HorizontalPodAutoscaler(in *extensions.HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*extensions.HorizontalPodAutoscaler))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_extensions_HorizontalPodAutoscalerSpec_To_v1_HorizontalPodAutoscalerSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_extensions_HorizontalPodAutoscalerStatus_To_v1_HorizontalPodAutoscalerStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_extensions_HorizontalPodAutoscaler_To_v1_HorizontalPodAutoscaler(in *extensions.HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, s conversion.Scope) error { + return autoConvert_extensions_HorizontalPodAutoscaler_To_v1_HorizontalPodAutoscaler(in, out, s) +} + +func autoConvert_extensions_HorizontalPodAutoscalerList_To_v1_HorizontalPodAutoscalerList(in *extensions.HorizontalPodAutoscalerList, out *HorizontalPodAutoscalerList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*extensions.HorizontalPodAutoscalerList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + out.Items = make([]HorizontalPodAutoscaler, len(in.Items)) + for i := range in.Items { + if err := Convert_extensions_HorizontalPodAutoscaler_To_v1_HorizontalPodAutoscaler(&in.Items[i], &out.Items[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_extensions_HorizontalPodAutoscalerList_To_v1_HorizontalPodAutoscalerList(in *extensions.HorizontalPodAutoscalerList, out *HorizontalPodAutoscalerList, s conversion.Scope) error { + return autoConvert_extensions_HorizontalPodAutoscalerList_To_v1_HorizontalPodAutoscalerList(in, out, s) +} + +func autoConvert_extensions_HorizontalPodAutoscalerSpec_To_v1_HorizontalPodAutoscalerSpec(in *extensions.HorizontalPodAutoscalerSpec, out *HorizontalPodAutoscalerSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*extensions.HorizontalPodAutoscalerSpec))(in) + } + // in.ScaleRef has no peer in out + if in.MinReplicas != nil { + out.MinReplicas = new(int32) + *out.MinReplicas = int32(*in.MinReplicas) + } else { + out.MinReplicas = nil + } + out.MaxReplicas = int32(in.MaxReplicas) + // in.CPUUtilization has no peer in out + return nil +} + +func autoConvert_extensions_HorizontalPodAutoscalerStatus_To_v1_HorizontalPodAutoscalerStatus(in *extensions.HorizontalPodAutoscalerStatus, out *HorizontalPodAutoscalerStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*extensions.HorizontalPodAutoscalerStatus))(in) + } + if in.ObservedGeneration != nil { + out.ObservedGeneration = new(int64) + *out.ObservedGeneration = *in.ObservedGeneration + } else { + out.ObservedGeneration = nil + } + // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time + if in.LastScaleTime != nil { + out.LastScaleTime = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(in.LastScaleTime, out.LastScaleTime, s); err != nil { + return err + } + } else { + out.LastScaleTime = nil + } + out.CurrentReplicas = int32(in.CurrentReplicas) + out.DesiredReplicas = int32(in.DesiredReplicas) + if in.CurrentCPUUtilizationPercentage != nil { + out.CurrentCPUUtilizationPercentage = new(int32) + *out.CurrentCPUUtilizationPercentage = int32(*in.CurrentCPUUtilizationPercentage) + } else { + out.CurrentCPUUtilizationPercentage = nil + } + return nil +} + +func Convert_extensions_HorizontalPodAutoscalerStatus_To_v1_HorizontalPodAutoscalerStatus(in *extensions.HorizontalPodAutoscalerStatus, out *HorizontalPodAutoscalerStatus, s conversion.Scope) error { + return autoConvert_extensions_HorizontalPodAutoscalerStatus_To_v1_HorizontalPodAutoscalerStatus(in, out, s) +} + +func init() { + err := api.Scheme.AddGeneratedConversionFuncs( + autoConvert_api_ObjectMeta_To_v1_ObjectMeta, + autoConvert_autoscaling_ScaleSpec_To_v1_ScaleSpec, + autoConvert_autoscaling_ScaleStatus_To_v1_ScaleStatus, + autoConvert_autoscaling_Scale_To_v1_Scale, + autoConvert_extensions_HorizontalPodAutoscalerList_To_v1_HorizontalPodAutoscalerList, + autoConvert_extensions_HorizontalPodAutoscalerSpec_To_v1_HorizontalPodAutoscalerSpec, + autoConvert_extensions_HorizontalPodAutoscalerStatus_To_v1_HorizontalPodAutoscalerStatus, + autoConvert_extensions_HorizontalPodAutoscaler_To_v1_HorizontalPodAutoscaler, + autoConvert_v1_HorizontalPodAutoscalerList_To_extensions_HorizontalPodAutoscalerList, + autoConvert_v1_HorizontalPodAutoscalerSpec_To_extensions_HorizontalPodAutoscalerSpec, + autoConvert_v1_HorizontalPodAutoscalerStatus_To_extensions_HorizontalPodAutoscalerStatus, + autoConvert_v1_HorizontalPodAutoscaler_To_extensions_HorizontalPodAutoscaler, + autoConvert_v1_ObjectMeta_To_api_ObjectMeta, + autoConvert_v1_ScaleSpec_To_autoscaling_ScaleSpec, + autoConvert_v1_ScaleStatus_To_autoscaling_ScaleStatus, + autoConvert_v1_Scale_To_autoscaling_Scale, + ) + if err != nil { + // If one of the conversion functions is malformed, detect it immediately. + panic(err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/deep_copy_generated.go new file mode 100644 index 000000000..6932ba638 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/deep_copy_generated.go @@ -0,0 +1,166 @@ +// +build !ignore_autogenerated + +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + api_v1 "k8s.io/kubernetes/pkg/api/v1" + conversion "k8s.io/kubernetes/pkg/conversion" +) + +func init() { + if err := api.Scheme.AddGeneratedDeepCopyFuncs( + DeepCopy_v1_CrossVersionObjectReference, + DeepCopy_v1_HorizontalPodAutoscaler, + DeepCopy_v1_HorizontalPodAutoscalerList, + DeepCopy_v1_HorizontalPodAutoscalerSpec, + DeepCopy_v1_HorizontalPodAutoscalerStatus, + DeepCopy_v1_Scale, + DeepCopy_v1_ScaleSpec, + DeepCopy_v1_ScaleStatus, + ); err != nil { + // if one of the deep copy functions is malformed, detect it immediately. + panic(err) + } +} + +func DeepCopy_v1_CrossVersionObjectReference(in CrossVersionObjectReference, out *CrossVersionObjectReference, c *conversion.Cloner) error { + out.Kind = in.Kind + out.Name = in.Name + out.APIVersion = in.APIVersion + return nil +} + +func DeepCopy_v1_HorizontalPodAutoscaler(in HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api_v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := DeepCopy_v1_HorizontalPodAutoscalerSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_v1_HorizontalPodAutoscalerStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_v1_HorizontalPodAutoscalerList(in HorizontalPodAutoscalerList, out *HorizontalPodAutoscalerList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]HorizontalPodAutoscaler, len(in)) + for i := range in { + if err := DeepCopy_v1_HorizontalPodAutoscaler(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func DeepCopy_v1_HorizontalPodAutoscalerSpec(in HorizontalPodAutoscalerSpec, out *HorizontalPodAutoscalerSpec, c *conversion.Cloner) error { + if err := DeepCopy_v1_CrossVersionObjectReference(in.ScaleTargetRef, &out.ScaleTargetRef, c); err != nil { + return err + } + if in.MinReplicas != nil { + in, out := in.MinReplicas, &out.MinReplicas + *out = new(int32) + **out = *in + } else { + out.MinReplicas = nil + } + out.MaxReplicas = in.MaxReplicas + if in.TargetCPUUtilizationPercentage != nil { + in, out := in.TargetCPUUtilizationPercentage, &out.TargetCPUUtilizationPercentage + *out = new(int32) + **out = *in + } else { + out.TargetCPUUtilizationPercentage = nil + } + return nil +} + +func DeepCopy_v1_HorizontalPodAutoscalerStatus(in HorizontalPodAutoscalerStatus, out *HorizontalPodAutoscalerStatus, c *conversion.Cloner) error { + if in.ObservedGeneration != nil { + in, out := in.ObservedGeneration, &out.ObservedGeneration + *out = new(int64) + **out = *in + } else { + out.ObservedGeneration = nil + } + if in.LastScaleTime != nil { + in, out := in.LastScaleTime, &out.LastScaleTime + *out = new(unversioned.Time) + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { + return err + } + } else { + out.LastScaleTime = nil + } + out.CurrentReplicas = in.CurrentReplicas + out.DesiredReplicas = in.DesiredReplicas + if in.CurrentCPUUtilizationPercentage != nil { + in, out := in.CurrentCPUUtilizationPercentage, &out.CurrentCPUUtilizationPercentage + *out = new(int32) + **out = *in + } else { + out.CurrentCPUUtilizationPercentage = nil + } + return nil +} + +func DeepCopy_v1_Scale(in Scale, out *Scale, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api_v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := DeepCopy_v1_ScaleSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_v1_ScaleStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_v1_ScaleSpec(in ScaleSpec, out *ScaleSpec, c *conversion.Cloner) error { + out.Replicas = in.Replicas + return nil +} + +func DeepCopy_v1_ScaleStatus(in ScaleStatus, out *ScaleStatus, c *conversion.Cloner) error { + out.Replicas = in.Replicas + out.Selector = in.Selector + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/defaults.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/defaults.go new file mode 100644 index 000000000..2b5173dcd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/defaults.go @@ -0,0 +1,32 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "k8s.io/kubernetes/pkg/runtime" +) + +func addDefaultingFuncs(scheme *runtime.Scheme) { + scheme.AddDefaultingFuncs( + func(obj *HorizontalPodAutoscaler) { + if obj.Spec.MinReplicas == nil { + minReplicas := int32(1) + obj.Spec.MinReplicas = &minReplicas + } + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/register.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/register.go new file mode 100644 index 000000000..9a7e369c6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/register.go @@ -0,0 +1,51 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/runtime" + versionedwatch "k8s.io/kubernetes/pkg/watch/versioned" +) + +// GroupName is the group name use in this package +const GroupName = "autoscaling" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = unversioned.GroupVersion{Group: GroupName, Version: "v1"} + +func AddToScheme(scheme *runtime.Scheme) { + addKnownTypes(scheme) + addDefaultingFuncs(scheme) + addConversionFuncs(scheme) +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) { + scheme.AddKnownTypes(SchemeGroupVersion, + &HorizontalPodAutoscaler{}, + &HorizontalPodAutoscalerList{}, + &Scale{}, + &v1.ListOptions{}, + ) + versionedwatch.AddToGroupVersion(scheme, SchemeGroupVersion) +} + +func (obj *HorizontalPodAutoscaler) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } +func (obj *HorizontalPodAutoscalerList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } +func (obj *Scale) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/types.generated.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/types.generated.go new file mode 100644 index 000000000..1e5a195c2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/types.generated.go @@ -0,0 +1,2659 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// ************************************************************ +// DO NOT EDIT. +// THIS FILE IS AUTO-GENERATED BY codecgen. +// ************************************************************ + +package v1 + +import ( + "errors" + "fmt" + codec1978 "github.com/ugorji/go/codec" + pkg1_unversioned "k8s.io/kubernetes/pkg/api/unversioned" + pkg2_v1 "k8s.io/kubernetes/pkg/api/v1" + pkg3_types "k8s.io/kubernetes/pkg/types" + "reflect" + "runtime" + time "time" +) + +const ( + // ----- content types ---- + codecSelferC_UTF81234 = 1 + codecSelferC_RAW1234 = 0 + // ----- value types used ---- + codecSelferValueTypeArray1234 = 10 + codecSelferValueTypeMap1234 = 9 + // ----- containerStateValues ---- + codecSelfer_containerMapKey1234 = 2 + codecSelfer_containerMapValue1234 = 3 + codecSelfer_containerMapEnd1234 = 4 + codecSelfer_containerArrayElem1234 = 6 + codecSelfer_containerArrayEnd1234 = 7 +) + +var ( + codecSelferBitsize1234 = uint8(reflect.TypeOf(uint(0)).Bits()) + codecSelferOnlyMapOrArrayEncodeToStructErr1234 = errors.New(`only encoded map or array can be decoded into a struct`) +) + +type codecSelfer1234 struct{} + +func init() { + if codec1978.GenVersion != 5 { + _, file, _, _ := runtime.Caller(0) + err := fmt.Errorf("codecgen version mismatch: current: %v, need %v. Re-generate file: %v", + 5, codec1978.GenVersion, file) + panic(err) + } + if false { // reference the types, but skip this branch at build/run time + var v0 pkg1_unversioned.Time + var v1 pkg2_v1.ObjectMeta + var v2 pkg3_types.UID + var v3 time.Time + _, _, _, _ = v0, v1, v2, v3 + } +} + +func (x *CrossVersionObjectReference) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [3]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[2] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(3) + } else { + yynn2 = 2 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Name)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("name")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Name)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym11 := z.EncBinary() + _ = yym11 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *CrossVersionObjectReference) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *CrossVersionObjectReference) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "name": + if r.TryDecodeAsNil() { + x.Name = "" + } else { + x.Name = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *CrossVersionObjectReference) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Name = "" + } else { + x.Name = string(r.DecodeString()) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj7-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *HorizontalPodAutoscalerSpec) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [4]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[1] = x.MinReplicas != nil + yyq2[3] = x.TargetCPUUtilizationPercentage != nil + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(4) + } else { + yynn2 = 2 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy4 := &x.ScaleTargetRef + yy4.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("scaleTargetRef")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy6 := &x.ScaleTargetRef + yy6.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + if x.MinReplicas == nil { + r.EncodeNil() + } else { + yy9 := *x.MinReplicas + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeInt(int64(yy9)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("minReplicas")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.MinReplicas == nil { + r.EncodeNil() + } else { + yy11 := *x.MinReplicas + yym12 := z.EncBinary() + _ = yym12 + if false { + } else { + r.EncodeInt(int64(yy11)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym14 := z.EncBinary() + _ = yym14 + if false { + } else { + r.EncodeInt(int64(x.MaxReplicas)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("maxReplicas")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym15 := z.EncBinary() + _ = yym15 + if false { + } else { + r.EncodeInt(int64(x.MaxReplicas)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + if x.TargetCPUUtilizationPercentage == nil { + r.EncodeNil() + } else { + yy17 := *x.TargetCPUUtilizationPercentage + yym18 := z.EncBinary() + _ = yym18 + if false { + } else { + r.EncodeInt(int64(yy17)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("targetCPUUtilizationPercentage")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.TargetCPUUtilizationPercentage == nil { + r.EncodeNil() + } else { + yy19 := *x.TargetCPUUtilizationPercentage + yym20 := z.EncBinary() + _ = yym20 + if false { + } else { + r.EncodeInt(int64(yy19)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *HorizontalPodAutoscalerSpec) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *HorizontalPodAutoscalerSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "scaleTargetRef": + if r.TryDecodeAsNil() { + x.ScaleTargetRef = CrossVersionObjectReference{} + } else { + yyv4 := &x.ScaleTargetRef + yyv4.CodecDecodeSelf(d) + } + case "minReplicas": + if r.TryDecodeAsNil() { + if x.MinReplicas != nil { + x.MinReplicas = nil + } + } else { + if x.MinReplicas == nil { + x.MinReplicas = new(int32) + } + yym6 := z.DecBinary() + _ = yym6 + if false { + } else { + *((*int32)(x.MinReplicas)) = int32(r.DecodeInt(32)) + } + } + case "maxReplicas": + if r.TryDecodeAsNil() { + x.MaxReplicas = 0 + } else { + x.MaxReplicas = int32(r.DecodeInt(32)) + } + case "targetCPUUtilizationPercentage": + if r.TryDecodeAsNil() { + if x.TargetCPUUtilizationPercentage != nil { + x.TargetCPUUtilizationPercentage = nil + } + } else { + if x.TargetCPUUtilizationPercentage == nil { + x.TargetCPUUtilizationPercentage = new(int32) + } + yym9 := z.DecBinary() + _ = yym9 + if false { + } else { + *((*int32)(x.TargetCPUUtilizationPercentage)) = int32(r.DecodeInt(32)) + } + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *HorizontalPodAutoscalerSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj10 int + var yyb10 bool + var yyhl10 bool = l >= 0 + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ScaleTargetRef = CrossVersionObjectReference{} + } else { + yyv11 := &x.ScaleTargetRef + yyv11.CodecDecodeSelf(d) + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.MinReplicas != nil { + x.MinReplicas = nil + } + } else { + if x.MinReplicas == nil { + x.MinReplicas = new(int32) + } + yym13 := z.DecBinary() + _ = yym13 + if false { + } else { + *((*int32)(x.MinReplicas)) = int32(r.DecodeInt(32)) + } + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.MaxReplicas = 0 + } else { + x.MaxReplicas = int32(r.DecodeInt(32)) + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.TargetCPUUtilizationPercentage != nil { + x.TargetCPUUtilizationPercentage = nil + } + } else { + if x.TargetCPUUtilizationPercentage == nil { + x.TargetCPUUtilizationPercentage = new(int32) + } + yym16 := z.DecBinary() + _ = yym16 + if false { + } else { + *((*int32)(x.TargetCPUUtilizationPercentage)) = int32(r.DecodeInt(32)) + } + } + for { + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj10-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *HorizontalPodAutoscalerStatus) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [5]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.ObservedGeneration != nil + yyq2[1] = x.LastScaleTime != nil + yyq2[4] = x.CurrentCPUUtilizationPercentage != nil + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(5) + } else { + yynn2 = 2 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + if x.ObservedGeneration == nil { + r.EncodeNil() + } else { + yy4 := *x.ObservedGeneration + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeInt(int64(yy4)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("observedGeneration")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.ObservedGeneration == nil { + r.EncodeNil() + } else { + yy6 := *x.ObservedGeneration + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeInt(int64(yy6)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + if x.LastScaleTime == nil { + r.EncodeNil() + } else { + yym9 := z.EncBinary() + _ = yym9 + if false { + } else if z.HasExtensions() && z.EncExt(x.LastScaleTime) { + } else if yym9 { + z.EncBinaryMarshal(x.LastScaleTime) + } else if !yym9 && z.IsJSONHandle() { + z.EncJSONMarshal(x.LastScaleTime) + } else { + z.EncFallback(x.LastScaleTime) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("lastScaleTime")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.LastScaleTime == nil { + r.EncodeNil() + } else { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else if z.HasExtensions() && z.EncExt(x.LastScaleTime) { + } else if yym10 { + z.EncBinaryMarshal(x.LastScaleTime) + } else if !yym10 && z.IsJSONHandle() { + z.EncJSONMarshal(x.LastScaleTime) + } else { + z.EncFallback(x.LastScaleTime) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym12 := z.EncBinary() + _ = yym12 + if false { + } else { + r.EncodeInt(int64(x.CurrentReplicas)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("currentReplicas")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym13 := z.EncBinary() + _ = yym13 + if false { + } else { + r.EncodeInt(int64(x.CurrentReplicas)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym15 := z.EncBinary() + _ = yym15 + if false { + } else { + r.EncodeInt(int64(x.DesiredReplicas)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("desiredReplicas")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym16 := z.EncBinary() + _ = yym16 + if false { + } else { + r.EncodeInt(int64(x.DesiredReplicas)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[4] { + if x.CurrentCPUUtilizationPercentage == nil { + r.EncodeNil() + } else { + yy18 := *x.CurrentCPUUtilizationPercentage + yym19 := z.EncBinary() + _ = yym19 + if false { + } else { + r.EncodeInt(int64(yy18)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("currentCPUUtilizationPercentage")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.CurrentCPUUtilizationPercentage == nil { + r.EncodeNil() + } else { + yy20 := *x.CurrentCPUUtilizationPercentage + yym21 := z.EncBinary() + _ = yym21 + if false { + } else { + r.EncodeInt(int64(yy20)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *HorizontalPodAutoscalerStatus) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *HorizontalPodAutoscalerStatus) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "observedGeneration": + if r.TryDecodeAsNil() { + if x.ObservedGeneration != nil { + x.ObservedGeneration = nil + } + } else { + if x.ObservedGeneration == nil { + x.ObservedGeneration = new(int64) + } + yym5 := z.DecBinary() + _ = yym5 + if false { + } else { + *((*int64)(x.ObservedGeneration)) = int64(r.DecodeInt(64)) + } + } + case "lastScaleTime": + if r.TryDecodeAsNil() { + if x.LastScaleTime != nil { + x.LastScaleTime = nil + } + } else { + if x.LastScaleTime == nil { + x.LastScaleTime = new(pkg1_unversioned.Time) + } + yym7 := z.DecBinary() + _ = yym7 + if false { + } else if z.HasExtensions() && z.DecExt(x.LastScaleTime) { + } else if yym7 { + z.DecBinaryUnmarshal(x.LastScaleTime) + } else if !yym7 && z.IsJSONHandle() { + z.DecJSONUnmarshal(x.LastScaleTime) + } else { + z.DecFallback(x.LastScaleTime, false) + } + } + case "currentReplicas": + if r.TryDecodeAsNil() { + x.CurrentReplicas = 0 + } else { + x.CurrentReplicas = int32(r.DecodeInt(32)) + } + case "desiredReplicas": + if r.TryDecodeAsNil() { + x.DesiredReplicas = 0 + } else { + x.DesiredReplicas = int32(r.DecodeInt(32)) + } + case "currentCPUUtilizationPercentage": + if r.TryDecodeAsNil() { + if x.CurrentCPUUtilizationPercentage != nil { + x.CurrentCPUUtilizationPercentage = nil + } + } else { + if x.CurrentCPUUtilizationPercentage == nil { + x.CurrentCPUUtilizationPercentage = new(int32) + } + yym11 := z.DecBinary() + _ = yym11 + if false { + } else { + *((*int32)(x.CurrentCPUUtilizationPercentage)) = int32(r.DecodeInt(32)) + } + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *HorizontalPodAutoscalerStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj12 int + var yyb12 bool + var yyhl12 bool = l >= 0 + yyj12++ + if yyhl12 { + yyb12 = yyj12 > l + } else { + yyb12 = r.CheckBreak() + } + if yyb12 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.ObservedGeneration != nil { + x.ObservedGeneration = nil + } + } else { + if x.ObservedGeneration == nil { + x.ObservedGeneration = new(int64) + } + yym14 := z.DecBinary() + _ = yym14 + if false { + } else { + *((*int64)(x.ObservedGeneration)) = int64(r.DecodeInt(64)) + } + } + yyj12++ + if yyhl12 { + yyb12 = yyj12 > l + } else { + yyb12 = r.CheckBreak() + } + if yyb12 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.LastScaleTime != nil { + x.LastScaleTime = nil + } + } else { + if x.LastScaleTime == nil { + x.LastScaleTime = new(pkg1_unversioned.Time) + } + yym16 := z.DecBinary() + _ = yym16 + if false { + } else if z.HasExtensions() && z.DecExt(x.LastScaleTime) { + } else if yym16 { + z.DecBinaryUnmarshal(x.LastScaleTime) + } else if !yym16 && z.IsJSONHandle() { + z.DecJSONUnmarshal(x.LastScaleTime) + } else { + z.DecFallback(x.LastScaleTime, false) + } + } + yyj12++ + if yyhl12 { + yyb12 = yyj12 > l + } else { + yyb12 = r.CheckBreak() + } + if yyb12 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.CurrentReplicas = 0 + } else { + x.CurrentReplicas = int32(r.DecodeInt(32)) + } + yyj12++ + if yyhl12 { + yyb12 = yyj12 > l + } else { + yyb12 = r.CheckBreak() + } + if yyb12 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.DesiredReplicas = 0 + } else { + x.DesiredReplicas = int32(r.DecodeInt(32)) + } + yyj12++ + if yyhl12 { + yyb12 = yyj12 > l + } else { + yyb12 = r.CheckBreak() + } + if yyb12 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.CurrentCPUUtilizationPercentage != nil { + x.CurrentCPUUtilizationPercentage = nil + } + } else { + if x.CurrentCPUUtilizationPercentage == nil { + x.CurrentCPUUtilizationPercentage = new(int32) + } + yym20 := z.DecBinary() + _ = yym20 + if false { + } else { + *((*int32)(x.CurrentCPUUtilizationPercentage)) = int32(r.DecodeInt(32)) + } + } + for { + yyj12++ + if yyhl12 { + yyb12 = yyj12 > l + } else { + yyb12 = r.CheckBreak() + } + if yyb12 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj12-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *HorizontalPodAutoscaler) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [5]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = true + yyq2[1] = true + yyq2[2] = true + yyq2[3] = x.Kind != "" + yyq2[4] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(5) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + yy4 := &x.ObjectMeta + yy4.CodecEncodeSelf(e) + } else { + r.EncodeNil() + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("metadata")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy6 := &x.ObjectMeta + yy6.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + yy9 := &x.Spec + yy9.CodecEncodeSelf(e) + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("spec")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy11 := &x.Spec + yy11.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yy14 := &x.Status + yy14.CodecEncodeSelf(e) + } else { + r.EncodeNil() + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("status")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy16 := &x.Status + yy16.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym19 := z.EncBinary() + _ = yym19 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym20 := z.EncBinary() + _ = yym20 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[4] { + yym22 := z.EncBinary() + _ = yym22 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym23 := z.EncBinary() + _ = yym23 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *HorizontalPodAutoscaler) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *HorizontalPodAutoscaler) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "metadata": + if r.TryDecodeAsNil() { + x.ObjectMeta = pkg2_v1.ObjectMeta{} + } else { + yyv4 := &x.ObjectMeta + yyv4.CodecDecodeSelf(d) + } + case "spec": + if r.TryDecodeAsNil() { + x.Spec = HorizontalPodAutoscalerSpec{} + } else { + yyv5 := &x.Spec + yyv5.CodecDecodeSelf(d) + } + case "status": + if r.TryDecodeAsNil() { + x.Status = HorizontalPodAutoscalerStatus{} + } else { + yyv6 := &x.Status + yyv6.CodecDecodeSelf(d) + } + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *HorizontalPodAutoscaler) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj9 int + var yyb9 bool + var yyhl9 bool = l >= 0 + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ObjectMeta = pkg2_v1.ObjectMeta{} + } else { + yyv10 := &x.ObjectMeta + yyv10.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Spec = HorizontalPodAutoscalerSpec{} + } else { + yyv11 := &x.Spec + yyv11.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Status = HorizontalPodAutoscalerStatus{} + } else { + yyv12 := &x.Status + yyv12.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj9-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *HorizontalPodAutoscalerList) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [4]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = true + yyq2[2] = x.Kind != "" + yyq2[3] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(4) + } else { + yynn2 = 1 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + yy4 := &x.ListMeta + yym5 := z.EncBinary() + _ = yym5 + if false { + } else if z.HasExtensions() && z.EncExt(yy4) { + } else { + z.EncFallback(yy4) + } + } else { + r.EncodeNil() + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("metadata")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy6 := &x.ListMeta + yym7 := z.EncBinary() + _ = yym7 + if false { + } else if z.HasExtensions() && z.EncExt(yy6) { + } else { + z.EncFallback(yy6) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if x.Items == nil { + r.EncodeNil() + } else { + yym9 := z.EncBinary() + _ = yym9 + if false { + } else { + h.encSliceHorizontalPodAutoscaler(([]HorizontalPodAutoscaler)(x.Items), e) + } + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("items")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Items == nil { + r.EncodeNil() + } else { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + h.encSliceHorizontalPodAutoscaler(([]HorizontalPodAutoscaler)(x.Items), e) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym12 := z.EncBinary() + _ = yym12 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym13 := z.EncBinary() + _ = yym13 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym15 := z.EncBinary() + _ = yym15 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym16 := z.EncBinary() + _ = yym16 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *HorizontalPodAutoscalerList) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *HorizontalPodAutoscalerList) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "metadata": + if r.TryDecodeAsNil() { + x.ListMeta = pkg1_unversioned.ListMeta{} + } else { + yyv4 := &x.ListMeta + yym5 := z.DecBinary() + _ = yym5 + if false { + } else if z.HasExtensions() && z.DecExt(yyv4) { + } else { + z.DecFallback(yyv4, false) + } + } + case "items": + if r.TryDecodeAsNil() { + x.Items = nil + } else { + yyv6 := &x.Items + yym7 := z.DecBinary() + _ = yym7 + if false { + } else { + h.decSliceHorizontalPodAutoscaler((*[]HorizontalPodAutoscaler)(yyv6), d) + } + } + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *HorizontalPodAutoscalerList) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj10 int + var yyb10 bool + var yyhl10 bool = l >= 0 + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ListMeta = pkg1_unversioned.ListMeta{} + } else { + yyv11 := &x.ListMeta + yym12 := z.DecBinary() + _ = yym12 + if false { + } else if z.HasExtensions() && z.DecExt(yyv11) { + } else { + z.DecFallback(yyv11, false) + } + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Items = nil + } else { + yyv13 := &x.Items + yym14 := z.DecBinary() + _ = yym14 + if false { + } else { + h.decSliceHorizontalPodAutoscaler((*[]HorizontalPodAutoscaler)(yyv13), d) + } + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj10-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *Scale) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [5]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = true + yyq2[1] = true + yyq2[2] = true + yyq2[3] = x.Kind != "" + yyq2[4] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(5) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + yy4 := &x.ObjectMeta + yy4.CodecEncodeSelf(e) + } else { + r.EncodeNil() + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("metadata")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy6 := &x.ObjectMeta + yy6.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + yy9 := &x.Spec + yy9.CodecEncodeSelf(e) + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("spec")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy11 := &x.Spec + yy11.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yy14 := &x.Status + yy14.CodecEncodeSelf(e) + } else { + r.EncodeNil() + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("status")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy16 := &x.Status + yy16.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym19 := z.EncBinary() + _ = yym19 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym20 := z.EncBinary() + _ = yym20 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[4] { + yym22 := z.EncBinary() + _ = yym22 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym23 := z.EncBinary() + _ = yym23 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *Scale) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *Scale) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "metadata": + if r.TryDecodeAsNil() { + x.ObjectMeta = pkg2_v1.ObjectMeta{} + } else { + yyv4 := &x.ObjectMeta + yyv4.CodecDecodeSelf(d) + } + case "spec": + if r.TryDecodeAsNil() { + x.Spec = ScaleSpec{} + } else { + yyv5 := &x.Spec + yyv5.CodecDecodeSelf(d) + } + case "status": + if r.TryDecodeAsNil() { + x.Status = ScaleStatus{} + } else { + yyv6 := &x.Status + yyv6.CodecDecodeSelf(d) + } + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *Scale) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj9 int + var yyb9 bool + var yyhl9 bool = l >= 0 + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ObjectMeta = pkg2_v1.ObjectMeta{} + } else { + yyv10 := &x.ObjectMeta + yyv10.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Spec = ScaleSpec{} + } else { + yyv11 := &x.Spec + yyv11.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Status = ScaleStatus{} + } else { + yyv12 := &x.Status + yyv12.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj9-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *ScaleSpec) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [1]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.Replicas != 0 + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(1) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeInt(int64(x.Replicas)) + } + } else { + r.EncodeInt(0) + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("replicas")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeInt(int64(x.Replicas)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *ScaleSpec) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *ScaleSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "replicas": + if r.TryDecodeAsNil() { + x.Replicas = 0 + } else { + x.Replicas = int32(r.DecodeInt(32)) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *ScaleSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj5 int + var yyb5 bool + var yyhl5 bool = l >= 0 + yyj5++ + if yyhl5 { + yyb5 = yyj5 > l + } else { + yyb5 = r.CheckBreak() + } + if yyb5 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Replicas = 0 + } else { + x.Replicas = int32(r.DecodeInt(32)) + } + for { + yyj5++ + if yyhl5 { + yyb5 = yyj5 > l + } else { + yyb5 = r.CheckBreak() + } + if yyb5 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj5-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *ScaleStatus) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [2]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[1] = x.Selector != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(2) + } else { + yynn2 = 1 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeInt(int64(x.Replicas)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("replicas")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeInt(int64(x.Replicas)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Selector)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("selector")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Selector)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *ScaleStatus) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *ScaleStatus) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "replicas": + if r.TryDecodeAsNil() { + x.Replicas = 0 + } else { + x.Replicas = int32(r.DecodeInt(32)) + } + case "selector": + if r.TryDecodeAsNil() { + x.Selector = "" + } else { + x.Selector = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *ScaleStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj6 int + var yyb6 bool + var yyhl6 bool = l >= 0 + yyj6++ + if yyhl6 { + yyb6 = yyj6 > l + } else { + yyb6 = r.CheckBreak() + } + if yyb6 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Replicas = 0 + } else { + x.Replicas = int32(r.DecodeInt(32)) + } + yyj6++ + if yyhl6 { + yyb6 = yyj6 > l + } else { + yyb6 = r.CheckBreak() + } + if yyb6 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Selector = "" + } else { + x.Selector = string(r.DecodeString()) + } + for { + yyj6++ + if yyhl6 { + yyb6 = yyj6 > l + } else { + yyb6 = r.CheckBreak() + } + if yyb6 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj6-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x codecSelfer1234) encSliceHorizontalPodAutoscaler(v []HorizontalPodAutoscaler, e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + r.EncodeArrayStart(len(v)) + for _, yyv1 := range v { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy2 := &yyv1 + yy2.CodecEncodeSelf(e) + } + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x codecSelfer1234) decSliceHorizontalPodAutoscaler(v *[]HorizontalPodAutoscaler, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + + yyv1 := *v + yyh1, yyl1 := z.DecSliceHelperStart() + var yyc1 bool + _ = yyc1 + if yyl1 == 0 { + if yyv1 == nil { + yyv1 = []HorizontalPodAutoscaler{} + yyc1 = true + } else if len(yyv1) != 0 { + yyv1 = yyv1[:0] + yyc1 = true + } + } else if yyl1 > 0 { + var yyrr1, yyrl1 int + var yyrt1 bool + _, _ = yyrl1, yyrt1 + yyrr1 = yyl1 // len(yyv1) + if yyl1 > cap(yyv1) { + + yyrg1 := len(yyv1) > 0 + yyv21 := yyv1 + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 296) + if yyrt1 { + if yyrl1 <= cap(yyv1) { + yyv1 = yyv1[:yyrl1] + } else { + yyv1 = make([]HorizontalPodAutoscaler, yyrl1) + } + } else { + yyv1 = make([]HorizontalPodAutoscaler, yyrl1) + } + yyc1 = true + yyrr1 = len(yyv1) + if yyrg1 { + copy(yyv1, yyv21) + } + } else if yyl1 != len(yyv1) { + yyv1 = yyv1[:yyl1] + yyc1 = true + } + yyj1 := 0 + for ; yyj1 < yyrr1; yyj1++ { + yyh1.ElemContainerState(yyj1) + if r.TryDecodeAsNil() { + yyv1[yyj1] = HorizontalPodAutoscaler{} + } else { + yyv2 := &yyv1[yyj1] + yyv2.CodecDecodeSelf(d) + } + + } + if yyrt1 { + for ; yyj1 < yyl1; yyj1++ { + yyv1 = append(yyv1, HorizontalPodAutoscaler{}) + yyh1.ElemContainerState(yyj1) + if r.TryDecodeAsNil() { + yyv1[yyj1] = HorizontalPodAutoscaler{} + } else { + yyv3 := &yyv1[yyj1] + yyv3.CodecDecodeSelf(d) + } + + } + } + + } else { + yyj1 := 0 + for ; !r.CheckBreak(); yyj1++ { + + if yyj1 >= len(yyv1) { + yyv1 = append(yyv1, HorizontalPodAutoscaler{}) // var yyz1 HorizontalPodAutoscaler + yyc1 = true + } + yyh1.ElemContainerState(yyj1) + if yyj1 < len(yyv1) { + if r.TryDecodeAsNil() { + yyv1[yyj1] = HorizontalPodAutoscaler{} + } else { + yyv4 := &yyv1[yyj1] + yyv4.CodecDecodeSelf(d) + } + + } else { + z.DecSwallow() + } + + } + if yyj1 < len(yyv1) { + yyv1 = yyv1[:yyj1] + yyc1 = true + } else if yyj1 == 0 && yyv1 == nil { + yyv1 = []HorizontalPodAutoscaler{} + yyc1 = true + } + } + yyh1.End() + if yyc1 { + *v = yyv1 + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/types.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/types.go new file mode 100644 index 000000000..58d886641 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/types.go @@ -0,0 +1,120 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" +) + +// CrossVersionObjectReference contains enough information to let you identify the referred resource. +type CrossVersionObjectReference struct { + // Kind of the referent; More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds" + Kind string `json:"kind"` + // Name of the referent; More info: http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#names + Name string `json:"name"` + // API version of the referent + APIVersion string `json:"apiVersion,omitempty"` +} + +// specification of a horizontal pod autoscaler. +type HorizontalPodAutoscalerSpec struct { + // reference to scaled resource; horizontal pod autoscaler will learn the current resource consumption + // and will set the desired number of pods by using its Scale subresource. + ScaleTargetRef CrossVersionObjectReference `json:"scaleTargetRef"` + // lower limit for the number of pods that can be set by the autoscaler, default 1. + MinReplicas *int32 `json:"minReplicas,omitempty"` + // upper limit for the number of pods that can be set by the autoscaler; cannot be smaller than MinReplicas. + MaxReplicas int32 `json:"maxReplicas"` + // target average CPU utilization (represented as a percentage of requested CPU) over all the pods; + // if not specified the default autoscaling policy will be used. + TargetCPUUtilizationPercentage *int32 `json:"targetCPUUtilizationPercentage,omitempty"` +} + +// current status of a horizontal pod autoscaler +type HorizontalPodAutoscalerStatus struct { + // most recent generation observed by this autoscaler. + ObservedGeneration *int64 `json:"observedGeneration,omitempty"` + + // last time the HorizontalPodAutoscaler scaled the number of pods; + // used by the autoscaler to control how often the number of pods is changed. + LastScaleTime *unversioned.Time `json:"lastScaleTime,omitempty"` + + // current number of replicas of pods managed by this autoscaler. + CurrentReplicas int32 `json:"currentReplicas"` + + // desired number of replicas of pods managed by this autoscaler. + DesiredReplicas int32 `json:"desiredReplicas"` + + // current average CPU utilization over all pods, represented as a percentage of requested CPU, + // e.g. 70 means that an average pod is using now 70% of its requested CPU. + CurrentCPUUtilizationPercentage *int32 `json:"currentCPUUtilizationPercentage,omitempty"` +} + +// configuration of a horizontal pod autoscaler. +type HorizontalPodAutoscaler struct { + unversioned.TypeMeta `json:",inline"` + // Standard object metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata + v1.ObjectMeta `json:"metadata,omitempty"` + + // behaviour of autoscaler. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status. + Spec HorizontalPodAutoscalerSpec `json:"spec,omitempty"` + + // current information about the autoscaler. + Status HorizontalPodAutoscalerStatus `json:"status,omitempty"` +} + +// list of horizontal pod autoscaler objects. +type HorizontalPodAutoscalerList struct { + unversioned.TypeMeta `json:",inline"` + // Standard list metadata. + unversioned.ListMeta `json:"metadata,omitempty"` + + // list of horizontal pod autoscaler objects. + Items []HorizontalPodAutoscaler `json:"items"` +} + +// Scale represents a scaling request for a resource. +type Scale struct { + unversioned.TypeMeta `json:",inline"` + // Standard object metadata; More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata. + v1.ObjectMeta `json:"metadata,omitempty"` + + // defines the behavior of the scale. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status. + Spec ScaleSpec `json:"spec,omitempty"` + + // current status of the scale. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status. Read-only. + Status ScaleStatus `json:"status,omitempty"` +} + +// ScaleSpec describes the attributes of a scale subresource. +type ScaleSpec struct { + // desired number of instances for the scaled object. + Replicas int32 `json:"replicas,omitempty"` +} + +// ScaleStatus represents the current status of a scale subresource. +type ScaleStatus struct { + // actual number of observed instances of the scaled object. + Replicas int32 `json:"replicas"` + + // label query over pods that should match the replicas count. This is same + // as the label selector but in the string format to avoid introspection + // by clients. The string will be in the same format as the query-param syntax. + // More info about label selectors: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors + Selector string `json:"selector,omitempty"` +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/types_swagger_doc_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/types_swagger_doc_generated.go new file mode 100644 index 000000000..56ed2bacc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/v1/types_swagger_doc_generated.go @@ -0,0 +1,117 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 v1 + +// This file contains a collection of methods that can be used from go-restful to +// generate Swagger API documentation for its models. Please read this PR for more +// information on the implementation: https://github.com/emicklei/go-restful/pull/215 +// +// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if +// they are on one line! For multiple line or blocks that you want to ignore use ---. +// Any context after a --- is ignored. +// +// Those methods can be generated by using hack/update-generated-swagger-docs.sh + +// AUTO-GENERATED FUNCTIONS START HERE +var map_CrossVersionObjectReference = map[string]string{ + "": "CrossVersionObjectReference contains enough information to let you identify the referred resource.", + "kind": "Kind of the referent; More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds\"", + "name": "Name of the referent; More info: http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#names", + "apiVersion": "API version of the referent", +} + +func (CrossVersionObjectReference) SwaggerDoc() map[string]string { + return map_CrossVersionObjectReference +} + +var map_HorizontalPodAutoscaler = map[string]string{ + "": "configuration of a horizontal pod autoscaler.", + "metadata": "Standard object metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata", + "spec": "behaviour of autoscaler. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status.", + "status": "current information about the autoscaler.", +} + +func (HorizontalPodAutoscaler) SwaggerDoc() map[string]string { + return map_HorizontalPodAutoscaler +} + +var map_HorizontalPodAutoscalerList = map[string]string{ + "": "list of horizontal pod autoscaler objects.", + "metadata": "Standard list metadata.", + "items": "list of horizontal pod autoscaler objects.", +} + +func (HorizontalPodAutoscalerList) SwaggerDoc() map[string]string { + return map_HorizontalPodAutoscalerList +} + +var map_HorizontalPodAutoscalerSpec = map[string]string{ + "": "specification of a horizontal pod autoscaler.", + "scaleTargetRef": "reference to scaled resource; horizontal pod autoscaler will learn the current resource consumption and will set the desired number of pods by using its Scale subresource.", + "minReplicas": "lower limit for the number of pods that can be set by the autoscaler, default 1.", + "maxReplicas": "upper limit for the number of pods that can be set by the autoscaler; cannot be smaller than MinReplicas.", + "targetCPUUtilizationPercentage": "target average CPU utilization (represented as a percentage of requested CPU) over all the pods; if not specified the default autoscaling policy will be used.", +} + +func (HorizontalPodAutoscalerSpec) SwaggerDoc() map[string]string { + return map_HorizontalPodAutoscalerSpec +} + +var map_HorizontalPodAutoscalerStatus = map[string]string{ + "": "current status of a horizontal pod autoscaler", + "observedGeneration": "most recent generation observed by this autoscaler.", + "lastScaleTime": "last time the HorizontalPodAutoscaler scaled the number of pods; used by the autoscaler to control how often the number of pods is changed.", + "currentReplicas": "current number of replicas of pods managed by this autoscaler.", + "desiredReplicas": "desired number of replicas of pods managed by this autoscaler.", + "currentCPUUtilizationPercentage": "current average CPU utilization over all pods, represented as a percentage of requested CPU, e.g. 70 means that an average pod is using now 70% of its requested CPU.", +} + +func (HorizontalPodAutoscalerStatus) SwaggerDoc() map[string]string { + return map_HorizontalPodAutoscalerStatus +} + +var map_Scale = map[string]string{ + "": "Scale represents a scaling request for a resource.", + "metadata": "Standard object metadata; More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata.", + "spec": "defines the behavior of the scale. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status.", + "status": "current status of the scale. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status. Read-only.", +} + +func (Scale) SwaggerDoc() map[string]string { + return map_Scale +} + +var map_ScaleSpec = map[string]string{ + "": "ScaleSpec describes the attributes of a scale subresource.", + "replicas": "desired number of instances for the scaled object.", +} + +func (ScaleSpec) SwaggerDoc() map[string]string { + return map_ScaleSpec +} + +var map_ScaleStatus = map[string]string{ + "": "ScaleStatus represents the current status of a scale subresource.", + "replicas": "actual number of observed instances of the scaled object.", + "selector": "label query over pods that should match the replicas count. This is same as the label selector but in the string format to avoid introspection by clients. The string will be in the same format as the query-param syntax. More info about label selectors: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors", +} + +func (ScaleStatus) SwaggerDoc() map[string]string { + return map_ScaleStatus +} + +// AUTO-GENERATED FUNCTIONS END HERE diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/validation/validation.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/validation/validation.go new file mode 100644 index 000000000..8b9a21a9d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/validation/validation.go @@ -0,0 +1,34 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 validation + +import ( + apivalidation "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/apis/autoscaling" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +func ValidateScale(scale *autoscaling.Scale) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&scale.ObjectMeta, true, apivalidation.NameIsDNSSubdomain, field.NewPath("metadata"))...) + + if scale.Spec.Replicas < 0 { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "replicas"), scale.Spec.Replicas, "must be greater than or equal to 0")) + } + + return allErrs +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/validation/validation_test.go b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/validation/validation_test.go new file mode 100644 index 000000000..ac8921ee0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/autoscaling/validation/validation_test.go @@ -0,0 +1,89 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 validation + +import ( + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/autoscaling" +) + +func TestValidateScale(t *testing.T) { + successCases := []autoscaling.Scale{ + { + ObjectMeta: api.ObjectMeta{ + Name: "frontend", + Namespace: api.NamespaceDefault, + }, + Spec: autoscaling.ScaleSpec{ + Replicas: 1, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + Name: "frontend", + Namespace: api.NamespaceDefault, + }, + Spec: autoscaling.ScaleSpec{ + Replicas: 10, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + Name: "frontend", + Namespace: api.NamespaceDefault, + }, + Spec: autoscaling.ScaleSpec{ + Replicas: 0, + }, + }, + } + + for _, successCase := range successCases { + if errs := ValidateScale(&successCase); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := []struct { + scale autoscaling.Scale + msg string + }{ + { + scale: autoscaling.Scale{ + ObjectMeta: api.ObjectMeta{ + Name: "frontend", + Namespace: api.NamespaceDefault, + }, + Spec: autoscaling.ScaleSpec{ + Replicas: -1, + }, + }, + msg: "must be greater than or equal to 0", + }, + } + + for _, c := range errorCases { + if errs := ValidateScale(&c.scale); len(errs) == 0 { + t.Errorf("expected failure for %s", c.msg) + } else if !strings.Contains(errs[0].Error(), c.msg) { + t.Errorf("unexpected error: %v, expected: %s", errs[0], c.msg) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/batch/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/batch/deep_copy_generated.go new file mode 100644 index 000000000..55d346a61 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/batch/deep_copy_generated.go @@ -0,0 +1,29 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// DO NOT EDIT. THIS FILE IS AUTO-GENERATED BY $KUBEROOT/hack/update-generated-deep-copies.sh. + +package batch + +import api "k8s.io/kubernetes/pkg/api" + +func init() { + err := api.Scheme.AddGeneratedDeepCopyFuncs() + if err != nil { + // if one of the deep copy functions is malformed, detect it immediately. + panic(err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/batch/install/install.go b/vendor/k8s.io/kubernetes/pkg/apis/batch/install/install.go new file mode 100644 index 000000000..830020a93 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/batch/install/install.go @@ -0,0 +1,129 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 install installs the batch API group, making it available as +// an option to all of the API encoding/decoding machinery. +package install + +import ( + "fmt" + + "github.com/golang/glog" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apimachinery" + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/apis/batch" + "k8s.io/kubernetes/pkg/apis/batch/v1" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/sets" +) + +const importPrefix = "k8s.io/kubernetes/pkg/apis/batch" + +var accessor = meta.NewAccessor() + +// availableVersions lists all known external versions for this group from most preferred to least preferred +var availableVersions = []unversioned.GroupVersion{v1.SchemeGroupVersion} + +func init() { + registered.RegisterVersions(availableVersions) + externalVersions := []unversioned.GroupVersion{} + for _, v := range availableVersions { + if registered.IsAllowedVersion(v) { + externalVersions = append(externalVersions, v) + } + } + if len(externalVersions) == 0 { + glog.V(4).Infof("No version is registered for group %v", batch.GroupName) + return + } + + if err := registered.EnableVersions(externalVersions...); err != nil { + glog.V(4).Infof("%v", err) + return + } + if err := enableVersions(externalVersions); err != nil { + glog.V(4).Infof("%v", err) + return + } +} + +// TODO: enableVersions should be centralized rather than spread in each API +// group. +// We can combine registered.RegisterVersions, registered.EnableVersions and +// registered.RegisterGroup once we have moved enableVersions there. +func enableVersions(externalVersions []unversioned.GroupVersion) error { + addVersionsToScheme(externalVersions...) + preferredExternalVersion := externalVersions[0] + + groupMeta := apimachinery.GroupMeta{ + GroupVersion: preferredExternalVersion, + GroupVersions: externalVersions, + RESTMapper: newRESTMapper(externalVersions), + SelfLinker: runtime.SelfLinker(accessor), + InterfacesFor: interfacesFor, + } + + if err := registered.RegisterGroup(groupMeta); err != nil { + return err + } + api.RegisterRESTMapper(groupMeta.RESTMapper) + return nil +} + +func newRESTMapper(externalVersions []unversioned.GroupVersion) meta.RESTMapper { + // the list of kinds that are scoped at the root of the api hierarchy + // if a kind is not enumerated here, it is assumed to have a namespace scope + rootScoped := sets.NewString() + + ignoredKinds := sets.NewString() + + return api.NewDefaultRESTMapper(externalVersions, interfacesFor, importPrefix, ignoredKinds, rootScoped) +} + +// interfacesFor returns the default Codec and ResourceVersioner for a given version +// string, or an error if the version is not known. +func interfacesFor(version unversioned.GroupVersion) (*meta.VersionInterfaces, error) { + switch version { + case v1.SchemeGroupVersion: + return &meta.VersionInterfaces{ + ObjectConvertor: api.Scheme, + MetadataAccessor: accessor, + }, nil + default: + g, _ := registered.Group(batch.GroupName) + return nil, fmt.Errorf("unsupported storage version: %s (valid: %v)", version, g.GroupVersions) + } +} + +func addVersionsToScheme(externalVersions ...unversioned.GroupVersion) { + // add the internal version to Scheme + batch.AddToScheme(api.Scheme) + // add the enabled external versions to Scheme + for _, v := range externalVersions { + if !registered.IsEnabledVersion(v) { + glog.Errorf("Version %s is not enabled, so it will not be added to the Scheme.", v) + continue + } + switch v { + case v1.SchemeGroupVersion: + v1.AddToScheme(api.Scheme) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/batch/register.go b/vendor/k8s.io/kubernetes/pkg/apis/batch/register.go new file mode 100644 index 000000000..a302fe751 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/batch/register.go @@ -0,0 +1,54 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 batch + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/runtime" +) + +// GroupName is the group name use in this package +const GroupName = "batch" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = unversioned.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind +func Kind(kind string) unversioned.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns back a Group qualified GroupResource +func Resource(resource string) unversioned.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +func AddToScheme(scheme *runtime.Scheme) { + // Add the API to Scheme. + addKnownTypes(scheme) +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) { + scheme.AddKnownTypes(SchemeGroupVersion, + &extensions.Job{}, + &extensions.JobList{}, + &api.ListOptions{}, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/conversion.go b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/conversion.go new file mode 100644 index 000000000..c1a77ee8e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/conversion.go @@ -0,0 +1,63 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/conversion" + "k8s.io/kubernetes/pkg/runtime" +) + +func addConversionFuncs(scheme *runtime.Scheme) { + // Add non-generated conversion functions + err := scheme.AddConversionFuncs( + Convert_api_PodSpec_To_v1_PodSpec, + Convert_v1_PodSpec_To_api_PodSpec, + ) + if err != nil { + // If one of the conversion functions is malformed, detect it immediately. + panic(err) + } + + err = api.Scheme.AddFieldLabelConversionFunc("batch/v1", "Job", + func(label, value string) (string, string, error) { + switch label { + case "metadata.name", "metadata.namespace", "status.successful": + return label, value, nil + default: + return "", "", fmt.Errorf("field label not supported: %s", label) + } + }) + if err != nil { + // If one of the conversion functions is malformed, detect it immediately. + panic(err) + } +} + +// The following two PodSpec conversions functions where copied from pkg/api/conversion.go +// for the generated functions to work properly. +// This should be fixed: https://github.com/kubernetes/kubernetes/issues/12977 +func Convert_api_PodSpec_To_v1_PodSpec(in *api.PodSpec, out *v1.PodSpec, s conversion.Scope) error { + return v1.Convert_api_PodSpec_To_v1_PodSpec(in, out, s) +} + +func Convert_v1_PodSpec_To_api_PodSpec(in *v1.PodSpec, out *api.PodSpec, s conversion.Scope) error { + return v1.Convert_v1_PodSpec_To_api_PodSpec(in, out, s) +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/conversion_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/conversion_generated.go new file mode 100644 index 000000000..9a7b4876f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/conversion_generated.go @@ -0,0 +1,3065 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// DO NOT EDIT. THIS FILE IS AUTO-GENERATED BY $KUBEROOT/hack/update-generated-conversions.sh + +package v1 + +import ( + reflect "reflect" + + api "k8s.io/kubernetes/pkg/api" + resource "k8s.io/kubernetes/pkg/api/resource" + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + v1 "k8s.io/kubernetes/pkg/api/v1" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + conversion "k8s.io/kubernetes/pkg/conversion" +) + +func autoConvert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in *api.AWSElasticBlockStoreVolumeSource, out *v1.AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.AWSElasticBlockStoreVolumeSource))(in) + } + out.VolumeID = in.VolumeID + out.FSType = in.FSType + out.Partition = int32(in.Partition) + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in *api.AWSElasticBlockStoreVolumeSource, out *v1.AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { + return autoConvert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in, out, s) +} + +func autoConvert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in *api.AzureFileVolumeSource, out *v1.AzureFileVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.AzureFileVolumeSource))(in) + } + out.SecretName = in.SecretName + out.ShareName = in.ShareName + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in *api.AzureFileVolumeSource, out *v1.AzureFileVolumeSource, s conversion.Scope) error { + return autoConvert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in, out, s) +} + +func autoConvert_api_Capabilities_To_v1_Capabilities(in *api.Capabilities, out *v1.Capabilities, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Capabilities))(in) + } + if in.Add != nil { + out.Add = make([]v1.Capability, len(in.Add)) + for i := range in.Add { + out.Add[i] = v1.Capability(in.Add[i]) + } + } else { + out.Add = nil + } + if in.Drop != nil { + out.Drop = make([]v1.Capability, len(in.Drop)) + for i := range in.Drop { + out.Drop[i] = v1.Capability(in.Drop[i]) + } + } else { + out.Drop = nil + } + return nil +} + +func Convert_api_Capabilities_To_v1_Capabilities(in *api.Capabilities, out *v1.Capabilities, s conversion.Scope) error { + return autoConvert_api_Capabilities_To_v1_Capabilities(in, out, s) +} + +func autoConvert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in *api.CephFSVolumeSource, out *v1.CephFSVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.CephFSVolumeSource))(in) + } + if in.Monitors != nil { + out.Monitors = make([]string, len(in.Monitors)) + for i := range in.Monitors { + out.Monitors[i] = in.Monitors[i] + } + } else { + out.Monitors = nil + } + out.Path = in.Path + out.User = in.User + out.SecretFile = in.SecretFile + // unable to generate simple pointer conversion for api.LocalObjectReference -> v1.LocalObjectReference + if in.SecretRef != nil { + out.SecretRef = new(v1.LocalObjectReference) + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(in.SecretRef, out.SecretRef, s); err != nil { + return err + } + } else { + out.SecretRef = nil + } + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in *api.CephFSVolumeSource, out *v1.CephFSVolumeSource, s conversion.Scope) error { + return autoConvert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in, out, s) +} + +func autoConvert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in *api.CinderVolumeSource, out *v1.CinderVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.CinderVolumeSource))(in) + } + out.VolumeID = in.VolumeID + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in *api.CinderVolumeSource, out *v1.CinderVolumeSource, s conversion.Scope) error { + return autoConvert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in, out, s) +} + +func autoConvert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in *api.ConfigMapKeySelector, out *v1.ConfigMapKeySelector, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ConfigMapKeySelector))(in) + } + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Key = in.Key + return nil +} + +func Convert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in *api.ConfigMapKeySelector, out *v1.ConfigMapKeySelector, s conversion.Scope) error { + return autoConvert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in, out, s) +} + +func autoConvert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in *api.ConfigMapVolumeSource, out *v1.ConfigMapVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ConfigMapVolumeSource))(in) + } + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + if in.Items != nil { + out.Items = make([]v1.KeyToPath, len(in.Items)) + for i := range in.Items { + if err := Convert_api_KeyToPath_To_v1_KeyToPath(&in.Items[i], &out.Items[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in *api.ConfigMapVolumeSource, out *v1.ConfigMapVolumeSource, s conversion.Scope) error { + return autoConvert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in, out, s) +} + +func autoConvert_api_Container_To_v1_Container(in *api.Container, out *v1.Container, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Container))(in) + } + out.Name = in.Name + out.Image = in.Image + if in.Command != nil { + out.Command = make([]string, len(in.Command)) + for i := range in.Command { + out.Command[i] = in.Command[i] + } + } else { + out.Command = nil + } + if in.Args != nil { + out.Args = make([]string, len(in.Args)) + for i := range in.Args { + out.Args[i] = in.Args[i] + } + } else { + out.Args = nil + } + out.WorkingDir = in.WorkingDir + if in.Ports != nil { + out.Ports = make([]v1.ContainerPort, len(in.Ports)) + for i := range in.Ports { + if err := Convert_api_ContainerPort_To_v1_ContainerPort(&in.Ports[i], &out.Ports[i], s); err != nil { + return err + } + } + } else { + out.Ports = nil + } + if in.Env != nil { + out.Env = make([]v1.EnvVar, len(in.Env)) + for i := range in.Env { + if err := Convert_api_EnvVar_To_v1_EnvVar(&in.Env[i], &out.Env[i], s); err != nil { + return err + } + } + } else { + out.Env = nil + } + if err := Convert_api_ResourceRequirements_To_v1_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil { + return err + } + if in.VolumeMounts != nil { + out.VolumeMounts = make([]v1.VolumeMount, len(in.VolumeMounts)) + for i := range in.VolumeMounts { + if err := Convert_api_VolumeMount_To_v1_VolumeMount(&in.VolumeMounts[i], &out.VolumeMounts[i], s); err != nil { + return err + } + } + } else { + out.VolumeMounts = nil + } + // unable to generate simple pointer conversion for api.Probe -> v1.Probe + if in.LivenessProbe != nil { + out.LivenessProbe = new(v1.Probe) + if err := Convert_api_Probe_To_v1_Probe(in.LivenessProbe, out.LivenessProbe, s); err != nil { + return err + } + } else { + out.LivenessProbe = nil + } + // unable to generate simple pointer conversion for api.Probe -> v1.Probe + if in.ReadinessProbe != nil { + out.ReadinessProbe = new(v1.Probe) + if err := Convert_api_Probe_To_v1_Probe(in.ReadinessProbe, out.ReadinessProbe, s); err != nil { + return err + } + } else { + out.ReadinessProbe = nil + } + // unable to generate simple pointer conversion for api.Lifecycle -> v1.Lifecycle + if in.Lifecycle != nil { + out.Lifecycle = new(v1.Lifecycle) + if err := Convert_api_Lifecycle_To_v1_Lifecycle(in.Lifecycle, out.Lifecycle, s); err != nil { + return err + } + } else { + out.Lifecycle = nil + } + out.TerminationMessagePath = in.TerminationMessagePath + out.ImagePullPolicy = v1.PullPolicy(in.ImagePullPolicy) + // unable to generate simple pointer conversion for api.SecurityContext -> v1.SecurityContext + if in.SecurityContext != nil { + out.SecurityContext = new(v1.SecurityContext) + if err := Convert_api_SecurityContext_To_v1_SecurityContext(in.SecurityContext, out.SecurityContext, s); err != nil { + return err + } + } else { + out.SecurityContext = nil + } + out.Stdin = in.Stdin + out.StdinOnce = in.StdinOnce + out.TTY = in.TTY + return nil +} + +func Convert_api_Container_To_v1_Container(in *api.Container, out *v1.Container, s conversion.Scope) error { + return autoConvert_api_Container_To_v1_Container(in, out, s) +} + +func autoConvert_api_ContainerPort_To_v1_ContainerPort(in *api.ContainerPort, out *v1.ContainerPort, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ContainerPort))(in) + } + out.Name = in.Name + out.HostPort = int32(in.HostPort) + out.ContainerPort = int32(in.ContainerPort) + out.Protocol = v1.Protocol(in.Protocol) + out.HostIP = in.HostIP + return nil +} + +func Convert_api_ContainerPort_To_v1_ContainerPort(in *api.ContainerPort, out *v1.ContainerPort, s conversion.Scope) error { + return autoConvert_api_ContainerPort_To_v1_ContainerPort(in, out, s) +} + +func autoConvert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in *api.DownwardAPIVolumeFile, out *v1.DownwardAPIVolumeFile, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.DownwardAPIVolumeFile))(in) + } + out.Path = in.Path + if err := Convert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(&in.FieldRef, &out.FieldRef, s); err != nil { + return err + } + return nil +} + +func Convert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in *api.DownwardAPIVolumeFile, out *v1.DownwardAPIVolumeFile, s conversion.Scope) error { + return autoConvert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(in, out, s) +} + +func autoConvert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in *api.DownwardAPIVolumeSource, out *v1.DownwardAPIVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.DownwardAPIVolumeSource))(in) + } + if in.Items != nil { + out.Items = make([]v1.DownwardAPIVolumeFile, len(in.Items)) + for i := range in.Items { + if err := Convert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile(&in.Items[i], &out.Items[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in *api.DownwardAPIVolumeSource, out *v1.DownwardAPIVolumeSource, s conversion.Scope) error { + return autoConvert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in, out, s) +} + +func autoConvert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in *api.EmptyDirVolumeSource, out *v1.EmptyDirVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.EmptyDirVolumeSource))(in) + } + out.Medium = v1.StorageMedium(in.Medium) + return nil +} + +func Convert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in *api.EmptyDirVolumeSource, out *v1.EmptyDirVolumeSource, s conversion.Scope) error { + return autoConvert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in, out, s) +} + +func autoConvert_api_EnvVar_To_v1_EnvVar(in *api.EnvVar, out *v1.EnvVar, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.EnvVar))(in) + } + out.Name = in.Name + out.Value = in.Value + // unable to generate simple pointer conversion for api.EnvVarSource -> v1.EnvVarSource + if in.ValueFrom != nil { + out.ValueFrom = new(v1.EnvVarSource) + if err := Convert_api_EnvVarSource_To_v1_EnvVarSource(in.ValueFrom, out.ValueFrom, s); err != nil { + return err + } + } else { + out.ValueFrom = nil + } + return nil +} + +func Convert_api_EnvVar_To_v1_EnvVar(in *api.EnvVar, out *v1.EnvVar, s conversion.Scope) error { + return autoConvert_api_EnvVar_To_v1_EnvVar(in, out, s) +} + +func autoConvert_api_EnvVarSource_To_v1_EnvVarSource(in *api.EnvVarSource, out *v1.EnvVarSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.EnvVarSource))(in) + } + // unable to generate simple pointer conversion for api.ObjectFieldSelector -> v1.ObjectFieldSelector + if in.FieldRef != nil { + out.FieldRef = new(v1.ObjectFieldSelector) + if err := Convert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(in.FieldRef, out.FieldRef, s); err != nil { + return err + } + } else { + out.FieldRef = nil + } + // unable to generate simple pointer conversion for api.ConfigMapKeySelector -> v1.ConfigMapKeySelector + if in.ConfigMapKeyRef != nil { + out.ConfigMapKeyRef = new(v1.ConfigMapKeySelector) + if err := Convert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector(in.ConfigMapKeyRef, out.ConfigMapKeyRef, s); err != nil { + return err + } + } else { + out.ConfigMapKeyRef = nil + } + // unable to generate simple pointer conversion for api.SecretKeySelector -> v1.SecretKeySelector + if in.SecretKeyRef != nil { + out.SecretKeyRef = new(v1.SecretKeySelector) + if err := Convert_api_SecretKeySelector_To_v1_SecretKeySelector(in.SecretKeyRef, out.SecretKeyRef, s); err != nil { + return err + } + } else { + out.SecretKeyRef = nil + } + return nil +} + +func Convert_api_EnvVarSource_To_v1_EnvVarSource(in *api.EnvVarSource, out *v1.EnvVarSource, s conversion.Scope) error { + return autoConvert_api_EnvVarSource_To_v1_EnvVarSource(in, out, s) +} + +func autoConvert_api_ExecAction_To_v1_ExecAction(in *api.ExecAction, out *v1.ExecAction, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ExecAction))(in) + } + if in.Command != nil { + out.Command = make([]string, len(in.Command)) + for i := range in.Command { + out.Command[i] = in.Command[i] + } + } else { + out.Command = nil + } + return nil +} + +func Convert_api_ExecAction_To_v1_ExecAction(in *api.ExecAction, out *v1.ExecAction, s conversion.Scope) error { + return autoConvert_api_ExecAction_To_v1_ExecAction(in, out, s) +} + +func autoConvert_api_FCVolumeSource_To_v1_FCVolumeSource(in *api.FCVolumeSource, out *v1.FCVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.FCVolumeSource))(in) + } + if in.TargetWWNs != nil { + out.TargetWWNs = make([]string, len(in.TargetWWNs)) + for i := range in.TargetWWNs { + out.TargetWWNs[i] = in.TargetWWNs[i] + } + } else { + out.TargetWWNs = nil + } + if in.Lun != nil { + out.Lun = new(int32) + *out.Lun = int32(*in.Lun) + } else { + out.Lun = nil + } + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_FCVolumeSource_To_v1_FCVolumeSource(in *api.FCVolumeSource, out *v1.FCVolumeSource, s conversion.Scope) error { + return autoConvert_api_FCVolumeSource_To_v1_FCVolumeSource(in, out, s) +} + +func autoConvert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in *api.FlexVolumeSource, out *v1.FlexVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.FlexVolumeSource))(in) + } + out.Driver = in.Driver + out.FSType = in.FSType + // unable to generate simple pointer conversion for api.LocalObjectReference -> v1.LocalObjectReference + if in.SecretRef != nil { + out.SecretRef = new(v1.LocalObjectReference) + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(in.SecretRef, out.SecretRef, s); err != nil { + return err + } + } else { + out.SecretRef = nil + } + out.ReadOnly = in.ReadOnly + if in.Options != nil { + out.Options = make(map[string]string) + for key, val := range in.Options { + out.Options[key] = val + } + } else { + out.Options = nil + } + return nil +} + +func Convert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in *api.FlexVolumeSource, out *v1.FlexVolumeSource, s conversion.Scope) error { + return autoConvert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in, out, s) +} + +func autoConvert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in *api.FlockerVolumeSource, out *v1.FlockerVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.FlockerVolumeSource))(in) + } + out.DatasetName = in.DatasetName + return nil +} + +func Convert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in *api.FlockerVolumeSource, out *v1.FlockerVolumeSource, s conversion.Scope) error { + return autoConvert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in, out, s) +} + +func autoConvert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in *api.GCEPersistentDiskVolumeSource, out *v1.GCEPersistentDiskVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.GCEPersistentDiskVolumeSource))(in) + } + out.PDName = in.PDName + out.FSType = in.FSType + out.Partition = int32(in.Partition) + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in *api.GCEPersistentDiskVolumeSource, out *v1.GCEPersistentDiskVolumeSource, s conversion.Scope) error { + return autoConvert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in, out, s) +} + +func autoConvert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in *api.GitRepoVolumeSource, out *v1.GitRepoVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.GitRepoVolumeSource))(in) + } + out.Repository = in.Repository + out.Revision = in.Revision + out.Directory = in.Directory + return nil +} + +func Convert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in *api.GitRepoVolumeSource, out *v1.GitRepoVolumeSource, s conversion.Scope) error { + return autoConvert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in, out, s) +} + +func autoConvert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in *api.GlusterfsVolumeSource, out *v1.GlusterfsVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.GlusterfsVolumeSource))(in) + } + out.EndpointsName = in.EndpointsName + out.Path = in.Path + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in *api.GlusterfsVolumeSource, out *v1.GlusterfsVolumeSource, s conversion.Scope) error { + return autoConvert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in, out, s) +} + +func autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction(in *api.HTTPGetAction, out *v1.HTTPGetAction, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.HTTPGetAction))(in) + } + out.Path = in.Path + if err := api.Convert_intstr_IntOrString_To_intstr_IntOrString(&in.Port, &out.Port, s); err != nil { + return err + } + out.Host = in.Host + out.Scheme = v1.URIScheme(in.Scheme) + if in.HTTPHeaders != nil { + out.HTTPHeaders = make([]v1.HTTPHeader, len(in.HTTPHeaders)) + for i := range in.HTTPHeaders { + if err := Convert_api_HTTPHeader_To_v1_HTTPHeader(&in.HTTPHeaders[i], &out.HTTPHeaders[i], s); err != nil { + return err + } + } + } else { + out.HTTPHeaders = nil + } + return nil +} + +func Convert_api_HTTPGetAction_To_v1_HTTPGetAction(in *api.HTTPGetAction, out *v1.HTTPGetAction, s conversion.Scope) error { + return autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction(in, out, s) +} + +func autoConvert_api_HTTPHeader_To_v1_HTTPHeader(in *api.HTTPHeader, out *v1.HTTPHeader, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.HTTPHeader))(in) + } + out.Name = in.Name + out.Value = in.Value + return nil +} + +func Convert_api_HTTPHeader_To_v1_HTTPHeader(in *api.HTTPHeader, out *v1.HTTPHeader, s conversion.Scope) error { + return autoConvert_api_HTTPHeader_To_v1_HTTPHeader(in, out, s) +} + +func autoConvert_api_Handler_To_v1_Handler(in *api.Handler, out *v1.Handler, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Handler))(in) + } + // unable to generate simple pointer conversion for api.ExecAction -> v1.ExecAction + if in.Exec != nil { + out.Exec = new(v1.ExecAction) + if err := Convert_api_ExecAction_To_v1_ExecAction(in.Exec, out.Exec, s); err != nil { + return err + } + } else { + out.Exec = nil + } + // unable to generate simple pointer conversion for api.HTTPGetAction -> v1.HTTPGetAction + if in.HTTPGet != nil { + out.HTTPGet = new(v1.HTTPGetAction) + if err := Convert_api_HTTPGetAction_To_v1_HTTPGetAction(in.HTTPGet, out.HTTPGet, s); err != nil { + return err + } + } else { + out.HTTPGet = nil + } + // unable to generate simple pointer conversion for api.TCPSocketAction -> v1.TCPSocketAction + if in.TCPSocket != nil { + out.TCPSocket = new(v1.TCPSocketAction) + if err := Convert_api_TCPSocketAction_To_v1_TCPSocketAction(in.TCPSocket, out.TCPSocket, s); err != nil { + return err + } + } else { + out.TCPSocket = nil + } + return nil +} + +func Convert_api_Handler_To_v1_Handler(in *api.Handler, out *v1.Handler, s conversion.Scope) error { + return autoConvert_api_Handler_To_v1_Handler(in, out, s) +} + +func autoConvert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in *api.HostPathVolumeSource, out *v1.HostPathVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.HostPathVolumeSource))(in) + } + out.Path = in.Path + return nil +} + +func Convert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in *api.HostPathVolumeSource, out *v1.HostPathVolumeSource, s conversion.Scope) error { + return autoConvert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in, out, s) +} + +func autoConvert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in *api.ISCSIVolumeSource, out *v1.ISCSIVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ISCSIVolumeSource))(in) + } + out.TargetPortal = in.TargetPortal + out.IQN = in.IQN + out.Lun = int32(in.Lun) + out.ISCSIInterface = in.ISCSIInterface + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in *api.ISCSIVolumeSource, out *v1.ISCSIVolumeSource, s conversion.Scope) error { + return autoConvert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in, out, s) +} + +func autoConvert_api_KeyToPath_To_v1_KeyToPath(in *api.KeyToPath, out *v1.KeyToPath, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.KeyToPath))(in) + } + out.Key = in.Key + out.Path = in.Path + return nil +} + +func Convert_api_KeyToPath_To_v1_KeyToPath(in *api.KeyToPath, out *v1.KeyToPath, s conversion.Scope) error { + return autoConvert_api_KeyToPath_To_v1_KeyToPath(in, out, s) +} + +func autoConvert_api_Lifecycle_To_v1_Lifecycle(in *api.Lifecycle, out *v1.Lifecycle, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Lifecycle))(in) + } + // unable to generate simple pointer conversion for api.Handler -> v1.Handler + if in.PostStart != nil { + out.PostStart = new(v1.Handler) + if err := Convert_api_Handler_To_v1_Handler(in.PostStart, out.PostStart, s); err != nil { + return err + } + } else { + out.PostStart = nil + } + // unable to generate simple pointer conversion for api.Handler -> v1.Handler + if in.PreStop != nil { + out.PreStop = new(v1.Handler) + if err := Convert_api_Handler_To_v1_Handler(in.PreStop, out.PreStop, s); err != nil { + return err + } + } else { + out.PreStop = nil + } + return nil +} + +func Convert_api_Lifecycle_To_v1_Lifecycle(in *api.Lifecycle, out *v1.Lifecycle, s conversion.Scope) error { + return autoConvert_api_Lifecycle_To_v1_Lifecycle(in, out, s) +} + +func autoConvert_api_LocalObjectReference_To_v1_LocalObjectReference(in *api.LocalObjectReference, out *v1.LocalObjectReference, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.LocalObjectReference))(in) + } + out.Name = in.Name + return nil +} + +func Convert_api_LocalObjectReference_To_v1_LocalObjectReference(in *api.LocalObjectReference, out *v1.LocalObjectReference, s conversion.Scope) error { + return autoConvert_api_LocalObjectReference_To_v1_LocalObjectReference(in, out, s) +} + +func autoConvert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in *api.NFSVolumeSource, out *v1.NFSVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.NFSVolumeSource))(in) + } + out.Server = in.Server + out.Path = in.Path + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in *api.NFSVolumeSource, out *v1.NFSVolumeSource, s conversion.Scope) error { + return autoConvert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in, out, s) +} + +func autoConvert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(in *api.ObjectFieldSelector, out *v1.ObjectFieldSelector, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ObjectFieldSelector))(in) + } + out.APIVersion = in.APIVersion + out.FieldPath = in.FieldPath + return nil +} + +func Convert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(in *api.ObjectFieldSelector, out *v1.ObjectFieldSelector, s conversion.Scope) error { + return autoConvert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector(in, out, s) +} + +func autoConvert_api_ObjectMeta_To_v1_ObjectMeta(in *api.ObjectMeta, out *v1.ObjectMeta, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ObjectMeta))(in) + } + out.Name = in.Name + out.GenerateName = in.GenerateName + out.Namespace = in.Namespace + out.SelfLink = in.SelfLink + out.UID = in.UID + out.ResourceVersion = in.ResourceVersion + out.Generation = in.Generation + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.CreationTimestamp, &out.CreationTimestamp, s); err != nil { + return err + } + // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time + if in.DeletionTimestamp != nil { + out.DeletionTimestamp = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(in.DeletionTimestamp, out.DeletionTimestamp, s); err != nil { + return err + } + } else { + out.DeletionTimestamp = nil + } + if in.DeletionGracePeriodSeconds != nil { + out.DeletionGracePeriodSeconds = new(int64) + *out.DeletionGracePeriodSeconds = *in.DeletionGracePeriodSeconds + } else { + out.DeletionGracePeriodSeconds = nil + } + if in.Labels != nil { + out.Labels = make(map[string]string) + for key, val := range in.Labels { + out.Labels[key] = val + } + } else { + out.Labels = nil + } + if in.Annotations != nil { + out.Annotations = make(map[string]string) + for key, val := range in.Annotations { + out.Annotations[key] = val + } + } else { + out.Annotations = nil + } + return nil +} + +func Convert_api_ObjectMeta_To_v1_ObjectMeta(in *api.ObjectMeta, out *v1.ObjectMeta, s conversion.Scope) error { + return autoConvert_api_ObjectMeta_To_v1_ObjectMeta(in, out, s) +} + +func autoConvert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in *api.PersistentVolumeClaimVolumeSource, out *v1.PersistentVolumeClaimVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PersistentVolumeClaimVolumeSource))(in) + } + out.ClaimName = in.ClaimName + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in *api.PersistentVolumeClaimVolumeSource, out *v1.PersistentVolumeClaimVolumeSource, s conversion.Scope) error { + return autoConvert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in, out, s) +} + +func autoConvert_api_PodSpec_To_v1_PodSpec(in *api.PodSpec, out *v1.PodSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PodSpec))(in) + } + if in.Volumes != nil { + out.Volumes = make([]v1.Volume, len(in.Volumes)) + for i := range in.Volumes { + if err := Convert_api_Volume_To_v1_Volume(&in.Volumes[i], &out.Volumes[i], s); err != nil { + return err + } + } + } else { + out.Volumes = nil + } + if in.Containers != nil { + out.Containers = make([]v1.Container, len(in.Containers)) + for i := range in.Containers { + if err := Convert_api_Container_To_v1_Container(&in.Containers[i], &out.Containers[i], s); err != nil { + return err + } + } + } else { + out.Containers = nil + } + out.RestartPolicy = v1.RestartPolicy(in.RestartPolicy) + if in.TerminationGracePeriodSeconds != nil { + out.TerminationGracePeriodSeconds = new(int64) + *out.TerminationGracePeriodSeconds = *in.TerminationGracePeriodSeconds + } else { + out.TerminationGracePeriodSeconds = nil + } + if in.ActiveDeadlineSeconds != nil { + out.ActiveDeadlineSeconds = new(int64) + *out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds + } else { + out.ActiveDeadlineSeconds = nil + } + out.DNSPolicy = v1.DNSPolicy(in.DNSPolicy) + if in.NodeSelector != nil { + out.NodeSelector = make(map[string]string) + for key, val := range in.NodeSelector { + out.NodeSelector[key] = val + } + } else { + out.NodeSelector = nil + } + out.ServiceAccountName = in.ServiceAccountName + out.NodeName = in.NodeName + // unable to generate simple pointer conversion for api.PodSecurityContext -> v1.PodSecurityContext + if in.SecurityContext != nil { + if err := s.Convert(&in.SecurityContext, &out.SecurityContext, 0); err != nil { + return err + } + } else { + out.SecurityContext = nil + } + if in.ImagePullSecrets != nil { + out.ImagePullSecrets = make([]v1.LocalObjectReference, len(in.ImagePullSecrets)) + for i := range in.ImagePullSecrets { + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.ImagePullSecrets[i], &out.ImagePullSecrets[i], s); err != nil { + return err + } + } + } else { + out.ImagePullSecrets = nil + } + return nil +} + +func autoConvert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in *api.PodTemplateSpec, out *v1.PodTemplateSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.PodTemplateSpec))(in) + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_api_PodSpec_To_v1_PodSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + return nil +} + +func Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in *api.PodTemplateSpec, out *v1.PodTemplateSpec, s conversion.Scope) error { + return autoConvert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in, out, s) +} + +func autoConvert_api_Probe_To_v1_Probe(in *api.Probe, out *v1.Probe, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Probe))(in) + } + if err := Convert_api_Handler_To_v1_Handler(&in.Handler, &out.Handler, s); err != nil { + return err + } + out.InitialDelaySeconds = int32(in.InitialDelaySeconds) + out.TimeoutSeconds = int32(in.TimeoutSeconds) + out.PeriodSeconds = int32(in.PeriodSeconds) + out.SuccessThreshold = int32(in.SuccessThreshold) + out.FailureThreshold = int32(in.FailureThreshold) + return nil +} + +func Convert_api_Probe_To_v1_Probe(in *api.Probe, out *v1.Probe, s conversion.Scope) error { + return autoConvert_api_Probe_To_v1_Probe(in, out, s) +} + +func autoConvert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in *api.RBDVolumeSource, out *v1.RBDVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.RBDVolumeSource))(in) + } + if in.CephMonitors != nil { + out.CephMonitors = make([]string, len(in.CephMonitors)) + for i := range in.CephMonitors { + out.CephMonitors[i] = in.CephMonitors[i] + } + } else { + out.CephMonitors = nil + } + out.RBDImage = in.RBDImage + out.FSType = in.FSType + out.RBDPool = in.RBDPool + out.RadosUser = in.RadosUser + out.Keyring = in.Keyring + // unable to generate simple pointer conversion for api.LocalObjectReference -> v1.LocalObjectReference + if in.SecretRef != nil { + out.SecretRef = new(v1.LocalObjectReference) + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(in.SecretRef, out.SecretRef, s); err != nil { + return err + } + } else { + out.SecretRef = nil + } + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in *api.RBDVolumeSource, out *v1.RBDVolumeSource, s conversion.Scope) error { + return autoConvert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in, out, s) +} + +func autoConvert_api_ResourceRequirements_To_v1_ResourceRequirements(in *api.ResourceRequirements, out *v1.ResourceRequirements, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.ResourceRequirements))(in) + } + if in.Limits != nil { + out.Limits = make(v1.ResourceList) + for key, val := range in.Limits { + newVal := resource.Quantity{} + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { + return err + } + out.Limits[v1.ResourceName(key)] = newVal + } + } else { + out.Limits = nil + } + if in.Requests != nil { + out.Requests = make(v1.ResourceList) + for key, val := range in.Requests { + newVal := resource.Quantity{} + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { + return err + } + out.Requests[v1.ResourceName(key)] = newVal + } + } else { + out.Requests = nil + } + return nil +} + +func Convert_api_ResourceRequirements_To_v1_ResourceRequirements(in *api.ResourceRequirements, out *v1.ResourceRequirements, s conversion.Scope) error { + return autoConvert_api_ResourceRequirements_To_v1_ResourceRequirements(in, out, s) +} + +func autoConvert_api_SELinuxOptions_To_v1_SELinuxOptions(in *api.SELinuxOptions, out *v1.SELinuxOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.SELinuxOptions))(in) + } + out.User = in.User + out.Role = in.Role + out.Type = in.Type + out.Level = in.Level + return nil +} + +func Convert_api_SELinuxOptions_To_v1_SELinuxOptions(in *api.SELinuxOptions, out *v1.SELinuxOptions, s conversion.Scope) error { + return autoConvert_api_SELinuxOptions_To_v1_SELinuxOptions(in, out, s) +} + +func autoConvert_api_SecretKeySelector_To_v1_SecretKeySelector(in *api.SecretKeySelector, out *v1.SecretKeySelector, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.SecretKeySelector))(in) + } + if err := Convert_api_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Key = in.Key + return nil +} + +func Convert_api_SecretKeySelector_To_v1_SecretKeySelector(in *api.SecretKeySelector, out *v1.SecretKeySelector, s conversion.Scope) error { + return autoConvert_api_SecretKeySelector_To_v1_SecretKeySelector(in, out, s) +} + +func autoConvert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in *api.SecretVolumeSource, out *v1.SecretVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.SecretVolumeSource))(in) + } + out.SecretName = in.SecretName + return nil +} + +func Convert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in *api.SecretVolumeSource, out *v1.SecretVolumeSource, s conversion.Scope) error { + return autoConvert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in, out, s) +} + +func autoConvert_api_SecurityContext_To_v1_SecurityContext(in *api.SecurityContext, out *v1.SecurityContext, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.SecurityContext))(in) + } + // unable to generate simple pointer conversion for api.Capabilities -> v1.Capabilities + if in.Capabilities != nil { + out.Capabilities = new(v1.Capabilities) + if err := Convert_api_Capabilities_To_v1_Capabilities(in.Capabilities, out.Capabilities, s); err != nil { + return err + } + } else { + out.Capabilities = nil + } + if in.Privileged != nil { + out.Privileged = new(bool) + *out.Privileged = *in.Privileged + } else { + out.Privileged = nil + } + // unable to generate simple pointer conversion for api.SELinuxOptions -> v1.SELinuxOptions + if in.SELinuxOptions != nil { + out.SELinuxOptions = new(v1.SELinuxOptions) + if err := Convert_api_SELinuxOptions_To_v1_SELinuxOptions(in.SELinuxOptions, out.SELinuxOptions, s); err != nil { + return err + } + } else { + out.SELinuxOptions = nil + } + if in.RunAsUser != nil { + out.RunAsUser = new(int64) + *out.RunAsUser = *in.RunAsUser + } else { + out.RunAsUser = nil + } + if in.RunAsNonRoot != nil { + out.RunAsNonRoot = new(bool) + *out.RunAsNonRoot = *in.RunAsNonRoot + } else { + out.RunAsNonRoot = nil + } + if in.ReadOnlyRootFilesystem != nil { + out.ReadOnlyRootFilesystem = new(bool) + *out.ReadOnlyRootFilesystem = *in.ReadOnlyRootFilesystem + } else { + out.ReadOnlyRootFilesystem = nil + } + return nil +} + +func Convert_api_SecurityContext_To_v1_SecurityContext(in *api.SecurityContext, out *v1.SecurityContext, s conversion.Scope) error { + return autoConvert_api_SecurityContext_To_v1_SecurityContext(in, out, s) +} + +func autoConvert_api_TCPSocketAction_To_v1_TCPSocketAction(in *api.TCPSocketAction, out *v1.TCPSocketAction, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.TCPSocketAction))(in) + } + if err := api.Convert_intstr_IntOrString_To_intstr_IntOrString(&in.Port, &out.Port, s); err != nil { + return err + } + return nil +} + +func Convert_api_TCPSocketAction_To_v1_TCPSocketAction(in *api.TCPSocketAction, out *v1.TCPSocketAction, s conversion.Scope) error { + return autoConvert_api_TCPSocketAction_To_v1_TCPSocketAction(in, out, s) +} + +func autoConvert_api_Volume_To_v1_Volume(in *api.Volume, out *v1.Volume, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.Volume))(in) + } + out.Name = in.Name + if err := Convert_api_VolumeSource_To_v1_VolumeSource(&in.VolumeSource, &out.VolumeSource, s); err != nil { + return err + } + return nil +} + +func Convert_api_Volume_To_v1_Volume(in *api.Volume, out *v1.Volume, s conversion.Scope) error { + return autoConvert_api_Volume_To_v1_Volume(in, out, s) +} + +func autoConvert_api_VolumeMount_To_v1_VolumeMount(in *api.VolumeMount, out *v1.VolumeMount, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.VolumeMount))(in) + } + out.Name = in.Name + out.ReadOnly = in.ReadOnly + out.MountPath = in.MountPath + return nil +} + +func Convert_api_VolumeMount_To_v1_VolumeMount(in *api.VolumeMount, out *v1.VolumeMount, s conversion.Scope) error { + return autoConvert_api_VolumeMount_To_v1_VolumeMount(in, out, s) +} + +func autoConvert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *v1.VolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*api.VolumeSource))(in) + } + // unable to generate simple pointer conversion for api.HostPathVolumeSource -> v1.HostPathVolumeSource + if in.HostPath != nil { + out.HostPath = new(v1.HostPathVolumeSource) + if err := Convert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource(in.HostPath, out.HostPath, s); err != nil { + return err + } + } else { + out.HostPath = nil + } + // unable to generate simple pointer conversion for api.EmptyDirVolumeSource -> v1.EmptyDirVolumeSource + if in.EmptyDir != nil { + out.EmptyDir = new(v1.EmptyDirVolumeSource) + if err := Convert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource(in.EmptyDir, out.EmptyDir, s); err != nil { + return err + } + } else { + out.EmptyDir = nil + } + // unable to generate simple pointer conversion for api.GCEPersistentDiskVolumeSource -> v1.GCEPersistentDiskVolumeSource + if in.GCEPersistentDisk != nil { + out.GCEPersistentDisk = new(v1.GCEPersistentDiskVolumeSource) + if err := Convert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource(in.GCEPersistentDisk, out.GCEPersistentDisk, s); err != nil { + return err + } + } else { + out.GCEPersistentDisk = nil + } + // unable to generate simple pointer conversion for api.AWSElasticBlockStoreVolumeSource -> v1.AWSElasticBlockStoreVolumeSource + if in.AWSElasticBlockStore != nil { + out.AWSElasticBlockStore = new(v1.AWSElasticBlockStoreVolumeSource) + if err := Convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in.AWSElasticBlockStore, out.AWSElasticBlockStore, s); err != nil { + return err + } + } else { + out.AWSElasticBlockStore = nil + } + // unable to generate simple pointer conversion for api.GitRepoVolumeSource -> v1.GitRepoVolumeSource + if in.GitRepo != nil { + out.GitRepo = new(v1.GitRepoVolumeSource) + if err := Convert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource(in.GitRepo, out.GitRepo, s); err != nil { + return err + } + } else { + out.GitRepo = nil + } + // unable to generate simple pointer conversion for api.SecretVolumeSource -> v1.SecretVolumeSource + if in.Secret != nil { + out.Secret = new(v1.SecretVolumeSource) + if err := Convert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in.Secret, out.Secret, s); err != nil { + return err + } + } else { + out.Secret = nil + } + // unable to generate simple pointer conversion for api.NFSVolumeSource -> v1.NFSVolumeSource + if in.NFS != nil { + out.NFS = new(v1.NFSVolumeSource) + if err := Convert_api_NFSVolumeSource_To_v1_NFSVolumeSource(in.NFS, out.NFS, s); err != nil { + return err + } + } else { + out.NFS = nil + } + // unable to generate simple pointer conversion for api.ISCSIVolumeSource -> v1.ISCSIVolumeSource + if in.ISCSI != nil { + out.ISCSI = new(v1.ISCSIVolumeSource) + if err := Convert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource(in.ISCSI, out.ISCSI, s); err != nil { + return err + } + } else { + out.ISCSI = nil + } + // unable to generate simple pointer conversion for api.GlusterfsVolumeSource -> v1.GlusterfsVolumeSource + if in.Glusterfs != nil { + out.Glusterfs = new(v1.GlusterfsVolumeSource) + if err := Convert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource(in.Glusterfs, out.Glusterfs, s); err != nil { + return err + } + } else { + out.Glusterfs = nil + } + // unable to generate simple pointer conversion for api.PersistentVolumeClaimVolumeSource -> v1.PersistentVolumeClaimVolumeSource + if in.PersistentVolumeClaim != nil { + out.PersistentVolumeClaim = new(v1.PersistentVolumeClaimVolumeSource) + if err := Convert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource(in.PersistentVolumeClaim, out.PersistentVolumeClaim, s); err != nil { + return err + } + } else { + out.PersistentVolumeClaim = nil + } + // unable to generate simple pointer conversion for api.RBDVolumeSource -> v1.RBDVolumeSource + if in.RBD != nil { + out.RBD = new(v1.RBDVolumeSource) + if err := Convert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in.RBD, out.RBD, s); err != nil { + return err + } + } else { + out.RBD = nil + } + // unable to generate simple pointer conversion for api.FlexVolumeSource -> v1.FlexVolumeSource + if in.FlexVolume != nil { + out.FlexVolume = new(v1.FlexVolumeSource) + if err := Convert_api_FlexVolumeSource_To_v1_FlexVolumeSource(in.FlexVolume, out.FlexVolume, s); err != nil { + return err + } + } else { + out.FlexVolume = nil + } + // unable to generate simple pointer conversion for api.CinderVolumeSource -> v1.CinderVolumeSource + if in.Cinder != nil { + out.Cinder = new(v1.CinderVolumeSource) + if err := Convert_api_CinderVolumeSource_To_v1_CinderVolumeSource(in.Cinder, out.Cinder, s); err != nil { + return err + } + } else { + out.Cinder = nil + } + // unable to generate simple pointer conversion for api.CephFSVolumeSource -> v1.CephFSVolumeSource + if in.CephFS != nil { + out.CephFS = new(v1.CephFSVolumeSource) + if err := Convert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource(in.CephFS, out.CephFS, s); err != nil { + return err + } + } else { + out.CephFS = nil + } + // unable to generate simple pointer conversion for api.FlockerVolumeSource -> v1.FlockerVolumeSource + if in.Flocker != nil { + out.Flocker = new(v1.FlockerVolumeSource) + if err := Convert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource(in.Flocker, out.Flocker, s); err != nil { + return err + } + } else { + out.Flocker = nil + } + // unable to generate simple pointer conversion for api.DownwardAPIVolumeSource -> v1.DownwardAPIVolumeSource + if in.DownwardAPI != nil { + out.DownwardAPI = new(v1.DownwardAPIVolumeSource) + if err := Convert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource(in.DownwardAPI, out.DownwardAPI, s); err != nil { + return err + } + } else { + out.DownwardAPI = nil + } + // unable to generate simple pointer conversion for api.FCVolumeSource -> v1.FCVolumeSource + if in.FC != nil { + out.FC = new(v1.FCVolumeSource) + if err := Convert_api_FCVolumeSource_To_v1_FCVolumeSource(in.FC, out.FC, s); err != nil { + return err + } + } else { + out.FC = nil + } + // unable to generate simple pointer conversion for api.AzureFileVolumeSource -> v1.AzureFileVolumeSource + if in.AzureFile != nil { + out.AzureFile = new(v1.AzureFileVolumeSource) + if err := Convert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource(in.AzureFile, out.AzureFile, s); err != nil { + return err + } + } else { + out.AzureFile = nil + } + // unable to generate simple pointer conversion for api.ConfigMapVolumeSource -> v1.ConfigMapVolumeSource + if in.ConfigMap != nil { + out.ConfigMap = new(v1.ConfigMapVolumeSource) + if err := Convert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource(in.ConfigMap, out.ConfigMap, s); err != nil { + return err + } + } else { + out.ConfigMap = nil + } + return nil +} + +func Convert_api_VolumeSource_To_v1_VolumeSource(in *api.VolumeSource, out *v1.VolumeSource, s conversion.Scope) error { + return autoConvert_api_VolumeSource_To_v1_VolumeSource(in, out, s) +} + +func autoConvert_unversioned_LabelSelector_To_v1_LabelSelector(in *unversioned.LabelSelector, out *LabelSelector, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*unversioned.LabelSelector))(in) + } + if in.MatchLabels != nil { + out.MatchLabels = make(map[string]string) + for key, val := range in.MatchLabels { + out.MatchLabels[key] = val + } + } else { + out.MatchLabels = nil + } + if in.MatchExpressions != nil { + out.MatchExpressions = make([]LabelSelectorRequirement, len(in.MatchExpressions)) + for i := range in.MatchExpressions { + if err := Convert_unversioned_LabelSelectorRequirement_To_v1_LabelSelectorRequirement(&in.MatchExpressions[i], &out.MatchExpressions[i], s); err != nil { + return err + } + } + } else { + out.MatchExpressions = nil + } + return nil +} + +func Convert_unversioned_LabelSelector_To_v1_LabelSelector(in *unversioned.LabelSelector, out *LabelSelector, s conversion.Scope) error { + return autoConvert_unversioned_LabelSelector_To_v1_LabelSelector(in, out, s) +} + +func autoConvert_unversioned_LabelSelectorRequirement_To_v1_LabelSelectorRequirement(in *unversioned.LabelSelectorRequirement, out *LabelSelectorRequirement, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*unversioned.LabelSelectorRequirement))(in) + } + out.Key = in.Key + out.Operator = LabelSelectorOperator(in.Operator) + if in.Values != nil { + out.Values = make([]string, len(in.Values)) + for i := range in.Values { + out.Values[i] = in.Values[i] + } + } else { + out.Values = nil + } + return nil +} + +func Convert_unversioned_LabelSelectorRequirement_To_v1_LabelSelectorRequirement(in *unversioned.LabelSelectorRequirement, out *LabelSelectorRequirement, s conversion.Scope) error { + return autoConvert_unversioned_LabelSelectorRequirement_To_v1_LabelSelectorRequirement(in, out, s) +} + +func autoConvert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource(in *v1.AWSElasticBlockStoreVolumeSource, out *api.AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.AWSElasticBlockStoreVolumeSource))(in) + } + out.VolumeID = in.VolumeID + out.FSType = in.FSType + out.Partition = int(in.Partition) + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource(in *v1.AWSElasticBlockStoreVolumeSource, out *api.AWSElasticBlockStoreVolumeSource, s conversion.Scope) error { + return autoConvert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource(in, out, s) +} + +func autoConvert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource(in *v1.AzureFileVolumeSource, out *api.AzureFileVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.AzureFileVolumeSource))(in) + } + out.SecretName = in.SecretName + out.ShareName = in.ShareName + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource(in *v1.AzureFileVolumeSource, out *api.AzureFileVolumeSource, s conversion.Scope) error { + return autoConvert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource(in, out, s) +} + +func autoConvert_v1_Capabilities_To_api_Capabilities(in *v1.Capabilities, out *api.Capabilities, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.Capabilities))(in) + } + if in.Add != nil { + out.Add = make([]api.Capability, len(in.Add)) + for i := range in.Add { + out.Add[i] = api.Capability(in.Add[i]) + } + } else { + out.Add = nil + } + if in.Drop != nil { + out.Drop = make([]api.Capability, len(in.Drop)) + for i := range in.Drop { + out.Drop[i] = api.Capability(in.Drop[i]) + } + } else { + out.Drop = nil + } + return nil +} + +func Convert_v1_Capabilities_To_api_Capabilities(in *v1.Capabilities, out *api.Capabilities, s conversion.Scope) error { + return autoConvert_v1_Capabilities_To_api_Capabilities(in, out, s) +} + +func autoConvert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource(in *v1.CephFSVolumeSource, out *api.CephFSVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.CephFSVolumeSource))(in) + } + if in.Monitors != nil { + out.Monitors = make([]string, len(in.Monitors)) + for i := range in.Monitors { + out.Monitors[i] = in.Monitors[i] + } + } else { + out.Monitors = nil + } + out.Path = in.Path + out.User = in.User + out.SecretFile = in.SecretFile + // unable to generate simple pointer conversion for v1.LocalObjectReference -> api.LocalObjectReference + if in.SecretRef != nil { + out.SecretRef = new(api.LocalObjectReference) + if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(in.SecretRef, out.SecretRef, s); err != nil { + return err + } + } else { + out.SecretRef = nil + } + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource(in *v1.CephFSVolumeSource, out *api.CephFSVolumeSource, s conversion.Scope) error { + return autoConvert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource(in, out, s) +} + +func autoConvert_v1_CinderVolumeSource_To_api_CinderVolumeSource(in *v1.CinderVolumeSource, out *api.CinderVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.CinderVolumeSource))(in) + } + out.VolumeID = in.VolumeID + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_v1_CinderVolumeSource_To_api_CinderVolumeSource(in *v1.CinderVolumeSource, out *api.CinderVolumeSource, s conversion.Scope) error { + return autoConvert_v1_CinderVolumeSource_To_api_CinderVolumeSource(in, out, s) +} + +func autoConvert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector(in *v1.ConfigMapKeySelector, out *api.ConfigMapKeySelector, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.ConfigMapKeySelector))(in) + } + if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Key = in.Key + return nil +} + +func Convert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector(in *v1.ConfigMapKeySelector, out *api.ConfigMapKeySelector, s conversion.Scope) error { + return autoConvert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector(in, out, s) +} + +func autoConvert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource(in *v1.ConfigMapVolumeSource, out *api.ConfigMapVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.ConfigMapVolumeSource))(in) + } + if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + if in.Items != nil { + out.Items = make([]api.KeyToPath, len(in.Items)) + for i := range in.Items { + if err := Convert_v1_KeyToPath_To_api_KeyToPath(&in.Items[i], &out.Items[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource(in *v1.ConfigMapVolumeSource, out *api.ConfigMapVolumeSource, s conversion.Scope) error { + return autoConvert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource(in, out, s) +} + +func autoConvert_v1_Container_To_api_Container(in *v1.Container, out *api.Container, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.Container))(in) + } + out.Name = in.Name + out.Image = in.Image + if in.Command != nil { + out.Command = make([]string, len(in.Command)) + for i := range in.Command { + out.Command[i] = in.Command[i] + } + } else { + out.Command = nil + } + if in.Args != nil { + out.Args = make([]string, len(in.Args)) + for i := range in.Args { + out.Args[i] = in.Args[i] + } + } else { + out.Args = nil + } + out.WorkingDir = in.WorkingDir + if in.Ports != nil { + out.Ports = make([]api.ContainerPort, len(in.Ports)) + for i := range in.Ports { + if err := Convert_v1_ContainerPort_To_api_ContainerPort(&in.Ports[i], &out.Ports[i], s); err != nil { + return err + } + } + } else { + out.Ports = nil + } + if in.Env != nil { + out.Env = make([]api.EnvVar, len(in.Env)) + for i := range in.Env { + if err := Convert_v1_EnvVar_To_api_EnvVar(&in.Env[i], &out.Env[i], s); err != nil { + return err + } + } + } else { + out.Env = nil + } + if err := Convert_v1_ResourceRequirements_To_api_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil { + return err + } + if in.VolumeMounts != nil { + out.VolumeMounts = make([]api.VolumeMount, len(in.VolumeMounts)) + for i := range in.VolumeMounts { + if err := Convert_v1_VolumeMount_To_api_VolumeMount(&in.VolumeMounts[i], &out.VolumeMounts[i], s); err != nil { + return err + } + } + } else { + out.VolumeMounts = nil + } + // unable to generate simple pointer conversion for v1.Probe -> api.Probe + if in.LivenessProbe != nil { + out.LivenessProbe = new(api.Probe) + if err := Convert_v1_Probe_To_api_Probe(in.LivenessProbe, out.LivenessProbe, s); err != nil { + return err + } + } else { + out.LivenessProbe = nil + } + // unable to generate simple pointer conversion for v1.Probe -> api.Probe + if in.ReadinessProbe != nil { + out.ReadinessProbe = new(api.Probe) + if err := Convert_v1_Probe_To_api_Probe(in.ReadinessProbe, out.ReadinessProbe, s); err != nil { + return err + } + } else { + out.ReadinessProbe = nil + } + // unable to generate simple pointer conversion for v1.Lifecycle -> api.Lifecycle + if in.Lifecycle != nil { + out.Lifecycle = new(api.Lifecycle) + if err := Convert_v1_Lifecycle_To_api_Lifecycle(in.Lifecycle, out.Lifecycle, s); err != nil { + return err + } + } else { + out.Lifecycle = nil + } + out.TerminationMessagePath = in.TerminationMessagePath + out.ImagePullPolicy = api.PullPolicy(in.ImagePullPolicy) + // unable to generate simple pointer conversion for v1.SecurityContext -> api.SecurityContext + if in.SecurityContext != nil { + out.SecurityContext = new(api.SecurityContext) + if err := Convert_v1_SecurityContext_To_api_SecurityContext(in.SecurityContext, out.SecurityContext, s); err != nil { + return err + } + } else { + out.SecurityContext = nil + } + out.Stdin = in.Stdin + out.StdinOnce = in.StdinOnce + out.TTY = in.TTY + return nil +} + +func Convert_v1_Container_To_api_Container(in *v1.Container, out *api.Container, s conversion.Scope) error { + return autoConvert_v1_Container_To_api_Container(in, out, s) +} + +func autoConvert_v1_ContainerPort_To_api_ContainerPort(in *v1.ContainerPort, out *api.ContainerPort, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.ContainerPort))(in) + } + out.Name = in.Name + out.HostPort = int(in.HostPort) + out.ContainerPort = int(in.ContainerPort) + out.Protocol = api.Protocol(in.Protocol) + out.HostIP = in.HostIP + return nil +} + +func Convert_v1_ContainerPort_To_api_ContainerPort(in *v1.ContainerPort, out *api.ContainerPort, s conversion.Scope) error { + return autoConvert_v1_ContainerPort_To_api_ContainerPort(in, out, s) +} + +func autoConvert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile(in *v1.DownwardAPIVolumeFile, out *api.DownwardAPIVolumeFile, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.DownwardAPIVolumeFile))(in) + } + out.Path = in.Path + if err := Convert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector(&in.FieldRef, &out.FieldRef, s); err != nil { + return err + } + return nil +} + +func Convert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile(in *v1.DownwardAPIVolumeFile, out *api.DownwardAPIVolumeFile, s conversion.Scope) error { + return autoConvert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile(in, out, s) +} + +func autoConvert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource(in *v1.DownwardAPIVolumeSource, out *api.DownwardAPIVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.DownwardAPIVolumeSource))(in) + } + if in.Items != nil { + out.Items = make([]api.DownwardAPIVolumeFile, len(in.Items)) + for i := range in.Items { + if err := Convert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile(&in.Items[i], &out.Items[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource(in *v1.DownwardAPIVolumeSource, out *api.DownwardAPIVolumeSource, s conversion.Scope) error { + return autoConvert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource(in, out, s) +} + +func autoConvert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource(in *v1.EmptyDirVolumeSource, out *api.EmptyDirVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.EmptyDirVolumeSource))(in) + } + out.Medium = api.StorageMedium(in.Medium) + return nil +} + +func Convert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource(in *v1.EmptyDirVolumeSource, out *api.EmptyDirVolumeSource, s conversion.Scope) error { + return autoConvert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource(in, out, s) +} + +func autoConvert_v1_EnvVar_To_api_EnvVar(in *v1.EnvVar, out *api.EnvVar, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.EnvVar))(in) + } + out.Name = in.Name + out.Value = in.Value + // unable to generate simple pointer conversion for v1.EnvVarSource -> api.EnvVarSource + if in.ValueFrom != nil { + out.ValueFrom = new(api.EnvVarSource) + if err := Convert_v1_EnvVarSource_To_api_EnvVarSource(in.ValueFrom, out.ValueFrom, s); err != nil { + return err + } + } else { + out.ValueFrom = nil + } + return nil +} + +func Convert_v1_EnvVar_To_api_EnvVar(in *v1.EnvVar, out *api.EnvVar, s conversion.Scope) error { + return autoConvert_v1_EnvVar_To_api_EnvVar(in, out, s) +} + +func autoConvert_v1_EnvVarSource_To_api_EnvVarSource(in *v1.EnvVarSource, out *api.EnvVarSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.EnvVarSource))(in) + } + // unable to generate simple pointer conversion for v1.ObjectFieldSelector -> api.ObjectFieldSelector + if in.FieldRef != nil { + out.FieldRef = new(api.ObjectFieldSelector) + if err := Convert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector(in.FieldRef, out.FieldRef, s); err != nil { + return err + } + } else { + out.FieldRef = nil + } + // unable to generate simple pointer conversion for v1.ConfigMapKeySelector -> api.ConfigMapKeySelector + if in.ConfigMapKeyRef != nil { + out.ConfigMapKeyRef = new(api.ConfigMapKeySelector) + if err := Convert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector(in.ConfigMapKeyRef, out.ConfigMapKeyRef, s); err != nil { + return err + } + } else { + out.ConfigMapKeyRef = nil + } + // unable to generate simple pointer conversion for v1.SecretKeySelector -> api.SecretKeySelector + if in.SecretKeyRef != nil { + out.SecretKeyRef = new(api.SecretKeySelector) + if err := Convert_v1_SecretKeySelector_To_api_SecretKeySelector(in.SecretKeyRef, out.SecretKeyRef, s); err != nil { + return err + } + } else { + out.SecretKeyRef = nil + } + return nil +} + +func Convert_v1_EnvVarSource_To_api_EnvVarSource(in *v1.EnvVarSource, out *api.EnvVarSource, s conversion.Scope) error { + return autoConvert_v1_EnvVarSource_To_api_EnvVarSource(in, out, s) +} + +func autoConvert_v1_ExecAction_To_api_ExecAction(in *v1.ExecAction, out *api.ExecAction, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.ExecAction))(in) + } + if in.Command != nil { + out.Command = make([]string, len(in.Command)) + for i := range in.Command { + out.Command[i] = in.Command[i] + } + } else { + out.Command = nil + } + return nil +} + +func Convert_v1_ExecAction_To_api_ExecAction(in *v1.ExecAction, out *api.ExecAction, s conversion.Scope) error { + return autoConvert_v1_ExecAction_To_api_ExecAction(in, out, s) +} + +func autoConvert_v1_FCVolumeSource_To_api_FCVolumeSource(in *v1.FCVolumeSource, out *api.FCVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.FCVolumeSource))(in) + } + if in.TargetWWNs != nil { + out.TargetWWNs = make([]string, len(in.TargetWWNs)) + for i := range in.TargetWWNs { + out.TargetWWNs[i] = in.TargetWWNs[i] + } + } else { + out.TargetWWNs = nil + } + if in.Lun != nil { + out.Lun = new(int) + *out.Lun = int(*in.Lun) + } else { + out.Lun = nil + } + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_v1_FCVolumeSource_To_api_FCVolumeSource(in *v1.FCVolumeSource, out *api.FCVolumeSource, s conversion.Scope) error { + return autoConvert_v1_FCVolumeSource_To_api_FCVolumeSource(in, out, s) +} + +func autoConvert_v1_FlexVolumeSource_To_api_FlexVolumeSource(in *v1.FlexVolumeSource, out *api.FlexVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.FlexVolumeSource))(in) + } + out.Driver = in.Driver + out.FSType = in.FSType + // unable to generate simple pointer conversion for v1.LocalObjectReference -> api.LocalObjectReference + if in.SecretRef != nil { + out.SecretRef = new(api.LocalObjectReference) + if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(in.SecretRef, out.SecretRef, s); err != nil { + return err + } + } else { + out.SecretRef = nil + } + out.ReadOnly = in.ReadOnly + if in.Options != nil { + out.Options = make(map[string]string) + for key, val := range in.Options { + out.Options[key] = val + } + } else { + out.Options = nil + } + return nil +} + +func Convert_v1_FlexVolumeSource_To_api_FlexVolumeSource(in *v1.FlexVolumeSource, out *api.FlexVolumeSource, s conversion.Scope) error { + return autoConvert_v1_FlexVolumeSource_To_api_FlexVolumeSource(in, out, s) +} + +func autoConvert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(in *v1.FlockerVolumeSource, out *api.FlockerVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.FlockerVolumeSource))(in) + } + out.DatasetName = in.DatasetName + return nil +} + +func Convert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(in *v1.FlockerVolumeSource, out *api.FlockerVolumeSource, s conversion.Scope) error { + return autoConvert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(in, out, s) +} + +func autoConvert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource(in *v1.GCEPersistentDiskVolumeSource, out *api.GCEPersistentDiskVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.GCEPersistentDiskVolumeSource))(in) + } + out.PDName = in.PDName + out.FSType = in.FSType + out.Partition = int(in.Partition) + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource(in *v1.GCEPersistentDiskVolumeSource, out *api.GCEPersistentDiskVolumeSource, s conversion.Scope) error { + return autoConvert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource(in, out, s) +} + +func autoConvert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource(in *v1.GitRepoVolumeSource, out *api.GitRepoVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.GitRepoVolumeSource))(in) + } + out.Repository = in.Repository + out.Revision = in.Revision + out.Directory = in.Directory + return nil +} + +func Convert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource(in *v1.GitRepoVolumeSource, out *api.GitRepoVolumeSource, s conversion.Scope) error { + return autoConvert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource(in, out, s) +} + +func autoConvert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource(in *v1.GlusterfsVolumeSource, out *api.GlusterfsVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.GlusterfsVolumeSource))(in) + } + out.EndpointsName = in.EndpointsName + out.Path = in.Path + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource(in *v1.GlusterfsVolumeSource, out *api.GlusterfsVolumeSource, s conversion.Scope) error { + return autoConvert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource(in, out, s) +} + +func autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction(in *v1.HTTPGetAction, out *api.HTTPGetAction, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.HTTPGetAction))(in) + } + out.Path = in.Path + if err := api.Convert_intstr_IntOrString_To_intstr_IntOrString(&in.Port, &out.Port, s); err != nil { + return err + } + out.Host = in.Host + out.Scheme = api.URIScheme(in.Scheme) + if in.HTTPHeaders != nil { + out.HTTPHeaders = make([]api.HTTPHeader, len(in.HTTPHeaders)) + for i := range in.HTTPHeaders { + if err := Convert_v1_HTTPHeader_To_api_HTTPHeader(&in.HTTPHeaders[i], &out.HTTPHeaders[i], s); err != nil { + return err + } + } + } else { + out.HTTPHeaders = nil + } + return nil +} + +func Convert_v1_HTTPGetAction_To_api_HTTPGetAction(in *v1.HTTPGetAction, out *api.HTTPGetAction, s conversion.Scope) error { + return autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction(in, out, s) +} + +func autoConvert_v1_HTTPHeader_To_api_HTTPHeader(in *v1.HTTPHeader, out *api.HTTPHeader, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.HTTPHeader))(in) + } + out.Name = in.Name + out.Value = in.Value + return nil +} + +func Convert_v1_HTTPHeader_To_api_HTTPHeader(in *v1.HTTPHeader, out *api.HTTPHeader, s conversion.Scope) error { + return autoConvert_v1_HTTPHeader_To_api_HTTPHeader(in, out, s) +} + +func autoConvert_v1_Handler_To_api_Handler(in *v1.Handler, out *api.Handler, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.Handler))(in) + } + // unable to generate simple pointer conversion for v1.ExecAction -> api.ExecAction + if in.Exec != nil { + out.Exec = new(api.ExecAction) + if err := Convert_v1_ExecAction_To_api_ExecAction(in.Exec, out.Exec, s); err != nil { + return err + } + } else { + out.Exec = nil + } + // unable to generate simple pointer conversion for v1.HTTPGetAction -> api.HTTPGetAction + if in.HTTPGet != nil { + out.HTTPGet = new(api.HTTPGetAction) + if err := Convert_v1_HTTPGetAction_To_api_HTTPGetAction(in.HTTPGet, out.HTTPGet, s); err != nil { + return err + } + } else { + out.HTTPGet = nil + } + // unable to generate simple pointer conversion for v1.TCPSocketAction -> api.TCPSocketAction + if in.TCPSocket != nil { + out.TCPSocket = new(api.TCPSocketAction) + if err := Convert_v1_TCPSocketAction_To_api_TCPSocketAction(in.TCPSocket, out.TCPSocket, s); err != nil { + return err + } + } else { + out.TCPSocket = nil + } + return nil +} + +func Convert_v1_Handler_To_api_Handler(in *v1.Handler, out *api.Handler, s conversion.Scope) error { + return autoConvert_v1_Handler_To_api_Handler(in, out, s) +} + +func autoConvert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource(in *v1.HostPathVolumeSource, out *api.HostPathVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.HostPathVolumeSource))(in) + } + out.Path = in.Path + return nil +} + +func Convert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource(in *v1.HostPathVolumeSource, out *api.HostPathVolumeSource, s conversion.Scope) error { + return autoConvert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource(in, out, s) +} + +func autoConvert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource(in *v1.ISCSIVolumeSource, out *api.ISCSIVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.ISCSIVolumeSource))(in) + } + out.TargetPortal = in.TargetPortal + out.IQN = in.IQN + out.Lun = int(in.Lun) + out.ISCSIInterface = in.ISCSIInterface + out.FSType = in.FSType + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource(in *v1.ISCSIVolumeSource, out *api.ISCSIVolumeSource, s conversion.Scope) error { + return autoConvert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource(in, out, s) +} + +func autoConvert_v1_KeyToPath_To_api_KeyToPath(in *v1.KeyToPath, out *api.KeyToPath, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.KeyToPath))(in) + } + out.Key = in.Key + out.Path = in.Path + return nil +} + +func Convert_v1_KeyToPath_To_api_KeyToPath(in *v1.KeyToPath, out *api.KeyToPath, s conversion.Scope) error { + return autoConvert_v1_KeyToPath_To_api_KeyToPath(in, out, s) +} + +func autoConvert_v1_Lifecycle_To_api_Lifecycle(in *v1.Lifecycle, out *api.Lifecycle, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.Lifecycle))(in) + } + // unable to generate simple pointer conversion for v1.Handler -> api.Handler + if in.PostStart != nil { + out.PostStart = new(api.Handler) + if err := Convert_v1_Handler_To_api_Handler(in.PostStart, out.PostStart, s); err != nil { + return err + } + } else { + out.PostStart = nil + } + // unable to generate simple pointer conversion for v1.Handler -> api.Handler + if in.PreStop != nil { + out.PreStop = new(api.Handler) + if err := Convert_v1_Handler_To_api_Handler(in.PreStop, out.PreStop, s); err != nil { + return err + } + } else { + out.PreStop = nil + } + return nil +} + +func Convert_v1_Lifecycle_To_api_Lifecycle(in *v1.Lifecycle, out *api.Lifecycle, s conversion.Scope) error { + return autoConvert_v1_Lifecycle_To_api_Lifecycle(in, out, s) +} + +func autoConvert_v1_LocalObjectReference_To_api_LocalObjectReference(in *v1.LocalObjectReference, out *api.LocalObjectReference, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.LocalObjectReference))(in) + } + out.Name = in.Name + return nil +} + +func Convert_v1_LocalObjectReference_To_api_LocalObjectReference(in *v1.LocalObjectReference, out *api.LocalObjectReference, s conversion.Scope) error { + return autoConvert_v1_LocalObjectReference_To_api_LocalObjectReference(in, out, s) +} + +func autoConvert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in *v1.NFSVolumeSource, out *api.NFSVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.NFSVolumeSource))(in) + } + out.Server = in.Server + out.Path = in.Path + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in *v1.NFSVolumeSource, out *api.NFSVolumeSource, s conversion.Scope) error { + return autoConvert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in, out, s) +} + +func autoConvert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector(in *v1.ObjectFieldSelector, out *api.ObjectFieldSelector, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.ObjectFieldSelector))(in) + } + out.APIVersion = in.APIVersion + out.FieldPath = in.FieldPath + return nil +} + +func Convert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector(in *v1.ObjectFieldSelector, out *api.ObjectFieldSelector, s conversion.Scope) error { + return autoConvert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector(in, out, s) +} + +func autoConvert_v1_ObjectMeta_To_api_ObjectMeta(in *v1.ObjectMeta, out *api.ObjectMeta, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.ObjectMeta))(in) + } + out.Name = in.Name + out.GenerateName = in.GenerateName + out.Namespace = in.Namespace + out.SelfLink = in.SelfLink + out.UID = in.UID + out.ResourceVersion = in.ResourceVersion + out.Generation = in.Generation + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.CreationTimestamp, &out.CreationTimestamp, s); err != nil { + return err + } + // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time + if in.DeletionTimestamp != nil { + out.DeletionTimestamp = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(in.DeletionTimestamp, out.DeletionTimestamp, s); err != nil { + return err + } + } else { + out.DeletionTimestamp = nil + } + if in.DeletionGracePeriodSeconds != nil { + out.DeletionGracePeriodSeconds = new(int64) + *out.DeletionGracePeriodSeconds = *in.DeletionGracePeriodSeconds + } else { + out.DeletionGracePeriodSeconds = nil + } + if in.Labels != nil { + out.Labels = make(map[string]string) + for key, val := range in.Labels { + out.Labels[key] = val + } + } else { + out.Labels = nil + } + if in.Annotations != nil { + out.Annotations = make(map[string]string) + for key, val := range in.Annotations { + out.Annotations[key] = val + } + } else { + out.Annotations = nil + } + return nil +} + +func Convert_v1_ObjectMeta_To_api_ObjectMeta(in *v1.ObjectMeta, out *api.ObjectMeta, s conversion.Scope) error { + return autoConvert_v1_ObjectMeta_To_api_ObjectMeta(in, out, s) +} + +func autoConvert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource(in *v1.PersistentVolumeClaimVolumeSource, out *api.PersistentVolumeClaimVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.PersistentVolumeClaimVolumeSource))(in) + } + out.ClaimName = in.ClaimName + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource(in *v1.PersistentVolumeClaimVolumeSource, out *api.PersistentVolumeClaimVolumeSource, s conversion.Scope) error { + return autoConvert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource(in, out, s) +} + +func autoConvert_v1_PodSpec_To_api_PodSpec(in *v1.PodSpec, out *api.PodSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.PodSpec))(in) + } + if in.Volumes != nil { + out.Volumes = make([]api.Volume, len(in.Volumes)) + for i := range in.Volumes { + if err := Convert_v1_Volume_To_api_Volume(&in.Volumes[i], &out.Volumes[i], s); err != nil { + return err + } + } + } else { + out.Volumes = nil + } + if in.Containers != nil { + out.Containers = make([]api.Container, len(in.Containers)) + for i := range in.Containers { + if err := Convert_v1_Container_To_api_Container(&in.Containers[i], &out.Containers[i], s); err != nil { + return err + } + } + } else { + out.Containers = nil + } + out.RestartPolicy = api.RestartPolicy(in.RestartPolicy) + if in.TerminationGracePeriodSeconds != nil { + out.TerminationGracePeriodSeconds = new(int64) + *out.TerminationGracePeriodSeconds = *in.TerminationGracePeriodSeconds + } else { + out.TerminationGracePeriodSeconds = nil + } + if in.ActiveDeadlineSeconds != nil { + out.ActiveDeadlineSeconds = new(int64) + *out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds + } else { + out.ActiveDeadlineSeconds = nil + } + out.DNSPolicy = api.DNSPolicy(in.DNSPolicy) + if in.NodeSelector != nil { + out.NodeSelector = make(map[string]string) + for key, val := range in.NodeSelector { + out.NodeSelector[key] = val + } + } else { + out.NodeSelector = nil + } + out.ServiceAccountName = in.ServiceAccountName + // in.DeprecatedServiceAccount has no peer in out + out.NodeName = in.NodeName + // in.HostNetwork has no peer in out + // in.HostPID has no peer in out + // in.HostIPC has no peer in out + // unable to generate simple pointer conversion for v1.PodSecurityContext -> api.PodSecurityContext + if in.SecurityContext != nil { + if err := s.Convert(&in.SecurityContext, &out.SecurityContext, 0); err != nil { + return err + } + } else { + out.SecurityContext = nil + } + if in.ImagePullSecrets != nil { + out.ImagePullSecrets = make([]api.LocalObjectReference, len(in.ImagePullSecrets)) + for i := range in.ImagePullSecrets { + if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(&in.ImagePullSecrets[i], &out.ImagePullSecrets[i], s); err != nil { + return err + } + } + } else { + out.ImagePullSecrets = nil + } + return nil +} + +func autoConvert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in *v1.PodTemplateSpec, out *api.PodTemplateSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.PodTemplateSpec))(in) + } + if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_v1_PodSpec_To_api_PodSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + return nil +} + +func Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in *v1.PodTemplateSpec, out *api.PodTemplateSpec, s conversion.Scope) error { + return autoConvert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in, out, s) +} + +func autoConvert_v1_Probe_To_api_Probe(in *v1.Probe, out *api.Probe, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.Probe))(in) + } + if err := Convert_v1_Handler_To_api_Handler(&in.Handler, &out.Handler, s); err != nil { + return err + } + out.InitialDelaySeconds = int(in.InitialDelaySeconds) + out.TimeoutSeconds = int(in.TimeoutSeconds) + out.PeriodSeconds = int(in.PeriodSeconds) + out.SuccessThreshold = int(in.SuccessThreshold) + out.FailureThreshold = int(in.FailureThreshold) + return nil +} + +func Convert_v1_Probe_To_api_Probe(in *v1.Probe, out *api.Probe, s conversion.Scope) error { + return autoConvert_v1_Probe_To_api_Probe(in, out, s) +} + +func autoConvert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in *v1.RBDVolumeSource, out *api.RBDVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.RBDVolumeSource))(in) + } + if in.CephMonitors != nil { + out.CephMonitors = make([]string, len(in.CephMonitors)) + for i := range in.CephMonitors { + out.CephMonitors[i] = in.CephMonitors[i] + } + } else { + out.CephMonitors = nil + } + out.RBDImage = in.RBDImage + out.FSType = in.FSType + out.RBDPool = in.RBDPool + out.RadosUser = in.RadosUser + out.Keyring = in.Keyring + // unable to generate simple pointer conversion for v1.LocalObjectReference -> api.LocalObjectReference + if in.SecretRef != nil { + out.SecretRef = new(api.LocalObjectReference) + if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(in.SecretRef, out.SecretRef, s); err != nil { + return err + } + } else { + out.SecretRef = nil + } + out.ReadOnly = in.ReadOnly + return nil +} + +func Convert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in *v1.RBDVolumeSource, out *api.RBDVolumeSource, s conversion.Scope) error { + return autoConvert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in, out, s) +} + +func autoConvert_v1_ResourceRequirements_To_api_ResourceRequirements(in *v1.ResourceRequirements, out *api.ResourceRequirements, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.ResourceRequirements))(in) + } + if in.Limits != nil { + out.Limits = make(api.ResourceList) + for key, val := range in.Limits { + newVal := resource.Quantity{} + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { + return err + } + out.Limits[api.ResourceName(key)] = newVal + } + } else { + out.Limits = nil + } + if in.Requests != nil { + out.Requests = make(api.ResourceList) + for key, val := range in.Requests { + newVal := resource.Quantity{} + if err := api.Convert_resource_Quantity_To_resource_Quantity(&val, &newVal, s); err != nil { + return err + } + out.Requests[api.ResourceName(key)] = newVal + } + } else { + out.Requests = nil + } + return nil +} + +func Convert_v1_ResourceRequirements_To_api_ResourceRequirements(in *v1.ResourceRequirements, out *api.ResourceRequirements, s conversion.Scope) error { + return autoConvert_v1_ResourceRequirements_To_api_ResourceRequirements(in, out, s) +} + +func autoConvert_v1_SELinuxOptions_To_api_SELinuxOptions(in *v1.SELinuxOptions, out *api.SELinuxOptions, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.SELinuxOptions))(in) + } + out.User = in.User + out.Role = in.Role + out.Type = in.Type + out.Level = in.Level + return nil +} + +func Convert_v1_SELinuxOptions_To_api_SELinuxOptions(in *v1.SELinuxOptions, out *api.SELinuxOptions, s conversion.Scope) error { + return autoConvert_v1_SELinuxOptions_To_api_SELinuxOptions(in, out, s) +} + +func autoConvert_v1_SecretKeySelector_To_api_SecretKeySelector(in *v1.SecretKeySelector, out *api.SecretKeySelector, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.SecretKeySelector))(in) + } + if err := Convert_v1_LocalObjectReference_To_api_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { + return err + } + out.Key = in.Key + return nil +} + +func Convert_v1_SecretKeySelector_To_api_SecretKeySelector(in *v1.SecretKeySelector, out *api.SecretKeySelector, s conversion.Scope) error { + return autoConvert_v1_SecretKeySelector_To_api_SecretKeySelector(in, out, s) +} + +func autoConvert_v1_SecretVolumeSource_To_api_SecretVolumeSource(in *v1.SecretVolumeSource, out *api.SecretVolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.SecretVolumeSource))(in) + } + out.SecretName = in.SecretName + return nil +} + +func Convert_v1_SecretVolumeSource_To_api_SecretVolumeSource(in *v1.SecretVolumeSource, out *api.SecretVolumeSource, s conversion.Scope) error { + return autoConvert_v1_SecretVolumeSource_To_api_SecretVolumeSource(in, out, s) +} + +func autoConvert_v1_SecurityContext_To_api_SecurityContext(in *v1.SecurityContext, out *api.SecurityContext, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.SecurityContext))(in) + } + // unable to generate simple pointer conversion for v1.Capabilities -> api.Capabilities + if in.Capabilities != nil { + out.Capabilities = new(api.Capabilities) + if err := Convert_v1_Capabilities_To_api_Capabilities(in.Capabilities, out.Capabilities, s); err != nil { + return err + } + } else { + out.Capabilities = nil + } + if in.Privileged != nil { + out.Privileged = new(bool) + *out.Privileged = *in.Privileged + } else { + out.Privileged = nil + } + // unable to generate simple pointer conversion for v1.SELinuxOptions -> api.SELinuxOptions + if in.SELinuxOptions != nil { + out.SELinuxOptions = new(api.SELinuxOptions) + if err := Convert_v1_SELinuxOptions_To_api_SELinuxOptions(in.SELinuxOptions, out.SELinuxOptions, s); err != nil { + return err + } + } else { + out.SELinuxOptions = nil + } + if in.RunAsUser != nil { + out.RunAsUser = new(int64) + *out.RunAsUser = *in.RunAsUser + } else { + out.RunAsUser = nil + } + if in.RunAsNonRoot != nil { + out.RunAsNonRoot = new(bool) + *out.RunAsNonRoot = *in.RunAsNonRoot + } else { + out.RunAsNonRoot = nil + } + if in.ReadOnlyRootFilesystem != nil { + out.ReadOnlyRootFilesystem = new(bool) + *out.ReadOnlyRootFilesystem = *in.ReadOnlyRootFilesystem + } else { + out.ReadOnlyRootFilesystem = nil + } + return nil +} + +func Convert_v1_SecurityContext_To_api_SecurityContext(in *v1.SecurityContext, out *api.SecurityContext, s conversion.Scope) error { + return autoConvert_v1_SecurityContext_To_api_SecurityContext(in, out, s) +} + +func autoConvert_v1_TCPSocketAction_To_api_TCPSocketAction(in *v1.TCPSocketAction, out *api.TCPSocketAction, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.TCPSocketAction))(in) + } + if err := api.Convert_intstr_IntOrString_To_intstr_IntOrString(&in.Port, &out.Port, s); err != nil { + return err + } + return nil +} + +func Convert_v1_TCPSocketAction_To_api_TCPSocketAction(in *v1.TCPSocketAction, out *api.TCPSocketAction, s conversion.Scope) error { + return autoConvert_v1_TCPSocketAction_To_api_TCPSocketAction(in, out, s) +} + +func autoConvert_v1_Volume_To_api_Volume(in *v1.Volume, out *api.Volume, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.Volume))(in) + } + out.Name = in.Name + if err := Convert_v1_VolumeSource_To_api_VolumeSource(&in.VolumeSource, &out.VolumeSource, s); err != nil { + return err + } + return nil +} + +func Convert_v1_Volume_To_api_Volume(in *v1.Volume, out *api.Volume, s conversion.Scope) error { + return autoConvert_v1_Volume_To_api_Volume(in, out, s) +} + +func autoConvert_v1_VolumeMount_To_api_VolumeMount(in *v1.VolumeMount, out *api.VolumeMount, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.VolumeMount))(in) + } + out.Name = in.Name + out.ReadOnly = in.ReadOnly + out.MountPath = in.MountPath + return nil +} + +func Convert_v1_VolumeMount_To_api_VolumeMount(in *v1.VolumeMount, out *api.VolumeMount, s conversion.Scope) error { + return autoConvert_v1_VolumeMount_To_api_VolumeMount(in, out, s) +} + +func autoConvert_v1_VolumeSource_To_api_VolumeSource(in *v1.VolumeSource, out *api.VolumeSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*v1.VolumeSource))(in) + } + // unable to generate simple pointer conversion for v1.HostPathVolumeSource -> api.HostPathVolumeSource + if in.HostPath != nil { + out.HostPath = new(api.HostPathVolumeSource) + if err := Convert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource(in.HostPath, out.HostPath, s); err != nil { + return err + } + } else { + out.HostPath = nil + } + // unable to generate simple pointer conversion for v1.EmptyDirVolumeSource -> api.EmptyDirVolumeSource + if in.EmptyDir != nil { + out.EmptyDir = new(api.EmptyDirVolumeSource) + if err := Convert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource(in.EmptyDir, out.EmptyDir, s); err != nil { + return err + } + } else { + out.EmptyDir = nil + } + // unable to generate simple pointer conversion for v1.GCEPersistentDiskVolumeSource -> api.GCEPersistentDiskVolumeSource + if in.GCEPersistentDisk != nil { + out.GCEPersistentDisk = new(api.GCEPersistentDiskVolumeSource) + if err := Convert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource(in.GCEPersistentDisk, out.GCEPersistentDisk, s); err != nil { + return err + } + } else { + out.GCEPersistentDisk = nil + } + // unable to generate simple pointer conversion for v1.AWSElasticBlockStoreVolumeSource -> api.AWSElasticBlockStoreVolumeSource + if in.AWSElasticBlockStore != nil { + out.AWSElasticBlockStore = new(api.AWSElasticBlockStoreVolumeSource) + if err := Convert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource(in.AWSElasticBlockStore, out.AWSElasticBlockStore, s); err != nil { + return err + } + } else { + out.AWSElasticBlockStore = nil + } + // unable to generate simple pointer conversion for v1.GitRepoVolumeSource -> api.GitRepoVolumeSource + if in.GitRepo != nil { + out.GitRepo = new(api.GitRepoVolumeSource) + if err := Convert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource(in.GitRepo, out.GitRepo, s); err != nil { + return err + } + } else { + out.GitRepo = nil + } + // unable to generate simple pointer conversion for v1.SecretVolumeSource -> api.SecretVolumeSource + if in.Secret != nil { + out.Secret = new(api.SecretVolumeSource) + if err := Convert_v1_SecretVolumeSource_To_api_SecretVolumeSource(in.Secret, out.Secret, s); err != nil { + return err + } + } else { + out.Secret = nil + } + // unable to generate simple pointer conversion for v1.NFSVolumeSource -> api.NFSVolumeSource + if in.NFS != nil { + out.NFS = new(api.NFSVolumeSource) + if err := Convert_v1_NFSVolumeSource_To_api_NFSVolumeSource(in.NFS, out.NFS, s); err != nil { + return err + } + } else { + out.NFS = nil + } + // unable to generate simple pointer conversion for v1.ISCSIVolumeSource -> api.ISCSIVolumeSource + if in.ISCSI != nil { + out.ISCSI = new(api.ISCSIVolumeSource) + if err := Convert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource(in.ISCSI, out.ISCSI, s); err != nil { + return err + } + } else { + out.ISCSI = nil + } + // unable to generate simple pointer conversion for v1.GlusterfsVolumeSource -> api.GlusterfsVolumeSource + if in.Glusterfs != nil { + out.Glusterfs = new(api.GlusterfsVolumeSource) + if err := Convert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource(in.Glusterfs, out.Glusterfs, s); err != nil { + return err + } + } else { + out.Glusterfs = nil + } + // unable to generate simple pointer conversion for v1.PersistentVolumeClaimVolumeSource -> api.PersistentVolumeClaimVolumeSource + if in.PersistentVolumeClaim != nil { + out.PersistentVolumeClaim = new(api.PersistentVolumeClaimVolumeSource) + if err := Convert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource(in.PersistentVolumeClaim, out.PersistentVolumeClaim, s); err != nil { + return err + } + } else { + out.PersistentVolumeClaim = nil + } + // unable to generate simple pointer conversion for v1.RBDVolumeSource -> api.RBDVolumeSource + if in.RBD != nil { + out.RBD = new(api.RBDVolumeSource) + if err := Convert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in.RBD, out.RBD, s); err != nil { + return err + } + } else { + out.RBD = nil + } + // unable to generate simple pointer conversion for v1.FlexVolumeSource -> api.FlexVolumeSource + if in.FlexVolume != nil { + out.FlexVolume = new(api.FlexVolumeSource) + if err := Convert_v1_FlexVolumeSource_To_api_FlexVolumeSource(in.FlexVolume, out.FlexVolume, s); err != nil { + return err + } + } else { + out.FlexVolume = nil + } + // unable to generate simple pointer conversion for v1.CinderVolumeSource -> api.CinderVolumeSource + if in.Cinder != nil { + out.Cinder = new(api.CinderVolumeSource) + if err := Convert_v1_CinderVolumeSource_To_api_CinderVolumeSource(in.Cinder, out.Cinder, s); err != nil { + return err + } + } else { + out.Cinder = nil + } + // unable to generate simple pointer conversion for v1.CephFSVolumeSource -> api.CephFSVolumeSource + if in.CephFS != nil { + out.CephFS = new(api.CephFSVolumeSource) + if err := Convert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource(in.CephFS, out.CephFS, s); err != nil { + return err + } + } else { + out.CephFS = nil + } + // unable to generate simple pointer conversion for v1.FlockerVolumeSource -> api.FlockerVolumeSource + if in.Flocker != nil { + out.Flocker = new(api.FlockerVolumeSource) + if err := Convert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource(in.Flocker, out.Flocker, s); err != nil { + return err + } + } else { + out.Flocker = nil + } + // unable to generate simple pointer conversion for v1.DownwardAPIVolumeSource -> api.DownwardAPIVolumeSource + if in.DownwardAPI != nil { + out.DownwardAPI = new(api.DownwardAPIVolumeSource) + if err := Convert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource(in.DownwardAPI, out.DownwardAPI, s); err != nil { + return err + } + } else { + out.DownwardAPI = nil + } + // unable to generate simple pointer conversion for v1.FCVolumeSource -> api.FCVolumeSource + if in.FC != nil { + out.FC = new(api.FCVolumeSource) + if err := Convert_v1_FCVolumeSource_To_api_FCVolumeSource(in.FC, out.FC, s); err != nil { + return err + } + } else { + out.FC = nil + } + // unable to generate simple pointer conversion for v1.AzureFileVolumeSource -> api.AzureFileVolumeSource + if in.AzureFile != nil { + out.AzureFile = new(api.AzureFileVolumeSource) + if err := Convert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource(in.AzureFile, out.AzureFile, s); err != nil { + return err + } + } else { + out.AzureFile = nil + } + // unable to generate simple pointer conversion for v1.ConfigMapVolumeSource -> api.ConfigMapVolumeSource + if in.ConfigMap != nil { + out.ConfigMap = new(api.ConfigMapVolumeSource) + if err := Convert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource(in.ConfigMap, out.ConfigMap, s); err != nil { + return err + } + } else { + out.ConfigMap = nil + } + return nil +} + +func Convert_v1_VolumeSource_To_api_VolumeSource(in *v1.VolumeSource, out *api.VolumeSource, s conversion.Scope) error { + return autoConvert_v1_VolumeSource_To_api_VolumeSource(in, out, s) +} + +func autoConvert_v1_Job_To_extensions_Job(in *Job, out *extensions.Job, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*Job))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_v1_JobSpec_To_extensions_JobSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1_JobStatus_To_extensions_JobStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_v1_Job_To_extensions_Job(in *Job, out *extensions.Job, s conversion.Scope) error { + return autoConvert_v1_Job_To_extensions_Job(in, out, s) +} + +func autoConvert_v1_JobCondition_To_extensions_JobCondition(in *JobCondition, out *extensions.JobCondition, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*JobCondition))(in) + } + out.Type = extensions.JobConditionType(in.Type) + out.Status = api.ConditionStatus(in.Status) + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastProbeTime, &out.LastProbeTime, s); err != nil { + return err + } + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastTransitionTime, &out.LastTransitionTime, s); err != nil { + return err + } + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +func Convert_v1_JobCondition_To_extensions_JobCondition(in *JobCondition, out *extensions.JobCondition, s conversion.Scope) error { + return autoConvert_v1_JobCondition_To_extensions_JobCondition(in, out, s) +} + +func autoConvert_v1_JobList_To_extensions_JobList(in *JobList, out *extensions.JobList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*JobList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + out.Items = make([]extensions.Job, len(in.Items)) + for i := range in.Items { + if err := Convert_v1_Job_To_extensions_Job(&in.Items[i], &out.Items[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_v1_JobList_To_extensions_JobList(in *JobList, out *extensions.JobList, s conversion.Scope) error { + return autoConvert_v1_JobList_To_extensions_JobList(in, out, s) +} + +func autoConvert_v1_JobSpec_To_extensions_JobSpec(in *JobSpec, out *extensions.JobSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*JobSpec))(in) + } + if in.Parallelism != nil { + out.Parallelism = new(int) + *out.Parallelism = int(*in.Parallelism) + } else { + out.Parallelism = nil + } + if in.Completions != nil { + out.Completions = new(int) + *out.Completions = int(*in.Completions) + } else { + out.Completions = nil + } + if in.ActiveDeadlineSeconds != nil { + out.ActiveDeadlineSeconds = new(int64) + *out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds + } else { + out.ActiveDeadlineSeconds = nil + } + // unable to generate simple pointer conversion for v1.LabelSelector -> unversioned.LabelSelector + if in.Selector != nil { + out.Selector = new(unversioned.LabelSelector) + if err := Convert_v1_LabelSelector_To_unversioned_LabelSelector(in.Selector, out.Selector, s); err != nil { + return err + } + } else { + out.Selector = nil + } + if in.ManualSelector != nil { + out.ManualSelector = new(bool) + *out.ManualSelector = *in.ManualSelector + } else { + out.ManualSelector = nil + } + if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + return nil +} + +func Convert_v1_JobSpec_To_extensions_JobSpec(in *JobSpec, out *extensions.JobSpec, s conversion.Scope) error { + return autoConvert_v1_JobSpec_To_extensions_JobSpec(in, out, s) +} + +func autoConvert_v1_JobStatus_To_extensions_JobStatus(in *JobStatus, out *extensions.JobStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*JobStatus))(in) + } + if in.Conditions != nil { + out.Conditions = make([]extensions.JobCondition, len(in.Conditions)) + for i := range in.Conditions { + if err := Convert_v1_JobCondition_To_extensions_JobCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil { + return err + } + } + } else { + out.Conditions = nil + } + // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time + if in.StartTime != nil { + out.StartTime = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(in.StartTime, out.StartTime, s); err != nil { + return err + } + } else { + out.StartTime = nil + } + // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time + if in.CompletionTime != nil { + out.CompletionTime = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(in.CompletionTime, out.CompletionTime, s); err != nil { + return err + } + } else { + out.CompletionTime = nil + } + out.Active = int(in.Active) + out.Succeeded = int(in.Succeeded) + out.Failed = int(in.Failed) + return nil +} + +func Convert_v1_JobStatus_To_extensions_JobStatus(in *JobStatus, out *extensions.JobStatus, s conversion.Scope) error { + return autoConvert_v1_JobStatus_To_extensions_JobStatus(in, out, s) +} + +func autoConvert_v1_LabelSelector_To_unversioned_LabelSelector(in *LabelSelector, out *unversioned.LabelSelector, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*LabelSelector))(in) + } + if in.MatchLabels != nil { + out.MatchLabels = make(map[string]string) + for key, val := range in.MatchLabels { + out.MatchLabels[key] = val + } + } else { + out.MatchLabels = nil + } + if in.MatchExpressions != nil { + out.MatchExpressions = make([]unversioned.LabelSelectorRequirement, len(in.MatchExpressions)) + for i := range in.MatchExpressions { + if err := Convert_v1_LabelSelectorRequirement_To_unversioned_LabelSelectorRequirement(&in.MatchExpressions[i], &out.MatchExpressions[i], s); err != nil { + return err + } + } + } else { + out.MatchExpressions = nil + } + return nil +} + +func Convert_v1_LabelSelector_To_unversioned_LabelSelector(in *LabelSelector, out *unversioned.LabelSelector, s conversion.Scope) error { + return autoConvert_v1_LabelSelector_To_unversioned_LabelSelector(in, out, s) +} + +func autoConvert_v1_LabelSelectorRequirement_To_unversioned_LabelSelectorRequirement(in *LabelSelectorRequirement, out *unversioned.LabelSelectorRequirement, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*LabelSelectorRequirement))(in) + } + out.Key = in.Key + out.Operator = unversioned.LabelSelectorOperator(in.Operator) + if in.Values != nil { + out.Values = make([]string, len(in.Values)) + for i := range in.Values { + out.Values[i] = in.Values[i] + } + } else { + out.Values = nil + } + return nil +} + +func Convert_v1_LabelSelectorRequirement_To_unversioned_LabelSelectorRequirement(in *LabelSelectorRequirement, out *unversioned.LabelSelectorRequirement, s conversion.Scope) error { + return autoConvert_v1_LabelSelectorRequirement_To_unversioned_LabelSelectorRequirement(in, out, s) +} + +func autoConvert_extensions_Job_To_v1_Job(in *extensions.Job, out *Job, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*extensions.Job))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { + return err + } + if err := Convert_extensions_JobSpec_To_v1_JobSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_extensions_JobStatus_To_v1_JobStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +func Convert_extensions_Job_To_v1_Job(in *extensions.Job, out *Job, s conversion.Scope) error { + return autoConvert_extensions_Job_To_v1_Job(in, out, s) +} + +func autoConvert_extensions_JobCondition_To_v1_JobCondition(in *extensions.JobCondition, out *JobCondition, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*extensions.JobCondition))(in) + } + out.Type = JobConditionType(in.Type) + out.Status = v1.ConditionStatus(in.Status) + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastProbeTime, &out.LastProbeTime, s); err != nil { + return err + } + if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastTransitionTime, &out.LastTransitionTime, s); err != nil { + return err + } + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +func Convert_extensions_JobCondition_To_v1_JobCondition(in *extensions.JobCondition, out *JobCondition, s conversion.Scope) error { + return autoConvert_extensions_JobCondition_To_v1_JobCondition(in, out, s) +} + +func autoConvert_extensions_JobList_To_v1_JobList(in *extensions.JobList, out *JobList, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*extensions.JobList))(in) + } + if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { + return err + } + if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { + return err + } + if in.Items != nil { + out.Items = make([]Job, len(in.Items)) + for i := range in.Items { + if err := Convert_extensions_Job_To_v1_Job(&in.Items[i], &out.Items[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func Convert_extensions_JobList_To_v1_JobList(in *extensions.JobList, out *JobList, s conversion.Scope) error { + return autoConvert_extensions_JobList_To_v1_JobList(in, out, s) +} + +func autoConvert_extensions_JobSpec_To_v1_JobSpec(in *extensions.JobSpec, out *JobSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*extensions.JobSpec))(in) + } + if in.Parallelism != nil { + out.Parallelism = new(int32) + *out.Parallelism = int32(*in.Parallelism) + } else { + out.Parallelism = nil + } + if in.Completions != nil { + out.Completions = new(int32) + *out.Completions = int32(*in.Completions) + } else { + out.Completions = nil + } + if in.ActiveDeadlineSeconds != nil { + out.ActiveDeadlineSeconds = new(int64) + *out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds + } else { + out.ActiveDeadlineSeconds = nil + } + // unable to generate simple pointer conversion for unversioned.LabelSelector -> v1.LabelSelector + if in.Selector != nil { + out.Selector = new(LabelSelector) + if err := Convert_unversioned_LabelSelector_To_v1_LabelSelector(in.Selector, out.Selector, s); err != nil { + return err + } + } else { + out.Selector = nil + } + if in.ManualSelector != nil { + out.ManualSelector = new(bool) + *out.ManualSelector = *in.ManualSelector + } else { + out.ManualSelector = nil + } + if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + return nil +} + +func Convert_extensions_JobSpec_To_v1_JobSpec(in *extensions.JobSpec, out *JobSpec, s conversion.Scope) error { + return autoConvert_extensions_JobSpec_To_v1_JobSpec(in, out, s) +} + +func autoConvert_extensions_JobStatus_To_v1_JobStatus(in *extensions.JobStatus, out *JobStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*extensions.JobStatus))(in) + } + if in.Conditions != nil { + out.Conditions = make([]JobCondition, len(in.Conditions)) + for i := range in.Conditions { + if err := Convert_extensions_JobCondition_To_v1_JobCondition(&in.Conditions[i], &out.Conditions[i], s); err != nil { + return err + } + } + } else { + out.Conditions = nil + } + // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time + if in.StartTime != nil { + out.StartTime = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(in.StartTime, out.StartTime, s); err != nil { + return err + } + } else { + out.StartTime = nil + } + // unable to generate simple pointer conversion for unversioned.Time -> unversioned.Time + if in.CompletionTime != nil { + out.CompletionTime = new(unversioned.Time) + if err := api.Convert_unversioned_Time_To_unversioned_Time(in.CompletionTime, out.CompletionTime, s); err != nil { + return err + } + } else { + out.CompletionTime = nil + } + out.Active = int32(in.Active) + out.Succeeded = int32(in.Succeeded) + out.Failed = int32(in.Failed) + return nil +} + +func Convert_extensions_JobStatus_To_v1_JobStatus(in *extensions.JobStatus, out *JobStatus, s conversion.Scope) error { + return autoConvert_extensions_JobStatus_To_v1_JobStatus(in, out, s) +} + +func init() { + err := api.Scheme.AddGeneratedConversionFuncs( + autoConvert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource, + autoConvert_api_AzureFileVolumeSource_To_v1_AzureFileVolumeSource, + autoConvert_api_Capabilities_To_v1_Capabilities, + autoConvert_api_CephFSVolumeSource_To_v1_CephFSVolumeSource, + autoConvert_api_CinderVolumeSource_To_v1_CinderVolumeSource, + autoConvert_api_ConfigMapKeySelector_To_v1_ConfigMapKeySelector, + autoConvert_api_ConfigMapVolumeSource_To_v1_ConfigMapVolumeSource, + autoConvert_api_ContainerPort_To_v1_ContainerPort, + autoConvert_api_Container_To_v1_Container, + autoConvert_api_DownwardAPIVolumeFile_To_v1_DownwardAPIVolumeFile, + autoConvert_api_DownwardAPIVolumeSource_To_v1_DownwardAPIVolumeSource, + autoConvert_api_EmptyDirVolumeSource_To_v1_EmptyDirVolumeSource, + autoConvert_api_EnvVarSource_To_v1_EnvVarSource, + autoConvert_api_EnvVar_To_v1_EnvVar, + autoConvert_api_ExecAction_To_v1_ExecAction, + autoConvert_api_FCVolumeSource_To_v1_FCVolumeSource, + autoConvert_api_FlexVolumeSource_To_v1_FlexVolumeSource, + autoConvert_api_FlockerVolumeSource_To_v1_FlockerVolumeSource, + autoConvert_api_GCEPersistentDiskVolumeSource_To_v1_GCEPersistentDiskVolumeSource, + autoConvert_api_GitRepoVolumeSource_To_v1_GitRepoVolumeSource, + autoConvert_api_GlusterfsVolumeSource_To_v1_GlusterfsVolumeSource, + autoConvert_api_HTTPGetAction_To_v1_HTTPGetAction, + autoConvert_api_HTTPHeader_To_v1_HTTPHeader, + autoConvert_api_Handler_To_v1_Handler, + autoConvert_api_HostPathVolumeSource_To_v1_HostPathVolumeSource, + autoConvert_api_ISCSIVolumeSource_To_v1_ISCSIVolumeSource, + autoConvert_api_KeyToPath_To_v1_KeyToPath, + autoConvert_api_Lifecycle_To_v1_Lifecycle, + autoConvert_api_LocalObjectReference_To_v1_LocalObjectReference, + autoConvert_api_NFSVolumeSource_To_v1_NFSVolumeSource, + autoConvert_api_ObjectFieldSelector_To_v1_ObjectFieldSelector, + autoConvert_api_ObjectMeta_To_v1_ObjectMeta, + autoConvert_api_PersistentVolumeClaimVolumeSource_To_v1_PersistentVolumeClaimVolumeSource, + autoConvert_api_PodSpec_To_v1_PodSpec, + autoConvert_api_PodTemplateSpec_To_v1_PodTemplateSpec, + autoConvert_api_Probe_To_v1_Probe, + autoConvert_api_RBDVolumeSource_To_v1_RBDVolumeSource, + autoConvert_api_ResourceRequirements_To_v1_ResourceRequirements, + autoConvert_api_SELinuxOptions_To_v1_SELinuxOptions, + autoConvert_api_SecretKeySelector_To_v1_SecretKeySelector, + autoConvert_api_SecretVolumeSource_To_v1_SecretVolumeSource, + autoConvert_api_SecurityContext_To_v1_SecurityContext, + autoConvert_api_TCPSocketAction_To_v1_TCPSocketAction, + autoConvert_api_VolumeMount_To_v1_VolumeMount, + autoConvert_api_VolumeSource_To_v1_VolumeSource, + autoConvert_api_Volume_To_v1_Volume, + autoConvert_extensions_JobCondition_To_v1_JobCondition, + autoConvert_extensions_JobList_To_v1_JobList, + autoConvert_extensions_JobSpec_To_v1_JobSpec, + autoConvert_extensions_JobStatus_To_v1_JobStatus, + autoConvert_extensions_Job_To_v1_Job, + autoConvert_unversioned_LabelSelectorRequirement_To_v1_LabelSelectorRequirement, + autoConvert_unversioned_LabelSelector_To_v1_LabelSelector, + autoConvert_v1_AWSElasticBlockStoreVolumeSource_To_api_AWSElasticBlockStoreVolumeSource, + autoConvert_v1_AzureFileVolumeSource_To_api_AzureFileVolumeSource, + autoConvert_v1_Capabilities_To_api_Capabilities, + autoConvert_v1_CephFSVolumeSource_To_api_CephFSVolumeSource, + autoConvert_v1_CinderVolumeSource_To_api_CinderVolumeSource, + autoConvert_v1_ConfigMapKeySelector_To_api_ConfigMapKeySelector, + autoConvert_v1_ConfigMapVolumeSource_To_api_ConfigMapVolumeSource, + autoConvert_v1_ContainerPort_To_api_ContainerPort, + autoConvert_v1_Container_To_api_Container, + autoConvert_v1_DownwardAPIVolumeFile_To_api_DownwardAPIVolumeFile, + autoConvert_v1_DownwardAPIVolumeSource_To_api_DownwardAPIVolumeSource, + autoConvert_v1_EmptyDirVolumeSource_To_api_EmptyDirVolumeSource, + autoConvert_v1_EnvVarSource_To_api_EnvVarSource, + autoConvert_v1_EnvVar_To_api_EnvVar, + autoConvert_v1_ExecAction_To_api_ExecAction, + autoConvert_v1_FCVolumeSource_To_api_FCVolumeSource, + autoConvert_v1_FlexVolumeSource_To_api_FlexVolumeSource, + autoConvert_v1_FlockerVolumeSource_To_api_FlockerVolumeSource, + autoConvert_v1_GCEPersistentDiskVolumeSource_To_api_GCEPersistentDiskVolumeSource, + autoConvert_v1_GitRepoVolumeSource_To_api_GitRepoVolumeSource, + autoConvert_v1_GlusterfsVolumeSource_To_api_GlusterfsVolumeSource, + autoConvert_v1_HTTPGetAction_To_api_HTTPGetAction, + autoConvert_v1_HTTPHeader_To_api_HTTPHeader, + autoConvert_v1_Handler_To_api_Handler, + autoConvert_v1_HostPathVolumeSource_To_api_HostPathVolumeSource, + autoConvert_v1_ISCSIVolumeSource_To_api_ISCSIVolumeSource, + autoConvert_v1_JobCondition_To_extensions_JobCondition, + autoConvert_v1_JobList_To_extensions_JobList, + autoConvert_v1_JobSpec_To_extensions_JobSpec, + autoConvert_v1_JobStatus_To_extensions_JobStatus, + autoConvert_v1_Job_To_extensions_Job, + autoConvert_v1_KeyToPath_To_api_KeyToPath, + autoConvert_v1_LabelSelectorRequirement_To_unversioned_LabelSelectorRequirement, + autoConvert_v1_LabelSelector_To_unversioned_LabelSelector, + autoConvert_v1_Lifecycle_To_api_Lifecycle, + autoConvert_v1_LocalObjectReference_To_api_LocalObjectReference, + autoConvert_v1_NFSVolumeSource_To_api_NFSVolumeSource, + autoConvert_v1_ObjectFieldSelector_To_api_ObjectFieldSelector, + autoConvert_v1_ObjectMeta_To_api_ObjectMeta, + autoConvert_v1_PersistentVolumeClaimVolumeSource_To_api_PersistentVolumeClaimVolumeSource, + autoConvert_v1_PodSpec_To_api_PodSpec, + autoConvert_v1_PodTemplateSpec_To_api_PodTemplateSpec, + autoConvert_v1_Probe_To_api_Probe, + autoConvert_v1_RBDVolumeSource_To_api_RBDVolumeSource, + autoConvert_v1_ResourceRequirements_To_api_ResourceRequirements, + autoConvert_v1_SELinuxOptions_To_api_SELinuxOptions, + autoConvert_v1_SecretKeySelector_To_api_SecretKeySelector, + autoConvert_v1_SecretVolumeSource_To_api_SecretVolumeSource, + autoConvert_v1_SecurityContext_To_api_SecurityContext, + autoConvert_v1_TCPSocketAction_To_api_TCPSocketAction, + autoConvert_v1_VolumeMount_To_api_VolumeMount, + autoConvert_v1_VolumeSource_To_api_VolumeSource, + autoConvert_v1_Volume_To_api_Volume, + ) + if err != nil { + // If one of the conversion functions is malformed, detect it immediately. + panic(err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/deep_copy_generated.go new file mode 100644 index 000000000..c2a50b4ee --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/deep_copy_generated.go @@ -0,0 +1,211 @@ +// +build !ignore_autogenerated + +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + api_v1 "k8s.io/kubernetes/pkg/api/v1" + conversion "k8s.io/kubernetes/pkg/conversion" +) + +func init() { + if err := api.Scheme.AddGeneratedDeepCopyFuncs( + DeepCopy_v1_Job, + DeepCopy_v1_JobCondition, + DeepCopy_v1_JobList, + DeepCopy_v1_JobSpec, + DeepCopy_v1_JobStatus, + DeepCopy_v1_LabelSelector, + DeepCopy_v1_LabelSelectorRequirement, + ); err != nil { + // if one of the deep copy functions is malformed, detect it immediately. + panic(err) + } +} + +func DeepCopy_v1_Job(in Job, out *Job, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api_v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := DeepCopy_v1_JobSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_v1_JobStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_v1_JobCondition(in JobCondition, out *JobCondition, c *conversion.Cloner) error { + out.Type = in.Type + out.Status = in.Status + if err := unversioned.DeepCopy_unversioned_Time(in.LastProbeTime, &out.LastProbeTime, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil { + return err + } + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +func DeepCopy_v1_JobList(in JobList, out *JobList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]Job, len(in)) + for i := range in { + if err := DeepCopy_v1_Job(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func DeepCopy_v1_JobSpec(in JobSpec, out *JobSpec, c *conversion.Cloner) error { + if in.Parallelism != nil { + in, out := in.Parallelism, &out.Parallelism + *out = new(int32) + **out = *in + } else { + out.Parallelism = nil + } + if in.Completions != nil { + in, out := in.Completions, &out.Completions + *out = new(int32) + **out = *in + } else { + out.Completions = nil + } + if in.ActiveDeadlineSeconds != nil { + in, out := in.ActiveDeadlineSeconds, &out.ActiveDeadlineSeconds + *out = new(int64) + **out = *in + } else { + out.ActiveDeadlineSeconds = nil + } + if in.Selector != nil { + in, out := in.Selector, &out.Selector + *out = new(LabelSelector) + if err := DeepCopy_v1_LabelSelector(*in, *out, c); err != nil { + return err + } + } else { + out.Selector = nil + } + if in.ManualSelector != nil { + in, out := in.ManualSelector, &out.ManualSelector + *out = new(bool) + **out = *in + } else { + out.ManualSelector = nil + } + if err := api_v1.DeepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil { + return err + } + return nil +} + +func DeepCopy_v1_JobStatus(in JobStatus, out *JobStatus, c *conversion.Cloner) error { + if in.Conditions != nil { + in, out := in.Conditions, &out.Conditions + *out = make([]JobCondition, len(in)) + for i := range in { + if err := DeepCopy_v1_JobCondition(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Conditions = nil + } + if in.StartTime != nil { + in, out := in.StartTime, &out.StartTime + *out = new(unversioned.Time) + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { + return err + } + } else { + out.StartTime = nil + } + if in.CompletionTime != nil { + in, out := in.CompletionTime, &out.CompletionTime + *out = new(unversioned.Time) + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { + return err + } + } else { + out.CompletionTime = nil + } + out.Active = in.Active + out.Succeeded = in.Succeeded + out.Failed = in.Failed + return nil +} + +func DeepCopy_v1_LabelSelector(in LabelSelector, out *LabelSelector, c *conversion.Cloner) error { + if in.MatchLabels != nil { + in, out := in.MatchLabels, &out.MatchLabels + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val + } + } else { + out.MatchLabels = nil + } + if in.MatchExpressions != nil { + in, out := in.MatchExpressions, &out.MatchExpressions + *out = make([]LabelSelectorRequirement, len(in)) + for i := range in { + if err := DeepCopy_v1_LabelSelectorRequirement(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.MatchExpressions = nil + } + return nil +} + +func DeepCopy_v1_LabelSelectorRequirement(in LabelSelectorRequirement, out *LabelSelectorRequirement, c *conversion.Cloner) error { + out.Key = in.Key + out.Operator = in.Operator + if in.Values != nil { + in, out := in.Values, &out.Values + *out = make([]string, len(in)) + copy(*out, in) + } else { + out.Values = nil + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/defaults.go b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/defaults.go new file mode 100644 index 000000000..759ab0fb6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/defaults.go @@ -0,0 +1,40 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "k8s.io/kubernetes/pkg/runtime" +) + +func addDefaultingFuncs(scheme *runtime.Scheme) { + scheme.AddDefaultingFuncs( + func(obj *Job) { + // For a non-parallel job, you can leave both `.spec.completions` and + // `.spec.parallelism` unset. When both are unset, both are defaulted to 1. + if obj.Spec.Completions == nil && obj.Spec.Parallelism == nil { + obj.Spec.Completions = new(int32) + *obj.Spec.Completions = 1 + obj.Spec.Parallelism = new(int32) + *obj.Spec.Parallelism = 1 + } + if obj.Spec.Parallelism == nil { + obj.Spec.Parallelism = new(int32) + *obj.Spec.Parallelism = 1 + } + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/register.go b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/register.go new file mode 100644 index 000000000..a8c5e484c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/register.go @@ -0,0 +1,49 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/runtime" + versionedwatch "k8s.io/kubernetes/pkg/watch/versioned" +) + +// GroupName is the group name use in this package +const GroupName = "batch" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = unversioned.GroupVersion{Group: GroupName, Version: "v1"} + +func AddToScheme(scheme *runtime.Scheme) { + addKnownTypes(scheme) + addDefaultingFuncs(scheme) + addConversionFuncs(scheme) +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) { + scheme.AddKnownTypes(SchemeGroupVersion, + &Job{}, + &JobList{}, + &v1.ListOptions{}, + ) + versionedwatch.AddToGroupVersion(scheme, SchemeGroupVersion) +} + +func (obj *Job) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } +func (obj *JobList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/types.generated.go b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/types.generated.go new file mode 100644 index 000000000..2c07db216 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/types.generated.go @@ -0,0 +1,3186 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// ************************************************************ +// DO NOT EDIT. +// THIS FILE IS AUTO-GENERATED BY codecgen. +// ************************************************************ + +package v1 + +import ( + "errors" + "fmt" + codec1978 "github.com/ugorji/go/codec" + pkg4_resource "k8s.io/kubernetes/pkg/api/resource" + pkg1_unversioned "k8s.io/kubernetes/pkg/api/unversioned" + pkg2_v1 "k8s.io/kubernetes/pkg/api/v1" + pkg3_types "k8s.io/kubernetes/pkg/types" + pkg6_intstr "k8s.io/kubernetes/pkg/util/intstr" + "reflect" + "runtime" + pkg5_inf "speter.net/go/exp/math/dec/inf" + time "time" +) + +const ( + // ----- content types ---- + codecSelferC_UTF81234 = 1 + codecSelferC_RAW1234 = 0 + // ----- value types used ---- + codecSelferValueTypeArray1234 = 10 + codecSelferValueTypeMap1234 = 9 + // ----- containerStateValues ---- + codecSelfer_containerMapKey1234 = 2 + codecSelfer_containerMapValue1234 = 3 + codecSelfer_containerMapEnd1234 = 4 + codecSelfer_containerArrayElem1234 = 6 + codecSelfer_containerArrayEnd1234 = 7 +) + +var ( + codecSelferBitsize1234 = uint8(reflect.TypeOf(uint(0)).Bits()) + codecSelferOnlyMapOrArrayEncodeToStructErr1234 = errors.New(`only encoded map or array can be decoded into a struct`) +) + +type codecSelfer1234 struct{} + +func init() { + if codec1978.GenVersion != 5 { + _, file, _, _ := runtime.Caller(0) + err := fmt.Errorf("codecgen version mismatch: current: %v, need %v. Re-generate file: %v", + 5, codec1978.GenVersion, file) + panic(err) + } + if false { // reference the types, but skip this branch at build/run time + var v0 pkg4_resource.Quantity + var v1 pkg1_unversioned.TypeMeta + var v2 pkg2_v1.ObjectMeta + var v3 pkg3_types.UID + var v4 pkg6_intstr.IntOrString + var v5 pkg5_inf.Dec + var v6 time.Time + _, _, _, _, _, _, _ = v0, v1, v2, v3, v4, v5, v6 + } +} + +func (x *Job) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [5]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = true + yyq2[1] = true + yyq2[2] = true + yyq2[3] = x.Kind != "" + yyq2[4] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(5) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + yy4 := &x.ObjectMeta + yy4.CodecEncodeSelf(e) + } else { + r.EncodeNil() + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("metadata")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy6 := &x.ObjectMeta + yy6.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + yy9 := &x.Spec + yy9.CodecEncodeSelf(e) + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("spec")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy11 := &x.Spec + yy11.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yy14 := &x.Status + yy14.CodecEncodeSelf(e) + } else { + r.EncodeNil() + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("status")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy16 := &x.Status + yy16.CodecEncodeSelf(e) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym19 := z.EncBinary() + _ = yym19 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym20 := z.EncBinary() + _ = yym20 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[4] { + yym22 := z.EncBinary() + _ = yym22 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym23 := z.EncBinary() + _ = yym23 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *Job) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *Job) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "metadata": + if r.TryDecodeAsNil() { + x.ObjectMeta = pkg2_v1.ObjectMeta{} + } else { + yyv4 := &x.ObjectMeta + yyv4.CodecDecodeSelf(d) + } + case "spec": + if r.TryDecodeAsNil() { + x.Spec = JobSpec{} + } else { + yyv5 := &x.Spec + yyv5.CodecDecodeSelf(d) + } + case "status": + if r.TryDecodeAsNil() { + x.Status = JobStatus{} + } else { + yyv6 := &x.Status + yyv6.CodecDecodeSelf(d) + } + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *Job) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj9 int + var yyb9 bool + var yyhl9 bool = l >= 0 + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ObjectMeta = pkg2_v1.ObjectMeta{} + } else { + yyv10 := &x.ObjectMeta + yyv10.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Spec = JobSpec{} + } else { + yyv11 := &x.Spec + yyv11.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Status = JobStatus{} + } else { + yyv12 := &x.Status + yyv12.CodecDecodeSelf(d) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj9-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *JobList) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [4]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = true + yyq2[2] = x.Kind != "" + yyq2[3] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(4) + } else { + yynn2 = 1 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + yy4 := &x.ListMeta + yym5 := z.EncBinary() + _ = yym5 + if false { + } else if z.HasExtensions() && z.EncExt(yy4) { + } else { + z.EncFallback(yy4) + } + } else { + r.EncodeNil() + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("metadata")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy6 := &x.ListMeta + yym7 := z.EncBinary() + _ = yym7 + if false { + } else if z.HasExtensions() && z.EncExt(yy6) { + } else { + z.EncFallback(yy6) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if x.Items == nil { + r.EncodeNil() + } else { + yym9 := z.EncBinary() + _ = yym9 + if false { + } else { + h.encSliceJob(([]Job)(x.Items), e) + } + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("items")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Items == nil { + r.EncodeNil() + } else { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + h.encSliceJob(([]Job)(x.Items), e) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym12 := z.EncBinary() + _ = yym12 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym13 := z.EncBinary() + _ = yym13 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym15 := z.EncBinary() + _ = yym15 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym16 := z.EncBinary() + _ = yym16 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *JobList) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *JobList) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "metadata": + if r.TryDecodeAsNil() { + x.ListMeta = pkg1_unversioned.ListMeta{} + } else { + yyv4 := &x.ListMeta + yym5 := z.DecBinary() + _ = yym5 + if false { + } else if z.HasExtensions() && z.DecExt(yyv4) { + } else { + z.DecFallback(yyv4, false) + } + } + case "items": + if r.TryDecodeAsNil() { + x.Items = nil + } else { + yyv6 := &x.Items + yym7 := z.DecBinary() + _ = yym7 + if false { + } else { + h.decSliceJob((*[]Job)(yyv6), d) + } + } + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *JobList) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj10 int + var yyb10 bool + var yyhl10 bool = l >= 0 + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ListMeta = pkg1_unversioned.ListMeta{} + } else { + yyv11 := &x.ListMeta + yym12 := z.DecBinary() + _ = yym12 + if false { + } else if z.HasExtensions() && z.DecExt(yyv11) { + } else { + z.DecFallback(yyv11, false) + } + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Items = nil + } else { + yyv13 := &x.Items + yym14 := z.DecBinary() + _ = yym14 + if false { + } else { + h.decSliceJob((*[]Job)(yyv13), d) + } + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj10-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *JobSpec) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [6]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = x.Parallelism != nil + yyq2[1] = x.Completions != nil + yyq2[2] = x.ActiveDeadlineSeconds != nil + yyq2[3] = x.Selector != nil + yyq2[4] = x.ManualSelector != nil + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(6) + } else { + yynn2 = 1 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + if x.Parallelism == nil { + r.EncodeNil() + } else { + yy4 := *x.Parallelism + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeInt(int64(yy4)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("parallelism")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Parallelism == nil { + r.EncodeNil() + } else { + yy6 := *x.Parallelism + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeInt(int64(yy6)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + if x.Completions == nil { + r.EncodeNil() + } else { + yy9 := *x.Completions + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeInt(int64(yy9)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("completions")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Completions == nil { + r.EncodeNil() + } else { + yy11 := *x.Completions + yym12 := z.EncBinary() + _ = yym12 + if false { + } else { + r.EncodeInt(int64(yy11)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + if x.ActiveDeadlineSeconds == nil { + r.EncodeNil() + } else { + yy14 := *x.ActiveDeadlineSeconds + yym15 := z.EncBinary() + _ = yym15 + if false { + } else { + r.EncodeInt(int64(yy14)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("activeDeadlineSeconds")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.ActiveDeadlineSeconds == nil { + r.EncodeNil() + } else { + yy16 := *x.ActiveDeadlineSeconds + yym17 := z.EncBinary() + _ = yym17 + if false { + } else { + r.EncodeInt(int64(yy16)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + if x.Selector == nil { + r.EncodeNil() + } else { + x.Selector.CodecEncodeSelf(e) + } + } else { + r.EncodeNil() + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("selector")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Selector == nil { + r.EncodeNil() + } else { + x.Selector.CodecEncodeSelf(e) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[4] { + if x.ManualSelector == nil { + r.EncodeNil() + } else { + yy22 := *x.ManualSelector + yym23 := z.EncBinary() + _ = yym23 + if false { + } else { + r.EncodeBool(bool(yy22)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("manualSelector")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.ManualSelector == nil { + r.EncodeNil() + } else { + yy24 := *x.ManualSelector + yym25 := z.EncBinary() + _ = yym25 + if false { + } else { + r.EncodeBool(bool(yy24)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy27 := &x.Template + yy27.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("template")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy29 := &x.Template + yy29.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *JobSpec) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *JobSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "parallelism": + if r.TryDecodeAsNil() { + if x.Parallelism != nil { + x.Parallelism = nil + } + } else { + if x.Parallelism == nil { + x.Parallelism = new(int32) + } + yym5 := z.DecBinary() + _ = yym5 + if false { + } else { + *((*int32)(x.Parallelism)) = int32(r.DecodeInt(32)) + } + } + case "completions": + if r.TryDecodeAsNil() { + if x.Completions != nil { + x.Completions = nil + } + } else { + if x.Completions == nil { + x.Completions = new(int32) + } + yym7 := z.DecBinary() + _ = yym7 + if false { + } else { + *((*int32)(x.Completions)) = int32(r.DecodeInt(32)) + } + } + case "activeDeadlineSeconds": + if r.TryDecodeAsNil() { + if x.ActiveDeadlineSeconds != nil { + x.ActiveDeadlineSeconds = nil + } + } else { + if x.ActiveDeadlineSeconds == nil { + x.ActiveDeadlineSeconds = new(int64) + } + yym9 := z.DecBinary() + _ = yym9 + if false { + } else { + *((*int64)(x.ActiveDeadlineSeconds)) = int64(r.DecodeInt(64)) + } + } + case "selector": + if r.TryDecodeAsNil() { + if x.Selector != nil { + x.Selector = nil + } + } else { + if x.Selector == nil { + x.Selector = new(LabelSelector) + } + x.Selector.CodecDecodeSelf(d) + } + case "manualSelector": + if r.TryDecodeAsNil() { + if x.ManualSelector != nil { + x.ManualSelector = nil + } + } else { + if x.ManualSelector == nil { + x.ManualSelector = new(bool) + } + yym12 := z.DecBinary() + _ = yym12 + if false { + } else { + *((*bool)(x.ManualSelector)) = r.DecodeBool() + } + } + case "template": + if r.TryDecodeAsNil() { + x.Template = pkg2_v1.PodTemplateSpec{} + } else { + yyv13 := &x.Template + yyv13.CodecDecodeSelf(d) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj14 int + var yyb14 bool + var yyhl14 bool = l >= 0 + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l + } else { + yyb14 = r.CheckBreak() + } + if yyb14 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.Parallelism != nil { + x.Parallelism = nil + } + } else { + if x.Parallelism == nil { + x.Parallelism = new(int32) + } + yym16 := z.DecBinary() + _ = yym16 + if false { + } else { + *((*int32)(x.Parallelism)) = int32(r.DecodeInt(32)) + } + } + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l + } else { + yyb14 = r.CheckBreak() + } + if yyb14 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.Completions != nil { + x.Completions = nil + } + } else { + if x.Completions == nil { + x.Completions = new(int32) + } + yym18 := z.DecBinary() + _ = yym18 + if false { + } else { + *((*int32)(x.Completions)) = int32(r.DecodeInt(32)) + } + } + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l + } else { + yyb14 = r.CheckBreak() + } + if yyb14 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.ActiveDeadlineSeconds != nil { + x.ActiveDeadlineSeconds = nil + } + } else { + if x.ActiveDeadlineSeconds == nil { + x.ActiveDeadlineSeconds = new(int64) + } + yym20 := z.DecBinary() + _ = yym20 + if false { + } else { + *((*int64)(x.ActiveDeadlineSeconds)) = int64(r.DecodeInt(64)) + } + } + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l + } else { + yyb14 = r.CheckBreak() + } + if yyb14 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.Selector != nil { + x.Selector = nil + } + } else { + if x.Selector == nil { + x.Selector = new(LabelSelector) + } + x.Selector.CodecDecodeSelf(d) + } + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l + } else { + yyb14 = r.CheckBreak() + } + if yyb14 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.ManualSelector != nil { + x.ManualSelector = nil + } + } else { + if x.ManualSelector == nil { + x.ManualSelector = new(bool) + } + yym23 := z.DecBinary() + _ = yym23 + if false { + } else { + *((*bool)(x.ManualSelector)) = r.DecodeBool() + } + } + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l + } else { + yyb14 = r.CheckBreak() + } + if yyb14 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Template = pkg2_v1.PodTemplateSpec{} + } else { + yyv24 := &x.Template + yyv24.CodecDecodeSelf(d) + } + for { + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l + } else { + yyb14 = r.CheckBreak() + } + if yyb14 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj14-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *JobStatus) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [6]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = len(x.Conditions) != 0 + yyq2[1] = x.StartTime != nil + yyq2[2] = x.CompletionTime != nil + yyq2[3] = x.Active != 0 + yyq2[4] = x.Succeeded != 0 + yyq2[5] = x.Failed != 0 + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(6) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + if x.Conditions == nil { + r.EncodeNil() + } else { + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + h.encSliceJobCondition(([]JobCondition)(x.Conditions), e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("conditions")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Conditions == nil { + r.EncodeNil() + } else { + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + h.encSliceJobCondition(([]JobCondition)(x.Conditions), e) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + if x.StartTime == nil { + r.EncodeNil() + } else { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else if z.HasExtensions() && z.EncExt(x.StartTime) { + } else if yym7 { + z.EncBinaryMarshal(x.StartTime) + } else if !yym7 && z.IsJSONHandle() { + z.EncJSONMarshal(x.StartTime) + } else { + z.EncFallback(x.StartTime) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("startTime")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.StartTime == nil { + r.EncodeNil() + } else { + yym8 := z.EncBinary() + _ = yym8 + if false { + } else if z.HasExtensions() && z.EncExt(x.StartTime) { + } else if yym8 { + z.EncBinaryMarshal(x.StartTime) + } else if !yym8 && z.IsJSONHandle() { + z.EncJSONMarshal(x.StartTime) + } else { + z.EncFallback(x.StartTime) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + if x.CompletionTime == nil { + r.EncodeNil() + } else { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else if z.HasExtensions() && z.EncExt(x.CompletionTime) { + } else if yym10 { + z.EncBinaryMarshal(x.CompletionTime) + } else if !yym10 && z.IsJSONHandle() { + z.EncJSONMarshal(x.CompletionTime) + } else { + z.EncFallback(x.CompletionTime) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("completionTime")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.CompletionTime == nil { + r.EncodeNil() + } else { + yym11 := z.EncBinary() + _ = yym11 + if false { + } else if z.HasExtensions() && z.EncExt(x.CompletionTime) { + } else if yym11 { + z.EncBinaryMarshal(x.CompletionTime) + } else if !yym11 && z.IsJSONHandle() { + z.EncJSONMarshal(x.CompletionTime) + } else { + z.EncFallback(x.CompletionTime) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym13 := z.EncBinary() + _ = yym13 + if false { + } else { + r.EncodeInt(int64(x.Active)) + } + } else { + r.EncodeInt(0) + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("active")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym14 := z.EncBinary() + _ = yym14 + if false { + } else { + r.EncodeInt(int64(x.Active)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[4] { + yym16 := z.EncBinary() + _ = yym16 + if false { + } else { + r.EncodeInt(int64(x.Succeeded)) + } + } else { + r.EncodeInt(0) + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("succeeded")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym17 := z.EncBinary() + _ = yym17 + if false { + } else { + r.EncodeInt(int64(x.Succeeded)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[5] { + yym19 := z.EncBinary() + _ = yym19 + if false { + } else { + r.EncodeInt(int64(x.Failed)) + } + } else { + r.EncodeInt(0) + } + } else { + if yyq2[5] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("failed")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym20 := z.EncBinary() + _ = yym20 + if false { + } else { + r.EncodeInt(int64(x.Failed)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *JobStatus) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *JobStatus) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "conditions": + if r.TryDecodeAsNil() { + x.Conditions = nil + } else { + yyv4 := &x.Conditions + yym5 := z.DecBinary() + _ = yym5 + if false { + } else { + h.decSliceJobCondition((*[]JobCondition)(yyv4), d) + } + } + case "startTime": + if r.TryDecodeAsNil() { + if x.StartTime != nil { + x.StartTime = nil + } + } else { + if x.StartTime == nil { + x.StartTime = new(pkg1_unversioned.Time) + } + yym7 := z.DecBinary() + _ = yym7 + if false { + } else if z.HasExtensions() && z.DecExt(x.StartTime) { + } else if yym7 { + z.DecBinaryUnmarshal(x.StartTime) + } else if !yym7 && z.IsJSONHandle() { + z.DecJSONUnmarshal(x.StartTime) + } else { + z.DecFallback(x.StartTime, false) + } + } + case "completionTime": + if r.TryDecodeAsNil() { + if x.CompletionTime != nil { + x.CompletionTime = nil + } + } else { + if x.CompletionTime == nil { + x.CompletionTime = new(pkg1_unversioned.Time) + } + yym9 := z.DecBinary() + _ = yym9 + if false { + } else if z.HasExtensions() && z.DecExt(x.CompletionTime) { + } else if yym9 { + z.DecBinaryUnmarshal(x.CompletionTime) + } else if !yym9 && z.IsJSONHandle() { + z.DecJSONUnmarshal(x.CompletionTime) + } else { + z.DecFallback(x.CompletionTime, false) + } + } + case "active": + if r.TryDecodeAsNil() { + x.Active = 0 + } else { + x.Active = int32(r.DecodeInt(32)) + } + case "succeeded": + if r.TryDecodeAsNil() { + x.Succeeded = 0 + } else { + x.Succeeded = int32(r.DecodeInt(32)) + } + case "failed": + if r.TryDecodeAsNil() { + x.Failed = 0 + } else { + x.Failed = int32(r.DecodeInt(32)) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *JobStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj13 int + var yyb13 bool + var yyhl13 bool = l >= 0 + yyj13++ + if yyhl13 { + yyb13 = yyj13 > l + } else { + yyb13 = r.CheckBreak() + } + if yyb13 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Conditions = nil + } else { + yyv14 := &x.Conditions + yym15 := z.DecBinary() + _ = yym15 + if false { + } else { + h.decSliceJobCondition((*[]JobCondition)(yyv14), d) + } + } + yyj13++ + if yyhl13 { + yyb13 = yyj13 > l + } else { + yyb13 = r.CheckBreak() + } + if yyb13 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.StartTime != nil { + x.StartTime = nil + } + } else { + if x.StartTime == nil { + x.StartTime = new(pkg1_unversioned.Time) + } + yym17 := z.DecBinary() + _ = yym17 + if false { + } else if z.HasExtensions() && z.DecExt(x.StartTime) { + } else if yym17 { + z.DecBinaryUnmarshal(x.StartTime) + } else if !yym17 && z.IsJSONHandle() { + z.DecJSONUnmarshal(x.StartTime) + } else { + z.DecFallback(x.StartTime, false) + } + } + yyj13++ + if yyhl13 { + yyb13 = yyj13 > l + } else { + yyb13 = r.CheckBreak() + } + if yyb13 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.CompletionTime != nil { + x.CompletionTime = nil + } + } else { + if x.CompletionTime == nil { + x.CompletionTime = new(pkg1_unversioned.Time) + } + yym19 := z.DecBinary() + _ = yym19 + if false { + } else if z.HasExtensions() && z.DecExt(x.CompletionTime) { + } else if yym19 { + z.DecBinaryUnmarshal(x.CompletionTime) + } else if !yym19 && z.IsJSONHandle() { + z.DecJSONUnmarshal(x.CompletionTime) + } else { + z.DecFallback(x.CompletionTime, false) + } + } + yyj13++ + if yyhl13 { + yyb13 = yyj13 > l + } else { + yyb13 = r.CheckBreak() + } + if yyb13 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Active = 0 + } else { + x.Active = int32(r.DecodeInt(32)) + } + yyj13++ + if yyhl13 { + yyb13 = yyj13 > l + } else { + yyb13 = r.CheckBreak() + } + if yyb13 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Succeeded = 0 + } else { + x.Succeeded = int32(r.DecodeInt(32)) + } + yyj13++ + if yyhl13 { + yyb13 = yyj13 > l + } else { + yyb13 = r.CheckBreak() + } + if yyb13 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Failed = 0 + } else { + x.Failed = int32(r.DecodeInt(32)) + } + for { + yyj13++ + if yyhl13 { + yyb13 = yyj13 > l + } else { + yyb13 = r.CheckBreak() + } + if yyb13 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj13-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x JobConditionType) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x)) + } +} + +func (x *JobConditionType) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + *((*string)(x)) = r.DecodeString() + } +} + +func (x *JobCondition) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [6]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[2] = true + yyq2[3] = true + yyq2[4] = x.Reason != "" + yyq2[5] = x.Message != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(6) + } else { + yynn2 = 2 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + x.Type.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("type")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + x.Type.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yysf7 := &x.Status + yysf7.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("status")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yysf8 := &x.Status + yysf8.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yy10 := &x.LastProbeTime + yym11 := z.EncBinary() + _ = yym11 + if false { + } else if z.HasExtensions() && z.EncExt(yy10) { + } else if yym11 { + z.EncBinaryMarshal(yy10) + } else if !yym11 && z.IsJSONHandle() { + z.EncJSONMarshal(yy10) + } else { + z.EncFallback(yy10) + } + } else { + r.EncodeNil() + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("lastProbeTime")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy12 := &x.LastProbeTime + yym13 := z.EncBinary() + _ = yym13 + if false { + } else if z.HasExtensions() && z.EncExt(yy12) { + } else if yym13 { + z.EncBinaryMarshal(yy12) + } else if !yym13 && z.IsJSONHandle() { + z.EncJSONMarshal(yy12) + } else { + z.EncFallback(yy12) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yy15 := &x.LastTransitionTime + yym16 := z.EncBinary() + _ = yym16 + if false { + } else if z.HasExtensions() && z.EncExt(yy15) { + } else if yym16 { + z.EncBinaryMarshal(yy15) + } else if !yym16 && z.IsJSONHandle() { + z.EncJSONMarshal(yy15) + } else { + z.EncFallback(yy15) + } + } else { + r.EncodeNil() + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("lastTransitionTime")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy17 := &x.LastTransitionTime + yym18 := z.EncBinary() + _ = yym18 + if false { + } else if z.HasExtensions() && z.EncExt(yy17) { + } else if yym18 { + z.EncBinaryMarshal(yy17) + } else if !yym18 && z.IsJSONHandle() { + z.EncJSONMarshal(yy17) + } else { + z.EncFallback(yy17) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[4] { + yym20 := z.EncBinary() + _ = yym20 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Reason)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("reason")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym21 := z.EncBinary() + _ = yym21 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Reason)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[5] { + yym23 := z.EncBinary() + _ = yym23 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Message)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[5] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("message")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym24 := z.EncBinary() + _ = yym24 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Message)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *JobCondition) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *JobCondition) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "type": + if r.TryDecodeAsNil() { + x.Type = "" + } else { + x.Type = JobConditionType(r.DecodeString()) + } + case "status": + if r.TryDecodeAsNil() { + x.Status = "" + } else { + x.Status = pkg2_v1.ConditionStatus(r.DecodeString()) + } + case "lastProbeTime": + if r.TryDecodeAsNil() { + x.LastProbeTime = pkg1_unversioned.Time{} + } else { + yyv6 := &x.LastProbeTime + yym7 := z.DecBinary() + _ = yym7 + if false { + } else if z.HasExtensions() && z.DecExt(yyv6) { + } else if yym7 { + z.DecBinaryUnmarshal(yyv6) + } else if !yym7 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv6) + } else { + z.DecFallback(yyv6, false) + } + } + case "lastTransitionTime": + if r.TryDecodeAsNil() { + x.LastTransitionTime = pkg1_unversioned.Time{} + } else { + yyv8 := &x.LastTransitionTime + yym9 := z.DecBinary() + _ = yym9 + if false { + } else if z.HasExtensions() && z.DecExt(yyv8) { + } else if yym9 { + z.DecBinaryUnmarshal(yyv8) + } else if !yym9 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv8) + } else { + z.DecFallback(yyv8, false) + } + } + case "reason": + if r.TryDecodeAsNil() { + x.Reason = "" + } else { + x.Reason = string(r.DecodeString()) + } + case "message": + if r.TryDecodeAsNil() { + x.Message = "" + } else { + x.Message = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *JobCondition) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj12 int + var yyb12 bool + var yyhl12 bool = l >= 0 + yyj12++ + if yyhl12 { + yyb12 = yyj12 > l + } else { + yyb12 = r.CheckBreak() + } + if yyb12 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Type = "" + } else { + x.Type = JobConditionType(r.DecodeString()) + } + yyj12++ + if yyhl12 { + yyb12 = yyj12 > l + } else { + yyb12 = r.CheckBreak() + } + if yyb12 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Status = "" + } else { + x.Status = pkg2_v1.ConditionStatus(r.DecodeString()) + } + yyj12++ + if yyhl12 { + yyb12 = yyj12 > l + } else { + yyb12 = r.CheckBreak() + } + if yyb12 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.LastProbeTime = pkg1_unversioned.Time{} + } else { + yyv15 := &x.LastProbeTime + yym16 := z.DecBinary() + _ = yym16 + if false { + } else if z.HasExtensions() && z.DecExt(yyv15) { + } else if yym16 { + z.DecBinaryUnmarshal(yyv15) + } else if !yym16 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv15) + } else { + z.DecFallback(yyv15, false) + } + } + yyj12++ + if yyhl12 { + yyb12 = yyj12 > l + } else { + yyb12 = r.CheckBreak() + } + if yyb12 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.LastTransitionTime = pkg1_unversioned.Time{} + } else { + yyv17 := &x.LastTransitionTime + yym18 := z.DecBinary() + _ = yym18 + if false { + } else if z.HasExtensions() && z.DecExt(yyv17) { + } else if yym18 { + z.DecBinaryUnmarshal(yyv17) + } else if !yym18 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv17) + } else { + z.DecFallback(yyv17, false) + } + } + yyj12++ + if yyhl12 { + yyb12 = yyj12 > l + } else { + yyb12 = r.CheckBreak() + } + if yyb12 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Reason = "" + } else { + x.Reason = string(r.DecodeString()) + } + yyj12++ + if yyhl12 { + yyb12 = yyj12 > l + } else { + yyb12 = r.CheckBreak() + } + if yyb12 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Message = "" + } else { + x.Message = string(r.DecodeString()) + } + for { + yyj12++ + if yyhl12 { + yyb12 = yyj12 > l + } else { + yyb12 = r.CheckBreak() + } + if yyb12 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj12-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *LabelSelector) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [2]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[0] = len(x.MatchLabels) != 0 + yyq2[1] = len(x.MatchExpressions) != 0 + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(2) + } else { + yynn2 = 0 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[0] { + if x.MatchLabels == nil { + r.EncodeNil() + } else { + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + z.F.EncMapStringStringV(x.MatchLabels, false, e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[0] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("matchLabels")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.MatchLabels == nil { + r.EncodeNil() + } else { + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + z.F.EncMapStringStringV(x.MatchLabels, false, e) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + if x.MatchExpressions == nil { + r.EncodeNil() + } else { + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + h.encSliceLabelSelectorRequirement(([]LabelSelectorRequirement)(x.MatchExpressions), e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("matchExpressions")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.MatchExpressions == nil { + r.EncodeNil() + } else { + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + h.encSliceLabelSelectorRequirement(([]LabelSelectorRequirement)(x.MatchExpressions), e) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *LabelSelector) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *LabelSelector) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "matchLabels": + if r.TryDecodeAsNil() { + x.MatchLabels = nil + } else { + yyv4 := &x.MatchLabels + yym5 := z.DecBinary() + _ = yym5 + if false { + } else { + z.F.DecMapStringStringX(yyv4, false, d) + } + } + case "matchExpressions": + if r.TryDecodeAsNil() { + x.MatchExpressions = nil + } else { + yyv6 := &x.MatchExpressions + yym7 := z.DecBinary() + _ = yym7 + if false { + } else { + h.decSliceLabelSelectorRequirement((*[]LabelSelectorRequirement)(yyv6), d) + } + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *LabelSelector) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj8 int + var yyb8 bool + var yyhl8 bool = l >= 0 + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.MatchLabels = nil + } else { + yyv9 := &x.MatchLabels + yym10 := z.DecBinary() + _ = yym10 + if false { + } else { + z.F.DecMapStringStringX(yyv9, false, d) + } + } + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.MatchExpressions = nil + } else { + yyv11 := &x.MatchExpressions + yym12 := z.DecBinary() + _ = yym12 + if false { + } else { + h.decSliceLabelSelectorRequirement((*[]LabelSelectorRequirement)(yyv11), d) + } + } + for { + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj8-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *LabelSelectorRequirement) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [3]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[2] = len(x.Values) != 0 + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(3) + } else { + yynn2 = 2 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Key)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("key")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Key)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + x.Operator.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("operator")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + x.Operator.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + if x.Values == nil { + r.EncodeNil() + } else { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + z.F.EncSliceStringV(x.Values, false, e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("values")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Values == nil { + r.EncodeNil() + } else { + yym11 := z.EncBinary() + _ = yym11 + if false { + } else { + z.F.EncSliceStringV(x.Values, false, e) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *LabelSelectorRequirement) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *LabelSelectorRequirement) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "key": + if r.TryDecodeAsNil() { + x.Key = "" + } else { + x.Key = string(r.DecodeString()) + } + case "operator": + if r.TryDecodeAsNil() { + x.Operator = "" + } else { + x.Operator = LabelSelectorOperator(r.DecodeString()) + } + case "values": + if r.TryDecodeAsNil() { + x.Values = nil + } else { + yyv6 := &x.Values + yym7 := z.DecBinary() + _ = yym7 + if false { + } else { + z.F.DecSliceStringX(yyv6, false, d) + } + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *LabelSelectorRequirement) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj8 int + var yyb8 bool + var yyhl8 bool = l >= 0 + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Key = "" + } else { + x.Key = string(r.DecodeString()) + } + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Operator = "" + } else { + x.Operator = LabelSelectorOperator(r.DecodeString()) + } + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Values = nil + } else { + yyv11 := &x.Values + yym12 := z.DecBinary() + _ = yym12 + if false { + } else { + z.F.DecSliceStringX(yyv11, false, d) + } + } + for { + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj8-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x LabelSelectorOperator) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x)) + } +} + +func (x *LabelSelectorOperator) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + *((*string)(x)) = r.DecodeString() + } +} + +func (x codecSelfer1234) encSliceJob(v []Job, e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + r.EncodeArrayStart(len(v)) + for _, yyv1 := range v { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy2 := &yyv1 + yy2.CodecEncodeSelf(e) + } + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x codecSelfer1234) decSliceJob(v *[]Job, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + + yyv1 := *v + yyh1, yyl1 := z.DecSliceHelperStart() + var yyc1 bool + _ = yyc1 + if yyl1 == 0 { + if yyv1 == nil { + yyv1 = []Job{} + yyc1 = true + } else if len(yyv1) != 0 { + yyv1 = yyv1[:0] + yyc1 = true + } + } else if yyl1 > 0 { + var yyrr1, yyrl1 int + var yyrt1 bool + _, _ = yyrl1, yyrt1 + yyrr1 = yyl1 // len(yyv1) + if yyl1 > cap(yyv1) { + + yyrg1 := len(yyv1) > 0 + yyv21 := yyv1 + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 640) + if yyrt1 { + if yyrl1 <= cap(yyv1) { + yyv1 = yyv1[:yyrl1] + } else { + yyv1 = make([]Job, yyrl1) + } + } else { + yyv1 = make([]Job, yyrl1) + } + yyc1 = true + yyrr1 = len(yyv1) + if yyrg1 { + copy(yyv1, yyv21) + } + } else if yyl1 != len(yyv1) { + yyv1 = yyv1[:yyl1] + yyc1 = true + } + yyj1 := 0 + for ; yyj1 < yyrr1; yyj1++ { + yyh1.ElemContainerState(yyj1) + if r.TryDecodeAsNil() { + yyv1[yyj1] = Job{} + } else { + yyv2 := &yyv1[yyj1] + yyv2.CodecDecodeSelf(d) + } + + } + if yyrt1 { + for ; yyj1 < yyl1; yyj1++ { + yyv1 = append(yyv1, Job{}) + yyh1.ElemContainerState(yyj1) + if r.TryDecodeAsNil() { + yyv1[yyj1] = Job{} + } else { + yyv3 := &yyv1[yyj1] + yyv3.CodecDecodeSelf(d) + } + + } + } + + } else { + yyj1 := 0 + for ; !r.CheckBreak(); yyj1++ { + + if yyj1 >= len(yyv1) { + yyv1 = append(yyv1, Job{}) // var yyz1 Job + yyc1 = true + } + yyh1.ElemContainerState(yyj1) + if yyj1 < len(yyv1) { + if r.TryDecodeAsNil() { + yyv1[yyj1] = Job{} + } else { + yyv4 := &yyv1[yyj1] + yyv4.CodecDecodeSelf(d) + } + + } else { + z.DecSwallow() + } + + } + if yyj1 < len(yyv1) { + yyv1 = yyv1[:yyj1] + yyc1 = true + } else if yyj1 == 0 && yyv1 == nil { + yyv1 = []Job{} + yyc1 = true + } + } + yyh1.End() + if yyc1 { + *v = yyv1 + } +} + +func (x codecSelfer1234) encSliceJobCondition(v []JobCondition, e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + r.EncodeArrayStart(len(v)) + for _, yyv1 := range v { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy2 := &yyv1 + yy2.CodecEncodeSelf(e) + } + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x codecSelfer1234) decSliceJobCondition(v *[]JobCondition, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + + yyv1 := *v + yyh1, yyl1 := z.DecSliceHelperStart() + var yyc1 bool + _ = yyc1 + if yyl1 == 0 { + if yyv1 == nil { + yyv1 = []JobCondition{} + yyc1 = true + } else if len(yyv1) != 0 { + yyv1 = yyv1[:0] + yyc1 = true + } + } else if yyl1 > 0 { + var yyrr1, yyrl1 int + var yyrt1 bool + _, _ = yyrl1, yyrt1 + yyrr1 = yyl1 // len(yyv1) + if yyl1 > cap(yyv1) { + + yyrg1 := len(yyv1) > 0 + yyv21 := yyv1 + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 112) + if yyrt1 { + if yyrl1 <= cap(yyv1) { + yyv1 = yyv1[:yyrl1] + } else { + yyv1 = make([]JobCondition, yyrl1) + } + } else { + yyv1 = make([]JobCondition, yyrl1) + } + yyc1 = true + yyrr1 = len(yyv1) + if yyrg1 { + copy(yyv1, yyv21) + } + } else if yyl1 != len(yyv1) { + yyv1 = yyv1[:yyl1] + yyc1 = true + } + yyj1 := 0 + for ; yyj1 < yyrr1; yyj1++ { + yyh1.ElemContainerState(yyj1) + if r.TryDecodeAsNil() { + yyv1[yyj1] = JobCondition{} + } else { + yyv2 := &yyv1[yyj1] + yyv2.CodecDecodeSelf(d) + } + + } + if yyrt1 { + for ; yyj1 < yyl1; yyj1++ { + yyv1 = append(yyv1, JobCondition{}) + yyh1.ElemContainerState(yyj1) + if r.TryDecodeAsNil() { + yyv1[yyj1] = JobCondition{} + } else { + yyv3 := &yyv1[yyj1] + yyv3.CodecDecodeSelf(d) + } + + } + } + + } else { + yyj1 := 0 + for ; !r.CheckBreak(); yyj1++ { + + if yyj1 >= len(yyv1) { + yyv1 = append(yyv1, JobCondition{}) // var yyz1 JobCondition + yyc1 = true + } + yyh1.ElemContainerState(yyj1) + if yyj1 < len(yyv1) { + if r.TryDecodeAsNil() { + yyv1[yyj1] = JobCondition{} + } else { + yyv4 := &yyv1[yyj1] + yyv4.CodecDecodeSelf(d) + } + + } else { + z.DecSwallow() + } + + } + if yyj1 < len(yyv1) { + yyv1 = yyv1[:yyj1] + yyc1 = true + } else if yyj1 == 0 && yyv1 == nil { + yyv1 = []JobCondition{} + yyc1 = true + } + } + yyh1.End() + if yyc1 { + *v = yyv1 + } +} + +func (x codecSelfer1234) encSliceLabelSelectorRequirement(v []LabelSelectorRequirement, e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + r.EncodeArrayStart(len(v)) + for _, yyv1 := range v { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy2 := &yyv1 + yy2.CodecEncodeSelf(e) + } + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x codecSelfer1234) decSliceLabelSelectorRequirement(v *[]LabelSelectorRequirement, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + + yyv1 := *v + yyh1, yyl1 := z.DecSliceHelperStart() + var yyc1 bool + _ = yyc1 + if yyl1 == 0 { + if yyv1 == nil { + yyv1 = []LabelSelectorRequirement{} + yyc1 = true + } else if len(yyv1) != 0 { + yyv1 = yyv1[:0] + yyc1 = true + } + } else if yyl1 > 0 { + var yyrr1, yyrl1 int + var yyrt1 bool + _, _ = yyrl1, yyrt1 + yyrr1 = yyl1 // len(yyv1) + if yyl1 > cap(yyv1) { + + yyrg1 := len(yyv1) > 0 + yyv21 := yyv1 + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 56) + if yyrt1 { + if yyrl1 <= cap(yyv1) { + yyv1 = yyv1[:yyrl1] + } else { + yyv1 = make([]LabelSelectorRequirement, yyrl1) + } + } else { + yyv1 = make([]LabelSelectorRequirement, yyrl1) + } + yyc1 = true + yyrr1 = len(yyv1) + if yyrg1 { + copy(yyv1, yyv21) + } + } else if yyl1 != len(yyv1) { + yyv1 = yyv1[:yyl1] + yyc1 = true + } + yyj1 := 0 + for ; yyj1 < yyrr1; yyj1++ { + yyh1.ElemContainerState(yyj1) + if r.TryDecodeAsNil() { + yyv1[yyj1] = LabelSelectorRequirement{} + } else { + yyv2 := &yyv1[yyj1] + yyv2.CodecDecodeSelf(d) + } + + } + if yyrt1 { + for ; yyj1 < yyl1; yyj1++ { + yyv1 = append(yyv1, LabelSelectorRequirement{}) + yyh1.ElemContainerState(yyj1) + if r.TryDecodeAsNil() { + yyv1[yyj1] = LabelSelectorRequirement{} + } else { + yyv3 := &yyv1[yyj1] + yyv3.CodecDecodeSelf(d) + } + + } + } + + } else { + yyj1 := 0 + for ; !r.CheckBreak(); yyj1++ { + + if yyj1 >= len(yyv1) { + yyv1 = append(yyv1, LabelSelectorRequirement{}) // var yyz1 LabelSelectorRequirement + yyc1 = true + } + yyh1.ElemContainerState(yyj1) + if yyj1 < len(yyv1) { + if r.TryDecodeAsNil() { + yyv1[yyj1] = LabelSelectorRequirement{} + } else { + yyv4 := &yyv1[yyj1] + yyv4.CodecDecodeSelf(d) + } + + } else { + z.DecSwallow() + } + + } + if yyj1 < len(yyv1) { + yyv1 = yyv1[:yyj1] + yyc1 = true + } else if yyj1 == 0 && yyv1 == nil { + yyv1 = []LabelSelectorRequirement{} + yyc1 = true + } + } + yyh1.End() + if yyc1 { + *v = yyv1 + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/types.go b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/types.go new file mode 100644 index 000000000..9dfe0d3dc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/types.go @@ -0,0 +1,184 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" +) + +// Job represents the configuration of a single job. +type Job struct { + unversioned.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata + v1.ObjectMeta `json:"metadata,omitempty"` + + // Spec is a structure defining the expected behavior of a job. + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status + Spec JobSpec `json:"spec,omitempty"` + + // Status is a structure describing current status of a job. + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status + Status JobStatus `json:"status,omitempty"` +} + +// JobList is a collection of jobs. +type JobList struct { + unversioned.TypeMeta `json:",inline"` + // Standard list metadata + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata + unversioned.ListMeta `json:"metadata,omitempty"` + + // Items is the list of Job. + Items []Job `json:"items"` +} + +// JobSpec describes how the job execution will look like. +type JobSpec struct { + + // Parallelism specifies the maximum desired number of pods the job should + // run at any given time. The actual number of pods running in steady state will + // be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), + // i.e. when the work left to do is less than max parallelism. + // More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md + Parallelism *int32 `json:"parallelism,omitempty"` + + // Completions specifies the desired number of successfully finished pods the + // job should be run with. Setting to nil means that the success of any + // pod signals the success of all pods, and allows parallelism to have any positive + // value. Setting to 1 means that parallelism is limited to 1 and the success of that + // pod signals the success of the job. + // More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md + Completions *int32 `json:"completions,omitempty"` + + // Optional duration in seconds relative to the startTime that the job may be active + // before the system tries to terminate it; value must be positive integer + ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"` + + // Selector is a label query over pods that should match the pod count. + // Normally, the system sets this field for you. + // More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors + Selector *LabelSelector `json:"selector,omitempty"` + + // ManualSelector controls generation of pod labels and pod selectors. + // Leave `manualSelector` unset unless you are certain what you are doing. + // When false or unset, the system pick labels unique to this job + // and appends those labels to the pod template. When true, + // the user is responsible for picking unique labels and specifying + // the selector. Failure to pick a unique label may cause this + // and other jobs to not function correctly. However, You may see + // `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` + // API. + // More info: http://releases.k8s.io/HEAD/docs/design/selector-generation.md + ManualSelector *bool `json:"manualSelector,omitempty"` + + // Template is the object that describes the pod that will be created when + // executing a job. + // More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md + Template v1.PodTemplateSpec `json:"template"` +} + +// JobStatus represents the current state of a Job. +type JobStatus struct { + + // Conditions represent the latest available observations of an object's current state. + // More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md + Conditions []JobCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` + + // StartTime represents time when the job was acknowledged by the Job Manager. + // It is not guaranteed to be set in happens-before order across separate operations. + // It is represented in RFC3339 form and is in UTC. + StartTime *unversioned.Time `json:"startTime,omitempty"` + + // CompletionTime represents time when the job was completed. It is not guaranteed to + // be set in happens-before order across separate operations. + // It is represented in RFC3339 form and is in UTC. + CompletionTime *unversioned.Time `json:"completionTime,omitempty"` + + // Active is the number of actively running pods. + Active int32 `json:"active,omitempty"` + + // Succeeded is the number of pods which reached Phase Succeeded. + Succeeded int32 `json:"succeeded,omitempty"` + + // Failed is the number of pods which reached Phase Failed. + Failed int32 `json:"failed,omitempty"` +} + +type JobConditionType string + +// These are valid conditions of a job. +const ( + // JobComplete means the job has completed its execution. + JobComplete JobConditionType = "Complete" + // JobFailed means the job has failed its execution. + JobFailed JobConditionType = "Failed" +) + +// JobCondition describes current state of a job. +type JobCondition struct { + // Type of job condition, Complete or Failed. + Type JobConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status v1.ConditionStatus `json:"status"` + // Last time the condition was checked. + LastProbeTime unversioned.Time `json:"lastProbeTime,omitempty"` + // Last time the condition transit from one status to another. + LastTransitionTime unversioned.Time `json:"lastTransitionTime,omitempty"` + // (brief) reason for the condition's last transition. + Reason string `json:"reason,omitempty"` + // Human readable message indicating details about last transition. + Message string `json:"message,omitempty"` +} + +// A label selector is a label query over a set of resources. The result of matchLabels and +// matchExpressions are ANDed. An empty label selector matches all objects. A null +// label selector matches no objects. +type LabelSelector struct { + // matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + // map is equivalent to an element of matchExpressions, whose key field is "key", the + // operator is "In", and the values array contains only "value". The requirements are ANDed. + MatchLabels map[string]string `json:"matchLabels,omitempty"` + // matchExpressions is a list of label selector requirements. The requirements are ANDed. + MatchExpressions []LabelSelectorRequirement `json:"matchExpressions,omitempty"` +} + +// A label selector requirement is a selector that contains values, a key, and an operator that +// relates the key and values. +type LabelSelectorRequirement struct { + // key is the label key that the selector applies to. + Key string `json:"key" patchStrategy:"merge" patchMergeKey:"key"` + // operator represents a key's relationship to a set of values. + // Valid operators ard In, NotIn, Exists and DoesNotExist. + Operator LabelSelectorOperator `json:"operator"` + // values is an array of string values. If the operator is In or NotIn, + // the values array must be non-empty. If the operator is Exists or DoesNotExist, + // the values array must be empty. This array is replaced during a strategic + // merge patch. + Values []string `json:"values,omitempty"` +} + +// A label selector operator is the set of operators that can be used in a selector requirement. +type LabelSelectorOperator string + +const ( + LabelSelectorOpIn LabelSelectorOperator = "In" + LabelSelectorOpNotIn LabelSelectorOperator = "NotIn" + LabelSelectorOpExists LabelSelectorOperator = "Exists" + LabelSelectorOpDoesNotExist LabelSelectorOperator = "DoesNotExist" +) diff --git a/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/types_swagger_doc_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/types_swagger_doc_generated.go new file mode 100644 index 000000000..8b5255843 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/batch/v1/types_swagger_doc_generated.go @@ -0,0 +1,114 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 v1 + +// This file contains a collection of methods that can be used from go-restful to +// generate Swagger API documentation for its models. Please read this PR for more +// information on the implementation: https://github.com/emicklei/go-restful/pull/215 +// +// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if +// they are on one line! For multiple line or blocks that you want to ignore use ---. +// Any context after a --- is ignored. +// +// Those methods can be generated by using hack/update-generated-swagger-docs.sh + +// AUTO-GENERATED FUNCTIONS START HERE +var map_Job = map[string]string{ + "": "Job represents the configuration of a single job.", + "metadata": "Standard object's metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata", + "spec": "Spec is a structure defining the expected behavior of a job. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status", + "status": "Status is a structure describing current status of a job. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status", +} + +func (Job) SwaggerDoc() map[string]string { + return map_Job +} + +var map_JobCondition = map[string]string{ + "": "JobCondition describes current state of a job.", + "type": "Type of job condition, Complete or Failed.", + "status": "Status of the condition, one of True, False, Unknown.", + "lastProbeTime": "Last time the condition was checked.", + "lastTransitionTime": "Last time the condition transit from one status to another.", + "reason": "(brief) reason for the condition's last transition.", + "message": "Human readable message indicating details about last transition.", +} + +func (JobCondition) SwaggerDoc() map[string]string { + return map_JobCondition +} + +var map_JobList = map[string]string{ + "": "JobList is a collection of jobs.", + "metadata": "Standard list metadata More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata", + "items": "Items is the list of Job.", +} + +func (JobList) SwaggerDoc() map[string]string { + return map_JobList +} + +var map_JobSpec = map[string]string{ + "": "JobSpec describes how the job execution will look like.", + "parallelism": "Parallelism specifies the maximum desired number of pods the job should run at any given time. The actual number of pods running in steady state will be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), i.e. when the work left to do is less than max parallelism. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md", + "completions": "Completions specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md", + "activeDeadlineSeconds": "Optional duration in seconds relative to the startTime that the job may be active before the system tries to terminate it; value must be positive integer", + "selector": "Selector is a label query over pods that should match the pod count. Normally, the system sets this field for you. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors", + "manualSelector": "ManualSelector controls generation of pod labels and pod selectors. Leave `manualSelector` unset unless you are certain what you are doing. When false or unset, the system pick labels unique to this job and appends those labels to the pod template. When true, the user is responsible for picking unique labels and specifying the selector. Failure to pick a unique label may cause this and other jobs to not function correctly. However, You may see `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` API. More info: http://releases.k8s.io/HEAD/docs/design/selector-generation.md", + "template": "Template is the object that describes the pod that will be created when executing a job. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md", +} + +func (JobSpec) SwaggerDoc() map[string]string { + return map_JobSpec +} + +var map_JobStatus = map[string]string{ + "": "JobStatus represents the current state of a Job.", + "conditions": "Conditions represent the latest available observations of an object's current state. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md", + "startTime": "StartTime represents time when the job was acknowledged by the Job Manager. It is not guaranteed to be set in happens-before order across separate operations. It is represented in RFC3339 form and is in UTC.", + "completionTime": "CompletionTime represents time when the job was completed. It is not guaranteed to be set in happens-before order across separate operations. It is represented in RFC3339 form and is in UTC.", + "active": "Active is the number of actively running pods.", + "succeeded": "Succeeded is the number of pods which reached Phase Succeeded.", + "failed": "Failed is the number of pods which reached Phase Failed.", +} + +func (JobStatus) SwaggerDoc() map[string]string { + return map_JobStatus +} + +var map_LabelSelector = map[string]string{ + "": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + "matchLabels": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + "matchExpressions": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", +} + +func (LabelSelector) SwaggerDoc() map[string]string { + return map_LabelSelector +} + +var map_LabelSelectorRequirement = map[string]string{ + "": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + "key": "key is the label key that the selector applies to.", + "operator": "operator represents a key's relationship to a set of values. Valid operators ard In, NotIn, Exists and DoesNotExist.", + "values": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", +} + +func (LabelSelectorRequirement) SwaggerDoc() map[string]string { + return map_LabelSelectorRequirement +} + +// AUTO-GENERATED FUNCTIONS END HERE diff --git a/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/deep_copy_generated.go index c9a7b31e2..35d73940c 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/deep_copy_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/deep_copy_generated.go @@ -1,5 +1,7 @@ +// +build !ignore_autogenerated + /* -Copyright 2015 The Kubernetes Authors All rights reserved. +Copyright 2016 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,16 +16,328 @@ See the License for the specific language governing permissions and limitations under the License. */ -// DO NOT EDIT. THIS FILE IS AUTO-GENERATED BY $KUBEROOT/hack/update-generated-deep-copies.sh. +// This file was autogenerated by deepcopy-gen. Do not edit it manually! package componentconfig -import api "k8s.io/kubernetes/pkg/api" +import ( + api "k8s.io/kubernetes/pkg/api" + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + conversion "k8s.io/kubernetes/pkg/conversion" +) func init() { - err := api.Scheme.AddGeneratedDeepCopyFuncs() - if err != nil { + if err := api.Scheme.AddGeneratedDeepCopyFuncs( + DeepCopy_componentconfig_IPVar, + DeepCopy_componentconfig_KubeControllerManagerConfiguration, + DeepCopy_componentconfig_KubeProxyConfiguration, + DeepCopy_componentconfig_KubeSchedulerConfiguration, + DeepCopy_componentconfig_KubeletConfiguration, + DeepCopy_componentconfig_LeaderElectionConfiguration, + DeepCopy_componentconfig_PersistentVolumeRecyclerConfiguration, + DeepCopy_componentconfig_PortRangeVar, + DeepCopy_componentconfig_VolumeConfiguration, + ); err != nil { // if one of the deep copy functions is malformed, detect it immediately. panic(err) } } + +func DeepCopy_componentconfig_IPVar(in IPVar, out *IPVar, c *conversion.Cloner) error { + if in.Val != nil { + in, out := in.Val, &out.Val + *out = new(string) + **out = *in + } else { + out.Val = nil + } + return nil +} + +func DeepCopy_componentconfig_KubeControllerManagerConfiguration(in KubeControllerManagerConfiguration, out *KubeControllerManagerConfiguration, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + out.Port = in.Port + out.Address = in.Address + out.CloudProvider = in.CloudProvider + out.CloudConfigFile = in.CloudConfigFile + out.ConcurrentEndpointSyncs = in.ConcurrentEndpointSyncs + out.ConcurrentRSSyncs = in.ConcurrentRSSyncs + out.ConcurrentRCSyncs = in.ConcurrentRCSyncs + out.ConcurrentResourceQuotaSyncs = in.ConcurrentResourceQuotaSyncs + out.ConcurrentDeploymentSyncs = in.ConcurrentDeploymentSyncs + out.ConcurrentDaemonSetSyncs = in.ConcurrentDaemonSetSyncs + out.ConcurrentJobSyncs = in.ConcurrentJobSyncs + out.ConcurrentNamespaceSyncs = in.ConcurrentNamespaceSyncs + out.LookupCacheSizeForRC = in.LookupCacheSizeForRC + out.LookupCacheSizeForRS = in.LookupCacheSizeForRS + out.LookupCacheSizeForDaemonSet = in.LookupCacheSizeForDaemonSet + if err := unversioned.DeepCopy_unversioned_Duration(in.ServiceSyncPeriod, &out.ServiceSyncPeriod, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Duration(in.NodeSyncPeriod, &out.NodeSyncPeriod, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Duration(in.ResourceQuotaSyncPeriod, &out.ResourceQuotaSyncPeriod, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Duration(in.NamespaceSyncPeriod, &out.NamespaceSyncPeriod, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Duration(in.PVClaimBinderSyncPeriod, &out.PVClaimBinderSyncPeriod, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Duration(in.MinResyncPeriod, &out.MinResyncPeriod, c); err != nil { + return err + } + out.TerminatedPodGCThreshold = in.TerminatedPodGCThreshold + if err := unversioned.DeepCopy_unversioned_Duration(in.HorizontalPodAutoscalerSyncPeriod, &out.HorizontalPodAutoscalerSyncPeriod, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Duration(in.DeploymentControllerSyncPeriod, &out.DeploymentControllerSyncPeriod, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Duration(in.PodEvictionTimeout, &out.PodEvictionTimeout, c); err != nil { + return err + } + out.DeletingPodsQps = in.DeletingPodsQps + out.DeletingPodsBurst = in.DeletingPodsBurst + if err := unversioned.DeepCopy_unversioned_Duration(in.NodeMonitorGracePeriod, &out.NodeMonitorGracePeriod, c); err != nil { + return err + } + out.RegisterRetryCount = in.RegisterRetryCount + if err := unversioned.DeepCopy_unversioned_Duration(in.NodeStartupGracePeriod, &out.NodeStartupGracePeriod, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Duration(in.NodeMonitorPeriod, &out.NodeMonitorPeriod, c); err != nil { + return err + } + out.ServiceAccountKeyFile = in.ServiceAccountKeyFile + out.EnableProfiling = in.EnableProfiling + out.ClusterName = in.ClusterName + out.ClusterCIDR = in.ClusterCIDR + out.AllocateNodeCIDRs = in.AllocateNodeCIDRs + out.RootCAFile = in.RootCAFile + out.KubeAPIQPS = in.KubeAPIQPS + out.KubeAPIBurst = in.KubeAPIBurst + if err := DeepCopy_componentconfig_LeaderElectionConfiguration(in.LeaderElection, &out.LeaderElection, c); err != nil { + return err + } + if err := DeepCopy_componentconfig_VolumeConfiguration(in.VolumeConfiguration, &out.VolumeConfiguration, c); err != nil { + return err + } + return nil +} + +func DeepCopy_componentconfig_KubeProxyConfiguration(in KubeProxyConfiguration, out *KubeProxyConfiguration, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + out.BindAddress = in.BindAddress + out.HealthzBindAddress = in.HealthzBindAddress + out.HealthzPort = in.HealthzPort + out.HostnameOverride = in.HostnameOverride + if in.IPTablesMasqueradeBit != nil { + in, out := in.IPTablesMasqueradeBit, &out.IPTablesMasqueradeBit + *out = new(int) + **out = *in + } else { + out.IPTablesMasqueradeBit = nil + } + if err := unversioned.DeepCopy_unversioned_Duration(in.IPTablesSyncPeriod, &out.IPTablesSyncPeriod, c); err != nil { + return err + } + out.KubeconfigPath = in.KubeconfigPath + out.MasqueradeAll = in.MasqueradeAll + out.Master = in.Master + if in.OOMScoreAdj != nil { + in, out := in.OOMScoreAdj, &out.OOMScoreAdj + *out = new(int) + **out = *in + } else { + out.OOMScoreAdj = nil + } + out.Mode = in.Mode + out.PortRange = in.PortRange + out.ResourceContainer = in.ResourceContainer + if err := unversioned.DeepCopy_unversioned_Duration(in.UDPIdleTimeout, &out.UDPIdleTimeout, c); err != nil { + return err + } + out.ConntrackMax = in.ConntrackMax + if err := unversioned.DeepCopy_unversioned_Duration(in.ConntrackTCPEstablishedTimeout, &out.ConntrackTCPEstablishedTimeout, c); err != nil { + return err + } + return nil +} + +func DeepCopy_componentconfig_KubeSchedulerConfiguration(in KubeSchedulerConfiguration, out *KubeSchedulerConfiguration, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + out.Port = in.Port + out.Address = in.Address + out.AlgorithmProvider = in.AlgorithmProvider + out.PolicyConfigFile = in.PolicyConfigFile + out.EnableProfiling = in.EnableProfiling + out.KubeAPIQPS = in.KubeAPIQPS + out.KubeAPIBurst = in.KubeAPIBurst + out.SchedulerName = in.SchedulerName + if err := DeepCopy_componentconfig_LeaderElectionConfiguration(in.LeaderElection, &out.LeaderElection, c); err != nil { + return err + } + return nil +} + +func DeepCopy_componentconfig_KubeletConfiguration(in KubeletConfiguration, out *KubeletConfiguration, c *conversion.Cloner) error { + out.Config = in.Config + if err := unversioned.DeepCopy_unversioned_Duration(in.SyncFrequency, &out.SyncFrequency, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Duration(in.FileCheckFrequency, &out.FileCheckFrequency, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Duration(in.HTTPCheckFrequency, &out.HTTPCheckFrequency, c); err != nil { + return err + } + out.ManifestURL = in.ManifestURL + out.ManifestURLHeader = in.ManifestURLHeader + out.EnableServer = in.EnableServer + out.Address = in.Address + out.Port = in.Port + out.ReadOnlyPort = in.ReadOnlyPort + out.TLSCertFile = in.TLSCertFile + out.TLSPrivateKeyFile = in.TLSPrivateKeyFile + out.CertDirectory = in.CertDirectory + out.HostnameOverride = in.HostnameOverride + out.PodInfraContainerImage = in.PodInfraContainerImage + out.DockerEndpoint = in.DockerEndpoint + out.RootDirectory = in.RootDirectory + out.AllowPrivileged = in.AllowPrivileged + out.HostNetworkSources = in.HostNetworkSources + out.HostPIDSources = in.HostPIDSources + out.HostIPCSources = in.HostIPCSources + out.RegistryPullQPS = in.RegistryPullQPS + out.RegistryBurst = in.RegistryBurst + out.EventRecordQPS = in.EventRecordQPS + out.EventBurst = in.EventBurst + out.EnableDebuggingHandlers = in.EnableDebuggingHandlers + if err := unversioned.DeepCopy_unversioned_Duration(in.MinimumGCAge, &out.MinimumGCAge, c); err != nil { + return err + } + out.MaxPerPodContainerCount = in.MaxPerPodContainerCount + out.MaxContainerCount = in.MaxContainerCount + out.CAdvisorPort = in.CAdvisorPort + out.HealthzPort = in.HealthzPort + out.HealthzBindAddress = in.HealthzBindAddress + out.OOMScoreAdj = in.OOMScoreAdj + out.RegisterNode = in.RegisterNode + out.ClusterDomain = in.ClusterDomain + out.MasterServiceNamespace = in.MasterServiceNamespace + out.ClusterDNS = in.ClusterDNS + if err := unversioned.DeepCopy_unversioned_Duration(in.StreamingConnectionIdleTimeout, &out.StreamingConnectionIdleTimeout, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Duration(in.NodeStatusUpdateFrequency, &out.NodeStatusUpdateFrequency, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Duration(in.ImageMinimumGCAge, &out.ImageMinimumGCAge, c); err != nil { + return err + } + out.ImageGCHighThresholdPercent = in.ImageGCHighThresholdPercent + out.ImageGCLowThresholdPercent = in.ImageGCLowThresholdPercent + out.LowDiskSpaceThresholdMB = in.LowDiskSpaceThresholdMB + if err := unversioned.DeepCopy_unversioned_Duration(in.VolumeStatsAggPeriod, &out.VolumeStatsAggPeriod, c); err != nil { + return err + } + out.NetworkPluginName = in.NetworkPluginName + out.NetworkPluginDir = in.NetworkPluginDir + out.VolumePluginDir = in.VolumePluginDir + out.CloudProvider = in.CloudProvider + out.CloudConfigFile = in.CloudConfigFile + out.KubeletCgroups = in.KubeletCgroups + out.RuntimeCgroups = in.RuntimeCgroups + out.SystemCgroups = in.SystemCgroups + out.CgroupRoot = in.CgroupRoot + out.ContainerRuntime = in.ContainerRuntime + out.RktPath = in.RktPath + out.RktAPIEndpoint = in.RktAPIEndpoint + out.RktStage1Image = in.RktStage1Image + out.LockFilePath = in.LockFilePath + out.ConfigureCBR0 = in.ConfigureCBR0 + out.HairpinMode = in.HairpinMode + out.BabysitDaemons = in.BabysitDaemons + out.MaxPods = in.MaxPods + out.DockerExecHandlerName = in.DockerExecHandlerName + out.PodCIDR = in.PodCIDR + out.ResolverConfig = in.ResolverConfig + out.CPUCFSQuota = in.CPUCFSQuota + out.Containerized = in.Containerized + out.MaxOpenFiles = in.MaxOpenFiles + out.ReconcileCIDR = in.ReconcileCIDR + out.RegisterSchedulable = in.RegisterSchedulable + out.KubeAPIQPS = in.KubeAPIQPS + out.KubeAPIBurst = in.KubeAPIBurst + out.SerializeImagePulls = in.SerializeImagePulls + out.ExperimentalFlannelOverlay = in.ExperimentalFlannelOverlay + if err := unversioned.DeepCopy_unversioned_Duration(in.OutOfDiskTransitionFrequency, &out.OutOfDiskTransitionFrequency, c); err != nil { + return err + } + out.NodeIP = in.NodeIP + if in.NodeLabels != nil { + in, out := in.NodeLabels, &out.NodeLabels + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val + } + } else { + out.NodeLabels = nil + } + out.NonMasqueradeCIDR = in.NonMasqueradeCIDR + out.EnableCustomMetrics = in.EnableCustomMetrics + return nil +} + +func DeepCopy_componentconfig_LeaderElectionConfiguration(in LeaderElectionConfiguration, out *LeaderElectionConfiguration, c *conversion.Cloner) error { + out.LeaderElect = in.LeaderElect + if err := unversioned.DeepCopy_unversioned_Duration(in.LeaseDuration, &out.LeaseDuration, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Duration(in.RenewDeadline, &out.RenewDeadline, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Duration(in.RetryPeriod, &out.RetryPeriod, c); err != nil { + return err + } + return nil +} + +func DeepCopy_componentconfig_PersistentVolumeRecyclerConfiguration(in PersistentVolumeRecyclerConfiguration, out *PersistentVolumeRecyclerConfiguration, c *conversion.Cloner) error { + out.MaximumRetry = in.MaximumRetry + out.MinimumTimeoutNFS = in.MinimumTimeoutNFS + out.PodTemplateFilePathNFS = in.PodTemplateFilePathNFS + out.IncrementTimeoutNFS = in.IncrementTimeoutNFS + out.PodTemplateFilePathHostPath = in.PodTemplateFilePathHostPath + out.MinimumTimeoutHostPath = in.MinimumTimeoutHostPath + out.IncrementTimeoutHostPath = in.IncrementTimeoutHostPath + return nil +} + +func DeepCopy_componentconfig_PortRangeVar(in PortRangeVar, out *PortRangeVar, c *conversion.Cloner) error { + if in.Val != nil { + in, out := in.Val, &out.Val + *out = new(string) + **out = *in + } else { + out.Val = nil + } + return nil +} + +func DeepCopy_componentconfig_VolumeConfiguration(in VolumeConfiguration, out *VolumeConfiguration, c *conversion.Cloner) error { + out.EnableHostPathProvisioning = in.EnableHostPathProvisioning + if err := DeepCopy_componentconfig_PersistentVolumeRecyclerConfiguration(in.PersistentVolumeRecyclerConfiguration, &out.PersistentVolumeRecyclerConfiguration, c); err != nil { + return err + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/types.generated.go b/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/types.generated.go index 755fe14a6..3ca4b476c 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/types.generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/types.generated.go @@ -1094,6 +1094,32 @@ func (x *ProxyMode) CodecDecodeSelf(d *codec1978.Decoder) { } } +func (x HairpinMode) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x)) + } +} + +func (x *HairpinMode) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + *((*string)(x)) = r.DecodeString() + } +} + func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -1108,24 +1134,25 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [76]bool + var yyq2 [79]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[46] = x.CloudProvider != "" - yyq2[47] = x.CloudConfigFile != "" - yyq2[48] = x.KubeletCgroups != "" - yyq2[49] = x.RuntimeCgroups != "" - yyq2[50] = x.SystemCgroups != "" - yyq2[51] = x.CgroupRoot != "" - yyq2[53] = x.RktPath != "" - yyq2[55] = x.RktStage1Image != "" - yyq2[71] = true - yyq2[72] = x.NodeIP != "" + yyq2[47] = x.CloudProvider != "" + yyq2[48] = x.CloudConfigFile != "" + yyq2[49] = x.KubeletCgroups != "" + yyq2[50] = x.RuntimeCgroups != "" + yyq2[51] = x.SystemCgroups != "" + yyq2[52] = x.CgroupRoot != "" + yyq2[54] = x.RktPath != "" + yyq2[55] = x.RktAPIEndpoint != "" + yyq2[56] = x.RktStage1Image != "" + yyq2[74] = true + yyq2[75] = x.NodeIP != "" var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(76) + r.EncodeArrayStart(79) } else { - yynn2 = 66 + yynn2 = 68 for _, b := range yyq2 { if b { yynn2++ @@ -1925,8 +1952,35 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym133 := z.EncBinary() - _ = yym133 + yy133 := &x.ImageMinimumGCAge + yym134 := z.EncBinary() + _ = yym134 + if false { + } else if z.HasExtensions() && z.EncExt(yy133) { + } else if !yym134 && z.IsJSONHandle() { + z.EncJSONMarshal(yy133) + } else { + z.EncFallback(yy133) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("imageMinimumGCAge")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy135 := &x.ImageMinimumGCAge + yym136 := z.EncBinary() + _ = yym136 + if false { + } else if z.HasExtensions() && z.EncExt(yy135) { + } else if !yym136 && z.IsJSONHandle() { + z.EncJSONMarshal(yy135) + } else { + z.EncFallback(yy135) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym138 := z.EncBinary() + _ = yym138 if false { } else { r.EncodeInt(int64(x.ImageGCHighThresholdPercent)) @@ -1935,8 +1989,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("imageGCHighThresholdPercent")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym134 := z.EncBinary() - _ = yym134 + yym139 := z.EncBinary() + _ = yym139 if false { } else { r.EncodeInt(int64(x.ImageGCHighThresholdPercent)) @@ -1944,8 +1998,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym136 := z.EncBinary() - _ = yym136 + yym141 := z.EncBinary() + _ = yym141 if false { } else { r.EncodeInt(int64(x.ImageGCLowThresholdPercent)) @@ -1954,8 +2008,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("imageGCLowThresholdPercent")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym137 := z.EncBinary() - _ = yym137 + yym142 := z.EncBinary() + _ = yym142 if false { } else { r.EncodeInt(int64(x.ImageGCLowThresholdPercent)) @@ -1963,8 +2017,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym139 := z.EncBinary() - _ = yym139 + yym144 := z.EncBinary() + _ = yym144 if false { } else { r.EncodeInt(int64(x.LowDiskSpaceThresholdMB)) @@ -1973,8 +2027,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("lowDiskSpaceThresholdMB")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym140 := z.EncBinary() - _ = yym140 + yym145 := z.EncBinary() + _ = yym145 if false { } else { r.EncodeInt(int64(x.LowDiskSpaceThresholdMB)) @@ -1982,35 +2036,35 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy142 := &x.VolumeStatsAggPeriod - yym143 := z.EncBinary() - _ = yym143 + yy147 := &x.VolumeStatsAggPeriod + yym148 := z.EncBinary() + _ = yym148 if false { - } else if z.HasExtensions() && z.EncExt(yy142) { - } else if !yym143 && z.IsJSONHandle() { - z.EncJSONMarshal(yy142) + } else if z.HasExtensions() && z.EncExt(yy147) { + } else if !yym148 && z.IsJSONHandle() { + z.EncJSONMarshal(yy147) } else { - z.EncFallback(yy142) + z.EncFallback(yy147) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("VolumeStatsAggPeriod")) + r.EncodeString(codecSelferC_UTF81234, string("volumeStatsAggPeriod")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy144 := &x.VolumeStatsAggPeriod - yym145 := z.EncBinary() - _ = yym145 + yy149 := &x.VolumeStatsAggPeriod + yym150 := z.EncBinary() + _ = yym150 if false { - } else if z.HasExtensions() && z.EncExt(yy144) { - } else if !yym145 && z.IsJSONHandle() { - z.EncJSONMarshal(yy144) + } else if z.HasExtensions() && z.EncExt(yy149) { + } else if !yym150 && z.IsJSONHandle() { + z.EncJSONMarshal(yy149) } else { - z.EncFallback(yy144) + z.EncFallback(yy149) } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym147 := z.EncBinary() - _ = yym147 + yym152 := z.EncBinary() + _ = yym152 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.NetworkPluginName)) @@ -2019,8 +2073,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("networkPluginName")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym148 := z.EncBinary() - _ = yym148 + yym153 := z.EncBinary() + _ = yym153 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.NetworkPluginName)) @@ -2028,8 +2082,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym150 := z.EncBinary() - _ = yym150 + yym155 := z.EncBinary() + _ = yym155 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.NetworkPluginDir)) @@ -2038,8 +2092,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("networkPluginDir")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym151 := z.EncBinary() - _ = yym151 + yym156 := z.EncBinary() + _ = yym156 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.NetworkPluginDir)) @@ -2047,8 +2101,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym153 := z.EncBinary() - _ = yym153 + yym158 := z.EncBinary() + _ = yym158 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.VolumePluginDir)) @@ -2057,8 +2111,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("volumePluginDir")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym154 := z.EncBinary() - _ = yym154 + yym159 := z.EncBinary() + _ = yym159 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.VolumePluginDir)) @@ -2066,9 +2120,9 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[46] { - yym156 := z.EncBinary() - _ = yym156 + if yyq2[47] { + yym161 := z.EncBinary() + _ = yym161 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.CloudProvider)) @@ -2077,62 +2131,62 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { r.EncodeString(codecSelferC_UTF81234, "") } } else { - if yyq2[46] { + if yyq2[47] { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("cloudProvider")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym157 := z.EncBinary() - _ = yym157 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.CloudProvider)) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[47] { - yym159 := z.EncBinary() - _ = yym159 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.CloudConfigFile)) - } - } else { - r.EncodeString(codecSelferC_UTF81234, "") - } - } else { - if yyq2[47] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("cloudConfigFile")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym160 := z.EncBinary() - _ = yym160 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.CloudConfigFile)) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[48] { yym162 := z.EncBinary() _ = yym162 if false { } else { - r.EncodeString(codecSelferC_UTF81234, string(x.KubeletCgroups)) + r.EncodeString(codecSelferC_UTF81234, string(x.CloudProvider)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[48] { + yym164 := z.EncBinary() + _ = yym164 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.CloudConfigFile)) } } else { r.EncodeString(codecSelferC_UTF81234, "") } } else { if yyq2[48] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("cloudConfigFile")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym165 := z.EncBinary() + _ = yym165 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.CloudConfigFile)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[49] { + yym167 := z.EncBinary() + _ = yym167 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.KubeletCgroups)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[49] { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("kubeletCgroups")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym163 := z.EncBinary() - _ = yym163 + yym168 := z.EncBinary() + _ = yym168 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.KubeletCgroups)) @@ -2141,9 +2195,9 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[49] { - yym165 := z.EncBinary() - _ = yym165 + if yyq2[50] { + yym170 := z.EncBinary() + _ = yym170 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.RuntimeCgroups)) @@ -2152,62 +2206,62 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { r.EncodeString(codecSelferC_UTF81234, "") } } else { - if yyq2[49] { + if yyq2[50] { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("runtimeCgroups")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym166 := z.EncBinary() - _ = yym166 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.RuntimeCgroups)) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[50] { - yym168 := z.EncBinary() - _ = yym168 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.SystemCgroups)) - } - } else { - r.EncodeString(codecSelferC_UTF81234, "") - } - } else { - if yyq2[50] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("systemContainer")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym169 := z.EncBinary() - _ = yym169 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.SystemCgroups)) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[51] { yym171 := z.EncBinary() _ = yym171 if false { } else { - r.EncodeString(codecSelferC_UTF81234, string(x.CgroupRoot)) + r.EncodeString(codecSelferC_UTF81234, string(x.RuntimeCgroups)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[51] { + yym173 := z.EncBinary() + _ = yym173 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.SystemCgroups)) } } else { r.EncodeString(codecSelferC_UTF81234, "") } } else { if yyq2[51] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("systemContainer")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym174 := z.EncBinary() + _ = yym174 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.SystemCgroups)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[52] { + yym176 := z.EncBinary() + _ = yym176 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.CgroupRoot)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[52] { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("cgroupRoot")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym172 := z.EncBinary() - _ = yym172 + yym177 := z.EncBinary() + _ = yym177 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.CgroupRoot)) @@ -2216,8 +2270,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym174 := z.EncBinary() - _ = yym174 + yym179 := z.EncBinary() + _ = yym179 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.ContainerRuntime)) @@ -2226,8 +2280,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("containerRuntime")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym175 := z.EncBinary() - _ = yym175 + yym180 := z.EncBinary() + _ = yym180 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.ContainerRuntime)) @@ -2235,9 +2289,9 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[53] { - yym177 := z.EncBinary() - _ = yym177 + if yyq2[54] { + yym182 := z.EncBinary() + _ = yym182 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.RktPath)) @@ -2246,12 +2300,12 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { r.EncodeString(codecSelferC_UTF81234, "") } } else { - if yyq2[53] { + if yyq2[54] { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("rktPath")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym178 := z.EncBinary() - _ = yym178 + yym183 := z.EncBinary() + _ = yym183 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.RktPath)) @@ -2260,8 +2314,58 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym180 := z.EncBinary() - _ = yym180 + if yyq2[55] { + yym185 := z.EncBinary() + _ = yym185 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.RktAPIEndpoint)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[55] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("rktAPIEndpoint")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym186 := z.EncBinary() + _ = yym186 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.RktAPIEndpoint)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[56] { + yym188 := z.EncBinary() + _ = yym188 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.RktStage1Image)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[56] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("rktStage1Image")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym189 := z.EncBinary() + _ = yym189 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.RktStage1Image)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym191 := z.EncBinary() + _ = yym191 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.LockFilePath)) @@ -2270,8 +2374,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("lockFilePath")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym181 := z.EncBinary() - _ = yym181 + yym192 := z.EncBinary() + _ = yym192 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.LockFilePath)) @@ -2279,33 +2383,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[55] { - yym183 := z.EncBinary() - _ = yym183 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.RktStage1Image)) - } - } else { - r.EncodeString(codecSelferC_UTF81234, "") - } - } else { - if yyq2[55] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("rktStage1Image")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym184 := z.EncBinary() - _ = yym184 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.RktStage1Image)) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym186 := z.EncBinary() - _ = yym186 + yym194 := z.EncBinary() + _ = yym194 if false { } else { r.EncodeBool(bool(x.ConfigureCBR0)) @@ -2314,8 +2393,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("configureCbr0")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym187 := z.EncBinary() - _ = yym187 + yym195 := z.EncBinary() + _ = yym195 if false { } else { r.EncodeBool(bool(x.ConfigureCBR0)) @@ -2323,27 +2402,46 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym189 := z.EncBinary() - _ = yym189 + yym197 := z.EncBinary() + _ = yym197 if false { } else { - r.EncodeBool(bool(x.HairpinMode)) + r.EncodeString(codecSelferC_UTF81234, string(x.HairpinMode)) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("configureHairpinMode")) + r.EncodeString(codecSelferC_UTF81234, string("hairpinMode")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym190 := z.EncBinary() - _ = yym190 + yym198 := z.EncBinary() + _ = yym198 if false { } else { - r.EncodeBool(bool(x.HairpinMode)) + r.EncodeString(codecSelferC_UTF81234, string(x.HairpinMode)) } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym192 := z.EncBinary() - _ = yym192 + yym200 := z.EncBinary() + _ = yym200 + if false { + } else { + r.EncodeBool(bool(x.BabysitDaemons)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("babysitDaemons")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym201 := z.EncBinary() + _ = yym201 + if false { + } else { + r.EncodeBool(bool(x.BabysitDaemons)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym203 := z.EncBinary() + _ = yym203 if false { } else { r.EncodeInt(int64(x.MaxPods)) @@ -2352,8 +2450,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("maxPods")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym193 := z.EncBinary() - _ = yym193 + yym204 := z.EncBinary() + _ = yym204 if false { } else { r.EncodeInt(int64(x.MaxPods)) @@ -2361,8 +2459,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym195 := z.EncBinary() - _ = yym195 + yym206 := z.EncBinary() + _ = yym206 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.DockerExecHandlerName)) @@ -2371,8 +2469,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("dockerExecHandlerName")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym196 := z.EncBinary() - _ = yym196 + yym207 := z.EncBinary() + _ = yym207 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.DockerExecHandlerName)) @@ -2380,8 +2478,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym198 := z.EncBinary() - _ = yym198 + yym209 := z.EncBinary() + _ = yym209 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.PodCIDR)) @@ -2390,8 +2488,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("podCIDR")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym199 := z.EncBinary() - _ = yym199 + yym210 := z.EncBinary() + _ = yym210 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.PodCIDR)) @@ -2399,8 +2497,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym201 := z.EncBinary() - _ = yym201 + yym212 := z.EncBinary() + _ = yym212 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.ResolverConfig)) @@ -2409,8 +2507,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("resolvConf")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym202 := z.EncBinary() - _ = yym202 + yym213 := z.EncBinary() + _ = yym213 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.ResolverConfig)) @@ -2418,8 +2516,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym204 := z.EncBinary() - _ = yym204 + yym215 := z.EncBinary() + _ = yym215 if false { } else { r.EncodeBool(bool(x.CPUCFSQuota)) @@ -2428,8 +2526,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("cpuCFSQuota")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym205 := z.EncBinary() - _ = yym205 + yym216 := z.EncBinary() + _ = yym216 if false { } else { r.EncodeBool(bool(x.CPUCFSQuota)) @@ -2437,8 +2535,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym207 := z.EncBinary() - _ = yym207 + yym218 := z.EncBinary() + _ = yym218 if false { } else { r.EncodeBool(bool(x.Containerized)) @@ -2447,8 +2545,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("containerized")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym208 := z.EncBinary() - _ = yym208 + yym219 := z.EncBinary() + _ = yym219 if false { } else { r.EncodeBool(bool(x.Containerized)) @@ -2456,8 +2554,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym210 := z.EncBinary() - _ = yym210 + yym221 := z.EncBinary() + _ = yym221 if false { } else { r.EncodeUint(uint64(x.MaxOpenFiles)) @@ -2466,8 +2564,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("maxOpenFiles")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym211 := z.EncBinary() - _ = yym211 + yym222 := z.EncBinary() + _ = yym222 if false { } else { r.EncodeUint(uint64(x.MaxOpenFiles)) @@ -2475,8 +2573,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym213 := z.EncBinary() - _ = yym213 + yym224 := z.EncBinary() + _ = yym224 if false { } else { r.EncodeBool(bool(x.ReconcileCIDR)) @@ -2485,8 +2583,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("reconcileCIDR")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym214 := z.EncBinary() - _ = yym214 + yym225 := z.EncBinary() + _ = yym225 if false { } else { r.EncodeBool(bool(x.ReconcileCIDR)) @@ -2494,8 +2592,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym216 := z.EncBinary() - _ = yym216 + yym227 := z.EncBinary() + _ = yym227 if false { } else { r.EncodeBool(bool(x.RegisterSchedulable)) @@ -2504,8 +2602,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("registerSchedulable")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym217 := z.EncBinary() - _ = yym217 + yym228 := z.EncBinary() + _ = yym228 if false { } else { r.EncodeBool(bool(x.RegisterSchedulable)) @@ -2513,8 +2611,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym219 := z.EncBinary() - _ = yym219 + yym230 := z.EncBinary() + _ = yym230 if false { } else { r.EncodeFloat32(float32(x.KubeAPIQPS)) @@ -2523,8 +2621,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("kubeAPIQPS")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym220 := z.EncBinary() - _ = yym220 + yym231 := z.EncBinary() + _ = yym231 if false { } else { r.EncodeFloat32(float32(x.KubeAPIQPS)) @@ -2532,8 +2630,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym222 := z.EncBinary() - _ = yym222 + yym233 := z.EncBinary() + _ = yym233 if false { } else { r.EncodeInt(int64(x.KubeAPIBurst)) @@ -2542,8 +2640,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("kubeAPIBurst")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym223 := z.EncBinary() - _ = yym223 + yym234 := z.EncBinary() + _ = yym234 if false { } else { r.EncodeInt(int64(x.KubeAPIBurst)) @@ -2551,8 +2649,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym225 := z.EncBinary() - _ = yym225 + yym236 := z.EncBinary() + _ = yym236 if false { } else { r.EncodeBool(bool(x.SerializeImagePulls)) @@ -2561,8 +2659,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("serializeImagePulls")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym226 := z.EncBinary() - _ = yym226 + yym237 := z.EncBinary() + _ = yym237 if false { } else { r.EncodeBool(bool(x.SerializeImagePulls)) @@ -2570,8 +2668,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym228 := z.EncBinary() - _ = yym228 + yym239 := z.EncBinary() + _ = yym239 if false { } else { r.EncodeBool(bool(x.ExperimentalFlannelOverlay)) @@ -2580,8 +2678,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("experimentalFlannelOverlay")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym229 := z.EncBinary() - _ = yym229 + yym240 := z.EncBinary() + _ = yym240 if false { } else { r.EncodeBool(bool(x.ExperimentalFlannelOverlay)) @@ -2589,42 +2687,42 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[71] { - yy231 := &x.OutOfDiskTransitionFrequency - yym232 := z.EncBinary() - _ = yym232 + if yyq2[74] { + yy242 := &x.OutOfDiskTransitionFrequency + yym243 := z.EncBinary() + _ = yym243 if false { - } else if z.HasExtensions() && z.EncExt(yy231) { - } else if !yym232 && z.IsJSONHandle() { - z.EncJSONMarshal(yy231) + } else if z.HasExtensions() && z.EncExt(yy242) { + } else if !yym243 && z.IsJSONHandle() { + z.EncJSONMarshal(yy242) } else { - z.EncFallback(yy231) + z.EncFallback(yy242) } } else { r.EncodeNil() } } else { - if yyq2[71] { + if yyq2[74] { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("outOfDiskTransitionFrequency")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy233 := &x.OutOfDiskTransitionFrequency - yym234 := z.EncBinary() - _ = yym234 + yy244 := &x.OutOfDiskTransitionFrequency + yym245 := z.EncBinary() + _ = yym245 if false { - } else if z.HasExtensions() && z.EncExt(yy233) { - } else if !yym234 && z.IsJSONHandle() { - z.EncJSONMarshal(yy233) + } else if z.HasExtensions() && z.EncExt(yy244) { + } else if !yym245 && z.IsJSONHandle() { + z.EncJSONMarshal(yy244) } else { - z.EncFallback(yy233) + z.EncFallback(yy244) } } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[72] { - yym236 := z.EncBinary() - _ = yym236 + if yyq2[75] { + yym247 := z.EncBinary() + _ = yym247 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.NodeIP)) @@ -2633,12 +2731,12 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { r.EncodeString(codecSelferC_UTF81234, "") } } else { - if yyq2[72] { + if yyq2[75] { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("nodeIP")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym237 := z.EncBinary() - _ = yym237 + yym248 := z.EncBinary() + _ = yym248 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.NodeIP)) @@ -2650,8 +2748,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { if x.NodeLabels == nil { r.EncodeNil() } else { - yym239 := z.EncBinary() - _ = yym239 + yym250 := z.EncBinary() + _ = yym250 if false { } else { z.F.EncMapStringStringV(x.NodeLabels, false, e) @@ -2664,8 +2762,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { if x.NodeLabels == nil { r.EncodeNil() } else { - yym240 := z.EncBinary() - _ = yym240 + yym251 := z.EncBinary() + _ = yym251 if false { } else { z.F.EncMapStringStringV(x.NodeLabels, false, e) @@ -2674,8 +2772,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym242 := z.EncBinary() - _ = yym242 + yym253 := z.EncBinary() + _ = yym253 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.NonMasqueradeCIDR)) @@ -2684,8 +2782,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("nonMasqueradeCIDR")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym243 := z.EncBinary() - _ = yym243 + yym254 := z.EncBinary() + _ = yym254 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.NonMasqueradeCIDR)) @@ -2693,8 +2791,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym245 := z.EncBinary() - _ = yym245 + yym256 := z.EncBinary() + _ = yym256 if false { } else { r.EncodeBool(bool(x.EnableCustomMetrics)) @@ -2703,8 +2801,8 @@ func (x *KubeletConfiguration) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("enableCustomMetrics")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym246 := z.EncBinary() - _ = yym246 + yym257 := z.EncBinary() + _ = yym257 if false { } else { r.EncodeBool(bool(x.EnableCustomMetrics)) @@ -3059,6 +3157,21 @@ func (x *KubeletConfiguration) codecDecodeSelfFromMap(l int, d *codec1978.Decode z.DecFallback(yyv47, false) } } + case "imageMinimumGCAge": + if r.TryDecodeAsNil() { + x.ImageMinimumGCAge = pkg1_unversioned.Duration{} + } else { + yyv49 := &x.ImageMinimumGCAge + yym50 := z.DecBinary() + _ = yym50 + if false { + } else if z.HasExtensions() && z.DecExt(yyv49) { + } else if !yym50 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv49) + } else { + z.DecFallback(yyv49, false) + } + } case "imageGCHighThresholdPercent": if r.TryDecodeAsNil() { x.ImageGCHighThresholdPercent = 0 @@ -3077,19 +3190,19 @@ func (x *KubeletConfiguration) codecDecodeSelfFromMap(l int, d *codec1978.Decode } else { x.LowDiskSpaceThresholdMB = int(r.DecodeInt(codecSelferBitsize1234)) } - case "VolumeStatsAggPeriod": + case "volumeStatsAggPeriod": if r.TryDecodeAsNil() { x.VolumeStatsAggPeriod = pkg1_unversioned.Duration{} } else { - yyv52 := &x.VolumeStatsAggPeriod - yym53 := z.DecBinary() - _ = yym53 + yyv54 := &x.VolumeStatsAggPeriod + yym55 := z.DecBinary() + _ = yym55 if false { - } else if z.HasExtensions() && z.DecExt(yyv52) { - } else if !yym53 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv52) + } else if z.HasExtensions() && z.DecExt(yyv54) { + } else if !yym55 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv54) } else { - z.DecFallback(yyv52, false) + z.DecFallback(yyv54, false) } } case "networkPluginName": @@ -3158,11 +3271,11 @@ func (x *KubeletConfiguration) codecDecodeSelfFromMap(l int, d *codec1978.Decode } else { x.RktPath = string(r.DecodeString()) } - case "lockFilePath": + case "rktAPIEndpoint": if r.TryDecodeAsNil() { - x.LockFilePath = "" + x.RktAPIEndpoint = "" } else { - x.LockFilePath = string(r.DecodeString()) + x.RktAPIEndpoint = string(r.DecodeString()) } case "rktStage1Image": if r.TryDecodeAsNil() { @@ -3170,17 +3283,29 @@ func (x *KubeletConfiguration) codecDecodeSelfFromMap(l int, d *codec1978.Decode } else { x.RktStage1Image = string(r.DecodeString()) } + case "lockFilePath": + if r.TryDecodeAsNil() { + x.LockFilePath = "" + } else { + x.LockFilePath = string(r.DecodeString()) + } case "configureCbr0": if r.TryDecodeAsNil() { x.ConfigureCBR0 = false } else { x.ConfigureCBR0 = bool(r.DecodeBool()) } - case "configureHairpinMode": + case "hairpinMode": if r.TryDecodeAsNil() { - x.HairpinMode = false + x.HairpinMode = "" } else { - x.HairpinMode = bool(r.DecodeBool()) + x.HairpinMode = string(r.DecodeString()) + } + case "babysitDaemons": + if r.TryDecodeAsNil() { + x.BabysitDaemons = false + } else { + x.BabysitDaemons = bool(r.DecodeBool()) } case "maxPods": if r.TryDecodeAsNil() { @@ -3264,15 +3389,15 @@ func (x *KubeletConfiguration) codecDecodeSelfFromMap(l int, d *codec1978.Decode if r.TryDecodeAsNil() { x.OutOfDiskTransitionFrequency = pkg1_unversioned.Duration{} } else { - yyv82 := &x.OutOfDiskTransitionFrequency - yym83 := z.DecBinary() - _ = yym83 + yyv86 := &x.OutOfDiskTransitionFrequency + yym87 := z.DecBinary() + _ = yym87 if false { - } else if z.HasExtensions() && z.DecExt(yyv82) { - } else if !yym83 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv82) + } else if z.HasExtensions() && z.DecExt(yyv86) { + } else if !yym87 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv86) } else { - z.DecFallback(yyv82, false) + z.DecFallback(yyv86, false) } } case "nodeIP": @@ -3285,12 +3410,12 @@ func (x *KubeletConfiguration) codecDecodeSelfFromMap(l int, d *codec1978.Decode if r.TryDecodeAsNil() { x.NodeLabels = nil } else { - yyv85 := &x.NodeLabels - yym86 := z.DecBinary() - _ = yym86 + yyv89 := &x.NodeLabels + yym90 := z.DecBinary() + _ = yym90 if false { } else { - z.F.DecMapStringStringX(yyv85, false, d) + z.F.DecMapStringStringX(yyv89, false, d) } } case "nonMasqueradeCIDR": @@ -3316,16 +3441,16 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj89 int - var yyb89 bool - var yyhl89 bool = l >= 0 - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + var yyj93 int + var yyb93 bool + var yyhl93 bool = l >= 0 + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3335,13 +3460,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.Config = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3349,57 +3474,7 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco if r.TryDecodeAsNil() { x.SyncFrequency = pkg1_unversioned.Duration{} } else { - yyv91 := &x.SyncFrequency - yym92 := z.DecBinary() - _ = yym92 - if false { - } else if z.HasExtensions() && z.DecExt(yyv91) { - } else if !yym92 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv91) - } else { - z.DecFallback(yyv91, false) - } - } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l - } else { - yyb89 = r.CheckBreak() - } - if yyb89 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.FileCheckFrequency = pkg1_unversioned.Duration{} - } else { - yyv93 := &x.FileCheckFrequency - yym94 := z.DecBinary() - _ = yym94 - if false { - } else if z.HasExtensions() && z.DecExt(yyv93) { - } else if !yym94 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv93) - } else { - z.DecFallback(yyv93, false) - } - } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l - } else { - yyb89 = r.CheckBreak() - } - if yyb89 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.HTTPCheckFrequency = pkg1_unversioned.Duration{} - } else { - yyv95 := &x.HTTPCheckFrequency + yyv95 := &x.SyncFrequency yym96 := z.DecBinary() _ = yym96 if false { @@ -3410,13 +3485,63 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco z.DecFallback(yyv95, false) } } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.FileCheckFrequency = pkg1_unversioned.Duration{} + } else { + yyv97 := &x.FileCheckFrequency + yym98 := z.DecBinary() + _ = yym98 + if false { + } else if z.HasExtensions() && z.DecExt(yyv97) { + } else if !yym98 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv97) + } else { + z.DecFallback(yyv97, false) + } + } + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l + } else { + yyb93 = r.CheckBreak() + } + if yyb93 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.HTTPCheckFrequency = pkg1_unversioned.Duration{} + } else { + yyv99 := &x.HTTPCheckFrequency + yym100 := z.DecBinary() + _ = yym100 + if false { + } else if z.HasExtensions() && z.DecExt(yyv99) { + } else if !yym100 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv99) + } else { + z.DecFallback(yyv99, false) + } + } + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l + } else { + yyb93 = r.CheckBreak() + } + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3426,13 +3551,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.ManifestURL = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3442,13 +3567,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.ManifestURLHeader = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3458,13 +3583,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.EnableServer = bool(r.DecodeBool()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3474,13 +3599,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.Address = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3490,13 +3615,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.Port = uint(r.DecodeUint(codecSelferBitsize1234)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3506,13 +3631,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.ReadOnlyPort = uint(r.DecodeUint(codecSelferBitsize1234)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3522,13 +3647,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.TLSCertFile = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3538,13 +3663,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.TLSPrivateKeyFile = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3554,13 +3679,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.CertDirectory = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3570,13 +3695,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.HostnameOverride = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3586,13 +3711,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.PodInfraContainerImage = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3602,13 +3727,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.DockerEndpoint = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3618,13 +3743,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.RootDirectory = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3634,13 +3759,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.AllowPrivileged = bool(r.DecodeBool()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3650,13 +3775,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.HostNetworkSources = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3666,13 +3791,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.HostPIDSources = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3682,13 +3807,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.HostIPCSources = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3698,13 +3823,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.RegistryPullQPS = float64(r.DecodeFloat(false)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3714,13 +3839,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.RegistryBurst = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3730,13 +3855,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.EventRecordQPS = float32(r.DecodeFloat(true)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3746,13 +3871,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.EventBurst = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3762,13 +3887,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.EnableDebuggingHandlers = bool(r.DecodeBool()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3776,24 +3901,24 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco if r.TryDecodeAsNil() { x.MinimumGCAge = pkg1_unversioned.Duration{} } else { - yyv119 := &x.MinimumGCAge - yym120 := z.DecBinary() - _ = yym120 + yyv123 := &x.MinimumGCAge + yym124 := z.DecBinary() + _ = yym124 if false { - } else if z.HasExtensions() && z.DecExt(yyv119) { - } else if !yym120 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv119) + } else if z.HasExtensions() && z.DecExt(yyv123) { + } else if !yym124 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv123) } else { - z.DecFallback(yyv119, false) + z.DecFallback(yyv123, false) } } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3803,13 +3928,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.MaxPerPodContainerCount = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3819,13 +3944,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.MaxContainerCount = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3835,13 +3960,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.CAdvisorPort = uint(r.DecodeUint(codecSelferBitsize1234)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3851,13 +3976,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.HealthzPort = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3867,13 +3992,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.HealthzBindAddress = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3883,13 +4008,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.OOMScoreAdj = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3899,13 +4024,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.RegisterNode = bool(r.DecodeBool()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3915,13 +4040,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.ClusterDomain = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3931,13 +4056,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.MasterServiceNamespace = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3947,13 +4072,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.ClusterDNS = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3961,24 +4086,24 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco if r.TryDecodeAsNil() { x.StreamingConnectionIdleTimeout = pkg1_unversioned.Duration{} } else { - yyv131 := &x.StreamingConnectionIdleTimeout - yym132 := z.DecBinary() - _ = yym132 + yyv135 := &x.StreamingConnectionIdleTimeout + yym136 := z.DecBinary() + _ = yym136 if false { - } else if z.HasExtensions() && z.DecExt(yyv131) { - } else if !yym132 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv131) + } else if z.HasExtensions() && z.DecExt(yyv135) { + } else if !yym136 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv135) } else { - z.DecFallback(yyv131, false) + z.DecFallback(yyv135, false) } } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -3986,24 +4111,49 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco if r.TryDecodeAsNil() { x.NodeStatusUpdateFrequency = pkg1_unversioned.Duration{} } else { - yyv133 := &x.NodeStatusUpdateFrequency - yym134 := z.DecBinary() - _ = yym134 + yyv137 := &x.NodeStatusUpdateFrequency + yym138 := z.DecBinary() + _ = yym138 if false { - } else if z.HasExtensions() && z.DecExt(yyv133) { - } else if !yym134 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv133) + } else if z.HasExtensions() && z.DecExt(yyv137) { + } else if !yym138 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv137) } else { - z.DecFallback(yyv133, false) + z.DecFallback(yyv137, false) } } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ImageMinimumGCAge = pkg1_unversioned.Duration{} + } else { + yyv139 := &x.ImageMinimumGCAge + yym140 := z.DecBinary() + _ = yym140 + if false { + } else if z.HasExtensions() && z.DecExt(yyv139) { + } else if !yym140 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv139) + } else { + z.DecFallback(yyv139, false) + } + } + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l + } else { + yyb93 = r.CheckBreak() + } + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4013,13 +4163,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.ImageGCHighThresholdPercent = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4029,13 +4179,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.ImageGCLowThresholdPercent = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4045,13 +4195,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.LowDiskSpaceThresholdMB = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4059,24 +4209,24 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco if r.TryDecodeAsNil() { x.VolumeStatsAggPeriod = pkg1_unversioned.Duration{} } else { - yyv138 := &x.VolumeStatsAggPeriod - yym139 := z.DecBinary() - _ = yym139 + yyv144 := &x.VolumeStatsAggPeriod + yym145 := z.DecBinary() + _ = yym145 if false { - } else if z.HasExtensions() && z.DecExt(yyv138) { - } else if !yym139 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv138) + } else if z.HasExtensions() && z.DecExt(yyv144) { + } else if !yym145 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv144) } else { - z.DecFallback(yyv138, false) + z.DecFallback(yyv144, false) } } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4086,13 +4236,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.NetworkPluginName = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4102,13 +4252,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.NetworkPluginDir = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4118,13 +4268,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.VolumePluginDir = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4134,13 +4284,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.CloudProvider = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4150,13 +4300,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.CloudConfigFile = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4166,13 +4316,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.KubeletCgroups = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4182,13 +4332,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.RuntimeCgroups = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4198,13 +4348,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.SystemCgroups = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4214,13 +4364,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.CgroupRoot = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4230,13 +4380,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.ContainerRuntime = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4246,29 +4396,29 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.RktPath = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.LockFilePath = "" + x.RktAPIEndpoint = "" } else { - x.LockFilePath = string(r.DecodeString()) + x.RktAPIEndpoint = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4278,13 +4428,29 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.RktStage1Image = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.LockFilePath = "" + } else { + x.LockFilePath = string(r.DecodeString()) + } + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l + } else { + yyb93 = r.CheckBreak() + } + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4294,29 +4460,45 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.ConfigureCBR0 = bool(r.DecodeBool()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.HairpinMode = false + x.HairpinMode = "" } else { - x.HairpinMode = bool(r.DecodeBool()) + x.HairpinMode = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.BabysitDaemons = false + } else { + x.BabysitDaemons = bool(r.DecodeBool()) + } + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l + } else { + yyb93 = r.CheckBreak() + } + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4326,13 +4508,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.MaxPods = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4342,13 +4524,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.DockerExecHandlerName = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4358,13 +4540,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.PodCIDR = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4374,13 +4556,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.ResolverConfig = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4390,13 +4572,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.CPUCFSQuota = bool(r.DecodeBool()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4406,13 +4588,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.Containerized = bool(r.DecodeBool()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4422,13 +4604,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.MaxOpenFiles = uint64(r.DecodeUint(64)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4438,13 +4620,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.ReconcileCIDR = bool(r.DecodeBool()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4454,13 +4636,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.RegisterSchedulable = bool(r.DecodeBool()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4470,13 +4652,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.KubeAPIQPS = float32(r.DecodeFloat(true)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4486,13 +4668,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.KubeAPIBurst = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4502,13 +4684,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.SerializeImagePulls = bool(r.DecodeBool()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4518,13 +4700,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.ExperimentalFlannelOverlay = bool(r.DecodeBool()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4532,24 +4714,24 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco if r.TryDecodeAsNil() { x.OutOfDiskTransitionFrequency = pkg1_unversioned.Duration{} } else { - yyv168 := &x.OutOfDiskTransitionFrequency - yym169 := z.DecBinary() - _ = yym169 + yyv176 := &x.OutOfDiskTransitionFrequency + yym177 := z.DecBinary() + _ = yym177 if false { - } else if z.HasExtensions() && z.DecExt(yyv168) { - } else if !yym169 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv168) + } else if z.HasExtensions() && z.DecExt(yyv176) { + } else if !yym177 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv176) } else { - z.DecFallback(yyv168, false) + z.DecFallback(yyv176, false) } } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4559,13 +4741,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.NodeIP = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4573,21 +4755,21 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco if r.TryDecodeAsNil() { x.NodeLabels = nil } else { - yyv171 := &x.NodeLabels - yym172 := z.DecBinary() - _ = yym172 + yyv179 := &x.NodeLabels + yym180 := z.DecBinary() + _ = yym180 if false { } else { - z.F.DecMapStringStringX(yyv171, false, d) + z.F.DecMapStringStringX(yyv179, false, d) } } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4597,13 +4779,13 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco } else { x.NonMasqueradeCIDR = string(r.DecodeString()) } - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4614,17 +4796,17 @@ func (x *KubeletConfiguration) codecDecodeSelfFromArray(l int, d *codec1978.Deco x.EnableCustomMetrics = bool(r.DecodeBool()) } for { - yyj89++ - if yyhl89 { - yyb89 = yyj89 > l + yyj93++ + if yyhl93 { + yyb93 = yyj93 > l } else { - yyb89 = r.CheckBreak() + yyb93 = r.CheckBreak() } - if yyb89 { + if yyb93 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj89-1, "") + z.DecStructFieldNotFound(yyj93-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -5586,16 +5768,16 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [40]bool + var yyq2 [43]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[38] = x.Kind != "" - yyq2[39] = x.APIVersion != "" + yyq2[41] = x.Kind != "" + yyq2[42] = x.APIVersion != "" var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(40) + r.EncodeArrayStart(43) } else { - yynn2 = 38 + yynn2 = 41 for _, b := range yyq2 { if b { yynn2++ @@ -5834,170 +6016,227 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy40 := &x.ServiceSyncPeriod + yym40 := z.EncBinary() + _ = yym40 + if false { + } else { + r.EncodeInt(int64(x.LookupCacheSizeForRC)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("lookupCacheSizeForRC")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) yym41 := z.EncBinary() _ = yym41 if false { - } else if z.HasExtensions() && z.EncExt(yy40) { - } else if !yym41 && z.IsJSONHandle() { - z.EncJSONMarshal(yy40) } else { - z.EncFallback(yy40) + r.EncodeInt(int64(x.LookupCacheSizeForRC)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym43 := z.EncBinary() + _ = yym43 + if false { + } else { + r.EncodeInt(int64(x.LookupCacheSizeForRS)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("lookupCacheSizeForRS")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym44 := z.EncBinary() + _ = yym44 + if false { + } else { + r.EncodeInt(int64(x.LookupCacheSizeForRS)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym46 := z.EncBinary() + _ = yym46 + if false { + } else { + r.EncodeInt(int64(x.LookupCacheSizeForDaemonSet)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("lookupCacheSizeForDaemonSet")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym47 := z.EncBinary() + _ = yym47 + if false { + } else { + r.EncodeInt(int64(x.LookupCacheSizeForDaemonSet)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy49 := &x.ServiceSyncPeriod + yym50 := z.EncBinary() + _ = yym50 + if false { + } else if z.HasExtensions() && z.EncExt(yy49) { + } else if !yym50 && z.IsJSONHandle() { + z.EncJSONMarshal(yy49) + } else { + z.EncFallback(yy49) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("serviceSyncPeriod")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy42 := &x.ServiceSyncPeriod - yym43 := z.EncBinary() - _ = yym43 + yy51 := &x.ServiceSyncPeriod + yym52 := z.EncBinary() + _ = yym52 if false { - } else if z.HasExtensions() && z.EncExt(yy42) { - } else if !yym43 && z.IsJSONHandle() { - z.EncJSONMarshal(yy42) + } else if z.HasExtensions() && z.EncExt(yy51) { + } else if !yym52 && z.IsJSONHandle() { + z.EncJSONMarshal(yy51) } else { - z.EncFallback(yy42) + z.EncFallback(yy51) } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy45 := &x.NodeSyncPeriod - yym46 := z.EncBinary() - _ = yym46 + yy54 := &x.NodeSyncPeriod + yym55 := z.EncBinary() + _ = yym55 if false { - } else if z.HasExtensions() && z.EncExt(yy45) { - } else if !yym46 && z.IsJSONHandle() { - z.EncJSONMarshal(yy45) + } else if z.HasExtensions() && z.EncExt(yy54) { + } else if !yym55 && z.IsJSONHandle() { + z.EncJSONMarshal(yy54) } else { - z.EncFallback(yy45) + z.EncFallback(yy54) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("nodeSyncPeriod")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy47 := &x.NodeSyncPeriod - yym48 := z.EncBinary() - _ = yym48 + yy56 := &x.NodeSyncPeriod + yym57 := z.EncBinary() + _ = yym57 if false { - } else if z.HasExtensions() && z.EncExt(yy47) { - } else if !yym48 && z.IsJSONHandle() { - z.EncJSONMarshal(yy47) + } else if z.HasExtensions() && z.EncExt(yy56) { + } else if !yym57 && z.IsJSONHandle() { + z.EncJSONMarshal(yy56) } else { - z.EncFallback(yy47) + z.EncFallback(yy56) } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy50 := &x.ResourceQuotaSyncPeriod - yym51 := z.EncBinary() - _ = yym51 + yy59 := &x.ResourceQuotaSyncPeriod + yym60 := z.EncBinary() + _ = yym60 if false { - } else if z.HasExtensions() && z.EncExt(yy50) { - } else if !yym51 && z.IsJSONHandle() { - z.EncJSONMarshal(yy50) + } else if z.HasExtensions() && z.EncExt(yy59) { + } else if !yym60 && z.IsJSONHandle() { + z.EncJSONMarshal(yy59) } else { - z.EncFallback(yy50) + z.EncFallback(yy59) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("resourceQuotaSyncPeriod")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy52 := &x.ResourceQuotaSyncPeriod - yym53 := z.EncBinary() - _ = yym53 + yy61 := &x.ResourceQuotaSyncPeriod + yym62 := z.EncBinary() + _ = yym62 if false { - } else if z.HasExtensions() && z.EncExt(yy52) { - } else if !yym53 && z.IsJSONHandle() { - z.EncJSONMarshal(yy52) + } else if z.HasExtensions() && z.EncExt(yy61) { + } else if !yym62 && z.IsJSONHandle() { + z.EncJSONMarshal(yy61) } else { - z.EncFallback(yy52) + z.EncFallback(yy61) } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy55 := &x.NamespaceSyncPeriod - yym56 := z.EncBinary() - _ = yym56 + yy64 := &x.NamespaceSyncPeriod + yym65 := z.EncBinary() + _ = yym65 if false { - } else if z.HasExtensions() && z.EncExt(yy55) { - } else if !yym56 && z.IsJSONHandle() { - z.EncJSONMarshal(yy55) + } else if z.HasExtensions() && z.EncExt(yy64) { + } else if !yym65 && z.IsJSONHandle() { + z.EncJSONMarshal(yy64) } else { - z.EncFallback(yy55) + z.EncFallback(yy64) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("namespaceSyncPeriod")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy57 := &x.NamespaceSyncPeriod - yym58 := z.EncBinary() - _ = yym58 + yy66 := &x.NamespaceSyncPeriod + yym67 := z.EncBinary() + _ = yym67 if false { - } else if z.HasExtensions() && z.EncExt(yy57) { - } else if !yym58 && z.IsJSONHandle() { - z.EncJSONMarshal(yy57) + } else if z.HasExtensions() && z.EncExt(yy66) { + } else if !yym67 && z.IsJSONHandle() { + z.EncJSONMarshal(yy66) } else { - z.EncFallback(yy57) + z.EncFallback(yy66) } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy60 := &x.PVClaimBinderSyncPeriod - yym61 := z.EncBinary() - _ = yym61 + yy69 := &x.PVClaimBinderSyncPeriod + yym70 := z.EncBinary() + _ = yym70 if false { - } else if z.HasExtensions() && z.EncExt(yy60) { - } else if !yym61 && z.IsJSONHandle() { - z.EncJSONMarshal(yy60) + } else if z.HasExtensions() && z.EncExt(yy69) { + } else if !yym70 && z.IsJSONHandle() { + z.EncJSONMarshal(yy69) } else { - z.EncFallback(yy60) + z.EncFallback(yy69) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("pvClaimBinderSyncPeriod")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy62 := &x.PVClaimBinderSyncPeriod - yym63 := z.EncBinary() - _ = yym63 + yy71 := &x.PVClaimBinderSyncPeriod + yym72 := z.EncBinary() + _ = yym72 if false { - } else if z.HasExtensions() && z.EncExt(yy62) { - } else if !yym63 && z.IsJSONHandle() { - z.EncJSONMarshal(yy62) + } else if z.HasExtensions() && z.EncExt(yy71) { + } else if !yym72 && z.IsJSONHandle() { + z.EncJSONMarshal(yy71) } else { - z.EncFallback(yy62) + z.EncFallback(yy71) } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy65 := &x.MinResyncPeriod - yym66 := z.EncBinary() - _ = yym66 + yy74 := &x.MinResyncPeriod + yym75 := z.EncBinary() + _ = yym75 if false { - } else if z.HasExtensions() && z.EncExt(yy65) { - } else if !yym66 && z.IsJSONHandle() { - z.EncJSONMarshal(yy65) + } else if z.HasExtensions() && z.EncExt(yy74) { + } else if !yym75 && z.IsJSONHandle() { + z.EncJSONMarshal(yy74) } else { - z.EncFallback(yy65) + z.EncFallback(yy74) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("minResyncPeriod")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy67 := &x.MinResyncPeriod - yym68 := z.EncBinary() - _ = yym68 + yy76 := &x.MinResyncPeriod + yym77 := z.EncBinary() + _ = yym77 if false { - } else if z.HasExtensions() && z.EncExt(yy67) { - } else if !yym68 && z.IsJSONHandle() { - z.EncJSONMarshal(yy67) + } else if z.HasExtensions() && z.EncExt(yy76) { + } else if !yym77 && z.IsJSONHandle() { + z.EncJSONMarshal(yy76) } else { - z.EncFallback(yy67) + z.EncFallback(yy76) } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym70 := z.EncBinary() - _ = yym70 + yym79 := z.EncBinary() + _ = yym79 if false { } else { r.EncodeInt(int64(x.TerminatedPodGCThreshold)) @@ -6006,8 +6245,8 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("terminatedPodGCThreshold")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym71 := z.EncBinary() - _ = yym71 + yym80 := z.EncBinary() + _ = yym80 if false { } else { r.EncodeInt(int64(x.TerminatedPodGCThreshold)) @@ -6015,126 +6254,75 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy73 := &x.HorizontalPodAutoscalerSyncPeriod - yym74 := z.EncBinary() - _ = yym74 + yy82 := &x.HorizontalPodAutoscalerSyncPeriod + yym83 := z.EncBinary() + _ = yym83 if false { - } else if z.HasExtensions() && z.EncExt(yy73) { - } else if !yym74 && z.IsJSONHandle() { - z.EncJSONMarshal(yy73) + } else if z.HasExtensions() && z.EncExt(yy82) { + } else if !yym83 && z.IsJSONHandle() { + z.EncJSONMarshal(yy82) } else { - z.EncFallback(yy73) + z.EncFallback(yy82) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("horizontalPodAutoscalerSyncPeriod")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy75 := &x.HorizontalPodAutoscalerSyncPeriod - yym76 := z.EncBinary() - _ = yym76 + yy84 := &x.HorizontalPodAutoscalerSyncPeriod + yym85 := z.EncBinary() + _ = yym85 if false { - } else if z.HasExtensions() && z.EncExt(yy75) { - } else if !yym76 && z.IsJSONHandle() { - z.EncJSONMarshal(yy75) + } else if z.HasExtensions() && z.EncExt(yy84) { + } else if !yym85 && z.IsJSONHandle() { + z.EncJSONMarshal(yy84) } else { - z.EncFallback(yy75) + z.EncFallback(yy84) } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy78 := &x.DeploymentControllerSyncPeriod - yym79 := z.EncBinary() - _ = yym79 + yy87 := &x.DeploymentControllerSyncPeriod + yym88 := z.EncBinary() + _ = yym88 if false { - } else if z.HasExtensions() && z.EncExt(yy78) { - } else if !yym79 && z.IsJSONHandle() { - z.EncJSONMarshal(yy78) + } else if z.HasExtensions() && z.EncExt(yy87) { + } else if !yym88 && z.IsJSONHandle() { + z.EncJSONMarshal(yy87) } else { - z.EncFallback(yy78) + z.EncFallback(yy87) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("deploymentControllerSyncPeriod")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy80 := &x.DeploymentControllerSyncPeriod - yym81 := z.EncBinary() - _ = yym81 + yy89 := &x.DeploymentControllerSyncPeriod + yym90 := z.EncBinary() + _ = yym90 if false { - } else if z.HasExtensions() && z.EncExt(yy80) { - } else if !yym81 && z.IsJSONHandle() { - z.EncJSONMarshal(yy80) + } else if z.HasExtensions() && z.EncExt(yy89) { + } else if !yym90 && z.IsJSONHandle() { + z.EncJSONMarshal(yy89) } else { - z.EncFallback(yy80) + z.EncFallback(yy89) } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy83 := &x.PodEvictionTimeout - yym84 := z.EncBinary() - _ = yym84 + yy92 := &x.PodEvictionTimeout + yym93 := z.EncBinary() + _ = yym93 if false { - } else if z.HasExtensions() && z.EncExt(yy83) { - } else if !yym84 && z.IsJSONHandle() { - z.EncJSONMarshal(yy83) + } else if z.HasExtensions() && z.EncExt(yy92) { + } else if !yym93 && z.IsJSONHandle() { + z.EncJSONMarshal(yy92) } else { - z.EncFallback(yy83) + z.EncFallback(yy92) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("podEvictionTimeout")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy85 := &x.PodEvictionTimeout - yym86 := z.EncBinary() - _ = yym86 - if false { - } else if z.HasExtensions() && z.EncExt(yy85) { - } else if !yym86 && z.IsJSONHandle() { - z.EncJSONMarshal(yy85) - } else { - z.EncFallback(yy85) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym88 := z.EncBinary() - _ = yym88 - if false { - } else { - r.EncodeFloat32(float32(x.DeletingPodsQps)) - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("deletingPodsQps")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym89 := z.EncBinary() - _ = yym89 - if false { - } else { - r.EncodeFloat32(float32(x.DeletingPodsQps)) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym91 := z.EncBinary() - _ = yym91 - if false { - } else { - r.EncodeInt(int64(x.DeletingPodsBurst)) - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("deletingPodsBurst")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym92 := z.EncBinary() - _ = yym92 - if false { - } else { - r.EncodeInt(int64(x.DeletingPodsBurst)) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy94 := &x.NodeMonitorGracePeriod + yy94 := &x.PodEvictionTimeout yym95 := z.EncBinary() _ = yym95 if false { @@ -6144,25 +6332,76 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode } else { z.EncFallback(yy94) } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("nodeMonitorGracePeriod")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy96 := &x.NodeMonitorGracePeriod + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) yym97 := z.EncBinary() _ = yym97 if false { - } else if z.HasExtensions() && z.EncExt(yy96) { - } else if !yym97 && z.IsJSONHandle() { - z.EncJSONMarshal(yy96) } else { - z.EncFallback(yy96) + r.EncodeFloat32(float32(x.DeletingPodsQps)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("deletingPodsQps")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym98 := z.EncBinary() + _ = yym98 + if false { + } else { + r.EncodeFloat32(float32(x.DeletingPodsQps)) } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym99 := z.EncBinary() - _ = yym99 + yym100 := z.EncBinary() + _ = yym100 + if false { + } else { + r.EncodeInt(int64(x.DeletingPodsBurst)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("deletingPodsBurst")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym101 := z.EncBinary() + _ = yym101 + if false { + } else { + r.EncodeInt(int64(x.DeletingPodsBurst)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy103 := &x.NodeMonitorGracePeriod + yym104 := z.EncBinary() + _ = yym104 + if false { + } else if z.HasExtensions() && z.EncExt(yy103) { + } else if !yym104 && z.IsJSONHandle() { + z.EncJSONMarshal(yy103) + } else { + z.EncFallback(yy103) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("nodeMonitorGracePeriod")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy105 := &x.NodeMonitorGracePeriod + yym106 := z.EncBinary() + _ = yym106 + if false { + } else if z.HasExtensions() && z.EncExt(yy105) { + } else if !yym106 && z.IsJSONHandle() { + z.EncJSONMarshal(yy105) + } else { + z.EncFallback(yy105) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym108 := z.EncBinary() + _ = yym108 if false { } else { r.EncodeInt(int64(x.RegisterRetryCount)) @@ -6171,8 +6410,8 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("registerRetryCount")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym100 := z.EncBinary() - _ = yym100 + yym109 := z.EncBinary() + _ = yym109 if false { } else { r.EncodeInt(int64(x.RegisterRetryCount)) @@ -6180,113 +6419,56 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy102 := &x.NodeStartupGracePeriod - yym103 := z.EncBinary() - _ = yym103 + yy111 := &x.NodeStartupGracePeriod + yym112 := z.EncBinary() + _ = yym112 if false { - } else if z.HasExtensions() && z.EncExt(yy102) { - } else if !yym103 && z.IsJSONHandle() { - z.EncJSONMarshal(yy102) + } else if z.HasExtensions() && z.EncExt(yy111) { + } else if !yym112 && z.IsJSONHandle() { + z.EncJSONMarshal(yy111) } else { - z.EncFallback(yy102) + z.EncFallback(yy111) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("nodeStartupGracePeriod")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy104 := &x.NodeStartupGracePeriod - yym105 := z.EncBinary() - _ = yym105 + yy113 := &x.NodeStartupGracePeriod + yym114 := z.EncBinary() + _ = yym114 if false { - } else if z.HasExtensions() && z.EncExt(yy104) { - } else if !yym105 && z.IsJSONHandle() { - z.EncJSONMarshal(yy104) + } else if z.HasExtensions() && z.EncExt(yy113) { + } else if !yym114 && z.IsJSONHandle() { + z.EncJSONMarshal(yy113) } else { - z.EncFallback(yy104) + z.EncFallback(yy113) } } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy107 := &x.NodeMonitorPeriod - yym108 := z.EncBinary() - _ = yym108 + yy116 := &x.NodeMonitorPeriod + yym117 := z.EncBinary() + _ = yym117 if false { - } else if z.HasExtensions() && z.EncExt(yy107) { - } else if !yym108 && z.IsJSONHandle() { - z.EncJSONMarshal(yy107) + } else if z.HasExtensions() && z.EncExt(yy116) { + } else if !yym117 && z.IsJSONHandle() { + z.EncJSONMarshal(yy116) } else { - z.EncFallback(yy107) + z.EncFallback(yy116) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("nodeMonitorPeriod")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy109 := &x.NodeMonitorPeriod - yym110 := z.EncBinary() - _ = yym110 - if false { - } else if z.HasExtensions() && z.EncExt(yy109) { - } else if !yym110 && z.IsJSONHandle() { - z.EncJSONMarshal(yy109) - } else { - z.EncFallback(yy109) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym112 := z.EncBinary() - _ = yym112 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.ServiceAccountKeyFile)) - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("serviceAccountKeyFile")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym113 := z.EncBinary() - _ = yym113 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.ServiceAccountKeyFile)) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym115 := z.EncBinary() - _ = yym115 - if false { - } else { - r.EncodeBool(bool(x.EnableProfiling)) - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("enableProfiling")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym116 := z.EncBinary() - _ = yym116 - if false { - } else { - r.EncodeBool(bool(x.EnableProfiling)) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym118 := z.EncBinary() - _ = yym118 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.ClusterName)) - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("clusterName")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy118 := &x.NodeMonitorPeriod yym119 := z.EncBinary() _ = yym119 if false { + } else if z.HasExtensions() && z.EncExt(yy118) { + } else if !yym119 && z.IsJSONHandle() { + z.EncJSONMarshal(yy118) } else { - r.EncodeString(codecSelferC_UTF81234, string(x.ClusterName)) + z.EncFallback(yy118) } } if yyr2 || yy2arr2 { @@ -6295,17 +6477,17 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode _ = yym121 if false { } else { - r.EncodeString(codecSelferC_UTF81234, string(x.ClusterCIDR)) + r.EncodeString(codecSelferC_UTF81234, string(x.ServiceAccountKeyFile)) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("clusterCIDR")) + r.EncodeString(codecSelferC_UTF81234, string("serviceAccountKeyFile")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym122 := z.EncBinary() _ = yym122 if false { } else { - r.EncodeString(codecSelferC_UTF81234, string(x.ClusterCIDR)) + r.EncodeString(codecSelferC_UTF81234, string(x.ServiceAccountKeyFile)) } } if yyr2 || yy2arr2 { @@ -6314,17 +6496,17 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode _ = yym124 if false { } else { - r.EncodeBool(bool(x.AllocateNodeCIDRs)) + r.EncodeBool(bool(x.EnableProfiling)) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("allocateNodeCIDRs")) + r.EncodeString(codecSelferC_UTF81234, string("enableProfiling")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym125 := z.EncBinary() _ = yym125 if false { } else { - r.EncodeBool(bool(x.AllocateNodeCIDRs)) + r.EncodeBool(bool(x.EnableProfiling)) } } if yyr2 || yy2arr2 { @@ -6333,17 +6515,17 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode _ = yym127 if false { } else { - r.EncodeString(codecSelferC_UTF81234, string(x.RootCAFile)) + r.EncodeString(codecSelferC_UTF81234, string(x.ClusterName)) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("rootCAFile")) + r.EncodeString(codecSelferC_UTF81234, string("clusterName")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym128 := z.EncBinary() _ = yym128 if false { } else { - r.EncodeString(codecSelferC_UTF81234, string(x.RootCAFile)) + r.EncodeString(codecSelferC_UTF81234, string(x.ClusterName)) } } if yyr2 || yy2arr2 { @@ -6352,17 +6534,17 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode _ = yym130 if false { } else { - r.EncodeFloat32(float32(x.KubeAPIQPS)) + r.EncodeString(codecSelferC_UTF81234, string(x.ClusterCIDR)) } } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("kubeAPIQPS")) + r.EncodeString(codecSelferC_UTF81234, string("clusterCIDR")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym131 := z.EncBinary() _ = yym131 if false { } else { - r.EncodeFloat32(float32(x.KubeAPIQPS)) + r.EncodeString(codecSelferC_UTF81234, string(x.ClusterCIDR)) } } if yyr2 || yy2arr2 { @@ -6370,6 +6552,63 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode yym133 := z.EncBinary() _ = yym133 if false { + } else { + r.EncodeBool(bool(x.AllocateNodeCIDRs)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("allocateNodeCIDRs")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym134 := z.EncBinary() + _ = yym134 + if false { + } else { + r.EncodeBool(bool(x.AllocateNodeCIDRs)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym136 := z.EncBinary() + _ = yym136 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.RootCAFile)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("rootCAFile")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym137 := z.EncBinary() + _ = yym137 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.RootCAFile)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym139 := z.EncBinary() + _ = yym139 + if false { + } else { + r.EncodeFloat32(float32(x.KubeAPIQPS)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kubeAPIQPS")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym140 := z.EncBinary() + _ = yym140 + if false { + } else { + r.EncodeFloat32(float32(x.KubeAPIQPS)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym142 := z.EncBinary() + _ = yym142 + if false { } else { r.EncodeInt(int64(x.KubeAPIBurst)) } @@ -6377,8 +6616,8 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("kubeAPIBurst")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym134 := z.EncBinary() - _ = yym134 + yym143 := z.EncBinary() + _ = yym143 if false { } else { r.EncodeInt(int64(x.KubeAPIBurst)) @@ -6386,31 +6625,31 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy136 := &x.LeaderElection - yy136.CodecEncodeSelf(e) + yy145 := &x.LeaderElection + yy145.CodecEncodeSelf(e) } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("leaderElection")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy138 := &x.LeaderElection - yy138.CodecEncodeSelf(e) + yy147 := &x.LeaderElection + yy147.CodecEncodeSelf(e) } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy141 := &x.VolumeConfiguration - yy141.CodecEncodeSelf(e) + yy150 := &x.VolumeConfiguration + yy150.CodecEncodeSelf(e) } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("volumeConfiguration")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy143 := &x.VolumeConfiguration - yy143.CodecEncodeSelf(e) + yy152 := &x.VolumeConfiguration + yy152.CodecEncodeSelf(e) } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[38] { - yym146 := z.EncBinary() - _ = yym146 + if yyq2[41] { + yym155 := z.EncBinary() + _ = yym155 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) @@ -6419,12 +6658,12 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode r.EncodeString(codecSelferC_UTF81234, "") } } else { - if yyq2[38] { + if yyq2[41] { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("kind")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym147 := z.EncBinary() - _ = yym147 + yym156 := z.EncBinary() + _ = yym156 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) @@ -6433,9 +6672,9 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[39] { - yym149 := z.EncBinary() - _ = yym149 + if yyq2[42] { + yym158 := z.EncBinary() + _ = yym158 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) @@ -6444,12 +6683,12 @@ func (x *KubeControllerManagerConfiguration) CodecEncodeSelf(e *codec1978.Encode r.EncodeString(codecSelferC_UTF81234, "") } } else { - if yyq2[39] { + if yyq2[42] { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym150 := z.EncBinary() - _ = yym150 + yym159 := z.EncBinary() + _ = yym159 if false { } else { r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) @@ -6589,94 +6828,112 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromMap(l int, d *co } else { x.ConcurrentNamespaceSyncs = int(r.DecodeInt(codecSelferBitsize1234)) } + case "lookupCacheSizeForRC": + if r.TryDecodeAsNil() { + x.LookupCacheSizeForRC = 0 + } else { + x.LookupCacheSizeForRC = int(r.DecodeInt(codecSelferBitsize1234)) + } + case "lookupCacheSizeForRS": + if r.TryDecodeAsNil() { + x.LookupCacheSizeForRS = 0 + } else { + x.LookupCacheSizeForRS = int(r.DecodeInt(codecSelferBitsize1234)) + } + case "lookupCacheSizeForDaemonSet": + if r.TryDecodeAsNil() { + x.LookupCacheSizeForDaemonSet = 0 + } else { + x.LookupCacheSizeForDaemonSet = int(r.DecodeInt(codecSelferBitsize1234)) + } case "serviceSyncPeriod": if r.TryDecodeAsNil() { x.ServiceSyncPeriod = pkg1_unversioned.Duration{} } else { - yyv16 := &x.ServiceSyncPeriod - yym17 := z.DecBinary() - _ = yym17 + yyv19 := &x.ServiceSyncPeriod + yym20 := z.DecBinary() + _ = yym20 if false { - } else if z.HasExtensions() && z.DecExt(yyv16) { - } else if !yym17 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv16) + } else if z.HasExtensions() && z.DecExt(yyv19) { + } else if !yym20 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv19) } else { - z.DecFallback(yyv16, false) + z.DecFallback(yyv19, false) } } case "nodeSyncPeriod": if r.TryDecodeAsNil() { x.NodeSyncPeriod = pkg1_unversioned.Duration{} } else { - yyv18 := &x.NodeSyncPeriod - yym19 := z.DecBinary() - _ = yym19 + yyv21 := &x.NodeSyncPeriod + yym22 := z.DecBinary() + _ = yym22 if false { - } else if z.HasExtensions() && z.DecExt(yyv18) { - } else if !yym19 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv18) + } else if z.HasExtensions() && z.DecExt(yyv21) { + } else if !yym22 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv21) } else { - z.DecFallback(yyv18, false) + z.DecFallback(yyv21, false) } } case "resourceQuotaSyncPeriod": if r.TryDecodeAsNil() { x.ResourceQuotaSyncPeriod = pkg1_unversioned.Duration{} } else { - yyv20 := &x.ResourceQuotaSyncPeriod - yym21 := z.DecBinary() - _ = yym21 + yyv23 := &x.ResourceQuotaSyncPeriod + yym24 := z.DecBinary() + _ = yym24 if false { - } else if z.HasExtensions() && z.DecExt(yyv20) { - } else if !yym21 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv20) + } else if z.HasExtensions() && z.DecExt(yyv23) { + } else if !yym24 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv23) } else { - z.DecFallback(yyv20, false) + z.DecFallback(yyv23, false) } } case "namespaceSyncPeriod": if r.TryDecodeAsNil() { x.NamespaceSyncPeriod = pkg1_unversioned.Duration{} } else { - yyv22 := &x.NamespaceSyncPeriod - yym23 := z.DecBinary() - _ = yym23 + yyv25 := &x.NamespaceSyncPeriod + yym26 := z.DecBinary() + _ = yym26 if false { - } else if z.HasExtensions() && z.DecExt(yyv22) { - } else if !yym23 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv22) + } else if z.HasExtensions() && z.DecExt(yyv25) { + } else if !yym26 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv25) } else { - z.DecFallback(yyv22, false) + z.DecFallback(yyv25, false) } } case "pvClaimBinderSyncPeriod": if r.TryDecodeAsNil() { x.PVClaimBinderSyncPeriod = pkg1_unversioned.Duration{} } else { - yyv24 := &x.PVClaimBinderSyncPeriod - yym25 := z.DecBinary() - _ = yym25 + yyv27 := &x.PVClaimBinderSyncPeriod + yym28 := z.DecBinary() + _ = yym28 if false { - } else if z.HasExtensions() && z.DecExt(yyv24) { - } else if !yym25 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv24) + } else if z.HasExtensions() && z.DecExt(yyv27) { + } else if !yym28 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv27) } else { - z.DecFallback(yyv24, false) + z.DecFallback(yyv27, false) } } case "minResyncPeriod": if r.TryDecodeAsNil() { x.MinResyncPeriod = pkg1_unversioned.Duration{} } else { - yyv26 := &x.MinResyncPeriod - yym27 := z.DecBinary() - _ = yym27 + yyv29 := &x.MinResyncPeriod + yym30 := z.DecBinary() + _ = yym30 if false { - } else if z.HasExtensions() && z.DecExt(yyv26) { - } else if !yym27 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv26) + } else if z.HasExtensions() && z.DecExt(yyv29) { + } else if !yym30 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv29) } else { - z.DecFallback(yyv26, false) + z.DecFallback(yyv29, false) } } case "terminatedPodGCThreshold": @@ -6689,45 +6946,45 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromMap(l int, d *co if r.TryDecodeAsNil() { x.HorizontalPodAutoscalerSyncPeriod = pkg1_unversioned.Duration{} } else { - yyv29 := &x.HorizontalPodAutoscalerSyncPeriod - yym30 := z.DecBinary() - _ = yym30 + yyv32 := &x.HorizontalPodAutoscalerSyncPeriod + yym33 := z.DecBinary() + _ = yym33 if false { - } else if z.HasExtensions() && z.DecExt(yyv29) { - } else if !yym30 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv29) + } else if z.HasExtensions() && z.DecExt(yyv32) { + } else if !yym33 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv32) } else { - z.DecFallback(yyv29, false) + z.DecFallback(yyv32, false) } } case "deploymentControllerSyncPeriod": if r.TryDecodeAsNil() { x.DeploymentControllerSyncPeriod = pkg1_unversioned.Duration{} } else { - yyv31 := &x.DeploymentControllerSyncPeriod - yym32 := z.DecBinary() - _ = yym32 + yyv34 := &x.DeploymentControllerSyncPeriod + yym35 := z.DecBinary() + _ = yym35 if false { - } else if z.HasExtensions() && z.DecExt(yyv31) { - } else if !yym32 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv31) + } else if z.HasExtensions() && z.DecExt(yyv34) { + } else if !yym35 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv34) } else { - z.DecFallback(yyv31, false) + z.DecFallback(yyv34, false) } } case "podEvictionTimeout": if r.TryDecodeAsNil() { x.PodEvictionTimeout = pkg1_unversioned.Duration{} } else { - yyv33 := &x.PodEvictionTimeout - yym34 := z.DecBinary() - _ = yym34 + yyv36 := &x.PodEvictionTimeout + yym37 := z.DecBinary() + _ = yym37 if false { - } else if z.HasExtensions() && z.DecExt(yyv33) { - } else if !yym34 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv33) + } else if z.HasExtensions() && z.DecExt(yyv36) { + } else if !yym37 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv36) } else { - z.DecFallback(yyv33, false) + z.DecFallback(yyv36, false) } } case "deletingPodsQps": @@ -6746,15 +7003,15 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromMap(l int, d *co if r.TryDecodeAsNil() { x.NodeMonitorGracePeriod = pkg1_unversioned.Duration{} } else { - yyv37 := &x.NodeMonitorGracePeriod - yym38 := z.DecBinary() - _ = yym38 + yyv40 := &x.NodeMonitorGracePeriod + yym41 := z.DecBinary() + _ = yym41 if false { - } else if z.HasExtensions() && z.DecExt(yyv37) { - } else if !yym38 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv37) + } else if z.HasExtensions() && z.DecExt(yyv40) { + } else if !yym41 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv40) } else { - z.DecFallback(yyv37, false) + z.DecFallback(yyv40, false) } } case "registerRetryCount": @@ -6767,30 +7024,30 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromMap(l int, d *co if r.TryDecodeAsNil() { x.NodeStartupGracePeriod = pkg1_unversioned.Duration{} } else { - yyv40 := &x.NodeStartupGracePeriod - yym41 := z.DecBinary() - _ = yym41 + yyv43 := &x.NodeStartupGracePeriod + yym44 := z.DecBinary() + _ = yym44 if false { - } else if z.HasExtensions() && z.DecExt(yyv40) { - } else if !yym41 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv40) + } else if z.HasExtensions() && z.DecExt(yyv43) { + } else if !yym44 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv43) } else { - z.DecFallback(yyv40, false) + z.DecFallback(yyv43, false) } } case "nodeMonitorPeriod": if r.TryDecodeAsNil() { x.NodeMonitorPeriod = pkg1_unversioned.Duration{} } else { - yyv42 := &x.NodeMonitorPeriod - yym43 := z.DecBinary() - _ = yym43 + yyv45 := &x.NodeMonitorPeriod + yym46 := z.DecBinary() + _ = yym46 if false { - } else if z.HasExtensions() && z.DecExt(yyv42) { - } else if !yym43 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv42) + } else if z.HasExtensions() && z.DecExt(yyv45) { + } else if !yym46 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv45) } else { - z.DecFallback(yyv42, false) + z.DecFallback(yyv45, false) } } case "serviceAccountKeyFile": @@ -6845,15 +7102,15 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromMap(l int, d *co if r.TryDecodeAsNil() { x.LeaderElection = LeaderElectionConfiguration{} } else { - yyv52 := &x.LeaderElection - yyv52.CodecDecodeSelf(d) + yyv55 := &x.LeaderElection + yyv55.CodecDecodeSelf(d) } case "volumeConfiguration": if r.TryDecodeAsNil() { x.VolumeConfiguration = VolumeConfiguration{} } else { - yyv53 := &x.VolumeConfiguration - yyv53.CodecDecodeSelf(d) + yyv56 := &x.VolumeConfiguration + yyv56.CodecDecodeSelf(d) } case "kind": if r.TryDecodeAsNil() { @@ -6878,16 +7135,16 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj56 int - var yyb56 bool - var yyhl56 bool = l >= 0 - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + var yyj59 int + var yyb59 bool + var yyhl59 bool = l >= 0 + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -6897,13 +7154,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.Port = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -6913,13 +7170,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.Address = string(r.DecodeString()) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -6929,13 +7186,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.CloudProvider = string(r.DecodeString()) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -6945,13 +7202,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.CloudConfigFile = string(r.DecodeString()) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -6961,13 +7218,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.ConcurrentEndpointSyncs = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -6977,13 +7234,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.ConcurrentRSSyncs = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -6993,13 +7250,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.ConcurrentRCSyncs = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7009,13 +7266,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.ConcurrentResourceQuotaSyncs = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7025,13 +7282,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.ConcurrentDeploymentSyncs = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7041,13 +7298,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.ConcurrentDaemonSetSyncs = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7057,13 +7314,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.ConcurrentJobSyncs = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7073,13 +7330,61 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.ConcurrentNamespaceSyncs = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.LookupCacheSizeForRC = 0 + } else { + x.LookupCacheSizeForRC = int(r.DecodeInt(codecSelferBitsize1234)) + } + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l + } else { + yyb59 = r.CheckBreak() + } + if yyb59 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.LookupCacheSizeForRS = 0 + } else { + x.LookupCacheSizeForRS = int(r.DecodeInt(codecSelferBitsize1234)) + } + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l + } else { + yyb59 = r.CheckBreak() + } + if yyb59 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.LookupCacheSizeForDaemonSet = 0 + } else { + x.LookupCacheSizeForDaemonSet = int(r.DecodeInt(codecSelferBitsize1234)) + } + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l + } else { + yyb59 = r.CheckBreak() + } + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7087,82 +7392,7 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * if r.TryDecodeAsNil() { x.ServiceSyncPeriod = pkg1_unversioned.Duration{} } else { - yyv69 := &x.ServiceSyncPeriod - yym70 := z.DecBinary() - _ = yym70 - if false { - } else if z.HasExtensions() && z.DecExt(yyv69) { - } else if !yym70 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv69) - } else { - z.DecFallback(yyv69, false) - } - } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l - } else { - yyb56 = r.CheckBreak() - } - if yyb56 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.NodeSyncPeriod = pkg1_unversioned.Duration{} - } else { - yyv71 := &x.NodeSyncPeriod - yym72 := z.DecBinary() - _ = yym72 - if false { - } else if z.HasExtensions() && z.DecExt(yyv71) { - } else if !yym72 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv71) - } else { - z.DecFallback(yyv71, false) - } - } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l - } else { - yyb56 = r.CheckBreak() - } - if yyb56 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.ResourceQuotaSyncPeriod = pkg1_unversioned.Duration{} - } else { - yyv73 := &x.ResourceQuotaSyncPeriod - yym74 := z.DecBinary() - _ = yym74 - if false { - } else if z.HasExtensions() && z.DecExt(yyv73) { - } else if !yym74 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv73) - } else { - z.DecFallback(yyv73, false) - } - } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l - } else { - yyb56 = r.CheckBreak() - } - if yyb56 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.NamespaceSyncPeriod = pkg1_unversioned.Duration{} - } else { - yyv75 := &x.NamespaceSyncPeriod + yyv75 := &x.ServiceSyncPeriod yym76 := z.DecBinary() _ = yym76 if false { @@ -7173,21 +7403,21 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * z.DecFallback(yyv75, false) } } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.PVClaimBinderSyncPeriod = pkg1_unversioned.Duration{} + x.NodeSyncPeriod = pkg1_unversioned.Duration{} } else { - yyv77 := &x.PVClaimBinderSyncPeriod + yyv77 := &x.NodeSyncPeriod yym78 := z.DecBinary() _ = yym78 if false { @@ -7198,21 +7428,21 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * z.DecFallback(yyv77, false) } } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.MinResyncPeriod = pkg1_unversioned.Duration{} + x.ResourceQuotaSyncPeriod = pkg1_unversioned.Duration{} } else { - yyv79 := &x.MinResyncPeriod + yyv79 := &x.ResourceQuotaSyncPeriod yym80 := z.DecBinary() _ = yym80 if false { @@ -7223,13 +7453,88 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * z.DecFallback(yyv79, false) } } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.NamespaceSyncPeriod = pkg1_unversioned.Duration{} + } else { + yyv81 := &x.NamespaceSyncPeriod + yym82 := z.DecBinary() + _ = yym82 + if false { + } else if z.HasExtensions() && z.DecExt(yyv81) { + } else if !yym82 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv81) + } else { + z.DecFallback(yyv81, false) + } + } + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l + } else { + yyb59 = r.CheckBreak() + } + if yyb59 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.PVClaimBinderSyncPeriod = pkg1_unversioned.Duration{} + } else { + yyv83 := &x.PVClaimBinderSyncPeriod + yym84 := z.DecBinary() + _ = yym84 + if false { + } else if z.HasExtensions() && z.DecExt(yyv83) { + } else if !yym84 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv83) + } else { + z.DecFallback(yyv83, false) + } + } + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l + } else { + yyb59 = r.CheckBreak() + } + if yyb59 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.MinResyncPeriod = pkg1_unversioned.Duration{} + } else { + yyv85 := &x.MinResyncPeriod + yym86 := z.DecBinary() + _ = yym86 + if false { + } else if z.HasExtensions() && z.DecExt(yyv85) { + } else if !yym86 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv85) + } else { + z.DecFallback(yyv85, false) + } + } + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l + } else { + yyb59 = r.CheckBreak() + } + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7239,13 +7544,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.TerminatedPodGCThreshold = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7253,24 +7558,24 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * if r.TryDecodeAsNil() { x.HorizontalPodAutoscalerSyncPeriod = pkg1_unversioned.Duration{} } else { - yyv82 := &x.HorizontalPodAutoscalerSyncPeriod - yym83 := z.DecBinary() - _ = yym83 + yyv88 := &x.HorizontalPodAutoscalerSyncPeriod + yym89 := z.DecBinary() + _ = yym89 if false { - } else if z.HasExtensions() && z.DecExt(yyv82) { - } else if !yym83 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv82) + } else if z.HasExtensions() && z.DecExt(yyv88) { + } else if !yym89 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv88) } else { - z.DecFallback(yyv82, false) + z.DecFallback(yyv88, false) } } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7278,89 +7583,7 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * if r.TryDecodeAsNil() { x.DeploymentControllerSyncPeriod = pkg1_unversioned.Duration{} } else { - yyv84 := &x.DeploymentControllerSyncPeriod - yym85 := z.DecBinary() - _ = yym85 - if false { - } else if z.HasExtensions() && z.DecExt(yyv84) { - } else if !yym85 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv84) - } else { - z.DecFallback(yyv84, false) - } - } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l - } else { - yyb56 = r.CheckBreak() - } - if yyb56 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.PodEvictionTimeout = pkg1_unversioned.Duration{} - } else { - yyv86 := &x.PodEvictionTimeout - yym87 := z.DecBinary() - _ = yym87 - if false { - } else if z.HasExtensions() && z.DecExt(yyv86) { - } else if !yym87 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv86) - } else { - z.DecFallback(yyv86, false) - } - } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l - } else { - yyb56 = r.CheckBreak() - } - if yyb56 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.DeletingPodsQps = 0 - } else { - x.DeletingPodsQps = float32(r.DecodeFloat(true)) - } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l - } else { - yyb56 = r.CheckBreak() - } - if yyb56 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.DeletingPodsBurst = 0 - } else { - x.DeletingPodsBurst = int(r.DecodeInt(codecSelferBitsize1234)) - } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l - } else { - yyb56 = r.CheckBreak() - } - if yyb56 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.NodeMonitorGracePeriod = pkg1_unversioned.Duration{} - } else { - yyv90 := &x.NodeMonitorGracePeriod + yyv90 := &x.DeploymentControllerSyncPeriod yym91 := z.DecBinary() _ = yym91 if false { @@ -7371,13 +7594,95 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * z.DecFallback(yyv90, false) } } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.PodEvictionTimeout = pkg1_unversioned.Duration{} + } else { + yyv92 := &x.PodEvictionTimeout + yym93 := z.DecBinary() + _ = yym93 + if false { + } else if z.HasExtensions() && z.DecExt(yyv92) { + } else if !yym93 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv92) + } else { + z.DecFallback(yyv92, false) + } + } + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l + } else { + yyb59 = r.CheckBreak() + } + if yyb59 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.DeletingPodsQps = 0 + } else { + x.DeletingPodsQps = float32(r.DecodeFloat(true)) + } + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l + } else { + yyb59 = r.CheckBreak() + } + if yyb59 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.DeletingPodsBurst = 0 + } else { + x.DeletingPodsBurst = int(r.DecodeInt(codecSelferBitsize1234)) + } + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l + } else { + yyb59 = r.CheckBreak() + } + if yyb59 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.NodeMonitorGracePeriod = pkg1_unversioned.Duration{} + } else { + yyv96 := &x.NodeMonitorGracePeriod + yym97 := z.DecBinary() + _ = yym97 + if false { + } else if z.HasExtensions() && z.DecExt(yyv96) { + } else if !yym97 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv96) + } else { + z.DecFallback(yyv96, false) + } + } + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l + } else { + yyb59 = r.CheckBreak() + } + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7387,13 +7692,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.RegisterRetryCount = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7401,24 +7706,24 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * if r.TryDecodeAsNil() { x.NodeStartupGracePeriod = pkg1_unversioned.Duration{} } else { - yyv93 := &x.NodeStartupGracePeriod - yym94 := z.DecBinary() - _ = yym94 + yyv99 := &x.NodeStartupGracePeriod + yym100 := z.DecBinary() + _ = yym100 if false { - } else if z.HasExtensions() && z.DecExt(yyv93) { - } else if !yym94 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv93) + } else if z.HasExtensions() && z.DecExt(yyv99) { + } else if !yym100 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv99) } else { - z.DecFallback(yyv93, false) + z.DecFallback(yyv99, false) } } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7426,24 +7731,24 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * if r.TryDecodeAsNil() { x.NodeMonitorPeriod = pkg1_unversioned.Duration{} } else { - yyv95 := &x.NodeMonitorPeriod - yym96 := z.DecBinary() - _ = yym96 + yyv101 := &x.NodeMonitorPeriod + yym102 := z.DecBinary() + _ = yym102 if false { - } else if z.HasExtensions() && z.DecExt(yyv95) { - } else if !yym96 && z.IsJSONHandle() { - z.DecJSONUnmarshal(yyv95) + } else if z.HasExtensions() && z.DecExt(yyv101) { + } else if !yym102 && z.IsJSONHandle() { + z.DecJSONUnmarshal(yyv101) } else { - z.DecFallback(yyv95, false) + z.DecFallback(yyv101, false) } } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7453,13 +7758,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.ServiceAccountKeyFile = string(r.DecodeString()) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7469,13 +7774,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.EnableProfiling = bool(r.DecodeBool()) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7485,13 +7790,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.ClusterName = string(r.DecodeString()) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7501,13 +7806,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.ClusterCIDR = string(r.DecodeString()) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7517,13 +7822,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.AllocateNodeCIDRs = bool(r.DecodeBool()) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7533,13 +7838,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.RootCAFile = string(r.DecodeString()) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7549,13 +7854,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.KubeAPIQPS = float32(r.DecodeFloat(true)) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7565,13 +7870,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.KubeAPIBurst = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7579,16 +7884,16 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * if r.TryDecodeAsNil() { x.LeaderElection = LeaderElectionConfiguration{} } else { - yyv105 := &x.LeaderElection - yyv105.CodecDecodeSelf(d) + yyv111 := &x.LeaderElection + yyv111.CodecDecodeSelf(d) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7596,16 +7901,16 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * if r.TryDecodeAsNil() { x.VolumeConfiguration = VolumeConfiguration{} } else { - yyv106 := &x.VolumeConfiguration - yyv106.CodecDecodeSelf(d) + yyv112 := &x.VolumeConfiguration + yyv112.CodecDecodeSelf(d) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7615,13 +7920,13 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * } else { x.Kind = string(r.DecodeString()) } - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7632,17 +7937,17 @@ func (x *KubeControllerManagerConfiguration) codecDecodeSelfFromArray(l int, d * x.APIVersion = string(r.DecodeString()) } for { - yyj56++ - if yyhl56 { - yyb56 = yyj56 > l + yyj59++ + if yyhl59 { + yyb59 = yyj59 > l } else { - yyb56 = r.CheckBreak() + yyb59 = r.CheckBreak() } - if yyb56 { + if yyb59 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj56-1, "") + z.DecStructFieldNotFound(yyj59-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } diff --git a/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/types.go b/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/types.go index 3982ab7a6..9fb0d6577 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/types.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/types.go @@ -78,6 +78,24 @@ const ( ProxyModeIPTables ProxyMode = "iptables" ) +// HairpinMode denotes how the kubelet should configure networking to handle +// hairpin packets. +type HairpinMode string + +// Enum settings for different ways to handle hairpin packets. +const ( + // Set the hairpin flag on the veth of containers in the respective + // container runtime. + HairpinVeth = "hairpin-veth" + // Make the container bridge promiscuous. This will force it to accept + // hairpin packets, even if the flag isn't set on ports of the bridge. + PromiscuousBridge = "promiscuous-bridge" + // Neither of the above. If the kubelet is started in this hairpin mode + // and kube-proxy is running in iptables mode, hairpin packets will be + // dropped by the container bridge. + HairpinNone = "none" +) + // TODO: curate the ordering and structure of this config object type KubeletConfiguration struct { // config is the path to the config file or directory of files @@ -197,6 +215,9 @@ type KubeletConfiguration struct { // status to master. Note: be cautious when changing the constant, it // must work with nodeMonitorGracePeriod in nodecontroller. NodeStatusUpdateFrequency unversioned.Duration `json:"nodeStatusUpdateFrequency"` + // minimumGCAge is the minimum age for a unused image before it is + // garbage collected. + ImageMinimumGCAge unversioned.Duration `json:"imageMinimumGCAge"` // imageGCHighThresholdPercent is the percent of disk usage after which // image garbage collection is always run. ImageGCHighThresholdPercent int `json:"imageGCHighThresholdPercent"` @@ -209,7 +230,7 @@ type KubeletConfiguration struct { // be rejected. LowDiskSpaceThresholdMB int `json:"lowDiskSpaceThresholdMB"` // How frequently to calculate and cache volume disk usage for all pods - VolumeStatsAggPeriod unversioned.Duration `json:volumeStatsAggPeriod` + VolumeStatsAggPeriod unversioned.Duration `json:"volumeStatsAggPeriod"` // networkPluginName is the name of the network plugin to be invoked for // various events in kubelet/pod lifecycle NetworkPluginName string `json:"networkPluginName"` @@ -236,24 +257,33 @@ type KubeletConfiguration struct { CgroupRoot string `json:"cgroupRoot,omitempty"` // containerRuntime is the container runtime to use. ContainerRuntime string `json:"containerRuntime"` - // rktPath is hte path of rkt binary. Leave empty to use the first rkt in + // rktPath is the path of rkt binary. Leave empty to use the first rkt in // $PATH. RktPath string `json:"rktPath,omitempty"` + // rktApiEndpoint is the endpoint of the rkt API service to communicate with. + RktAPIEndpoint string `json:"rktAPIEndpoint,omitempty"` + // rktStage1Image is the image to use as stage1. Local paths and + // http/https URLs are supported. + RktStage1Image string `json:"rktStage1Image,omitempty"` // lockFilePath is the path that kubelet will use to as a lock file. // It uses this file as a lock to synchronize with other kubelet processes // that may be running. LockFilePath string `json:"lockFilePath"` - // rktStage1Image is the image to use as stage1. Local paths and - // http/https URLs are supported. - RktStage1Image string `json:"rktStage1Image,omitempty"` // configureCBR0 enables the kublet to configure cbr0 based on // Node.Spec.PodCIDR. ConfigureCBR0 bool `json:"configureCbr0"` - // Should the kubelet set the hairpin flag on veth interfaces for containers - // it creates? Setting this flag allows endpoints in a Service to - // loadbalance back to themselves if they should try to access their own - // Service. - HairpinMode bool `json:"configureHairpinMode"` + // How should the kubelet configure the container bridge for hairpin packets. + // Setting this flag allows endpoints in a Service to loadbalance back to + // themselves if they should try to access their own Service. Values: + // "promiscuous-bridge": make the container bridge promiscuous. + // "hairpin-veth": set the hairpin flag on container veth interfaces. + // "none": do nothing. + // Setting --configure-cbr0 to false implies that to achieve hairpin NAT + // one must set --hairpin-mode=veth-flag, because bridge assumes the + // existence of a container bridge named cbr0. + HairpinMode string `json:"hairpinMode"` + // The node has babysitter process monitoring docker and kubelet. + BabysitDaemons bool `json:"babysitDaemons"` // maxPods is the number of pods that can run on this Kubelet. MaxPods int `json:"maxPods"` // dockerExecHandlerName is the handler to use when executing a command @@ -373,11 +403,11 @@ type KubeControllerManagerConfiguration struct { // but more CPU (and network) load. ConcurrentEndpointSyncs int `json:"concurrentEndpointSyncs"` // concurrentRSSyncs is the number of replica sets that are allowed to sync - // concurrently. Larger number = more reponsive replica management, but more + // concurrently. Larger number = more responsive replica management, but more // CPU (and network) load. ConcurrentRSSyncs int `json:"concurrentRSSyncs"` // concurrentRCSyncs is the number of replication controllers that are - // allowed to sync concurrently. Larger number = more reponsive replica + // allowed to sync concurrently. Larger number = more responsive replica // management, but more CPU (and network) load. ConcurrentRCSyncs int `json:"concurrentRCSyncs"` // concurrentResourceQuotaSyncs is the number of resource quotas that are @@ -385,20 +415,29 @@ type KubeControllerManagerConfiguration struct { // management, but more CPU (and network) load. ConcurrentResourceQuotaSyncs int `json:"concurrentResourceQuotaSyncs"` // concurrentDeploymentSyncs is the number of deployment objects that are - // allowed to sync concurrently. Larger number = more reponsive deployments, + // allowed to sync concurrently. Larger number = more responsive deployments, // but more CPU (and network) load. ConcurrentDeploymentSyncs int `json:"concurrentDeploymentSyncs"` // concurrentDaemonSetSyncs is the number of daemonset objects that are - // allowed to sync concurrently. Larger number = more reponsive DaemonSet, + // allowed to sync concurrently. Larger number = more responsive daemonset, // but more CPU (and network) load. ConcurrentDaemonSetSyncs int `json:"concurrentDaemonSetSyncs"` // concurrentJobSyncs is the number of job objects that are - // allowed to sync concurrently. Larger number = more reponsive jobs, + // allowed to sync concurrently. Larger number = more responsive jobs, // but more CPU (and network) load. ConcurrentJobSyncs int `json:"concurrentJobSyncs"` // concurrentNamespaceSyncs is the number of namespace objects that are // allowed to sync concurrently. ConcurrentNamespaceSyncs int `json:"concurrentNamespaceSyncs"` + // lookupCacheSizeForRC is the size of lookup cache for replication controllers. + // Larger number = more responsive replica management, but more MEM load. + LookupCacheSizeForRC int `json:"lookupCacheSizeForRC"` + // lookupCacheSizeForRS is the size of lookup cache for replicatsets. + // Larger number = more responsive replica management, but more MEM load. + LookupCacheSizeForRS int `json:"lookupCacheSizeForRS"` + // lookupCacheSizeForDaemonSet is the size of lookup cache for daemonsets. + // Larger number = more responsive daemonset, but more MEM load. + LookupCacheSizeForDaemonSet int `json:"lookupCacheSizeForDaemonSet"` // serviceSyncPeriod is the period for syncing services with their external // load balancers. ServiceSyncPeriod unversioned.Duration `json:"serviceSyncPeriod"` diff --git a/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1/conversion_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1/conversion_generated.go index b4bff5c81..9a5ce35ee 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1/conversion_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1/conversion_generated.go @@ -83,7 +83,7 @@ func autoConvert_componentconfig_KubeSchedulerConfiguration_To_v1alpha1_KubeSche out.Address = in.Address out.AlgorithmProvider = in.AlgorithmProvider out.PolicyConfigFile = in.PolicyConfigFile - if err := api.Convert_bool_To_bool_ref(&in.EnableProfiling, &out.EnableProfiling, s); err != nil { + if err := api.Convert_bool_To_Pointer_bool(&in.EnableProfiling, &out.EnableProfiling, s); err != nil { return err } out.KubeAPIQPS = in.KubeAPIQPS @@ -103,7 +103,7 @@ func autoConvert_componentconfig_LeaderElectionConfiguration_To_v1alpha1_LeaderE if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*componentconfig.LeaderElectionConfiguration))(in) } - if err := api.Convert_bool_To_bool_ref(&in.LeaderElect, &out.LeaderElect, s); err != nil { + if err := api.Convert_bool_To_Pointer_bool(&in.LeaderElect, &out.LeaderElect, s); err != nil { return err } if err := s.Convert(&in.LeaseDuration, &out.LeaseDuration, 0); err != nil { @@ -179,7 +179,7 @@ func autoConvert_v1alpha1_KubeSchedulerConfiguration_To_componentconfig_KubeSche out.Address = in.Address out.AlgorithmProvider = in.AlgorithmProvider out.PolicyConfigFile = in.PolicyConfigFile - if err := api.Convert_bool_ref_To_bool(&in.EnableProfiling, &out.EnableProfiling, s); err != nil { + if err := api.Convert_Pointer_bool_To_bool(&in.EnableProfiling, &out.EnableProfiling, s); err != nil { return err } out.KubeAPIQPS = in.KubeAPIQPS @@ -199,7 +199,7 @@ func autoConvert_v1alpha1_LeaderElectionConfiguration_To_componentconfig_LeaderE if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*LeaderElectionConfiguration))(in) } - if err := api.Convert_bool_ref_To_bool(&in.LeaderElect, &out.LeaderElect, s); err != nil { + if err := api.Convert_Pointer_bool_To_bool(&in.LeaderElect, &out.LeaderElect, s); err != nil { return err } if err := s.Convert(&in.LeaseDuration, &out.LeaseDuration, 0); err != nil { diff --git a/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1/deep_copy_generated.go index 928491342..d901043e9 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1/deep_copy_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1/deep_copy_generated.go @@ -1,5 +1,7 @@ +// +build !ignore_autogenerated + /* -Copyright 2015 The Kubernetes Authors All rights reserved. +Copyright 2016 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// DO NOT EDIT. THIS FILE IS AUTO-GENERATED BY $KUBEROOT/hack/update-generated-deep-copies.sh. +// This file was autogenerated by deepcopy-gen. Do not edit it manually! package v1alpha1 @@ -24,19 +26,19 @@ import ( conversion "k8s.io/kubernetes/pkg/conversion" ) -func deepCopy_unversioned_Duration(in unversioned.Duration, out *unversioned.Duration, c *conversion.Cloner) error { - out.Duration = in.Duration - return nil +func init() { + if err := api.Scheme.AddGeneratedDeepCopyFuncs( + DeepCopy_v1alpha1_KubeProxyConfiguration, + DeepCopy_v1alpha1_KubeSchedulerConfiguration, + DeepCopy_v1alpha1_LeaderElectionConfiguration, + ); err != nil { + // if one of the deep copy functions is malformed, detect it immediately. + panic(err) + } } -func deepCopy_unversioned_TypeMeta(in unversioned.TypeMeta, out *unversioned.TypeMeta, c *conversion.Cloner) error { - out.Kind = in.Kind - out.APIVersion = in.APIVersion - return nil -} - -func deepCopy_v1alpha1_KubeProxyConfiguration(in KubeProxyConfiguration, out *KubeProxyConfiguration, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1alpha1_KubeProxyConfiguration(in KubeProxyConfiguration, out *KubeProxyConfiguration, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.BindAddress = in.BindAddress @@ -44,38 +46,40 @@ func deepCopy_v1alpha1_KubeProxyConfiguration(in KubeProxyConfiguration, out *Ku out.HealthzPort = in.HealthzPort out.HostnameOverride = in.HostnameOverride if in.IPTablesMasqueradeBit != nil { - out.IPTablesMasqueradeBit = new(int32) - *out.IPTablesMasqueradeBit = *in.IPTablesMasqueradeBit + in, out := in.IPTablesMasqueradeBit, &out.IPTablesMasqueradeBit + *out = new(int32) + **out = *in } else { out.IPTablesMasqueradeBit = nil } - if err := deepCopy_unversioned_Duration(in.IPTablesSyncPeriod, &out.IPTablesSyncPeriod, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Duration(in.IPTablesSyncPeriod, &out.IPTablesSyncPeriod, c); err != nil { return err } out.KubeconfigPath = in.KubeconfigPath out.MasqueradeAll = in.MasqueradeAll out.Master = in.Master if in.OOMScoreAdj != nil { - out.OOMScoreAdj = new(int32) - *out.OOMScoreAdj = *in.OOMScoreAdj + in, out := in.OOMScoreAdj, &out.OOMScoreAdj + *out = new(int32) + **out = *in } else { out.OOMScoreAdj = nil } out.Mode = in.Mode out.PortRange = in.PortRange out.ResourceContainer = in.ResourceContainer - if err := deepCopy_unversioned_Duration(in.UDPIdleTimeout, &out.UDPIdleTimeout, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Duration(in.UDPIdleTimeout, &out.UDPIdleTimeout, c); err != nil { return err } out.ConntrackMax = in.ConntrackMax - if err := deepCopy_unversioned_Duration(in.ConntrackTCPEstablishedTimeout, &out.ConntrackTCPEstablishedTimeout, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Duration(in.ConntrackTCPEstablishedTimeout, &out.ConntrackTCPEstablishedTimeout, c); err != nil { return err } return nil } -func deepCopy_v1alpha1_KubeSchedulerConfiguration(in KubeSchedulerConfiguration, out *KubeSchedulerConfiguration, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1alpha1_KubeSchedulerConfiguration(in KubeSchedulerConfiguration, out *KubeSchedulerConfiguration, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.Port = in.Port @@ -83,49 +87,37 @@ func deepCopy_v1alpha1_KubeSchedulerConfiguration(in KubeSchedulerConfiguration, out.AlgorithmProvider = in.AlgorithmProvider out.PolicyConfigFile = in.PolicyConfigFile if in.EnableProfiling != nil { - out.EnableProfiling = new(bool) - *out.EnableProfiling = *in.EnableProfiling + in, out := in.EnableProfiling, &out.EnableProfiling + *out = new(bool) + **out = *in } else { out.EnableProfiling = nil } out.KubeAPIQPS = in.KubeAPIQPS out.KubeAPIBurst = in.KubeAPIBurst out.SchedulerName = in.SchedulerName - if err := deepCopy_v1alpha1_LeaderElectionConfiguration(in.LeaderElection, &out.LeaderElection, c); err != nil { + if err := DeepCopy_v1alpha1_LeaderElectionConfiguration(in.LeaderElection, &out.LeaderElection, c); err != nil { return err } return nil } -func deepCopy_v1alpha1_LeaderElectionConfiguration(in LeaderElectionConfiguration, out *LeaderElectionConfiguration, c *conversion.Cloner) error { +func DeepCopy_v1alpha1_LeaderElectionConfiguration(in LeaderElectionConfiguration, out *LeaderElectionConfiguration, c *conversion.Cloner) error { if in.LeaderElect != nil { - out.LeaderElect = new(bool) - *out.LeaderElect = *in.LeaderElect + in, out := in.LeaderElect, &out.LeaderElect + *out = new(bool) + **out = *in } else { out.LeaderElect = nil } - if err := deepCopy_unversioned_Duration(in.LeaseDuration, &out.LeaseDuration, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Duration(in.LeaseDuration, &out.LeaseDuration, c); err != nil { return err } - if err := deepCopy_unversioned_Duration(in.RenewDeadline, &out.RenewDeadline, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Duration(in.RenewDeadline, &out.RenewDeadline, c); err != nil { return err } - if err := deepCopy_unversioned_Duration(in.RetryPeriod, &out.RetryPeriod, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Duration(in.RetryPeriod, &out.RetryPeriod, c); err != nil { return err } return nil } - -func init() { - err := api.Scheme.AddGeneratedDeepCopyFuncs( - deepCopy_unversioned_Duration, - deepCopy_unversioned_TypeMeta, - deepCopy_v1alpha1_KubeProxyConfiguration, - deepCopy_v1alpha1_KubeSchedulerConfiguration, - deepCopy_v1alpha1_LeaderElectionConfiguration, - ) - if err != nil { - // if one of the deep copy functions is malformed, detect it immediately. - panic(err) - } -} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1/defaults.go b/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1/defaults.go index af35c7d38..3ef1a5500 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1/defaults.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1/defaults.go @@ -46,11 +46,11 @@ func addDefaultingFuncs(scheme *runtime.Scheme) { obj.ResourceContainer = "/kube-proxy" } if obj.IPTablesSyncPeriod.Duration == 0 { - obj.IPTablesSyncPeriod = unversioned.Duration{30 * time.Second} + obj.IPTablesSyncPeriod = unversioned.Duration{Duration: 30 * time.Second} } zero := unversioned.Duration{} if obj.UDPIdleTimeout == zero { - obj.UDPIdleTimeout = unversioned.Duration{250 * time.Millisecond} + obj.UDPIdleTimeout = unversioned.Duration{Duration: 250 * time.Millisecond} } if obj.ConntrackMax == 0 { obj.ConntrackMax = 256 * 1024 // 4x default (64k) @@ -86,13 +86,13 @@ func addDefaultingFuncs(scheme *runtime.Scheme) { func(obj *LeaderElectionConfiguration) { zero := unversioned.Duration{} if obj.LeaseDuration == zero { - obj.LeaseDuration = unversioned.Duration{15 * time.Second} + obj.LeaseDuration = unversioned.Duration{Duration: 15 * time.Second} } if obj.RenewDeadline == zero { - obj.RenewDeadline = unversioned.Duration{10 * time.Second} + obj.RenewDeadline = unversioned.Duration{Duration: 10 * time.Second} } if obj.RetryPeriod == zero { - obj.RetryPeriod = unversioned.Duration{2 * time.Second} + obj.RetryPeriod = unversioned.Duration{Duration: 2 * time.Second} } }, ) diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/deep_copy_generated.go index ff5898cfe..f80f04bc6 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/deep_copy_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/deep_copy_generated.go @@ -1,5 +1,7 @@ +// +build !ignore_autogenerated + /* -Copyright 2015 The Kubernetes Authors All rights reserved. +Copyright 2016 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,16 +16,1003 @@ See the License for the specific language governing permissions and limitations under the License. */ -// DO NOT EDIT. THIS FILE IS AUTO-GENERATED BY $KUBEROOT/hack/update-generated-deep-copies.sh. +// This file was autogenerated by deepcopy-gen. Do not edit it manually! package extensions -import api "k8s.io/kubernetes/pkg/api" +import ( + api "k8s.io/kubernetes/pkg/api" + resource "k8s.io/kubernetes/pkg/api/resource" + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + conversion "k8s.io/kubernetes/pkg/conversion" + intstr "k8s.io/kubernetes/pkg/util/intstr" +) func init() { - err := api.Scheme.AddGeneratedDeepCopyFuncs() - if err != nil { + if err := api.Scheme.AddGeneratedDeepCopyFuncs( + DeepCopy_extensions_APIVersion, + DeepCopy_extensions_CPUTargetUtilization, + DeepCopy_extensions_CustomMetricCurrentStatus, + DeepCopy_extensions_CustomMetricCurrentStatusList, + DeepCopy_extensions_CustomMetricTarget, + DeepCopy_extensions_CustomMetricTargetList, + DeepCopy_extensions_DaemonSet, + DeepCopy_extensions_DaemonSetList, + DeepCopy_extensions_DaemonSetSpec, + DeepCopy_extensions_DaemonSetStatus, + DeepCopy_extensions_Deployment, + DeepCopy_extensions_DeploymentList, + DeepCopy_extensions_DeploymentRollback, + DeepCopy_extensions_DeploymentSpec, + DeepCopy_extensions_DeploymentStatus, + DeepCopy_extensions_DeploymentStrategy, + DeepCopy_extensions_HTTPIngressPath, + DeepCopy_extensions_HTTPIngressRuleValue, + DeepCopy_extensions_HorizontalPodAutoscaler, + DeepCopy_extensions_HorizontalPodAutoscalerList, + DeepCopy_extensions_HorizontalPodAutoscalerSpec, + DeepCopy_extensions_HorizontalPodAutoscalerStatus, + DeepCopy_extensions_HostPortRange, + DeepCopy_extensions_IDRange, + DeepCopy_extensions_Ingress, + DeepCopy_extensions_IngressBackend, + DeepCopy_extensions_IngressList, + DeepCopy_extensions_IngressRule, + DeepCopy_extensions_IngressRuleValue, + DeepCopy_extensions_IngressSpec, + DeepCopy_extensions_IngressStatus, + DeepCopy_extensions_IngressTLS, + DeepCopy_extensions_Job, + DeepCopy_extensions_JobCondition, + DeepCopy_extensions_JobList, + DeepCopy_extensions_JobSpec, + DeepCopy_extensions_JobStatus, + DeepCopy_extensions_PodSecurityPolicy, + DeepCopy_extensions_PodSecurityPolicyList, + DeepCopy_extensions_PodSecurityPolicySpec, + DeepCopy_extensions_ReplicaSet, + DeepCopy_extensions_ReplicaSetList, + DeepCopy_extensions_ReplicaSetSpec, + DeepCopy_extensions_ReplicaSetStatus, + DeepCopy_extensions_ReplicationControllerDummy, + DeepCopy_extensions_RollbackConfig, + DeepCopy_extensions_RollingUpdateDeployment, + DeepCopy_extensions_RunAsUserStrategyOptions, + DeepCopy_extensions_SELinuxStrategyOptions, + DeepCopy_extensions_Scale, + DeepCopy_extensions_ScaleSpec, + DeepCopy_extensions_ScaleStatus, + DeepCopy_extensions_SubresourceReference, + DeepCopy_extensions_ThirdPartyResource, + DeepCopy_extensions_ThirdPartyResourceData, + DeepCopy_extensions_ThirdPartyResourceDataList, + DeepCopy_extensions_ThirdPartyResourceList, + ); err != nil { // if one of the deep copy functions is malformed, detect it immediately. panic(err) } } + +func DeepCopy_extensions_APIVersion(in APIVersion, out *APIVersion, c *conversion.Cloner) error { + out.Name = in.Name + return nil +} + +func DeepCopy_extensions_CPUTargetUtilization(in CPUTargetUtilization, out *CPUTargetUtilization, c *conversion.Cloner) error { + out.TargetPercentage = in.TargetPercentage + return nil +} + +func DeepCopy_extensions_CustomMetricCurrentStatus(in CustomMetricCurrentStatus, out *CustomMetricCurrentStatus, c *conversion.Cloner) error { + out.Name = in.Name + if err := resource.DeepCopy_resource_Quantity(in.CurrentValue, &out.CurrentValue, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_CustomMetricCurrentStatusList(in CustomMetricCurrentStatusList, out *CustomMetricCurrentStatusList, c *conversion.Cloner) error { + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]CustomMetricCurrentStatus, len(in)) + for i := range in { + if err := DeepCopy_extensions_CustomMetricCurrentStatus(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func DeepCopy_extensions_CustomMetricTarget(in CustomMetricTarget, out *CustomMetricTarget, c *conversion.Cloner) error { + out.Name = in.Name + if err := resource.DeepCopy_resource_Quantity(in.TargetValue, &out.TargetValue, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_CustomMetricTargetList(in CustomMetricTargetList, out *CustomMetricTargetList, c *conversion.Cloner) error { + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]CustomMetricTarget, len(in)) + for i := range in { + if err := DeepCopy_extensions_CustomMetricTarget(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func DeepCopy_extensions_DaemonSet(in DaemonSet, out *DaemonSet, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api.DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := DeepCopy_extensions_DaemonSetSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_extensions_DaemonSetStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_DaemonSetList(in DaemonSetList, out *DaemonSetList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]DaemonSet, len(in)) + for i := range in { + if err := DeepCopy_extensions_DaemonSet(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func DeepCopy_extensions_DaemonSetSpec(in DaemonSetSpec, out *DaemonSetSpec, c *conversion.Cloner) error { + if in.Selector != nil { + in, out := in.Selector, &out.Selector + *out = new(unversioned.LabelSelector) + if err := unversioned.DeepCopy_unversioned_LabelSelector(*in, *out, c); err != nil { + return err + } + } else { + out.Selector = nil + } + if err := api.DeepCopy_api_PodTemplateSpec(in.Template, &out.Template, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_DaemonSetStatus(in DaemonSetStatus, out *DaemonSetStatus, c *conversion.Cloner) error { + out.CurrentNumberScheduled = in.CurrentNumberScheduled + out.NumberMisscheduled = in.NumberMisscheduled + out.DesiredNumberScheduled = in.DesiredNumberScheduled + return nil +} + +func DeepCopy_extensions_Deployment(in Deployment, out *Deployment, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api.DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := DeepCopy_extensions_DeploymentSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_extensions_DeploymentStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_DeploymentList(in DeploymentList, out *DeploymentList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]Deployment, len(in)) + for i := range in { + if err := DeepCopy_extensions_Deployment(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func DeepCopy_extensions_DeploymentRollback(in DeploymentRollback, out *DeploymentRollback, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + out.Name = in.Name + if in.UpdatedAnnotations != nil { + in, out := in.UpdatedAnnotations, &out.UpdatedAnnotations + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val + } + } else { + out.UpdatedAnnotations = nil + } + if err := DeepCopy_extensions_RollbackConfig(in.RollbackTo, &out.RollbackTo, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_DeploymentSpec(in DeploymentSpec, out *DeploymentSpec, c *conversion.Cloner) error { + out.Replicas = in.Replicas + if in.Selector != nil { + in, out := in.Selector, &out.Selector + *out = new(unversioned.LabelSelector) + if err := unversioned.DeepCopy_unversioned_LabelSelector(*in, *out, c); err != nil { + return err + } + } else { + out.Selector = nil + } + if err := api.DeepCopy_api_PodTemplateSpec(in.Template, &out.Template, c); err != nil { + return err + } + if err := DeepCopy_extensions_DeploymentStrategy(in.Strategy, &out.Strategy, c); err != nil { + return err + } + out.MinReadySeconds = in.MinReadySeconds + if in.RevisionHistoryLimit != nil { + in, out := in.RevisionHistoryLimit, &out.RevisionHistoryLimit + *out = new(int) + **out = *in + } else { + out.RevisionHistoryLimit = nil + } + out.Paused = in.Paused + if in.RollbackTo != nil { + in, out := in.RollbackTo, &out.RollbackTo + *out = new(RollbackConfig) + if err := DeepCopy_extensions_RollbackConfig(*in, *out, c); err != nil { + return err + } + } else { + out.RollbackTo = nil + } + return nil +} + +func DeepCopy_extensions_DeploymentStatus(in DeploymentStatus, out *DeploymentStatus, c *conversion.Cloner) error { + out.ObservedGeneration = in.ObservedGeneration + out.Replicas = in.Replicas + out.UpdatedReplicas = in.UpdatedReplicas + out.AvailableReplicas = in.AvailableReplicas + out.UnavailableReplicas = in.UnavailableReplicas + return nil +} + +func DeepCopy_extensions_DeploymentStrategy(in DeploymentStrategy, out *DeploymentStrategy, c *conversion.Cloner) error { + out.Type = in.Type + if in.RollingUpdate != nil { + in, out := in.RollingUpdate, &out.RollingUpdate + *out = new(RollingUpdateDeployment) + if err := DeepCopy_extensions_RollingUpdateDeployment(*in, *out, c); err != nil { + return err + } + } else { + out.RollingUpdate = nil + } + return nil +} + +func DeepCopy_extensions_HTTPIngressPath(in HTTPIngressPath, out *HTTPIngressPath, c *conversion.Cloner) error { + out.Path = in.Path + if err := DeepCopy_extensions_IngressBackend(in.Backend, &out.Backend, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_HTTPIngressRuleValue(in HTTPIngressRuleValue, out *HTTPIngressRuleValue, c *conversion.Cloner) error { + if in.Paths != nil { + in, out := in.Paths, &out.Paths + *out = make([]HTTPIngressPath, len(in)) + for i := range in { + if err := DeepCopy_extensions_HTTPIngressPath(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Paths = nil + } + return nil +} + +func DeepCopy_extensions_HorizontalPodAutoscaler(in HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api.DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := DeepCopy_extensions_HorizontalPodAutoscalerSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_extensions_HorizontalPodAutoscalerStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_HorizontalPodAutoscalerList(in HorizontalPodAutoscalerList, out *HorizontalPodAutoscalerList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]HorizontalPodAutoscaler, len(in)) + for i := range in { + if err := DeepCopy_extensions_HorizontalPodAutoscaler(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func DeepCopy_extensions_HorizontalPodAutoscalerSpec(in HorizontalPodAutoscalerSpec, out *HorizontalPodAutoscalerSpec, c *conversion.Cloner) error { + if err := DeepCopy_extensions_SubresourceReference(in.ScaleRef, &out.ScaleRef, c); err != nil { + return err + } + if in.MinReplicas != nil { + in, out := in.MinReplicas, &out.MinReplicas + *out = new(int) + **out = *in + } else { + out.MinReplicas = nil + } + out.MaxReplicas = in.MaxReplicas + if in.CPUUtilization != nil { + in, out := in.CPUUtilization, &out.CPUUtilization + *out = new(CPUTargetUtilization) + if err := DeepCopy_extensions_CPUTargetUtilization(*in, *out, c); err != nil { + return err + } + } else { + out.CPUUtilization = nil + } + return nil +} + +func DeepCopy_extensions_HorizontalPodAutoscalerStatus(in HorizontalPodAutoscalerStatus, out *HorizontalPodAutoscalerStatus, c *conversion.Cloner) error { + if in.ObservedGeneration != nil { + in, out := in.ObservedGeneration, &out.ObservedGeneration + *out = new(int64) + **out = *in + } else { + out.ObservedGeneration = nil + } + if in.LastScaleTime != nil { + in, out := in.LastScaleTime, &out.LastScaleTime + *out = new(unversioned.Time) + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { + return err + } + } else { + out.LastScaleTime = nil + } + out.CurrentReplicas = in.CurrentReplicas + out.DesiredReplicas = in.DesiredReplicas + if in.CurrentCPUUtilizationPercentage != nil { + in, out := in.CurrentCPUUtilizationPercentage, &out.CurrentCPUUtilizationPercentage + *out = new(int) + **out = *in + } else { + out.CurrentCPUUtilizationPercentage = nil + } + return nil +} + +func DeepCopy_extensions_HostPortRange(in HostPortRange, out *HostPortRange, c *conversion.Cloner) error { + out.Min = in.Min + out.Max = in.Max + return nil +} + +func DeepCopy_extensions_IDRange(in IDRange, out *IDRange, c *conversion.Cloner) error { + out.Min = in.Min + out.Max = in.Max + return nil +} + +func DeepCopy_extensions_Ingress(in Ingress, out *Ingress, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api.DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := DeepCopy_extensions_IngressSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_extensions_IngressStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_IngressBackend(in IngressBackend, out *IngressBackend, c *conversion.Cloner) error { + out.ServiceName = in.ServiceName + if err := intstr.DeepCopy_intstr_IntOrString(in.ServicePort, &out.ServicePort, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_IngressList(in IngressList, out *IngressList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]Ingress, len(in)) + for i := range in { + if err := DeepCopy_extensions_Ingress(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func DeepCopy_extensions_IngressRule(in IngressRule, out *IngressRule, c *conversion.Cloner) error { + out.Host = in.Host + if err := DeepCopy_extensions_IngressRuleValue(in.IngressRuleValue, &out.IngressRuleValue, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_IngressRuleValue(in IngressRuleValue, out *IngressRuleValue, c *conversion.Cloner) error { + if in.HTTP != nil { + in, out := in.HTTP, &out.HTTP + *out = new(HTTPIngressRuleValue) + if err := DeepCopy_extensions_HTTPIngressRuleValue(*in, *out, c); err != nil { + return err + } + } else { + out.HTTP = nil + } + return nil +} + +func DeepCopy_extensions_IngressSpec(in IngressSpec, out *IngressSpec, c *conversion.Cloner) error { + if in.Backend != nil { + in, out := in.Backend, &out.Backend + *out = new(IngressBackend) + if err := DeepCopy_extensions_IngressBackend(*in, *out, c); err != nil { + return err + } + } else { + out.Backend = nil + } + if in.TLS != nil { + in, out := in.TLS, &out.TLS + *out = make([]IngressTLS, len(in)) + for i := range in { + if err := DeepCopy_extensions_IngressTLS(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.TLS = nil + } + if in.Rules != nil { + in, out := in.Rules, &out.Rules + *out = make([]IngressRule, len(in)) + for i := range in { + if err := DeepCopy_extensions_IngressRule(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Rules = nil + } + return nil +} + +func DeepCopy_extensions_IngressStatus(in IngressStatus, out *IngressStatus, c *conversion.Cloner) error { + if err := api.DeepCopy_api_LoadBalancerStatus(in.LoadBalancer, &out.LoadBalancer, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_IngressTLS(in IngressTLS, out *IngressTLS, c *conversion.Cloner) error { + if in.Hosts != nil { + in, out := in.Hosts, &out.Hosts + *out = make([]string, len(in)) + copy(*out, in) + } else { + out.Hosts = nil + } + out.SecretName = in.SecretName + return nil +} + +func DeepCopy_extensions_Job(in Job, out *Job, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api.DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := DeepCopy_extensions_JobSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_extensions_JobStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_JobCondition(in JobCondition, out *JobCondition, c *conversion.Cloner) error { + out.Type = in.Type + out.Status = in.Status + if err := unversioned.DeepCopy_unversioned_Time(in.LastProbeTime, &out.LastProbeTime, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil { + return err + } + out.Reason = in.Reason + out.Message = in.Message + return nil +} + +func DeepCopy_extensions_JobList(in JobList, out *JobList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]Job, len(in)) + for i := range in { + if err := DeepCopy_extensions_Job(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func DeepCopy_extensions_JobSpec(in JobSpec, out *JobSpec, c *conversion.Cloner) error { + if in.Parallelism != nil { + in, out := in.Parallelism, &out.Parallelism + *out = new(int) + **out = *in + } else { + out.Parallelism = nil + } + if in.Completions != nil { + in, out := in.Completions, &out.Completions + *out = new(int) + **out = *in + } else { + out.Completions = nil + } + if in.ActiveDeadlineSeconds != nil { + in, out := in.ActiveDeadlineSeconds, &out.ActiveDeadlineSeconds + *out = new(int64) + **out = *in + } else { + out.ActiveDeadlineSeconds = nil + } + if in.Selector != nil { + in, out := in.Selector, &out.Selector + *out = new(unversioned.LabelSelector) + if err := unversioned.DeepCopy_unversioned_LabelSelector(*in, *out, c); err != nil { + return err + } + } else { + out.Selector = nil + } + if in.ManualSelector != nil { + in, out := in.ManualSelector, &out.ManualSelector + *out = new(bool) + **out = *in + } else { + out.ManualSelector = nil + } + if err := api.DeepCopy_api_PodTemplateSpec(in.Template, &out.Template, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_JobStatus(in JobStatus, out *JobStatus, c *conversion.Cloner) error { + if in.Conditions != nil { + in, out := in.Conditions, &out.Conditions + *out = make([]JobCondition, len(in)) + for i := range in { + if err := DeepCopy_extensions_JobCondition(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Conditions = nil + } + if in.StartTime != nil { + in, out := in.StartTime, &out.StartTime + *out = new(unversioned.Time) + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { + return err + } + } else { + out.StartTime = nil + } + if in.CompletionTime != nil { + in, out := in.CompletionTime, &out.CompletionTime + *out = new(unversioned.Time) + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { + return err + } + } else { + out.CompletionTime = nil + } + out.Active = in.Active + out.Succeeded = in.Succeeded + out.Failed = in.Failed + return nil +} + +func DeepCopy_extensions_PodSecurityPolicy(in PodSecurityPolicy, out *PodSecurityPolicy, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api.DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := DeepCopy_extensions_PodSecurityPolicySpec(in.Spec, &out.Spec, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_PodSecurityPolicyList(in PodSecurityPolicyList, out *PodSecurityPolicyList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]PodSecurityPolicy, len(in)) + for i := range in { + if err := DeepCopy_extensions_PodSecurityPolicy(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func DeepCopy_extensions_PodSecurityPolicySpec(in PodSecurityPolicySpec, out *PodSecurityPolicySpec, c *conversion.Cloner) error { + out.Privileged = in.Privileged + if in.Capabilities != nil { + in, out := in.Capabilities, &out.Capabilities + *out = make([]api.Capability, len(in)) + for i := range in { + (*out)[i] = in[i] + } + } else { + out.Capabilities = nil + } + if in.Volumes != nil { + in, out := in.Volumes, &out.Volumes + *out = make([]FSType, len(in)) + for i := range in { + (*out)[i] = in[i] + } + } else { + out.Volumes = nil + } + out.HostNetwork = in.HostNetwork + if in.HostPorts != nil { + in, out := in.HostPorts, &out.HostPorts + *out = make([]HostPortRange, len(in)) + for i := range in { + if err := DeepCopy_extensions_HostPortRange(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.HostPorts = nil + } + out.HostPID = in.HostPID + out.HostIPC = in.HostIPC + if err := DeepCopy_extensions_SELinuxStrategyOptions(in.SELinux, &out.SELinux, c); err != nil { + return err + } + if err := DeepCopy_extensions_RunAsUserStrategyOptions(in.RunAsUser, &out.RunAsUser, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_ReplicaSet(in ReplicaSet, out *ReplicaSet, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api.DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := DeepCopy_extensions_ReplicaSetSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_extensions_ReplicaSetStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_ReplicaSetList(in ReplicaSetList, out *ReplicaSetList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]ReplicaSet, len(in)) + for i := range in { + if err := DeepCopy_extensions_ReplicaSet(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func DeepCopy_extensions_ReplicaSetSpec(in ReplicaSetSpec, out *ReplicaSetSpec, c *conversion.Cloner) error { + out.Replicas = in.Replicas + if in.Selector != nil { + in, out := in.Selector, &out.Selector + *out = new(unversioned.LabelSelector) + if err := unversioned.DeepCopy_unversioned_LabelSelector(*in, *out, c); err != nil { + return err + } + } else { + out.Selector = nil + } + if err := api.DeepCopy_api_PodTemplateSpec(in.Template, &out.Template, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_ReplicaSetStatus(in ReplicaSetStatus, out *ReplicaSetStatus, c *conversion.Cloner) error { + out.Replicas = in.Replicas + out.FullyLabeledReplicas = in.FullyLabeledReplicas + out.ObservedGeneration = in.ObservedGeneration + return nil +} + +func DeepCopy_extensions_ReplicationControllerDummy(in ReplicationControllerDummy, out *ReplicationControllerDummy, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_RollbackConfig(in RollbackConfig, out *RollbackConfig, c *conversion.Cloner) error { + out.Revision = in.Revision + return nil +} + +func DeepCopy_extensions_RollingUpdateDeployment(in RollingUpdateDeployment, out *RollingUpdateDeployment, c *conversion.Cloner) error { + if err := intstr.DeepCopy_intstr_IntOrString(in.MaxUnavailable, &out.MaxUnavailable, c); err != nil { + return err + } + if err := intstr.DeepCopy_intstr_IntOrString(in.MaxSurge, &out.MaxSurge, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_RunAsUserStrategyOptions(in RunAsUserStrategyOptions, out *RunAsUserStrategyOptions, c *conversion.Cloner) error { + out.Rule = in.Rule + if in.Ranges != nil { + in, out := in.Ranges, &out.Ranges + *out = make([]IDRange, len(in)) + for i := range in { + if err := DeepCopy_extensions_IDRange(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Ranges = nil + } + return nil +} + +func DeepCopy_extensions_SELinuxStrategyOptions(in SELinuxStrategyOptions, out *SELinuxStrategyOptions, c *conversion.Cloner) error { + out.Rule = in.Rule + if in.SELinuxOptions != nil { + in, out := in.SELinuxOptions, &out.SELinuxOptions + *out = new(api.SELinuxOptions) + if err := api.DeepCopy_api_SELinuxOptions(*in, *out, c); err != nil { + return err + } + } else { + out.SELinuxOptions = nil + } + return nil +} + +func DeepCopy_extensions_Scale(in Scale, out *Scale, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api.DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := DeepCopy_extensions_ScaleSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_extensions_ScaleStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_extensions_ScaleSpec(in ScaleSpec, out *ScaleSpec, c *conversion.Cloner) error { + out.Replicas = in.Replicas + return nil +} + +func DeepCopy_extensions_ScaleStatus(in ScaleStatus, out *ScaleStatus, c *conversion.Cloner) error { + out.Replicas = in.Replicas + if in.Selector != nil { + in, out := in.Selector, &out.Selector + *out = new(unversioned.LabelSelector) + if err := unversioned.DeepCopy_unversioned_LabelSelector(*in, *out, c); err != nil { + return err + } + } else { + out.Selector = nil + } + return nil +} + +func DeepCopy_extensions_SubresourceReference(in SubresourceReference, out *SubresourceReference, c *conversion.Cloner) error { + out.Kind = in.Kind + out.Name = in.Name + out.APIVersion = in.APIVersion + out.Subresource = in.Subresource + return nil +} + +func DeepCopy_extensions_ThirdPartyResource(in ThirdPartyResource, out *ThirdPartyResource, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api.DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + out.Description = in.Description + if in.Versions != nil { + in, out := in.Versions, &out.Versions + *out = make([]APIVersion, len(in)) + for i := range in { + if err := DeepCopy_extensions_APIVersion(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Versions = nil + } + return nil +} + +func DeepCopy_extensions_ThirdPartyResourceData(in ThirdPartyResourceData, out *ThirdPartyResourceData, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := api.DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if in.Data != nil { + in, out := in.Data, &out.Data + *out = make([]byte, len(in)) + copy(*out, in) + } else { + out.Data = nil + } + return nil +} + +func DeepCopy_extensions_ThirdPartyResourceDataList(in ThirdPartyResourceDataList, out *ThirdPartyResourceDataList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]ThirdPartyResourceData, len(in)) + for i := range in { + if err := DeepCopy_extensions_ThirdPartyResourceData(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func DeepCopy_extensions_ThirdPartyResourceList(in ThirdPartyResourceList, out *ThirdPartyResourceList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]ThirdPartyResource, len(in)) + for i := range in { + if err := DeepCopy_extensions_ThirdPartyResource(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/helpers.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/helpers.go deleted file mode 100644 index c6d79d663..000000000 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/helpers.go +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright 2015 The Kubernetes Authors All rights reserved. - -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 extensions - -// TODO(madhusudancs): Fix this when Scale group issues are resolved (see issue #18528). -// import ( -// "fmt" - -// "k8s.io/kubernetes/pkg/api" -// "k8s.io/kubernetes/pkg/api/unversioned" -// ) - -// // ScaleFromDeployment returns a scale subresource for a deployment. -// func ScaleFromDeployment(deployment *Deployment) (*Scale, error) { -// selector, err := unversioned.LabelSelectorAsSelector(deployment.Spec.Selector) -// if err != nil { -// return nil, fmt.Errorf("invalid label selector: %v", err) -// } -// return &Scale{ -// ObjectMeta: api.ObjectMeta{ -// Name: deployment.Name, -// Namespace: deployment.Namespace, -// CreationTimestamp: deployment.CreationTimestamp, -// }, -// Spec: ScaleSpec{ -// Replicas: deployment.Spec.Replicas, -// }, -// Status: ScaleStatus{ -// Replicas: deployment.Status.Replicas, -// Selector: selector.String(), -// }, -// }, nil -// } diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/register.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/register.go index db7b93770..057940d16 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/register.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/register.go @@ -47,8 +47,6 @@ func AddToScheme(scheme *runtime.Scheme) { func addKnownTypes(scheme *runtime.Scheme) { // TODO this gets cleaned up when the types are fixed scheme.AddKnownTypes(SchemeGroupVersion, - &ClusterAutoscaler{}, - &ClusterAutoscalerList{}, &Deployment{}, &DeploymentList{}, &DeploymentRollback{}, @@ -75,8 +73,6 @@ func addKnownTypes(scheme *runtime.Scheme) { ) } -func (obj *ClusterAutoscaler) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } -func (obj *ClusterAutoscalerList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *Deployment) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *DeploymentList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *DeploymentRollback) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/types.generated.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/types.generated.go index fabe00c2f..522830563 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/types.generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/types.generated.go @@ -68,7 +68,7 @@ func init() { if false { // reference the types, but skip this branch at build/run time var v0 pkg2_api.ObjectMeta var v1 pkg4_resource.Quantity - var v2 pkg1_unversioned.TypeMeta + var v2 pkg1_unversioned.LabelSelector var v3 pkg3_types.UID var v4 pkg6_intstr.IntOrString var v5 pkg5_inf.Dec @@ -263,7 +263,7 @@ func (x *ScaleStatus) CodecEncodeSelf(e *codec1978.Encoder) { var yyq2 [2]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[1] = len(x.Selector) != 0 + yyq2[1] = x.Selector != nil var yynn2 int if yyr2 || yy2arr2 { r.EncodeArrayStart(2) @@ -305,8 +305,9 @@ func (x *ScaleStatus) CodecEncodeSelf(e *codec1978.Encoder) { yym7 := z.EncBinary() _ = yym7 if false { + } else if z.HasExtensions() && z.EncExt(x.Selector) { } else { - z.F.EncMapStringStringV(x.Selector, false, e) + z.EncFallback(x.Selector) } } } else { @@ -323,8 +324,9 @@ func (x *ScaleStatus) CodecEncodeSelf(e *codec1978.Encoder) { yym8 := z.EncBinary() _ = yym8 if false { + } else if z.HasExtensions() && z.EncExt(x.Selector) { } else { - z.F.EncMapStringStringV(x.Selector, false, e) + z.EncFallback(x.Selector) } } } @@ -398,14 +400,19 @@ func (x *ScaleStatus) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { } case "selector": if r.TryDecodeAsNil() { - x.Selector = nil + if x.Selector != nil { + x.Selector = nil + } } else { - yyv5 := &x.Selector + if x.Selector == nil { + x.Selector = new(pkg1_unversioned.LabelSelector) + } yym6 := z.DecBinary() _ = yym6 if false { + } else if z.HasExtensions() && z.DecExt(x.Selector) { } else { - z.F.DecMapStringStringX(yyv5, false, d) + z.DecFallback(x.Selector, false) } } default: @@ -450,14 +457,19 @@ func (x *ScaleStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.Selector = nil + if x.Selector != nil { + x.Selector = nil + } } else { - yyv9 := &x.Selector + if x.Selector == nil { + x.Selector = new(pkg1_unversioned.LabelSelector) + } yym10 := z.DecBinary() _ = yym10 if false { + } else if z.HasExtensions() && z.DecExt(x.Selector) { } else { - z.F.DecMapStringStringX(yyv9, false, d) + z.DecFallback(x.Selector, false) } } for { @@ -4539,14 +4551,13 @@ func (x *APIVersion) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [2]bool + var yyq2 [1]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[0] = x.Name != "" - yyq2[1] = x.APIGroup != "" var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(2) + r.EncodeArrayStart(1) } else { yynn2 = 0 for _, b := range yyq2 { @@ -4582,31 +4593,6 @@ func (x *APIVersion) CodecEncodeSelf(e *codec1978.Encoder) { } } } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[1] { - yym7 := z.EncBinary() - _ = yym7 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIGroup)) - } - } else { - r.EncodeString(codecSelferC_UTF81234, "") - } - } else { - if yyq2[1] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("apiGroup")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym8 := z.EncBinary() - _ = yym8 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIGroup)) - } - } - } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayEnd1234) } else { @@ -4674,12 +4660,6 @@ func (x *APIVersion) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { } else { x.Name = string(r.DecodeString()) } - case "apiGroup": - if r.TryDecodeAsNil() { - x.APIGroup = "" - } else { - x.APIGroup = string(r.DecodeString()) - } default: z.DecStructFieldNotFound(-1, yys3) } // end switch yys3 @@ -4691,16 +4671,16 @@ func (x *APIVersion) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj6 int - var yyb6 bool - var yyhl6 bool = l >= 0 - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + var yyj5 int + var yyb5 bool + var yyhl5 bool = l >= 0 + yyj5++ + if yyhl5 { + yyb5 = yyj5 > l } else { - yyb6 = r.CheckBreak() + yyb5 = r.CheckBreak() } - if yyb6 { + if yyb5 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4710,34 +4690,18 @@ func (x *APIVersion) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } else { x.Name = string(r.DecodeString()) } - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l - } else { - yyb6 = r.CheckBreak() - } - if yyb6 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.APIGroup = "" - } else { - x.APIGroup = string(r.DecodeString()) - } for { - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + yyj5++ + if yyhl5 { + yyb5 = yyj5 > l } else { - yyb6 = r.CheckBreak() + yyb5 = r.CheckBreak() } - if yyb6 { + if yyb5 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj6-1, "") + z.DecStructFieldNotFound(yyj5-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -7022,16 +6986,17 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [4]bool + var yyq2 [5]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[0] = x.Replicas != 0 - yyq2[1] = x.UpdatedReplicas != 0 - yyq2[2] = x.AvailableReplicas != 0 - yyq2[3] = x.UnavailableReplicas != 0 + yyq2[0] = x.ObservedGeneration != 0 + yyq2[1] = x.Replicas != 0 + yyq2[2] = x.UpdatedReplicas != 0 + yyq2[3] = x.AvailableReplicas != 0 + yyq2[4] = x.UnavailableReplicas != 0 var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(4) + r.EncodeArrayStart(5) } else { yynn2 = 0 for _, b := range yyq2 { @@ -7049,7 +7014,7 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym4 if false { } else { - r.EncodeInt(int64(x.Replicas)) + r.EncodeInt(int64(x.ObservedGeneration)) } } else { r.EncodeInt(0) @@ -7057,13 +7022,13 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[0] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("replicas")) + r.EncodeString(codecSelferC_UTF81234, string("observedGeneration")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym5 := z.EncBinary() _ = yym5 if false { } else { - r.EncodeInt(int64(x.Replicas)) + r.EncodeInt(int64(x.ObservedGeneration)) } } } @@ -7074,7 +7039,7 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym7 if false { } else { - r.EncodeInt(int64(x.UpdatedReplicas)) + r.EncodeInt(int64(x.Replicas)) } } else { r.EncodeInt(0) @@ -7082,13 +7047,13 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[1] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("updatedReplicas")) + r.EncodeString(codecSelferC_UTF81234, string("replicas")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym8 := z.EncBinary() _ = yym8 if false { } else { - r.EncodeInt(int64(x.UpdatedReplicas)) + r.EncodeInt(int64(x.Replicas)) } } } @@ -7099,7 +7064,7 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym10 if false { } else { - r.EncodeInt(int64(x.AvailableReplicas)) + r.EncodeInt(int64(x.UpdatedReplicas)) } } else { r.EncodeInt(0) @@ -7107,13 +7072,13 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[2] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("availableReplicas")) + r.EncodeString(codecSelferC_UTF81234, string("updatedReplicas")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym11 := z.EncBinary() _ = yym11 if false { } else { - r.EncodeInt(int64(x.AvailableReplicas)) + r.EncodeInt(int64(x.UpdatedReplicas)) } } } @@ -7124,7 +7089,7 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym13 if false { } else { - r.EncodeInt(int64(x.UnavailableReplicas)) + r.EncodeInt(int64(x.AvailableReplicas)) } } else { r.EncodeInt(0) @@ -7132,11 +7097,36 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[3] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("unavailableReplicas")) + r.EncodeString(codecSelferC_UTF81234, string("availableReplicas")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym14 := z.EncBinary() _ = yym14 if false { + } else { + r.EncodeInt(int64(x.AvailableReplicas)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[4] { + yym16 := z.EncBinary() + _ = yym16 + if false { + } else { + r.EncodeInt(int64(x.UnavailableReplicas)) + } + } else { + r.EncodeInt(0) + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("unavailableReplicas")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym17 := z.EncBinary() + _ = yym17 + if false { } else { r.EncodeInt(int64(x.UnavailableReplicas)) } @@ -7203,6 +7193,12 @@ func (x *DeploymentStatus) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { yys3 := string(yys3Slc) z.DecSendContainerState(codecSelfer_containerMapValue1234) switch yys3 { + case "observedGeneration": + if r.TryDecodeAsNil() { + x.ObservedGeneration = 0 + } else { + x.ObservedGeneration = int64(r.DecodeInt(64)) + } case "replicas": if r.TryDecodeAsNil() { x.Replicas = 0 @@ -7238,16 +7234,32 @@ func (x *DeploymentStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj8 int - var yyb8 bool - var yyhl8 bool = l >= 0 - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + var yyj9 int + var yyb9 bool + var yyhl9 bool = l >= 0 + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ObservedGeneration = 0 + } else { + x.ObservedGeneration = int64(r.DecodeInt(64)) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7257,13 +7269,13 @@ func (x *DeploymentStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) } else { x.Replicas = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7273,13 +7285,13 @@ func (x *DeploymentStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) } else { x.UpdatedReplicas = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7289,13 +7301,13 @@ func (x *DeploymentStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) } else { x.AvailableReplicas = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7306,17 +7318,17 @@ func (x *DeploymentStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) x.UnavailableReplicas = int(r.DecodeInt(codecSelferBitsize1234)) } for { - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj8-1, "") + z.DecStructFieldNotFound(yyj9-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -9877,16 +9889,17 @@ func (x *JobSpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [5]bool + var yyq2 [6]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[0] = x.Parallelism != nil yyq2[1] = x.Completions != nil yyq2[2] = x.ActiveDeadlineSeconds != nil yyq2[3] = x.Selector != nil + yyq2[4] = x.ManualSelector != nil var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(5) + r.EncodeArrayStart(6) } else { yynn2 = 1 for _, b := range yyq2 { @@ -10039,14 +10052,49 @@ func (x *JobSpec) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy22 := &x.Template - yy22.CodecEncodeSelf(e) + if yyq2[4] { + if x.ManualSelector == nil { + r.EncodeNil() + } else { + yy22 := *x.ManualSelector + yym23 := z.EncBinary() + _ = yym23 + if false { + } else { + r.EncodeBool(bool(yy22)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("manualSelector")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.ManualSelector == nil { + r.EncodeNil() + } else { + yy24 := *x.ManualSelector + yym25 := z.EncBinary() + _ = yym25 + if false { + } else { + r.EncodeBool(bool(yy24)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy27 := &x.Template + yy27.CodecEncodeSelf(e) } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("template")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy24 := &x.Template - yy24.CodecEncodeSelf(e) + yy29 := &x.Template + yy29.CodecEncodeSelf(e) } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayEnd1234) @@ -10174,12 +10222,28 @@ func (x *JobSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { z.DecFallback(x.Selector, false) } } + case "manualSelector": + if r.TryDecodeAsNil() { + if x.ManualSelector != nil { + x.ManualSelector = nil + } + } else { + if x.ManualSelector == nil { + x.ManualSelector = new(bool) + } + yym13 := z.DecBinary() + _ = yym13 + if false { + } else { + *((*bool)(x.ManualSelector)) = r.DecodeBool() + } + } case "template": if r.TryDecodeAsNil() { x.Template = pkg2_api.PodTemplateSpec{} } else { - yyv12 := &x.Template - yyv12.CodecDecodeSelf(d) + yyv14 := &x.Template + yyv14.CodecDecodeSelf(d) } default: z.DecStructFieldNotFound(-1, yys3) @@ -10192,16 +10256,16 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj13 int - var yyb13 bool - var yyhl13 bool = l >= 0 - yyj13++ - if yyhl13 { - yyb13 = yyj13 > l + var yyj15 int + var yyb15 bool + var yyhl15 bool = l >= 0 + yyj15++ + if yyhl15 { + yyb15 = yyj15 > l } else { - yyb13 = r.CheckBreak() + yyb15 = r.CheckBreak() } - if yyb13 { + if yyb15 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -10214,20 +10278,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if x.Parallelism == nil { x.Parallelism = new(int) } - yym15 := z.DecBinary() - _ = yym15 + yym17 := z.DecBinary() + _ = yym17 if false { } else { *((*int)(x.Parallelism)) = int(r.DecodeInt(codecSelferBitsize1234)) } } - yyj13++ - if yyhl13 { - yyb13 = yyj13 > l + yyj15++ + if yyhl15 { + yyb15 = yyj15 > l } else { - yyb13 = r.CheckBreak() + yyb15 = r.CheckBreak() } - if yyb13 { + if yyb15 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -10240,20 +10304,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if x.Completions == nil { x.Completions = new(int) } - yym17 := z.DecBinary() - _ = yym17 + yym19 := z.DecBinary() + _ = yym19 if false { } else { *((*int)(x.Completions)) = int(r.DecodeInt(codecSelferBitsize1234)) } } - yyj13++ - if yyhl13 { - yyb13 = yyj13 > l + yyj15++ + if yyhl15 { + yyb15 = yyj15 > l } else { - yyb13 = r.CheckBreak() + yyb15 = r.CheckBreak() } - if yyb13 { + if yyb15 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -10266,20 +10330,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if x.ActiveDeadlineSeconds == nil { x.ActiveDeadlineSeconds = new(int64) } - yym19 := z.DecBinary() - _ = yym19 + yym21 := z.DecBinary() + _ = yym21 if false { } else { *((*int64)(x.ActiveDeadlineSeconds)) = int64(r.DecodeInt(64)) } } - yyj13++ - if yyhl13 { - yyb13 = yyj13 > l + yyj15++ + if yyhl15 { + yyb15 = yyj15 > l } else { - yyb13 = r.CheckBreak() + yyb15 = r.CheckBreak() } - if yyb13 { + if yyb15 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -10292,21 +10356,47 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if x.Selector == nil { x.Selector = new(pkg1_unversioned.LabelSelector) } - yym21 := z.DecBinary() - _ = yym21 + yym23 := z.DecBinary() + _ = yym23 if false { } else if z.HasExtensions() && z.DecExt(x.Selector) { } else { z.DecFallback(x.Selector, false) } } - yyj13++ - if yyhl13 { - yyb13 = yyj13 > l + yyj15++ + if yyhl15 { + yyb15 = yyj15 > l } else { - yyb13 = r.CheckBreak() + yyb15 = r.CheckBreak() } - if yyb13 { + if yyb15 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.ManualSelector != nil { + x.ManualSelector = nil + } + } else { + if x.ManualSelector == nil { + x.ManualSelector = new(bool) + } + yym25 := z.DecBinary() + _ = yym25 + if false { + } else { + *((*bool)(x.ManualSelector)) = r.DecodeBool() + } + } + yyj15++ + if yyhl15 { + yyb15 = yyj15 > l + } else { + yyb15 = r.CheckBreak() + } + if yyb15 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -10314,21 +10404,21 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if r.TryDecodeAsNil() { x.Template = pkg2_api.PodTemplateSpec{} } else { - yyv22 := &x.Template - yyv22.CodecDecodeSelf(d) + yyv26 := &x.Template + yyv26.CodecDecodeSelf(d) } for { - yyj13++ - if yyhl13 { - yyb13 = yyj13 > l + yyj15++ + if yyhl15 { + yyb15 = yyj15 > l } else { - yyb13 = r.CheckBreak() + yyb15 = r.CheckBreak() } - if yyb13 { + if yyb15 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj13-1, "") + z.DecStructFieldNotFound(yyj15-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -13766,1134 +13856,6 @@ func (x *IngressBackend) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } -func (x NodeResource) CodecEncodeSelf(e *codec1978.Encoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperEncoder(e) - _, _, _ = h, z, r - yym1 := z.EncBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.EncExt(x) { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x)) - } -} - -func (x *NodeResource) CodecDecodeSelf(d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - yym1 := z.DecBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.DecExt(x) { - } else { - *((*string)(x)) = r.DecodeString() - } -} - -func (x *NodeUtilization) CodecEncodeSelf(e *codec1978.Encoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperEncoder(e) - _, _, _ = h, z, r - if x == nil { - r.EncodeNil() - } else { - yym1 := z.EncBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.EncExt(x) { - } else { - yysep2 := !z.EncBinary() - yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [2]bool - _, _, _ = yysep2, yyq2, yy2arr2 - const yyr2 bool = false - var yynn2 int - if yyr2 || yy2arr2 { - r.EncodeArrayStart(2) - } else { - yynn2 = 2 - for _, b := range yyq2 { - if b { - yynn2++ - } - } - r.EncodeMapStart(yynn2) - yynn2 = 0 - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - x.Resource.CodecEncodeSelf(e) - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("resource")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - x.Resource.CodecEncodeSelf(e) - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym7 := z.EncBinary() - _ = yym7 - if false { - } else { - r.EncodeFloat64(float64(x.Value)) - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("value")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym8 := z.EncBinary() - _ = yym8 - if false { - } else { - r.EncodeFloat64(float64(x.Value)) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - z.EncSendContainerState(codecSelfer_containerMapEnd1234) - } - } - } -} - -func (x *NodeUtilization) CodecDecodeSelf(d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - yym1 := z.DecBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.DecExt(x) { - } else { - yyct2 := r.ContainerType() - if yyct2 == codecSelferValueTypeMap1234 { - yyl2 := r.ReadMapStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerMapEnd1234) - } else { - x.codecDecodeSelfFromMap(yyl2, d) - } - } else if yyct2 == codecSelferValueTypeArray1234 { - yyl2 := r.ReadArrayStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - x.codecDecodeSelfFromArray(yyl2, d) - } - } else { - panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) - } - } -} - -func (x *NodeUtilization) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yys3Slc = z.DecScratchBuffer() // default slice to decode into - _ = yys3Slc - var yyhl3 bool = l >= 0 - for yyj3 := 0; ; yyj3++ { - if yyhl3 { - if yyj3 >= l { - break - } - } else { - if r.CheckBreak() { - break - } - } - z.DecSendContainerState(codecSelfer_containerMapKey1234) - yys3Slc = r.DecodeBytes(yys3Slc, true, true) - yys3 := string(yys3Slc) - z.DecSendContainerState(codecSelfer_containerMapValue1234) - switch yys3 { - case "resource": - if r.TryDecodeAsNil() { - x.Resource = "" - } else { - x.Resource = NodeResource(r.DecodeString()) - } - case "value": - if r.TryDecodeAsNil() { - x.Value = 0 - } else { - x.Value = float64(r.DecodeFloat(false)) - } - default: - z.DecStructFieldNotFound(-1, yys3) - } // end switch yys3 - } // end for yyj3 - z.DecSendContainerState(codecSelfer_containerMapEnd1234) -} - -func (x *NodeUtilization) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yyj6 int - var yyb6 bool - var yyhl6 bool = l >= 0 - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l - } else { - yyb6 = r.CheckBreak() - } - if yyb6 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.Resource = "" - } else { - x.Resource = NodeResource(r.DecodeString()) - } - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l - } else { - yyb6 = r.CheckBreak() - } - if yyb6 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.Value = 0 - } else { - x.Value = float64(r.DecodeFloat(false)) - } - for { - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l - } else { - yyb6 = r.CheckBreak() - } - if yyb6 { - break - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj6-1, "") - } - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) -} - -func (x *ClusterAutoscalerSpec) CodecEncodeSelf(e *codec1978.Encoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperEncoder(e) - _, _, _ = h, z, r - if x == nil { - r.EncodeNil() - } else { - yym1 := z.EncBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.EncExt(x) { - } else { - yysep2 := !z.EncBinary() - yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [3]bool - _, _, _ = yysep2, yyq2, yy2arr2 - const yyr2 bool = false - var yynn2 int - if yyr2 || yy2arr2 { - r.EncodeArrayStart(3) - } else { - yynn2 = 3 - for _, b := range yyq2 { - if b { - yynn2++ - } - } - r.EncodeMapStart(yynn2) - yynn2 = 0 - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym4 := z.EncBinary() - _ = yym4 - if false { - } else { - r.EncodeInt(int64(x.MinNodes)) - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("minNodes")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym5 := z.EncBinary() - _ = yym5 - if false { - } else { - r.EncodeInt(int64(x.MinNodes)) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym7 := z.EncBinary() - _ = yym7 - if false { - } else { - r.EncodeInt(int64(x.MaxNodes)) - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("maxNodes")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym8 := z.EncBinary() - _ = yym8 - if false { - } else { - r.EncodeInt(int64(x.MaxNodes)) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if x.TargetUtilization == nil { - r.EncodeNil() - } else { - yym10 := z.EncBinary() - _ = yym10 - if false { - } else { - h.encSliceNodeUtilization(([]NodeUtilization)(x.TargetUtilization), e) - } - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("target")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.TargetUtilization == nil { - r.EncodeNil() - } else { - yym11 := z.EncBinary() - _ = yym11 - if false { - } else { - h.encSliceNodeUtilization(([]NodeUtilization)(x.TargetUtilization), e) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - z.EncSendContainerState(codecSelfer_containerMapEnd1234) - } - } - } -} - -func (x *ClusterAutoscalerSpec) CodecDecodeSelf(d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - yym1 := z.DecBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.DecExt(x) { - } else { - yyct2 := r.ContainerType() - if yyct2 == codecSelferValueTypeMap1234 { - yyl2 := r.ReadMapStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerMapEnd1234) - } else { - x.codecDecodeSelfFromMap(yyl2, d) - } - } else if yyct2 == codecSelferValueTypeArray1234 { - yyl2 := r.ReadArrayStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - x.codecDecodeSelfFromArray(yyl2, d) - } - } else { - panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) - } - } -} - -func (x *ClusterAutoscalerSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yys3Slc = z.DecScratchBuffer() // default slice to decode into - _ = yys3Slc - var yyhl3 bool = l >= 0 - for yyj3 := 0; ; yyj3++ { - if yyhl3 { - if yyj3 >= l { - break - } - } else { - if r.CheckBreak() { - break - } - } - z.DecSendContainerState(codecSelfer_containerMapKey1234) - yys3Slc = r.DecodeBytes(yys3Slc, true, true) - yys3 := string(yys3Slc) - z.DecSendContainerState(codecSelfer_containerMapValue1234) - switch yys3 { - case "minNodes": - if r.TryDecodeAsNil() { - x.MinNodes = 0 - } else { - x.MinNodes = int(r.DecodeInt(codecSelferBitsize1234)) - } - case "maxNodes": - if r.TryDecodeAsNil() { - x.MaxNodes = 0 - } else { - x.MaxNodes = int(r.DecodeInt(codecSelferBitsize1234)) - } - case "target": - if r.TryDecodeAsNil() { - x.TargetUtilization = nil - } else { - yyv6 := &x.TargetUtilization - yym7 := z.DecBinary() - _ = yym7 - if false { - } else { - h.decSliceNodeUtilization((*[]NodeUtilization)(yyv6), d) - } - } - default: - z.DecStructFieldNotFound(-1, yys3) - } // end switch yys3 - } // end for yyj3 - z.DecSendContainerState(codecSelfer_containerMapEnd1234) -} - -func (x *ClusterAutoscalerSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yyj8 int - var yyb8 bool - var yyhl8 bool = l >= 0 - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.MinNodes = 0 - } else { - x.MinNodes = int(r.DecodeInt(codecSelferBitsize1234)) - } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.MaxNodes = 0 - } else { - x.MaxNodes = int(r.DecodeInt(codecSelferBitsize1234)) - } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.TargetUtilization = nil - } else { - yyv11 := &x.TargetUtilization - yym12 := z.DecBinary() - _ = yym12 - if false { - } else { - h.decSliceNodeUtilization((*[]NodeUtilization)(yyv11), d) - } - } - for { - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - break - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj8-1, "") - } - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) -} - -func (x *ClusterAutoscaler) CodecEncodeSelf(e *codec1978.Encoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperEncoder(e) - _, _, _ = h, z, r - if x == nil { - r.EncodeNil() - } else { - yym1 := z.EncBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.EncExt(x) { - } else { - yysep2 := !z.EncBinary() - yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [4]bool - _, _, _ = yysep2, yyq2, yy2arr2 - const yyr2 bool = false - yyq2[0] = true - yyq2[1] = true - yyq2[2] = x.Kind != "" - yyq2[3] = x.APIVersion != "" - var yynn2 int - if yyr2 || yy2arr2 { - r.EncodeArrayStart(4) - } else { - yynn2 = 0 - for _, b := range yyq2 { - if b { - yynn2++ - } - } - r.EncodeMapStart(yynn2) - yynn2 = 0 - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[0] { - yy4 := &x.ObjectMeta - yy4.CodecEncodeSelf(e) - } else { - r.EncodeNil() - } - } else { - if yyq2[0] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("metadata")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy6 := &x.ObjectMeta - yy6.CodecEncodeSelf(e) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[1] { - yy9 := &x.Spec - yy9.CodecEncodeSelf(e) - } else { - r.EncodeNil() - } - } else { - if yyq2[1] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("spec")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy11 := &x.Spec - yy11.CodecEncodeSelf(e) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[2] { - yym14 := z.EncBinary() - _ = yym14 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) - } - } else { - r.EncodeString(codecSelferC_UTF81234, "") - } - } else { - if yyq2[2] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("kind")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym15 := z.EncBinary() - _ = yym15 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[3] { - yym17 := z.EncBinary() - _ = yym17 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) - } - } else { - r.EncodeString(codecSelferC_UTF81234, "") - } - } else { - if yyq2[3] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym18 := z.EncBinary() - _ = yym18 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - z.EncSendContainerState(codecSelfer_containerMapEnd1234) - } - } - } -} - -func (x *ClusterAutoscaler) CodecDecodeSelf(d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - yym1 := z.DecBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.DecExt(x) { - } else { - yyct2 := r.ContainerType() - if yyct2 == codecSelferValueTypeMap1234 { - yyl2 := r.ReadMapStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerMapEnd1234) - } else { - x.codecDecodeSelfFromMap(yyl2, d) - } - } else if yyct2 == codecSelferValueTypeArray1234 { - yyl2 := r.ReadArrayStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - x.codecDecodeSelfFromArray(yyl2, d) - } - } else { - panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) - } - } -} - -func (x *ClusterAutoscaler) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yys3Slc = z.DecScratchBuffer() // default slice to decode into - _ = yys3Slc - var yyhl3 bool = l >= 0 - for yyj3 := 0; ; yyj3++ { - if yyhl3 { - if yyj3 >= l { - break - } - } else { - if r.CheckBreak() { - break - } - } - z.DecSendContainerState(codecSelfer_containerMapKey1234) - yys3Slc = r.DecodeBytes(yys3Slc, true, true) - yys3 := string(yys3Slc) - z.DecSendContainerState(codecSelfer_containerMapValue1234) - switch yys3 { - case "metadata": - if r.TryDecodeAsNil() { - x.ObjectMeta = pkg2_api.ObjectMeta{} - } else { - yyv4 := &x.ObjectMeta - yyv4.CodecDecodeSelf(d) - } - case "spec": - if r.TryDecodeAsNil() { - x.Spec = ClusterAutoscalerSpec{} - } else { - yyv5 := &x.Spec - yyv5.CodecDecodeSelf(d) - } - case "kind": - if r.TryDecodeAsNil() { - x.Kind = "" - } else { - x.Kind = string(r.DecodeString()) - } - case "apiVersion": - if r.TryDecodeAsNil() { - x.APIVersion = "" - } else { - x.APIVersion = string(r.DecodeString()) - } - default: - z.DecStructFieldNotFound(-1, yys3) - } // end switch yys3 - } // end for yyj3 - z.DecSendContainerState(codecSelfer_containerMapEnd1234) -} - -func (x *ClusterAutoscaler) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yyj8 int - var yyb8 bool - var yyhl8 bool = l >= 0 - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.ObjectMeta = pkg2_api.ObjectMeta{} - } else { - yyv9 := &x.ObjectMeta - yyv9.CodecDecodeSelf(d) - } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.Spec = ClusterAutoscalerSpec{} - } else { - yyv10 := &x.Spec - yyv10.CodecDecodeSelf(d) - } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.Kind = "" - } else { - x.Kind = string(r.DecodeString()) - } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.APIVersion = "" - } else { - x.APIVersion = string(r.DecodeString()) - } - for { - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - break - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj8-1, "") - } - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) -} - -func (x *ClusterAutoscalerList) CodecEncodeSelf(e *codec1978.Encoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperEncoder(e) - _, _, _ = h, z, r - if x == nil { - r.EncodeNil() - } else { - yym1 := z.EncBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.EncExt(x) { - } else { - yysep2 := !z.EncBinary() - yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [4]bool - _, _, _ = yysep2, yyq2, yy2arr2 - const yyr2 bool = false - yyq2[0] = true - yyq2[2] = x.Kind != "" - yyq2[3] = x.APIVersion != "" - var yynn2 int - if yyr2 || yy2arr2 { - r.EncodeArrayStart(4) - } else { - yynn2 = 1 - for _, b := range yyq2 { - if b { - yynn2++ - } - } - r.EncodeMapStart(yynn2) - yynn2 = 0 - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[0] { - yy4 := &x.ListMeta - yym5 := z.EncBinary() - _ = yym5 - if false { - } else if z.HasExtensions() && z.EncExt(yy4) { - } else { - z.EncFallback(yy4) - } - } else { - r.EncodeNil() - } - } else { - if yyq2[0] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("metadata")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy6 := &x.ListMeta - yym7 := z.EncBinary() - _ = yym7 - if false { - } else if z.HasExtensions() && z.EncExt(yy6) { - } else { - z.EncFallback(yy6) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if x.Items == nil { - r.EncodeNil() - } else { - yym9 := z.EncBinary() - _ = yym9 - if false { - } else { - h.encSliceClusterAutoscaler(([]ClusterAutoscaler)(x.Items), e) - } - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("items")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.Items == nil { - r.EncodeNil() - } else { - yym10 := z.EncBinary() - _ = yym10 - if false { - } else { - h.encSliceClusterAutoscaler(([]ClusterAutoscaler)(x.Items), e) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[2] { - yym12 := z.EncBinary() - _ = yym12 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) - } - } else { - r.EncodeString(codecSelferC_UTF81234, "") - } - } else { - if yyq2[2] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("kind")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym13 := z.EncBinary() - _ = yym13 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[3] { - yym15 := z.EncBinary() - _ = yym15 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) - } - } else { - r.EncodeString(codecSelferC_UTF81234, "") - } - } else { - if yyq2[3] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym16 := z.EncBinary() - _ = yym16 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - z.EncSendContainerState(codecSelfer_containerMapEnd1234) - } - } - } -} - -func (x *ClusterAutoscalerList) CodecDecodeSelf(d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - yym1 := z.DecBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.DecExt(x) { - } else { - yyct2 := r.ContainerType() - if yyct2 == codecSelferValueTypeMap1234 { - yyl2 := r.ReadMapStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerMapEnd1234) - } else { - x.codecDecodeSelfFromMap(yyl2, d) - } - } else if yyct2 == codecSelferValueTypeArray1234 { - yyl2 := r.ReadArrayStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - x.codecDecodeSelfFromArray(yyl2, d) - } - } else { - panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) - } - } -} - -func (x *ClusterAutoscalerList) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yys3Slc = z.DecScratchBuffer() // default slice to decode into - _ = yys3Slc - var yyhl3 bool = l >= 0 - for yyj3 := 0; ; yyj3++ { - if yyhl3 { - if yyj3 >= l { - break - } - } else { - if r.CheckBreak() { - break - } - } - z.DecSendContainerState(codecSelfer_containerMapKey1234) - yys3Slc = r.DecodeBytes(yys3Slc, true, true) - yys3 := string(yys3Slc) - z.DecSendContainerState(codecSelfer_containerMapValue1234) - switch yys3 { - case "metadata": - if r.TryDecodeAsNil() { - x.ListMeta = pkg1_unversioned.ListMeta{} - } else { - yyv4 := &x.ListMeta - yym5 := z.DecBinary() - _ = yym5 - if false { - } else if z.HasExtensions() && z.DecExt(yyv4) { - } else { - z.DecFallback(yyv4, false) - } - } - case "items": - if r.TryDecodeAsNil() { - x.Items = nil - } else { - yyv6 := &x.Items - yym7 := z.DecBinary() - _ = yym7 - if false { - } else { - h.decSliceClusterAutoscaler((*[]ClusterAutoscaler)(yyv6), d) - } - } - case "kind": - if r.TryDecodeAsNil() { - x.Kind = "" - } else { - x.Kind = string(r.DecodeString()) - } - case "apiVersion": - if r.TryDecodeAsNil() { - x.APIVersion = "" - } else { - x.APIVersion = string(r.DecodeString()) - } - default: - z.DecStructFieldNotFound(-1, yys3) - } // end switch yys3 - } // end for yyj3 - z.DecSendContainerState(codecSelfer_containerMapEnd1234) -} - -func (x *ClusterAutoscalerList) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yyj10 int - var yyb10 bool - var yyhl10 bool = l >= 0 - yyj10++ - if yyhl10 { - yyb10 = yyj10 > l - } else { - yyb10 = r.CheckBreak() - } - if yyb10 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.ListMeta = pkg1_unversioned.ListMeta{} - } else { - yyv11 := &x.ListMeta - yym12 := z.DecBinary() - _ = yym12 - if false { - } else if z.HasExtensions() && z.DecExt(yyv11) { - } else { - z.DecFallback(yyv11, false) - } - } - yyj10++ - if yyhl10 { - yyb10 = yyj10 > l - } else { - yyb10 = r.CheckBreak() - } - if yyb10 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.Items = nil - } else { - yyv13 := &x.Items - yym14 := z.DecBinary() - _ = yym14 - if false { - } else { - h.decSliceClusterAutoscaler((*[]ClusterAutoscaler)(yyv13), d) - } - } - yyj10++ - if yyhl10 { - yyb10 = yyj10 > l - } else { - yyb10 = r.CheckBreak() - } - if yyb10 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.Kind = "" - } else { - x.Kind = string(r.DecodeString()) - } - yyj10++ - if yyhl10 { - yyb10 = yyj10 > l - } else { - yyb10 = r.CheckBreak() - } - if yyb10 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.APIVersion = "" - } else { - x.APIVersion = string(r.DecodeString()) - } - for { - yyj10++ - if yyhl10 { - yyb10 = yyj10 > l - } else { - yyb10 = r.CheckBreak() - } - if yyb10 { - break - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj10-1, "") - } - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) -} - func (x *ReplicaSet) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -15599,7 +14561,7 @@ func (x *ReplicaSetSpec) CodecEncodeSelf(e *codec1978.Encoder) { _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[1] = x.Selector != nil - yyq2[2] = x.Template != nil + yyq2[2] = true var yynn2 int if yyr2 || yy2arr2 { r.EncodeArrayStart(3) @@ -15670,11 +14632,8 @@ func (x *ReplicaSetSpec) CodecEncodeSelf(e *codec1978.Encoder) { if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[2] { - if x.Template == nil { - r.EncodeNil() - } else { - x.Template.CodecEncodeSelf(e) - } + yy10 := &x.Template + yy10.CodecEncodeSelf(e) } else { r.EncodeNil() } @@ -15683,11 +14642,8 @@ func (x *ReplicaSetSpec) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("template")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.Template == nil { - r.EncodeNil() - } else { - x.Template.CodecEncodeSelf(e) - } + yy12 := &x.Template + yy12.CodecEncodeSelf(e) } } if yyr2 || yy2arr2 { @@ -15776,14 +14732,10 @@ func (x *ReplicaSetSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { } case "template": if r.TryDecodeAsNil() { - if x.Template != nil { - x.Template = nil - } + x.Template = pkg2_api.PodTemplateSpec{} } else { - if x.Template == nil { - x.Template = new(pkg2_api.PodTemplateSpec) - } - x.Template.CodecDecodeSelf(d) + yyv7 := &x.Template + yyv7.CodecDecodeSelf(d) } default: z.DecStructFieldNotFound(-1, yys3) @@ -15854,14 +14806,10 @@ func (x *ReplicaSetSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - if x.Template != nil { - x.Template = nil - } + x.Template = pkg2_api.PodTemplateSpec{} } else { - if x.Template == nil { - x.Template = new(pkg2_api.PodTemplateSpec) - } - x.Template.CodecDecodeSelf(d) + yyv12 := &x.Template + yyv12.CodecDecodeSelf(d) } for { yyj8++ @@ -15893,13 +14841,14 @@ func (x *ReplicaSetStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [2]bool + var yyq2 [3]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[1] = x.ObservedGeneration != 0 + yyq2[1] = x.FullyLabeledReplicas != 0 + yyq2[2] = x.ObservedGeneration != 0 var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(2) + r.EncodeArrayStart(3) } else { yynn2 = 1 for _, b := range yyq2 { @@ -15936,7 +14885,7 @@ func (x *ReplicaSetStatus) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym7 if false { } else { - r.EncodeInt(int64(x.ObservedGeneration)) + r.EncodeInt(int64(x.FullyLabeledReplicas)) } } else { r.EncodeInt(0) @@ -15944,11 +14893,36 @@ func (x *ReplicaSetStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[1] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("observedGeneration")) + r.EncodeString(codecSelferC_UTF81234, string("fullyLabeledReplicas")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym8 := z.EncBinary() _ = yym8 if false { + } else { + r.EncodeInt(int64(x.FullyLabeledReplicas)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeInt(int64(x.ObservedGeneration)) + } + } else { + r.EncodeInt(0) + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("observedGeneration")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym11 := z.EncBinary() + _ = yym11 + if false { } else { r.EncodeInt(int64(x.ObservedGeneration)) } @@ -16021,6 +14995,12 @@ func (x *ReplicaSetStatus) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { } else { x.Replicas = int(r.DecodeInt(codecSelferBitsize1234)) } + case "fullyLabeledReplicas": + if r.TryDecodeAsNil() { + x.FullyLabeledReplicas = 0 + } else { + x.FullyLabeledReplicas = int(r.DecodeInt(codecSelferBitsize1234)) + } case "observedGeneration": if r.TryDecodeAsNil() { x.ObservedGeneration = 0 @@ -16038,16 +15018,16 @@ func (x *ReplicaSetStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj6 int - var yyb6 bool - var yyhl6 bool = l >= 0 - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb6 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb6 { + if yyb7 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -16057,13 +15037,29 @@ func (x *ReplicaSetStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) } else { x.Replicas = int(r.DecodeInt(codecSelferBitsize1234)) } - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb6 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb6 { + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.FullyLabeledReplicas = 0 + } else { + x.FullyLabeledReplicas = int(r.DecodeInt(codecSelferBitsize1234)) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -16074,17 +15070,17 @@ func (x *ReplicaSetStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) x.ObservedGeneration = int64(r.DecodeInt(64)) } for { - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb6 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb6 { + if yyb7 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj6-1, "") + z.DecStructFieldNotFound(yyj7-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -16631,7 +15627,7 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[7] { - yy25 := &x.SELinuxContext + yy25 := &x.SELinux yy25.CodecEncodeSelf(e) } else { r.EncodeNil() @@ -16639,9 +15635,9 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[7] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("seLinuxContext")) + r.EncodeString(codecSelferC_UTF81234, string("seLinux")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy27 := &x.SELinuxContext + yy27 := &x.SELinux yy27.CodecEncodeSelf(e) } } @@ -16783,11 +15779,11 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromMap(l int, d *codec1978.Decod } else { x.HostIPC = bool(r.DecodeBool()) } - case "seLinuxContext": + case "seLinux": if r.TryDecodeAsNil() { - x.SELinuxContext = SELinuxContextStrategyOptions{} + x.SELinux = SELinuxStrategyOptions{} } else { - yyv14 := &x.SELinuxContext + yyv14 := &x.SELinux yyv14.CodecDecodeSelf(d) } case "runAsUser": @@ -16953,9 +15949,9 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.SELinuxContext = SELinuxContextStrategyOptions{} + x.SELinux = SELinuxStrategyOptions{} } else { - yyv27 := &x.SELinuxContext + yyv27 := &x.SELinux yyv27.CodecDecodeSelf(d) } yyj16++ @@ -17220,7 +16216,7 @@ func (x *FSType) CodecDecodeSelf(d *codec1978.Decoder) { } } -func (x *SELinuxContextStrategyOptions) CodecEncodeSelf(e *codec1978.Encoder) { +func (x *SELinuxStrategyOptions) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) _, _, _ = h, z, r @@ -17253,12 +16249,12 @@ func (x *SELinuxContextStrategyOptions) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - x.Type.CodecEncodeSelf(e) + x.Rule.CodecEncodeSelf(e) } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("type")) + r.EncodeString(codecSelferC_UTF81234, string("rule")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - x.Type.CodecEncodeSelf(e) + x.Rule.CodecEncodeSelf(e) } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) @@ -17292,7 +16288,7 @@ func (x *SELinuxContextStrategyOptions) CodecEncodeSelf(e *codec1978.Encoder) { } } -func (x *SELinuxContextStrategyOptions) CodecDecodeSelf(d *codec1978.Decoder) { +func (x *SELinuxStrategyOptions) CodecDecodeSelf(d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r @@ -17322,7 +16318,7 @@ func (x *SELinuxContextStrategyOptions) CodecDecodeSelf(d *codec1978.Decoder) { } } -func (x *SELinuxContextStrategyOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { +func (x *SELinuxStrategyOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r @@ -17344,11 +16340,11 @@ func (x *SELinuxContextStrategyOptions) codecDecodeSelfFromMap(l int, d *codec19 yys3 := string(yys3Slc) z.DecSendContainerState(codecSelfer_containerMapValue1234) switch yys3 { - case "type": + case "rule": if r.TryDecodeAsNil() { - x.Type = "" + x.Rule = "" } else { - x.Type = SELinuxContextStrategy(r.DecodeString()) + x.Rule = SELinuxStrategy(r.DecodeString()) } case "seLinuxOptions": if r.TryDecodeAsNil() { @@ -17368,7 +16364,7 @@ func (x *SELinuxContextStrategyOptions) codecDecodeSelfFromMap(l int, d *codec19 z.DecSendContainerState(codecSelfer_containerMapEnd1234) } -func (x *SELinuxContextStrategyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { +func (x *SELinuxStrategyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r @@ -17387,9 +16383,9 @@ func (x *SELinuxContextStrategyOptions) codecDecodeSelfFromArray(l int, d *codec } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.Type = "" + x.Rule = "" } else { - x.Type = SELinuxContextStrategy(r.DecodeString()) + x.Rule = SELinuxStrategy(r.DecodeString()) } yyj6++ if yyhl6 { @@ -17428,7 +16424,7 @@ func (x *SELinuxContextStrategyOptions) codecDecodeSelfFromArray(l int, d *codec z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } -func (x SELinuxContextStrategy) CodecEncodeSelf(e *codec1978.Encoder) { +func (x SELinuxStrategy) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) _, _, _ = h, z, r @@ -17441,7 +16437,7 @@ func (x SELinuxContextStrategy) CodecEncodeSelf(e *codec1978.Encoder) { } } -func (x *SELinuxContextStrategy) CodecDecodeSelf(d *codec1978.Decoder) { +func (x *SELinuxStrategy) CodecDecodeSelf(d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r @@ -17487,12 +16483,12 @@ func (x *RunAsUserStrategyOptions) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - x.Type.CodecEncodeSelf(e) + x.Rule.CodecEncodeSelf(e) } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("type")) + r.EncodeString(codecSelferC_UTF81234, string("rule")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - x.Type.CodecEncodeSelf(e) + x.Rule.CodecEncodeSelf(e) } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) @@ -17588,11 +16584,11 @@ func (x *RunAsUserStrategyOptions) codecDecodeSelfFromMap(l int, d *codec1978.De yys3 := string(yys3Slc) z.DecSendContainerState(codecSelfer_containerMapValue1234) switch yys3 { - case "type": + case "rule": if r.TryDecodeAsNil() { - x.Type = "" + x.Rule = "" } else { - x.Type = RunAsUserStrategy(r.DecodeString()) + x.Rule = RunAsUserStrategy(r.DecodeString()) } case "ranges": if r.TryDecodeAsNil() { @@ -17632,9 +16628,9 @@ func (x *RunAsUserStrategyOptions) codecDecodeSelfFromArray(l int, d *codec1978. } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.Type = "" + x.Rule = "" } else { - x.Type = RunAsUserStrategy(r.DecodeString()) + x.Rule = RunAsUserStrategy(r.DecodeString()) } yyj7++ if yyhl7 { @@ -18643,7 +17639,7 @@ func (x codecSelfer1234) decSliceAPIVersion(v *[]APIVersion, d *codec1978.Decode yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 32) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 16) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] @@ -18881,7 +17877,7 @@ func (x codecSelfer1234) decSliceDeployment(v *[]Deployment, d *codec1978.Decode yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 624) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 632) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] @@ -19238,7 +18234,7 @@ func (x codecSelfer1234) decSliceJob(v *[]Job, d *codec1978.Decoder) { yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 616) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 624) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] @@ -19913,244 +18909,6 @@ func (x codecSelfer1234) decSliceHTTPIngressPath(v *[]HTTPIngressPath, d *codec1 } } -func (x codecSelfer1234) encSliceNodeUtilization(v []NodeUtilization, e *codec1978.Encoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperEncoder(e) - _, _, _ = h, z, r - r.EncodeArrayStart(len(v)) - for _, yyv1 := range v { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy2 := &yyv1 - yy2.CodecEncodeSelf(e) - } - z.EncSendContainerState(codecSelfer_containerArrayEnd1234) -} - -func (x codecSelfer1234) decSliceNodeUtilization(v *[]NodeUtilization, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - - yyv1 := *v - yyh1, yyl1 := z.DecSliceHelperStart() - var yyc1 bool - _ = yyc1 - if yyl1 == 0 { - if yyv1 == nil { - yyv1 = []NodeUtilization{} - yyc1 = true - } else if len(yyv1) != 0 { - yyv1 = yyv1[:0] - yyc1 = true - } - } else if yyl1 > 0 { - var yyrr1, yyrl1 int - var yyrt1 bool - _, _ = yyrl1, yyrt1 - yyrr1 = yyl1 // len(yyv1) - if yyl1 > cap(yyv1) { - - yyrg1 := len(yyv1) > 0 - yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 24) - if yyrt1 { - if yyrl1 <= cap(yyv1) { - yyv1 = yyv1[:yyrl1] - } else { - yyv1 = make([]NodeUtilization, yyrl1) - } - } else { - yyv1 = make([]NodeUtilization, yyrl1) - } - yyc1 = true - yyrr1 = len(yyv1) - if yyrg1 { - copy(yyv1, yyv21) - } - } else if yyl1 != len(yyv1) { - yyv1 = yyv1[:yyl1] - yyc1 = true - } - yyj1 := 0 - for ; yyj1 < yyrr1; yyj1++ { - yyh1.ElemContainerState(yyj1) - if r.TryDecodeAsNil() { - yyv1[yyj1] = NodeUtilization{} - } else { - yyv2 := &yyv1[yyj1] - yyv2.CodecDecodeSelf(d) - } - - } - if yyrt1 { - for ; yyj1 < yyl1; yyj1++ { - yyv1 = append(yyv1, NodeUtilization{}) - yyh1.ElemContainerState(yyj1) - if r.TryDecodeAsNil() { - yyv1[yyj1] = NodeUtilization{} - } else { - yyv3 := &yyv1[yyj1] - yyv3.CodecDecodeSelf(d) - } - - } - } - - } else { - yyj1 := 0 - for ; !r.CheckBreak(); yyj1++ { - - if yyj1 >= len(yyv1) { - yyv1 = append(yyv1, NodeUtilization{}) // var yyz1 NodeUtilization - yyc1 = true - } - yyh1.ElemContainerState(yyj1) - if yyj1 < len(yyv1) { - if r.TryDecodeAsNil() { - yyv1[yyj1] = NodeUtilization{} - } else { - yyv4 := &yyv1[yyj1] - yyv4.CodecDecodeSelf(d) - } - - } else { - z.DecSwallow() - } - - } - if yyj1 < len(yyv1) { - yyv1 = yyv1[:yyj1] - yyc1 = true - } else if yyj1 == 0 && yyv1 == nil { - yyv1 = []NodeUtilization{} - yyc1 = true - } - } - yyh1.End() - if yyc1 { - *v = yyv1 - } -} - -func (x codecSelfer1234) encSliceClusterAutoscaler(v []ClusterAutoscaler, e *codec1978.Encoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperEncoder(e) - _, _, _ = h, z, r - r.EncodeArrayStart(len(v)) - for _, yyv1 := range v { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy2 := &yyv1 - yy2.CodecEncodeSelf(e) - } - z.EncSendContainerState(codecSelfer_containerArrayEnd1234) -} - -func (x codecSelfer1234) decSliceClusterAutoscaler(v *[]ClusterAutoscaler, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - - yyv1 := *v - yyh1, yyl1 := z.DecSliceHelperStart() - var yyc1 bool - _ = yyc1 - if yyl1 == 0 { - if yyv1 == nil { - yyv1 = []ClusterAutoscaler{} - yyc1 = true - } else if len(yyv1) != 0 { - yyv1 = yyv1[:0] - yyc1 = true - } - } else if yyl1 > 0 { - var yyrr1, yyrl1 int - var yyrt1 bool - _, _ = yyrl1, yyrt1 - yyrr1 = yyl1 // len(yyv1) - if yyl1 > cap(yyv1) { - - yyrg1 := len(yyv1) > 0 - yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 232) - if yyrt1 { - if yyrl1 <= cap(yyv1) { - yyv1 = yyv1[:yyrl1] - } else { - yyv1 = make([]ClusterAutoscaler, yyrl1) - } - } else { - yyv1 = make([]ClusterAutoscaler, yyrl1) - } - yyc1 = true - yyrr1 = len(yyv1) - if yyrg1 { - copy(yyv1, yyv21) - } - } else if yyl1 != len(yyv1) { - yyv1 = yyv1[:yyl1] - yyc1 = true - } - yyj1 := 0 - for ; yyj1 < yyrr1; yyj1++ { - yyh1.ElemContainerState(yyj1) - if r.TryDecodeAsNil() { - yyv1[yyj1] = ClusterAutoscaler{} - } else { - yyv2 := &yyv1[yyj1] - yyv2.CodecDecodeSelf(d) - } - - } - if yyrt1 { - for ; yyj1 < yyl1; yyj1++ { - yyv1 = append(yyv1, ClusterAutoscaler{}) - yyh1.ElemContainerState(yyj1) - if r.TryDecodeAsNil() { - yyv1[yyj1] = ClusterAutoscaler{} - } else { - yyv3 := &yyv1[yyj1] - yyv3.CodecDecodeSelf(d) - } - - } - } - - } else { - yyj1 := 0 - for ; !r.CheckBreak(); yyj1++ { - - if yyj1 >= len(yyv1) { - yyv1 = append(yyv1, ClusterAutoscaler{}) // var yyz1 ClusterAutoscaler - yyc1 = true - } - yyh1.ElemContainerState(yyj1) - if yyj1 < len(yyv1) { - if r.TryDecodeAsNil() { - yyv1[yyj1] = ClusterAutoscaler{} - } else { - yyv4 := &yyv1[yyj1] - yyv4.CodecDecodeSelf(d) - } - - } else { - z.DecSwallow() - } - - } - if yyj1 < len(yyv1) { - yyv1 = yyv1[:yyj1] - yyc1 = true - } else if yyj1 == 0 && yyv1 == nil { - yyv1 = []ClusterAutoscaler{} - yyc1 = true - } - } - yyh1.End() - if yyc1 { - *v = yyv1 - } -} - func (x codecSelfer1234) encSliceReplicaSet(v []ReplicaSet, e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -20190,7 +18948,7 @@ func (x codecSelfer1234) decSliceReplicaSet(v *[]ReplicaSet, d *codec1978.Decode yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 232) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 560) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/types.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/types.go index 4bfbef8d0..edcc93988 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/types.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/types.go @@ -46,8 +46,9 @@ type ScaleStatus struct { // actual number of observed instances of the scaled object. Replicas int `json:"replicas"` - // label query over pods that should match the replicas count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors - Selector map[string]string `json:"selector,omitempty"` + // label query over pods that should match the replicas count. + // More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors + Selector *unversioned.LabelSelector `json:"selector,omitempty"` } // +genclient=true,noMethods=true @@ -200,9 +201,6 @@ type ThirdPartyResourceList struct { type APIVersion struct { // Name of this version (e.g. 'v1'). Name string `json:"name,omitempty"` - - // The API group to add this object into, default 'experimental'. - APIGroup string `json:"apiGroup,omitempty"` } // An internal object, used for versioned storage in etcd. Not exposed to the end user. @@ -332,6 +330,9 @@ type RollingUpdateDeployment struct { } type DeploymentStatus struct { + // The generation observed by the deployment controller. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + // Total number of non-terminated pods targeted by this deployment (their labels match the selector). Replicas int `json:"replicas,omitempty"` @@ -532,7 +533,10 @@ type JobSpec struct { Parallelism *int `json:"parallelism,omitempty"` // Completions specifies the desired number of successfully finished pods the - // job should be run with. When unset, any pod exiting signals the job to complete. + // job should be run with. Setting to nil means that the success of any + // pod signals the success of all pods, and allows parallelism to have any positive + // value. Setting to 1 means that parallelism is limited to 1 and the success of that + // pod signals the success of the job. Completions *int `json:"completions,omitempty"` // Optional duration in seconds relative to the startTime that the job may be active @@ -540,8 +544,20 @@ type JobSpec struct { ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"` // Selector is a label query over pods that should match the pod count. + // Normally, the system sets this field for you. Selector *unversioned.LabelSelector `json:"selector,omitempty"` + // ManualSelector controls generation of pod labels and pod selectors. + // Leave `manualSelector` unset unless you are certain what you are doing. + // When false or unset, the system pick labels unique to this job + // and appends those labels to the pod template. When true, + // the user is responsible for picking unique labels and specifying + // the selector. Failure to pick a unique label may cause this + // and other jobs to not function correctly. However, You may see + // `manualSelector=true` in jobs that were created with the old `extensions/v1beta1` + // API. + ManualSelector *bool `json:"manualSelector,omitempty"` + // Template is the object that describes the pod that will be created when // executing a job. Template api.PodTemplateSpec `json:"template"` @@ -639,10 +655,11 @@ type IngressSpec struct { // specify a global default. Backend *IngressBackend `json:"backend,omitempty"` - // TLS is the TLS configuration. Currently the Ingress only supports a single TLS - // port, 443, and assumes TLS termination. If multiple members of this - // list specify different hosts, they will be multiplexed on the same - // port according to the hostname specified through the SNI TLS extension. + // TLS configuration. Currently the Ingress only supports a single TLS + // port, 443. If multiple members of this list specify different hosts, they + // will be multiplexed on the same port according to the hostname specified + // through the SNI TLS extension, if the ingress controller fulfilling the + // ingress supports SNI. TLS []IngressTLS `json:"tls,omitempty"` // A list of host rules used to configure the Ingress. If unspecified, or @@ -750,67 +767,6 @@ type IngressBackend struct { ServicePort intstr.IntOrString `json:"servicePort"` } -type NodeResource string - -const ( - // Percentage of node's CPUs that is currently used. - CpuConsumption NodeResource = "CpuConsumption" - - // Percentage of node's CPUs that is currently requested for pods. - CpuRequest NodeResource = "CpuRequest" - - // Percentage od node's memory that is currently used. - MemConsumption NodeResource = "MemConsumption" - - // Percentage of node's CPUs that is currently requested for pods. - MemRequest NodeResource = "MemRequest" -) - -// NodeUtilization describes what percentage of a particular resource is used on a node. -type NodeUtilization struct { - Resource NodeResource `json:"resource"` - - // The accepted values are from 0 to 1. - Value float64 `json:"value"` -} - -// Configuration of the Cluster Autoscaler -type ClusterAutoscalerSpec struct { - // Minimum number of nodes that the cluster should have. - MinNodes int `json:"minNodes"` - - // Maximum number of nodes that the cluster should have. - MaxNodes int `json:"maxNodes"` - - // Target average utilization of the cluster nodes. New nodes will be added if one of the - // targets is exceeded. Cluster size will be decreased if the current utilization is too low - // for all targets. - TargetUtilization []NodeUtilization `json:"target"` -} - -type ClusterAutoscaler struct { - unversioned.TypeMeta `json:",inline"` - - // Standard object's metadata. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata - // For now (experimental api) it is required that the name is set to "ClusterAutoscaler" and namespace is "default". - api.ObjectMeta `json:"metadata,omitempty"` - - // Spec defines the desired behavior of this daemon set. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status - Spec ClusterAutoscalerSpec `json:"spec,omitempty"` -} - -// There will be just one (or none) ClusterAutoscaler. -type ClusterAutoscalerList struct { - unversioned.TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata - unversioned.ListMeta `json:"metadata,omitempty"` - - Items []ClusterAutoscaler `json:"items"` -} - // +genclient=true // ReplicaSet represents the configuration of a replica set. @@ -849,7 +805,7 @@ type ReplicaSetSpec struct { // Template is the object that describes the pod that will be created if // insufficient replicas are detected. - Template *api.PodTemplateSpec `json:"template,omitempty"` + Template api.PodTemplateSpec `json:"template,omitempty"` } // ReplicaSetStatus represents the current status of a ReplicaSet. @@ -857,6 +813,9 @@ type ReplicaSetStatus struct { // Replicas is the number of actual replicas. Replicas int `json:"replicas"` + // The number of pods that have labels matching the labels of the pod template of the replicaset. + FullyLabeledReplicas int `json:"fullyLabeledReplicas,omitempty"` + // ObservedGeneration is the most recent generation observed by the controller. ObservedGeneration int64 `json:"observedGeneration,omitempty"` } @@ -888,8 +847,8 @@ type PodSecurityPolicySpec struct { HostPID bool `json:"hostPID,omitempty"` // HostIPC determines if the policy allows the use of HostIPC in the pod spec. HostIPC bool `json:"hostIPC,omitempty"` - // SELinuxContext is the strategy that will dictate the allowable labels that may be set. - SELinuxContext SELinuxContextStrategyOptions `json:"seLinuxContext,omitempty"` + // SELinux is the strategy that will dictate the allowable labels that may be set. + SELinux SELinuxStrategyOptions `json:"seLinux,omitempty"` // RunAsUser is the strategy that will dictate the allowable RunAsUser values that may be set. RunAsUser RunAsUserStrategyOptions `json:"runAsUser,omitempty"` } @@ -924,30 +883,30 @@ var ( FC FSType = "fc" ) -// SELinuxContextStrategyOptions defines the strategy type and any options used to create the strategy. -type SELinuxContextStrategyOptions struct { - // Type is the strategy that will dictate the allowable labels that may be set. - Type SELinuxContextStrategy `json:"type"` +// SELinuxStrategyOptions defines the strategy type and any options used to create the strategy. +type SELinuxStrategyOptions struct { + // Rule is the strategy that will dictate the allowable labels that may be set. + Rule SELinuxStrategy `json:"rule"` // seLinuxOptions required to run as; required for MustRunAs // More info: http://releases.k8s.io/HEAD/docs/design/security_context.md#security-context SELinuxOptions *api.SELinuxOptions `json:"seLinuxOptions,omitempty"` } -// SELinuxContextStrategyType denotes strategy types for generating SELinux options for a -// SecurityContext. -type SELinuxContextStrategy string +// SELinuxStrategy denotes strategy types for generating SELinux options for a +// Security. +type SELinuxStrategy string const ( // container must have SELinux labels of X applied. - SELinuxStrategyMustRunAs SELinuxContextStrategy = "MustRunAs" + SELinuxStrategyMustRunAs SELinuxStrategy = "MustRunAs" // container may make requests for any SELinux context labels. - SELinuxStrategyRunAsAny SELinuxContextStrategy = "RunAsAny" + SELinuxStrategyRunAsAny SELinuxStrategy = "RunAsAny" ) // RunAsUserStrategyOptions defines the strategy type and any options used to create the strategy. type RunAsUserStrategyOptions struct { - // Type is the strategy that will dictate the allowable RunAsUser values that may be set. - Type RunAsUserStrategy `json:"type"` + // Rule is the strategy that will dictate the allowable RunAsUser values that may be set. + Rule RunAsUserStrategy `json:"rule"` // Ranges are the allowed ranges of uids that may be used. Ranges []IDRange `json:"ranges,omitempty"` } @@ -960,7 +919,7 @@ type IDRange struct { Max int64 `json:"max"` } -// RunAsUserStrategyType denotes strategy types for generating RunAsUser values for a +// RunAsUserStrategy denotes strategy types for generating RunAsUser values for a // SecurityContext. type RunAsUserStrategy string diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion.go index 595091e02..5f4841cb4 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion.go @@ -34,6 +34,8 @@ func addConversionFuncs(scheme *runtime.Scheme) { err := scheme.AddConversionFuncs( Convert_api_PodSpec_To_v1_PodSpec, Convert_v1_PodSpec_To_api_PodSpec, + Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus, + Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus, Convert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec, Convert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec, Convert_extensions_DeploymentStrategy_To_v1beta1_DeploymentStrategy, @@ -42,6 +44,8 @@ func addConversionFuncs(scheme *runtime.Scheme) { Convert_v1beta1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment, Convert_extensions_ReplicaSetSpec_To_v1beta1_ReplicaSetSpec, Convert_v1beta1_ReplicaSetSpec_To_extensions_ReplicaSetSpec, + Convert_extensions_JobSpec_To_v1beta1_JobSpec, + Convert_v1beta1_JobSpec_To_extensions_JobSpec, ) if err != nil { // If one of the conversion functions is malformed, detect it immediately. @@ -91,6 +95,58 @@ func Convert_v1_PodSpec_To_api_PodSpec(in *v1.PodSpec, out *api.PodSpec, s conve return v1.Convert_v1_PodSpec_To_api_PodSpec(in, out, s) } +func Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus(in *extensions.ScaleStatus, out *ScaleStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*extensions.ScaleStatus))(in) + } + out.Replicas = int32(in.Replicas) + + out.Selector = nil + out.TargetSelector = "" + if in.Selector != nil { + if in.Selector.MatchExpressions == nil || len(in.Selector.MatchExpressions) == 0 { + out.Selector = in.Selector.MatchLabels + } + + selector, err := unversioned.LabelSelectorAsSelector(in.Selector) + if err != nil { + return fmt.Errorf("invalid label selector: %v", err) + } + out.TargetSelector = selector.String() + } + return nil +} + +func Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(in *ScaleStatus, out *extensions.ScaleStatus, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*ScaleStatus))(in) + } + out.Replicas = int(in.Replicas) + + // Normally when 2 fields map to the same internal value we favor the old field, since + // old clients can't be expected to know about new fields but clients that know about the + // new field can be expected to know about the old field (though that's not quite true, due + // to kubectl apply). However, these fields are readonly, so any non-nil value should work. + if in.TargetSelector != "" { + labelSelector, err := unversioned.ParseToLabelSelector(in.TargetSelector) + if err != nil { + out.Selector = nil + return fmt.Errorf("failed to parse target selector: %v", err) + } + out.Selector = labelSelector + } else if in.Selector != nil { + out.Selector = new(unversioned.LabelSelector) + selector := make(map[string]string) + for key, val := range in.Selector { + selector[key] = val + } + out.Selector.MatchLabels = selector + } else { + out.Selector = nil + } + return nil +} + func Convert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec(in *extensions.DeploymentSpec, out *DeploymentSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*extensions.DeploymentSpec))(in) @@ -241,13 +297,9 @@ func Convert_extensions_ReplicaSetSpec_To_v1beta1_ReplicaSetSpec(in *extensions. } else { out.Selector = nil } - if in.Template != nil { - out.Template = new(v1.PodTemplateSpec) - if err := v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in.Template, out.Template, s); err != nil { - return err - } - } else { - out.Template = nil + + if err := v1.Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err } return nil } @@ -267,13 +319,112 @@ func Convert_v1beta1_ReplicaSetSpec_To_extensions_ReplicaSetSpec(in *ReplicaSetS } else { out.Selector = nil } - if in.Template != nil { - out.Template = new(api.PodTemplateSpec) - if err := v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in.Template, out.Template, s); err != nil { + if err := v1.Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + return nil +} + +func Convert_extensions_JobSpec_To_v1beta1_JobSpec(in *extensions.JobSpec, out *JobSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*extensions.JobSpec))(in) + } + if in.Parallelism != nil { + out.Parallelism = new(int32) + *out.Parallelism = int32(*in.Parallelism) + } else { + out.Parallelism = nil + } + if in.Completions != nil { + out.Completions = new(int32) + *out.Completions = int32(*in.Completions) + } else { + out.Completions = nil + } + if in.ActiveDeadlineSeconds != nil { + out.ActiveDeadlineSeconds = new(int64) + *out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds + } else { + out.ActiveDeadlineSeconds = nil + } + // unable to generate simple pointer conversion for unversioned.LabelSelector -> v1beta1.LabelSelector + if in.Selector != nil { + out.Selector = new(LabelSelector) + if err := Convert_unversioned_LabelSelector_To_v1beta1_LabelSelector(in.Selector, out.Selector, s); err != nil { return err } + } else { + out.Selector = nil + } + + // BEGIN non-standard conversion + // autoSelector has opposite meaning as manualSelector. + // in both cases, unset means false, and unset is always preferred to false. + // unset vs set-false distinction is not preserved. + manualSelector := in.ManualSelector != nil && *in.ManualSelector + autoSelector := !manualSelector + if autoSelector { + out.AutoSelector = new(bool) + *out.AutoSelector = true + } else { + out.AutoSelector = nil + } + // END non-standard conversion + + if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err + } + return nil +} + +func Convert_v1beta1_JobSpec_To_extensions_JobSpec(in *JobSpec, out *extensions.JobSpec, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*JobSpec))(in) + } + if in.Parallelism != nil { + out.Parallelism = new(int) + *out.Parallelism = int(*in.Parallelism) + } else { + out.Parallelism = nil + } + if in.Completions != nil { + out.Completions = new(int) + *out.Completions = int(*in.Completions) + } else { + out.Completions = nil + } + if in.ActiveDeadlineSeconds != nil { + out.ActiveDeadlineSeconds = new(int64) + *out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds + } else { + out.ActiveDeadlineSeconds = nil + } + // unable to generate simple pointer conversion for v1beta1.LabelSelector -> unversioned.LabelSelector + if in.Selector != nil { + out.Selector = new(unversioned.LabelSelector) + if err := Convert_v1beta1_LabelSelector_To_unversioned_LabelSelector(in.Selector, out.Selector, s); err != nil { + return err + } + } else { + out.Selector = nil + } + + // BEGIN non-standard conversion + // autoSelector has opposite meaning as manualSelector. + // in both cases, unset means false, and unset is always preferred to false. + // unset vs set-false distinction is not preserved. + autoSelector := bool(in.AutoSelector != nil && *in.AutoSelector) + manualSelector := !autoSelector + if manualSelector { + out.ManualSelector = new(bool) + *out.ManualSelector = true } else { - out.Template = nil + out.ManualSelector = nil + } + // END non-standard conversion + + if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err } return nil } diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion_generated.go index f1c50ad2a..9d870935f 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion_generated.go @@ -2680,7 +2680,6 @@ func autoConvert_extensions_APIVersion_To_v1beta1_APIVersion(in *extensions.APIV defaulting.(func(*extensions.APIVersion))(in) } out.Name = in.Name - out.APIGroup = in.APIGroup return nil } @@ -2700,76 +2699,6 @@ func Convert_extensions_CPUTargetUtilization_To_v1beta1_CPUTargetUtilization(in return autoConvert_extensions_CPUTargetUtilization_To_v1beta1_CPUTargetUtilization(in, out, s) } -func autoConvert_extensions_ClusterAutoscaler_To_v1beta1_ClusterAutoscaler(in *extensions.ClusterAutoscaler, out *ClusterAutoscaler, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*extensions.ClusterAutoscaler))(in) - } - if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { - return err - } - if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_extensions_ClusterAutoscalerSpec_To_v1beta1_ClusterAutoscalerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -func Convert_extensions_ClusterAutoscaler_To_v1beta1_ClusterAutoscaler(in *extensions.ClusterAutoscaler, out *ClusterAutoscaler, s conversion.Scope) error { - return autoConvert_extensions_ClusterAutoscaler_To_v1beta1_ClusterAutoscaler(in, out, s) -} - -func autoConvert_extensions_ClusterAutoscalerList_To_v1beta1_ClusterAutoscalerList(in *extensions.ClusterAutoscalerList, out *ClusterAutoscalerList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*extensions.ClusterAutoscalerList))(in) - } - if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { - return err - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]ClusterAutoscaler, len(in.Items)) - for i := range in.Items { - if err := Convert_extensions_ClusterAutoscaler_To_v1beta1_ClusterAutoscaler(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_extensions_ClusterAutoscalerList_To_v1beta1_ClusterAutoscalerList(in *extensions.ClusterAutoscalerList, out *ClusterAutoscalerList, s conversion.Scope) error { - return autoConvert_extensions_ClusterAutoscalerList_To_v1beta1_ClusterAutoscalerList(in, out, s) -} - -func autoConvert_extensions_ClusterAutoscalerSpec_To_v1beta1_ClusterAutoscalerSpec(in *extensions.ClusterAutoscalerSpec, out *ClusterAutoscalerSpec, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*extensions.ClusterAutoscalerSpec))(in) - } - out.MinNodes = int32(in.MinNodes) - out.MaxNodes = int32(in.MaxNodes) - if in.TargetUtilization != nil { - out.TargetUtilization = make([]NodeUtilization, len(in.TargetUtilization)) - for i := range in.TargetUtilization { - if err := Convert_extensions_NodeUtilization_To_v1beta1_NodeUtilization(&in.TargetUtilization[i], &out.TargetUtilization[i], s); err != nil { - return err - } - } - } else { - out.TargetUtilization = nil - } - return nil -} - -func Convert_extensions_ClusterAutoscalerSpec_To_v1beta1_ClusterAutoscalerSpec(in *extensions.ClusterAutoscalerSpec, out *ClusterAutoscalerSpec, s conversion.Scope) error { - return autoConvert_extensions_ClusterAutoscalerSpec_To_v1beta1_ClusterAutoscalerSpec(in, out, s) -} - func autoConvert_extensions_DaemonSet_To_v1beta1_DaemonSet(in *extensions.DaemonSet, out *DaemonSet, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*extensions.DaemonSet))(in) @@ -2979,6 +2908,7 @@ func autoConvert_extensions_DeploymentStatus_To_v1beta1_DeploymentStatus(in *ext if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*extensions.DeploymentStatus))(in) } + out.ObservedGeneration = in.ObservedGeneration out.Replicas = int32(in.Replicas) out.UpdatedReplicas = int32(in.UpdatedReplicas) out.AvailableReplicas = int32(in.AvailableReplicas) @@ -3459,16 +3389,13 @@ func autoConvert_extensions_JobSpec_To_v1beta1_JobSpec(in *extensions.JobSpec, o } else { out.Selector = nil } + // in.ManualSelector has no peer in out if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } return nil } -func Convert_extensions_JobSpec_To_v1beta1_JobSpec(in *extensions.JobSpec, out *JobSpec, s conversion.Scope) error { - return autoConvert_extensions_JobSpec_To_v1beta1_JobSpec(in, out, s) -} - func autoConvert_extensions_JobStatus_To_v1beta1_JobStatus(in *extensions.JobStatus, out *JobStatus, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*extensions.JobStatus))(in) @@ -3511,19 +3438,6 @@ func Convert_extensions_JobStatus_To_v1beta1_JobStatus(in *extensions.JobStatus, return autoConvert_extensions_JobStatus_To_v1beta1_JobStatus(in, out, s) } -func autoConvert_extensions_NodeUtilization_To_v1beta1_NodeUtilization(in *extensions.NodeUtilization, out *NodeUtilization, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*extensions.NodeUtilization))(in) - } - out.Resource = NodeResource(in.Resource) - out.Value = in.Value - return nil -} - -func Convert_extensions_NodeUtilization_To_v1beta1_NodeUtilization(in *extensions.NodeUtilization, out *NodeUtilization, s conversion.Scope) error { - return autoConvert_extensions_NodeUtilization_To_v1beta1_NodeUtilization(in, out, s) -} - func autoConvert_extensions_PodSecurityPolicy_To_v1beta1_PodSecurityPolicy(in *extensions.PodSecurityPolicy, out *PodSecurityPolicy, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*extensions.PodSecurityPolicy))(in) @@ -3605,7 +3519,7 @@ func autoConvert_extensions_PodSecurityPolicySpec_To_v1beta1_PodSecurityPolicySp } out.HostPID = in.HostPID out.HostIPC = in.HostIPC - if err := Convert_extensions_SELinuxContextStrategyOptions_To_v1beta1_SELinuxContextStrategyOptions(&in.SELinuxContext, &out.SELinuxContext, s); err != nil { + if err := Convert_extensions_SELinuxStrategyOptions_To_v1beta1_SELinuxStrategyOptions(&in.SELinux, &out.SELinux, s); err != nil { return err } if err := Convert_extensions_RunAsUserStrategyOptions_To_v1beta1_RunAsUserStrategyOptions(&in.RunAsUser, &out.RunAsUser, s); err != nil { @@ -3684,14 +3598,8 @@ func autoConvert_extensions_ReplicaSetSpec_To_v1beta1_ReplicaSetSpec(in *extensi } else { out.Selector = nil } - // unable to generate simple pointer conversion for api.PodTemplateSpec -> v1.PodTemplateSpec - if in.Template != nil { - out.Template = new(v1.PodTemplateSpec) - if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(in.Template, out.Template, s); err != nil { - return err - } - } else { - out.Template = nil + if err := Convert_api_PodTemplateSpec_To_v1_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err } return nil } @@ -3701,6 +3609,7 @@ func autoConvert_extensions_ReplicaSetStatus_To_v1beta1_ReplicaSetStatus(in *ext defaulting.(func(*extensions.ReplicaSetStatus))(in) } out.Replicas = int32(in.Replicas) + out.FullyLabeledReplicas = int32(in.FullyLabeledReplicas) out.ObservedGeneration = in.ObservedGeneration return nil } @@ -3752,7 +3661,7 @@ func autoConvert_extensions_RunAsUserStrategyOptions_To_v1beta1_RunAsUserStrateg if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*extensions.RunAsUserStrategyOptions))(in) } - out.Type = RunAsUserStrategy(in.Type) + out.Rule = RunAsUserStrategy(in.Rule) if in.Ranges != nil { out.Ranges = make([]IDRange, len(in.Ranges)) for i := range in.Ranges { @@ -3770,11 +3679,11 @@ func Convert_extensions_RunAsUserStrategyOptions_To_v1beta1_RunAsUserStrategyOpt return autoConvert_extensions_RunAsUserStrategyOptions_To_v1beta1_RunAsUserStrategyOptions(in, out, s) } -func autoConvert_extensions_SELinuxContextStrategyOptions_To_v1beta1_SELinuxContextStrategyOptions(in *extensions.SELinuxContextStrategyOptions, out *SELinuxContextStrategyOptions, s conversion.Scope) error { +func autoConvert_extensions_SELinuxStrategyOptions_To_v1beta1_SELinuxStrategyOptions(in *extensions.SELinuxStrategyOptions, out *SELinuxStrategyOptions, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*extensions.SELinuxContextStrategyOptions))(in) + defaulting.(func(*extensions.SELinuxStrategyOptions))(in) } - out.Type = SELinuxContextStrategy(in.Type) + out.Rule = SELinuxStrategy(in.Rule) // unable to generate simple pointer conversion for api.SELinuxOptions -> v1.SELinuxOptions if in.SELinuxOptions != nil { out.SELinuxOptions = new(v1.SELinuxOptions) @@ -3787,8 +3696,8 @@ func autoConvert_extensions_SELinuxContextStrategyOptions_To_v1beta1_SELinuxCont return nil } -func Convert_extensions_SELinuxContextStrategyOptions_To_v1beta1_SELinuxContextStrategyOptions(in *extensions.SELinuxContextStrategyOptions, out *SELinuxContextStrategyOptions, s conversion.Scope) error { - return autoConvert_extensions_SELinuxContextStrategyOptions_To_v1beta1_SELinuxContextStrategyOptions(in, out, s) +func Convert_extensions_SELinuxStrategyOptions_To_v1beta1_SELinuxStrategyOptions(in *extensions.SELinuxStrategyOptions, out *SELinuxStrategyOptions, s conversion.Scope) error { + return autoConvert_extensions_SELinuxStrategyOptions_To_v1beta1_SELinuxStrategyOptions(in, out, s) } func autoConvert_extensions_Scale_To_v1beta1_Scale(in *extensions.Scale, out *Scale, s conversion.Scope) error { @@ -3831,21 +3740,10 @@ func autoConvert_extensions_ScaleStatus_To_v1beta1_ScaleStatus(in *extensions.Sc defaulting.(func(*extensions.ScaleStatus))(in) } out.Replicas = int32(in.Replicas) - if in.Selector != nil { - out.Selector = make(map[string]string) - for key, val := range in.Selector { - out.Selector[key] = val - } - } else { - out.Selector = nil - } + // in.Selector has no peer in out return nil } -func Convert_extensions_ScaleStatus_To_v1beta1_ScaleStatus(in *extensions.ScaleStatus, out *ScaleStatus, s conversion.Scope) error { - return autoConvert_extensions_ScaleStatus_To_v1beta1_ScaleStatus(in, out, s) -} - func autoConvert_extensions_SubresourceReference_To_v1beta1_SubresourceReference(in *extensions.SubresourceReference, out *SubresourceReference, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*extensions.SubresourceReference))(in) @@ -3899,7 +3797,7 @@ func autoConvert_extensions_ThirdPartyResourceData_To_v1beta1_ThirdPartyResource if err := Convert_api_ObjectMeta_To_v1_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } - if err := conversion.ByteSliceCopy(&in.Data, &out.Data, s); err != nil { + if err := conversion.Convert_Slice_byte_To_Slice_byte(&in.Data, &out.Data, s); err != nil { return err } return nil @@ -3968,7 +3866,6 @@ func autoConvert_v1beta1_APIVersion_To_extensions_APIVersion(in *APIVersion, out defaulting.(func(*APIVersion))(in) } out.Name = in.Name - out.APIGroup = in.APIGroup return nil } @@ -3988,76 +3885,6 @@ func Convert_v1beta1_CPUTargetUtilization_To_extensions_CPUTargetUtilization(in return autoConvert_v1beta1_CPUTargetUtilization_To_extensions_CPUTargetUtilization(in, out, s) } -func autoConvert_v1beta1_ClusterAutoscaler_To_extensions_ClusterAutoscaler(in *ClusterAutoscaler, out *extensions.ClusterAutoscaler, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*ClusterAutoscaler))(in) - } - if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { - return err - } - if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { - return err - } - if err := Convert_v1beta1_ClusterAutoscalerSpec_To_extensions_ClusterAutoscalerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -func Convert_v1beta1_ClusterAutoscaler_To_extensions_ClusterAutoscaler(in *ClusterAutoscaler, out *extensions.ClusterAutoscaler, s conversion.Scope) error { - return autoConvert_v1beta1_ClusterAutoscaler_To_extensions_ClusterAutoscaler(in, out, s) -} - -func autoConvert_v1beta1_ClusterAutoscalerList_To_extensions_ClusterAutoscalerList(in *ClusterAutoscalerList, out *extensions.ClusterAutoscalerList, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*ClusterAutoscalerList))(in) - } - if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil { - return err - } - if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]extensions.ClusterAutoscaler, len(in.Items)) - for i := range in.Items { - if err := Convert_v1beta1_ClusterAutoscaler_To_extensions_ClusterAutoscaler(&in.Items[i], &out.Items[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func Convert_v1beta1_ClusterAutoscalerList_To_extensions_ClusterAutoscalerList(in *ClusterAutoscalerList, out *extensions.ClusterAutoscalerList, s conversion.Scope) error { - return autoConvert_v1beta1_ClusterAutoscalerList_To_extensions_ClusterAutoscalerList(in, out, s) -} - -func autoConvert_v1beta1_ClusterAutoscalerSpec_To_extensions_ClusterAutoscalerSpec(in *ClusterAutoscalerSpec, out *extensions.ClusterAutoscalerSpec, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*ClusterAutoscalerSpec))(in) - } - out.MinNodes = int(in.MinNodes) - out.MaxNodes = int(in.MaxNodes) - if in.TargetUtilization != nil { - out.TargetUtilization = make([]extensions.NodeUtilization, len(in.TargetUtilization)) - for i := range in.TargetUtilization { - if err := Convert_v1beta1_NodeUtilization_To_extensions_NodeUtilization(&in.TargetUtilization[i], &out.TargetUtilization[i], s); err != nil { - return err - } - } - } else { - out.TargetUtilization = nil - } - return nil -} - -func Convert_v1beta1_ClusterAutoscalerSpec_To_extensions_ClusterAutoscalerSpec(in *ClusterAutoscalerSpec, out *extensions.ClusterAutoscalerSpec, s conversion.Scope) error { - return autoConvert_v1beta1_ClusterAutoscalerSpec_To_extensions_ClusterAutoscalerSpec(in, out, s) -} - func autoConvert_v1beta1_DaemonSet_To_extensions_DaemonSet(in *DaemonSet, out *extensions.DaemonSet, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*DaemonSet))(in) @@ -4265,6 +4092,7 @@ func autoConvert_v1beta1_DeploymentStatus_To_extensions_DeploymentStatus(in *Dep if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*DeploymentStatus))(in) } + out.ObservedGeneration = in.ObservedGeneration out.Replicas = int(in.Replicas) out.UpdatedReplicas = int(in.UpdatedReplicas) out.AvailableReplicas = int(in.AvailableReplicas) @@ -4728,16 +4556,13 @@ func autoConvert_v1beta1_JobSpec_To_extensions_JobSpec(in *JobSpec, out *extensi } else { out.Selector = nil } + // in.AutoSelector has no peer in out if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { return err } return nil } -func Convert_v1beta1_JobSpec_To_extensions_JobSpec(in *JobSpec, out *extensions.JobSpec, s conversion.Scope) error { - return autoConvert_v1beta1_JobSpec_To_extensions_JobSpec(in, out, s) -} - func autoConvert_v1beta1_JobStatus_To_extensions_JobStatus(in *JobStatus, out *extensions.JobStatus, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*JobStatus))(in) @@ -4858,19 +4683,6 @@ func Convert_v1beta1_ListOptions_To_api_ListOptions(in *ListOptions, out *api.Li return autoConvert_v1beta1_ListOptions_To_api_ListOptions(in, out, s) } -func autoConvert_v1beta1_NodeUtilization_To_extensions_NodeUtilization(in *NodeUtilization, out *extensions.NodeUtilization, s conversion.Scope) error { - if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*NodeUtilization))(in) - } - out.Resource = extensions.NodeResource(in.Resource) - out.Value = in.Value - return nil -} - -func Convert_v1beta1_NodeUtilization_To_extensions_NodeUtilization(in *NodeUtilization, out *extensions.NodeUtilization, s conversion.Scope) error { - return autoConvert_v1beta1_NodeUtilization_To_extensions_NodeUtilization(in, out, s) -} - func autoConvert_v1beta1_PodSecurityPolicy_To_extensions_PodSecurityPolicy(in *PodSecurityPolicy, out *extensions.PodSecurityPolicy, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*PodSecurityPolicy))(in) @@ -4952,7 +4764,7 @@ func autoConvert_v1beta1_PodSecurityPolicySpec_To_extensions_PodSecurityPolicySp } out.HostPID = in.HostPID out.HostIPC = in.HostIPC - if err := Convert_v1beta1_SELinuxContextStrategyOptions_To_extensions_SELinuxContextStrategyOptions(&in.SELinuxContext, &out.SELinuxContext, s); err != nil { + if err := Convert_v1beta1_SELinuxStrategyOptions_To_extensions_SELinuxStrategyOptions(&in.SELinux, &out.SELinux, s); err != nil { return err } if err := Convert_v1beta1_RunAsUserStrategyOptions_To_extensions_RunAsUserStrategyOptions(&in.RunAsUser, &out.RunAsUser, s); err != nil { @@ -5029,14 +4841,8 @@ func autoConvert_v1beta1_ReplicaSetSpec_To_extensions_ReplicaSetSpec(in *Replica } else { out.Selector = nil } - // unable to generate simple pointer conversion for v1.PodTemplateSpec -> api.PodTemplateSpec - if in.Template != nil { - out.Template = new(api.PodTemplateSpec) - if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(in.Template, out.Template, s); err != nil { - return err - } - } else { - out.Template = nil + if err := Convert_v1_PodTemplateSpec_To_api_PodTemplateSpec(&in.Template, &out.Template, s); err != nil { + return err } return nil } @@ -5046,6 +4852,7 @@ func autoConvert_v1beta1_ReplicaSetStatus_To_extensions_ReplicaSetStatus(in *Rep defaulting.(func(*ReplicaSetStatus))(in) } out.Replicas = int(in.Replicas) + out.FullyLabeledReplicas = int(in.FullyLabeledReplicas) out.ObservedGeneration = in.ObservedGeneration return nil } @@ -5093,7 +4900,7 @@ func autoConvert_v1beta1_RunAsUserStrategyOptions_To_extensions_RunAsUserStrateg if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*RunAsUserStrategyOptions))(in) } - out.Type = extensions.RunAsUserStrategy(in.Type) + out.Rule = extensions.RunAsUserStrategy(in.Rule) if in.Ranges != nil { out.Ranges = make([]extensions.IDRange, len(in.Ranges)) for i := range in.Ranges { @@ -5111,11 +4918,11 @@ func Convert_v1beta1_RunAsUserStrategyOptions_To_extensions_RunAsUserStrategyOpt return autoConvert_v1beta1_RunAsUserStrategyOptions_To_extensions_RunAsUserStrategyOptions(in, out, s) } -func autoConvert_v1beta1_SELinuxContextStrategyOptions_To_extensions_SELinuxContextStrategyOptions(in *SELinuxContextStrategyOptions, out *extensions.SELinuxContextStrategyOptions, s conversion.Scope) error { +func autoConvert_v1beta1_SELinuxStrategyOptions_To_extensions_SELinuxStrategyOptions(in *SELinuxStrategyOptions, out *extensions.SELinuxStrategyOptions, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { - defaulting.(func(*SELinuxContextStrategyOptions))(in) + defaulting.(func(*SELinuxStrategyOptions))(in) } - out.Type = extensions.SELinuxContextStrategy(in.Type) + out.Rule = extensions.SELinuxStrategy(in.Rule) // unable to generate simple pointer conversion for v1.SELinuxOptions -> api.SELinuxOptions if in.SELinuxOptions != nil { out.SELinuxOptions = new(api.SELinuxOptions) @@ -5128,8 +4935,8 @@ func autoConvert_v1beta1_SELinuxContextStrategyOptions_To_extensions_SELinuxCont return nil } -func Convert_v1beta1_SELinuxContextStrategyOptions_To_extensions_SELinuxContextStrategyOptions(in *SELinuxContextStrategyOptions, out *extensions.SELinuxContextStrategyOptions, s conversion.Scope) error { - return autoConvert_v1beta1_SELinuxContextStrategyOptions_To_extensions_SELinuxContextStrategyOptions(in, out, s) +func Convert_v1beta1_SELinuxStrategyOptions_To_extensions_SELinuxStrategyOptions(in *SELinuxStrategyOptions, out *extensions.SELinuxStrategyOptions, s conversion.Scope) error { + return autoConvert_v1beta1_SELinuxStrategyOptions_To_extensions_SELinuxStrategyOptions(in, out, s) } func autoConvert_v1beta1_Scale_To_extensions_Scale(in *Scale, out *extensions.Scale, s conversion.Scope) error { @@ -5172,21 +4979,11 @@ func autoConvert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(in *ScaleStatus, defaulting.(func(*ScaleStatus))(in) } out.Replicas = int(in.Replicas) - if in.Selector != nil { - out.Selector = make(map[string]string) - for key, val := range in.Selector { - out.Selector[key] = val - } - } else { - out.Selector = nil - } + // in.Selector has no peer in out + // in.TargetSelector has no peer in out return nil } -func Convert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(in *ScaleStatus, out *extensions.ScaleStatus, s conversion.Scope) error { - return autoConvert_v1beta1_ScaleStatus_To_extensions_ScaleStatus(in, out, s) -} - func autoConvert_v1beta1_SubresourceReference_To_extensions_SubresourceReference(in *SubresourceReference, out *extensions.SubresourceReference, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*SubresourceReference))(in) @@ -5240,7 +5037,7 @@ func autoConvert_v1beta1_ThirdPartyResourceData_To_extensions_ThirdPartyResource if err := Convert_v1_ObjectMeta_To_api_ObjectMeta(&in.ObjectMeta, &out.ObjectMeta, s); err != nil { return err } - if err := conversion.ByteSliceCopy(&in.Data, &out.Data, s); err != nil { + if err := conversion.Convert_Slice_byte_To_Slice_byte(&in.Data, &out.Data, s); err != nil { return err } return nil @@ -5357,9 +5154,6 @@ func init() { autoConvert_api_Volume_To_v1_Volume, autoConvert_extensions_APIVersion_To_v1beta1_APIVersion, autoConvert_extensions_CPUTargetUtilization_To_v1beta1_CPUTargetUtilization, - autoConvert_extensions_ClusterAutoscalerList_To_v1beta1_ClusterAutoscalerList, - autoConvert_extensions_ClusterAutoscalerSpec_To_v1beta1_ClusterAutoscalerSpec, - autoConvert_extensions_ClusterAutoscaler_To_v1beta1_ClusterAutoscaler, autoConvert_extensions_DaemonSetList_To_v1beta1_DaemonSetList, autoConvert_extensions_DaemonSetSpec_To_v1beta1_DaemonSetSpec, autoConvert_extensions_DaemonSetStatus_To_v1beta1_DaemonSetStatus, @@ -5391,7 +5185,6 @@ func init() { autoConvert_extensions_JobSpec_To_v1beta1_JobSpec, autoConvert_extensions_JobStatus_To_v1beta1_JobStatus, autoConvert_extensions_Job_To_v1beta1_Job, - autoConvert_extensions_NodeUtilization_To_v1beta1_NodeUtilization, autoConvert_extensions_PodSecurityPolicyList_To_v1beta1_PodSecurityPolicyList, autoConvert_extensions_PodSecurityPolicySpec_To_v1beta1_PodSecurityPolicySpec, autoConvert_extensions_PodSecurityPolicy_To_v1beta1_PodSecurityPolicy, @@ -5403,7 +5196,7 @@ func init() { autoConvert_extensions_RollbackConfig_To_v1beta1_RollbackConfig, autoConvert_extensions_RollingUpdateDeployment_To_v1beta1_RollingUpdateDeployment, autoConvert_extensions_RunAsUserStrategyOptions_To_v1beta1_RunAsUserStrategyOptions, - autoConvert_extensions_SELinuxContextStrategyOptions_To_v1beta1_SELinuxContextStrategyOptions, + autoConvert_extensions_SELinuxStrategyOptions_To_v1beta1_SELinuxStrategyOptions, autoConvert_extensions_ScaleSpec_To_v1beta1_ScaleSpec, autoConvert_extensions_ScaleStatus_To_v1beta1_ScaleStatus, autoConvert_extensions_Scale_To_v1beta1_Scale, @@ -5464,9 +5257,6 @@ func init() { autoConvert_v1_Volume_To_api_Volume, autoConvert_v1beta1_APIVersion_To_extensions_APIVersion, autoConvert_v1beta1_CPUTargetUtilization_To_extensions_CPUTargetUtilization, - autoConvert_v1beta1_ClusterAutoscalerList_To_extensions_ClusterAutoscalerList, - autoConvert_v1beta1_ClusterAutoscalerSpec_To_extensions_ClusterAutoscalerSpec, - autoConvert_v1beta1_ClusterAutoscaler_To_extensions_ClusterAutoscaler, autoConvert_v1beta1_DaemonSetList_To_extensions_DaemonSetList, autoConvert_v1beta1_DaemonSetSpec_To_extensions_DaemonSetSpec, autoConvert_v1beta1_DaemonSetStatus_To_extensions_DaemonSetStatus, @@ -5500,7 +5290,6 @@ func init() { autoConvert_v1beta1_LabelSelectorRequirement_To_unversioned_LabelSelectorRequirement, autoConvert_v1beta1_LabelSelector_To_unversioned_LabelSelector, autoConvert_v1beta1_ListOptions_To_api_ListOptions, - autoConvert_v1beta1_NodeUtilization_To_extensions_NodeUtilization, autoConvert_v1beta1_PodSecurityPolicyList_To_extensions_PodSecurityPolicyList, autoConvert_v1beta1_PodSecurityPolicySpec_To_extensions_PodSecurityPolicySpec, autoConvert_v1beta1_PodSecurityPolicy_To_extensions_PodSecurityPolicy, @@ -5512,7 +5301,7 @@ func init() { autoConvert_v1beta1_RollbackConfig_To_extensions_RollbackConfig, autoConvert_v1beta1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment, autoConvert_v1beta1_RunAsUserStrategyOptions_To_extensions_RunAsUserStrategyOptions, - autoConvert_v1beta1_SELinuxContextStrategyOptions_To_extensions_SELinuxContextStrategyOptions, + autoConvert_v1beta1_SELinuxStrategyOptions_To_extensions_SELinuxStrategyOptions, autoConvert_v1beta1_ScaleSpec_To_extensions_ScaleSpec, autoConvert_v1beta1_ScaleStatus_To_extensions_ScaleStatus, autoConvert_v1beta1_Scale_To_extensions_Scale, diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion_test.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion_test.go new file mode 100644 index 000000000..a0e43dfbc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/conversion_test.go @@ -0,0 +1,83 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1_test + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + versioned "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" +) + +// TestJobSpecConversion tests that ManualSelector and AutoSelector +// are handled correctly. +func TestJobSpecConversion(t *testing.T) { + pTrue := new(bool) + *pTrue = true + pFalse := new(bool) + *pFalse = false + + // False or nil convert to true. + // True converts to nil. + tests := []struct { + in *bool + expectOut *bool + }{ + { + in: nil, + expectOut: pTrue, + }, + { + in: pFalse, + expectOut: pTrue, + }, + { + in: pTrue, + expectOut: nil, + }, + } + + // Test internal -> v1beta1. + for _, test := range tests { + i := &extensions.JobSpec{ + ManualSelector: test.in, + } + v := versioned.JobSpec{} + if err := api.Scheme.Convert(i, &v); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !reflect.DeepEqual(test.expectOut, v.AutoSelector) { + t.Fatalf("want v1beta1.AutoSelector %v, got %v", test.expectOut, v.AutoSelector) + } + } + + // Test v1beta1 -> internal. + for _, test := range tests { + i := &versioned.JobSpec{ + AutoSelector: test.in, + } + e := extensions.JobSpec{} + if err := api.Scheme.Convert(i, &e); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !reflect.DeepEqual(test.expectOut, e.ManualSelector) { + t.Fatalf("want extensions.ManualSelector %v, got %v", test.expectOut, e.ManualSelector) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/deep_copy_generated.go index d1ba9f6de..4ffbb0889 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/deep_copy_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/deep_copy_generated.go @@ -1,5 +1,7 @@ +// +build !ignore_autogenerated + /* -Copyright 2015 The Kubernetes Authors All rights reserved. +Copyright 2016 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,1046 +16,112 @@ See the License for the specific language governing permissions and limitations under the License. */ -// DO NOT EDIT. THIS FILE IS AUTO-GENERATED BY $KUBEROOT/hack/update-generated-deep-copies.sh. +// This file was autogenerated by deepcopy-gen. Do not edit it manually! package v1beta1 import ( - time "time" - api "k8s.io/kubernetes/pkg/api" resource "k8s.io/kubernetes/pkg/api/resource" unversioned "k8s.io/kubernetes/pkg/api/unversioned" v1 "k8s.io/kubernetes/pkg/api/v1" conversion "k8s.io/kubernetes/pkg/conversion" intstr "k8s.io/kubernetes/pkg/util/intstr" - inf "speter.net/go/exp/math/dec/inf" ) -func deepCopy_resource_Quantity(in resource.Quantity, out *resource.Quantity, c *conversion.Cloner) error { - if in.Amount != nil { - if newVal, err := c.DeepCopy(in.Amount); err != nil { - return err - } else { - out.Amount = newVal.(*inf.Dec) - } - } else { - out.Amount = nil +func init() { + if err := api.Scheme.AddGeneratedDeepCopyFuncs( + DeepCopy_v1beta1_APIVersion, + DeepCopy_v1beta1_CPUTargetUtilization, + DeepCopy_v1beta1_CustomMetricCurrentStatus, + DeepCopy_v1beta1_CustomMetricCurrentStatusList, + DeepCopy_v1beta1_CustomMetricTarget, + DeepCopy_v1beta1_CustomMetricTargetList, + DeepCopy_v1beta1_DaemonSet, + DeepCopy_v1beta1_DaemonSetList, + DeepCopy_v1beta1_DaemonSetSpec, + DeepCopy_v1beta1_DaemonSetStatus, + DeepCopy_v1beta1_Deployment, + DeepCopy_v1beta1_DeploymentList, + DeepCopy_v1beta1_DeploymentRollback, + DeepCopy_v1beta1_DeploymentSpec, + DeepCopy_v1beta1_DeploymentStatus, + DeepCopy_v1beta1_DeploymentStrategy, + DeepCopy_v1beta1_ExportOptions, + DeepCopy_v1beta1_HTTPIngressPath, + DeepCopy_v1beta1_HTTPIngressRuleValue, + DeepCopy_v1beta1_HorizontalPodAutoscaler, + DeepCopy_v1beta1_HorizontalPodAutoscalerList, + DeepCopy_v1beta1_HorizontalPodAutoscalerSpec, + DeepCopy_v1beta1_HorizontalPodAutoscalerStatus, + DeepCopy_v1beta1_HostPortRange, + DeepCopy_v1beta1_IDRange, + DeepCopy_v1beta1_Ingress, + DeepCopy_v1beta1_IngressBackend, + DeepCopy_v1beta1_IngressList, + DeepCopy_v1beta1_IngressRule, + DeepCopy_v1beta1_IngressRuleValue, + DeepCopy_v1beta1_IngressSpec, + DeepCopy_v1beta1_IngressStatus, + DeepCopy_v1beta1_IngressTLS, + DeepCopy_v1beta1_Job, + DeepCopy_v1beta1_JobCondition, + DeepCopy_v1beta1_JobList, + DeepCopy_v1beta1_JobSpec, + DeepCopy_v1beta1_JobStatus, + DeepCopy_v1beta1_LabelSelector, + DeepCopy_v1beta1_LabelSelectorRequirement, + DeepCopy_v1beta1_ListOptions, + DeepCopy_v1beta1_PodSecurityPolicy, + DeepCopy_v1beta1_PodSecurityPolicyList, + DeepCopy_v1beta1_PodSecurityPolicySpec, + DeepCopy_v1beta1_ReplicaSet, + DeepCopy_v1beta1_ReplicaSetList, + DeepCopy_v1beta1_ReplicaSetSpec, + DeepCopy_v1beta1_ReplicaSetStatus, + DeepCopy_v1beta1_ReplicationControllerDummy, + DeepCopy_v1beta1_RollbackConfig, + DeepCopy_v1beta1_RollingUpdateDeployment, + DeepCopy_v1beta1_RunAsUserStrategyOptions, + DeepCopy_v1beta1_SELinuxStrategyOptions, + DeepCopy_v1beta1_Scale, + DeepCopy_v1beta1_ScaleSpec, + DeepCopy_v1beta1_ScaleStatus, + DeepCopy_v1beta1_SubresourceReference, + DeepCopy_v1beta1_ThirdPartyResource, + DeepCopy_v1beta1_ThirdPartyResourceData, + DeepCopy_v1beta1_ThirdPartyResourceDataList, + DeepCopy_v1beta1_ThirdPartyResourceList, + ); err != nil { + // if one of the deep copy functions is malformed, detect it immediately. + panic(err) } - out.Format = in.Format - return nil } -func deepCopy_unversioned_ListMeta(in unversioned.ListMeta, out *unversioned.ListMeta, c *conversion.Cloner) error { - out.SelfLink = in.SelfLink - out.ResourceVersion = in.ResourceVersion - return nil -} - -func deepCopy_unversioned_Time(in unversioned.Time, out *unversioned.Time, c *conversion.Cloner) error { - if newVal, err := c.DeepCopy(in.Time); err != nil { - return err - } else { - out.Time = newVal.(time.Time) - } - return nil -} - -func deepCopy_unversioned_TypeMeta(in unversioned.TypeMeta, out *unversioned.TypeMeta, c *conversion.Cloner) error { - out.Kind = in.Kind - out.APIVersion = in.APIVersion - return nil -} - -func deepCopy_v1_AWSElasticBlockStoreVolumeSource(in v1.AWSElasticBlockStoreVolumeSource, out *v1.AWSElasticBlockStoreVolumeSource, c *conversion.Cloner) error { - out.VolumeID = in.VolumeID - out.FSType = in.FSType - out.Partition = in.Partition - out.ReadOnly = in.ReadOnly - return nil -} - -func deepCopy_v1_AzureFileVolumeSource(in v1.AzureFileVolumeSource, out *v1.AzureFileVolumeSource, c *conversion.Cloner) error { - out.SecretName = in.SecretName - out.ShareName = in.ShareName - out.ReadOnly = in.ReadOnly - return nil -} - -func deepCopy_v1_Capabilities(in v1.Capabilities, out *v1.Capabilities, c *conversion.Cloner) error { - if in.Add != nil { - out.Add = make([]v1.Capability, len(in.Add)) - for i := range in.Add { - out.Add[i] = in.Add[i] - } - } else { - out.Add = nil - } - if in.Drop != nil { - out.Drop = make([]v1.Capability, len(in.Drop)) - for i := range in.Drop { - out.Drop[i] = in.Drop[i] - } - } else { - out.Drop = nil - } - return nil -} - -func deepCopy_v1_CephFSVolumeSource(in v1.CephFSVolumeSource, out *v1.CephFSVolumeSource, c *conversion.Cloner) error { - if in.Monitors != nil { - out.Monitors = make([]string, len(in.Monitors)) - for i := range in.Monitors { - out.Monitors[i] = in.Monitors[i] - } - } else { - out.Monitors = nil - } - out.Path = in.Path - out.User = in.User - out.SecretFile = in.SecretFile - if in.SecretRef != nil { - out.SecretRef = new(v1.LocalObjectReference) - if err := deepCopy_v1_LocalObjectReference(*in.SecretRef, out.SecretRef, c); err != nil { - return err - } - } else { - out.SecretRef = nil - } - out.ReadOnly = in.ReadOnly - return nil -} - -func deepCopy_v1_CinderVolumeSource(in v1.CinderVolumeSource, out *v1.CinderVolumeSource, c *conversion.Cloner) error { - out.VolumeID = in.VolumeID - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - return nil -} - -func deepCopy_v1_ConfigMapKeySelector(in v1.ConfigMapKeySelector, out *v1.ConfigMapKeySelector, c *conversion.Cloner) error { - if err := deepCopy_v1_LocalObjectReference(in.LocalObjectReference, &out.LocalObjectReference, c); err != nil { - return err - } - out.Key = in.Key - return nil -} - -func deepCopy_v1_ConfigMapVolumeSource(in v1.ConfigMapVolumeSource, out *v1.ConfigMapVolumeSource, c *conversion.Cloner) error { - if err := deepCopy_v1_LocalObjectReference(in.LocalObjectReference, &out.LocalObjectReference, c); err != nil { - return err - } - if in.Items != nil { - out.Items = make([]v1.KeyToPath, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_KeyToPath(in.Items[i], &out.Items[i], c); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func deepCopy_v1_Container(in v1.Container, out *v1.Container, c *conversion.Cloner) error { - out.Name = in.Name - out.Image = in.Image - if in.Command != nil { - out.Command = make([]string, len(in.Command)) - for i := range in.Command { - out.Command[i] = in.Command[i] - } - } else { - out.Command = nil - } - if in.Args != nil { - out.Args = make([]string, len(in.Args)) - for i := range in.Args { - out.Args[i] = in.Args[i] - } - } else { - out.Args = nil - } - out.WorkingDir = in.WorkingDir - if in.Ports != nil { - out.Ports = make([]v1.ContainerPort, len(in.Ports)) - for i := range in.Ports { - if err := deepCopy_v1_ContainerPort(in.Ports[i], &out.Ports[i], c); err != nil { - return err - } - } - } else { - out.Ports = nil - } - if in.Env != nil { - out.Env = make([]v1.EnvVar, len(in.Env)) - for i := range in.Env { - if err := deepCopy_v1_EnvVar(in.Env[i], &out.Env[i], c); err != nil { - return err - } - } - } else { - out.Env = nil - } - if err := deepCopy_v1_ResourceRequirements(in.Resources, &out.Resources, c); err != nil { - return err - } - if in.VolumeMounts != nil { - out.VolumeMounts = make([]v1.VolumeMount, len(in.VolumeMounts)) - for i := range in.VolumeMounts { - if err := deepCopy_v1_VolumeMount(in.VolumeMounts[i], &out.VolumeMounts[i], c); err != nil { - return err - } - } - } else { - out.VolumeMounts = nil - } - if in.LivenessProbe != nil { - out.LivenessProbe = new(v1.Probe) - if err := deepCopy_v1_Probe(*in.LivenessProbe, out.LivenessProbe, c); err != nil { - return err - } - } else { - out.LivenessProbe = nil - } - if in.ReadinessProbe != nil { - out.ReadinessProbe = new(v1.Probe) - if err := deepCopy_v1_Probe(*in.ReadinessProbe, out.ReadinessProbe, c); err != nil { - return err - } - } else { - out.ReadinessProbe = nil - } - if in.Lifecycle != nil { - out.Lifecycle = new(v1.Lifecycle) - if err := deepCopy_v1_Lifecycle(*in.Lifecycle, out.Lifecycle, c); err != nil { - return err - } - } else { - out.Lifecycle = nil - } - out.TerminationMessagePath = in.TerminationMessagePath - out.ImagePullPolicy = in.ImagePullPolicy - if in.SecurityContext != nil { - out.SecurityContext = new(v1.SecurityContext) - if err := deepCopy_v1_SecurityContext(*in.SecurityContext, out.SecurityContext, c); err != nil { - return err - } - } else { - out.SecurityContext = nil - } - out.Stdin = in.Stdin - out.StdinOnce = in.StdinOnce - out.TTY = in.TTY - return nil -} - -func deepCopy_v1_ContainerPort(in v1.ContainerPort, out *v1.ContainerPort, c *conversion.Cloner) error { - out.Name = in.Name - out.HostPort = in.HostPort - out.ContainerPort = in.ContainerPort - out.Protocol = in.Protocol - out.HostIP = in.HostIP - return nil -} - -func deepCopy_v1_DownwardAPIVolumeFile(in v1.DownwardAPIVolumeFile, out *v1.DownwardAPIVolumeFile, c *conversion.Cloner) error { - out.Path = in.Path - if err := deepCopy_v1_ObjectFieldSelector(in.FieldRef, &out.FieldRef, c); err != nil { - return err - } - return nil -} - -func deepCopy_v1_DownwardAPIVolumeSource(in v1.DownwardAPIVolumeSource, out *v1.DownwardAPIVolumeSource, c *conversion.Cloner) error { - if in.Items != nil { - out.Items = make([]v1.DownwardAPIVolumeFile, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1_DownwardAPIVolumeFile(in.Items[i], &out.Items[i], c); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -func deepCopy_v1_EmptyDirVolumeSource(in v1.EmptyDirVolumeSource, out *v1.EmptyDirVolumeSource, c *conversion.Cloner) error { - out.Medium = in.Medium - return nil -} - -func deepCopy_v1_EnvVar(in v1.EnvVar, out *v1.EnvVar, c *conversion.Cloner) error { - out.Name = in.Name - out.Value = in.Value - if in.ValueFrom != nil { - out.ValueFrom = new(v1.EnvVarSource) - if err := deepCopy_v1_EnvVarSource(*in.ValueFrom, out.ValueFrom, c); err != nil { - return err - } - } else { - out.ValueFrom = nil - } - return nil -} - -func deepCopy_v1_EnvVarSource(in v1.EnvVarSource, out *v1.EnvVarSource, c *conversion.Cloner) error { - if in.FieldRef != nil { - out.FieldRef = new(v1.ObjectFieldSelector) - if err := deepCopy_v1_ObjectFieldSelector(*in.FieldRef, out.FieldRef, c); err != nil { - return err - } - } else { - out.FieldRef = nil - } - if in.ConfigMapKeyRef != nil { - out.ConfigMapKeyRef = new(v1.ConfigMapKeySelector) - if err := deepCopy_v1_ConfigMapKeySelector(*in.ConfigMapKeyRef, out.ConfigMapKeyRef, c); err != nil { - return err - } - } else { - out.ConfigMapKeyRef = nil - } - if in.SecretKeyRef != nil { - out.SecretKeyRef = new(v1.SecretKeySelector) - if err := deepCopy_v1_SecretKeySelector(*in.SecretKeyRef, out.SecretKeyRef, c); err != nil { - return err - } - } else { - out.SecretKeyRef = nil - } - return nil -} - -func deepCopy_v1_ExecAction(in v1.ExecAction, out *v1.ExecAction, c *conversion.Cloner) error { - if in.Command != nil { - out.Command = make([]string, len(in.Command)) - for i := range in.Command { - out.Command[i] = in.Command[i] - } - } else { - out.Command = nil - } - return nil -} - -func deepCopy_v1_FCVolumeSource(in v1.FCVolumeSource, out *v1.FCVolumeSource, c *conversion.Cloner) error { - if in.TargetWWNs != nil { - out.TargetWWNs = make([]string, len(in.TargetWWNs)) - for i := range in.TargetWWNs { - out.TargetWWNs[i] = in.TargetWWNs[i] - } - } else { - out.TargetWWNs = nil - } - if in.Lun != nil { - out.Lun = new(int32) - *out.Lun = *in.Lun - } else { - out.Lun = nil - } - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - return nil -} - -func deepCopy_v1_FlexVolumeSource(in v1.FlexVolumeSource, out *v1.FlexVolumeSource, c *conversion.Cloner) error { - out.Driver = in.Driver - out.FSType = in.FSType - if in.SecretRef != nil { - out.SecretRef = new(v1.LocalObjectReference) - if err := deepCopy_v1_LocalObjectReference(*in.SecretRef, out.SecretRef, c); err != nil { - return err - } - } else { - out.SecretRef = nil - } - out.ReadOnly = in.ReadOnly - if in.Options != nil { - out.Options = make(map[string]string) - for key, val := range in.Options { - out.Options[key] = val - } - } else { - out.Options = nil - } - return nil -} - -func deepCopy_v1_FlockerVolumeSource(in v1.FlockerVolumeSource, out *v1.FlockerVolumeSource, c *conversion.Cloner) error { - out.DatasetName = in.DatasetName - return nil -} - -func deepCopy_v1_GCEPersistentDiskVolumeSource(in v1.GCEPersistentDiskVolumeSource, out *v1.GCEPersistentDiskVolumeSource, c *conversion.Cloner) error { - out.PDName = in.PDName - out.FSType = in.FSType - out.Partition = in.Partition - out.ReadOnly = in.ReadOnly - return nil -} - -func deepCopy_v1_GitRepoVolumeSource(in v1.GitRepoVolumeSource, out *v1.GitRepoVolumeSource, c *conversion.Cloner) error { - out.Repository = in.Repository - out.Revision = in.Revision - out.Directory = in.Directory - return nil -} - -func deepCopy_v1_GlusterfsVolumeSource(in v1.GlusterfsVolumeSource, out *v1.GlusterfsVolumeSource, c *conversion.Cloner) error { - out.EndpointsName = in.EndpointsName - out.Path = in.Path - out.ReadOnly = in.ReadOnly - return nil -} - -func deepCopy_v1_HTTPGetAction(in v1.HTTPGetAction, out *v1.HTTPGetAction, c *conversion.Cloner) error { - out.Path = in.Path - if err := deepCopy_intstr_IntOrString(in.Port, &out.Port, c); err != nil { - return err - } - out.Host = in.Host - out.Scheme = in.Scheme - if in.HTTPHeaders != nil { - out.HTTPHeaders = make([]v1.HTTPHeader, len(in.HTTPHeaders)) - for i := range in.HTTPHeaders { - if err := deepCopy_v1_HTTPHeader(in.HTTPHeaders[i], &out.HTTPHeaders[i], c); err != nil { - return err - } - } - } else { - out.HTTPHeaders = nil - } - return nil -} - -func deepCopy_v1_HTTPHeader(in v1.HTTPHeader, out *v1.HTTPHeader, c *conversion.Cloner) error { - out.Name = in.Name - out.Value = in.Value - return nil -} - -func deepCopy_v1_Handler(in v1.Handler, out *v1.Handler, c *conversion.Cloner) error { - if in.Exec != nil { - out.Exec = new(v1.ExecAction) - if err := deepCopy_v1_ExecAction(*in.Exec, out.Exec, c); err != nil { - return err - } - } else { - out.Exec = nil - } - if in.HTTPGet != nil { - out.HTTPGet = new(v1.HTTPGetAction) - if err := deepCopy_v1_HTTPGetAction(*in.HTTPGet, out.HTTPGet, c); err != nil { - return err - } - } else { - out.HTTPGet = nil - } - if in.TCPSocket != nil { - out.TCPSocket = new(v1.TCPSocketAction) - if err := deepCopy_v1_TCPSocketAction(*in.TCPSocket, out.TCPSocket, c); err != nil { - return err - } - } else { - out.TCPSocket = nil - } - return nil -} - -func deepCopy_v1_HostPathVolumeSource(in v1.HostPathVolumeSource, out *v1.HostPathVolumeSource, c *conversion.Cloner) error { - out.Path = in.Path - return nil -} - -func deepCopy_v1_ISCSIVolumeSource(in v1.ISCSIVolumeSource, out *v1.ISCSIVolumeSource, c *conversion.Cloner) error { - out.TargetPortal = in.TargetPortal - out.IQN = in.IQN - out.Lun = in.Lun - out.ISCSIInterface = in.ISCSIInterface - out.FSType = in.FSType - out.ReadOnly = in.ReadOnly - return nil -} - -func deepCopy_v1_KeyToPath(in v1.KeyToPath, out *v1.KeyToPath, c *conversion.Cloner) error { - out.Key = in.Key - out.Path = in.Path - return nil -} - -func deepCopy_v1_Lifecycle(in v1.Lifecycle, out *v1.Lifecycle, c *conversion.Cloner) error { - if in.PostStart != nil { - out.PostStart = new(v1.Handler) - if err := deepCopy_v1_Handler(*in.PostStart, out.PostStart, c); err != nil { - return err - } - } else { - out.PostStart = nil - } - if in.PreStop != nil { - out.PreStop = new(v1.Handler) - if err := deepCopy_v1_Handler(*in.PreStop, out.PreStop, c); err != nil { - return err - } - } else { - out.PreStop = nil - } - return nil -} - -func deepCopy_v1_LoadBalancerIngress(in v1.LoadBalancerIngress, out *v1.LoadBalancerIngress, c *conversion.Cloner) error { - out.IP = in.IP - out.Hostname = in.Hostname - return nil -} - -func deepCopy_v1_LoadBalancerStatus(in v1.LoadBalancerStatus, out *v1.LoadBalancerStatus, c *conversion.Cloner) error { - if in.Ingress != nil { - out.Ingress = make([]v1.LoadBalancerIngress, len(in.Ingress)) - for i := range in.Ingress { - if err := deepCopy_v1_LoadBalancerIngress(in.Ingress[i], &out.Ingress[i], c); err != nil { - return err - } - } - } else { - out.Ingress = nil - } - return nil -} - -func deepCopy_v1_LocalObjectReference(in v1.LocalObjectReference, out *v1.LocalObjectReference, c *conversion.Cloner) error { +func DeepCopy_v1beta1_APIVersion(in APIVersion, out *APIVersion, c *conversion.Cloner) error { out.Name = in.Name return nil } -func deepCopy_v1_NFSVolumeSource(in v1.NFSVolumeSource, out *v1.NFSVolumeSource, c *conversion.Cloner) error { - out.Server = in.Server - out.Path = in.Path - out.ReadOnly = in.ReadOnly - return nil -} - -func deepCopy_v1_ObjectFieldSelector(in v1.ObjectFieldSelector, out *v1.ObjectFieldSelector, c *conversion.Cloner) error { - out.APIVersion = in.APIVersion - out.FieldPath = in.FieldPath - return nil -} - -func deepCopy_v1_ObjectMeta(in v1.ObjectMeta, out *v1.ObjectMeta, c *conversion.Cloner) error { - out.Name = in.Name - out.GenerateName = in.GenerateName - out.Namespace = in.Namespace - out.SelfLink = in.SelfLink - out.UID = in.UID - out.ResourceVersion = in.ResourceVersion - out.Generation = in.Generation - if err := deepCopy_unversioned_Time(in.CreationTimestamp, &out.CreationTimestamp, c); err != nil { - return err - } - if in.DeletionTimestamp != nil { - out.DeletionTimestamp = new(unversioned.Time) - if err := deepCopy_unversioned_Time(*in.DeletionTimestamp, out.DeletionTimestamp, c); err != nil { - return err - } - } else { - out.DeletionTimestamp = nil - } - if in.DeletionGracePeriodSeconds != nil { - out.DeletionGracePeriodSeconds = new(int64) - *out.DeletionGracePeriodSeconds = *in.DeletionGracePeriodSeconds - } else { - out.DeletionGracePeriodSeconds = nil - } - if in.Labels != nil { - out.Labels = make(map[string]string) - for key, val := range in.Labels { - out.Labels[key] = val - } - } else { - out.Labels = nil - } - if in.Annotations != nil { - out.Annotations = make(map[string]string) - for key, val := range in.Annotations { - out.Annotations[key] = val - } - } else { - out.Annotations = nil - } - return nil -} - -func deepCopy_v1_PersistentVolumeClaimVolumeSource(in v1.PersistentVolumeClaimVolumeSource, out *v1.PersistentVolumeClaimVolumeSource, c *conversion.Cloner) error { - out.ClaimName = in.ClaimName - out.ReadOnly = in.ReadOnly - return nil -} - -func deepCopy_v1_PodSecurityContext(in v1.PodSecurityContext, out *v1.PodSecurityContext, c *conversion.Cloner) error { - if in.SELinuxOptions != nil { - out.SELinuxOptions = new(v1.SELinuxOptions) - if err := deepCopy_v1_SELinuxOptions(*in.SELinuxOptions, out.SELinuxOptions, c); err != nil { - return err - } - } else { - out.SELinuxOptions = nil - } - if in.RunAsUser != nil { - out.RunAsUser = new(int64) - *out.RunAsUser = *in.RunAsUser - } else { - out.RunAsUser = nil - } - if in.RunAsNonRoot != nil { - out.RunAsNonRoot = new(bool) - *out.RunAsNonRoot = *in.RunAsNonRoot - } else { - out.RunAsNonRoot = nil - } - if in.SupplementalGroups != nil { - out.SupplementalGroups = make([]int64, len(in.SupplementalGroups)) - for i := range in.SupplementalGroups { - out.SupplementalGroups[i] = in.SupplementalGroups[i] - } - } else { - out.SupplementalGroups = nil - } - if in.FSGroup != nil { - out.FSGroup = new(int64) - *out.FSGroup = *in.FSGroup - } else { - out.FSGroup = nil - } - return nil -} - -func deepCopy_v1_PodSpec(in v1.PodSpec, out *v1.PodSpec, c *conversion.Cloner) error { - if in.Volumes != nil { - out.Volumes = make([]v1.Volume, len(in.Volumes)) - for i := range in.Volumes { - if err := deepCopy_v1_Volume(in.Volumes[i], &out.Volumes[i], c); err != nil { - return err - } - } - } else { - out.Volumes = nil - } - if in.Containers != nil { - out.Containers = make([]v1.Container, len(in.Containers)) - for i := range in.Containers { - if err := deepCopy_v1_Container(in.Containers[i], &out.Containers[i], c); err != nil { - return err - } - } - } else { - out.Containers = nil - } - out.RestartPolicy = in.RestartPolicy - if in.TerminationGracePeriodSeconds != nil { - out.TerminationGracePeriodSeconds = new(int64) - *out.TerminationGracePeriodSeconds = *in.TerminationGracePeriodSeconds - } else { - out.TerminationGracePeriodSeconds = nil - } - if in.ActiveDeadlineSeconds != nil { - out.ActiveDeadlineSeconds = new(int64) - *out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds - } else { - out.ActiveDeadlineSeconds = nil - } - out.DNSPolicy = in.DNSPolicy - if in.NodeSelector != nil { - out.NodeSelector = make(map[string]string) - for key, val := range in.NodeSelector { - out.NodeSelector[key] = val - } - } else { - out.NodeSelector = nil - } - out.ServiceAccountName = in.ServiceAccountName - out.DeprecatedServiceAccount = in.DeprecatedServiceAccount - out.NodeName = in.NodeName - out.HostNetwork = in.HostNetwork - out.HostPID = in.HostPID - out.HostIPC = in.HostIPC - if in.SecurityContext != nil { - out.SecurityContext = new(v1.PodSecurityContext) - if err := deepCopy_v1_PodSecurityContext(*in.SecurityContext, out.SecurityContext, c); err != nil { - return err - } - } else { - out.SecurityContext = nil - } - if in.ImagePullSecrets != nil { - out.ImagePullSecrets = make([]v1.LocalObjectReference, len(in.ImagePullSecrets)) - for i := range in.ImagePullSecrets { - if err := deepCopy_v1_LocalObjectReference(in.ImagePullSecrets[i], &out.ImagePullSecrets[i], c); err != nil { - return err - } - } - } else { - out.ImagePullSecrets = nil - } - return nil -} - -func deepCopy_v1_PodTemplateSpec(in v1.PodTemplateSpec, out *v1.PodTemplateSpec, c *conversion.Cloner) error { - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { - return err - } - if err := deepCopy_v1_PodSpec(in.Spec, &out.Spec, c); err != nil { - return err - } - return nil -} - -func deepCopy_v1_Probe(in v1.Probe, out *v1.Probe, c *conversion.Cloner) error { - if err := deepCopy_v1_Handler(in.Handler, &out.Handler, c); err != nil { - return err - } - out.InitialDelaySeconds = in.InitialDelaySeconds - out.TimeoutSeconds = in.TimeoutSeconds - out.PeriodSeconds = in.PeriodSeconds - out.SuccessThreshold = in.SuccessThreshold - out.FailureThreshold = in.FailureThreshold - return nil -} - -func deepCopy_v1_RBDVolumeSource(in v1.RBDVolumeSource, out *v1.RBDVolumeSource, c *conversion.Cloner) error { - if in.CephMonitors != nil { - out.CephMonitors = make([]string, len(in.CephMonitors)) - for i := range in.CephMonitors { - out.CephMonitors[i] = in.CephMonitors[i] - } - } else { - out.CephMonitors = nil - } - out.RBDImage = in.RBDImage - out.FSType = in.FSType - out.RBDPool = in.RBDPool - out.RadosUser = in.RadosUser - out.Keyring = in.Keyring - if in.SecretRef != nil { - out.SecretRef = new(v1.LocalObjectReference) - if err := deepCopy_v1_LocalObjectReference(*in.SecretRef, out.SecretRef, c); err != nil { - return err - } - } else { - out.SecretRef = nil - } - out.ReadOnly = in.ReadOnly - return nil -} - -func deepCopy_v1_ResourceRequirements(in v1.ResourceRequirements, out *v1.ResourceRequirements, c *conversion.Cloner) error { - if in.Limits != nil { - out.Limits = make(v1.ResourceList) - for key, val := range in.Limits { - newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { - return err - } - out.Limits[key] = *newVal - } - } else { - out.Limits = nil - } - if in.Requests != nil { - out.Requests = make(v1.ResourceList) - for key, val := range in.Requests { - newVal := new(resource.Quantity) - if err := deepCopy_resource_Quantity(val, newVal, c); err != nil { - return err - } - out.Requests[key] = *newVal - } - } else { - out.Requests = nil - } - return nil -} - -func deepCopy_v1_SELinuxOptions(in v1.SELinuxOptions, out *v1.SELinuxOptions, c *conversion.Cloner) error { - out.User = in.User - out.Role = in.Role - out.Type = in.Type - out.Level = in.Level - return nil -} - -func deepCopy_v1_SecretKeySelector(in v1.SecretKeySelector, out *v1.SecretKeySelector, c *conversion.Cloner) error { - if err := deepCopy_v1_LocalObjectReference(in.LocalObjectReference, &out.LocalObjectReference, c); err != nil { - return err - } - out.Key = in.Key - return nil -} - -func deepCopy_v1_SecretVolumeSource(in v1.SecretVolumeSource, out *v1.SecretVolumeSource, c *conversion.Cloner) error { - out.SecretName = in.SecretName - return nil -} - -func deepCopy_v1_SecurityContext(in v1.SecurityContext, out *v1.SecurityContext, c *conversion.Cloner) error { - if in.Capabilities != nil { - out.Capabilities = new(v1.Capabilities) - if err := deepCopy_v1_Capabilities(*in.Capabilities, out.Capabilities, c); err != nil { - return err - } - } else { - out.Capabilities = nil - } - if in.Privileged != nil { - out.Privileged = new(bool) - *out.Privileged = *in.Privileged - } else { - out.Privileged = nil - } - if in.SELinuxOptions != nil { - out.SELinuxOptions = new(v1.SELinuxOptions) - if err := deepCopy_v1_SELinuxOptions(*in.SELinuxOptions, out.SELinuxOptions, c); err != nil { - return err - } - } else { - out.SELinuxOptions = nil - } - if in.RunAsUser != nil { - out.RunAsUser = new(int64) - *out.RunAsUser = *in.RunAsUser - } else { - out.RunAsUser = nil - } - if in.RunAsNonRoot != nil { - out.RunAsNonRoot = new(bool) - *out.RunAsNonRoot = *in.RunAsNonRoot - } else { - out.RunAsNonRoot = nil - } - if in.ReadOnlyRootFilesystem != nil { - out.ReadOnlyRootFilesystem = new(bool) - *out.ReadOnlyRootFilesystem = *in.ReadOnlyRootFilesystem - } else { - out.ReadOnlyRootFilesystem = nil - } - return nil -} - -func deepCopy_v1_TCPSocketAction(in v1.TCPSocketAction, out *v1.TCPSocketAction, c *conversion.Cloner) error { - if err := deepCopy_intstr_IntOrString(in.Port, &out.Port, c); err != nil { - return err - } - return nil -} - -func deepCopy_v1_Volume(in v1.Volume, out *v1.Volume, c *conversion.Cloner) error { - out.Name = in.Name - if err := deepCopy_v1_VolumeSource(in.VolumeSource, &out.VolumeSource, c); err != nil { - return err - } - return nil -} - -func deepCopy_v1_VolumeMount(in v1.VolumeMount, out *v1.VolumeMount, c *conversion.Cloner) error { - out.Name = in.Name - out.ReadOnly = in.ReadOnly - out.MountPath = in.MountPath - return nil -} - -func deepCopy_v1_VolumeSource(in v1.VolumeSource, out *v1.VolumeSource, c *conversion.Cloner) error { - if in.HostPath != nil { - out.HostPath = new(v1.HostPathVolumeSource) - if err := deepCopy_v1_HostPathVolumeSource(*in.HostPath, out.HostPath, c); err != nil { - return err - } - } else { - out.HostPath = nil - } - if in.EmptyDir != nil { - out.EmptyDir = new(v1.EmptyDirVolumeSource) - if err := deepCopy_v1_EmptyDirVolumeSource(*in.EmptyDir, out.EmptyDir, c); err != nil { - return err - } - } else { - out.EmptyDir = nil - } - if in.GCEPersistentDisk != nil { - out.GCEPersistentDisk = new(v1.GCEPersistentDiskVolumeSource) - if err := deepCopy_v1_GCEPersistentDiskVolumeSource(*in.GCEPersistentDisk, out.GCEPersistentDisk, c); err != nil { - return err - } - } else { - out.GCEPersistentDisk = nil - } - if in.AWSElasticBlockStore != nil { - out.AWSElasticBlockStore = new(v1.AWSElasticBlockStoreVolumeSource) - if err := deepCopy_v1_AWSElasticBlockStoreVolumeSource(*in.AWSElasticBlockStore, out.AWSElasticBlockStore, c); err != nil { - return err - } - } else { - out.AWSElasticBlockStore = nil - } - if in.GitRepo != nil { - out.GitRepo = new(v1.GitRepoVolumeSource) - if err := deepCopy_v1_GitRepoVolumeSource(*in.GitRepo, out.GitRepo, c); err != nil { - return err - } - } else { - out.GitRepo = nil - } - if in.Secret != nil { - out.Secret = new(v1.SecretVolumeSource) - if err := deepCopy_v1_SecretVolumeSource(*in.Secret, out.Secret, c); err != nil { - return err - } - } else { - out.Secret = nil - } - if in.NFS != nil { - out.NFS = new(v1.NFSVolumeSource) - if err := deepCopy_v1_NFSVolumeSource(*in.NFS, out.NFS, c); err != nil { - return err - } - } else { - out.NFS = nil - } - if in.ISCSI != nil { - out.ISCSI = new(v1.ISCSIVolumeSource) - if err := deepCopy_v1_ISCSIVolumeSource(*in.ISCSI, out.ISCSI, c); err != nil { - return err - } - } else { - out.ISCSI = nil - } - if in.Glusterfs != nil { - out.Glusterfs = new(v1.GlusterfsVolumeSource) - if err := deepCopy_v1_GlusterfsVolumeSource(*in.Glusterfs, out.Glusterfs, c); err != nil { - return err - } - } else { - out.Glusterfs = nil - } - if in.PersistentVolumeClaim != nil { - out.PersistentVolumeClaim = new(v1.PersistentVolumeClaimVolumeSource) - if err := deepCopy_v1_PersistentVolumeClaimVolumeSource(*in.PersistentVolumeClaim, out.PersistentVolumeClaim, c); err != nil { - return err - } - } else { - out.PersistentVolumeClaim = nil - } - if in.RBD != nil { - out.RBD = new(v1.RBDVolumeSource) - if err := deepCopy_v1_RBDVolumeSource(*in.RBD, out.RBD, c); err != nil { - return err - } - } else { - out.RBD = nil - } - if in.FlexVolume != nil { - out.FlexVolume = new(v1.FlexVolumeSource) - if err := deepCopy_v1_FlexVolumeSource(*in.FlexVolume, out.FlexVolume, c); err != nil { - return err - } - } else { - out.FlexVolume = nil - } - if in.Cinder != nil { - out.Cinder = new(v1.CinderVolumeSource) - if err := deepCopy_v1_CinderVolumeSource(*in.Cinder, out.Cinder, c); err != nil { - return err - } - } else { - out.Cinder = nil - } - if in.CephFS != nil { - out.CephFS = new(v1.CephFSVolumeSource) - if err := deepCopy_v1_CephFSVolumeSource(*in.CephFS, out.CephFS, c); err != nil { - return err - } - } else { - out.CephFS = nil - } - if in.Flocker != nil { - out.Flocker = new(v1.FlockerVolumeSource) - if err := deepCopy_v1_FlockerVolumeSource(*in.Flocker, out.Flocker, c); err != nil { - return err - } - } else { - out.Flocker = nil - } - if in.DownwardAPI != nil { - out.DownwardAPI = new(v1.DownwardAPIVolumeSource) - if err := deepCopy_v1_DownwardAPIVolumeSource(*in.DownwardAPI, out.DownwardAPI, c); err != nil { - return err - } - } else { - out.DownwardAPI = nil - } - if in.FC != nil { - out.FC = new(v1.FCVolumeSource) - if err := deepCopy_v1_FCVolumeSource(*in.FC, out.FC, c); err != nil { - return err - } - } else { - out.FC = nil - } - if in.AzureFile != nil { - out.AzureFile = new(v1.AzureFileVolumeSource) - if err := deepCopy_v1_AzureFileVolumeSource(*in.AzureFile, out.AzureFile, c); err != nil { - return err - } - } else { - out.AzureFile = nil - } - if in.ConfigMap != nil { - out.ConfigMap = new(v1.ConfigMapVolumeSource) - if err := deepCopy_v1_ConfigMapVolumeSource(*in.ConfigMap, out.ConfigMap, c); err != nil { - return err - } - } else { - out.ConfigMap = nil - } - return nil -} - -func deepCopy_v1beta1_APIVersion(in APIVersion, out *APIVersion, c *conversion.Cloner) error { - out.Name = in.Name - out.APIGroup = in.APIGroup - return nil -} - -func deepCopy_v1beta1_CPUTargetUtilization(in CPUTargetUtilization, out *CPUTargetUtilization, c *conversion.Cloner) error { +func DeepCopy_v1beta1_CPUTargetUtilization(in CPUTargetUtilization, out *CPUTargetUtilization, c *conversion.Cloner) error { out.TargetPercentage = in.TargetPercentage return nil } -func deepCopy_v1beta1_ClusterAutoscaler(in ClusterAutoscaler, out *ClusterAutoscaler, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { - return err - } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { - return err - } - if err := deepCopy_v1beta1_ClusterAutoscalerSpec(in.Spec, &out.Spec, c); err != nil { +func DeepCopy_v1beta1_CustomMetricCurrentStatus(in CustomMetricCurrentStatus, out *CustomMetricCurrentStatus, c *conversion.Cloner) error { + out.Name = in.Name + if err := resource.DeepCopy_resource_Quantity(in.CurrentValue, &out.CurrentValue, c); err != nil { return err } return nil } -func deepCopy_v1beta1_ClusterAutoscalerList(in ClusterAutoscalerList, out *ClusterAutoscalerList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { - return err - } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { - return err - } +func DeepCopy_v1beta1_CustomMetricCurrentStatusList(in CustomMetricCurrentStatusList, out *CustomMetricCurrentStatusList, c *conversion.Cloner) error { if in.Items != nil { - out.Items = make([]ClusterAutoscaler, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1beta1_ClusterAutoscaler(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]CustomMetricCurrentStatus, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_CustomMetricCurrentStatus(in[i], &(*out)[i], c); err != nil { return err } } @@ -1063,49 +131,20 @@ func deepCopy_v1beta1_ClusterAutoscalerList(in ClusterAutoscalerList, out *Clust return nil } -func deepCopy_v1beta1_ClusterAutoscalerSpec(in ClusterAutoscalerSpec, out *ClusterAutoscalerSpec, c *conversion.Cloner) error { - out.MinNodes = in.MinNodes - out.MaxNodes = in.MaxNodes - if in.TargetUtilization != nil { - out.TargetUtilization = make([]NodeUtilization, len(in.TargetUtilization)) - for i := range in.TargetUtilization { - if err := deepCopy_v1beta1_NodeUtilization(in.TargetUtilization[i], &out.TargetUtilization[i], c); err != nil { - return err - } - } - } else { - out.TargetUtilization = nil - } - return nil -} - -func deepCopy_v1beta1_DaemonSet(in DaemonSet, out *DaemonSet, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { - return err - } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { - return err - } - if err := deepCopy_v1beta1_DaemonSetSpec(in.Spec, &out.Spec, c); err != nil { - return err - } - if err := deepCopy_v1beta1_DaemonSetStatus(in.Status, &out.Status, c); err != nil { +func DeepCopy_v1beta1_CustomMetricTarget(in CustomMetricTarget, out *CustomMetricTarget, c *conversion.Cloner) error { + out.Name = in.Name + if err := resource.DeepCopy_resource_Quantity(in.TargetValue, &out.TargetValue, c); err != nil { return err } return nil } -func deepCopy_v1beta1_DaemonSetList(in DaemonSetList, out *DaemonSetList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { - return err - } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { - return err - } +func DeepCopy_v1beta1_CustomMetricTargetList(in CustomMetricTargetList, out *CustomMetricTargetList, c *conversion.Cloner) error { if in.Items != nil { - out.Items = make([]DaemonSet, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1beta1_DaemonSet(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]CustomMetricTarget, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_CustomMetricTarget(in[i], &(*out)[i], c); err != nil { return err } } @@ -1115,55 +154,94 @@ func deepCopy_v1beta1_DaemonSetList(in DaemonSetList, out *DaemonSetList, c *con return nil } -func deepCopy_v1beta1_DaemonSetSpec(in DaemonSetSpec, out *DaemonSetSpec, c *conversion.Cloner) error { +func DeepCopy_v1beta1_DaemonSet(in DaemonSet, out *DaemonSet, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + return err + } + if err := DeepCopy_v1beta1_DaemonSetSpec(in.Spec, &out.Spec, c); err != nil { + return err + } + if err := DeepCopy_v1beta1_DaemonSetStatus(in.Status, &out.Status, c); err != nil { + return err + } + return nil +} + +func DeepCopy_v1beta1_DaemonSetList(in DaemonSetList, out *DaemonSetList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + return err + } + if in.Items != nil { + in, out := in.Items, &out.Items + *out = make([]DaemonSet, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_DaemonSet(in[i], &(*out)[i], c); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +func DeepCopy_v1beta1_DaemonSetSpec(in DaemonSetSpec, out *DaemonSetSpec, c *conversion.Cloner) error { if in.Selector != nil { - out.Selector = new(LabelSelector) - if err := deepCopy_v1beta1_LabelSelector(*in.Selector, out.Selector, c); err != nil { + in, out := in.Selector, &out.Selector + *out = new(LabelSelector) + if err := DeepCopy_v1beta1_LabelSelector(*in, *out, c); err != nil { return err } } else { out.Selector = nil } - if err := deepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil { + if err := v1.DeepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil { return err } return nil } -func deepCopy_v1beta1_DaemonSetStatus(in DaemonSetStatus, out *DaemonSetStatus, c *conversion.Cloner) error { +func DeepCopy_v1beta1_DaemonSetStatus(in DaemonSetStatus, out *DaemonSetStatus, c *conversion.Cloner) error { out.CurrentNumberScheduled = in.CurrentNumberScheduled out.NumberMisscheduled = in.NumberMisscheduled out.DesiredNumberScheduled = in.DesiredNumberScheduled return nil } -func deepCopy_v1beta1_Deployment(in Deployment, out *Deployment, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_Deployment(in Deployment, out *Deployment, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1beta1_DeploymentSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1beta1_DeploymentSpec(in.Spec, &out.Spec, c); err != nil { return err } - if err := deepCopy_v1beta1_DeploymentStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1beta1_DeploymentStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1beta1_DeploymentList(in DeploymentList, out *DeploymentList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_DeploymentList(in DeploymentList, out *DeploymentList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]Deployment, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1beta1_Deployment(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]Deployment, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_Deployment(in[i], &(*out)[i], c); err != nil { return err } } @@ -1173,57 +251,62 @@ func deepCopy_v1beta1_DeploymentList(in DeploymentList, out *DeploymentList, c * return nil } -func deepCopy_v1beta1_DeploymentRollback(in DeploymentRollback, out *DeploymentRollback, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_DeploymentRollback(in DeploymentRollback, out *DeploymentRollback, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.Name = in.Name if in.UpdatedAnnotations != nil { - out.UpdatedAnnotations = make(map[string]string) - for key, val := range in.UpdatedAnnotations { - out.UpdatedAnnotations[key] = val + in, out := in.UpdatedAnnotations, &out.UpdatedAnnotations + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val } } else { out.UpdatedAnnotations = nil } - if err := deepCopy_v1beta1_RollbackConfig(in.RollbackTo, &out.RollbackTo, c); err != nil { + if err := DeepCopy_v1beta1_RollbackConfig(in.RollbackTo, &out.RollbackTo, c); err != nil { return err } return nil } -func deepCopy_v1beta1_DeploymentSpec(in DeploymentSpec, out *DeploymentSpec, c *conversion.Cloner) error { +func DeepCopy_v1beta1_DeploymentSpec(in DeploymentSpec, out *DeploymentSpec, c *conversion.Cloner) error { if in.Replicas != nil { - out.Replicas = new(int32) - *out.Replicas = *in.Replicas + in, out := in.Replicas, &out.Replicas + *out = new(int32) + **out = *in } else { out.Replicas = nil } if in.Selector != nil { - out.Selector = new(LabelSelector) - if err := deepCopy_v1beta1_LabelSelector(*in.Selector, out.Selector, c); err != nil { + in, out := in.Selector, &out.Selector + *out = new(LabelSelector) + if err := DeepCopy_v1beta1_LabelSelector(*in, *out, c); err != nil { return err } } else { out.Selector = nil } - if err := deepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil { + if err := v1.DeepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil { return err } - if err := deepCopy_v1beta1_DeploymentStrategy(in.Strategy, &out.Strategy, c); err != nil { + if err := DeepCopy_v1beta1_DeploymentStrategy(in.Strategy, &out.Strategy, c); err != nil { return err } out.MinReadySeconds = in.MinReadySeconds if in.RevisionHistoryLimit != nil { - out.RevisionHistoryLimit = new(int32) - *out.RevisionHistoryLimit = *in.RevisionHistoryLimit + in, out := in.RevisionHistoryLimit, &out.RevisionHistoryLimit + *out = new(int32) + **out = *in } else { out.RevisionHistoryLimit = nil } out.Paused = in.Paused if in.RollbackTo != nil { - out.RollbackTo = new(RollbackConfig) - if err := deepCopy_v1beta1_RollbackConfig(*in.RollbackTo, out.RollbackTo, c); err != nil { + in, out := in.RollbackTo, &out.RollbackTo + *out = new(RollbackConfig) + if err := DeepCopy_v1beta1_RollbackConfig(*in, *out, c); err != nil { return err } } else { @@ -1232,7 +315,8 @@ func deepCopy_v1beta1_DeploymentSpec(in DeploymentSpec, out *DeploymentSpec, c * return nil } -func deepCopy_v1beta1_DeploymentStatus(in DeploymentStatus, out *DeploymentStatus, c *conversion.Cloner) error { +func DeepCopy_v1beta1_DeploymentStatus(in DeploymentStatus, out *DeploymentStatus, c *conversion.Cloner) error { + out.ObservedGeneration = in.ObservedGeneration out.Replicas = in.Replicas out.UpdatedReplicas = in.UpdatedReplicas out.AvailableReplicas = in.AvailableReplicas @@ -1240,11 +324,12 @@ func deepCopy_v1beta1_DeploymentStatus(in DeploymentStatus, out *DeploymentStatu return nil } -func deepCopy_v1beta1_DeploymentStrategy(in DeploymentStrategy, out *DeploymentStrategy, c *conversion.Cloner) error { +func DeepCopy_v1beta1_DeploymentStrategy(in DeploymentStrategy, out *DeploymentStrategy, c *conversion.Cloner) error { out.Type = in.Type if in.RollingUpdate != nil { - out.RollingUpdate = new(RollingUpdateDeployment) - if err := deepCopy_v1beta1_RollingUpdateDeployment(*in.RollingUpdate, out.RollingUpdate, c); err != nil { + in, out := in.RollingUpdate, &out.RollingUpdate + *out = new(RollingUpdateDeployment) + if err := DeepCopy_v1beta1_RollingUpdateDeployment(*in, *out, c); err != nil { return err } } else { @@ -1253,19 +338,29 @@ func deepCopy_v1beta1_DeploymentStrategy(in DeploymentStrategy, out *DeploymentS return nil } -func deepCopy_v1beta1_HTTPIngressPath(in HTTPIngressPath, out *HTTPIngressPath, c *conversion.Cloner) error { +func DeepCopy_v1beta1_ExportOptions(in ExportOptions, out *ExportOptions, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + out.Export = in.Export + out.Exact = in.Exact + return nil +} + +func DeepCopy_v1beta1_HTTPIngressPath(in HTTPIngressPath, out *HTTPIngressPath, c *conversion.Cloner) error { out.Path = in.Path - if err := deepCopy_v1beta1_IngressBackend(in.Backend, &out.Backend, c); err != nil { + if err := DeepCopy_v1beta1_IngressBackend(in.Backend, &out.Backend, c); err != nil { return err } return nil } -func deepCopy_v1beta1_HTTPIngressRuleValue(in HTTPIngressRuleValue, out *HTTPIngressRuleValue, c *conversion.Cloner) error { +func DeepCopy_v1beta1_HTTPIngressRuleValue(in HTTPIngressRuleValue, out *HTTPIngressRuleValue, c *conversion.Cloner) error { if in.Paths != nil { - out.Paths = make([]HTTPIngressPath, len(in.Paths)) - for i := range in.Paths { - if err := deepCopy_v1beta1_HTTPIngressPath(in.Paths[i], &out.Paths[i], c); err != nil { + in, out := in.Paths, &out.Paths + *out = make([]HTTPIngressPath, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_HTTPIngressPath(in[i], &(*out)[i], c); err != nil { return err } } @@ -1275,33 +370,34 @@ func deepCopy_v1beta1_HTTPIngressRuleValue(in HTTPIngressRuleValue, out *HTTPIng return nil } -func deepCopy_v1beta1_HorizontalPodAutoscaler(in HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_HorizontalPodAutoscaler(in HorizontalPodAutoscaler, out *HorizontalPodAutoscaler, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1beta1_HorizontalPodAutoscalerSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1beta1_HorizontalPodAutoscalerSpec(in.Spec, &out.Spec, c); err != nil { return err } - if err := deepCopy_v1beta1_HorizontalPodAutoscalerStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1beta1_HorizontalPodAutoscalerStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1beta1_HorizontalPodAutoscalerList(in HorizontalPodAutoscalerList, out *HorizontalPodAutoscalerList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_HorizontalPodAutoscalerList(in HorizontalPodAutoscalerList, out *HorizontalPodAutoscalerList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]HorizontalPodAutoscaler, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1beta1_HorizontalPodAutoscaler(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]HorizontalPodAutoscaler, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_HorizontalPodAutoscaler(in[i], &(*out)[i], c); err != nil { return err } } @@ -1311,20 +407,22 @@ func deepCopy_v1beta1_HorizontalPodAutoscalerList(in HorizontalPodAutoscalerList return nil } -func deepCopy_v1beta1_HorizontalPodAutoscalerSpec(in HorizontalPodAutoscalerSpec, out *HorizontalPodAutoscalerSpec, c *conversion.Cloner) error { - if err := deepCopy_v1beta1_SubresourceReference(in.ScaleRef, &out.ScaleRef, c); err != nil { +func DeepCopy_v1beta1_HorizontalPodAutoscalerSpec(in HorizontalPodAutoscalerSpec, out *HorizontalPodAutoscalerSpec, c *conversion.Cloner) error { + if err := DeepCopy_v1beta1_SubresourceReference(in.ScaleRef, &out.ScaleRef, c); err != nil { return err } if in.MinReplicas != nil { - out.MinReplicas = new(int32) - *out.MinReplicas = *in.MinReplicas + in, out := in.MinReplicas, &out.MinReplicas + *out = new(int32) + **out = *in } else { out.MinReplicas = nil } out.MaxReplicas = in.MaxReplicas if in.CPUUtilization != nil { - out.CPUUtilization = new(CPUTargetUtilization) - if err := deepCopy_v1beta1_CPUTargetUtilization(*in.CPUUtilization, out.CPUUtilization, c); err != nil { + in, out := in.CPUUtilization, &out.CPUUtilization + *out = new(CPUTargetUtilization) + if err := DeepCopy_v1beta1_CPUTargetUtilization(*in, *out, c); err != nil { return err } } else { @@ -1333,16 +431,18 @@ func deepCopy_v1beta1_HorizontalPodAutoscalerSpec(in HorizontalPodAutoscalerSpec return nil } -func deepCopy_v1beta1_HorizontalPodAutoscalerStatus(in HorizontalPodAutoscalerStatus, out *HorizontalPodAutoscalerStatus, c *conversion.Cloner) error { +func DeepCopy_v1beta1_HorizontalPodAutoscalerStatus(in HorizontalPodAutoscalerStatus, out *HorizontalPodAutoscalerStatus, c *conversion.Cloner) error { if in.ObservedGeneration != nil { - out.ObservedGeneration = new(int64) - *out.ObservedGeneration = *in.ObservedGeneration + in, out := in.ObservedGeneration, &out.ObservedGeneration + *out = new(int64) + **out = *in } else { out.ObservedGeneration = nil } if in.LastScaleTime != nil { - out.LastScaleTime = new(unversioned.Time) - if err := deepCopy_unversioned_Time(*in.LastScaleTime, out.LastScaleTime, c); err != nil { + in, out := in.LastScaleTime, &out.LastScaleTime + *out = new(unversioned.Time) + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { return err } } else { @@ -1351,61 +451,63 @@ func deepCopy_v1beta1_HorizontalPodAutoscalerStatus(in HorizontalPodAutoscalerSt out.CurrentReplicas = in.CurrentReplicas out.DesiredReplicas = in.DesiredReplicas if in.CurrentCPUUtilizationPercentage != nil { - out.CurrentCPUUtilizationPercentage = new(int32) - *out.CurrentCPUUtilizationPercentage = *in.CurrentCPUUtilizationPercentage + in, out := in.CurrentCPUUtilizationPercentage, &out.CurrentCPUUtilizationPercentage + *out = new(int32) + **out = *in } else { out.CurrentCPUUtilizationPercentage = nil } return nil } -func deepCopy_v1beta1_HostPortRange(in HostPortRange, out *HostPortRange, c *conversion.Cloner) error { +func DeepCopy_v1beta1_HostPortRange(in HostPortRange, out *HostPortRange, c *conversion.Cloner) error { out.Min = in.Min out.Max = in.Max return nil } -func deepCopy_v1beta1_IDRange(in IDRange, out *IDRange, c *conversion.Cloner) error { +func DeepCopy_v1beta1_IDRange(in IDRange, out *IDRange, c *conversion.Cloner) error { out.Min = in.Min out.Max = in.Max return nil } -func deepCopy_v1beta1_Ingress(in Ingress, out *Ingress, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_Ingress(in Ingress, out *Ingress, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1beta1_IngressSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1beta1_IngressSpec(in.Spec, &out.Spec, c); err != nil { return err } - if err := deepCopy_v1beta1_IngressStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1beta1_IngressStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1beta1_IngressBackend(in IngressBackend, out *IngressBackend, c *conversion.Cloner) error { +func DeepCopy_v1beta1_IngressBackend(in IngressBackend, out *IngressBackend, c *conversion.Cloner) error { out.ServiceName = in.ServiceName - if err := deepCopy_intstr_IntOrString(in.ServicePort, &out.ServicePort, c); err != nil { + if err := intstr.DeepCopy_intstr_IntOrString(in.ServicePort, &out.ServicePort, c); err != nil { return err } return nil } -func deepCopy_v1beta1_IngressList(in IngressList, out *IngressList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_IngressList(in IngressList, out *IngressList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]Ingress, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1beta1_Ingress(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]Ingress, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_Ingress(in[i], &(*out)[i], c); err != nil { return err } } @@ -1415,18 +517,19 @@ func deepCopy_v1beta1_IngressList(in IngressList, out *IngressList, c *conversio return nil } -func deepCopy_v1beta1_IngressRule(in IngressRule, out *IngressRule, c *conversion.Cloner) error { +func DeepCopy_v1beta1_IngressRule(in IngressRule, out *IngressRule, c *conversion.Cloner) error { out.Host = in.Host - if err := deepCopy_v1beta1_IngressRuleValue(in.IngressRuleValue, &out.IngressRuleValue, c); err != nil { + if err := DeepCopy_v1beta1_IngressRuleValue(in.IngressRuleValue, &out.IngressRuleValue, c); err != nil { return err } return nil } -func deepCopy_v1beta1_IngressRuleValue(in IngressRuleValue, out *IngressRuleValue, c *conversion.Cloner) error { +func DeepCopy_v1beta1_IngressRuleValue(in IngressRuleValue, out *IngressRuleValue, c *conversion.Cloner) error { if in.HTTP != nil { - out.HTTP = new(HTTPIngressRuleValue) - if err := deepCopy_v1beta1_HTTPIngressRuleValue(*in.HTTP, out.HTTP, c); err != nil { + in, out := in.HTTP, &out.HTTP + *out = new(HTTPIngressRuleValue) + if err := DeepCopy_v1beta1_HTTPIngressRuleValue(*in, *out, c); err != nil { return err } } else { @@ -1435,19 +538,21 @@ func deepCopy_v1beta1_IngressRuleValue(in IngressRuleValue, out *IngressRuleValu return nil } -func deepCopy_v1beta1_IngressSpec(in IngressSpec, out *IngressSpec, c *conversion.Cloner) error { +func DeepCopy_v1beta1_IngressSpec(in IngressSpec, out *IngressSpec, c *conversion.Cloner) error { if in.Backend != nil { - out.Backend = new(IngressBackend) - if err := deepCopy_v1beta1_IngressBackend(*in.Backend, out.Backend, c); err != nil { + in, out := in.Backend, &out.Backend + *out = new(IngressBackend) + if err := DeepCopy_v1beta1_IngressBackend(*in, *out, c); err != nil { return err } } else { out.Backend = nil } if in.TLS != nil { - out.TLS = make([]IngressTLS, len(in.TLS)) - for i := range in.TLS { - if err := deepCopy_v1beta1_IngressTLS(in.TLS[i], &out.TLS[i], c); err != nil { + in, out := in.TLS, &out.TLS + *out = make([]IngressTLS, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_IngressTLS(in[i], &(*out)[i], c); err != nil { return err } } @@ -1455,9 +560,10 @@ func deepCopy_v1beta1_IngressSpec(in IngressSpec, out *IngressSpec, c *conversio out.TLS = nil } if in.Rules != nil { - out.Rules = make([]IngressRule, len(in.Rules)) - for i := range in.Rules { - if err := deepCopy_v1beta1_IngressRule(in.Rules[i], &out.Rules[i], c); err != nil { + in, out := in.Rules, &out.Rules + *out = make([]IngressRule, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_IngressRule(in[i], &(*out)[i], c); err != nil { return err } } @@ -1467,19 +573,18 @@ func deepCopy_v1beta1_IngressSpec(in IngressSpec, out *IngressSpec, c *conversio return nil } -func deepCopy_v1beta1_IngressStatus(in IngressStatus, out *IngressStatus, c *conversion.Cloner) error { - if err := deepCopy_v1_LoadBalancerStatus(in.LoadBalancer, &out.LoadBalancer, c); err != nil { +func DeepCopy_v1beta1_IngressStatus(in IngressStatus, out *IngressStatus, c *conversion.Cloner) error { + if err := v1.DeepCopy_v1_LoadBalancerStatus(in.LoadBalancer, &out.LoadBalancer, c); err != nil { return err } return nil } -func deepCopy_v1beta1_IngressTLS(in IngressTLS, out *IngressTLS, c *conversion.Cloner) error { +func DeepCopy_v1beta1_IngressTLS(in IngressTLS, out *IngressTLS, c *conversion.Cloner) error { if in.Hosts != nil { - out.Hosts = make([]string, len(in.Hosts)) - for i := range in.Hosts { - out.Hosts[i] = in.Hosts[i] - } + in, out := in.Hosts, &out.Hosts + *out = make([]string, len(in)) + copy(*out, in) } else { out.Hosts = nil } @@ -1487,29 +592,29 @@ func deepCopy_v1beta1_IngressTLS(in IngressTLS, out *IngressTLS, c *conversion.C return nil } -func deepCopy_v1beta1_Job(in Job, out *Job, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_Job(in Job, out *Job, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1beta1_JobSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1beta1_JobSpec(in.Spec, &out.Spec, c); err != nil { return err } - if err := deepCopy_v1beta1_JobStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1beta1_JobStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1beta1_JobCondition(in JobCondition, out *JobCondition, c *conversion.Cloner) error { +func DeepCopy_v1beta1_JobCondition(in JobCondition, out *JobCondition, c *conversion.Cloner) error { out.Type = in.Type out.Status = in.Status - if err := deepCopy_unversioned_Time(in.LastProbeTime, &out.LastProbeTime, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.LastProbeTime, &out.LastProbeTime, c); err != nil { return err } - if err := deepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil { + if err := unversioned.DeepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil { return err } out.Reason = in.Reason @@ -1517,17 +622,18 @@ func deepCopy_v1beta1_JobCondition(in JobCondition, out *JobCondition, c *conver return nil } -func deepCopy_v1beta1_JobList(in JobList, out *JobList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_JobList(in JobList, out *JobList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]Job, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1beta1_Job(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]Job, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_Job(in[i], &(*out)[i], c); err != nil { return err } } @@ -1537,44 +643,56 @@ func deepCopy_v1beta1_JobList(in JobList, out *JobList, c *conversion.Cloner) er return nil } -func deepCopy_v1beta1_JobSpec(in JobSpec, out *JobSpec, c *conversion.Cloner) error { +func DeepCopy_v1beta1_JobSpec(in JobSpec, out *JobSpec, c *conversion.Cloner) error { if in.Parallelism != nil { - out.Parallelism = new(int32) - *out.Parallelism = *in.Parallelism + in, out := in.Parallelism, &out.Parallelism + *out = new(int32) + **out = *in } else { out.Parallelism = nil } if in.Completions != nil { - out.Completions = new(int32) - *out.Completions = *in.Completions + in, out := in.Completions, &out.Completions + *out = new(int32) + **out = *in } else { out.Completions = nil } if in.ActiveDeadlineSeconds != nil { - out.ActiveDeadlineSeconds = new(int64) - *out.ActiveDeadlineSeconds = *in.ActiveDeadlineSeconds + in, out := in.ActiveDeadlineSeconds, &out.ActiveDeadlineSeconds + *out = new(int64) + **out = *in } else { out.ActiveDeadlineSeconds = nil } if in.Selector != nil { - out.Selector = new(LabelSelector) - if err := deepCopy_v1beta1_LabelSelector(*in.Selector, out.Selector, c); err != nil { + in, out := in.Selector, &out.Selector + *out = new(LabelSelector) + if err := DeepCopy_v1beta1_LabelSelector(*in, *out, c); err != nil { return err } } else { out.Selector = nil } - if err := deepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil { + if in.AutoSelector != nil { + in, out := in.AutoSelector, &out.AutoSelector + *out = new(bool) + **out = *in + } else { + out.AutoSelector = nil + } + if err := v1.DeepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil { return err } return nil } -func deepCopy_v1beta1_JobStatus(in JobStatus, out *JobStatus, c *conversion.Cloner) error { +func DeepCopy_v1beta1_JobStatus(in JobStatus, out *JobStatus, c *conversion.Cloner) error { if in.Conditions != nil { - out.Conditions = make([]JobCondition, len(in.Conditions)) - for i := range in.Conditions { - if err := deepCopy_v1beta1_JobCondition(in.Conditions[i], &out.Conditions[i], c); err != nil { + in, out := in.Conditions, &out.Conditions + *out = make([]JobCondition, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_JobCondition(in[i], &(*out)[i], c); err != nil { return err } } @@ -1582,16 +700,18 @@ func deepCopy_v1beta1_JobStatus(in JobStatus, out *JobStatus, c *conversion.Clon out.Conditions = nil } if in.StartTime != nil { - out.StartTime = new(unversioned.Time) - if err := deepCopy_unversioned_Time(*in.StartTime, out.StartTime, c); err != nil { + in, out := in.StartTime, &out.StartTime + *out = new(unversioned.Time) + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { return err } } else { out.StartTime = nil } if in.CompletionTime != nil { - out.CompletionTime = new(unversioned.Time) - if err := deepCopy_unversioned_Time(*in.CompletionTime, out.CompletionTime, c); err != nil { + in, out := in.CompletionTime, &out.CompletionTime + *out = new(unversioned.Time) + if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil { return err } } else { @@ -1603,19 +723,21 @@ func deepCopy_v1beta1_JobStatus(in JobStatus, out *JobStatus, c *conversion.Clon return nil } -func deepCopy_v1beta1_LabelSelector(in LabelSelector, out *LabelSelector, c *conversion.Cloner) error { +func DeepCopy_v1beta1_LabelSelector(in LabelSelector, out *LabelSelector, c *conversion.Cloner) error { if in.MatchLabels != nil { - out.MatchLabels = make(map[string]string) - for key, val := range in.MatchLabels { - out.MatchLabels[key] = val + in, out := in.MatchLabels, &out.MatchLabels + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val } } else { out.MatchLabels = nil } if in.MatchExpressions != nil { - out.MatchExpressions = make([]LabelSelectorRequirement, len(in.MatchExpressions)) - for i := range in.MatchExpressions { - if err := deepCopy_v1beta1_LabelSelectorRequirement(in.MatchExpressions[i], &out.MatchExpressions[i], c); err != nil { + in, out := in.MatchExpressions, &out.MatchExpressions + *out = make([]LabelSelectorRequirement, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_LabelSelectorRequirement(in[i], &(*out)[i], c); err != nil { return err } } @@ -1625,22 +747,21 @@ func deepCopy_v1beta1_LabelSelector(in LabelSelector, out *LabelSelector, c *con return nil } -func deepCopy_v1beta1_LabelSelectorRequirement(in LabelSelectorRequirement, out *LabelSelectorRequirement, c *conversion.Cloner) error { +func DeepCopy_v1beta1_LabelSelectorRequirement(in LabelSelectorRequirement, out *LabelSelectorRequirement, c *conversion.Cloner) error { out.Key = in.Key out.Operator = in.Operator if in.Values != nil { - out.Values = make([]string, len(in.Values)) - for i := range in.Values { - out.Values[i] = in.Values[i] - } + in, out := in.Values, &out.Values + *out = make([]string, len(in)) + copy(*out, in) } else { out.Values = nil } return nil } -func deepCopy_v1beta1_ListOptions(in ListOptions, out *ListOptions, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_ListOptions(in ListOptions, out *ListOptions, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } out.LabelSelector = in.LabelSelector @@ -1648,44 +769,40 @@ func deepCopy_v1beta1_ListOptions(in ListOptions, out *ListOptions, c *conversio out.Watch = in.Watch out.ResourceVersion = in.ResourceVersion if in.TimeoutSeconds != nil { - out.TimeoutSeconds = new(int64) - *out.TimeoutSeconds = *in.TimeoutSeconds + in, out := in.TimeoutSeconds, &out.TimeoutSeconds + *out = new(int64) + **out = *in } else { out.TimeoutSeconds = nil } return nil } -func deepCopy_v1beta1_NodeUtilization(in NodeUtilization, out *NodeUtilization, c *conversion.Cloner) error { - out.Resource = in.Resource - out.Value = in.Value - return nil -} - -func deepCopy_v1beta1_PodSecurityPolicy(in PodSecurityPolicy, out *PodSecurityPolicy, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_PodSecurityPolicy(in PodSecurityPolicy, out *PodSecurityPolicy, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1beta1_PodSecurityPolicySpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1beta1_PodSecurityPolicySpec(in.Spec, &out.Spec, c); err != nil { return err } return nil } -func deepCopy_v1beta1_PodSecurityPolicyList(in PodSecurityPolicyList, out *PodSecurityPolicyList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_PodSecurityPolicyList(in PodSecurityPolicyList, out *PodSecurityPolicyList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]PodSecurityPolicy, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1beta1_PodSecurityPolicy(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]PodSecurityPolicy, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_PodSecurityPolicy(in[i], &(*out)[i], c); err != nil { return err } } @@ -1695,29 +812,32 @@ func deepCopy_v1beta1_PodSecurityPolicyList(in PodSecurityPolicyList, out *PodSe return nil } -func deepCopy_v1beta1_PodSecurityPolicySpec(in PodSecurityPolicySpec, out *PodSecurityPolicySpec, c *conversion.Cloner) error { +func DeepCopy_v1beta1_PodSecurityPolicySpec(in PodSecurityPolicySpec, out *PodSecurityPolicySpec, c *conversion.Cloner) error { out.Privileged = in.Privileged if in.Capabilities != nil { - out.Capabilities = make([]v1.Capability, len(in.Capabilities)) - for i := range in.Capabilities { - out.Capabilities[i] = in.Capabilities[i] + in, out := in.Capabilities, &out.Capabilities + *out = make([]v1.Capability, len(in)) + for i := range in { + (*out)[i] = in[i] } } else { out.Capabilities = nil } if in.Volumes != nil { - out.Volumes = make([]FSType, len(in.Volumes)) - for i := range in.Volumes { - out.Volumes[i] = in.Volumes[i] + in, out := in.Volumes, &out.Volumes + *out = make([]FSType, len(in)) + for i := range in { + (*out)[i] = in[i] } } else { out.Volumes = nil } out.HostNetwork = in.HostNetwork if in.HostPorts != nil { - out.HostPorts = make([]HostPortRange, len(in.HostPorts)) - for i := range in.HostPorts { - if err := deepCopy_v1beta1_HostPortRange(in.HostPorts[i], &out.HostPorts[i], c); err != nil { + in, out := in.HostPorts, &out.HostPorts + *out = make([]HostPortRange, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_HostPortRange(in[i], &(*out)[i], c); err != nil { return err } } @@ -1726,42 +846,43 @@ func deepCopy_v1beta1_PodSecurityPolicySpec(in PodSecurityPolicySpec, out *PodSe } out.HostPID = in.HostPID out.HostIPC = in.HostIPC - if err := deepCopy_v1beta1_SELinuxContextStrategyOptions(in.SELinuxContext, &out.SELinuxContext, c); err != nil { + if err := DeepCopy_v1beta1_SELinuxStrategyOptions(in.SELinux, &out.SELinux, c); err != nil { return err } - if err := deepCopy_v1beta1_RunAsUserStrategyOptions(in.RunAsUser, &out.RunAsUser, c); err != nil { + if err := DeepCopy_v1beta1_RunAsUserStrategyOptions(in.RunAsUser, &out.RunAsUser, c); err != nil { return err } return nil } -func deepCopy_v1beta1_ReplicaSet(in ReplicaSet, out *ReplicaSet, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_ReplicaSet(in ReplicaSet, out *ReplicaSet, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1beta1_ReplicaSetSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1beta1_ReplicaSetSpec(in.Spec, &out.Spec, c); err != nil { return err } - if err := deepCopy_v1beta1_ReplicaSetStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1beta1_ReplicaSetStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1beta1_ReplicaSetList(in ReplicaSetList, out *ReplicaSetList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_ReplicaSetList(in ReplicaSetList, out *ReplicaSetList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]ReplicaSet, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1beta1_ReplicaSet(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]ReplicaSet, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_ReplicaSet(in[i], &(*out)[i], c); err != nil { return err } } @@ -1771,62 +892,62 @@ func deepCopy_v1beta1_ReplicaSetList(in ReplicaSetList, out *ReplicaSetList, c * return nil } -func deepCopy_v1beta1_ReplicaSetSpec(in ReplicaSetSpec, out *ReplicaSetSpec, c *conversion.Cloner) error { +func DeepCopy_v1beta1_ReplicaSetSpec(in ReplicaSetSpec, out *ReplicaSetSpec, c *conversion.Cloner) error { if in.Replicas != nil { - out.Replicas = new(int32) - *out.Replicas = *in.Replicas + in, out := in.Replicas, &out.Replicas + *out = new(int32) + **out = *in } else { out.Replicas = nil } if in.Selector != nil { - out.Selector = new(LabelSelector) - if err := deepCopy_v1beta1_LabelSelector(*in.Selector, out.Selector, c); err != nil { + in, out := in.Selector, &out.Selector + *out = new(LabelSelector) + if err := DeepCopy_v1beta1_LabelSelector(*in, *out, c); err != nil { return err } } else { out.Selector = nil } - if in.Template != nil { - out.Template = new(v1.PodTemplateSpec) - if err := deepCopy_v1_PodTemplateSpec(*in.Template, out.Template, c); err != nil { - return err - } - } else { - out.Template = nil - } - return nil -} - -func deepCopy_v1beta1_ReplicaSetStatus(in ReplicaSetStatus, out *ReplicaSetStatus, c *conversion.Cloner) error { - out.Replicas = in.Replicas - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -func deepCopy_v1beta1_ReplicationControllerDummy(in ReplicationControllerDummy, out *ReplicationControllerDummy, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + if err := v1.DeepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil { return err } return nil } -func deepCopy_v1beta1_RollbackConfig(in RollbackConfig, out *RollbackConfig, c *conversion.Cloner) error { +func DeepCopy_v1beta1_ReplicaSetStatus(in ReplicaSetStatus, out *ReplicaSetStatus, c *conversion.Cloner) error { + out.Replicas = in.Replicas + out.FullyLabeledReplicas = in.FullyLabeledReplicas + out.ObservedGeneration = in.ObservedGeneration + return nil +} + +func DeepCopy_v1beta1_ReplicationControllerDummy(in ReplicationControllerDummy, out *ReplicationControllerDummy, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + return nil +} + +func DeepCopy_v1beta1_RollbackConfig(in RollbackConfig, out *RollbackConfig, c *conversion.Cloner) error { out.Revision = in.Revision return nil } -func deepCopy_v1beta1_RollingUpdateDeployment(in RollingUpdateDeployment, out *RollingUpdateDeployment, c *conversion.Cloner) error { +func DeepCopy_v1beta1_RollingUpdateDeployment(in RollingUpdateDeployment, out *RollingUpdateDeployment, c *conversion.Cloner) error { if in.MaxUnavailable != nil { - out.MaxUnavailable = new(intstr.IntOrString) - if err := deepCopy_intstr_IntOrString(*in.MaxUnavailable, out.MaxUnavailable, c); err != nil { + in, out := in.MaxUnavailable, &out.MaxUnavailable + *out = new(intstr.IntOrString) + if err := intstr.DeepCopy_intstr_IntOrString(*in, *out, c); err != nil { return err } } else { out.MaxUnavailable = nil } if in.MaxSurge != nil { - out.MaxSurge = new(intstr.IntOrString) - if err := deepCopy_intstr_IntOrString(*in.MaxSurge, out.MaxSurge, c); err != nil { + in, out := in.MaxSurge, &out.MaxSurge + *out = new(intstr.IntOrString) + if err := intstr.DeepCopy_intstr_IntOrString(*in, *out, c); err != nil { return err } } else { @@ -1835,12 +956,13 @@ func deepCopy_v1beta1_RollingUpdateDeployment(in RollingUpdateDeployment, out *R return nil } -func deepCopy_v1beta1_RunAsUserStrategyOptions(in RunAsUserStrategyOptions, out *RunAsUserStrategyOptions, c *conversion.Cloner) error { - out.Type = in.Type +func DeepCopy_v1beta1_RunAsUserStrategyOptions(in RunAsUserStrategyOptions, out *RunAsUserStrategyOptions, c *conversion.Cloner) error { + out.Rule = in.Rule if in.Ranges != nil { - out.Ranges = make([]IDRange, len(in.Ranges)) - for i := range in.Ranges { - if err := deepCopy_v1beta1_IDRange(in.Ranges[i], &out.Ranges[i], c); err != nil { + in, out := in.Ranges, &out.Ranges + *out = make([]IDRange, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_IDRange(in[i], &(*out)[i], c); err != nil { return err } } @@ -1850,11 +972,12 @@ func deepCopy_v1beta1_RunAsUserStrategyOptions(in RunAsUserStrategyOptions, out return nil } -func deepCopy_v1beta1_SELinuxContextStrategyOptions(in SELinuxContextStrategyOptions, out *SELinuxContextStrategyOptions, c *conversion.Cloner) error { - out.Type = in.Type +func DeepCopy_v1beta1_SELinuxStrategyOptions(in SELinuxStrategyOptions, out *SELinuxStrategyOptions, c *conversion.Cloner) error { + out.Rule = in.Rule if in.SELinuxOptions != nil { - out.SELinuxOptions = new(v1.SELinuxOptions) - if err := deepCopy_v1_SELinuxOptions(*in.SELinuxOptions, out.SELinuxOptions, c); err != nil { + in, out := in.SELinuxOptions, &out.SELinuxOptions + *out = new(v1.SELinuxOptions) + if err := v1.DeepCopy_v1_SELinuxOptions(*in, *out, c); err != nil { return err } } else { @@ -1863,41 +986,43 @@ func deepCopy_v1beta1_SELinuxContextStrategyOptions(in SELinuxContextStrategyOpt return nil } -func deepCopy_v1beta1_Scale(in Scale, out *Scale, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_Scale(in Scale, out *Scale, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } - if err := deepCopy_v1beta1_ScaleSpec(in.Spec, &out.Spec, c); err != nil { + if err := DeepCopy_v1beta1_ScaleSpec(in.Spec, &out.Spec, c); err != nil { return err } - if err := deepCopy_v1beta1_ScaleStatus(in.Status, &out.Status, c); err != nil { + if err := DeepCopy_v1beta1_ScaleStatus(in.Status, &out.Status, c); err != nil { return err } return nil } -func deepCopy_v1beta1_ScaleSpec(in ScaleSpec, out *ScaleSpec, c *conversion.Cloner) error { +func DeepCopy_v1beta1_ScaleSpec(in ScaleSpec, out *ScaleSpec, c *conversion.Cloner) error { out.Replicas = in.Replicas return nil } -func deepCopy_v1beta1_ScaleStatus(in ScaleStatus, out *ScaleStatus, c *conversion.Cloner) error { +func DeepCopy_v1beta1_ScaleStatus(in ScaleStatus, out *ScaleStatus, c *conversion.Cloner) error { out.Replicas = in.Replicas if in.Selector != nil { - out.Selector = make(map[string]string) - for key, val := range in.Selector { - out.Selector[key] = val + in, out := in.Selector, &out.Selector + *out = make(map[string]string) + for key, val := range in { + (*out)[key] = val } } else { out.Selector = nil } + out.TargetSelector = in.TargetSelector return nil } -func deepCopy_v1beta1_SubresourceReference(in SubresourceReference, out *SubresourceReference, c *conversion.Cloner) error { +func DeepCopy_v1beta1_SubresourceReference(in SubresourceReference, out *SubresourceReference, c *conversion.Cloner) error { out.Kind = in.Kind out.Name = in.Name out.APIVersion = in.APIVersion @@ -1905,18 +1030,19 @@ func deepCopy_v1beta1_SubresourceReference(in SubresourceReference, out *Subreso return nil } -func deepCopy_v1beta1_ThirdPartyResource(in ThirdPartyResource, out *ThirdPartyResource, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_ThirdPartyResource(in ThirdPartyResource, out *ThirdPartyResource, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } out.Description = in.Description if in.Versions != nil { - out.Versions = make([]APIVersion, len(in.Versions)) - for i := range in.Versions { - if err := deepCopy_v1beta1_APIVersion(in.Versions[i], &out.Versions[i], c); err != nil { + in, out := in.Versions, &out.Versions + *out = make([]APIVersion, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_APIVersion(in[i], &(*out)[i], c); err != nil { return err } } @@ -1926,35 +1052,35 @@ func deepCopy_v1beta1_ThirdPartyResource(in ThirdPartyResource, out *ThirdPartyR return nil } -func deepCopy_v1beta1_ThirdPartyResourceData(in ThirdPartyResourceData, out *ThirdPartyResourceData, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_ThirdPartyResourceData(in ThirdPartyResourceData, out *ThirdPartyResourceData, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { + if err := v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil { return err } if in.Data != nil { - out.Data = make([]uint8, len(in.Data)) - for i := range in.Data { - out.Data[i] = in.Data[i] - } + in, out := in.Data, &out.Data + *out = make([]byte, len(in)) + copy(*out, in) } else { out.Data = nil } return nil } -func deepCopy_v1beta1_ThirdPartyResourceDataList(in ThirdPartyResourceDataList, out *ThirdPartyResourceDataList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_ThirdPartyResourceDataList(in ThirdPartyResourceDataList, out *ThirdPartyResourceDataList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]ThirdPartyResourceData, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1beta1_ThirdPartyResourceData(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]ThirdPartyResourceData, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_ThirdPartyResourceData(in[i], &(*out)[i], c); err != nil { return err } } @@ -1964,17 +1090,18 @@ func deepCopy_v1beta1_ThirdPartyResourceDataList(in ThirdPartyResourceDataList, return nil } -func deepCopy_v1beta1_ThirdPartyResourceList(in ThirdPartyResourceList, out *ThirdPartyResourceList, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { +func DeepCopy_v1beta1_ThirdPartyResourceList(in ThirdPartyResourceList, out *ThirdPartyResourceList, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { return err } - if err := deepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { + if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil { return err } if in.Items != nil { - out.Items = make([]ThirdPartyResource, len(in.Items)) - for i := range in.Items { - if err := deepCopy_v1beta1_ThirdPartyResource(in.Items[i], &out.Items[i], c); err != nil { + in, out := in.Items, &out.Items + *out = make([]ThirdPartyResource, len(in)) + for i := range in { + if err := DeepCopy_v1beta1_ThirdPartyResource(in[i], &(*out)[i], c); err != nil { return err } } @@ -1983,133 +1110,3 @@ func deepCopy_v1beta1_ThirdPartyResourceList(in ThirdPartyResourceList, out *Thi } return nil } - -func deepCopy_intstr_IntOrString(in intstr.IntOrString, out *intstr.IntOrString, c *conversion.Cloner) error { - out.Type = in.Type - out.IntVal = in.IntVal - out.StrVal = in.StrVal - return nil -} - -func init() { - err := api.Scheme.AddGeneratedDeepCopyFuncs( - deepCopy_resource_Quantity, - deepCopy_unversioned_ListMeta, - deepCopy_unversioned_Time, - deepCopy_unversioned_TypeMeta, - deepCopy_v1_AWSElasticBlockStoreVolumeSource, - deepCopy_v1_AzureFileVolumeSource, - deepCopy_v1_Capabilities, - deepCopy_v1_CephFSVolumeSource, - deepCopy_v1_CinderVolumeSource, - deepCopy_v1_ConfigMapKeySelector, - deepCopy_v1_ConfigMapVolumeSource, - deepCopy_v1_Container, - deepCopy_v1_ContainerPort, - deepCopy_v1_DownwardAPIVolumeFile, - deepCopy_v1_DownwardAPIVolumeSource, - deepCopy_v1_EmptyDirVolumeSource, - deepCopy_v1_EnvVar, - deepCopy_v1_EnvVarSource, - deepCopy_v1_ExecAction, - deepCopy_v1_FCVolumeSource, - deepCopy_v1_FlexVolumeSource, - deepCopy_v1_FlockerVolumeSource, - deepCopy_v1_GCEPersistentDiskVolumeSource, - deepCopy_v1_GitRepoVolumeSource, - deepCopy_v1_GlusterfsVolumeSource, - deepCopy_v1_HTTPGetAction, - deepCopy_v1_HTTPHeader, - deepCopy_v1_Handler, - deepCopy_v1_HostPathVolumeSource, - deepCopy_v1_ISCSIVolumeSource, - deepCopy_v1_KeyToPath, - deepCopy_v1_Lifecycle, - deepCopy_v1_LoadBalancerIngress, - deepCopy_v1_LoadBalancerStatus, - deepCopy_v1_LocalObjectReference, - deepCopy_v1_NFSVolumeSource, - deepCopy_v1_ObjectFieldSelector, - deepCopy_v1_ObjectMeta, - deepCopy_v1_PersistentVolumeClaimVolumeSource, - deepCopy_v1_PodSecurityContext, - deepCopy_v1_PodSpec, - deepCopy_v1_PodTemplateSpec, - deepCopy_v1_Probe, - deepCopy_v1_RBDVolumeSource, - deepCopy_v1_ResourceRequirements, - deepCopy_v1_SELinuxOptions, - deepCopy_v1_SecretKeySelector, - deepCopy_v1_SecretVolumeSource, - deepCopy_v1_SecurityContext, - deepCopy_v1_TCPSocketAction, - deepCopy_v1_Volume, - deepCopy_v1_VolumeMount, - deepCopy_v1_VolumeSource, - deepCopy_v1beta1_APIVersion, - deepCopy_v1beta1_CPUTargetUtilization, - deepCopy_v1beta1_ClusterAutoscaler, - deepCopy_v1beta1_ClusterAutoscalerList, - deepCopy_v1beta1_ClusterAutoscalerSpec, - deepCopy_v1beta1_DaemonSet, - deepCopy_v1beta1_DaemonSetList, - deepCopy_v1beta1_DaemonSetSpec, - deepCopy_v1beta1_DaemonSetStatus, - deepCopy_v1beta1_Deployment, - deepCopy_v1beta1_DeploymentList, - deepCopy_v1beta1_DeploymentRollback, - deepCopy_v1beta1_DeploymentSpec, - deepCopy_v1beta1_DeploymentStatus, - deepCopy_v1beta1_DeploymentStrategy, - deepCopy_v1beta1_HTTPIngressPath, - deepCopy_v1beta1_HTTPIngressRuleValue, - deepCopy_v1beta1_HorizontalPodAutoscaler, - deepCopy_v1beta1_HorizontalPodAutoscalerList, - deepCopy_v1beta1_HorizontalPodAutoscalerSpec, - deepCopy_v1beta1_HorizontalPodAutoscalerStatus, - deepCopy_v1beta1_HostPortRange, - deepCopy_v1beta1_IDRange, - deepCopy_v1beta1_Ingress, - deepCopy_v1beta1_IngressBackend, - deepCopy_v1beta1_IngressList, - deepCopy_v1beta1_IngressRule, - deepCopy_v1beta1_IngressRuleValue, - deepCopy_v1beta1_IngressSpec, - deepCopy_v1beta1_IngressStatus, - deepCopy_v1beta1_IngressTLS, - deepCopy_v1beta1_Job, - deepCopy_v1beta1_JobCondition, - deepCopy_v1beta1_JobList, - deepCopy_v1beta1_JobSpec, - deepCopy_v1beta1_JobStatus, - deepCopy_v1beta1_LabelSelector, - deepCopy_v1beta1_LabelSelectorRequirement, - deepCopy_v1beta1_ListOptions, - deepCopy_v1beta1_NodeUtilization, - deepCopy_v1beta1_PodSecurityPolicy, - deepCopy_v1beta1_PodSecurityPolicyList, - deepCopy_v1beta1_PodSecurityPolicySpec, - deepCopy_v1beta1_ReplicaSet, - deepCopy_v1beta1_ReplicaSetList, - deepCopy_v1beta1_ReplicaSetSpec, - deepCopy_v1beta1_ReplicaSetStatus, - deepCopy_v1beta1_ReplicationControllerDummy, - deepCopy_v1beta1_RollbackConfig, - deepCopy_v1beta1_RollingUpdateDeployment, - deepCopy_v1beta1_RunAsUserStrategyOptions, - deepCopy_v1beta1_SELinuxContextStrategyOptions, - deepCopy_v1beta1_Scale, - deepCopy_v1beta1_ScaleSpec, - deepCopy_v1beta1_ScaleStatus, - deepCopy_v1beta1_SubresourceReference, - deepCopy_v1beta1_ThirdPartyResource, - deepCopy_v1beta1_ThirdPartyResourceData, - deepCopy_v1beta1_ThirdPartyResourceDataList, - deepCopy_v1beta1_ThirdPartyResourceList, - deepCopy_intstr_IntOrString, - ) - if err != nil { - // if one of the deep copy functions is malformed, detect it immediately. - panic(err) - } -} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/defaults.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/defaults.go index cb197a965..5ecba6c80 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/defaults.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/defaults.go @@ -24,9 +24,6 @@ import ( func addDefaultingFuncs(scheme *runtime.Scheme) { scheme.AddDefaultingFuncs( func(obj *APIVersion) { - if len(obj.APIGroup) == 0 { - obj.APIGroup = GroupName - } }, func(obj *DaemonSet) { labels := obj.Spec.Template.Labels @@ -86,7 +83,14 @@ func addDefaultingFuncs(scheme *runtime.Scheme) { labels := obj.Spec.Template.Labels // TODO: support templates defined elsewhere when we support them in the API if labels != nil { - if obj.Spec.Selector == nil { + // if an autoselector is requested, we'll build the selector later with controller-uid and job-name + autoSelector := bool(obj.Spec.AutoSelector != nil && *obj.Spec.AutoSelector) + + // otherwise, we are using a manual selector + manualSelector := !autoSelector + + // and default behavior for an unspecified manual selector is to use the pod template labels + if manualSelector && obj.Spec.Selector == nil { obj.Spec.Selector = &LabelSelector{ MatchLabels: labels, } @@ -118,10 +122,8 @@ func addDefaultingFuncs(scheme *runtime.Scheme) { } }, func(obj *ReplicaSet) { - var labels map[string]string - if obj.Spec.Template != nil { - labels = obj.Spec.Template.Labels - } + labels := obj.Spec.Template.Labels + // TODO: support templates defined elsewhere when we support them in the API if labels != nil { if obj.Spec.Selector == nil { diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/defaults_test.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/defaults_test.go index 85b991980..f02b2c0d4 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/defaults_test.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/defaults_test.go @@ -374,45 +374,107 @@ func TestSetDefaultJobParallelismAndCompletions(t *testing.T) { } func TestSetDefaultJobSelector(t *testing.T) { - expected := &Job{ - Spec: JobSpec{ - Selector: &LabelSelector{ - MatchLabels: map[string]string{"job": "selector"}, - }, - Completions: newInt32(1), - Parallelism: newInt32(1), - }, - } - tests := []*Job{ - // selector set explicitly, completions and parallelism - default + tests := []struct { + original *Job + expectedSelector *LabelSelector + }{ + // selector set explicitly, nil autoSelector { - Spec: JobSpec{ - Selector: &LabelSelector{ - MatchLabels: map[string]string{"job": "selector"}, - }, - }, - }, - // selector from template labels, completions and parallelism - default - { - Spec: JobSpec{ - Template: v1.PodTemplateSpec{ - ObjectMeta: v1.ObjectMeta{ - Labels: map[string]string{"job": "selector"}, + original: &Job{ + Spec: JobSpec{ + Selector: &LabelSelector{ + MatchLabels: map[string]string{"job": "selector"}, }, }, }, + expectedSelector: &LabelSelector{ + MatchLabels: map[string]string{"job": "selector"}, + }, + }, + // selector set explicitly, autoSelector=true + { + original: &Job{ + Spec: JobSpec{ + Selector: &LabelSelector{ + MatchLabels: map[string]string{"job": "selector"}, + }, + AutoSelector: newBool(true), + }, + }, + expectedSelector: &LabelSelector{ + MatchLabels: map[string]string{"job": "selector"}, + }, + }, + // selector set explicitly, autoSelector=false + { + original: &Job{ + Spec: JobSpec{ + Selector: &LabelSelector{ + MatchLabels: map[string]string{"job": "selector"}, + }, + AutoSelector: newBool(false), + }, + }, + expectedSelector: &LabelSelector{ + MatchLabels: map[string]string{"job": "selector"}, + }, + }, + // selector from template labels + { + original: &Job{ + Spec: JobSpec{ + Template: v1.PodTemplateSpec{ + ObjectMeta: v1.ObjectMeta{ + Labels: map[string]string{"job": "selector"}, + }, + }, + }, + }, + expectedSelector: &LabelSelector{ + MatchLabels: map[string]string{"job": "selector"}, + }, + }, + // selector from template labels, autoSelector=false + { + original: &Job{ + Spec: JobSpec{ + Template: v1.PodTemplateSpec{ + ObjectMeta: v1.ObjectMeta{ + Labels: map[string]string{"job": "selector"}, + }, + }, + AutoSelector: newBool(false), + }, + }, + expectedSelector: &LabelSelector{ + MatchLabels: map[string]string{"job": "selector"}, + }, + }, + // selector not copied from template labels, autoSelector=true + { + original: &Job{ + Spec: JobSpec{ + Template: v1.PodTemplateSpec{ + ObjectMeta: v1.ObjectMeta{ + Labels: map[string]string{"job": "selector"}, + }, + }, + AutoSelector: newBool(true), + }, + }, + expectedSelector: nil, }, } - for _, original := range tests { - obj2 := roundTrip(t, runtime.Object(original)) + for i, testcase := range tests { + obj2 := roundTrip(t, runtime.Object(testcase.original)) got, ok := obj2.(*Job) if !ok { - t.Errorf("unexpected object: %v", got) + t.Errorf("%d: unexpected object: %v", i, got) t.FailNow() } - if !reflect.DeepEqual(got.Spec.Selector, expected.Spec.Selector) { - t.Errorf("got different selectors %#v %#v", got.Spec.Selector, expected.Spec.Selector) + if !reflect.DeepEqual(got.Spec.Selector, testcase.expectedSelector) { + t.Errorf("%d: got different selectors %#v %#v", i, got.Spec.Selector, testcase.expectedSelector) } } } @@ -426,7 +488,7 @@ func TestSetDefaultReplicaSet(t *testing.T) { { rs: &ReplicaSet{ Spec: ReplicaSetSpec{ - Template: &v1.PodTemplateSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: v1.ObjectMeta{ Labels: map[string]string{ "foo": "bar", @@ -446,7 +508,7 @@ func TestSetDefaultReplicaSet(t *testing.T) { }, }, Spec: ReplicaSetSpec{ - Template: &v1.PodTemplateSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: v1.ObjectMeta{ Labels: map[string]string{ "foo": "bar", @@ -471,7 +533,7 @@ func TestSetDefaultReplicaSet(t *testing.T) { "some": "other", }, }, - Template: &v1.PodTemplateSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: v1.ObjectMeta{ Labels: map[string]string{ "foo": "bar", @@ -491,7 +553,7 @@ func TestSetDefaultReplicaSet(t *testing.T) { "some": "other", }, }, - Template: &v1.PodTemplateSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: v1.ObjectMeta{ Labels: map[string]string{ "foo": "bar", @@ -538,7 +600,7 @@ func TestSetDefaultReplicaSetReplicas(t *testing.T) { { rs: ReplicaSet{ Spec: ReplicaSetSpec{ - Template: &v1.PodTemplateSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: v1.ObjectMeta{ Labels: map[string]string{ "foo": "bar", @@ -553,7 +615,7 @@ func TestSetDefaultReplicaSetReplicas(t *testing.T) { rs: ReplicaSet{ Spec: ReplicaSetSpec{ Replicas: newInt32(0), - Template: &v1.PodTemplateSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: v1.ObjectMeta{ Labels: map[string]string{ "foo": "bar", @@ -568,7 +630,7 @@ func TestSetDefaultReplicaSetReplicas(t *testing.T) { rs: ReplicaSet{ Spec: ReplicaSetSpec{ Replicas: newInt32(3), - Template: &v1.PodTemplateSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: v1.ObjectMeta{ Labels: map[string]string{ "foo": "bar", @@ -611,7 +673,7 @@ func TestDefaultRequestIsNotSetForReplicaSet(t *testing.T) { rs := &ReplicaSet{ Spec: ReplicaSetSpec{ Replicas: newInt32(3), - Template: &v1.PodTemplateSpec{ + Template: v1.PodTemplateSpec{ ObjectMeta: v1.ObjectMeta{ Labels: map[string]string{ "foo": "bar", @@ -661,3 +723,9 @@ func newString(val string) *string { *p = val return p } + +func newBool(val bool) *bool { + b := new(bool) + *b = val + return b +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/register.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/register.go index d2750a639..ee662c463 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/register.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/register.go @@ -20,6 +20,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/runtime" + versionedwatch "k8s.io/kubernetes/pkg/watch/versioned" ) // GroupName is the group name use in this package @@ -37,8 +38,6 @@ func AddToScheme(scheme *runtime.Scheme) { // Adds the list of known types to api.Scheme. func addKnownTypes(scheme *runtime.Scheme) { scheme.AddKnownTypes(SchemeGroupVersion, - &ClusterAutoscaler{}, - &ClusterAutoscalerList{}, &Deployment{}, &DeploymentList{}, &DeploymentRollback{}, @@ -63,10 +62,10 @@ func addKnownTypes(scheme *runtime.Scheme) { &PodSecurityPolicy{}, &PodSecurityPolicyList{}, ) + // Add the watch version that applies + versionedwatch.AddToGroupVersion(scheme, SchemeGroupVersion) } -func (obj *ClusterAutoscaler) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } -func (obj *ClusterAutoscalerList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *Deployment) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *DeploymentList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func (obj *DeploymentRollback) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types.generated.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types.generated.go index 1e2b081ec..dcc5fc5f9 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types.generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types.generated.go @@ -260,13 +260,14 @@ func (x *ScaleStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [2]bool + var yyq2 [3]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[1] = len(x.Selector) != 0 + yyq2[2] = x.TargetSelector != "" var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(2) + r.EncodeArrayStart(3) } else { yynn2 = 1 for _, b := range yyq2 { @@ -329,6 +330,31 @@ func (x *ScaleStatus) CodecEncodeSelf(e *codec1978.Encoder) { } } } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.TargetSelector)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("targetSelector")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym11 := z.EncBinary() + _ = yym11 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.TargetSelector)) + } + } + } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayEnd1234) } else { @@ -408,6 +434,12 @@ func (x *ScaleStatus) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { z.F.DecMapStringStringX(yyv5, false, d) } } + case "targetSelector": + if r.TryDecodeAsNil() { + x.TargetSelector = "" + } else { + x.TargetSelector = string(r.DecodeString()) + } default: z.DecStructFieldNotFound(-1, yys3) } // end switch yys3 @@ -419,16 +451,16 @@ func (x *ScaleStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj7 int - var yyb7 bool - var yyhl7 bool = l >= 0 - yyj7++ - if yyhl7 { - yyb7 = yyj7 > l + var yyj8 int + var yyb8 bool + var yyhl8 bool = l >= 0 + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l } else { - yyb7 = r.CheckBreak() + yyb8 = r.CheckBreak() } - if yyb7 { + if yyb8 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -438,13 +470,13 @@ func (x *ScaleStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } else { x.Replicas = int32(r.DecodeInt(32)) } - yyj7++ - if yyhl7 { - yyb7 = yyj7 > l + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l } else { - yyb7 = r.CheckBreak() + yyb8 = r.CheckBreak() } - if yyb7 { + if yyb8 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -452,26 +484,42 @@ func (x *ScaleStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if r.TryDecodeAsNil() { x.Selector = nil } else { - yyv9 := &x.Selector - yym10 := z.DecBinary() - _ = yym10 + yyv10 := &x.Selector + yym11 := z.DecBinary() + _ = yym11 if false { } else { - z.F.DecMapStringStringX(yyv9, false, d) + z.F.DecMapStringStringX(yyv10, false, d) } } + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.TargetSelector = "" + } else { + x.TargetSelector = string(r.DecodeString()) + } for { - yyj7++ - if yyhl7 { - yyb7 = yyj7 > l + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l } else { - yyb7 = r.CheckBreak() + yyb8 = r.CheckBreak() } - if yyb7 { + if yyb8 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj7-1, "") + z.DecStructFieldNotFound(yyj8-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -4539,14 +4587,13 @@ func (x *APIVersion) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [2]bool + var yyq2 [1]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[0] = x.Name != "" - yyq2[1] = x.APIGroup != "" var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(2) + r.EncodeArrayStart(1) } else { yynn2 = 0 for _, b := range yyq2 { @@ -4582,31 +4629,6 @@ func (x *APIVersion) CodecEncodeSelf(e *codec1978.Encoder) { } } } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[1] { - yym7 := z.EncBinary() - _ = yym7 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIGroup)) - } - } else { - r.EncodeString(codecSelferC_UTF81234, "") - } - } else { - if yyq2[1] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("apiGroup")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym8 := z.EncBinary() - _ = yym8 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIGroup)) - } - } - } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayEnd1234) } else { @@ -4674,12 +4696,6 @@ func (x *APIVersion) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { } else { x.Name = string(r.DecodeString()) } - case "apiGroup": - if r.TryDecodeAsNil() { - x.APIGroup = "" - } else { - x.APIGroup = string(r.DecodeString()) - } default: z.DecStructFieldNotFound(-1, yys3) } // end switch yys3 @@ -4691,16 +4707,16 @@ func (x *APIVersion) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj6 int - var yyb6 bool - var yyhl6 bool = l >= 0 - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + var yyj5 int + var yyb5 bool + var yyhl5 bool = l >= 0 + yyj5++ + if yyhl5 { + yyb5 = yyj5 > l } else { - yyb6 = r.CheckBreak() + yyb5 = r.CheckBreak() } - if yyb6 { + if yyb5 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -4710,34 +4726,18 @@ func (x *APIVersion) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } else { x.Name = string(r.DecodeString()) } - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l - } else { - yyb6 = r.CheckBreak() - } - if yyb6 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.APIGroup = "" - } else { - x.APIGroup = string(r.DecodeString()) - } for { - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + yyj5++ + if yyhl5 { + yyb5 = yyj5 > l } else { - yyb6 = r.CheckBreak() + yyb5 = r.CheckBreak() } - if yyb6 { + if yyb5 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj6-1, "") + z.DecStructFieldNotFound(yyj5-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -7056,16 +7056,17 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [4]bool + var yyq2 [5]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[0] = x.Replicas != 0 - yyq2[1] = x.UpdatedReplicas != 0 - yyq2[2] = x.AvailableReplicas != 0 - yyq2[3] = x.UnavailableReplicas != 0 + yyq2[0] = x.ObservedGeneration != 0 + yyq2[1] = x.Replicas != 0 + yyq2[2] = x.UpdatedReplicas != 0 + yyq2[3] = x.AvailableReplicas != 0 + yyq2[4] = x.UnavailableReplicas != 0 var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(4) + r.EncodeArrayStart(5) } else { yynn2 = 0 for _, b := range yyq2 { @@ -7083,7 +7084,7 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym4 if false { } else { - r.EncodeInt(int64(x.Replicas)) + r.EncodeInt(int64(x.ObservedGeneration)) } } else { r.EncodeInt(0) @@ -7091,13 +7092,13 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[0] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("replicas")) + r.EncodeString(codecSelferC_UTF81234, string("observedGeneration")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym5 := z.EncBinary() _ = yym5 if false { } else { - r.EncodeInt(int64(x.Replicas)) + r.EncodeInt(int64(x.ObservedGeneration)) } } } @@ -7108,7 +7109,7 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym7 if false { } else { - r.EncodeInt(int64(x.UpdatedReplicas)) + r.EncodeInt(int64(x.Replicas)) } } else { r.EncodeInt(0) @@ -7116,13 +7117,13 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[1] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("updatedReplicas")) + r.EncodeString(codecSelferC_UTF81234, string("replicas")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym8 := z.EncBinary() _ = yym8 if false { } else { - r.EncodeInt(int64(x.UpdatedReplicas)) + r.EncodeInt(int64(x.Replicas)) } } } @@ -7133,7 +7134,7 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym10 if false { } else { - r.EncodeInt(int64(x.AvailableReplicas)) + r.EncodeInt(int64(x.UpdatedReplicas)) } } else { r.EncodeInt(0) @@ -7141,13 +7142,13 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[2] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("availableReplicas")) + r.EncodeString(codecSelferC_UTF81234, string("updatedReplicas")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym11 := z.EncBinary() _ = yym11 if false { } else { - r.EncodeInt(int64(x.AvailableReplicas)) + r.EncodeInt(int64(x.UpdatedReplicas)) } } } @@ -7158,7 +7159,7 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym13 if false { } else { - r.EncodeInt(int64(x.UnavailableReplicas)) + r.EncodeInt(int64(x.AvailableReplicas)) } } else { r.EncodeInt(0) @@ -7166,11 +7167,36 @@ func (x *DeploymentStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[3] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("unavailableReplicas")) + r.EncodeString(codecSelferC_UTF81234, string("availableReplicas")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym14 := z.EncBinary() _ = yym14 if false { + } else { + r.EncodeInt(int64(x.AvailableReplicas)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[4] { + yym16 := z.EncBinary() + _ = yym16 + if false { + } else { + r.EncodeInt(int64(x.UnavailableReplicas)) + } + } else { + r.EncodeInt(0) + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("unavailableReplicas")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym17 := z.EncBinary() + _ = yym17 + if false { } else { r.EncodeInt(int64(x.UnavailableReplicas)) } @@ -7237,6 +7263,12 @@ func (x *DeploymentStatus) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { yys3 := string(yys3Slc) z.DecSendContainerState(codecSelfer_containerMapValue1234) switch yys3 { + case "observedGeneration": + if r.TryDecodeAsNil() { + x.ObservedGeneration = 0 + } else { + x.ObservedGeneration = int64(r.DecodeInt(64)) + } case "replicas": if r.TryDecodeAsNil() { x.Replicas = 0 @@ -7272,16 +7304,32 @@ func (x *DeploymentStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj8 int - var yyb8 bool - var yyhl8 bool = l >= 0 - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + var yyj9 int + var yyb9 bool + var yyhl9 bool = l >= 0 + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ObservedGeneration = 0 + } else { + x.ObservedGeneration = int64(r.DecodeInt(64)) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7291,13 +7339,13 @@ func (x *DeploymentStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) } else { x.Replicas = int32(r.DecodeInt(32)) } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7307,13 +7355,13 @@ func (x *DeploymentStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) } else { x.UpdatedReplicas = int32(r.DecodeInt(32)) } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7323,13 +7371,13 @@ func (x *DeploymentStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) } else { x.AvailableReplicas = int32(r.DecodeInt(32)) } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -7340,17 +7388,17 @@ func (x *DeploymentStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) x.UnavailableReplicas = int32(r.DecodeInt(32)) } for { - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l } else { - yyb8 = r.CheckBreak() + yyb9 = r.CheckBreak() } - if yyb8 { + if yyb9 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj8-1, "") + z.DecStructFieldNotFound(yyj9-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -9887,16 +9935,17 @@ func (x *JobSpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [5]bool + var yyq2 [6]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[0] = x.Parallelism != nil yyq2[1] = x.Completions != nil yyq2[2] = x.ActiveDeadlineSeconds != nil yyq2[3] = x.Selector != nil + yyq2[4] = x.AutoSelector != nil var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(5) + r.EncodeArrayStart(6) } else { yynn2 = 1 for _, b := range yyq2 { @@ -10037,14 +10086,49 @@ func (x *JobSpec) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy22 := &x.Template - yy22.CodecEncodeSelf(e) + if yyq2[4] { + if x.AutoSelector == nil { + r.EncodeNil() + } else { + yy22 := *x.AutoSelector + yym23 := z.EncBinary() + _ = yym23 + if false { + } else { + r.EncodeBool(bool(yy22)) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("autoSelector")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.AutoSelector == nil { + r.EncodeNil() + } else { + yy24 := *x.AutoSelector + yym25 := z.EncBinary() + _ = yym25 + if false { + } else { + r.EncodeBool(bool(yy24)) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy27 := &x.Template + yy27.CodecEncodeSelf(e) } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("template")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy24 := &x.Template - yy24.CodecEncodeSelf(e) + yy29 := &x.Template + yy29.CodecEncodeSelf(e) } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayEnd1234) @@ -10166,12 +10250,28 @@ func (x *JobSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { } x.Selector.CodecDecodeSelf(d) } + case "autoSelector": + if r.TryDecodeAsNil() { + if x.AutoSelector != nil { + x.AutoSelector = nil + } + } else { + if x.AutoSelector == nil { + x.AutoSelector = new(bool) + } + yym12 := z.DecBinary() + _ = yym12 + if false { + } else { + *((*bool)(x.AutoSelector)) = r.DecodeBool() + } + } case "template": if r.TryDecodeAsNil() { x.Template = pkg2_v1.PodTemplateSpec{} } else { - yyv11 := &x.Template - yyv11.CodecDecodeSelf(d) + yyv13 := &x.Template + yyv13.CodecDecodeSelf(d) } default: z.DecStructFieldNotFound(-1, yys3) @@ -10184,16 +10284,16 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj12 int - var yyb12 bool - var yyhl12 bool = l >= 0 - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + var yyj14 int + var yyb14 bool + var yyhl14 bool = l >= 0 + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -10206,20 +10306,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if x.Parallelism == nil { x.Parallelism = new(int32) } - yym14 := z.DecBinary() - _ = yym14 + yym16 := z.DecBinary() + _ = yym16 if false { } else { *((*int32)(x.Parallelism)) = int32(r.DecodeInt(32)) } } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -10232,20 +10332,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if x.Completions == nil { x.Completions = new(int32) } - yym16 := z.DecBinary() - _ = yym16 + yym18 := z.DecBinary() + _ = yym18 if false { } else { *((*int32)(x.Completions)) = int32(r.DecodeInt(32)) } } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -10258,20 +10358,20 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if x.ActiveDeadlineSeconds == nil { x.ActiveDeadlineSeconds = new(int64) } - yym18 := z.DecBinary() - _ = yym18 + yym20 := z.DecBinary() + _ = yym20 if false { } else { *((*int64)(x.ActiveDeadlineSeconds)) = int64(r.DecodeInt(64)) } } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -10286,13 +10386,39 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } x.Selector.CodecDecodeSelf(d) } - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + if x.AutoSelector != nil { + x.AutoSelector = nil + } + } else { + if x.AutoSelector == nil { + x.AutoSelector = new(bool) + } + yym23 := z.DecBinary() + _ = yym23 + if false { + } else { + *((*bool)(x.AutoSelector)) = r.DecodeBool() + } + } + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l + } else { + yyb14 = r.CheckBreak() + } + if yyb14 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -10300,21 +10426,21 @@ func (x *JobSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { if r.TryDecodeAsNil() { x.Template = pkg2_v1.PodTemplateSpec{} } else { - yyv20 := &x.Template - yyv20.CodecDecodeSelf(d) + yyv24 := &x.Template + yyv24.CodecDecodeSelf(d) } for { - yyj12++ - if yyhl12 { - yyb12 = yyj12 > l + yyj14++ + if yyhl14 { + yyb14 = yyj14 > l } else { - yyb12 = r.CheckBreak() + yyb14 = r.CheckBreak() } - if yyb12 { + if yyb14 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj12-1, "") + z.DecStructFieldNotFound(yyj14-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -13752,1134 +13878,6 @@ func (x *IngressBackend) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } -func (x NodeResource) CodecEncodeSelf(e *codec1978.Encoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperEncoder(e) - _, _, _ = h, z, r - yym1 := z.EncBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.EncExt(x) { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x)) - } -} - -func (x *NodeResource) CodecDecodeSelf(d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - yym1 := z.DecBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.DecExt(x) { - } else { - *((*string)(x)) = r.DecodeString() - } -} - -func (x *NodeUtilization) CodecEncodeSelf(e *codec1978.Encoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperEncoder(e) - _, _, _ = h, z, r - if x == nil { - r.EncodeNil() - } else { - yym1 := z.EncBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.EncExt(x) { - } else { - yysep2 := !z.EncBinary() - yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [2]bool - _, _, _ = yysep2, yyq2, yy2arr2 - const yyr2 bool = false - var yynn2 int - if yyr2 || yy2arr2 { - r.EncodeArrayStart(2) - } else { - yynn2 = 2 - for _, b := range yyq2 { - if b { - yynn2++ - } - } - r.EncodeMapStart(yynn2) - yynn2 = 0 - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - x.Resource.CodecEncodeSelf(e) - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("resource")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - x.Resource.CodecEncodeSelf(e) - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym7 := z.EncBinary() - _ = yym7 - if false { - } else { - r.EncodeFloat64(float64(x.Value)) - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("value")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym8 := z.EncBinary() - _ = yym8 - if false { - } else { - r.EncodeFloat64(float64(x.Value)) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - z.EncSendContainerState(codecSelfer_containerMapEnd1234) - } - } - } -} - -func (x *NodeUtilization) CodecDecodeSelf(d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - yym1 := z.DecBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.DecExt(x) { - } else { - yyct2 := r.ContainerType() - if yyct2 == codecSelferValueTypeMap1234 { - yyl2 := r.ReadMapStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerMapEnd1234) - } else { - x.codecDecodeSelfFromMap(yyl2, d) - } - } else if yyct2 == codecSelferValueTypeArray1234 { - yyl2 := r.ReadArrayStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - x.codecDecodeSelfFromArray(yyl2, d) - } - } else { - panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) - } - } -} - -func (x *NodeUtilization) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yys3Slc = z.DecScratchBuffer() // default slice to decode into - _ = yys3Slc - var yyhl3 bool = l >= 0 - for yyj3 := 0; ; yyj3++ { - if yyhl3 { - if yyj3 >= l { - break - } - } else { - if r.CheckBreak() { - break - } - } - z.DecSendContainerState(codecSelfer_containerMapKey1234) - yys3Slc = r.DecodeBytes(yys3Slc, true, true) - yys3 := string(yys3Slc) - z.DecSendContainerState(codecSelfer_containerMapValue1234) - switch yys3 { - case "resource": - if r.TryDecodeAsNil() { - x.Resource = "" - } else { - x.Resource = NodeResource(r.DecodeString()) - } - case "value": - if r.TryDecodeAsNil() { - x.Value = 0 - } else { - x.Value = float64(r.DecodeFloat(false)) - } - default: - z.DecStructFieldNotFound(-1, yys3) - } // end switch yys3 - } // end for yyj3 - z.DecSendContainerState(codecSelfer_containerMapEnd1234) -} - -func (x *NodeUtilization) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yyj6 int - var yyb6 bool - var yyhl6 bool = l >= 0 - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l - } else { - yyb6 = r.CheckBreak() - } - if yyb6 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.Resource = "" - } else { - x.Resource = NodeResource(r.DecodeString()) - } - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l - } else { - yyb6 = r.CheckBreak() - } - if yyb6 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.Value = 0 - } else { - x.Value = float64(r.DecodeFloat(false)) - } - for { - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l - } else { - yyb6 = r.CheckBreak() - } - if yyb6 { - break - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj6-1, "") - } - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) -} - -func (x *ClusterAutoscalerSpec) CodecEncodeSelf(e *codec1978.Encoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperEncoder(e) - _, _, _ = h, z, r - if x == nil { - r.EncodeNil() - } else { - yym1 := z.EncBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.EncExt(x) { - } else { - yysep2 := !z.EncBinary() - yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [3]bool - _, _, _ = yysep2, yyq2, yy2arr2 - const yyr2 bool = false - var yynn2 int - if yyr2 || yy2arr2 { - r.EncodeArrayStart(3) - } else { - yynn2 = 3 - for _, b := range yyq2 { - if b { - yynn2++ - } - } - r.EncodeMapStart(yynn2) - yynn2 = 0 - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym4 := z.EncBinary() - _ = yym4 - if false { - } else { - r.EncodeInt(int64(x.MinNodes)) - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("minNodes")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym5 := z.EncBinary() - _ = yym5 - if false { - } else { - r.EncodeInt(int64(x.MinNodes)) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yym7 := z.EncBinary() - _ = yym7 - if false { - } else { - r.EncodeInt(int64(x.MaxNodes)) - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("maxNodes")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym8 := z.EncBinary() - _ = yym8 - if false { - } else { - r.EncodeInt(int64(x.MaxNodes)) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if x.TargetUtilization == nil { - r.EncodeNil() - } else { - yym10 := z.EncBinary() - _ = yym10 - if false { - } else { - h.encSliceNodeUtilization(([]NodeUtilization)(x.TargetUtilization), e) - } - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("target")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.TargetUtilization == nil { - r.EncodeNil() - } else { - yym11 := z.EncBinary() - _ = yym11 - if false { - } else { - h.encSliceNodeUtilization(([]NodeUtilization)(x.TargetUtilization), e) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - z.EncSendContainerState(codecSelfer_containerMapEnd1234) - } - } - } -} - -func (x *ClusterAutoscalerSpec) CodecDecodeSelf(d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - yym1 := z.DecBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.DecExt(x) { - } else { - yyct2 := r.ContainerType() - if yyct2 == codecSelferValueTypeMap1234 { - yyl2 := r.ReadMapStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerMapEnd1234) - } else { - x.codecDecodeSelfFromMap(yyl2, d) - } - } else if yyct2 == codecSelferValueTypeArray1234 { - yyl2 := r.ReadArrayStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - x.codecDecodeSelfFromArray(yyl2, d) - } - } else { - panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) - } - } -} - -func (x *ClusterAutoscalerSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yys3Slc = z.DecScratchBuffer() // default slice to decode into - _ = yys3Slc - var yyhl3 bool = l >= 0 - for yyj3 := 0; ; yyj3++ { - if yyhl3 { - if yyj3 >= l { - break - } - } else { - if r.CheckBreak() { - break - } - } - z.DecSendContainerState(codecSelfer_containerMapKey1234) - yys3Slc = r.DecodeBytes(yys3Slc, true, true) - yys3 := string(yys3Slc) - z.DecSendContainerState(codecSelfer_containerMapValue1234) - switch yys3 { - case "minNodes": - if r.TryDecodeAsNil() { - x.MinNodes = 0 - } else { - x.MinNodes = int32(r.DecodeInt(32)) - } - case "maxNodes": - if r.TryDecodeAsNil() { - x.MaxNodes = 0 - } else { - x.MaxNodes = int32(r.DecodeInt(32)) - } - case "target": - if r.TryDecodeAsNil() { - x.TargetUtilization = nil - } else { - yyv6 := &x.TargetUtilization - yym7 := z.DecBinary() - _ = yym7 - if false { - } else { - h.decSliceNodeUtilization((*[]NodeUtilization)(yyv6), d) - } - } - default: - z.DecStructFieldNotFound(-1, yys3) - } // end switch yys3 - } // end for yyj3 - z.DecSendContainerState(codecSelfer_containerMapEnd1234) -} - -func (x *ClusterAutoscalerSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yyj8 int - var yyb8 bool - var yyhl8 bool = l >= 0 - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.MinNodes = 0 - } else { - x.MinNodes = int32(r.DecodeInt(32)) - } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.MaxNodes = 0 - } else { - x.MaxNodes = int32(r.DecodeInt(32)) - } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.TargetUtilization = nil - } else { - yyv11 := &x.TargetUtilization - yym12 := z.DecBinary() - _ = yym12 - if false { - } else { - h.decSliceNodeUtilization((*[]NodeUtilization)(yyv11), d) - } - } - for { - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - break - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj8-1, "") - } - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) -} - -func (x *ClusterAutoscaler) CodecEncodeSelf(e *codec1978.Encoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperEncoder(e) - _, _, _ = h, z, r - if x == nil { - r.EncodeNil() - } else { - yym1 := z.EncBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.EncExt(x) { - } else { - yysep2 := !z.EncBinary() - yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [4]bool - _, _, _ = yysep2, yyq2, yy2arr2 - const yyr2 bool = false - yyq2[0] = true - yyq2[1] = true - yyq2[2] = x.Kind != "" - yyq2[3] = x.APIVersion != "" - var yynn2 int - if yyr2 || yy2arr2 { - r.EncodeArrayStart(4) - } else { - yynn2 = 0 - for _, b := range yyq2 { - if b { - yynn2++ - } - } - r.EncodeMapStart(yynn2) - yynn2 = 0 - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[0] { - yy4 := &x.ObjectMeta - yy4.CodecEncodeSelf(e) - } else { - r.EncodeNil() - } - } else { - if yyq2[0] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("metadata")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy6 := &x.ObjectMeta - yy6.CodecEncodeSelf(e) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[1] { - yy9 := &x.Spec - yy9.CodecEncodeSelf(e) - } else { - r.EncodeNil() - } - } else { - if yyq2[1] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("spec")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy11 := &x.Spec - yy11.CodecEncodeSelf(e) - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[2] { - yym14 := z.EncBinary() - _ = yym14 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) - } - } else { - r.EncodeString(codecSelferC_UTF81234, "") - } - } else { - if yyq2[2] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("kind")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym15 := z.EncBinary() - _ = yym15 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[3] { - yym17 := z.EncBinary() - _ = yym17 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) - } - } else { - r.EncodeString(codecSelferC_UTF81234, "") - } - } else { - if yyq2[3] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym18 := z.EncBinary() - _ = yym18 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - z.EncSendContainerState(codecSelfer_containerMapEnd1234) - } - } - } -} - -func (x *ClusterAutoscaler) CodecDecodeSelf(d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - yym1 := z.DecBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.DecExt(x) { - } else { - yyct2 := r.ContainerType() - if yyct2 == codecSelferValueTypeMap1234 { - yyl2 := r.ReadMapStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerMapEnd1234) - } else { - x.codecDecodeSelfFromMap(yyl2, d) - } - } else if yyct2 == codecSelferValueTypeArray1234 { - yyl2 := r.ReadArrayStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - x.codecDecodeSelfFromArray(yyl2, d) - } - } else { - panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) - } - } -} - -func (x *ClusterAutoscaler) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yys3Slc = z.DecScratchBuffer() // default slice to decode into - _ = yys3Slc - var yyhl3 bool = l >= 0 - for yyj3 := 0; ; yyj3++ { - if yyhl3 { - if yyj3 >= l { - break - } - } else { - if r.CheckBreak() { - break - } - } - z.DecSendContainerState(codecSelfer_containerMapKey1234) - yys3Slc = r.DecodeBytes(yys3Slc, true, true) - yys3 := string(yys3Slc) - z.DecSendContainerState(codecSelfer_containerMapValue1234) - switch yys3 { - case "metadata": - if r.TryDecodeAsNil() { - x.ObjectMeta = pkg2_v1.ObjectMeta{} - } else { - yyv4 := &x.ObjectMeta - yyv4.CodecDecodeSelf(d) - } - case "spec": - if r.TryDecodeAsNil() { - x.Spec = ClusterAutoscalerSpec{} - } else { - yyv5 := &x.Spec - yyv5.CodecDecodeSelf(d) - } - case "kind": - if r.TryDecodeAsNil() { - x.Kind = "" - } else { - x.Kind = string(r.DecodeString()) - } - case "apiVersion": - if r.TryDecodeAsNil() { - x.APIVersion = "" - } else { - x.APIVersion = string(r.DecodeString()) - } - default: - z.DecStructFieldNotFound(-1, yys3) - } // end switch yys3 - } // end for yyj3 - z.DecSendContainerState(codecSelfer_containerMapEnd1234) -} - -func (x *ClusterAutoscaler) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yyj8 int - var yyb8 bool - var yyhl8 bool = l >= 0 - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.ObjectMeta = pkg2_v1.ObjectMeta{} - } else { - yyv9 := &x.ObjectMeta - yyv9.CodecDecodeSelf(d) - } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.Spec = ClusterAutoscalerSpec{} - } else { - yyv10 := &x.Spec - yyv10.CodecDecodeSelf(d) - } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.Kind = "" - } else { - x.Kind = string(r.DecodeString()) - } - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.APIVersion = "" - } else { - x.APIVersion = string(r.DecodeString()) - } - for { - yyj8++ - if yyhl8 { - yyb8 = yyj8 > l - } else { - yyb8 = r.CheckBreak() - } - if yyb8 { - break - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj8-1, "") - } - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) -} - -func (x *ClusterAutoscalerList) CodecEncodeSelf(e *codec1978.Encoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperEncoder(e) - _, _, _ = h, z, r - if x == nil { - r.EncodeNil() - } else { - yym1 := z.EncBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.EncExt(x) { - } else { - yysep2 := !z.EncBinary() - yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [4]bool - _, _, _ = yysep2, yyq2, yy2arr2 - const yyr2 bool = false - yyq2[0] = true - yyq2[2] = x.Kind != "" - yyq2[3] = x.APIVersion != "" - var yynn2 int - if yyr2 || yy2arr2 { - r.EncodeArrayStart(4) - } else { - yynn2 = 1 - for _, b := range yyq2 { - if b { - yynn2++ - } - } - r.EncodeMapStart(yynn2) - yynn2 = 0 - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[0] { - yy4 := &x.ListMeta - yym5 := z.EncBinary() - _ = yym5 - if false { - } else if z.HasExtensions() && z.EncExt(yy4) { - } else { - z.EncFallback(yy4) - } - } else { - r.EncodeNil() - } - } else { - if yyq2[0] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("metadata")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy6 := &x.ListMeta - yym7 := z.EncBinary() - _ = yym7 - if false { - } else if z.HasExtensions() && z.EncExt(yy6) { - } else { - z.EncFallback(yy6) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if x.Items == nil { - r.EncodeNil() - } else { - yym9 := z.EncBinary() - _ = yym9 - if false { - } else { - h.encSliceClusterAutoscaler(([]ClusterAutoscaler)(x.Items), e) - } - } - } else { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("items")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.Items == nil { - r.EncodeNil() - } else { - yym10 := z.EncBinary() - _ = yym10 - if false { - } else { - h.encSliceClusterAutoscaler(([]ClusterAutoscaler)(x.Items), e) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[2] { - yym12 := z.EncBinary() - _ = yym12 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) - } - } else { - r.EncodeString(codecSelferC_UTF81234, "") - } - } else { - if yyq2[2] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("kind")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym13 := z.EncBinary() - _ = yym13 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - if yyq2[3] { - yym15 := z.EncBinary() - _ = yym15 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) - } - } else { - r.EncodeString(codecSelferC_UTF81234, "") - } - } else { - if yyq2[3] { - z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) - z.EncSendContainerState(codecSelfer_containerMapValue1234) - yym16 := z.EncBinary() - _ = yym16 - if false { - } else { - r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) - } - } - } - if yyr2 || yy2arr2 { - z.EncSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - z.EncSendContainerState(codecSelfer_containerMapEnd1234) - } - } - } -} - -func (x *ClusterAutoscalerList) CodecDecodeSelf(d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - yym1 := z.DecBinary() - _ = yym1 - if false { - } else if z.HasExtensions() && z.DecExt(x) { - } else { - yyct2 := r.ContainerType() - if yyct2 == codecSelferValueTypeMap1234 { - yyl2 := r.ReadMapStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerMapEnd1234) - } else { - x.codecDecodeSelfFromMap(yyl2, d) - } - } else if yyct2 == codecSelferValueTypeArray1234 { - yyl2 := r.ReadArrayStart() - if yyl2 == 0 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - } else { - x.codecDecodeSelfFromArray(yyl2, d) - } - } else { - panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) - } - } -} - -func (x *ClusterAutoscalerList) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yys3Slc = z.DecScratchBuffer() // default slice to decode into - _ = yys3Slc - var yyhl3 bool = l >= 0 - for yyj3 := 0; ; yyj3++ { - if yyhl3 { - if yyj3 >= l { - break - } - } else { - if r.CheckBreak() { - break - } - } - z.DecSendContainerState(codecSelfer_containerMapKey1234) - yys3Slc = r.DecodeBytes(yys3Slc, true, true) - yys3 := string(yys3Slc) - z.DecSendContainerState(codecSelfer_containerMapValue1234) - switch yys3 { - case "metadata": - if r.TryDecodeAsNil() { - x.ListMeta = pkg1_unversioned.ListMeta{} - } else { - yyv4 := &x.ListMeta - yym5 := z.DecBinary() - _ = yym5 - if false { - } else if z.HasExtensions() && z.DecExt(yyv4) { - } else { - z.DecFallback(yyv4, false) - } - } - case "items": - if r.TryDecodeAsNil() { - x.Items = nil - } else { - yyv6 := &x.Items - yym7 := z.DecBinary() - _ = yym7 - if false { - } else { - h.decSliceClusterAutoscaler((*[]ClusterAutoscaler)(yyv6), d) - } - } - case "kind": - if r.TryDecodeAsNil() { - x.Kind = "" - } else { - x.Kind = string(r.DecodeString()) - } - case "apiVersion": - if r.TryDecodeAsNil() { - x.APIVersion = "" - } else { - x.APIVersion = string(r.DecodeString()) - } - default: - z.DecStructFieldNotFound(-1, yys3) - } // end switch yys3 - } // end for yyj3 - z.DecSendContainerState(codecSelfer_containerMapEnd1234) -} - -func (x *ClusterAutoscalerList) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - var yyj10 int - var yyb10 bool - var yyhl10 bool = l >= 0 - yyj10++ - if yyhl10 { - yyb10 = yyj10 > l - } else { - yyb10 = r.CheckBreak() - } - if yyb10 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.ListMeta = pkg1_unversioned.ListMeta{} - } else { - yyv11 := &x.ListMeta - yym12 := z.DecBinary() - _ = yym12 - if false { - } else if z.HasExtensions() && z.DecExt(yyv11) { - } else { - z.DecFallback(yyv11, false) - } - } - yyj10++ - if yyhl10 { - yyb10 = yyj10 > l - } else { - yyb10 = r.CheckBreak() - } - if yyb10 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.Items = nil - } else { - yyv13 := &x.Items - yym14 := z.DecBinary() - _ = yym14 - if false { - } else { - h.decSliceClusterAutoscaler((*[]ClusterAutoscaler)(yyv13), d) - } - } - yyj10++ - if yyhl10 { - yyb10 = yyj10 > l - } else { - yyb10 = r.CheckBreak() - } - if yyb10 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.Kind = "" - } else { - x.Kind = string(r.DecodeString()) - } - yyj10++ - if yyhl10 { - yyb10 = yyj10 > l - } else { - yyb10 = r.CheckBreak() - } - if yyb10 { - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) - return - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - if r.TryDecodeAsNil() { - x.APIVersion = "" - } else { - x.APIVersion = string(r.DecodeString()) - } - for { - yyj10++ - if yyhl10 { - yyb10 = yyj10 > l - } else { - yyb10 = r.CheckBreak() - } - if yyb10 { - break - } - z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj10-1, "") - } - z.DecSendContainerState(codecSelfer_containerArrayEnd1234) -} - func (x *ExportOptions) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -16916,7 +15914,7 @@ func (x *ReplicaSetSpec) CodecEncodeSelf(e *codec1978.Encoder) { const yyr2 bool = false yyq2[0] = x.Replicas != nil yyq2[1] = x.Selector != nil - yyq2[2] = x.Template != nil + yyq2[2] = true var yynn2 int if yyr2 || yy2arr2 { r.EncodeArrayStart(3) @@ -16991,11 +15989,8 @@ func (x *ReplicaSetSpec) CodecEncodeSelf(e *codec1978.Encoder) { if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[2] { - if x.Template == nil { - r.EncodeNil() - } else { - x.Template.CodecEncodeSelf(e) - } + yy12 := &x.Template + yy12.CodecEncodeSelf(e) } else { r.EncodeNil() } @@ -17004,11 +15999,8 @@ func (x *ReplicaSetSpec) CodecEncodeSelf(e *codec1978.Encoder) { z.EncSendContainerState(codecSelfer_containerMapKey1234) r.EncodeString(codecSelferC_UTF81234, string("template")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - if x.Template == nil { - r.EncodeNil() - } else { - x.Template.CodecEncodeSelf(e) - } + yy14 := &x.Template + yy14.CodecEncodeSelf(e) } } if yyr2 || yy2arr2 { @@ -17101,14 +16093,10 @@ func (x *ReplicaSetSpec) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { } case "template": if r.TryDecodeAsNil() { - if x.Template != nil { - x.Template = nil - } + x.Template = pkg2_v1.PodTemplateSpec{} } else { - if x.Template == nil { - x.Template = new(pkg2_v1.PodTemplateSpec) - } - x.Template.CodecDecodeSelf(d) + yyv7 := &x.Template + yyv7.CodecDecodeSelf(d) } default: z.DecStructFieldNotFound(-1, yys3) @@ -17183,14 +16171,10 @@ func (x *ReplicaSetSpec) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - if x.Template != nil { - x.Template = nil - } + x.Template = pkg2_v1.PodTemplateSpec{} } else { - if x.Template == nil { - x.Template = new(pkg2_v1.PodTemplateSpec) - } - x.Template.CodecDecodeSelf(d) + yyv12 := &x.Template + yyv12.CodecDecodeSelf(d) } for { yyj8++ @@ -17222,13 +16206,14 @@ func (x *ReplicaSetStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [2]bool + var yyq2 [3]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false - yyq2[1] = x.ObservedGeneration != 0 + yyq2[1] = x.FullyLabeledReplicas != 0 + yyq2[2] = x.ObservedGeneration != 0 var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(2) + r.EncodeArrayStart(3) } else { yynn2 = 1 for _, b := range yyq2 { @@ -17265,7 +16250,7 @@ func (x *ReplicaSetStatus) CodecEncodeSelf(e *codec1978.Encoder) { _ = yym7 if false { } else { - r.EncodeInt(int64(x.ObservedGeneration)) + r.EncodeInt(int64(x.FullyLabeledReplicas)) } } else { r.EncodeInt(0) @@ -17273,11 +16258,36 @@ func (x *ReplicaSetStatus) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[1] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("observedGeneration")) + r.EncodeString(codecSelferC_UTF81234, string("fullyLabeledReplicas")) z.EncSendContainerState(codecSelfer_containerMapValue1234) yym8 := z.EncBinary() _ = yym8 if false { + } else { + r.EncodeInt(int64(x.FullyLabeledReplicas)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeInt(int64(x.ObservedGeneration)) + } + } else { + r.EncodeInt(0) + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("observedGeneration")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym11 := z.EncBinary() + _ = yym11 + if false { } else { r.EncodeInt(int64(x.ObservedGeneration)) } @@ -17350,6 +16360,12 @@ func (x *ReplicaSetStatus) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { } else { x.Replicas = int32(r.DecodeInt(32)) } + case "fullyLabeledReplicas": + if r.TryDecodeAsNil() { + x.FullyLabeledReplicas = 0 + } else { + x.FullyLabeledReplicas = int32(r.DecodeInt(32)) + } case "observedGeneration": if r.TryDecodeAsNil() { x.ObservedGeneration = 0 @@ -17367,16 +16383,16 @@ func (x *ReplicaSetStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj6 int - var yyb6 bool - var yyhl6 bool = l >= 0 - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + var yyj7 int + var yyb7 bool + var yyhl7 bool = l >= 0 + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb6 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb6 { + if yyb7 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -17386,13 +16402,29 @@ func (x *ReplicaSetStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) } else { x.Replicas = int32(r.DecodeInt(32)) } - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb6 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb6 { + if yyb7 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.FullyLabeledReplicas = 0 + } else { + x.FullyLabeledReplicas = int32(r.DecodeInt(32)) + } + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l + } else { + yyb7 = r.CheckBreak() + } + if yyb7 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -17403,17 +16435,17 @@ func (x *ReplicaSetStatus) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) x.ObservedGeneration = int64(r.DecodeInt(64)) } for { - yyj6++ - if yyhl6 { - yyb6 = yyj6 > l + yyj7++ + if yyhl7 { + yyb7 = yyj7 > l } else { - yyb6 = r.CheckBreak() + yyb7 = r.CheckBreak() } - if yyb6 { + if yyb7 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj6-1, "") + z.DecStructFieldNotFound(yyj7-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -17960,7 +16992,7 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) if yyq2[7] { - yy25 := &x.SELinuxContext + yy25 := &x.SELinux yy25.CodecEncodeSelf(e) } else { r.EncodeNil() @@ -17968,9 +17000,9 @@ func (x *PodSecurityPolicySpec) CodecEncodeSelf(e *codec1978.Encoder) { } else { if yyq2[7] { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("seLinuxContext")) + r.EncodeString(codecSelferC_UTF81234, string("seLinux")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - yy27 := &x.SELinuxContext + yy27 := &x.SELinux yy27.CodecEncodeSelf(e) } } @@ -18112,11 +17144,11 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromMap(l int, d *codec1978.Decod } else { x.HostIPC = bool(r.DecodeBool()) } - case "seLinuxContext": + case "seLinux": if r.TryDecodeAsNil() { - x.SELinuxContext = SELinuxContextStrategyOptions{} + x.SELinux = SELinuxStrategyOptions{} } else { - yyv14 := &x.SELinuxContext + yyv14 := &x.SELinux yyv14.CodecDecodeSelf(d) } case "runAsUser": @@ -18282,9 +17314,9 @@ func (x *PodSecurityPolicySpec) codecDecodeSelfFromArray(l int, d *codec1978.Dec } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.SELinuxContext = SELinuxContextStrategyOptions{} + x.SELinux = SELinuxStrategyOptions{} } else { - yyv27 := &x.SELinuxContext + yyv27 := &x.SELinux yyv27.CodecDecodeSelf(d) } yyj16++ @@ -18549,7 +17581,7 @@ func (x *HostPortRange) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } -func (x *SELinuxContextStrategyOptions) CodecEncodeSelf(e *codec1978.Encoder) { +func (x *SELinuxStrategyOptions) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) _, _, _ = h, z, r @@ -18582,12 +17614,12 @@ func (x *SELinuxContextStrategyOptions) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - x.Type.CodecEncodeSelf(e) + x.Rule.CodecEncodeSelf(e) } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("type")) + r.EncodeString(codecSelferC_UTF81234, string("rule")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - x.Type.CodecEncodeSelf(e) + x.Rule.CodecEncodeSelf(e) } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) @@ -18621,7 +17653,7 @@ func (x *SELinuxContextStrategyOptions) CodecEncodeSelf(e *codec1978.Encoder) { } } -func (x *SELinuxContextStrategyOptions) CodecDecodeSelf(d *codec1978.Decoder) { +func (x *SELinuxStrategyOptions) CodecDecodeSelf(d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r @@ -18651,7 +17683,7 @@ func (x *SELinuxContextStrategyOptions) CodecDecodeSelf(d *codec1978.Decoder) { } } -func (x *SELinuxContextStrategyOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { +func (x *SELinuxStrategyOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r @@ -18673,11 +17705,11 @@ func (x *SELinuxContextStrategyOptions) codecDecodeSelfFromMap(l int, d *codec19 yys3 := string(yys3Slc) z.DecSendContainerState(codecSelfer_containerMapValue1234) switch yys3 { - case "type": + case "rule": if r.TryDecodeAsNil() { - x.Type = "" + x.Rule = "" } else { - x.Type = SELinuxContextStrategy(r.DecodeString()) + x.Rule = SELinuxStrategy(r.DecodeString()) } case "seLinuxOptions": if r.TryDecodeAsNil() { @@ -18697,7 +17729,7 @@ func (x *SELinuxContextStrategyOptions) codecDecodeSelfFromMap(l int, d *codec19 z.DecSendContainerState(codecSelfer_containerMapEnd1234) } -func (x *SELinuxContextStrategyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { +func (x *SELinuxStrategyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r @@ -18716,9 +17748,9 @@ func (x *SELinuxContextStrategyOptions) codecDecodeSelfFromArray(l int, d *codec } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.Type = "" + x.Rule = "" } else { - x.Type = SELinuxContextStrategy(r.DecodeString()) + x.Rule = SELinuxStrategy(r.DecodeString()) } yyj6++ if yyhl6 { @@ -18757,7 +17789,7 @@ func (x *SELinuxContextStrategyOptions) codecDecodeSelfFromArray(l int, d *codec z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } -func (x SELinuxContextStrategy) CodecEncodeSelf(e *codec1978.Encoder) { +func (x SELinuxStrategy) CodecEncodeSelf(e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) _, _, _ = h, z, r @@ -18770,7 +17802,7 @@ func (x SELinuxContextStrategy) CodecEncodeSelf(e *codec1978.Encoder) { } } -func (x *SELinuxContextStrategy) CodecDecodeSelf(d *codec1978.Decoder) { +func (x *SELinuxStrategy) CodecDecodeSelf(d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r @@ -18816,12 +17848,12 @@ func (x *RunAsUserStrategyOptions) CodecEncodeSelf(e *codec1978.Encoder) { } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) - x.Type.CodecEncodeSelf(e) + x.Rule.CodecEncodeSelf(e) } else { z.EncSendContainerState(codecSelfer_containerMapKey1234) - r.EncodeString(codecSelferC_UTF81234, string("type")) + r.EncodeString(codecSelferC_UTF81234, string("rule")) z.EncSendContainerState(codecSelfer_containerMapValue1234) - x.Type.CodecEncodeSelf(e) + x.Rule.CodecEncodeSelf(e) } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayElem1234) @@ -18917,11 +17949,11 @@ func (x *RunAsUserStrategyOptions) codecDecodeSelfFromMap(l int, d *codec1978.De yys3 := string(yys3Slc) z.DecSendContainerState(codecSelfer_containerMapValue1234) switch yys3 { - case "type": + case "rule": if r.TryDecodeAsNil() { - x.Type = "" + x.Rule = "" } else { - x.Type = RunAsUserStrategy(r.DecodeString()) + x.Rule = RunAsUserStrategy(r.DecodeString()) } case "ranges": if r.TryDecodeAsNil() { @@ -18961,9 +17993,9 @@ func (x *RunAsUserStrategyOptions) codecDecodeSelfFromArray(l int, d *codec1978. } z.DecSendContainerState(codecSelfer_containerArrayElem1234) if r.TryDecodeAsNil() { - x.Type = "" + x.Rule = "" } else { - x.Type = RunAsUserStrategy(r.DecodeString()) + x.Rule = RunAsUserStrategy(r.DecodeString()) } yyj7++ if yyhl7 { @@ -19972,7 +19004,7 @@ func (x codecSelfer1234) decSliceAPIVersion(v *[]APIVersion, d *codec1978.Decode yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 32) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 16) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] @@ -20210,7 +19242,7 @@ func (x codecSelfer1234) decSliceDeployment(v *[]Deployment, d *codec1978.Decode yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 632) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 640) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] @@ -20567,7 +19599,7 @@ func (x codecSelfer1234) decSliceJob(v *[]Job, d *codec1978.Decoder) { yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 632) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 640) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] @@ -21242,244 +20274,6 @@ func (x codecSelfer1234) decSliceHTTPIngressPath(v *[]HTTPIngressPath, d *codec1 } } -func (x codecSelfer1234) encSliceNodeUtilization(v []NodeUtilization, e *codec1978.Encoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperEncoder(e) - _, _, _ = h, z, r - r.EncodeArrayStart(len(v)) - for _, yyv1 := range v { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy2 := &yyv1 - yy2.CodecEncodeSelf(e) - } - z.EncSendContainerState(codecSelfer_containerArrayEnd1234) -} - -func (x codecSelfer1234) decSliceNodeUtilization(v *[]NodeUtilization, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - - yyv1 := *v - yyh1, yyl1 := z.DecSliceHelperStart() - var yyc1 bool - _ = yyc1 - if yyl1 == 0 { - if yyv1 == nil { - yyv1 = []NodeUtilization{} - yyc1 = true - } else if len(yyv1) != 0 { - yyv1 = yyv1[:0] - yyc1 = true - } - } else if yyl1 > 0 { - var yyrr1, yyrl1 int - var yyrt1 bool - _, _ = yyrl1, yyrt1 - yyrr1 = yyl1 // len(yyv1) - if yyl1 > cap(yyv1) { - - yyrg1 := len(yyv1) > 0 - yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 24) - if yyrt1 { - if yyrl1 <= cap(yyv1) { - yyv1 = yyv1[:yyrl1] - } else { - yyv1 = make([]NodeUtilization, yyrl1) - } - } else { - yyv1 = make([]NodeUtilization, yyrl1) - } - yyc1 = true - yyrr1 = len(yyv1) - if yyrg1 { - copy(yyv1, yyv21) - } - } else if yyl1 != len(yyv1) { - yyv1 = yyv1[:yyl1] - yyc1 = true - } - yyj1 := 0 - for ; yyj1 < yyrr1; yyj1++ { - yyh1.ElemContainerState(yyj1) - if r.TryDecodeAsNil() { - yyv1[yyj1] = NodeUtilization{} - } else { - yyv2 := &yyv1[yyj1] - yyv2.CodecDecodeSelf(d) - } - - } - if yyrt1 { - for ; yyj1 < yyl1; yyj1++ { - yyv1 = append(yyv1, NodeUtilization{}) - yyh1.ElemContainerState(yyj1) - if r.TryDecodeAsNil() { - yyv1[yyj1] = NodeUtilization{} - } else { - yyv3 := &yyv1[yyj1] - yyv3.CodecDecodeSelf(d) - } - - } - } - - } else { - yyj1 := 0 - for ; !r.CheckBreak(); yyj1++ { - - if yyj1 >= len(yyv1) { - yyv1 = append(yyv1, NodeUtilization{}) // var yyz1 NodeUtilization - yyc1 = true - } - yyh1.ElemContainerState(yyj1) - if yyj1 < len(yyv1) { - if r.TryDecodeAsNil() { - yyv1[yyj1] = NodeUtilization{} - } else { - yyv4 := &yyv1[yyj1] - yyv4.CodecDecodeSelf(d) - } - - } else { - z.DecSwallow() - } - - } - if yyj1 < len(yyv1) { - yyv1 = yyv1[:yyj1] - yyc1 = true - } else if yyj1 == 0 && yyv1 == nil { - yyv1 = []NodeUtilization{} - yyc1 = true - } - } - yyh1.End() - if yyc1 { - *v = yyv1 - } -} - -func (x codecSelfer1234) encSliceClusterAutoscaler(v []ClusterAutoscaler, e *codec1978.Encoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperEncoder(e) - _, _, _ = h, z, r - r.EncodeArrayStart(len(v)) - for _, yyv1 := range v { - z.EncSendContainerState(codecSelfer_containerArrayElem1234) - yy2 := &yyv1 - yy2.CodecEncodeSelf(e) - } - z.EncSendContainerState(codecSelfer_containerArrayEnd1234) -} - -func (x codecSelfer1234) decSliceClusterAutoscaler(v *[]ClusterAutoscaler, d *codec1978.Decoder) { - var h codecSelfer1234 - z, r := codec1978.GenHelperDecoder(d) - _, _, _ = h, z, r - - yyv1 := *v - yyh1, yyl1 := z.DecSliceHelperStart() - var yyc1 bool - _ = yyc1 - if yyl1 == 0 { - if yyv1 == nil { - yyv1 = []ClusterAutoscaler{} - yyc1 = true - } else if len(yyv1) != 0 { - yyv1 = yyv1[:0] - yyc1 = true - } - } else if yyl1 > 0 { - var yyrr1, yyrl1 int - var yyrt1 bool - _, _ = yyrl1, yyrt1 - yyrr1 = yyl1 // len(yyv1) - if yyl1 > cap(yyv1) { - - yyrg1 := len(yyv1) > 0 - yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 224) - if yyrt1 { - if yyrl1 <= cap(yyv1) { - yyv1 = yyv1[:yyrl1] - } else { - yyv1 = make([]ClusterAutoscaler, yyrl1) - } - } else { - yyv1 = make([]ClusterAutoscaler, yyrl1) - } - yyc1 = true - yyrr1 = len(yyv1) - if yyrg1 { - copy(yyv1, yyv21) - } - } else if yyl1 != len(yyv1) { - yyv1 = yyv1[:yyl1] - yyc1 = true - } - yyj1 := 0 - for ; yyj1 < yyrr1; yyj1++ { - yyh1.ElemContainerState(yyj1) - if r.TryDecodeAsNil() { - yyv1[yyj1] = ClusterAutoscaler{} - } else { - yyv2 := &yyv1[yyj1] - yyv2.CodecDecodeSelf(d) - } - - } - if yyrt1 { - for ; yyj1 < yyl1; yyj1++ { - yyv1 = append(yyv1, ClusterAutoscaler{}) - yyh1.ElemContainerState(yyj1) - if r.TryDecodeAsNil() { - yyv1[yyj1] = ClusterAutoscaler{} - } else { - yyv3 := &yyv1[yyj1] - yyv3.CodecDecodeSelf(d) - } - - } - } - - } else { - yyj1 := 0 - for ; !r.CheckBreak(); yyj1++ { - - if yyj1 >= len(yyv1) { - yyv1 = append(yyv1, ClusterAutoscaler{}) // var yyz1 ClusterAutoscaler - yyc1 = true - } - yyh1.ElemContainerState(yyj1) - if yyj1 < len(yyv1) { - if r.TryDecodeAsNil() { - yyv1[yyj1] = ClusterAutoscaler{} - } else { - yyv4 := &yyv1[yyj1] - yyv4.CodecDecodeSelf(d) - } - - } else { - z.DecSwallow() - } - - } - if yyj1 < len(yyv1) { - yyv1 = yyv1[:yyj1] - yyc1 = true - } else if yyj1 == 0 && yyv1 == nil { - yyv1 = []ClusterAutoscaler{} - yyc1 = true - } - } - yyh1.End() - if yyc1 { - *v = yyv1 - } -} - func (x codecSelfer1234) encSliceLabelSelectorRequirement(v []LabelSelectorRequirement, e *codec1978.Encoder) { var h codecSelfer1234 z, r := codec1978.GenHelperEncoder(e) @@ -21638,7 +20432,7 @@ func (x codecSelfer1234) decSliceReplicaSet(v *[]ReplicaSet, d *codec1978.Decode yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 232) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 576) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types.go index 4de157e3c..5817b4497 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types.go @@ -36,6 +36,14 @@ type ScaleStatus struct { // label query over pods that should match the replicas count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors Selector map[string]string `json:"selector,omitempty"` + + // label selector for pods that should match the replicas count. This is a serializated + // version of both map-based and more expressive set-based selectors. This is done to + // avoid introspection in the clients. The string will be in the same format as the + // query-param syntax. If the target type only supports map-based selectors, both this + // field and map-based selector field are populated. + // More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors + TargetSelector string `json:"targetSelector,omitempty"` } // +genclient=true,noMethods=true @@ -190,9 +198,6 @@ type ThirdPartyResourceList struct { type APIVersion struct { // Name of this version (e.g. 'v1'). Name string `json:"name,omitempty"` - - // The API group to add this object into, default 'experimental'. - APIGroup string `json:"apiGroup,omitempty"` } // An internal object, used for versioned storage in etcd. Not exposed to the end user. @@ -328,6 +333,9 @@ type RollingUpdateDeployment struct { // DeploymentStatus is the most recently observed status of the Deployment. type DeploymentStatus struct { + // The generation observed by the deployment controller. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + // Total number of non-terminated pods targeted by this deployment (their labels match the selector). Replicas int32 `json:"replicas,omitempty"` @@ -439,17 +447,17 @@ const ( type DaemonSetStatus struct { // CurrentNumberScheduled is the number of nodes that are running at least 1 // daemon pod and are supposed to run the daemon pod. - // More info: http://releases.k8s.io/HEAD/docs/admin/daemon.md + // More info: http://releases.k8s.io/HEAD/docs/admin/daemons.md CurrentNumberScheduled int32 `json:"currentNumberScheduled"` // NumberMisscheduled is the number of nodes that are running the daemon pod, but are // not supposed to run the daemon pod. - // More info: http://releases.k8s.io/HEAD/docs/admin/daemon.md + // More info: http://releases.k8s.io/HEAD/docs/admin/daemons.md NumberMisscheduled int32 `json:"numberMisscheduled"` // DesiredNumberScheduled is the total number of nodes that should be running the daemon // pod (including nodes correctly running the daemon pod). - // More info: http://releases.k8s.io/HEAD/docs/admin/daemon.md + // More info: http://releases.k8s.io/HEAD/docs/admin/daemons.md DesiredNumberScheduled int32 `json:"desiredNumberScheduled"` } @@ -540,6 +548,7 @@ type JobSpec struct { // pod signals the success of all pods, and allows parallelism to have any positive // value. Setting to 1 means that parallelism is limited to 1 and the success of that // pod signals the success of the job. + // More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md Completions *int32 `json:"completions,omitempty"` // Optional duration in seconds relative to the startTime that the job may be active @@ -547,9 +556,17 @@ type JobSpec struct { ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"` // Selector is a label query over pods that should match the pod count. + // Normally, the system sets this field for you. // More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors Selector *LabelSelector `json:"selector,omitempty"` + // AutoSelector controls generation of pod labels and pod selectors. + // It was not present in the original extensions/v1beta1 Job definition, but exists + // to allow conversion from batch/v1 Jobs, where it corresponds to, but has the opposite + // meaning as, ManualSelector. + // More info: http://releases.k8s.io/HEAD/docs/design/selector-generation.md + AutoSelector *bool `json:"autoSelector,omitempty"` + // Template is the object that describes the pod that will be created when // executing a job. // More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md @@ -650,9 +667,10 @@ type IngressSpec struct { Backend *IngressBackend `json:"backend,omitempty"` // TLS configuration. Currently the Ingress only supports a single TLS - // port, 443, and assumes TLS termination. If multiple members of this - // list specify different hosts, they will be multiplexed on the same - // port according to the hostname specified through the SNI TLS extension. + // port, 443. If multiple members of this list specify different hosts, they + // will be multiplexed on the same port according to the hostname specified + // through the SNI TLS extension, if the ingress controller fulfilling the + // ingress supports SNI. TLS []IngressTLS `json:"tls,omitempty"` // A list of host rules used to configure the Ingress. If unspecified, or @@ -760,67 +778,6 @@ type IngressBackend struct { ServicePort intstr.IntOrString `json:"servicePort"` } -type NodeResource string - -const ( - // Percentage of node's CPUs that is currently used. - CpuConsumption NodeResource = "CpuConsumption" - - // Percentage of node's CPUs that is currently requested for pods. - CpuRequest NodeResource = "CpuRequest" - - // Percentage od node's memory that is currently used. - MemConsumption NodeResource = "MemConsumption" - - // Percentage of node's CPUs that is currently requested for pods. - MemRequest NodeResource = "MemRequest" -) - -// NodeUtilization describes what percentage of a particular resource is used on a node. -type NodeUtilization struct { - Resource NodeResource `json:"resource"` - - // The accepted values are from 0 to 1. - Value float64 `json:"value"` -} - -// Configuration of the Cluster Autoscaler -type ClusterAutoscalerSpec struct { - // Minimum number of nodes that the cluster should have. - MinNodes int32 `json:"minNodes"` - - // Maximum number of nodes that the cluster should have. - MaxNodes int32 `json:"maxNodes"` - - // Target average utilization of the cluster nodes. New nodes will be added if one of the - // targets is exceeded. Cluster size will be decreased if the current utilization is too low - // for all targets. - TargetUtilization []NodeUtilization `json:"target"` -} - -type ClusterAutoscaler struct { - unversioned.TypeMeta `json:",inline"` - - // Standard object's metadata. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata - // For now (experimental api) it is required that the name is set to "ClusterAutoscaler" and namespace is "default". - v1.ObjectMeta `json:"metadata,omitempty"` - - // Spec defines the desired behavior of this daemon set. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status - Spec ClusterAutoscalerSpec `json:"spec,omitempty"` -} - -// There will be just one (or none) ClusterAutoscaler. -type ClusterAutoscalerList struct { - unversioned.TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata - unversioned.ListMeta `json:"metadata,omitempty"` - - Items []ClusterAutoscaler `json:"items"` -} - // ExportOptions is the query options to the standard REST get call. type ExportOptions struct { unversioned.TypeMeta `json:",inline"` @@ -939,7 +896,7 @@ type ReplicaSetSpec struct { // Template is the object that describes the pod that will be created if // insufficient replicas are detected. // More info: http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md#pod-template - Template *v1.PodTemplateSpec `json:"template,omitempty"` + Template v1.PodTemplateSpec `json:"template,omitempty"` } // ReplicaSetStatus represents the current status of a ReplicaSet. @@ -948,6 +905,9 @@ type ReplicaSetStatus struct { // More info: http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md#what-is-a-replication-controller Replicas int32 `json:"replicas"` + // The number of pods that have labels matching the labels of the pod template of the replicaset. + FullyLabeledReplicas int32 `json:"fullyLabeledReplicas,omitempty"` + // ObservedGeneration reflects the generation of the most recently observed ReplicaSet. ObservedGeneration int64 `json:"observedGeneration,omitempty"` } @@ -981,8 +941,8 @@ type PodSecurityPolicySpec struct { HostPID bool `json:"hostPID,omitempty"` // hostIPC determines if the policy allows the use of HostIPC in the pod spec. HostIPC bool `json:"hostIPC,omitempty"` - // seLinuxContext is the strategy that will dictate the allowable labels that may be set. - SELinuxContext SELinuxContextStrategyOptions `json:"seLinuxContext,omitempty"` + // seLinux is the strategy that will dictate the allowable labels that may be set. + SELinux SELinuxStrategyOptions `json:"seLinux,omitempty"` // runAsUser is the strategy that will dictate the allowable RunAsUser values that may be set. RunAsUser RunAsUserStrategyOptions `json:"runAsUser,omitempty"` } @@ -1017,30 +977,30 @@ type HostPortRange struct { Max int32 `json:"max"` } -// SELinux Context Strategy Options defines the strategy type and any options used to create the strategy. -type SELinuxContextStrategyOptions struct { +// SELinux Strategy Options defines the strategy type and any options used to create the strategy. +type SELinuxStrategyOptions struct { // type is the strategy that will dictate the allowable labels that may be set. - Type SELinuxContextStrategy `json:"type"` + Rule SELinuxStrategy `json:"rule"` // seLinuxOptions required to run as; required for MustRunAs // More info: http://releases.k8s.io/HEAD/docs/design/security_context.md#security-context SELinuxOptions *v1.SELinuxOptions `json:"seLinuxOptions,omitempty"` } -// SELinux Context Strategy Type denotes strategy types for generating SELinux options for a +// SELinuxStrategy denotes strategy types for generating SELinux options for a // Security Context. -type SELinuxContextStrategy string +type SELinuxStrategy string const ( // container must have SELinux labels of X applied. - SELinuxStrategyMustRunAs SELinuxContextStrategy = "MustRunAs" + SELinuxStrategyMustRunAs SELinuxStrategy = "MustRunAs" // container may make requests for any SELinux context labels. - SELinuxStrategyRunAsAny SELinuxContextStrategy = "RunAsAny" + SELinuxStrategyRunAsAny SELinuxStrategy = "RunAsAny" ) // Run A sUser Strategy Options defines the strategy type and any options used to create the strategy. type RunAsUserStrategyOptions struct { - // type is the strategy that will dictate the allowable RunAsUser values that may be set. - Type RunAsUserStrategy `json:"type"` + // Rule is the strategy that will dictate the allowable RunAsUser values that may be set. + Rule RunAsUserStrategy `json:"rule"` // Ranges are the allowed ranges of uids that may be used. Ranges []IDRange `json:"ranges,omitempty"` } @@ -1053,7 +1013,7 @@ type IDRange struct { Max int64 `json:"max"` } -// Run As User Strategy Type denotes strategy types for generating RunAsUser values for a +// RunAsUserStrategy denotes strategy types for generating RunAsUser values for a // Security Context. type RunAsUserStrategy string @@ -1070,7 +1030,7 @@ const ( type PodSecurityPolicyList struct { unversioned.TypeMeta `json:",inline"` // Standard list metadata. - // More info: http://docs.k8s.io/api-conventions.md#metadata + // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata unversioned.ListMeta `json:"metadata,omitempty"` // Items is a list of schema objects. diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go index e6176c293..8e92ffb78 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/v1beta1/types_swagger_doc_generated.go @@ -16,7 +16,7 @@ limitations under the License. package v1beta1 -// This file contains a collection of methods that can be used from go-resful to +// This file contains a collection of methods that can be used from go-restful to // generate Swagger API documentation for its models. Please read this PR for more // information on the implementation: https://github.com/emicklei/go-restful/pull/215 // @@ -28,9 +28,8 @@ package v1beta1 // AUTO-GENERATED FUNCTIONS START HERE var map_APIVersion = map[string]string{ - "": "An APIVersion represents a single concrete version of an object model.", - "name": "Name of this version (e.g. 'v1').", - "apiGroup": "The API group to add this object into, default 'experimental'.", + "": "An APIVersion represents a single concrete version of an object model.", + "name": "Name of this version (e.g. 'v1').", } func (APIVersion) SwaggerDoc() map[string]string { @@ -45,35 +44,6 @@ func (CPUTargetUtilization) SwaggerDoc() map[string]string { return map_CPUTargetUtilization } -var map_ClusterAutoscaler = map[string]string{ - "metadata": "Standard object's metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata For now (experimental api) it is required that the name is set to \"ClusterAutoscaler\" and namespace is \"default\".", - "spec": "Spec defines the desired behavior of this daemon set. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status", -} - -func (ClusterAutoscaler) SwaggerDoc() map[string]string { - return map_ClusterAutoscaler -} - -var map_ClusterAutoscalerList = map[string]string{ - "": "There will be just one (or none) ClusterAutoscaler.", - "metadata": "Standard object's metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata", -} - -func (ClusterAutoscalerList) SwaggerDoc() map[string]string { - return map_ClusterAutoscalerList -} - -var map_ClusterAutoscalerSpec = map[string]string{ - "": "Configuration of the Cluster Autoscaler", - "minNodes": "Minimum number of nodes that the cluster should have.", - "maxNodes": "Maximum number of nodes that the cluster should have.", - "target": "Target average utilization of the cluster nodes. New nodes will be added if one of the targets is exceeded. Cluster size will be decreased if the current utilization is too low for all targets.", -} - -func (ClusterAutoscalerSpec) SwaggerDoc() map[string]string { - return map_ClusterAutoscalerSpec -} - var map_CustomMetricCurrentStatus = map[string]string{ "name": "Custom Metric name.", "value": "Custom Metric value (average).", @@ -126,9 +96,9 @@ func (DaemonSetSpec) SwaggerDoc() map[string]string { var map_DaemonSetStatus = map[string]string{ "": "DaemonSetStatus represents the current status of a daemon set.", - "currentNumberScheduled": "CurrentNumberScheduled is the number of nodes that are running at least 1 daemon pod and are supposed to run the daemon pod. More info: http://releases.k8s.io/HEAD/docs/admin/daemon.md", - "numberMisscheduled": "NumberMisscheduled is the number of nodes that are running the daemon pod, but are not supposed to run the daemon pod. More info: http://releases.k8s.io/HEAD/docs/admin/daemon.md", - "desiredNumberScheduled": "DesiredNumberScheduled is the total number of nodes that should be running the daemon pod (including nodes correctly running the daemon pod). More info: http://releases.k8s.io/HEAD/docs/admin/daemon.md", + "currentNumberScheduled": "CurrentNumberScheduled is the number of nodes that are running at least 1 daemon pod and are supposed to run the daemon pod. More info: http://releases.k8s.io/HEAD/docs/admin/daemons.md", + "numberMisscheduled": "NumberMisscheduled is the number of nodes that are running the daemon pod, but are not supposed to run the daemon pod. More info: http://releases.k8s.io/HEAD/docs/admin/daemons.md", + "desiredNumberScheduled": "DesiredNumberScheduled is the total number of nodes that should be running the daemon pod (including nodes correctly running the daemon pod). More info: http://releases.k8s.io/HEAD/docs/admin/daemons.md", } func (DaemonSetStatus) SwaggerDoc() map[string]string { @@ -185,6 +155,7 @@ func (DeploymentSpec) SwaggerDoc() map[string]string { var map_DeploymentStatus = map[string]string{ "": "DeploymentStatus is the most recently observed status of the Deployment.", + "observedGeneration": "The generation observed by the deployment controller.", "replicas": "Total number of non-terminated pods targeted by this deployment (their labels match the selector).", "updatedReplicas": "Total number of non-terminated pods targeted by this deployment that have the desired template spec.", "availableReplicas": "Total number of available pods (ready for at least minReadySeconds) targeted by this deployment.", @@ -351,7 +322,7 @@ func (IngressRuleValue) SwaggerDoc() map[string]string { var map_IngressSpec = map[string]string{ "": "IngressSpec describes the Ingress the user wishes to exist.", "backend": "A default backend capable of servicing requests that don't match any rule. At least one of 'backend' or 'rules' must be specified. This field is optional to allow the loadbalancer controller or defaulting logic to specify a global default.", - "tls": "TLS configuration. Currently the Ingress only supports a single TLS port, 443, and assumes TLS termination. If multiple members of this list specify different hosts, they will be multiplexed on the same port according to the hostname specified through the SNI TLS extension.", + "tls": "TLS configuration. Currently the Ingress only supports a single TLS port, 443. If multiple members of this list specify different hosts, they will be multiplexed on the same port according to the hostname specified through the SNI TLS extension, if the ingress controller fulfilling the ingress supports SNI.", "rules": "A list of host rules used to configure the Ingress. If unspecified, or no rule matches, all traffic is sent to the default backend.", } @@ -416,9 +387,10 @@ func (JobList) SwaggerDoc() map[string]string { var map_JobSpec = map[string]string{ "": "JobSpec describes how the job execution will look like.", "parallelism": "Parallelism specifies the maximum desired number of pods the job should run at any given time. The actual number of pods running in steady state will be less than this number when ((.spec.completions - .status.successful) < .spec.parallelism), i.e. when the work left to do is less than max parallelism. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md", - "completions": "Completions specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job.", + "completions": "Completions specifies the desired number of successfully finished pods the job should be run with. Setting to nil means that the success of any pod signals the success of all pods, and allows parallelism to have any positive value. Setting to 1 means that parallelism is limited to 1 and the success of that pod signals the success of the job. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md", "activeDeadlineSeconds": "Optional duration in seconds relative to the startTime that the job may be active before the system tries to terminate it; value must be positive integer", - "selector": "Selector is a label query over pods that should match the pod count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors", + "selector": "Selector is a label query over pods that should match the pod count. Normally, the system sets this field for you. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors", + "autoSelector": "AutoSelector controls generation of pod labels and pod selectors. It was not present in the original extensions/v1beta1 Job definition, but exists to allow conversion from batch/v1 Jobs, where it corresponds to, but has the opposite meaning as, ManualSelector. More info: http://releases.k8s.io/HEAD/docs/design/selector-generation.md", "template": "Template is the object that describes the pod that will be created when executing a job. More info: http://releases.k8s.io/HEAD/docs/user-guide/jobs.md", } @@ -474,15 +446,6 @@ func (ListOptions) SwaggerDoc() map[string]string { return map_ListOptions } -var map_NodeUtilization = map[string]string{ - "": "NodeUtilization describes what percentage of a particular resource is used on a node.", - "value": "The accepted values are from 0 to 1.", -} - -func (NodeUtilization) SwaggerDoc() map[string]string { - return map_NodeUtilization -} - var map_PodSecurityPolicy = map[string]string{ "": "Pod Security Policy governs the ability to make requests that affect the Security Context that will be applied to a pod and container.", "metadata": "Standard object's metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata", @@ -495,7 +458,7 @@ func (PodSecurityPolicy) SwaggerDoc() map[string]string { var map_PodSecurityPolicyList = map[string]string{ "": "Pod Security Policy List is a list of PodSecurityPolicy objects.", - "metadata": "Standard list metadata. More info: http://docs.k8s.io/api-conventions.md#metadata", + "metadata": "Standard list metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata", "items": "Items is a list of schema objects.", } @@ -504,16 +467,16 @@ func (PodSecurityPolicyList) SwaggerDoc() map[string]string { } var map_PodSecurityPolicySpec = map[string]string{ - "": "Pod Security Policy Spec defines the policy enforced.", - "privileged": "privileged determines if a pod can request to be run as privileged.", - "capabilities": "capabilities is a list of capabilities that can be added.", - "volumes": "volumes is a white list of allowed volume plugins. Empty indicates that all plugins may be used.", - "hostNetwork": "hostNetwork determines if the policy allows the use of HostNetwork in the pod spec.", - "hostPorts": "hostPorts determines which host port ranges are allowed to be exposed.", - "hostPID": "hostPID determines if the policy allows the use of HostPID in the pod spec.", - "hostIPC": "hostIPC determines if the policy allows the use of HostIPC in the pod spec.", - "seLinuxContext": "seLinuxContext is the strategy that will dictate the allowable labels that may be set.", - "runAsUser": "runAsUser is the strategy that will dictate the allowable RunAsUser values that may be set.", + "": "Pod Security Policy Spec defines the policy enforced.", + "privileged": "privileged determines if a pod can request to be run as privileged.", + "capabilities": "capabilities is a list of capabilities that can be added.", + "volumes": "volumes is a white list of allowed volume plugins. Empty indicates that all plugins may be used.", + "hostNetwork": "hostNetwork determines if the policy allows the use of HostNetwork in the pod spec.", + "hostPorts": "hostPorts determines which host port ranges are allowed to be exposed.", + "hostPID": "hostPID determines if the policy allows the use of HostPID in the pod spec.", + "hostIPC": "hostIPC determines if the policy allows the use of HostIPC in the pod spec.", + "seLinux": "seLinux is the strategy that will dictate the allowable labels that may be set.", + "runAsUser": "runAsUser is the strategy that will dictate the allowable RunAsUser values that may be set.", } func (PodSecurityPolicySpec) SwaggerDoc() map[string]string { @@ -553,9 +516,10 @@ func (ReplicaSetSpec) SwaggerDoc() map[string]string { } var map_ReplicaSetStatus = map[string]string{ - "": "ReplicaSetStatus represents the current status of a ReplicaSet.", - "replicas": "Replicas is the most recently oberved number of replicas. More info: http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md#what-is-a-replication-controller", - "observedGeneration": "ObservedGeneration reflects the generation of the most recently observed ReplicaSet.", + "": "ReplicaSetStatus represents the current status of a ReplicaSet.", + "replicas": "Replicas is the most recently oberved number of replicas. More info: http://releases.k8s.io/HEAD/docs/user-guide/replication-controller.md#what-is-a-replication-controller", + "fullyLabeledReplicas": "The number of pods that have labels matching the labels of the pod template of the replicaset.", + "observedGeneration": "ObservedGeneration reflects the generation of the most recently observed ReplicaSet.", } func (ReplicaSetStatus) SwaggerDoc() map[string]string { @@ -590,7 +554,7 @@ func (RollingUpdateDeployment) SwaggerDoc() map[string]string { var map_RunAsUserStrategyOptions = map[string]string{ "": "Run A sUser Strategy Options defines the strategy type and any options used to create the strategy.", - "type": "type is the strategy that will dictate the allowable RunAsUser values that may be set.", + "rule": "Rule is the strategy that will dictate the allowable RunAsUser values that may be set.", "ranges": "Ranges are the allowed ranges of uids that may be used.", } @@ -598,14 +562,14 @@ func (RunAsUserStrategyOptions) SwaggerDoc() map[string]string { return map_RunAsUserStrategyOptions } -var map_SELinuxContextStrategyOptions = map[string]string{ - "": "SELinux Context Strategy Options defines the strategy type and any options used to create the strategy.", - "type": "type is the strategy that will dictate the allowable labels that may be set.", +var map_SELinuxStrategyOptions = map[string]string{ + "": "SELinux Strategy Options defines the strategy type and any options used to create the strategy.", + "rule": "type is the strategy that will dictate the allowable labels that may be set.", "seLinuxOptions": "seLinuxOptions required to run as; required for MustRunAs More info: http://releases.k8s.io/HEAD/docs/design/security_context.md#security-context", } -func (SELinuxContextStrategyOptions) SwaggerDoc() map[string]string { - return map_SELinuxContextStrategyOptions +func (SELinuxStrategyOptions) SwaggerDoc() map[string]string { + return map_SELinuxStrategyOptions } var map_Scale = map[string]string{ @@ -629,9 +593,10 @@ func (ScaleSpec) SwaggerDoc() map[string]string { } var map_ScaleStatus = map[string]string{ - "": "represents the current status of a scale subresource.", - "replicas": "actual number of observed instances of the scaled object.", - "selector": "label query over pods that should match the replicas count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors", + "": "represents the current status of a scale subresource.", + "replicas": "actual number of observed instances of the scaled object.", + "selector": "label query over pods that should match the replicas count. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors", + "targetSelector": "label selector for pods that should match the replicas count. This is a serializated version of both map-based and more expressive set-based selectors. This is done to avoid introspection in the clients. The string will be in the same format as the query-param syntax. If the target type only supports map-based selectors, both this field and map-based selector field are populated. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors", } func (ScaleStatus) SwaggerDoc() map[string]string { diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation.go index 22aaac64e..2bed2dd90 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation.go @@ -124,9 +124,9 @@ func ValidateHorizontalPodAutoscalerUpdate(newAutoscaler, oldAutoscaler *extensi return allErrs } -func ValidateHorizontalPodAutoscalerStatusUpdate(controller, oldController *extensions.HorizontalPodAutoscaler) field.ErrorList { - allErrs := apivalidation.ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, field.NewPath("metadata")) - status := controller.Status +func ValidateHorizontalPodAutoscalerStatusUpdate(newAutoscaler, oldAutoscaler *extensions.HorizontalPodAutoscaler) field.ErrorList { + allErrs := apivalidation.ValidateObjectMetaUpdate(&newAutoscaler.ObjectMeta, &oldAutoscaler.ObjectMeta, field.NewPath("metadata")) + status := newAutoscaler.Status allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.CurrentReplicas), field.NewPath("status", "currentReplicas"))...) allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.DesiredReplicas), field.NewPath("status", "desiredReplicasa"))...) return allErrs @@ -162,16 +162,16 @@ func ValidateThirdPartyResource(obj *extensions.ThirdPartyResource) field.ErrorL } // ValidateDaemonSet tests if required fields in the DaemonSet are set. -func ValidateDaemonSet(controller *extensions.DaemonSet) field.ErrorList { - allErrs := apivalidation.ValidateObjectMeta(&controller.ObjectMeta, true, ValidateDaemonSetName, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateDaemonSetSpec(&controller.Spec, field.NewPath("spec"))...) +func ValidateDaemonSet(ds *extensions.DaemonSet) field.ErrorList { + allErrs := apivalidation.ValidateObjectMeta(&ds.ObjectMeta, true, ValidateDaemonSetName, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateDaemonSetSpec(&ds.Spec, field.NewPath("spec"))...) return allErrs } // ValidateDaemonSetUpdate tests if required fields in the DaemonSet are set. -func ValidateDaemonSetUpdate(controller, oldController *extensions.DaemonSet) field.ErrorList { - allErrs := apivalidation.ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateDaemonSetSpec(&controller.Spec, field.NewPath("spec"))...) +func ValidateDaemonSetUpdate(ds, oldDS *extensions.DaemonSet) field.ErrorList { + allErrs := apivalidation.ValidateObjectMetaUpdate(&ds.ObjectMeta, &oldDS.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateDaemonSetSpec(&ds.Spec, field.NewPath("spec"))...) return allErrs } @@ -185,9 +185,9 @@ func validateDaemonSetStatus(status *extensions.DaemonSetStatus, fldPath *field. } // ValidateDaemonSetStatus validates tests if required fields in the DaemonSet Status section -func ValidateDaemonSetStatusUpdate(controller, oldController *extensions.DaemonSet) field.ErrorList { - allErrs := apivalidation.ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, validateDaemonSetStatus(&controller.Status, field.NewPath("status"))...) +func ValidateDaemonSetStatusUpdate(ds, oldDS *extensions.DaemonSet) field.ErrorList { + allErrs := apivalidation.ValidateObjectMetaUpdate(&ds.ObjectMeta, &oldDS.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, validateDaemonSetStatus(&ds.Status, field.NewPath("status"))...) return allErrs } @@ -201,6 +201,9 @@ func ValidateDaemonSetSpec(spec *extensions.DaemonSetSpec, fldPath *field.Path) if err == nil && !selector.Matches(labels.Set(spec.Template.Labels)) { allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "`selector` does not match template `labels`")) } + if spec.Selector != nil && len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is not valid for daemonset.")) + } allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"))...) // Daemons typically run on more than one node, so mark Read-Write persistent disks as invalid. @@ -329,12 +332,29 @@ func ValidateDeploymentSpec(spec *extensions.DeploymentSpec, fldPath *field.Path return allErrs } +// Validates given deployment status. +func ValidateDeploymentStatus(status *extensions.DeploymentStatus, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(status.ObservedGeneration, fldPath.Child("observedGeneration"))...) + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.Replicas), fldPath.Child("replicas"))...) + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UpdatedReplicas), fldPath.Child("updatedReplicas"))...) + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.AvailableReplicas), fldPath.Child("availableReplicas"))...) + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(status.UnavailableReplicas), fldPath.Child("unavailableReplicas"))...) + return allErrs +} + func ValidateDeploymentUpdate(update, old *extensions.Deployment) field.ErrorList { allErrs := apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata")) allErrs = append(allErrs, ValidateDeploymentSpec(&update.Spec, field.NewPath("spec"))...) return allErrs } +func ValidateDeploymentStatusUpdate(update, old *extensions.Deployment) field.ErrorList { + allErrs := apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateDeploymentStatus(&update.Status, field.NewPath("status"))...) + return allErrs +} + func ValidateDeployment(obj *extensions.Deployment) field.ErrorList { allErrs := apivalidation.ValidateObjectMeta(&obj.ObjectMeta, true, ValidateDeploymentName, field.NewPath("metadata")) allErrs = append(allErrs, ValidateDeploymentSpec(&obj.Spec, field.NewPath("spec"))...) @@ -362,9 +382,62 @@ func ValidateThirdPartyResourceData(obj *extensions.ThirdPartyResourceData) fiel return allErrs } +// TODO: generalize for other controller objects that will follow the same pattern, such as ReplicaSet and DaemonSet, and +// move to new location. Replace extensions.Job with an interface. +// +// ValidateGeneratedSelector validates that the generated selector on a controller object match the controller object +// metadata, and the labels on the pod template are as generated. +func ValidateGeneratedSelector(obj *extensions.Job) field.ErrorList { + allErrs := field.ErrorList{} + if obj.Spec.ManualSelector != nil && *obj.Spec.ManualSelector { + return allErrs + } + + if obj.Spec.Selector == nil { + return allErrs // This case should already have been checked in caller. No need for more errors. + } + + // If somehow uid was unset then we would get "controller-uid=" as the selector + // which is bad. + if obj.ObjectMeta.UID == "" { + allErrs = append(allErrs, field.Required(field.NewPath("metadata").Child("uid"), "")) + } + + // If somehow uid was unset then we would get "controller-uid=" as the selector + // which is bad. + if obj.ObjectMeta.UID == "" { + allErrs = append(allErrs, field.Required(field.NewPath("metadata").Child("uid"), "")) + } + + // If selector generation was requested, then expected labels must be + // present on pod template, and much match job's uid and name. The + // generated (not-manual) selectors/labels ensure no overlap with other + // controllers. The manual mode allows orphaning, adoption, + // backward-compatibility, and experimentation with new + // labeling/selection schemes. Automatic selector generation should + // have placed certain labels on the pod, but this could have failed if + // the user added coflicting labels. Validate that the expected + // generated ones are there. + + allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "controller-uid", string(obj.UID))...) + allErrs = append(allErrs, apivalidation.ValidateHasLabel(obj.Spec.Template.ObjectMeta, field.NewPath("spec").Child("template").Child("metadata"), "job-name", string(obj.Name))...) + expectedLabels := make(map[string]string) + expectedLabels["controller-uid"] = string(obj.UID) + expectedLabels["job-name"] = string(obj.Name) + // Whether manually or automatically generated, the selector of the job must match the pods it will produce. + if selector, err := unversioned.LabelSelectorAsSelector(obj.Spec.Selector); err == nil { + if !selector.Matches(labels.Set(expectedLabels)) { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("selector"), obj.Spec.Selector, "`selector` not auto-generated")) + } + } + + return allErrs +} + func ValidateJob(job *extensions.Job) field.ErrorList { // Jobs and rcs have the same name validation allErrs := apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateGeneratedSelector(job)...) allErrs = append(allErrs, ValidateJobSpec(&job.Spec, field.NewPath("spec"))...) return allErrs } @@ -387,6 +460,7 @@ func ValidateJobSpec(spec *extensions.JobSpec, fldPath *field.Path) field.ErrorL allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) } + // Whether manually or automatically generated, the selector of the job must match the pods it will produce. if selector, err := unversioned.LabelSelectorAsSelector(spec.Selector); err == nil { labels := labels.Set(spec.Template.Labels) if !selector.Matches(labels) { @@ -452,13 +526,6 @@ func ValidateIngressName(name string, prefix bool) (bool, string) { func validateIngressTLS(spec *extensions.IngressSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} - // Currently the Ingress only supports HTTP(S), so a secretName is required. - // This will not be the case if we support SSL routing at L4 via SNI. - for i, t := range spec.TLS { - if t.SecretName == "" { - allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("secretName"), spec.TLS[i].SecretName)) - } - } // TODO: Perform a more thorough validation of spec.TLS.Hosts that takes // the wildcard spec from RFC 6125 into account. return allErrs @@ -577,43 +644,6 @@ func validateIngressBackend(backend *extensions.IngressBackend, fldPath *field.P return allErrs } -func validateClusterAutoscalerSpec(spec extensions.ClusterAutoscalerSpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - if spec.MinNodes < 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("minNodes"), spec.MinNodes, "must be greater than or equal to 0")) - } - if spec.MaxNodes < spec.MinNodes { - allErrs = append(allErrs, field.Invalid(fldPath.Child("maxNodes"), spec.MaxNodes, "must be greater than or equal to `minNodes`")) - } - if len(spec.TargetUtilization) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("targetUtilization"), "")) - } - for _, target := range spec.TargetUtilization { - if len(target.Resource) == 0 { - allErrs = append(allErrs, field.Required(fldPath.Child("targetUtilization", "resource"), "")) - } - if target.Value <= 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("targetUtilization", "value"), target.Value, "must be greater than 0")) - } - if target.Value > 1 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("targetUtilization", "value"), target.Value, "must be less than or equal to 1")) - } - } - return allErrs -} - -func ValidateClusterAutoscaler(autoscaler *extensions.ClusterAutoscaler) field.ErrorList { - allErrs := field.ErrorList{} - if autoscaler.Name != "ClusterAutoscaler" { - allErrs = append(allErrs, field.Invalid(field.NewPath("metadata", "name"), autoscaler.Name, "must be 'ClusterAutoscaler'")) - } - if autoscaler.Namespace != api.NamespaceDefault { - allErrs = append(allErrs, field.Invalid(field.NewPath("metadata", "namespace"), autoscaler.Namespace, "must be 'default'")) - } - allErrs = append(allErrs, validateClusterAutoscalerSpec(autoscaler.Spec, field.NewPath("spec"))...) - return allErrs -} - func ValidateScale(scale *extensions.Scale) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&scale.ObjectMeta, true, apivalidation.NameIsDNSSubdomain, field.NewPath("metadata"))...) @@ -653,6 +683,7 @@ func ValidateReplicaSetStatusUpdate(rs, oldRs *extensions.ReplicaSet) field.Erro allErrs := field.ErrorList{} allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&rs.ObjectMeta, &oldRs.ObjectMeta, field.NewPath("metadata"))...) allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(rs.Status.Replicas), field.NewPath("status", "replicas"))...) + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(rs.Status.FullyLabeledReplicas), field.NewPath("status", "fullyLabeledReplicas"))...) allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(rs.Status.ObservedGeneration), field.NewPath("status", "observedGeneration"))...) return allErrs } @@ -676,7 +707,7 @@ func ValidateReplicaSetSpec(spec *extensions.ReplicaSetSpec, fldPath *field.Path if err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "invalid label selector.")) } else { - allErrs = append(allErrs, ValidatePodTemplateSpecForReplicaSet(spec.Template, selector, spec.Replicas, fldPath.Child("template"))...) + allErrs = append(allErrs, ValidatePodTemplateSpecForReplicaSet(&spec.Template, selector, spec.Replicas, fldPath.Child("template"))...) } return allErrs } @@ -725,21 +756,21 @@ func ValidatePodSecurityPolicySpec(spec *extensions.PodSecurityPolicySpec, fldPa allErrs := field.ErrorList{} allErrs = append(allErrs, validatePSPRunAsUser(fldPath.Child("runAsUser"), &spec.RunAsUser)...) - allErrs = append(allErrs, validatePSPSELinuxContext(fldPath.Child("seLinuxContext"), &spec.SELinuxContext)...) + allErrs = append(allErrs, validatePSPSELinux(fldPath.Child("seLinux"), &spec.SELinux)...) allErrs = append(allErrs, validatePodSecurityPolicyVolumes(fldPath, spec.Volumes)...) return allErrs } -// validatePSPSELinuxContext validates the SELinuxContext fields of PodSecurityPolicy. -func validatePSPSELinuxContext(fldPath *field.Path, seLinuxContext *extensions.SELinuxContextStrategyOptions) field.ErrorList { +// validatePSPSELinux validates the SELinux fields of PodSecurityPolicy. +func validatePSPSELinux(fldPath *field.Path, seLinux *extensions.SELinuxStrategyOptions) field.ErrorList { allErrs := field.ErrorList{} - // ensure the selinux strategy has a valid type - supportedSELinuxContextTypes := sets.NewString(string(extensions.SELinuxStrategyMustRunAs), + // ensure the selinux strategy has a valid rule + supportedSELinuxRules := sets.NewString(string(extensions.SELinuxStrategyMustRunAs), string(extensions.SELinuxStrategyRunAsAny)) - if !supportedSELinuxContextTypes.Has(string(seLinuxContext.Type)) { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), seLinuxContext.Type, supportedSELinuxContextTypes.List())) + if !supportedSELinuxRules.Has(string(seLinux.Rule)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("rule"), seLinux.Rule, supportedSELinuxRules.List())) } return allErrs @@ -749,12 +780,12 @@ func validatePSPSELinuxContext(fldPath *field.Path, seLinuxContext *extensions.S func validatePSPRunAsUser(fldPath *field.Path, runAsUser *extensions.RunAsUserStrategyOptions) field.ErrorList { allErrs := field.ErrorList{} - // ensure the user strategy has a valid type - supportedRunAsUserTypes := sets.NewString(string(extensions.RunAsUserStrategyMustRunAs), + // ensure the user strategy has a valid rule + supportedRunAsUserRules := sets.NewString(string(extensions.RunAsUserStrategyMustRunAs), string(extensions.RunAsUserStrategyMustRunAsNonRoot), string(extensions.RunAsUserStrategyRunAsAny)) - if !supportedRunAsUserTypes.Has(string(runAsUser.Type)) { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), runAsUser.Type, supportedRunAsUserTypes.List())) + if !supportedRunAsUserRules.Has(string(runAsUser.Rule)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("rule"), runAsUser.Rule, supportedRunAsUserRules.List())) } // validate range settings diff --git a/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation_test.go b/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation_test.go index c421bdd40..6a7db3b81 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation_test.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/extensions/validation/validation_test.go @@ -25,6 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/controller/podautoscaler" + "k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/util/intstr" ) @@ -353,11 +354,14 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) { t.Errorf("expected success: %v", errs) } } - errorCases := map[string]dsUpdateTest{ "negative values": { old: extensions.DaemonSet{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: api.NamespaceDefault, + ResourceVersion: "10", + }, Status: extensions.DaemonSetStatus{ CurrentNumberScheduled: 1, NumberMisscheduled: 2, @@ -365,7 +369,11 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) { }, }, update: extensions.DaemonSet{ - ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: api.NamespaceDefault, + ResourceVersion: "10", + }, Status: extensions.DaemonSetStatus{ CurrentNumberScheduled: -1, NumberMisscheduled: -1, @@ -376,7 +384,7 @@ func TestValidateDaemonSetStatusUpdate(t *testing.T) { } for testName, errorCase := range errorCases { - if errs := ValidateDaemonSetStatusUpdate(&errorCase.old, &errorCase.update); len(errs) == 0 { + if errs := ValidateDaemonSetStatusUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 { t.Errorf("expected failure: %s", testName) } } @@ -692,9 +700,16 @@ func TestValidateDaemonSet(t *testing.T) { Template: validPodTemplate.Template, }, }, + "nil selector": { + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: extensions.DaemonSetSpec{ + Template: validPodTemplate.Template, + }, + }, "empty selector": { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: extensions.DaemonSetSpec{ + Selector: &unversioned.LabelSelector{}, Template: validPodTemplate.Template, }, }, @@ -975,12 +990,15 @@ func TestValidateDeploymentRollback(t *testing.T) { } func TestValidateJob(t *testing.T) { - validSelector := &unversioned.LabelSelector{ + validManualSelector := &unversioned.LabelSelector{ MatchLabels: map[string]string{"a": "b"}, } - validPodTemplateSpec := api.PodTemplateSpec{ + validGeneratedSelector := &unversioned.LabelSelector{ + MatchLabels: map[string]string{"controller-uid": "1a2b3c", "job-name": "myjob"}, + } + validPodTemplateSpecForManual := api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ - Labels: validSelector.MatchLabels, + Labels: validManualSelector.MatchLabels, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyOnFailure, @@ -988,21 +1006,45 @@ func TestValidateJob(t *testing.T) { Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, }, } - successCases := []extensions.Job{ - { + validPodTemplateSpecForGenerated := api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validGeneratedSelector.MatchLabels, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyOnFailure, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + } + successCases := map[string]extensions.Job{ + "manual selector": { ObjectMeta: api.ObjectMeta{ Name: "myjob", Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), }, Spec: extensions.JobSpec{ - Selector: validSelector, - Template: validPodTemplateSpec, + Selector: validManualSelector, + ManualSelector: newBool(true), + Template: validPodTemplateSpecForManual, + }, + }, + "generated selector": { + ObjectMeta: api.ObjectMeta{ + Name: "myjob", + Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: extensions.JobSpec{ + Selector: validGeneratedSelector, + ManualSelector: newBool(false), + Template: validPodTemplateSpecForGenerated, }, }, } - for _, successCase := range successCases { - if errs := ValidateJob(&successCase); len(errs) != 0 { - t.Errorf("expected success: %v", errs) + for k, v := range successCases { + if errs := ValidateJob(&v); len(errs) != 0 { + t.Errorf("expected success for %s: %v", k, errs) } } negative := -1 @@ -1012,51 +1054,59 @@ func TestValidateJob(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "myjob", Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), }, Spec: extensions.JobSpec{ - Parallelism: &negative, - Selector: validSelector, - Template: validPodTemplateSpec, + Parallelism: &negative, + ManualSelector: newBool(true), + Template: validPodTemplateSpecForGenerated, }, }, "spec.completions:must be greater than or equal to 0": { ObjectMeta: api.ObjectMeta{ Name: "myjob", Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), }, Spec: extensions.JobSpec{ - Completions: &negative, - Selector: validSelector, - Template: validPodTemplateSpec, + Completions: &negative, + Selector: validManualSelector, + ManualSelector: newBool(true), + Template: validPodTemplateSpecForGenerated, }, }, "spec.activeDeadlineSeconds:must be greater than or equal to 0": { ObjectMeta: api.ObjectMeta{ Name: "myjob", Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), }, Spec: extensions.JobSpec{ ActiveDeadlineSeconds: &negative64, - Selector: validSelector, - Template: validPodTemplateSpec, + Selector: validManualSelector, + ManualSelector: newBool(true), + Template: validPodTemplateSpecForGenerated, }, }, "spec.selector:Required value": { ObjectMeta: api.ObjectMeta{ Name: "myjob", Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), }, Spec: extensions.JobSpec{ - Template: validPodTemplateSpec, + Template: validPodTemplateSpecForGenerated, }, }, "spec.template.metadata.labels: Invalid value: {\"y\":\"z\"}: `selector` does not match template `labels`": { ObjectMeta: api.ObjectMeta{ Name: "myjob", Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), }, Spec: extensions.JobSpec{ - Selector: validSelector, + Selector: validManualSelector, + ManualSelector: newBool(true), Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: map[string]string{"y": "z"}, @@ -1069,16 +1119,39 @@ func TestValidateJob(t *testing.T) { }, }, }, + "spec.template.metadata.labels: Invalid value: {\"controller-uid\":\"4d5e6f\"}: `selector` does not match template `labels`": { + ObjectMeta: api.ObjectMeta{ + Name: "myjob", + Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: extensions.JobSpec{ + Selector: validManualSelector, + ManualSelector: newBool(true), + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"controller-uid": "4d5e6f"}, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyOnFailure, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + }, + }, + }, "spec.template.spec.restartPolicy: Unsupported value": { ObjectMeta: api.ObjectMeta{ Name: "myjob", Namespace: api.NamespaceDefault, + UID: types.UID("1a2b3c"), }, Spec: extensions.JobSpec{ - Selector: validSelector, + Selector: validManualSelector, + ManualSelector: newBool(true), Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ - Labels: validSelector.MatchLabels, + Labels: validManualSelector.MatchLabels, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyAlways, @@ -1104,6 +1177,82 @@ func TestValidateJob(t *testing.T) { } } +func TestValidateJobUpdateStatus(t *testing.T) { + type testcase struct { + old extensions.Job + update extensions.Job + } + + successCases := []testcase{ + { + old: extensions.Job{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Status: extensions.JobStatus{ + Active: 1, + Succeeded: 2, + Failed: 3, + }, + }, + update: extensions.Job{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Status: extensions.JobStatus{ + Active: 1, + Succeeded: 1, + Failed: 3, + }, + }, + }, + } + + for _, successCase := range successCases { + successCase.old.ObjectMeta.ResourceVersion = "1" + successCase.update.ObjectMeta.ResourceVersion = "1" + if errs := ValidateJobUpdateStatus(&successCase.update, &successCase.old); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + + errorCases := map[string]testcase{ + "[status.active: Invalid value: -1: must be greater than or equal to 0, status.succeeded: Invalid value: -2: must be greater than or equal to 0]": { + old: extensions.Job{ + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: api.NamespaceDefault, + ResourceVersion: "10", + }, + Status: extensions.JobStatus{ + Active: 1, + Succeeded: 2, + Failed: 3, + }, + }, + update: extensions.Job{ + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: api.NamespaceDefault, + ResourceVersion: "10", + }, + Status: extensions.JobStatus{ + Active: -1, + Succeeded: -2, + Failed: 3, + }, + }, + }, + } + + for testName, errorCase := range errorCases { + errs := ValidateJobUpdateStatus(&errorCase.update, &errorCase.old) + if len(errs) == 0 { + t.Errorf("expected failure: %s", testName) + continue + } + if errs.ToAggregate().Error() != testName { + t.Errorf("expected '%s' got '%s'", errs.ToAggregate().Error(), testName) + } + } +} + type ingressRules map[string]string func TestValidateIngress(t *testing.T) { @@ -1178,8 +1327,6 @@ func TestValidateIngress(t *testing.T) { badHostIP := newValid() badHostIP.Spec.Rules[0].Host = hostIP badHostIPErr := fmt.Sprintf("spec.rules[0].host: Invalid value: '%v'", hostIP) - noSecretName := newValid() - noSecretName.Spec.TLS = []extensions.IngressTLS{{SecretName: ""}} errorCases := map[string]extensions.Ingress{ "spec.backend.serviceName: Required value": servicelessBackend, @@ -1188,7 +1335,6 @@ func TestValidateIngress(t *testing.T) { "spec.rules[0].host: Invalid value": badHost, "spec.rules[0].http.paths: Required value": noPaths, "spec.rules[0].http.paths[0].path: Invalid value": noForwardSlashPath, - "spec.tls[0].secretName: Required value": noSecretName, } errorCases[badPathErr] = badRegexPath errorCases[badHostIPErr] = badHostIP @@ -1299,120 +1445,6 @@ func TestValidateIngressStatusUpdate(t *testing.T) { } } -func TestValidateClusterAutoscaler(t *testing.T) { - successCases := []extensions.ClusterAutoscaler{ - { - ObjectMeta: api.ObjectMeta{ - Name: "ClusterAutoscaler", - Namespace: api.NamespaceDefault, - }, - Spec: extensions.ClusterAutoscalerSpec{ - MinNodes: 1, - MaxNodes: 5, - TargetUtilization: []extensions.NodeUtilization{ - { - Resource: extensions.CpuRequest, - Value: 0.7, - }, - }, - }, - }, - } - for _, successCase := range successCases { - if errs := ValidateClusterAutoscaler(&successCase); len(errs) != 0 { - t.Errorf("expected success: %v", errs) - } - } - - errorCases := map[string]extensions.ClusterAutoscaler{ - "must be 'ClusterAutoscaler'": { - ObjectMeta: api.ObjectMeta{ - Name: "TestClusterAutoscaler", - Namespace: api.NamespaceDefault, - }, - Spec: extensions.ClusterAutoscalerSpec{ - MinNodes: 1, - MaxNodes: 5, - TargetUtilization: []extensions.NodeUtilization{ - { - Resource: extensions.CpuRequest, - Value: 0.7, - }, - }, - }, - }, - "must be 'default'": { - ObjectMeta: api.ObjectMeta{ - Name: "ClusterAutoscaler", - Namespace: "test", - }, - Spec: extensions.ClusterAutoscalerSpec{ - MinNodes: 1, - MaxNodes: 5, - TargetUtilization: []extensions.NodeUtilization{ - { - Resource: extensions.CpuRequest, - Value: 0.7, - }, - }, - }, - }, - - `must be greater than or equal to 0`: { - ObjectMeta: api.ObjectMeta{ - Name: "ClusterAutoscaler", - Namespace: api.NamespaceDefault, - }, - Spec: extensions.ClusterAutoscalerSpec{ - MinNodes: -1, - MaxNodes: 5, - TargetUtilization: []extensions.NodeUtilization{ - { - Resource: extensions.CpuRequest, - Value: 0.7, - }, - }, - }, - }, - "must be greater than or equal to `minNodes`": { - ObjectMeta: api.ObjectMeta{ - Name: "ClusterAutoscaler", - Namespace: api.NamespaceDefault, - }, - Spec: extensions.ClusterAutoscalerSpec{ - MinNodes: 10, - MaxNodes: 5, - TargetUtilization: []extensions.NodeUtilization{ - { - Resource: extensions.CpuRequest, - Value: 0.7, - }, - }, - }, - }, - "Required value": { - ObjectMeta: api.ObjectMeta{ - Name: "ClusterAutoscaler", - Namespace: api.NamespaceDefault, - }, - Spec: extensions.ClusterAutoscalerSpec{ - MinNodes: 1, - MaxNodes: 5, - TargetUtilization: []extensions.NodeUtilization{}, - }, - }, - } - - for k, v := range errorCases { - errs := ValidateClusterAutoscaler(&v) - if len(errs) == 0 { - t.Errorf("[%s] expected failure", k) - } else if !strings.Contains(errs[0].Error(), k) { - t.Errorf("unexpected error: %v, expected: %q", errs[0], k) - } - } -} - func TestValidateScale(t *testing.T) { successCases := []extensions.Scale{ { @@ -1501,7 +1533,7 @@ func TestValidateReplicaSetStatusUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, Status: extensions.ReplicaSetStatus{ Replicas: 2, @@ -1512,7 +1544,7 @@ func TestValidateReplicaSetStatusUpdate(t *testing.T) { Spec: extensions.ReplicaSetSpec{ Replicas: 3, Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, Status: extensions.ReplicaSetStatus{ Replicas: 4, @@ -1533,7 +1565,7 @@ func TestValidateReplicaSetStatusUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, Status: extensions.ReplicaSetStatus{ Replicas: 3, @@ -1544,7 +1576,7 @@ func TestValidateReplicaSetStatusUpdate(t *testing.T) { Spec: extensions.ReplicaSetSpec{ Replicas: 2, Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, Status: extensions.ReplicaSetStatus{ Replicas: -3, @@ -1609,7 +1641,7 @@ func TestValidateReplicaSetUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, update: extensions.ReplicaSet{ @@ -1617,7 +1649,7 @@ func TestValidateReplicaSetUpdate(t *testing.T) { Spec: extensions.ReplicaSetSpec{ Replicas: 3, Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, }, @@ -1626,7 +1658,7 @@ func TestValidateReplicaSetUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, update: extensions.ReplicaSet{ @@ -1634,7 +1666,7 @@ func TestValidateReplicaSetUpdate(t *testing.T) { Spec: extensions.ReplicaSetSpec{ Replicas: 1, Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &readWriteVolumePodTemplate.Template, + Template: readWriteVolumePodTemplate.Template, }, }, }, @@ -1652,7 +1684,7 @@ func TestValidateReplicaSetUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, update: extensions.ReplicaSet{ @@ -1660,7 +1692,7 @@ func TestValidateReplicaSetUpdate(t *testing.T) { Spec: extensions.ReplicaSetSpec{ Replicas: 2, Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &readWriteVolumePodTemplate.Template, + Template: readWriteVolumePodTemplate.Template, }, }, }, @@ -1669,7 +1701,7 @@ func TestValidateReplicaSetUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, update: extensions.ReplicaSet{ @@ -1677,7 +1709,7 @@ func TestValidateReplicaSetUpdate(t *testing.T) { Spec: extensions.ReplicaSetSpec{ Replicas: 2, Selector: &unversioned.LabelSelector{MatchLabels: invalidLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, }, @@ -1686,7 +1718,7 @@ func TestValidateReplicaSetUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, update: extensions.ReplicaSet{ @@ -1694,7 +1726,7 @@ func TestValidateReplicaSetUpdate(t *testing.T) { Spec: extensions.ReplicaSetSpec{ Replicas: 2, Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &invalidPodTemplate.Template, + Template: invalidPodTemplate.Template, }, }, }, @@ -1703,7 +1735,7 @@ func TestValidateReplicaSetUpdate(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, update: extensions.ReplicaSet{ @@ -1711,7 +1743,7 @@ func TestValidateReplicaSetUpdate(t *testing.T) { Spec: extensions.ReplicaSetSpec{ Replicas: -1, Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, }, @@ -1767,14 +1799,14 @@ func TestValidateReplicaSet(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, { ObjectMeta: api.ObjectMeta{Name: "abc-123", Namespace: api.NamespaceDefault}, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, { @@ -1782,7 +1814,7 @@ func TestValidateReplicaSet(t *testing.T) { Spec: extensions.ReplicaSetSpec{ Replicas: 1, Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &readWriteVolumePodTemplate.Template, + Template: readWriteVolumePodTemplate.Template, }, }, } @@ -1797,27 +1829,27 @@ func TestValidateReplicaSet(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault}, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, "missing-namespace": { ObjectMeta: api.ObjectMeta{Name: "abc-123"}, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, "empty selector": { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: extensions.ReplicaSetSpec{ - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, "selector_doesnt_match": { ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, "invalid manifest": { @@ -1831,7 +1863,7 @@ func TestValidateReplicaSet(t *testing.T) { Spec: extensions.ReplicaSetSpec{ Replicas: 2, Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &readWriteVolumePodTemplate.Template, + Template: readWriteVolumePodTemplate.Template, }, }, "negative_replicas": { @@ -1851,7 +1883,7 @@ func TestValidateReplicaSet(t *testing.T) { }, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, "invalid_label 2": { @@ -1863,7 +1895,7 @@ func TestValidateReplicaSet(t *testing.T) { }, }, Spec: extensions.ReplicaSetSpec{ - Template: &invalidPodTemplate.Template, + Template: invalidPodTemplate.Template, }, }, "invalid_annotation": { @@ -1876,7 +1908,7 @@ func TestValidateReplicaSet(t *testing.T) { }, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &validPodTemplate.Template, + Template: validPodTemplate.Template, }, }, "invalid restart policy 1": { @@ -1886,7 +1918,7 @@ func TestValidateReplicaSet(t *testing.T) { }, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyOnFailure, DNSPolicy: api.DNSClusterFirst, @@ -1905,7 +1937,7 @@ func TestValidateReplicaSet(t *testing.T) { }, Spec: extensions.ReplicaSetSpec{ Selector: &unversioned.LabelSelector{MatchLabels: validLabels}, - Template: &api.PodTemplateSpec{ + Template: api.PodTemplateSpec{ Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyNever, DNSPolicy: api.DNSClusterFirst, @@ -1953,27 +1985,27 @@ func TestValidatePodSecurityPolicy(t *testing.T) { return &extensions.PodSecurityPolicy{ ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: extensions.PodSecurityPolicySpec{ - SELinuxContext: extensions.SELinuxContextStrategyOptions{ - Type: extensions.SELinuxStrategyRunAsAny, + SELinux: extensions.SELinuxStrategyOptions{ + Rule: extensions.SELinuxStrategyRunAsAny, }, RunAsUser: extensions.RunAsUserStrategyOptions{ - Type: extensions.RunAsUserStrategyRunAsAny, + Rule: extensions.RunAsUserStrategyRunAsAny, }, }, } } noUserOptions := validSCC() - noUserOptions.Spec.RunAsUser.Type = "" + noUserOptions.Spec.RunAsUser.Rule = "" noSELinuxOptions := validSCC() - noSELinuxOptions.Spec.SELinuxContext.Type = "" + noSELinuxOptions.Spec.SELinux.Rule = "" - invalidUserStratType := validSCC() - invalidUserStratType.Spec.RunAsUser.Type = "invalid" + invalidUserStratRule := validSCC() + invalidUserStratRule.Spec.RunAsUser.Rule = "invalid" - invalidSELinuxStratType := validSCC() - invalidSELinuxStratType.Spec.SELinuxContext.Type = "invalid" + invalidSELinuxStratRule := validSCC() + invalidSELinuxStratRule.Spec.SELinux.Rule = "invalid" missingObjectMetaName := validSCC() missingObjectMetaName.ObjectMeta.Name = "" @@ -2005,12 +2037,12 @@ func TestValidatePodSecurityPolicy(t *testing.T) { scc: noSELinuxOptions, errorDetail: "supported values: MustRunAs, RunAsAny", }, - "invalid user strategy type": { - scc: invalidUserStratType, + "invalid user strategy rule": { + scc: invalidUserStratRule, errorDetail: "supported values: MustRunAs, MustRunAsNonRoot, RunAsAny", }, - "invalid selinux strategy type": { - scc: invalidSELinuxStratType, + "invalid selinux strategy rule": { + scc: invalidSELinuxStratRule, errorDetail: "supported values: MustRunAs, RunAsAny", }, "missing object meta name": { @@ -2038,17 +2070,17 @@ func TestValidatePodSecurityPolicy(t *testing.T) { } mustRunAs := validSCC() - mustRunAs.Spec.RunAsUser.Type = extensions.RunAsUserStrategyMustRunAs + mustRunAs.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAs mustRunAs.Spec.RunAsUser.Ranges = []extensions.IDRange{ { Min: 1, Max: 1, }, } - mustRunAs.Spec.SELinuxContext.Type = extensions.SELinuxStrategyMustRunAs + mustRunAs.Spec.SELinux.Rule = extensions.SELinuxStrategyMustRunAs runAsNonRoot := validSCC() - runAsNonRoot.Spec.RunAsUser.Type = extensions.RunAsUserStrategyMustRunAsNonRoot + runAsNonRoot.Spec.RunAsUser.Rule = extensions.RunAsUserStrategyMustRunAsNonRoot successCases := map[string]struct { scc *extensions.PodSecurityPolicy @@ -2070,3 +2102,9 @@ func TestValidatePodSecurityPolicy(t *testing.T) { } } } + +func newBool(val bool) *bool { + p := new(bool) + *p = val + return p +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/metrics/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/metrics/deep_copy_generated.go index e7562de52..d467e6f59 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/metrics/deep_copy_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/metrics/deep_copy_generated.go @@ -1,5 +1,7 @@ +// +build !ignore_autogenerated + /* -Copyright 2015 The Kubernetes Authors All rights reserved. +Copyright 2016 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,16 +16,36 @@ See the License for the specific language governing permissions and limitations under the License. */ -// DO NOT EDIT. THIS FILE IS AUTO-GENERATED BY $KUBEROOT/hack/update-generated-deep-copies.sh. +// This file was autogenerated by deepcopy-gen. Do not edit it manually! package metrics -import api "k8s.io/kubernetes/pkg/api" +import ( + api "k8s.io/kubernetes/pkg/api" + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + conversion "k8s.io/kubernetes/pkg/conversion" +) func init() { - err := api.Scheme.AddGeneratedDeepCopyFuncs() - if err != nil { + if err := api.Scheme.AddGeneratedDeepCopyFuncs( + DeepCopy_metrics_RawNode, + DeepCopy_metrics_RawPod, + ); err != nil { // if one of the deep copy functions is malformed, detect it immediately. panic(err) } } + +func DeepCopy_metrics_RawNode(in RawNode, out *RawNode, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + return nil +} + +func DeepCopy_metrics_RawPod(in RawPod, out *RawPod, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/metrics/v1alpha1/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/apis/metrics/v1alpha1/deep_copy_generated.go index d235d1a48..c5de91744 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/metrics/v1alpha1/deep_copy_generated.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/metrics/v1alpha1/deep_copy_generated.go @@ -1,5 +1,7 @@ +// +build !ignore_autogenerated + /* -Copyright 2015 The Kubernetes Authors All rights reserved. +Copyright 2016 The Kubernetes Authors All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// DO NOT EDIT. THIS FILE IS AUTO-GENERATED BY $KUBEROOT/hack/update-generated-deep-copies.sh. +// This file was autogenerated by deepcopy-gen. Do not edit it manually! package v1alpha1 @@ -24,34 +26,26 @@ import ( conversion "k8s.io/kubernetes/pkg/conversion" ) -func deepCopy_unversioned_TypeMeta(in unversioned.TypeMeta, out *unversioned.TypeMeta, c *conversion.Cloner) error { - out.Kind = in.Kind - out.APIVersion = in.APIVersion - return nil -} - -func deepCopy_v1alpha1_RawNode(in RawNode, out *RawNode, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { - return err - } - return nil -} - -func deepCopy_v1alpha1_RawPod(in RawPod, out *RawPod, c *conversion.Cloner) error { - if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { - return err - } - return nil -} - func init() { - err := api.Scheme.AddGeneratedDeepCopyFuncs( - deepCopy_unversioned_TypeMeta, - deepCopy_v1alpha1_RawNode, - deepCopy_v1alpha1_RawPod, - ) - if err != nil { + if err := api.Scheme.AddGeneratedDeepCopyFuncs( + DeepCopy_v1alpha1_RawNode, + DeepCopy_v1alpha1_RawPod, + ); err != nil { // if one of the deep copy functions is malformed, detect it immediately. panic(err) } } + +func DeepCopy_v1alpha1_RawNode(in RawNode, out *RawNode, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + return nil +} + +func DeepCopy_v1alpha1_RawPod(in RawPod, out *RawPod, c *conversion.Cloner) error { + if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil { + return err + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apis/metrics/v1alpha1/register.go b/vendor/k8s.io/kubernetes/pkg/apis/metrics/v1alpha1/register.go index c943d5468..4af5dbfea 100644 --- a/vendor/k8s.io/kubernetes/pkg/apis/metrics/v1alpha1/register.go +++ b/vendor/k8s.io/kubernetes/pkg/apis/metrics/v1alpha1/register.go @@ -20,6 +20,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/runtime" + versionedwatch "k8s.io/kubernetes/pkg/watch/versioned" ) // GroupName is the group name use in this package @@ -40,6 +41,8 @@ func addKnownTypes(scheme *runtime.Scheme) { &RawPod{}, &v1.DeleteOptions{}, ) + // Add the watch version that applies + versionedwatch.AddToGroupVersion(scheme, SchemeGroupVersion) } func (obj *RawNode) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/OWNERS b/vendor/k8s.io/kubernetes/pkg/apiserver/OWNERS new file mode 100644 index 000000000..76e1b30e9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/OWNERS @@ -0,0 +1,4 @@ +assignees: + - lavalamp + - nikhiljindal + - smarterclayton diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/api_installer.go b/vendor/k8s.io/kubernetes/pkg/apiserver/api_installer.go new file mode 100644 index 000000000..248e1f8bb --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/api_installer.go @@ -0,0 +1,983 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "fmt" + "net/http" + gpath "path" + "reflect" + "sort" + "strings" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apiserver/metrics" + "k8s.io/kubernetes/pkg/conversion" + "k8s.io/kubernetes/pkg/runtime" + + "github.com/emicklei/go-restful" +) + +type APIInstaller struct { + group *APIGroupVersion + info *RequestInfoResolver + prefix string // Path prefix where API resources are to be registered. + minRequestTimeout time.Duration +} + +// Struct capturing information about an action ("GET", "POST", "WATCH", PROXY", etc). +type action struct { + Verb string // Verb identifying the action ("GET", "POST", "WATCH", PROXY", etc). + Path string // The path of the action + Params []*restful.Parameter // List of parameters associated with the action. + Namer ScopeNamer +} + +// An interface to see if an object supports swagger documentation as a method +type documentable interface { + SwaggerDoc() map[string]string +} + +// errEmptyName is returned when API requests do not fill the name section of the path. +var errEmptyName = errors.NewBadRequest("name must be provided") + +// Installs handlers for API resources. +func (a *APIInstaller) Install(ws *restful.WebService) (apiResources []unversioned.APIResource, errors []error) { + errors = make([]error, 0) + + proxyHandler := (&ProxyHandler{ + prefix: a.prefix + "/proxy/", + storage: a.group.Storage, + serializer: a.group.Serializer, + context: a.group.Context, + requestInfoResolver: a.info, + }) + + // Register the paths in a deterministic (sorted) order to get a deterministic swagger spec. + paths := make([]string, len(a.group.Storage)) + var i int = 0 + for path := range a.group.Storage { + paths[i] = path + i++ + } + sort.Strings(paths) + for _, path := range paths { + apiResource, err := a.registerResourceHandlers(path, a.group.Storage[path], ws, proxyHandler) + if err != nil { + errors = append(errors, fmt.Errorf("error in registering resource: %s, %v", path, err)) + } + if apiResource != nil { + apiResources = append(apiResources, *apiResource) + } + } + return apiResources, errors +} + +// NewWebService creates a new restful webservice with the api installer's prefix and version. +func (a *APIInstaller) NewWebService() *restful.WebService { + ws := new(restful.WebService) + ws.Path(a.prefix) + // a.prefix contains "prefix/group/version" + ws.Doc("API at " + a.prefix) + // Backwards compatibility, we accepted objects with empty content-type at V1. + // If we stop using go-restful, we can default empty content-type to application/json on an + // endpoint by endpoint basis + ws.Consumes("*/*") + ws.Produces(a.group.Serializer.SupportedMediaTypes()...) + ws.ApiVersion(a.group.GroupVersion.String()) + + return ws +} + +// getResourceKind returns the external group version kind registered for the given storage +// object. If the storage object is a subresource and has an override supplied for it, it returns +// the group version kind supplied in the override. +func (a *APIInstaller) getResourceKind(path string, storage rest.Storage) (unversioned.GroupVersionKind, error) { + if fqKindToRegister, ok := a.group.SubresourceGroupVersionKind[path]; ok { + return fqKindToRegister, nil + } + + object := storage.New() + fqKinds, err := a.group.Typer.ObjectKinds(object) + if err != nil { + return unversioned.GroupVersionKind{}, err + } + + // a given go type can have multiple potential fully qualified kinds. Find the one that corresponds with the group + // we're trying to register here + fqKindToRegister := unversioned.GroupVersionKind{} + for _, fqKind := range fqKinds { + if fqKind.Group == a.group.GroupVersion.Group { + fqKindToRegister = a.group.GroupVersion.WithKind(fqKind.Kind) + break + } + + // TODO This keeps it doing what it was doing before, but it doesn't feel right. + if fqKind.Group == extensions.GroupName && fqKind.Kind == "ThirdPartyResourceData" { + fqKindToRegister = a.group.GroupVersion.WithKind(fqKind.Kind) + } + } + if fqKindToRegister.IsEmpty() { + return unversioned.GroupVersionKind{}, fmt.Errorf("unable to locate fully qualified kind for %v: found %v when registering for %v", reflect.TypeOf(object), fqKinds, a.group.GroupVersion) + } + return fqKindToRegister, nil +} + +// restMapping returns rest mapper for the resource. +// Example REST paths that this mapper maps. +// 1. Resource only, no subresource: +// Resource Type: batch/v1.Job (input args: resource = "jobs") +// REST path: /apis/batch/v1/namespaces/{namespace}/job/{name} +// 2. Subresource and its parent belong to different API groups and/or versions: +// Resource Type: extensions/v1beta1.ReplicaSet (input args: resource = "replicasets") +// Subresource Type: autoscaling/v1.Scale +// REST path: /apis/extensions/v1beta1/namespaces/{namespace}/replicaset/{name}/scale +func (a *APIInstaller) restMapping(resource string) (*meta.RESTMapping, error) { + // subresources must have parent resources, and follow the namespacing rules of their parent. + // So get the storage of the resource (which is the parent resource in case of subresources) + storage, ok := a.group.Storage[resource] + if !ok { + return nil, fmt.Errorf("unable to locate the storage object for resource: %s", resource) + } + fqKindToRegister, err := a.getResourceKind(resource, storage) + if err != nil { + return nil, fmt.Errorf("unable to locate fully qualified kind for mapper resource %s: %v", resource, err) + } + return a.group.Mapper.RESTMapping(fqKindToRegister.GroupKind(), fqKindToRegister.Version) +} + +func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService, proxyHandler http.Handler) (*unversioned.APIResource, error) { + admit := a.group.Admit + context := a.group.Context + + optionsExternalVersion := a.group.GroupVersion + if a.group.OptionsExternalVersion != nil { + optionsExternalVersion = *a.group.OptionsExternalVersion + } + + resource, subresource, err := splitSubresource(path) + if err != nil { + return nil, err + } + + mapping, err := a.restMapping(resource) + if err != nil { + return nil, err + } + + fqKindToRegister, err := a.getResourceKind(path, storage) + if err != nil { + return nil, err + } + + versionedPtr, err := a.group.Creater.New(fqKindToRegister) + if err != nil { + return nil, err + } + versionedObject := indirectArbitraryPointer(versionedPtr) + kind := fqKindToRegister.Kind + hasSubresource := len(subresource) > 0 + + // what verbs are supported by the storage, used to know what verbs we support per path + creater, isCreater := storage.(rest.Creater) + namedCreater, isNamedCreater := storage.(rest.NamedCreater) + lister, isLister := storage.(rest.Lister) + getter, isGetter := storage.(rest.Getter) + getterWithOptions, isGetterWithOptions := storage.(rest.GetterWithOptions) + deleter, isDeleter := storage.(rest.Deleter) + gracefulDeleter, isGracefulDeleter := storage.(rest.GracefulDeleter) + collectionDeleter, isCollectionDeleter := storage.(rest.CollectionDeleter) + updater, isUpdater := storage.(rest.Updater) + patcher, isPatcher := storage.(rest.Patcher) + watcher, isWatcher := storage.(rest.Watcher) + _, isRedirector := storage.(rest.Redirector) + connecter, isConnecter := storage.(rest.Connecter) + storageMeta, isMetadata := storage.(rest.StorageMetadata) + if !isMetadata { + storageMeta = defaultStorageMetadata{} + } + exporter, isExporter := storage.(rest.Exporter) + if !isExporter { + exporter = nil + } + + versionedExportOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("ExportOptions")) + if err != nil { + return nil, err + } + + if isNamedCreater { + isCreater = true + } + + var versionedList interface{} + if isLister { + list := lister.NewList() + listGVK, err := a.group.Typer.ObjectKind(list) + versionedListPtr, err := a.group.Creater.New(a.group.GroupVersion.WithKind(listGVK.Kind)) + if err != nil { + return nil, err + } + versionedList = indirectArbitraryPointer(versionedListPtr) + } + + versionedListOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("ListOptions")) + if err != nil { + return nil, err + } + + var versionedDeleterObject interface{} + switch { + case isGracefulDeleter: + objectPtr, err := a.group.Creater.New(optionsExternalVersion.WithKind("DeleteOptions")) + if err != nil { + return nil, err + } + versionedDeleterObject = indirectArbitraryPointer(objectPtr) + isDeleter = true + case isDeleter: + gracefulDeleter = rest.GracefulDeleteAdapter{Deleter: deleter} + } + + versionedStatusPtr, err := a.group.Creater.New(optionsExternalVersion.WithKind("Status")) + if err != nil { + return nil, err + } + versionedStatus := indirectArbitraryPointer(versionedStatusPtr) + var ( + getOptions runtime.Object + versionedGetOptions runtime.Object + getOptionsInternalKind unversioned.GroupVersionKind + getSubpath bool + ) + if isGetterWithOptions { + getOptions, getSubpath, _ = getterWithOptions.NewGetOptions() + getOptionsInternalKind, err = a.group.Typer.ObjectKind(getOptions) + if err != nil { + return nil, err + } + versionedGetOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind(getOptionsInternalKind.Kind)) + if err != nil { + return nil, err + } + isGetter = true + } + + var versionedWatchEvent runtime.Object + if isWatcher { + versionedWatchEvent, err = a.group.Creater.New(a.group.GroupVersion.WithKind("WatchEvent")) + if err != nil { + return nil, err + } + } + + var ( + connectOptions runtime.Object + versionedConnectOptions runtime.Object + connectOptionsInternalKind unversioned.GroupVersionKind + connectSubpath bool + ) + if isConnecter { + connectOptions, connectSubpath, _ = connecter.NewConnectOptions() + if connectOptions != nil { + connectOptionsInternalKind, err = a.group.Typer.ObjectKind(connectOptions) + if err != nil { + return nil, err + } + + versionedConnectOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind(connectOptionsInternalKind.Kind)) + } + } + + var ctxFn ContextFunc + ctxFn = func(req *restful.Request) api.Context { + if context == nil { + return api.NewContext() + } + if ctx, ok := context.Get(req.Request); ok { + return ctx + } + return api.NewContext() + } + + allowWatchList := isWatcher && isLister // watching on lists is allowed only for kinds that support both watch and list. + scope := mapping.Scope + nameParam := ws.PathParameter("name", "name of the "+kind).DataType("string") + pathParam := ws.PathParameter("path", "path to the resource").DataType("string") + + params := []*restful.Parameter{} + actions := []action{} + + var resourceKind string + kindProvider, ok := storage.(rest.KindProvider) + if ok { + resourceKind = kindProvider.Kind() + } else { + resourceKind = kind + } + + var apiResource unversioned.APIResource + // Get the list of actions for the given scope. + switch scope.Name() { + case meta.RESTScopeNameRoot: + // Handle non-namespace scoped resources like nodes. + resourcePath := resource + resourceParams := params + itemPath := resourcePath + "/{name}" + nameParams := append(params, nameParam) + proxyParams := append(nameParams, pathParam) + if hasSubresource { + itemPath = itemPath + "/" + subresource + resourcePath = itemPath + resourceParams = nameParams + } + apiResource.Name = path + apiResource.Namespaced = false + apiResource.Kind = resourceKind + namer := rootScopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath)} + + // Handler for standard REST verbs (GET, PUT, POST and DELETE). + // Add actions at the resource path: /api/apiVersion/resource + actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer}, isLister) + actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer}, isCreater) + actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath, resourceParams, namer}, isCollectionDeleter) + // DEPRECATED + actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer}, allowWatchList) + + // Add actions at the item path: /api/apiVersion/resource/{name} + actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter) + if getSubpath { + actions = appendIf(actions, action{"GET", itemPath + "/{path:*}", proxyParams, namer}, isGetter) + } + actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater) + actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer}, isPatcher) + actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter) + actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher) + // We add "proxy" subresource to remove the need for the generic top level prefix proxy. + // The generic top level prefix proxy is deprecated in v1.2, and will be removed in 1.3, or 1.4 at the latest. + // TODO: DEPRECATED in v1.2. + actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath + "/{path:*}", proxyParams, namer}, isRedirector) + // TODO: DEPRECATED in v1.2. + actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath, nameParams, namer}, isRedirector) + actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer}, isConnecter) + actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer}, isConnecter && connectSubpath) + break + case meta.RESTScopeNameNamespace: + // Handler for standard REST verbs (GET, PUT, POST and DELETE). + namespaceParam := ws.PathParameter(scope.ArgumentName(), scope.ParamDescription()).DataType("string") + namespacedPath := scope.ParamName() + "/{" + scope.ArgumentName() + "}/" + resource + namespaceParams := []*restful.Parameter{namespaceParam} + + resourcePath := namespacedPath + resourceParams := namespaceParams + itemPath := namespacedPath + "/{name}" + nameParams := append(namespaceParams, nameParam) + proxyParams := append(nameParams, pathParam) + if hasSubresource { + itemPath = itemPath + "/" + subresource + resourcePath = itemPath + resourceParams = nameParams + } + apiResource.Name = path + apiResource.Namespaced = true + apiResource.Kind = resourceKind + namer := scopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath), false} + + actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer}, isLister) + actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer}, isCreater) + actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath, resourceParams, namer}, isCollectionDeleter) + // DEPRECATED + actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer}, allowWatchList) + + actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter) + if getSubpath { + actions = appendIf(actions, action{"GET", itemPath + "/{path:*}", proxyParams, namer}, isGetter) + } + actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater) + actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer}, isPatcher) + actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter) + actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher) + // We add "proxy" subresource to remove the need for the generic top level prefix proxy. + // The generic top level prefix proxy is deprecated in v1.2, and will be removed in 1.3, or 1.4 at the latest. + // TODO: DEPRECATED in v1.2. + actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath + "/{path:*}", proxyParams, namer}, isRedirector) + // TODO: DEPRECATED in v1.2. + actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath, nameParams, namer}, isRedirector) + actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer}, isConnecter) + actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer}, isConnecter && connectSubpath) + + // list or post across namespace. + // For ex: LIST all pods in all namespaces by sending a LIST request at /api/apiVersion/pods. + // TODO: more strongly type whether a resource allows these actions on "all namespaces" (bulk delete) + if !hasSubresource { + namer = scopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath), true} + actions = appendIf(actions, action{"LIST", resource, params, namer}, isLister) + actions = appendIf(actions, action{"WATCHLIST", "watch/" + resource, params, namer}, allowWatchList) + } + break + default: + return nil, fmt.Errorf("unsupported restscope: %s", scope.Name()) + } + + // Create Routes for the actions. + // TODO: Add status documentation using Returns() + // Errors (see api/errors/errors.go as well as go-restful router): + // http.StatusNotFound, http.StatusMethodNotAllowed, + // http.StatusUnsupportedMediaType, http.StatusNotAcceptable, + // http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, + // http.StatusRequestTimeout, http.StatusConflict, http.StatusPreconditionFailed, + // 422 (StatusUnprocessableEntity), http.StatusInternalServerError, + // http.StatusServiceUnavailable + // and api error codes + // Note that if we specify a versioned Status object here, we may need to + // create one for the tests, also + // Success: + // http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent + // + // test/integration/auth_test.go is currently the most comprehensive status code test + + reqScope := RequestScope{ + ContextFunc: ctxFn, + Serializer: a.group.Serializer, + StreamSerializer: a.group.StreamSerializer, + ParameterCodec: a.group.ParameterCodec, + Creater: a.group.Creater, + Convertor: a.group.Convertor, + + // TODO: This seems wrong for cross-group subresources. It makes an assumption that a subresource and its parent are in the same group version. Revisit this. + Resource: a.group.GroupVersion.WithResource(resource), + Subresource: subresource, + Kind: fqKindToRegister, + } + for _, action := range actions { + reqScope.Namer = action.Namer + namespaced := "" + if apiResource.Namespaced { + namespaced = "Namespaced" + } + switch action.Verb { + case "GET": // Get a resource. + var handler restful.RouteFunction + if isGetterWithOptions { + handler = GetResourceWithOptions(getterWithOptions, reqScope) + } else { + handler = GetResource(getter, exporter, reqScope) + } + handler = metrics.InstrumentRouteFunc(action.Verb, resource, handler) + doc := "read the specified " + kind + if hasSubresource { + doc = "read " + subresource + " of the specified " + kind + } + route := ws.GET(action.Path).To(handler). + Doc(doc). + Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). + Operation("read"+namespaced+kind+strings.Title(subresource)). + Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...). + Returns(http.StatusOK, "OK", versionedObject). + Writes(versionedObject) + if isGetterWithOptions { + if err := addObjectParams(ws, route, versionedGetOptions); err != nil { + return nil, err + } + } + if isExporter { + if err := addObjectParams(ws, route, versionedExportOptions); err != nil { + return nil, err + } + } + addParams(route, action.Params) + ws.Route(route) + case "LIST": // List all resources of a kind. + doc := "list objects of kind " + kind + if hasSubresource { + doc = "list " + subresource + " of objects of kind " + kind + } + handler := metrics.InstrumentRouteFunc(action.Verb, resource, ListResource(lister, watcher, reqScope, false, a.minRequestTimeout)) + route := ws.GET(action.Path).To(handler). + Doc(doc). + Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). + Operation("list"+namespaced+kind+strings.Title(subresource)). + Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...). + Returns(http.StatusOK, "OK", versionedList). + Writes(versionedList) + if err := addObjectParams(ws, route, versionedListOptions); err != nil { + return nil, err + } + switch { + case isLister && isWatcher: + doc := "list or watch objects of kind " + kind + if hasSubresource { + doc = "list or watch " + subresource + " of objects of kind " + kind + } + route.Doc(doc) + case isWatcher: + doc := "watch objects of kind " + kind + if hasSubresource { + doc = "watch " + subresource + "of objects of kind " + kind + } + route.Doc(doc) + } + addParams(route, action.Params) + ws.Route(route) + case "PUT": // Update a resource. + doc := "replace the specified " + kind + if hasSubresource { + doc = "replace " + subresource + " of the specified " + kind + } + handler := metrics.InstrumentRouteFunc(action.Verb, resource, UpdateResource(updater, reqScope, a.group.Typer, admit)) + route := ws.PUT(action.Path).To(handler). + Doc(doc). + Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). + Operation("replace"+namespaced+kind+strings.Title(subresource)). + Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...). + Returns(http.StatusOK, "OK", versionedObject). + Reads(versionedObject). + Writes(versionedObject) + addParams(route, action.Params) + ws.Route(route) + case "PATCH": // Partially update a resource + doc := "partially update the specified " + kind + if hasSubresource { + doc = "partially update " + subresource + " of the specified " + kind + } + handler := metrics.InstrumentRouteFunc(action.Verb, resource, PatchResource(patcher, reqScope, a.group.Typer, admit, mapping.ObjectConvertor)) + route := ws.PATCH(action.Path).To(handler). + Doc(doc). + Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). + Consumes(string(api.JSONPatchType), string(api.MergePatchType), string(api.StrategicMergePatchType)). + Operation("patch"+namespaced+kind+strings.Title(subresource)). + Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...). + Returns(http.StatusOK, "OK", versionedObject). + Reads(unversioned.Patch{}). + Writes(versionedObject) + addParams(route, action.Params) + ws.Route(route) + case "POST": // Create a resource. + var handler restful.RouteFunction + if isNamedCreater { + handler = CreateNamedResource(namedCreater, reqScope, a.group.Typer, admit) + } else { + handler = CreateResource(creater, reqScope, a.group.Typer, admit) + } + handler = metrics.InstrumentRouteFunc(action.Verb, resource, handler) + doc := "create a " + kind + if hasSubresource { + doc = "create " + subresource + " of a " + kind + } + route := ws.POST(action.Path).To(handler). + Doc(doc). + Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). + Operation("create"+namespaced+kind+strings.Title(subresource)). + Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...). + Returns(http.StatusOK, "OK", versionedObject). + Reads(versionedObject). + Writes(versionedObject) + addParams(route, action.Params) + ws.Route(route) + case "DELETE": // Delete a resource. + doc := "delete a " + kind + if hasSubresource { + doc = "delete " + subresource + " of a " + kind + } + handler := metrics.InstrumentRouteFunc(action.Verb, resource, DeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit)) + route := ws.DELETE(action.Path).To(handler). + Doc(doc). + Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). + Operation("delete"+namespaced+kind+strings.Title(subresource)). + Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...). + Writes(versionedStatus). + Returns(http.StatusOK, "OK", versionedStatus) + if isGracefulDeleter { + route.Reads(versionedDeleterObject) + } + addParams(route, action.Params) + ws.Route(route) + case "DELETECOLLECTION": + doc := "delete collection of " + kind + if hasSubresource { + doc = "delete collection of " + subresource + " of a " + kind + } + handler := metrics.InstrumentRouteFunc(action.Verb, resource, DeleteCollection(collectionDeleter, isCollectionDeleter, reqScope, admit)) + route := ws.DELETE(action.Path).To(handler). + Doc(doc). + Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). + Operation("deletecollection"+namespaced+kind+strings.Title(subresource)). + Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...). + Writes(versionedStatus). + Returns(http.StatusOK, "OK", versionedStatus) + if err := addObjectParams(ws, route, versionedListOptions); err != nil { + return nil, err + } + addParams(route, action.Params) + ws.Route(route) + // TODO: deprecated + case "WATCH": // Watch a resource. + doc := "watch changes to an object of kind " + kind + if hasSubresource { + doc = "watch changes to " + subresource + " of an object of kind " + kind + } + handler := metrics.InstrumentRouteFunc(action.Verb, resource, ListResource(lister, watcher, reqScope, true, a.minRequestTimeout)) + route := ws.GET(action.Path).To(handler). + Doc(doc). + Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). + Operation("watch"+namespaced+kind+strings.Title(subresource)). + Produces(a.group.StreamSerializer.SupportedMediaTypes()...). + Returns(http.StatusOK, "OK", versionedWatchEvent). + Writes(versionedWatchEvent) + if err := addObjectParams(ws, route, versionedListOptions); err != nil { + return nil, err + } + addParams(route, action.Params) + ws.Route(route) + // TODO: deprecated + case "WATCHLIST": // Watch all resources of a kind. + doc := "watch individual changes to a list of " + kind + if hasSubresource { + doc = "watch individual changes to a list of " + subresource + " of " + kind + } + handler := metrics.InstrumentRouteFunc(action.Verb, resource, ListResource(lister, watcher, reqScope, true, a.minRequestTimeout)) + route := ws.GET(action.Path).To(handler). + Doc(doc). + Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). + Operation("watch"+namespaced+kind+strings.Title(subresource)+"List"). + Produces(a.group.StreamSerializer.SupportedMediaTypes()...). + Returns(http.StatusOK, "OK", versionedWatchEvent). + Writes(versionedWatchEvent) + if err := addObjectParams(ws, route, versionedListOptions); err != nil { + return nil, err + } + addParams(route, action.Params) + ws.Route(route) + // We add "proxy" subresource to remove the need for the generic top level prefix proxy. + // The generic top level prefix proxy is deprecated in v1.2, and will be removed in 1.3, or 1.4 at the latest. + // TODO: DEPRECATED in v1.2. + case "PROXY": // Proxy requests to a resource. + // Accept all methods as per http://issue.k8s.io/3996 + addProxyRoute(ws, "GET", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params) + addProxyRoute(ws, "PUT", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params) + addProxyRoute(ws, "POST", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params) + addProxyRoute(ws, "DELETE", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params) + addProxyRoute(ws, "HEAD", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params) + addProxyRoute(ws, "OPTIONS", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params) + case "CONNECT": + for _, method := range connecter.ConnectMethods() { + doc := "connect " + method + " requests to " + kind + if hasSubresource { + doc = "connect " + method + " requests to " + subresource + " of " + kind + } + handler := metrics.InstrumentRouteFunc(action.Verb, resource, ConnectResource(connecter, reqScope, admit, path)) + route := ws.Method(method).Path(action.Path). + To(handler). + Doc(doc). + Operation("connect" + strings.Title(strings.ToLower(method)) + namespaced + kind + strings.Title(subresource)). + Produces("*/*"). + Consumes("*/*"). + Writes("string") + if versionedConnectOptions != nil { + if err := addObjectParams(ws, route, versionedConnectOptions); err != nil { + return nil, err + } + } + addParams(route, action.Params) + ws.Route(route) + } + default: + return nil, fmt.Errorf("unrecognized action verb: %s", action.Verb) + } + // Note: update GetAttribs() when adding a custom handler. + } + return &apiResource, nil +} + +// rootScopeNaming reads only names from a request and ignores namespaces. It implements ScopeNamer +// for root scoped resources. +type rootScopeNaming struct { + scope meta.RESTScope + runtime.SelfLinker + itemPath string +} + +// rootScopeNaming implements ScopeNamer +var _ ScopeNamer = rootScopeNaming{} + +// Namespace returns an empty string because root scoped objects have no namespace. +func (n rootScopeNaming) Namespace(req *restful.Request) (namespace string, err error) { + return "", nil +} + +// Name returns the name from the path and an empty string for namespace, or an error if the +// name is empty. +func (n rootScopeNaming) Name(req *restful.Request) (namespace, name string, err error) { + name = req.PathParameter("name") + if len(name) == 0 { + return "", "", errEmptyName + } + return "", name, nil +} + +// GenerateLink returns the appropriate path and query to locate an object by its canonical path. +func (n rootScopeNaming) GenerateLink(req *restful.Request, obj runtime.Object) (path, query string, err error) { + _, name, err := n.ObjectName(obj) + if err != nil { + return "", "", err + } + if len(name) == 0 { + _, name, err = n.Name(req) + if err != nil { + return "", "", err + } + } + path = strings.Replace(n.itemPath, "{name}", name, 1) + return path, "", nil +} + +// GenerateListLink returns the appropriate path and query to locate a list by its canonical path. +func (n rootScopeNaming) GenerateListLink(req *restful.Request) (path, query string, err error) { + path = req.Request.URL.Path + return path, "", nil +} + +// ObjectName returns the name set on the object, or an error if the +// name cannot be returned. Namespace is empty +// TODO: distinguish between objects with name/namespace and without via a specific error. +func (n rootScopeNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) { + name, err = n.SelfLinker.Name(obj) + if err != nil { + return "", "", err + } + if len(name) == 0 { + return "", "", errEmptyName + } + return "", name, nil +} + +// scopeNaming returns naming information from a request. It implements ScopeNamer for +// namespace scoped resources. +type scopeNaming struct { + scope meta.RESTScope + runtime.SelfLinker + itemPath string + allNamespaces bool +} + +// scopeNaming implements ScopeNamer +var _ ScopeNamer = scopeNaming{} + +// Namespace returns the namespace from the path or the default. +func (n scopeNaming) Namespace(req *restful.Request) (namespace string, err error) { + if n.allNamespaces { + return "", nil + } + namespace = req.PathParameter(n.scope.ArgumentName()) + if len(namespace) == 0 { + // a URL was constructed without the namespace, or this method was invoked + // on an object without a namespace path parameter. + return "", fmt.Errorf("no namespace parameter found on request") + } + return namespace, nil +} + +// Name returns the name from the path, the namespace (or default), or an error if the +// name is empty. +func (n scopeNaming) Name(req *restful.Request) (namespace, name string, err error) { + namespace, _ = n.Namespace(req) + name = req.PathParameter("name") + if len(name) == 0 { + return "", "", errEmptyName + } + return +} + +// GenerateLink returns the appropriate path and query to locate an object by its canonical path. +func (n scopeNaming) GenerateLink(req *restful.Request, obj runtime.Object) (path, query string, err error) { + namespace, name, err := n.ObjectName(obj) + if err != nil { + return "", "", err + } + if len(namespace) == 0 && len(name) == 0 { + namespace, name, err = n.Name(req) + if err != nil { + return "", "", err + } + } + if len(name) == 0 { + return "", "", errEmptyName + } + path = strings.Replace(n.itemPath, "{name}", name, 1) + path = strings.Replace(path, "{"+n.scope.ArgumentName()+"}", namespace, 1) + return path, "", nil +} + +// GenerateListLink returns the appropriate path and query to locate a list by its canonical path. +func (n scopeNaming) GenerateListLink(req *restful.Request) (path, query string, err error) { + path = req.Request.URL.Path + return path, "", nil +} + +// ObjectName returns the name and namespace set on the object, or an error if the +// name cannot be returned. +// TODO: distinguish between objects with name/namespace and without via a specific error. +func (n scopeNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) { + name, err = n.SelfLinker.Name(obj) + if err != nil { + return "", "", err + } + namespace, err = n.SelfLinker.Namespace(obj) + if err != nil { + return "", "", err + } + return namespace, name, err +} + +// This magic incantation returns *ptrToObject for an arbitrary pointer +func indirectArbitraryPointer(ptrToObject interface{}) interface{} { + return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface() +} + +func appendIf(actions []action, a action, shouldAppend bool) []action { + if shouldAppend { + actions = append(actions, a) + } + return actions +} + +// Wraps a http.Handler function inside a restful.RouteFunction +func routeFunction(handler http.Handler) restful.RouteFunction { + return func(restReq *restful.Request, restResp *restful.Response) { + handler.ServeHTTP(restResp.ResponseWriter, restReq.Request) + } +} + +func addProxyRoute(ws *restful.WebService, method string, prefix string, path string, proxyHandler http.Handler, namespaced, kind, resource, subresource string, hasSubresource bool, params []*restful.Parameter) { + doc := "proxy " + method + " requests to " + kind + if hasSubresource { + doc = "proxy " + method + " requests to " + subresource + " of " + kind + } + handler := metrics.InstrumentRouteFunc("PROXY", resource, routeFunction(proxyHandler)) + proxyRoute := ws.Method(method).Path(path).To(handler). + Doc(doc). + Operation("proxy" + strings.Title(method) + namespaced + kind + strings.Title(subresource)). + Produces("*/*"). + Consumes("*/*"). + Writes("string") + addParams(proxyRoute, params) + ws.Route(proxyRoute) +} + +func addParams(route *restful.RouteBuilder, params []*restful.Parameter) { + for _, param := range params { + route.Param(param) + } +} + +// addObjectParams converts a runtime.Object into a set of go-restful Param() definitions on the route. +// The object must be a pointer to a struct; only fields at the top level of the struct that are not +// themselves interfaces or structs are used; only fields with a json tag that is non empty (the standard +// Go JSON behavior for omitting a field) become query parameters. The name of the query parameter is +// the JSON field name. If a description struct tag is set on the field, that description is used on the +// query parameter. In essence, it converts a standard JSON top level object into a query param schema. +func addObjectParams(ws *restful.WebService, route *restful.RouteBuilder, obj interface{}) error { + sv, err := conversion.EnforcePtr(obj) + if err != nil { + return err + } + st := sv.Type() + switch st.Kind() { + case reflect.Struct: + for i := 0; i < st.NumField(); i++ { + name := st.Field(i).Name + sf, ok := st.FieldByName(name) + if !ok { + continue + } + switch sf.Type.Kind() { + case reflect.Interface, reflect.Struct: + default: + jsonTag := sf.Tag.Get("json") + if len(jsonTag) == 0 { + continue + } + jsonName := strings.SplitN(jsonTag, ",", 2)[0] + if len(jsonName) == 0 { + continue + } + + var desc string + if docable, ok := obj.(documentable); ok { + desc = docable.SwaggerDoc()[jsonName] + } + route.Param(ws.QueryParameter(jsonName, desc).DataType(typeToJSON(sf.Type.String()))) + } + } + } + return nil +} + +// TODO: this is incomplete, expand as needed. +// Convert the name of a golang type to the name of a JSON type +func typeToJSON(typeName string) string { + switch typeName { + case "bool", "*bool": + return "boolean" + case "uint8", "*uint8", "int", "*int", "int32", "*int32", "int64", "*int64", "uint32", "*uint32", "uint64", "*uint64": + return "integer" + case "float64", "*float64", "float32", "*float32": + return "number" + case "unversioned.Time", "*unversioned.Time": + return "string" + case "byte", "*byte": + return "string" + case "[]string", "[]*string": + // TODO: Fix this when go-restful supports a way to specify an array query param: + // https://github.com/emicklei/go-restful/issues/225 + return "string" + default: + return typeName + } +} + +// defaultStorageMetadata provides default answers to rest.StorageMetadata. +type defaultStorageMetadata struct{} + +// defaultStorageMetadata implements rest.StorageMetadata +var _ rest.StorageMetadata = defaultStorageMetadata{} + +func (defaultStorageMetadata) ProducesMIMETypes(verb string) []string { + return nil +} + +// splitSubresource checks if the given storage path is the path of a subresource and returns +// the resource and subresource components. +func splitSubresource(path string) (string, string, error) { + var resource, subresource string + switch parts := strings.Split(path, "/"); len(parts) { + case 2: + resource, subresource = parts[0], parts[1] + case 1: + resource = parts[0] + default: + // TODO: support deeper paths + return "", "", fmt.Errorf("api_installer allows only one or two segment paths (resource or resource/subresource)") + } + return resource, subresource, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/api_installer_test.go b/vendor/k8s.io/kubernetes/pkg/apiserver/api_installer_test.go new file mode 100644 index 000000000..57df10e94 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/api_installer_test.go @@ -0,0 +1,55 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" + + "github.com/emicklei/go-restful" +) + +func TestScopeNamingGenerateLink(t *testing.T) { + selfLinker := &setTestSelfLinker{ + t: t, + expectedSet: "/api/v1/namespaces/other/services/foo", + name: "foo", + namespace: "other", + } + s := scopeNaming{ + meta.RESTScopeNamespace, + selfLinker, + "/api/v1/namespaces/{namespace}/services/{name}", + true, + } + service := &api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "other", + }, + TypeMeta: unversioned.TypeMeta{ + Kind: "Service", + }, + } + _, _, err := s.GenerateLink(&restful.Request{}, service) + if err != nil { + t.Errorf("Unexpected error %v", err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/apiserver.go b/vendor/k8s.io/kubernetes/pkg/apiserver/apiserver.go new file mode 100644 index 000000000..66b9dc3d1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/apiserver.go @@ -0,0 +1,507 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "path" + rt "runtime" + "strings" + "time" + + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + apierrors "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apiserver/metrics" + "k8s.io/kubernetes/pkg/healthz" + "k8s.io/kubernetes/pkg/runtime" + utilerrors "k8s.io/kubernetes/pkg/util/errors" + "k8s.io/kubernetes/pkg/util/flushwriter" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wsstream" + "k8s.io/kubernetes/pkg/version" + + "github.com/emicklei/go-restful" + "github.com/golang/glog" + "github.com/prometheus/client_golang/prometheus" +) + +func init() { + metrics.Register() +} + +// mux is an object that can register http handlers. +type Mux interface { + Handle(pattern string, handler http.Handler) + HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) +} + +// APIGroupVersion is a helper for exposing rest.Storage objects as http.Handlers via go-restful +// It handles URLs of the form: +// /${storage_key}[/${object_name}] +// Where 'storage_key' points to a rest.Storage object stored in storage. +// This object should contain all parameterization necessary for running a particular API version +type APIGroupVersion struct { + Storage map[string]rest.Storage + + Root string + + // GroupVersion is the external group version + GroupVersion unversioned.GroupVersion + + // RequestInfoResolver is used to parse URLs for the legacy proxy handler. Don't use this for anything else + // TODO: refactor proxy handler to use sub resources + RequestInfoResolver *RequestInfoResolver + + // OptionsExternalVersion controls the Kubernetes APIVersion used for common objects in the apiserver + // schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may + // define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. If + // empty, defaults to GroupVersion. + OptionsExternalVersion *unversioned.GroupVersion + + Mapper meta.RESTMapper + + // Serializer is used to determine how to convert responses from API methods into bytes to send over + // the wire. + Serializer runtime.NegotiatedSerializer + // StreamSerializer is used for sending a series of objects to the client over a single channel, where + // the underlying channel has no innate framing (such as an io.Writer) + StreamSerializer runtime.NegotiatedSerializer + ParameterCodec runtime.ParameterCodec + + Typer runtime.ObjectTyper + Creater runtime.ObjectCreater + Convertor runtime.ObjectConvertor + Linker runtime.SelfLinker + + Admit admission.Interface + Context api.RequestContextMapper + + MinRequestTimeout time.Duration + + // SubresourceGroupVersionKind contains the GroupVersionKind overrides for each subresource that is + // accessible from this API group version. The GroupVersionKind is that of the external version of + // the subresource. The key of this map should be the path of the subresource. The keys here should + // match the keys in the Storage map above for subresources. + SubresourceGroupVersionKind map[string]unversioned.GroupVersionKind +} + +type ProxyDialerFunc func(network, addr string) (net.Conn, error) + +// TODO: Pipe these in through the apiserver cmd line +const ( + // Minimum duration before timing out read/write requests + MinTimeoutSecs = 300 + // Maximum duration before timing out read/write requests + MaxTimeoutSecs = 600 +) + +// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container. +// It is expected that the provided path root prefix will serve all operations. Root MUST NOT end +// in a slash. +func (g *APIGroupVersion) InstallREST(container *restful.Container) error { + installer := g.newInstaller() + ws := installer.NewWebService() + apiResources, registrationErrors := installer.Install(ws) + AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, apiResources) + container.Add(ws) + return utilerrors.NewAggregate(registrationErrors) +} + +// UpdateREST registers the REST handlers for this APIGroupVersion to an existing web service +// in the restful Container. It will use the prefix (root/version) to find the existing +// web service. If a web service does not exist within the container to support the prefix +// this method will return an error. +func (g *APIGroupVersion) UpdateREST(container *restful.Container) error { + installer := g.newInstaller() + var ws *restful.WebService = nil + + for i, s := range container.RegisteredWebServices() { + if s.RootPath() == installer.prefix { + ws = container.RegisteredWebServices()[i] + break + } + } + + if ws == nil { + return apierrors.NewInternalError(fmt.Errorf("unable to find an existing webservice for prefix %s", installer.prefix)) + } + apiResources, registrationErrors := installer.Install(ws) + AddSupportedResourcesWebService(g.Serializer, ws, g.GroupVersion, apiResources) + return utilerrors.NewAggregate(registrationErrors) +} + +// newInstaller is a helper to create the installer. Used by InstallREST and UpdateREST. +func (g *APIGroupVersion) newInstaller() *APIInstaller { + prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version) + installer := &APIInstaller{ + group: g, + info: g.RequestInfoResolver, + prefix: prefix, + minRequestTimeout: g.MinRequestTimeout, + } + return installer +} + +// TODO: document all handlers +// InstallSupport registers the APIServer support functions +func InstallSupport(mux Mux, ws *restful.WebService, checks ...healthz.HealthzChecker) { + // TODO: convert healthz and metrics to restful and remove container arg + healthz.InstallHandler(mux, checks...) + mux.Handle("/metrics", prometheus.Handler()) + + // Set up a service to return the git code version. + ws.Path("/version") + ws.Doc("git code version from which this is built") + ws.Route( + ws.GET("/").To(handleVersion). + Doc("get the code version"). + Operation("getCodeVersion"). + Produces(restful.MIME_JSON). + Consumes(restful.MIME_JSON)) +} + +// InstallLogsSupport registers the APIServer log support function into a mux. +func InstallLogsSupport(mux Mux) { + // TODO: use restful: ws.Route(ws.GET("/logs/{logpath:*}").To(fileHandler)) + // See github.com/emicklei/go-restful/blob/master/examples/restful-serve-static.go + mux.Handle("/logs/", http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log/")))) +} + +// TODO: needs to perform response type negotiation, this is probably the wrong way to recover panics +func InstallRecoverHandler(s runtime.NegotiatedSerializer, container *restful.Container) { + container.RecoverHandler(func(panicReason interface{}, httpWriter http.ResponseWriter) { + logStackOnRecover(s, panicReason, httpWriter) + }) +} + +//TODO: Unify with RecoverPanics? +func logStackOnRecover(s runtime.NegotiatedSerializer, panicReason interface{}, w http.ResponseWriter) { + var buffer bytes.Buffer + buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason)) + for i := 2; ; i += 1 { + _, file, line, ok := rt.Caller(i) + if !ok { + break + } + buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line)) + } + glog.Errorln(buffer.String()) + + headers := http.Header{} + if ct := w.Header().Get("Content-Type"); len(ct) > 0 { + headers.Set("Accept", ct) + } + errorNegotiated(apierrors.NewGenericServerResponse(http.StatusInternalServerError, "", api.Resource(""), "", "", 0, false), s, unversioned.GroupVersion{}, w, &http.Request{Header: headers}) +} + +func InstallServiceErrorHandler(s runtime.NegotiatedSerializer, container *restful.Container, requestResolver *RequestInfoResolver, apiVersions []string) { + container.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) { + serviceErrorHandler(s, requestResolver, apiVersions, serviceErr, request, response) + }) +} + +func serviceErrorHandler(s runtime.NegotiatedSerializer, requestResolver *RequestInfoResolver, apiVersions []string, serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) { + errorNegotiated(apierrors.NewGenericServerResponse(serviceErr.Code, "", api.Resource(""), "", "", 0, false), s, unversioned.GroupVersion{}, response.ResponseWriter, request.Request) +} + +// Adds a service to return the supported api versions at the legacy /api. +func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, getAPIVersionsFunc func(req *restful.Request) *unversioned.APIVersions) { + // TODO: InstallREST should register each version automatically + + // Because in release 1.1, /api returns response with empty APIVersion, we + // use StripVersionNegotiatedSerializer to keep the response backwards + // compatible. + ss := StripVersionNegotiatedSerializer{s} + versionHandler := APIVersionHandler(ss, getAPIVersionsFunc) + ws := new(restful.WebService) + ws.Path(apiPrefix) + ws.Doc("get available API versions") + ws.Route(ws.GET("/").To(versionHandler). + Doc("get available API versions"). + Operation("getAPIVersions"). + Produces(s.SupportedMediaTypes()...). + Consumes(s.SupportedMediaTypes()...). + Writes(unversioned.APIVersions{})) + container.Add(ws) +} + +// stripVersionEncoder strips APIVersion field from the encoding output. It's +// used to keep the responses at the discovery endpoints backward compatible +// with release-1.1, when the responses have empty APIVersion. +type stripVersionEncoder struct { + encoder runtime.Encoder + serializer runtime.Serializer +} + +func (c stripVersionEncoder) EncodeToStream(obj runtime.Object, w io.Writer, overrides ...unversioned.GroupVersion) error { + buf := bytes.NewBuffer([]byte{}) + err := c.encoder.EncodeToStream(obj, buf, overrides...) + if err != nil { + return err + } + roundTrippedObj, gvk, err := c.serializer.Decode(buf.Bytes(), nil, nil) + if err != nil { + return err + } + gvk.Group = "" + gvk.Version = "" + roundTrippedObj.GetObjectKind().SetGroupVersionKind(gvk) + return c.serializer.EncodeToStream(roundTrippedObj, w) +} + +// StripVersionNegotiatedSerializer will return stripVersionEncoder when +// EncoderForVersion is called. See comments for stripVersionEncoder. +type StripVersionNegotiatedSerializer struct { + runtime.NegotiatedSerializer +} + +func (n StripVersionNegotiatedSerializer) EncoderForVersion(serializer runtime.Serializer, gv unversioned.GroupVersion) runtime.Encoder { + encoder := n.NegotiatedSerializer.EncoderForVersion(serializer, gv) + return stripVersionEncoder{encoder, serializer} +} + +func keepUnversioned(group string) bool { + return group == "" || group == "extensions" +} + +// Adds a service to return the supported api versions at /apis. +func AddApisWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, f func(req *restful.Request) []unversioned.APIGroup) { + // Because in release 1.1, /apis returns response with empty APIVersion, we + // use StripVersionNegotiatedSerializer to keep the response backwards + // compatible. + ss := StripVersionNegotiatedSerializer{s} + rootAPIHandler := RootAPIHandler(ss, f) + ws := new(restful.WebService) + ws.Path(apiPrefix) + ws.Doc("get available API versions") + ws.Route(ws.GET("/").To(rootAPIHandler). + Doc("get available API versions"). + Operation("getAPIVersions"). + Produces(s.SupportedMediaTypes()...). + Consumes(s.SupportedMediaTypes()...). + Writes(unversioned.APIGroupList{})) + container.Add(ws) +} + +// Adds a service to return the supported versions, preferred version, and name +// of a group. E.g., a such web service will be registered at /apis/extensions. +func AddGroupWebService(s runtime.NegotiatedSerializer, container *restful.Container, path string, group unversioned.APIGroup) { + ss := s + if keepUnversioned(group.Name) { + // Because in release 1.1, /apis/extensions returns response with empty + // APIVersion, we use StripVersionNegotiatedSerializer to keep the + // response backwards compatible. + ss = StripVersionNegotiatedSerializer{s} + } + groupHandler := GroupHandler(ss, group) + ws := new(restful.WebService) + ws.Path(path) + ws.Doc("get information of a group") + ws.Route(ws.GET("/").To(groupHandler). + Doc("get information of a group"). + Operation("getAPIGroup"). + Produces(s.SupportedMediaTypes()...). + Consumes(s.SupportedMediaTypes()...). + Writes(unversioned.APIGroup{})) + container.Add(ws) +} + +// Adds a service to return the supported resources, E.g., a such web service +// will be registered at /apis/extensions/v1. +func AddSupportedResourcesWebService(s runtime.NegotiatedSerializer, ws *restful.WebService, groupVersion unversioned.GroupVersion, apiResources []unversioned.APIResource) { + ss := s + if keepUnversioned(groupVersion.Group) { + // Because in release 1.1, /apis/extensions/v1beta1 returns response + // with empty APIVersion, we use StripVersionNegotiatedSerializer to + // keep the response backwards compatible. + ss = StripVersionNegotiatedSerializer{s} + } + resourceHandler := SupportedResourcesHandler(ss, groupVersion, apiResources) + ws.Route(ws.GET("/").To(resourceHandler). + Doc("get available resources"). + Operation("getAPIResources"). + Produces(s.SupportedMediaTypes()...). + Consumes(s.SupportedMediaTypes()...). + Writes(unversioned.APIResourceList{})) +} + +// handleVersion writes the server's version information. +func handleVersion(req *restful.Request, resp *restful.Response) { + writeRawJSON(http.StatusOK, version.Get(), resp.ResponseWriter) +} + +// APIVersionHandler returns a handler which will list the provided versions as available. +func APIVersionHandler(s runtime.NegotiatedSerializer, getAPIVersionsFunc func(req *restful.Request) *unversioned.APIVersions) restful.RouteFunction { + return func(req *restful.Request, resp *restful.Response) { + writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, getAPIVersionsFunc(req)) + } +} + +// RootAPIHandler returns a handler which will list the provided groups and versions as available. +func RootAPIHandler(s runtime.NegotiatedSerializer, f func(req *restful.Request) []unversioned.APIGroup) restful.RouteFunction { + return func(req *restful.Request, resp *restful.Response) { + writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &unversioned.APIGroupList{Groups: f(req)}) + } +} + +// GroupHandler returns a handler which will return the api.GroupAndVersion of +// the group. +func GroupHandler(s runtime.NegotiatedSerializer, group unversioned.APIGroup) restful.RouteFunction { + return func(req *restful.Request, resp *restful.Response) { + writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &group) + } +} + +// SupportedResourcesHandler returns a handler which will list the provided resources as available. +func SupportedResourcesHandler(s runtime.NegotiatedSerializer, groupVersion unversioned.GroupVersion, apiResources []unversioned.APIResource) restful.RouteFunction { + return func(req *restful.Request, resp *restful.Response) { + writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &unversioned.APIResourceList{GroupVersion: groupVersion.String(), APIResources: apiResources}) + } +} + +// write renders a returned runtime.Object to the response as a stream or an encoded object. If the object +// returned by the response implements rest.ResourceStreamer that interface will be used to render the +// response. The Accept header and current API version will be passed in, and the output will be copied +// directly to the response body. If content type is returned it is used, otherwise the content type will +// be "application/octet-stream". All other objects are sent to standard JSON serialization. +func write(statusCode int, gv unversioned.GroupVersion, s runtime.NegotiatedSerializer, object runtime.Object, w http.ResponseWriter, req *http.Request) { + if stream, ok := object.(rest.ResourceStreamer); ok { + out, flush, contentType, err := stream.InputStream(gv.String(), req.Header.Get("Accept")) + if err != nil { + errorNegotiated(err, s, gv, w, req) + return + } + if out == nil { + // No output provided - return StatusNoContent + w.WriteHeader(http.StatusNoContent) + return + } + defer out.Close() + + if wsstream.IsWebSocketRequest(req) { + r := wsstream.NewReader(out, true) + if err := r.Copy(w, req); err != nil { + utilruntime.HandleError(fmt.Errorf("error encountered while streaming results via websocket: %v", err)) + } + return + } + + if len(contentType) == 0 { + contentType = "application/octet-stream" + } + w.Header().Set("Content-Type", contentType) + w.WriteHeader(statusCode) + writer := w.(io.Writer) + if flush { + writer = flushwriter.Wrap(w) + } + io.Copy(writer, out) + return + } + writeNegotiated(s, gv, w, req, statusCode, object) +} + +// writeNegotiated renders an object in the content type negotiated by the client +func writeNegotiated(s runtime.NegotiatedSerializer, gv unversioned.GroupVersion, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) { + serializer, contentType, err := negotiateOutputSerializer(req, s) + if err != nil { + status := errToAPIStatus(err) + writeRawJSON(int(status.Code), status, w) + return + } + + w.Header().Set("Content-Type", contentType) + w.WriteHeader(statusCode) + + encoder := s.EncoderForVersion(serializer, gv) + if err := encoder.EncodeToStream(object, w); err != nil { + errorJSONFatal(err, encoder, w) + } +} + +// errorNegotiated renders an error to the response. Returns the HTTP status code of the error. +func errorNegotiated(err error, s runtime.NegotiatedSerializer, gv unversioned.GroupVersion, w http.ResponseWriter, req *http.Request) int { + status := errToAPIStatus(err) + code := int(status.Code) + writeNegotiated(s, gv, w, req, code, status) + return code +} + +// errorJSONFatal renders an error to the response, and if codec fails will render plaintext. +// Returns the HTTP status code of the error. +func errorJSONFatal(err error, codec runtime.Encoder, w http.ResponseWriter) int { + utilruntime.HandleError(fmt.Errorf("apiserver was unable to write a JSON response: %v", err)) + status := errToAPIStatus(err) + code := int(status.Code) + output, err := runtime.Encode(codec, status) + if err != nil { + w.WriteHeader(code) + fmt.Fprintf(w, "%s: %s", status.Reason, status.Message) + return code + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + w.Write(output) + return code +} + +// writeRawJSON writes a non-API object in JSON. +func writeRawJSON(statusCode int, object interface{}, w http.ResponseWriter) { + output, err := json.MarshalIndent(object, "", " ") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + w.Write(output) +} + +func parseTimeout(str string) time.Duration { + if str != "" { + timeout, err := time.ParseDuration(str) + if err == nil { + return timeout + } + glog.Errorf("Failed to parse %q: %v", str, err) + } + return 30 * time.Second +} + +func readBody(req *http.Request) ([]byte, error) { + defer req.Body.Close() + return ioutil.ReadAll(req.Body) +} + +// splitPath returns the segments for a URL path. +func splitPath(path string) []string { + path = strings.Trim(path, "/") + if path == "" { + return []string{} + } + return strings.Split(path, "/") +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/apiserver_test.go b/vendor/k8s.io/kubernetes/pkg/apiserver/apiserver_test.go new file mode 100644 index 000000000..48e94bed9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/apiserver_test.go @@ -0,0 +1,3311 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "strings" + "sync" + "testing" + "time" + + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + apierrs "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" + apiservertesting "k8s.io/kubernetes/pkg/apiserver/testing" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/version" + "k8s.io/kubernetes/pkg/watch" + "k8s.io/kubernetes/pkg/watch/versioned" + "k8s.io/kubernetes/plugin/pkg/admission/admit" + "k8s.io/kubernetes/plugin/pkg/admission/deny" + + "github.com/emicklei/go-restful" +) + +func convert(obj runtime.Object) (runtime.Object, error) { + return obj, nil +} + +// This creates fake API versions, similar to api/latest.go. +var testAPIGroup = "test.group" +var testAPIGroup2 = "test.group2" +var testInternalGroupVersion = unversioned.GroupVersion{Group: testAPIGroup, Version: runtime.APIVersionInternal} +var testGroupVersion = unversioned.GroupVersion{Group: testAPIGroup, Version: "version"} +var newGroupVersion = unversioned.GroupVersion{Group: testAPIGroup, Version: "version2"} +var testGroup2Version = unversioned.GroupVersion{Group: testAPIGroup2, Version: "version"} +var testInternalGroup2Version = unversioned.GroupVersion{Group: testAPIGroup2, Version: runtime.APIVersionInternal} +var prefix = "apis" + +var grouplessGroupVersion = unversioned.GroupVersion{Group: "", Version: "v1"} +var grouplessInternalGroupVersion = unversioned.GroupVersion{Group: "", Version: runtime.APIVersionInternal} +var grouplessPrefix = "api" + +var groupVersions = []unversioned.GroupVersion{grouplessGroupVersion, testGroupVersion, newGroupVersion} + +var codec = api.Codecs.LegacyCodec(groupVersions...) +var grouplessCodec = api.Codecs.LegacyCodec(grouplessGroupVersion) +var testCodec = api.Codecs.LegacyCodec(testGroupVersion) +var newCodec = api.Codecs.LegacyCodec(newGroupVersion) + +var accessor = meta.NewAccessor() +var versioner runtime.ResourceVersioner = accessor +var selfLinker runtime.SelfLinker = accessor +var mapper, namespaceMapper meta.RESTMapper // The mappers with namespace and with legacy namespace scopes. +var admissionControl admission.Interface +var requestContextMapper api.RequestContextMapper + +func interfacesFor(version unversioned.GroupVersion) (*meta.VersionInterfaces, error) { + switch version { + case testGroupVersion: + return &meta.VersionInterfaces{ + ObjectConvertor: api.Scheme, + MetadataAccessor: accessor, + }, nil + case newGroupVersion: + return &meta.VersionInterfaces{ + ObjectConvertor: api.Scheme, + MetadataAccessor: accessor, + }, nil + case grouplessGroupVersion: + return &meta.VersionInterfaces{ + ObjectConvertor: api.Scheme, + MetadataAccessor: accessor, + }, nil + case testGroup2Version: + return &meta.VersionInterfaces{ + ObjectConvertor: api.Scheme, + MetadataAccessor: accessor, + }, nil + default: + return nil, fmt.Errorf("unsupported storage version: %s (valid: %v)", version, groupVersions) + } +} + +func newMapper() *meta.DefaultRESTMapper { + return meta.NewDefaultRESTMapper([]unversioned.GroupVersion{testGroupVersion, newGroupVersion}, interfacesFor) +} + +func addGrouplessTypes() { + type ListOptions struct { + runtime.Object + unversioned.TypeMeta `json:",inline"` + LabelSelector string `json:"labelSelector,omitempty"` + FieldSelector string `json:"fieldSelector,omitempty"` + Watch bool `json:"watch,omitempty"` + ResourceVersion string `json:"resourceVersion,omitempty"` + TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"` + } + api.Scheme.AddKnownTypes(grouplessGroupVersion, + &apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &ListOptions{}, + &api.DeleteOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}) + api.Scheme.AddKnownTypes(grouplessGroupVersion, &api.Pod{}) + api.Scheme.AddKnownTypes(grouplessInternalGroupVersion, + &apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &api.ListOptions{}, + &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}) +} + +func addTestTypes() { + type ListOptions struct { + runtime.Object + unversioned.TypeMeta `json:",inline"` + LabelSelector string `json:"labelSelector,omitempty"` + FieldSelector string `json:"fieldSelector,omitempty"` + Watch bool `json:"watch,omitempty"` + ResourceVersion string `json:"resourceVersion,omitempty"` + TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"` + } + api.Scheme.AddKnownTypes(testGroupVersion, + &apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &ListOptions{}, + &api.DeleteOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}, + &SimpleXGSubresource{}) + api.Scheme.AddKnownTypes(testGroupVersion, &api.Pod{}) + api.Scheme.AddKnownTypes(testInternalGroupVersion, + &apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &api.ListOptions{}, + &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}, + &SimpleXGSubresource{}) + // Register SimpleXGSubresource in both testGroupVersion and testGroup2Version, and also their + // their corresponding internal versions, to verify that the desired group version object is + // served in the tests. + api.Scheme.AddKnownTypes(testGroup2Version, &SimpleXGSubresource{}) + api.Scheme.AddKnownTypes(testInternalGroup2Version, &SimpleXGSubresource{}) + versioned.AddToGroupVersion(api.Scheme, testGroupVersion) +} + +func addNewTestTypes() { + type ListOptions struct { + runtime.Object + unversioned.TypeMeta `json:",inline"` + LabelSelector string `json:"labelSelector,omitempty"` + FieldSelector string `json:"fieldSelector,omitempty"` + Watch bool `json:"watch,omitempty"` + ResourceVersion string `json:"resourceVersion,omitempty"` + TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty"` + } + api.Scheme.AddKnownTypes(newGroupVersion, + &apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &ListOptions{}, + &api.DeleteOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}, + &v1.Pod{}, + ) + versioned.AddToGroupVersion(api.Scheme, newGroupVersion) +} + +func init() { + // Certain API objects are returned regardless of the contents of storage: + // api.Status is returned in errors + + addGrouplessTypes() + addTestTypes() + addNewTestTypes() + + nsMapper := newMapper() + + // enumerate all supported versions, get the kinds, and register with + // the mapper how to address our resources + for _, gv := range groupVersions { + for kind := range api.Scheme.KnownTypes(gv) { + gvk := gv.WithKind(kind) + root := bool(kind == "SimpleRoot") + if root { + nsMapper.Add(gvk, meta.RESTScopeRoot) + } else { + nsMapper.Add(gvk, meta.RESTScopeNamespace) + } + } + } + + mapper = nsMapper + namespaceMapper = nsMapper + admissionControl = admit.NewAlwaysAdmit() + requestContextMapper = api.NewRequestContextMapper() + + api.Scheme.AddFieldLabelConversionFunc(grouplessGroupVersion.String(), "Simple", + func(label, value string) (string, string, error) { + return label, value, nil + }, + ) + api.Scheme.AddFieldLabelConversionFunc(testGroupVersion.String(), "Simple", + func(label, value string) (string, string, error) { + return label, value, nil + }, + ) + api.Scheme.AddFieldLabelConversionFunc(newGroupVersion.String(), "Simple", + func(label, value string) (string, string, error) { + return label, value, nil + }, + ) +} + +// defaultAPIServer exposes nested objects for testability. +type defaultAPIServer struct { + http.Handler + container *restful.Container +} + +// uses the default settings +func handle(storage map[string]rest.Storage) http.Handler { + return handleInternal(storage, admissionControl, selfLinker) +} + +// tests with a deny admission controller +func handleDeny(storage map[string]rest.Storage) http.Handler { + return handleInternal(storage, deny.NewAlwaysDeny(), selfLinker) +} + +// tests using the new namespace scope mechanism +func handleNamespaced(storage map[string]rest.Storage) http.Handler { + return handleInternal(storage, admissionControl, selfLinker) +} + +// tests using a custom self linker +func handleLinker(storage map[string]rest.Storage, selfLinker runtime.SelfLinker) http.Handler { + return handleInternal(storage, admissionControl, selfLinker) +} + +func newTestRequestInfoResolver() *RequestInfoResolver { + return &RequestInfoResolver{sets.NewString("api", "apis"), sets.NewString("api")} +} + +func handleInternal(storage map[string]rest.Storage, admissionControl admission.Interface, selfLinker runtime.SelfLinker) http.Handler { + container := restful.NewContainer() + container.Router(restful.CurlyRouter{}) + mux := container.ServeMux + + template := APIGroupVersion{ + Storage: storage, + + RequestInfoResolver: newTestRequestInfoResolver(), + + Creater: api.Scheme, + Convertor: api.Scheme, + Typer: api.Scheme, + Linker: selfLinker, + Mapper: namespaceMapper, + + ParameterCodec: api.ParameterCodec, + + Admit: admissionControl, + Context: requestContextMapper, + } + + // groupless v1 version + { + group := template + group.Root = "/" + grouplessPrefix + group.GroupVersion = grouplessGroupVersion + group.OptionsExternalVersion = &grouplessGroupVersion + group.Serializer = api.Codecs + group.StreamSerializer = api.StreamCodecs + if err := (&group).InstallREST(container); err != nil { + panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) + } + } + + // group version 1 + { + group := template + group.Root = "/" + prefix + group.GroupVersion = testGroupVersion + group.OptionsExternalVersion = &testGroupVersion + group.Serializer = api.Codecs + group.StreamSerializer = api.StreamCodecs + if err := (&group).InstallREST(container); err != nil { + panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) + } + } + + // group version 2 + { + group := template + group.Root = "/" + prefix + group.GroupVersion = newGroupVersion + group.OptionsExternalVersion = &newGroupVersion + group.Serializer = api.Codecs + group.StreamSerializer = api.StreamCodecs + if err := (&group).InstallREST(container); err != nil { + panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) + } + } + + ws := new(restful.WebService) + InstallSupport(mux, ws) + container.Add(ws) + return &defaultAPIServer{mux, container} +} + +func TestSimpleSetupRight(t *testing.T) { + s := &apiservertesting.Simple{ObjectMeta: api.ObjectMeta{Name: "aName"}} + wire, err := runtime.Encode(codec, s) + if err != nil { + t.Fatal(err) + } + s2, err := runtime.Decode(codec, wire) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(s, s2) { + t.Fatalf("encode/decode broken:\n%#v\n%#v\n", s, s2) + } +} + +func TestSimpleOptionsSetupRight(t *testing.T) { + s := &apiservertesting.SimpleGetOptions{} + wire, err := runtime.Encode(codec, s) + if err != nil { + t.Fatal(err) + } + s2, err := runtime.Decode(codec, wire) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(s, s2) { + t.Fatalf("encode/decode broken:\n%#v\n%#v\n", s, s2) + } +} + +type SimpleRESTStorage struct { + errors map[string]error + list []apiservertesting.Simple + item apiservertesting.Simple + + updated *apiservertesting.Simple + created *apiservertesting.Simple + + stream *SimpleStream + + deleted string + deleteOptions *api.DeleteOptions + + actualNamespace string + namespacePresent bool + + // These are set when Watch is called + fakeWatch *watch.FakeWatcher + requestedLabelSelector labels.Selector + requestedFieldSelector fields.Selector + requestedResourceVersion string + requestedResourceNamespace string + + // The id requested, and location to return for ResourceLocation + requestedResourceLocationID string + resourceLocation *url.URL + resourceLocationTransport http.RoundTripper + expectedResourceNamespace string + + // If non-nil, called inside the WorkFunc when answering update, delete, create. + // obj receives the original input to the update, delete, or create call. + injectedFunction func(obj runtime.Object) (returnObj runtime.Object, err error) +} + +func (storage *SimpleRESTStorage) Export(ctx api.Context, name string, opts unversioned.ExportOptions) (runtime.Object, error) { + obj, err := storage.Get(ctx, name) + if err != nil { + return nil, err + } + s, ok := obj.(*apiservertesting.Simple) + if !ok { + return nil, fmt.Errorf("unexpected object") + } + + // Set a marker to verify the method was called + s.Other = "exported" + return obj, storage.errors["export"] +} + +func (storage *SimpleRESTStorage) List(ctx api.Context, options *api.ListOptions) (runtime.Object, error) { + storage.checkContext(ctx) + result := &apiservertesting.SimpleList{ + Items: storage.list, + } + storage.requestedLabelSelector = labels.Everything() + if options != nil && options.LabelSelector != nil { + storage.requestedLabelSelector = options.LabelSelector + } + storage.requestedFieldSelector = fields.Everything() + if options != nil && options.FieldSelector != nil { + storage.requestedFieldSelector = options.FieldSelector + } + return result, storage.errors["list"] +} + +type SimpleStream struct { + version string + accept string + contentType string + err error + + io.Reader + closed bool +} + +func (s *SimpleStream) Close() error { + s.closed = true + return nil +} + +func (obj *SimpleStream) GetObjectKind() unversioned.ObjectKind { return unversioned.EmptyObjectKind } + +func (s *SimpleStream) InputStream(version, accept string) (io.ReadCloser, bool, string, error) { + s.version = version + s.accept = accept + return s, false, s.contentType, s.err +} + +type OutputConnect struct { + response string +} + +func (h *OutputConnect) ServeHTTP(w http.ResponseWriter, req *http.Request) { + w.Write([]byte(h.response)) +} + +func (storage *SimpleRESTStorage) Get(ctx api.Context, id string) (runtime.Object, error) { + storage.checkContext(ctx) + if id == "binary" { + return storage.stream, storage.errors["get"] + } + copied, err := api.Scheme.Copy(&storage.item) + if err != nil { + panic(err) + } + return copied, storage.errors["get"] +} + +func (storage *SimpleRESTStorage) checkContext(ctx api.Context) { + storage.actualNamespace, storage.namespacePresent = api.NamespaceFrom(ctx) +} + +func (storage *SimpleRESTStorage) Delete(ctx api.Context, id string, options *api.DeleteOptions) (runtime.Object, error) { + storage.checkContext(ctx) + storage.deleted = id + storage.deleteOptions = options + if err := storage.errors["delete"]; err != nil { + return nil, err + } + var obj runtime.Object = &unversioned.Status{Status: unversioned.StatusSuccess} + var err error + if storage.injectedFunction != nil { + obj, err = storage.injectedFunction(&apiservertesting.Simple{ObjectMeta: api.ObjectMeta{Name: id}}) + } + return obj, err +} + +func (storage *SimpleRESTStorage) New() runtime.Object { + return &apiservertesting.Simple{} +} + +func (storage *SimpleRESTStorage) NewList() runtime.Object { + return &apiservertesting.SimpleList{} +} + +func (storage *SimpleRESTStorage) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { + storage.checkContext(ctx) + storage.created = obj.(*apiservertesting.Simple) + if err := storage.errors["create"]; err != nil { + return nil, err + } + var err error + if storage.injectedFunction != nil { + obj, err = storage.injectedFunction(obj) + } + return obj, err +} + +func (storage *SimpleRESTStorage) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + storage.checkContext(ctx) + storage.updated = obj.(*apiservertesting.Simple) + if err := storage.errors["update"]; err != nil { + return nil, false, err + } + var err error + if storage.injectedFunction != nil { + obj, err = storage.injectedFunction(obj) + } + return obj, false, err +} + +// Implement ResourceWatcher. +func (storage *SimpleRESTStorage) Watch(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + storage.checkContext(ctx) + storage.requestedLabelSelector = labels.Everything() + if options != nil && options.LabelSelector != nil { + storage.requestedLabelSelector = options.LabelSelector + } + storage.requestedFieldSelector = fields.Everything() + if options != nil && options.FieldSelector != nil { + storage.requestedFieldSelector = options.FieldSelector + } + storage.requestedResourceVersion = "" + if options != nil { + storage.requestedResourceVersion = options.ResourceVersion + } + storage.requestedResourceNamespace = api.NamespaceValue(ctx) + if err := storage.errors["watch"]; err != nil { + return nil, err + } + storage.fakeWatch = watch.NewFake() + return storage.fakeWatch, nil +} + +// Implement Redirector. +var _ = rest.Redirector(&SimpleRESTStorage{}) + +// Implement Redirector. +func (storage *SimpleRESTStorage) ResourceLocation(ctx api.Context, id string) (*url.URL, http.RoundTripper, error) { + storage.checkContext(ctx) + // validate that the namespace context on the request matches the expected input + storage.requestedResourceNamespace = api.NamespaceValue(ctx) + if storage.expectedResourceNamespace != storage.requestedResourceNamespace { + return nil, nil, fmt.Errorf("Expected request namespace %s, but got namespace %s", storage.expectedResourceNamespace, storage.requestedResourceNamespace) + } + storage.requestedResourceLocationID = id + if err := storage.errors["resourceLocation"]; err != nil { + return nil, nil, err + } + // Make a copy so the internal URL never gets mutated + locationCopy := *storage.resourceLocation + return &locationCopy, storage.resourceLocationTransport, nil +} + +// Implement Connecter +type ConnecterRESTStorage struct { + connectHandler http.Handler + handlerFunc func() http.Handler + + emptyConnectOptions runtime.Object + receivedConnectOptions runtime.Object + receivedID string + receivedResponder rest.Responder + takesPath string +} + +// Implement Connecter +var _ = rest.Connecter(&ConnecterRESTStorage{}) + +func (s *ConnecterRESTStorage) New() runtime.Object { + return &apiservertesting.Simple{} +} + +func (s *ConnecterRESTStorage) Connect(ctx api.Context, id string, options runtime.Object, responder rest.Responder) (http.Handler, error) { + s.receivedConnectOptions = options + s.receivedID = id + s.receivedResponder = responder + if s.handlerFunc != nil { + return s.handlerFunc(), nil + } + return s.connectHandler, nil +} + +func (s *ConnecterRESTStorage) ConnectMethods() []string { + return []string{"GET", "POST", "PUT", "DELETE"} +} + +func (s *ConnecterRESTStorage) NewConnectOptions() (runtime.Object, bool, string) { + if len(s.takesPath) > 0 { + return s.emptyConnectOptions, true, s.takesPath + } + return s.emptyConnectOptions, false, "" +} + +type LegacyRESTStorage struct { + *SimpleRESTStorage +} + +func (storage LegacyRESTStorage) Delete(ctx api.Context, id string) (runtime.Object, error) { + return storage.SimpleRESTStorage.Delete(ctx, id, nil) +} + +type MetadataRESTStorage struct { + *SimpleRESTStorage + types []string +} + +func (m *MetadataRESTStorage) ProducesMIMETypes(method string) []string { + return m.types +} + +var _ rest.StorageMetadata = &MetadataRESTStorage{} + +type GetWithOptionsRESTStorage struct { + *SimpleRESTStorage + optionsReceived runtime.Object + takesPath string +} + +func (r *GetWithOptionsRESTStorage) Get(ctx api.Context, name string, options runtime.Object) (runtime.Object, error) { + if _, ok := options.(*apiservertesting.SimpleGetOptions); !ok { + return nil, fmt.Errorf("Unexpected options object: %#v", options) + } + r.optionsReceived = options + return r.SimpleRESTStorage.Get(ctx, name) +} + +func (r *GetWithOptionsRESTStorage) NewGetOptions() (runtime.Object, bool, string) { + if len(r.takesPath) > 0 { + return &apiservertesting.SimpleGetOptions{}, true, r.takesPath + } + return &apiservertesting.SimpleGetOptions{}, false, "" +} + +var _ rest.GetterWithOptions = &GetWithOptionsRESTStorage{} + +type NamedCreaterRESTStorage struct { + *SimpleRESTStorage + createdName string +} + +func (storage *NamedCreaterRESTStorage) Create(ctx api.Context, name string, obj runtime.Object) (runtime.Object, error) { + storage.checkContext(ctx) + storage.created = obj.(*apiservertesting.Simple) + storage.createdName = name + if err := storage.errors["create"]; err != nil { + return nil, err + } + var err error + if storage.injectedFunction != nil { + obj, err = storage.injectedFunction(obj) + } + return obj, err +} + +type SimpleTypedStorage struct { + errors map[string]error + item runtime.Object + baseType runtime.Object + + actualNamespace string + namespacePresent bool +} + +func (storage *SimpleTypedStorage) New() runtime.Object { + return storage.baseType +} + +func (storage *SimpleTypedStorage) Get(ctx api.Context, id string) (runtime.Object, error) { + storage.checkContext(ctx) + copied, err := api.Scheme.Copy(storage.item) + if err != nil { + panic(err) + } + return copied, storage.errors["get"] +} + +func (storage *SimpleTypedStorage) checkContext(ctx api.Context) { + storage.actualNamespace, storage.namespacePresent = api.NamespaceFrom(ctx) +} + +func extractBody(response *http.Response, object runtime.Object) (string, error) { + return extractBodyDecoder(response, object, codec) +} + +func extractBodyDecoder(response *http.Response, object runtime.Object, decoder runtime.Decoder) (string, error) { + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + if err != nil { + return string(body), err + } + return string(body), runtime.DecodeInto(decoder, body, object) +} + +func TestNotFound(t *testing.T) { + type T struct { + Method string + Path string + Status int + } + cases := map[string]T{ + // Positive checks to make sure everything is wired correctly + "groupless GET root": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusOK}, + "groupless GET namespaced": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusOK}, + + "groupless GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound}, + + "groupless root PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, + "groupless root GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", http.StatusNotFound}, + "groupless root GET with extra segment": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, + "groupless root DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, + "groupless root DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, + "groupless root PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, + "groupless root PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, + "groupless root watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusNotFound}, + + "groupless namespaced PATCH method": {"PATCH", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, + "groupless namespaced GET long prefix": {"GET", "/" + grouplessPrefix + "/", http.StatusNotFound}, + "groupless namespaced GET missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/blah", http.StatusNotFound}, + "groupless namespaced GET with extra segment": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, + "groupless namespaced POST with extra segment": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, + "groupless namespaced DELETE without extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, + "groupless namespaced DELETE with extra segment": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, + "groupless namespaced PUT without extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, + "groupless namespaced PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, + "groupless namespaced watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusNotFound}, + "groupless namespaced watch with bad method": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, + + // Positive checks to make sure everything is wired correctly + "GET root": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusOK}, + // TODO: JTL: "GET root item": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar", http.StatusOK}, + "GET namespaced": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusOK}, + // TODO: JTL: "GET namespaced item": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar", http.StatusOK}, + + "GET long prefix": {"GET", "/" + prefix + "/", http.StatusNotFound}, + + "root PATCH method": {"PATCH", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, + "root GET missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/blah", http.StatusNotFound}, + "root GET with extra segment": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, + // TODO: JTL: "root POST with extra segment": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar", http.StatusMethodNotAllowed}, + "root DELETE without extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, + "root DELETE with extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, + "root PUT without extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusMethodNotAllowed}, + "root PUT with extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots/bar/baz", http.StatusNotFound}, + "root watch missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusNotFound}, + // TODO: JTL: "root watch with bad method": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simpleroot/bar", http.StatusMethodNotAllowed}, + + "namespaced PATCH method": {"PATCH", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, + "namespaced GET long prefix": {"GET", "/" + prefix + "/", http.StatusNotFound}, + "namespaced GET missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/blah", http.StatusNotFound}, + "namespaced GET with extra segment": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, + "namespaced POST with extra segment": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, + "namespaced DELETE without extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, + "namespaced DELETE with extra segment": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, + "namespaced PUT without extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples", http.StatusMethodNotAllowed}, + "namespaced PUT with extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, + "namespaced watch missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusNotFound}, + "namespaced watch with bad method": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, + } + handler := handle(map[string]rest.Storage{ + "simples": &SimpleRESTStorage{}, + "simpleroots": &SimpleRESTStorage{}, + }) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + for k, v := range cases { + request, err := http.NewRequest(v.Method, server.URL+v.Path, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if response.StatusCode != v.Status { + t.Errorf("Expected %d for %s (%s), Got %#v", v.Status, v.Method, k, response) + t.Errorf("MAPPER: %v", mapper) + } + } +} + +type UnimplementedRESTStorage struct{} + +func (UnimplementedRESTStorage) New() runtime.Object { + return &apiservertesting.Simple{} +} + +// TestUnimplementedRESTStorage ensures that if a rest.Storage does not implement a given +// method, that it is literally not registered with the server. In the past, +// we registered everything, and returned method not supported if it didn't support +// a verb. Now we literally do not register a storage if it does not implement anything. +// TODO: in future, we should update proxy/redirect +func TestUnimplementedRESTStorage(t *testing.T) { + type T struct { + Method string + Path string + ErrCode int + } + cases := map[string]T{ + "groupless GET object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound}, + "groupless GET list": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo", http.StatusNotFound}, + "groupless POST list": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo", http.StatusNotFound}, + "groupless PUT object": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound}, + "groupless DELETE object": {"DELETE", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/foo/bar", http.StatusNotFound}, + "groupless watch list": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/foo", http.StatusNotFound}, + "groupless watch object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/foo/bar", http.StatusNotFound}, + "groupless proxy object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/proxy/foo/bar", http.StatusNotFound}, + "groupless redirect object": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/redirect/foo/bar", http.StatusNotFound}, + + "GET object": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound}, + "GET list": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo", http.StatusNotFound}, + "POST list": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo", http.StatusNotFound}, + "PUT object": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound}, + "DELETE object": {"DELETE", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/foo/bar", http.StatusNotFound}, + "watch list": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/foo", http.StatusNotFound}, + "watch object": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/foo/bar", http.StatusNotFound}, + "proxy object": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/proxy/foo/bar", http.StatusNotFound}, + "redirect object": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/redirect/foo/bar", http.StatusNotFound}, + } + handler := handle(map[string]rest.Storage{ + "foo": UnimplementedRESTStorage{}, + }) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + for k, v := range cases { + request, err := http.NewRequest(v.Method, server.URL+v.Path, bytes.NewReader([]byte(`{"kind":"Simple","apiVersion":"version"}`))) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + response, err := client.Do(request) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + defer response.Body.Close() + data, err := ioutil.ReadAll(response.Body) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if response.StatusCode != v.ErrCode { + t.Errorf("%s: expected %d for %s, Got %s", k, v.ErrCode, v.Method, string(data)) + continue + } + } +} + +func TestVersion(t *testing.T) { + handler := handle(map[string]rest.Storage{}) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + + request, err := http.NewRequest("GET", server.URL+"/version", nil) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + var info version.Info + err = json.NewDecoder(response.Body).Decode(&info) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if !reflect.DeepEqual(version.Get(), info) { + t.Errorf("Expected %#v, Got %#v", version.Get(), info) + } +} + +func TestList(t *testing.T) { + testCases := []struct { + url string + namespace string + selfLink string + legacy bool + label string + field string + }{ + // Groupless API + + // legacy namespace param is ignored + { + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=", + namespace: "", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", + legacy: true, + }, + { + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=other", + namespace: "", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", + legacy: true, + }, + { + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple?namespace=other&labelSelector=a%3Db&fieldSelector=c%3Dd", + namespace: "", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", + legacy: true, + label: "a=b", + field: "c=d", + }, + // legacy api version is honored + { + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", + namespace: "", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", + legacy: true, + }, + { + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", + namespace: "other", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", + legacy: true, + }, + { + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", + namespace: "other", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", + legacy: true, + label: "a=b", + field: "c=d", + }, + // list items across all namespaces + { + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", + namespace: "", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", + legacy: true, + }, + // list items in a namespace in the path + { + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple", + namespace: "default", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/default/simple", + }, + { + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", + namespace: "other", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", + }, + { + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", + namespace: "other", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/other/simple", + label: "a=b", + field: "c=d", + }, + // list items across all namespaces + { + url: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", + namespace: "", + selfLink: "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/simple", + }, + + // Group API + + // legacy namespace param is ignored + { + url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=", + namespace: "", + selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", + legacy: true, + }, + { + url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=other", + namespace: "", + selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", + legacy: true, + }, + { + url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple?namespace=other&labelSelector=a%3Db&fieldSelector=c%3Dd", + namespace: "", + selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", + legacy: true, + label: "a=b", + field: "c=d", + }, + // legacy api version is honored + { + url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", + namespace: "", + selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", + legacy: true, + }, + { + url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple", + namespace: "other", + selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple", + legacy: true, + }, + { + url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", + namespace: "other", + selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple", + legacy: true, + label: "a=b", + field: "c=d", + }, + // list items across all namespaces + { + url: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", + namespace: "", + selfLink: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple", + legacy: true, + }, + // list items in a namespace in the path + { + url: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/default/simple", + namespace: "default", + selfLink: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/default/simple", + }, + { + url: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/other/simple", + namespace: "other", + selfLink: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/other/simple", + }, + { + url: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/other/simple?labelSelector=a%3Db&fieldSelector=c%3Dd", + namespace: "other", + selfLink: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/other/simple", + label: "a=b", + field: "c=d", + }, + // list items across all namespaces + { + url: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/simple", + namespace: "", + selfLink: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/simple", + }, + } + for i, testCase := range testCases { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{expectedResourceNamespace: testCase.namespace} + storage["simple"] = &simpleStorage + selfLinker := &setTestSelfLinker{ + t: t, + namespace: testCase.namespace, + expectedSet: testCase.selfLink, + } + var handler = handleInternal(storage, admissionControl, selfLinker) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + testCase.url) + if err != nil { + t.Errorf("%d: unexpected error: %v", i, err) + continue + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + t.Errorf("%d: unexpected status: %d from url %s, Expected: %d, %#v", i, resp.StatusCode, testCase.url, http.StatusOK, resp) + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("%d: unexpected error: %v", i, err) + continue + } + t.Logf("%d: body: %s", i, string(body)) + continue + } + // TODO: future, restore get links + if !selfLinker.called { + t.Errorf("%d: never set self link", i) + } + if !simpleStorage.namespacePresent { + t.Errorf("%d: namespace not set", i) + } else if simpleStorage.actualNamespace != testCase.namespace { + t.Errorf("%d: unexpected resource namespace: %s", i, simpleStorage.actualNamespace) + } + if simpleStorage.requestedLabelSelector == nil || simpleStorage.requestedLabelSelector.String() != testCase.label { + t.Errorf("%d: unexpected label selector: %v", i, simpleStorage.requestedLabelSelector) + } + if simpleStorage.requestedFieldSelector == nil || simpleStorage.requestedFieldSelector.String() != testCase.field { + t.Errorf("%d: unexpected field selector: %v", i, simpleStorage.requestedFieldSelector) + } + } +} + +func TestErrorList(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{ + errors: map[string]error{"list": fmt.Errorf("test Error")}, + } + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if resp.StatusCode != http.StatusInternalServerError { + t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusInternalServerError, resp) + } +} + +func TestNonEmptyList(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{ + list: []apiservertesting.Simple{ + { + ObjectMeta: api.ObjectMeta{Name: "something", Namespace: "other"}, + Other: "foo", + }, + }, + } + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp) + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Logf("Data: %s", string(body)) + } + + var listOut apiservertesting.SimpleList + body, err := extractBody(resp, &listOut) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if len(listOut.Items) != 1 { + t.Errorf("Unexpected response: %#v", listOut) + return + } + if listOut.Items[0].Other != simpleStorage.list[0].Other { + t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body)) + } + if listOut.SelfLink != "/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/simple" { + t.Errorf("unexpected list self link: %#v", listOut) + } + expectedSelfLink := "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/simple/something" + if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink { + t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink) + } +} + +func TestSelfLinkSkipsEmptyName(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{ + list: []apiservertesting.Simple{ + { + ObjectMeta: api.ObjectMeta{Namespace: "other"}, + Other: "foo", + }, + }, + } + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("Unexpected status: %d, Expected: %d, %#v", resp.StatusCode, http.StatusOK, resp) + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Logf("Data: %s", string(body)) + } + var listOut apiservertesting.SimpleList + body, err := extractBody(resp, &listOut) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if len(listOut.Items) != 1 { + t.Errorf("Unexpected response: %#v", listOut) + return + } + if listOut.Items[0].Other != simpleStorage.list[0].Other { + t.Errorf("Unexpected data: %#v, %s", listOut.Items[0], string(body)) + } + if listOut.SelfLink != "/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/simple" { + t.Errorf("unexpected list self link: %#v", listOut) + } + expectedSelfLink := "" + if listOut.Items[0].ObjectMeta.SelfLink != expectedSelfLink { + t.Errorf("Unexpected data: %#v, %s", listOut.Items[0].ObjectMeta.SelfLink, expectedSelfLink) + } +} + +func TestMetadata(t *testing.T) { + simpleStorage := &MetadataRESTStorage{&SimpleRESTStorage{}, []string{"text/plain"}} + h := handle(map[string]rest.Storage{"simple": simpleStorage}) + ws := h.(*defaultAPIServer).container.RegisteredWebServices() + if len(ws) == 0 { + t.Fatal("no web services registered") + } + matches := map[string]int{} + for _, w := range ws { + for _, r := range w.Routes() { + s := strings.Join(r.Produces, ",") + i := matches[s] + matches[s] = i + 1 + } + } + if matches["text/plain,application/json,application/yaml"] == 0 || + matches["application/json,application/yaml"] == 0 || + matches["application/json"] == 0 || + matches["*/*"] == 0 || + len(matches) != 4 { + t.Errorf("unexpected mime types: %v", matches) + } +} + +func TestExport(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{ + item: apiservertesting.Simple{ + ObjectMeta: api.ObjectMeta{ + ResourceVersion: "1234", + CreationTimestamp: unversioned.NewTime(time.Unix(10, 10)), + }, + Other: "foo", + }, + } + selfLinker := &setTestSelfLinker{ + t: t, + expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id", + name: "id", + namespace: "default", + } + storage["simple"] = &simpleStorage + handler := handleLinker(storage, selfLinker) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id?export=true") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != http.StatusOK { + data, _ := ioutil.ReadAll(resp.Body) + resp.Body.Close() + t.Fatalf("unexpected response: %#v\n%s\n", resp, string(data)) + } + var itemOut apiservertesting.Simple + body, err := extractBody(resp, &itemOut) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if itemOut.Name != simpleStorage.item.Name { + t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) + } + if itemOut.Other != "exported" { + t.Errorf("Expected: exported, saw: %s", itemOut.Other) + } + + if !selfLinker.called { + t.Errorf("Never set self link") + } +} + +func TestGet(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{ + item: apiservertesting.Simple{ + Other: "foo", + }, + } + selfLinker := &setTestSelfLinker{ + t: t, + expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id", + name: "id", + namespace: "default", + } + storage["simple"] = &simpleStorage + handler := handleLinker(storage, selfLinker) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatalf("unexpected response: %#v", resp) + } + var itemOut apiservertesting.Simple + body, err := extractBody(resp, &itemOut) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if itemOut.Name != simpleStorage.item.Name { + t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) + } + if !selfLinker.called { + t.Errorf("Never set self link") + } +} + +func TestGetBinary(t *testing.T) { + simpleStorage := SimpleRESTStorage{ + stream: &SimpleStream{ + contentType: "text/plain", + Reader: bytes.NewBufferString("response data"), + }, + } + stream := simpleStorage.stream + server := httptest.NewServer(handle(map[string]rest.Storage{"simple": &simpleStorage})) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + req, err := http.NewRequest("GET", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/binary", nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + req.Header.Add("Accept", "text/other, */*") + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatalf("unexpected response: %#v", resp) + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !stream.closed || stream.version != testGroupVersion.String() || stream.accept != "text/other, */*" || + resp.Header.Get("Content-Type") != stream.contentType || string(body) != "response data" { + t.Errorf("unexpected stream: %#v", stream) + } +} + +func validateSimpleGetOptionsParams(t *testing.T, route *restful.Route) { + // Validate name and description + expectedParams := map[string]string{ + "param1": "description for param1", + "param2": "description for param2", + "atAPath": "", + } + for _, p := range route.ParameterDocs { + data := p.Data() + if desc, exists := expectedParams[data.Name]; exists { + if desc != data.Description { + t.Errorf("unexpected description for parameter %s: %s\n", data.Name, data.Description) + } + delete(expectedParams, data.Name) + } + } + if len(expectedParams) > 0 { + t.Errorf("did not find all expected parameters: %#v", expectedParams) + } +} + +func TestGetWithOptionsRouteParams(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := GetWithOptionsRESTStorage{ + SimpleRESTStorage: &SimpleRESTStorage{}, + } + storage["simple"] = &simpleStorage + handler := handle(storage) + ws := handler.(*defaultAPIServer).container.RegisteredWebServices() + if len(ws) == 0 { + t.Fatal("no web services registered") + } + routes := ws[0].Routes() + for i := range routes { + if routes[i].Method == "GET" && routes[i].Operation == "readNamespacedSimple" { + validateSimpleGetOptionsParams(t, &routes[i]) + break + } + } +} + +func TestGetWithOptions(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := GetWithOptionsRESTStorage{ + SimpleRESTStorage: &SimpleRESTStorage{ + item: apiservertesting.Simple{ + Other: "foo", + }, + }, + } + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id?param1=test1¶m2=test2") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatalf("unexpected response: %#v", resp) + } + var itemOut apiservertesting.Simple + body, err := extractBody(resp, &itemOut) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if itemOut.Name != simpleStorage.item.Name { + t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) + } + + opts, ok := simpleStorage.optionsReceived.(*apiservertesting.SimpleGetOptions) + if !ok { + t.Errorf("Unexpected options object received: %#v", simpleStorage.optionsReceived) + return + } + if opts.Param1 != "test1" || opts.Param2 != "test2" { + t.Errorf("Did not receive expected options: %#v", opts) + } +} + +func TestGetWithOptionsAndPath(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := GetWithOptionsRESTStorage{ + SimpleRESTStorage: &SimpleRESTStorage{ + item: apiservertesting.Simple{ + Other: "foo", + }, + }, + takesPath: "atAPath", + } + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id/a/different/path?param1=test1¶m2=test2&atAPath=not") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatalf("unexpected response: %#v", resp) + } + var itemOut apiservertesting.Simple + body, err := extractBody(resp, &itemOut) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if itemOut.Name != simpleStorage.item.Name { + t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) + } + + opts, ok := simpleStorage.optionsReceived.(*apiservertesting.SimpleGetOptions) + if !ok { + t.Errorf("Unexpected options object received: %#v", simpleStorage.optionsReceived) + return + } + if opts.Param1 != "test1" || opts.Param2 != "test2" || opts.Path != "a/different/path" { + t.Errorf("Did not receive expected options: %#v", opts) + } +} +func TestGetAlternateSelfLink(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{ + item: apiservertesting.Simple{ + Other: "foo", + }, + } + selfLinker := &setTestSelfLinker{ + t: t, + expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/test/simple/id", + name: "id", + namespace: "test", + } + storage["simple"] = &simpleStorage + handler := handleLinker(storage, selfLinker) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/test/simple/id") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatalf("unexpected response: %#v", resp) + } + var itemOut apiservertesting.Simple + body, err := extractBody(resp, &itemOut) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if itemOut.Name != simpleStorage.item.Name { + t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) + } + if !selfLinker.called { + t.Errorf("Never set self link") + } +} + +func TestGetNamespaceSelfLink(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{ + item: apiservertesting.Simple{ + Other: "foo", + }, + } + selfLinker := &setTestSelfLinker{ + t: t, + expectedSet: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/foo/simple/id", + name: "id", + namespace: "foo", + } + storage["simple"] = &simpleStorage + handler := handleInternal(storage, admissionControl, selfLinker) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/foo/simple/id") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatalf("unexpected response: %#v", resp) + } + var itemOut apiservertesting.Simple + body, err := extractBody(resp, &itemOut) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if itemOut.Name != simpleStorage.item.Name { + t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simpleStorage.item, string(body)) + } + if !selfLinker.called { + t.Errorf("Never set self link") + } +} +func TestGetMissing(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{ + errors: map[string]error{"get": apierrs.NewNotFound(api.Resource("simples"), "id")}, + } + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simple/id") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if resp.StatusCode != http.StatusNotFound { + t.Errorf("Unexpected response %#v", resp) + } +} + +func TestConnect(t *testing.T) { + responseText := "Hello World" + itemID := "theID" + connectStorage := &ConnecterRESTStorage{ + connectHandler: &OutputConnect{ + response: responseText, + }, + } + storage := map[string]rest.Storage{ + "simple": &SimpleRESTStorage{}, + "simple/connect": connectStorage, + } + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect") + + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Errorf("unexpected response: %#v", resp) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if connectStorage.receivedID != itemID { + t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) + } + if string(body) != responseText { + t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body)) + } +} + +func TestConnectResponderObject(t *testing.T) { + itemID := "theID" + simple := &apiservertesting.Simple{Other: "foo"} + connectStorage := &ConnecterRESTStorage{} + connectStorage.handlerFunc = func() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + connectStorage.receivedResponder.Object(http.StatusCreated, simple) + }) + } + storage := map[string]rest.Storage{ + "simple": &SimpleRESTStorage{}, + "simple/connect": connectStorage, + } + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect") + + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if resp.StatusCode != http.StatusCreated { + t.Errorf("unexpected response: %#v", resp) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if connectStorage.receivedID != itemID { + t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) + } + obj, err := runtime.Decode(codec, body) + if err != nil { + t.Fatal(err) + } + if !api.Semantic.DeepEqual(obj, simple) { + t.Errorf("Unexpected response: %#v", obj) + } +} + +func TestConnectResponderError(t *testing.T) { + itemID := "theID" + connectStorage := &ConnecterRESTStorage{} + connectStorage.handlerFunc = func() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + connectStorage.receivedResponder.Error(apierrs.NewForbidden(api.Resource("simples"), itemID, errors.New("you are terminated"))) + }) + } + storage := map[string]rest.Storage{ + "simple": &SimpleRESTStorage{}, + "simple/connect": connectStorage, + } + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect") + + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if resp.StatusCode != http.StatusForbidden { + t.Errorf("unexpected response: %#v", resp) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if connectStorage.receivedID != itemID { + t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) + } + obj, err := runtime.Decode(codec, body) + if err != nil { + t.Fatal(err) + } + if obj.(*unversioned.Status).Code != http.StatusForbidden { + t.Errorf("Unexpected response: %#v", obj) + } +} + +func TestConnectWithOptionsRouteParams(t *testing.T) { + connectStorage := &ConnecterRESTStorage{ + connectHandler: &OutputConnect{}, + emptyConnectOptions: &apiservertesting.SimpleGetOptions{}, + } + storage := map[string]rest.Storage{ + "simple": &SimpleRESTStorage{}, + "simple/connect": connectStorage, + } + handler := handle(storage) + ws := handler.(*defaultAPIServer).container.RegisteredWebServices() + if len(ws) == 0 { + t.Fatal("no web services registered") + } + routes := ws[0].Routes() + for i := range routes { + switch routes[i].Operation { + case "connectGetNamespacedSimpleConnect": + case "connectPostNamespacedSimpleConnect": + case "connectPutNamespacedSimpleConnect": + case "connectDeleteNamespacedSimpleConnect": + validateSimpleGetOptionsParams(t, &routes[i]) + + } + } +} + +func TestConnectWithOptions(t *testing.T) { + responseText := "Hello World" + itemID := "theID" + connectStorage := &ConnecterRESTStorage{ + connectHandler: &OutputConnect{ + response: responseText, + }, + emptyConnectOptions: &apiservertesting.SimpleGetOptions{}, + } + storage := map[string]rest.Storage{ + "simple": &SimpleRESTStorage{}, + "simple/connect": connectStorage, + } + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect?param1=value1¶m2=value2") + + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Errorf("unexpected response: %#v", resp) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if connectStorage.receivedID != itemID { + t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) + } + if string(body) != responseText { + t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body)) + } + if connectStorage.receivedResponder == nil { + t.Errorf("Unexpected responder") + } + opts, ok := connectStorage.receivedConnectOptions.(*apiservertesting.SimpleGetOptions) + if !ok { + t.Fatalf("Unexpected options type: %#v", connectStorage.receivedConnectOptions) + } + if opts.Param1 != "value1" && opts.Param2 != "value2" { + t.Errorf("Unexpected options value: %#v", opts) + } +} + +func TestConnectWithOptionsAndPath(t *testing.T) { + responseText := "Hello World" + itemID := "theID" + testPath := "a/b/c/def" + connectStorage := &ConnecterRESTStorage{ + connectHandler: &OutputConnect{ + response: responseText, + }, + emptyConnectOptions: &apiservertesting.SimpleGetOptions{}, + takesPath: "atAPath", + } + storage := map[string]rest.Storage{ + "simple": &SimpleRESTStorage{}, + "simple/connect": connectStorage, + } + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/connect/" + testPath + "?param1=value1¶m2=value2") + + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Errorf("unexpected response: %#v", resp) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if connectStorage.receivedID != itemID { + t.Errorf("Unexpected item id. Expected: %s. Actual: %s.", itemID, connectStorage.receivedID) + } + if string(body) != responseText { + t.Errorf("Unexpected response. Expected: %s. Actual: %s.", responseText, string(body)) + } + opts, ok := connectStorage.receivedConnectOptions.(*apiservertesting.SimpleGetOptions) + if !ok { + t.Fatalf("Unexpected options type: %#v", connectStorage.receivedConnectOptions) + } + if opts.Param1 != "value1" && opts.Param2 != "value2" { + t.Errorf("Unexpected options value: %#v", opts) + } + if opts.Path != testPath { + t.Errorf("Unexpected path value. Expected: %s. Actual: %s.", testPath, opts.Path) + } +} + +func TestDelete(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{} + ID := "id" + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + client := http.Client{} + request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil) + res, err := client.Do(request) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if res.StatusCode != http.StatusOK { + t.Errorf("unexpected response: %#v", res) + } + if simpleStorage.deleted != ID { + t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID) + } +} + +func TestDeleteWithOptions(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{} + ID := "id" + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + grace := int64(300) + item := &api.DeleteOptions{ + GracePeriodSeconds: &grace, + } + body, err := runtime.Encode(codec, item) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + client := http.Client{} + request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) + res, err := client.Do(request) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if res.StatusCode != http.StatusOK { + t.Errorf("unexpected response: %s %#v", request.URL, res) + s, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Logf(string(s)) + } + if simpleStorage.deleted != ID { + t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID) + } + if !api.Semantic.DeepEqual(simpleStorage.deleteOptions, item) { + t.Errorf("unexpected delete options: %s", diff.ObjectDiff(simpleStorage.deleteOptions, item)) + } +} + +func TestLegacyDelete(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{} + ID := "id" + storage["simple"] = LegacyRESTStorage{&simpleStorage} + var _ rest.Deleter = storage["simple"].(LegacyRESTStorage) + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + client := http.Client{} + request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil) + res, err := client.Do(request) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if res.StatusCode != http.StatusOK { + t.Errorf("unexpected response: %#v", res) + } + if simpleStorage.deleted != ID { + t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID) + } + if simpleStorage.deleteOptions != nil { + t.Errorf("unexpected delete options: %#v", simpleStorage.deleteOptions) + } +} + +func TestLegacyDeleteIgnoresOptions(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{} + ID := "id" + storage["simple"] = LegacyRESTStorage{&simpleStorage} + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + item := api.NewDeleteOptions(300) + body, err := runtime.Encode(codec, item) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + client := http.Client{} + request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) + res, err := client.Do(request) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if res.StatusCode != http.StatusOK { + t.Errorf("unexpected response: %#v", res) + } + if simpleStorage.deleted != ID { + t.Errorf("Unexpected delete: %s, expected %s", simpleStorage.deleted, ID) + } + if simpleStorage.deleteOptions != nil { + t.Errorf("unexpected delete options: %#v", simpleStorage.deleteOptions) + } +} + +func TestDeleteInvokesAdmissionControl(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{} + ID := "id" + storage["simple"] = &simpleStorage + handler := handleDeny(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + client := http.Client{} + request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil) + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusForbidden { + t.Errorf("Unexpected response %#v", response) + } +} + +func TestDeleteMissing(t *testing.T) { + storage := map[string]rest.Storage{} + ID := "id" + simpleStorage := SimpleRESTStorage{ + errors: map[string]error{"delete": apierrs.NewNotFound(api.Resource("simples"), ID)}, + } + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + client := http.Client{} + request, err := http.NewRequest("DELETE", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, nil) + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if response.StatusCode != http.StatusNotFound { + t.Errorf("Unexpected response %#v", response) + } +} + +func TestPatch(t *testing.T) { + storage := map[string]rest.Storage{} + ID := "id" + item := &apiservertesting.Simple{ + ObjectMeta: api.ObjectMeta{ + Name: ID, + Namespace: "", // update should allow the client to send an empty namespace + }, + Other: "bar", + } + simpleStorage := SimpleRESTStorage{item: *item} + storage["simple"] = &simpleStorage + selfLinker := &setTestSelfLinker{ + t: t, + expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + ID, + name: ID, + namespace: api.NamespaceDefault, + } + handler := handleLinker(storage, selfLinker) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + client := http.Client{} + request, err := http.NewRequest("PATCH", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader([]byte(`{"labels":{"foo":"bar"}}`))) + request.Header.Set("Content-Type", "application/merge-patch+json; charset=UTF-8") + _, err = client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if simpleStorage.updated == nil || simpleStorage.updated.Labels["foo"] != "bar" { + t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item) + } + if !selfLinker.called { + t.Errorf("Never set self link") + } +} + +func TestPatchRequiresMatchingName(t *testing.T) { + storage := map[string]rest.Storage{} + ID := "id" + item := &apiservertesting.Simple{ + ObjectMeta: api.ObjectMeta{ + Name: ID, + Namespace: "", // update should allow the client to send an empty namespace + }, + Other: "bar", + } + simpleStorage := SimpleRESTStorage{item: *item} + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + client := http.Client{} + request, err := http.NewRequest("PATCH", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader([]byte(`{"metadata":{"name":"idbar"}}`))) + request.Header.Set("Content-Type", "application/merge-patch+json") + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusBadRequest { + t.Errorf("Unexpected response %#v", response) + } +} + +func TestUpdate(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{} + ID := "id" + storage["simple"] = &simpleStorage + selfLinker := &setTestSelfLinker{ + t: t, + expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + ID, + name: ID, + namespace: api.NamespaceDefault, + } + handler := handleLinker(storage, selfLinker) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + item := &apiservertesting.Simple{ + ObjectMeta: api.ObjectMeta{ + Name: ID, + Namespace: "", // update should allow the client to send an empty namespace + }, + Other: "bar", + } + body, err := runtime.Encode(testCodec, item) + if err != nil { + // The following cases will fail, so die now + t.Fatalf("unexpected error: %v", err) + } + + client := http.Client{} + request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) + _, err = client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if simpleStorage.updated == nil || simpleStorage.updated.Name != item.Name { + t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item) + } + if !selfLinker.called { + t.Errorf("Never set self link") + } +} + +func TestUpdateInvokesAdmissionControl(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{} + ID := "id" + storage["simple"] = &simpleStorage + handler := handleDeny(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + item := &apiservertesting.Simple{ + ObjectMeta: api.ObjectMeta{ + Name: ID, + Namespace: api.NamespaceDefault, + }, + Other: "bar", + } + body, err := runtime.Encode(testCodec, item) + if err != nil { + // The following cases will fail, so die now + t.Fatalf("unexpected error: %v", err) + } + + client := http.Client{} + request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusForbidden { + t.Errorf("Unexpected response %#v", response) + } +} + +func TestUpdateRequiresMatchingName(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{} + ID := "id" + storage["simple"] = &simpleStorage + handler := handleDeny(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + item := &apiservertesting.Simple{ + Other: "bar", + } + body, err := runtime.Encode(testCodec, item) + if err != nil { + // The following cases will fail, so die now + t.Fatalf("unexpected error: %v", err) + } + + client := http.Client{} + request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusBadRequest { + t.Errorf("Unexpected response %#v", response) + } +} + +func TestUpdateAllowsMissingNamespace(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{} + ID := "id" + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + item := &apiservertesting.Simple{ + ObjectMeta: api.ObjectMeta{ + Name: ID, + }, + Other: "bar", + } + body, err := runtime.Encode(testCodec, item) + if err != nil { + // The following cases will fail, so die now + t.Fatalf("unexpected error: %v", err) + } + + client := http.Client{} + request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusOK { + t.Errorf("Unexpected response %#v", response) + } +} + +// when the object name and namespace can't be retrieved, skip name checking +func TestUpdateAllowsMismatchedNamespaceOnError(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{} + ID := "id" + storage["simple"] = &simpleStorage + selfLinker := &setTestSelfLinker{ + t: t, + err: fmt.Errorf("test error"), + } + handler := handleLinker(storage, selfLinker) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + item := &apiservertesting.Simple{ + ObjectMeta: api.ObjectMeta{ + Name: ID, + Namespace: "other", // does not match request + }, + Other: "bar", + } + body, err := runtime.Encode(testCodec, item) + if err != nil { + // The following cases will fail, so die now + t.Fatalf("unexpected error: %v", err) + } + + client := http.Client{} + request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) + _, err = client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if simpleStorage.updated == nil || simpleStorage.updated.Name != item.Name { + t.Errorf("Unexpected update value %#v, expected %#v.", simpleStorage.updated, item) + } + if selfLinker.called { + t.Errorf("self link ignored") + } +} + +func TestUpdatePreventsMismatchedNamespace(t *testing.T) { + storage := map[string]rest.Storage{} + simpleStorage := SimpleRESTStorage{} + ID := "id" + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + item := &apiservertesting.Simple{ + ObjectMeta: api.ObjectMeta{ + Name: ID, + Namespace: "other", + }, + Other: "bar", + } + body, err := runtime.Encode(testCodec, item) + if err != nil { + // The following cases will fail, so die now + t.Fatalf("unexpected error: %v", err) + } + + client := http.Client{} + request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusBadRequest { + t.Errorf("Unexpected response %#v", response) + } +} + +func TestUpdateMissing(t *testing.T) { + storage := map[string]rest.Storage{} + ID := "id" + simpleStorage := SimpleRESTStorage{ + errors: map[string]error{"update": apierrs.NewNotFound(api.Resource("simples"), ID)}, + } + storage["simple"] = &simpleStorage + handler := handle(storage) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + item := &apiservertesting.Simple{ + ObjectMeta: api.ObjectMeta{ + Name: ID, + Namespace: api.NamespaceDefault, + }, + Other: "bar", + } + body, err := runtime.Encode(testCodec, item) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + client := http.Client{} + request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+ID, bytes.NewReader(body)) + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusNotFound { + t.Errorf("Unexpected response %#v", response) + } +} + +func TestCreateNotFound(t *testing.T) { + handler := handle(map[string]rest.Storage{ + "simple": &SimpleRESTStorage{ + // storage.Create can fail with not found error in theory. + // See http://pr.k8s.io/486#discussion_r15037092. + errors: map[string]error{"create": apierrs.NewNotFound(api.Resource("simples"), "id")}, + }, + }) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + + simple := &apiservertesting.Simple{Other: "foo"} + data, err := runtime.Encode(testCodec, simple) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data)) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if response.StatusCode != http.StatusNotFound { + t.Errorf("Unexpected response %#v", response) + } +} + +func TestCreateChecksDecode(t *testing.T) { + handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + + simple := &api.Pod{} + data, err := runtime.Encode(codec, simple, testGroupVersion) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data)) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusBadRequest { + t.Errorf("Unexpected response %#v", response) + } + b, err := ioutil.ReadAll(response.Body) + if err != nil { + t.Errorf("unexpected error: %v", err) + } else if !strings.Contains(string(b), "cannot be handled as a Simple") { + t.Errorf("unexpected response: %s", string(b)) + } +} + +// TestUpdateREST tests that you can add new rest implementations to a pre-existing +// web service. +func TestUpdateREST(t *testing.T) { + makeGroup := func(storage map[string]rest.Storage) *APIGroupVersion { + return &APIGroupVersion{ + Storage: storage, + Root: "/" + prefix, + RequestInfoResolver: newTestRequestInfoResolver(), + Creater: api.Scheme, + Convertor: api.Scheme, + Typer: api.Scheme, + Linker: selfLinker, + + Admit: admissionControl, + Context: requestContextMapper, + Mapper: namespaceMapper, + + GroupVersion: newGroupVersion, + OptionsExternalVersion: &newGroupVersion, + + Serializer: api.Codecs, + StreamSerializer: api.StreamCodecs, + ParameterCodec: api.ParameterCodec, + } + } + + makeStorage := func(paths ...string) map[string]rest.Storage { + storage := map[string]rest.Storage{} + for _, s := range paths { + storage[s] = &SimpleRESTStorage{} + } + return storage + } + + testREST := func(t *testing.T, container *restful.Container, barCode int) { + w := httptest.NewRecorder() + container.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/test/foo/test"}}) + if w.Code != http.StatusOK { + t.Fatalf("expected OK: %#v", w) + } + + w = httptest.NewRecorder() + container.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/test/bar/test"}}) + if w.Code != barCode { + t.Errorf("expected response code %d for GET to bar but received %d", barCode, w.Code) + } + } + + storage1 := makeStorage("foo") + group1 := makeGroup(storage1) + + storage2 := makeStorage("bar") + group2 := makeGroup(storage2) + + container := restful.NewContainer() + + // install group1. Ensure that + // 1. Foo storage is accessible + // 2. Bar storage is not accessible + if err := group1.InstallREST(container); err != nil { + t.Fatal(err) + } + testREST(t, container, http.StatusNotFound) + + // update with group2. Ensure that + // 1. Foo storage is still accessible + // 2. Bar storage is now accessible + if err := group2.UpdateREST(container); err != nil { + t.Fatal(err) + } + testREST(t, container, http.StatusOK) + + // try to update a group that does not have an existing webservice with a matching prefix + // should not affect the existing registered webservice + invalidGroup := makeGroup(storage1) + invalidGroup.Root = "bad" + if err := invalidGroup.UpdateREST(container); err == nil { + t.Fatal("expected an error from UpdateREST when updating a non-existing prefix but got none") + } + testREST(t, container, http.StatusOK) +} + +func TestParentResourceIsRequired(t *testing.T) { + storage := &SimpleTypedStorage{ + baseType: &apiservertesting.SimpleRoot{}, // a root scoped type + item: &apiservertesting.SimpleRoot{}, + } + group := &APIGroupVersion{ + Storage: map[string]rest.Storage{ + "simple/sub": storage, + }, + Root: "/" + prefix, + RequestInfoResolver: newTestRequestInfoResolver(), + Creater: api.Scheme, + Convertor: api.Scheme, + Typer: api.Scheme, + Linker: selfLinker, + + Admit: admissionControl, + Context: requestContextMapper, + Mapper: namespaceMapper, + + GroupVersion: newGroupVersion, + OptionsExternalVersion: &newGroupVersion, + + Serializer: api.Codecs, + StreamSerializer: api.StreamCodecs, + ParameterCodec: api.ParameterCodec, + } + container := restful.NewContainer() + if err := group.InstallREST(container); err == nil { + t.Fatal("expected error") + } + + storage = &SimpleTypedStorage{ + baseType: &apiservertesting.SimpleRoot{}, // a root scoped type + item: &apiservertesting.SimpleRoot{}, + } + group = &APIGroupVersion{ + Storage: map[string]rest.Storage{ + "simple": &SimpleRESTStorage{}, + "simple/sub": storage, + }, + Root: "/" + prefix, + RequestInfoResolver: newTestRequestInfoResolver(), + Creater: api.Scheme, + Convertor: api.Scheme, + Typer: api.Scheme, + Linker: selfLinker, + + Admit: admissionControl, + Context: requestContextMapper, + Mapper: namespaceMapper, + + GroupVersion: newGroupVersion, + OptionsExternalVersion: &newGroupVersion, + + Serializer: api.Codecs, + StreamSerializer: api.StreamCodecs, + ParameterCodec: api.ParameterCodec, + } + container = restful.NewContainer() + if err := group.InstallREST(container); err != nil { + t.Fatal(err) + } + + // resource is NOT registered in the root scope + w := httptest.NewRecorder() + container.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/simple/test/sub"}}) + if w.Code != http.StatusNotFound { + t.Errorf("expected not found: %#v", w) + } + + // resource is registered in the namespace scope + w = httptest.NewRecorder() + container.ServeHTTP(w, &http.Request{Method: "GET", URL: &url.URL{Path: "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/test/simple/test/sub"}}) + if w.Code != http.StatusOK { + t.Fatalf("expected OK: %#v", w) + } + if storage.actualNamespace != "test" { + t.Errorf("namespace should be set %#v", storage) + } +} + +func TestCreateWithName(t *testing.T) { + pathName := "helloworld" + storage := &NamedCreaterRESTStorage{SimpleRESTStorage: &SimpleRESTStorage{}} + handler := handle(map[string]rest.Storage{ + "simple": &SimpleRESTStorage{}, + "simple/sub": storage, + }) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + + simple := &apiservertesting.Simple{Other: "foo"} + data, err := runtime.Encode(testCodec, simple) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/"+pathName+"/sub", bytes.NewBuffer(data)) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusCreated { + t.Errorf("Unexpected response %#v", response) + } + if storage.createdName != pathName { + t.Errorf("Did not get expected name in create context. Got: %s, Expected: %s", storage.createdName, pathName) + } +} + +func TestUpdateChecksDecode(t *testing.T) { + handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + + simple := &api.Pod{} + data, err := runtime.Encode(codec, simple, testGroupVersion) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/bar", bytes.NewBuffer(data)) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusBadRequest { + t.Errorf("Unexpected response %#v\n%s", response, readBodyOrDie(response.Body)) + } + b, err := ioutil.ReadAll(response.Body) + if err != nil { + t.Errorf("unexpected error: %v", err) + } else if !strings.Contains(string(b), "cannot be handled as a Simple") { + t.Errorf("unexpected response: %s", string(b)) + } +} + +func TestParseTimeout(t *testing.T) { + if d := parseTimeout(""); d != 30*time.Second { + t.Errorf("blank timeout produces %v", d) + } + if d := parseTimeout("not a timeout"); d != 30*time.Second { + t.Errorf("bad timeout produces %v", d) + } + if d := parseTimeout("10s"); d != 10*time.Second { + t.Errorf("10s timeout produced: %v", d) + } +} + +type setTestSelfLinker struct { + t *testing.T + expectedSet string + name string + namespace string + called bool + err error +} + +func (s *setTestSelfLinker) Namespace(runtime.Object) (string, error) { return s.namespace, s.err } +func (s *setTestSelfLinker) Name(runtime.Object) (string, error) { return s.name, s.err } +func (s *setTestSelfLinker) SelfLink(runtime.Object) (string, error) { return "", s.err } +func (s *setTestSelfLinker) SetSelfLink(obj runtime.Object, selfLink string) error { + if e, a := s.expectedSet, selfLink; e != a { + s.t.Errorf("expected '%v', got '%v'", e, a) + } + s.called = true + return s.err +} + +func TestCreate(t *testing.T) { + storage := SimpleRESTStorage{ + injectedFunction: func(obj runtime.Object) (runtime.Object, error) { + time.Sleep(5 * time.Millisecond) + return obj, nil + }, + } + selfLinker := &setTestSelfLinker{ + t: t, + name: "bar", + namespace: "default", + expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo/bar", + } + handler := handleLinker(map[string]rest.Storage{"foo": &storage}, selfLinker) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + + simple := &apiservertesting.Simple{ + Other: "bar", + } + data, err := runtime.Encode(testCodec, simple) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo", bytes.NewBuffer(data)) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + wg := sync.WaitGroup{} + wg.Add(1) + var response *http.Response + go func() { + response, err = client.Do(request) + wg.Done() + }() + wg.Wait() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + var itemOut apiservertesting.Simple + body, err := extractBody(response, &itemOut) + if err != nil { + t.Errorf("unexpected error: %v %#v", err, response) + } + + if !reflect.DeepEqual(&itemOut, simple) { + t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body)) + } + if response.StatusCode != http.StatusCreated { + t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response) + } + if !selfLinker.called { + t.Errorf("Never set self link") + } +} + +func TestCreateYAML(t *testing.T) { + storage := SimpleRESTStorage{ + injectedFunction: func(obj runtime.Object) (runtime.Object, error) { + time.Sleep(5 * time.Millisecond) + return obj, nil + }, + } + selfLinker := &setTestSelfLinker{ + t: t, + name: "bar", + namespace: "default", + expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/foo/bar", + } + handler := handleLinker(map[string]rest.Storage{"foo": &storage}, selfLinker) + server := httptest.NewServer(handler) + defer server.Close() + client := http.Client{} + + // yaml encoder + simple := &apiservertesting.Simple{ + Other: "bar", + } + serializer, ok := api.Codecs.SerializerForMediaType("application/yaml", nil) + if !ok { + t.Fatal("No yaml serializer") + } + encoder := api.Codecs.EncoderForVersion(serializer, testGroupVersion) + decoder := api.Codecs.DecoderToVersion(serializer, testInternalGroupVersion) + + data, err := runtime.Encode(encoder, simple) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo", bytes.NewBuffer(data)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + request.Header.Set("Accept", "application/yaml, application/json") + request.Header.Set("Content-Type", "application/yaml") + + wg := sync.WaitGroup{} + wg.Add(1) + var response *http.Response + go func() { + response, err = client.Do(request) + wg.Done() + }() + wg.Wait() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + var itemOut apiservertesting.Simple + body, err := extractBodyDecoder(response, &itemOut, decoder) + if err != nil { + t.Fatalf("unexpected error: %v %#v", err, response) + } + + if !reflect.DeepEqual(&itemOut, simple) { + t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body)) + } + if response.StatusCode != http.StatusCreated { + t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response) + } + if !selfLinker.called { + t.Errorf("Never set self link") + } +} +func TestCreateInNamespace(t *testing.T) { + storage := SimpleRESTStorage{ + injectedFunction: func(obj runtime.Object) (runtime.Object, error) { + time.Sleep(5 * time.Millisecond) + return obj, nil + }, + } + selfLinker := &setTestSelfLinker{ + t: t, + name: "bar", + namespace: "other", + expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/foo/bar", + } + handler := handleLinker(map[string]rest.Storage{"foo": &storage}, selfLinker) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + + simple := &apiservertesting.Simple{ + Other: "bar", + } + data, err := runtime.Encode(testCodec, simple) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/foo", bytes.NewBuffer(data)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + wg := sync.WaitGroup{} + wg.Add(1) + var response *http.Response + go func() { + response, err = client.Do(request) + wg.Done() + }() + wg.Wait() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + var itemOut apiservertesting.Simple + body, err := extractBody(response, &itemOut) + if err != nil { + t.Fatalf("unexpected error: %v\n%s", err, data) + } + + if !reflect.DeepEqual(&itemOut, simple) { + t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, simple, string(body)) + } + if response.StatusCode != http.StatusCreated { + t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusOK, response) + } + if !selfLinker.called { + t.Errorf("Never set self link") + } +} + +func TestCreateInvokesAdmissionControl(t *testing.T) { + storage := SimpleRESTStorage{ + injectedFunction: func(obj runtime.Object) (runtime.Object, error) { + time.Sleep(5 * time.Millisecond) + return obj, nil + }, + } + selfLinker := &setTestSelfLinker{ + t: t, + name: "bar", + namespace: "other", + expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/other/foo/bar", + } + handler := handleInternal(map[string]rest.Storage{"foo": &storage}, deny.NewAlwaysDeny(), selfLinker) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + + simple := &apiservertesting.Simple{ + Other: "bar", + } + data, err := runtime.Encode(testCodec, simple) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/other/foo", bytes.NewBuffer(data)) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + wg := sync.WaitGroup{} + wg.Add(1) + var response *http.Response + go func() { + response, err = client.Do(request) + wg.Done() + }() + wg.Wait() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusForbidden { + t.Errorf("Unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusForbidden, response) + } +} + +func expectApiStatus(t *testing.T, method, url string, data []byte, code int) *unversioned.Status { + client := http.Client{} + request, err := http.NewRequest(method, url, bytes.NewBuffer(data)) + if err != nil { + t.Fatalf("unexpected error %#v", err) + return nil + } + response, err := client.Do(request) + if err != nil { + t.Fatalf("unexpected error on %s %s: %v", method, url, err) + return nil + } + var status unversioned.Status + if body, err := extractBody(response, &status); err != nil { + t.Fatalf("unexpected error on %s %s: %v\nbody:\n%s", method, url, err, body) + return nil + } + if code != response.StatusCode { + t.Fatalf("Expected %s %s to return %d, Got %d", method, url, code, response.StatusCode) + } + return &status +} + +func TestDelayReturnsError(t *testing.T) { + storage := SimpleRESTStorage{ + injectedFunction: func(obj runtime.Object) (runtime.Object, error) { + return nil, apierrs.NewAlreadyExists(api.Resource("foos"), "bar") + }, + } + handler := handle(map[string]rest.Storage{"foo": &storage}) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + status := expectApiStatus(t, "DELETE", fmt.Sprintf("%s/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo/bar", server.URL), nil, http.StatusConflict) + if status.Status != unversioned.StatusFailure || status.Message == "" || status.Details == nil || status.Reason != unversioned.StatusReasonAlreadyExists { + t.Errorf("Unexpected status %#v", status) + } +} + +type UnregisteredAPIObject struct { + Value string +} + +func (obj *UnregisteredAPIObject) GetObjectKind() unversioned.ObjectKind { + return unversioned.EmptyObjectKind +} + +func TestWriteJSONDecodeError(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + writeNegotiated(api.Codecs, newGroupVersion, w, req, http.StatusOK, &UnregisteredAPIObject{"Undecodable"}) + })) + // TODO: Uncomment when fix #19254 + // defer server.Close() + // We send a 200 status code before we encode the object, so we expect OK, but there will + // still be an error object. This seems ok, the alternative is to validate the object before + // encoding, but this really should never happen, so it's wasted compute for every API request. + status := expectApiStatus(t, "GET", server.URL, nil, http.StatusOK) + if status.Reason != unversioned.StatusReasonUnknown { + t.Errorf("unexpected reason %#v", status) + } + if !strings.Contains(status.Message, "no kind is registered for the type apiserver.UnregisteredAPIObject") { + t.Errorf("unexpected message %#v", status) + } +} + +type marshalError struct { + err error +} + +func (m *marshalError) MarshalJSON() ([]byte, error) { + return []byte{}, m.err +} + +func TestWriteRAWJSONMarshalError(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + writeRawJSON(http.StatusOK, &marshalError{errors.New("Undecodable")}, w) + })) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + resp, err := client.Get(server.URL) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if resp.StatusCode != http.StatusInternalServerError { + t.Errorf("unexpected status code %d", resp.StatusCode) + } +} + +func TestCreateTimeout(t *testing.T) { + testOver := make(chan struct{}) + defer close(testOver) + storage := SimpleRESTStorage{ + injectedFunction: func(obj runtime.Object) (runtime.Object, error) { + // Eliminate flakes by ensuring the create operation takes longer than this test. + <-testOver + return obj, nil + }, + } + handler := handle(map[string]rest.Storage{ + "foo": &storage, + }) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + simple := &apiservertesting.Simple{Other: "foo"} + data, err := runtime.Encode(testCodec, simple) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + itemOut := expectApiStatus(t, "POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/foo?timeout=4ms", data, apierrs.StatusServerTimeout) + if itemOut.Status != unversioned.StatusFailure || itemOut.Reason != unversioned.StatusReasonTimeout { + t.Errorf("Unexpected status %#v", itemOut) + } +} + +func TestCORSAllowedOrigins(t *testing.T) { + table := []struct { + allowedOrigins []string + origin string + allowed bool + }{ + {[]string{}, "example.com", false}, + {[]string{"example.com"}, "example.com", true}, + {[]string{"example.com"}, "not-allowed.com", false}, + {[]string{"not-matching.com", "example.com"}, "example.com", true}, + {[]string{".*"}, "example.com", true}, + } + + for _, item := range table { + allowedOriginRegexps, err := util.CompileRegexps(item.allowedOrigins) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + handler := CORS( + handle(map[string]rest.Storage{}), + allowedOriginRegexps, nil, nil, "true", + ) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + + request, err := http.NewRequest("GET", server.URL+"/version", nil) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + request.Header.Set("Origin", item.origin) + + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if item.allowed { + if !reflect.DeepEqual(item.origin, response.Header.Get("Access-Control-Allow-Origin")) { + t.Errorf("Expected %#v, Got %#v", item.origin, response.Header.Get("Access-Control-Allow-Origin")) + } + + if response.Header.Get("Access-Control-Allow-Credentials") == "" { + t.Errorf("Expected Access-Control-Allow-Credentials header to be set") + } + + if response.Header.Get("Access-Control-Allow-Headers") == "" { + t.Errorf("Expected Access-Control-Allow-Headers header to be set") + } + + if response.Header.Get("Access-Control-Allow-Methods") == "" { + t.Errorf("Expected Access-Control-Allow-Methods header to be set") + } + } else { + if response.Header.Get("Access-Control-Allow-Origin") != "" { + t.Errorf("Expected Access-Control-Allow-Origin header to not be set") + } + + if response.Header.Get("Access-Control-Allow-Credentials") != "" { + t.Errorf("Expected Access-Control-Allow-Credentials header to not be set") + } + + if response.Header.Get("Access-Control-Allow-Headers") != "" { + t.Errorf("Expected Access-Control-Allow-Headers header to not be set") + } + + if response.Header.Get("Access-Control-Allow-Methods") != "" { + t.Errorf("Expected Access-Control-Allow-Methods header to not be set") + } + } + } +} + +func TestCreateChecksAPIVersion(t *testing.T) { + handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + + simple := &apiservertesting.Simple{} + //using newCodec and send the request to testVersion URL shall cause a discrepancy in apiVersion + data, err := runtime.Encode(newCodec, simple) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data)) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusBadRequest { + t.Errorf("Unexpected response %#v", response) + } + b, err := ioutil.ReadAll(response.Body) + if err != nil { + t.Errorf("unexpected error: %v", err) + } else if !strings.Contains(string(b), "does not match the expected API version") { + t.Errorf("unexpected response: %s", string(b)) + } +} + +func TestCreateDefaultsAPIVersion(t *testing.T) { + handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + + simple := &apiservertesting.Simple{} + data, err := runtime.Encode(codec, simple) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + m := make(map[string]interface{}) + if err := json.Unmarshal(data, &m); err != nil { + t.Errorf("unexpected error: %v", err) + } + delete(m, "apiVersion") + data, err = json.Marshal(m) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + request, err := http.NewRequest("POST", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple", bytes.NewBuffer(data)) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusCreated { + t.Errorf("unexpected status: %d, Expected: %d, %#v", response.StatusCode, http.StatusCreated, response) + } +} + +func TestUpdateChecksAPIVersion(t *testing.T) { + handler := handle(map[string]rest.Storage{"simple": &SimpleRESTStorage{}}) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + + simple := &apiservertesting.Simple{ObjectMeta: api.ObjectMeta{Name: "bar"}} + data, err := runtime.Encode(newCodec, simple) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + request, err := http.NewRequest("PUT", server.URL+"/"+prefix+"/"+testGroupVersion.Group+"/"+testGroupVersion.Version+"/namespaces/default/simple/bar", bytes.NewBuffer(data)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + response, err := client.Do(request) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusBadRequest { + t.Errorf("Unexpected response %#v", response) + } + b, err := ioutil.ReadAll(response.Body) + if err != nil { + t.Errorf("unexpected error: %v", err) + } else if !strings.Contains(string(b), "does not match the expected API version") { + t.Errorf("unexpected response: %s", string(b)) + } +} + +// SimpleXGSubresource is a cross group subresource, i.e. the subresource does not belong to the +// same group as its parent resource. +type SimpleXGSubresource struct { + unversioned.TypeMeta `json:",inline"` + api.ObjectMeta `json:"metadata"` + SubresourceInfo string `json:"subresourceInfo,omitempty"` + Labels map[string]string `json:"labels,omitempty"` +} + +func (obj *SimpleXGSubresource) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } + +type SimpleXGSubresourceRESTStorage struct { + item SimpleXGSubresource +} + +func (storage *SimpleXGSubresourceRESTStorage) New() runtime.Object { + return &SimpleXGSubresource{} +} + +func (storage *SimpleXGSubresourceRESTStorage) Get(ctx api.Context, id string) (runtime.Object, error) { + copied, err := api.Scheme.Copy(&storage.item) + if err != nil { + panic(err) + } + return copied, nil +} + +func TestXGSubresource(t *testing.T) { + container := restful.NewContainer() + container.Router(restful.CurlyRouter{}) + mux := container.ServeMux + + itemID := "theID" + subresourceStorage := &SimpleXGSubresourceRESTStorage{ + item: SimpleXGSubresource{ + SubresourceInfo: "foo", + }, + } + storage := map[string]rest.Storage{ + "simple": &SimpleRESTStorage{}, + "simple/subsimple": subresourceStorage, + } + + group := APIGroupVersion{ + Storage: storage, + + RequestInfoResolver: newTestRequestInfoResolver(), + + Creater: api.Scheme, + Convertor: api.Scheme, + Typer: api.Scheme, + Linker: selfLinker, + Mapper: namespaceMapper, + + ParameterCodec: api.ParameterCodec, + + Admit: admissionControl, + Context: requestContextMapper, + + Root: "/" + prefix, + GroupVersion: testGroupVersion, + OptionsExternalVersion: &testGroupVersion, + Serializer: api.Codecs, + StreamSerializer: api.StreamCodecs, + + SubresourceGroupVersionKind: map[string]unversioned.GroupVersionKind{ + "simple/subsimple": testGroup2Version.WithKind("SimpleXGSubresource"), + }, + } + + if err := (&group).InstallREST(container); err != nil { + panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) + } + + ws := new(restful.WebService) + InstallSupport(mux, ws) + container.Add(ws) + + handler := defaultAPIServer{mux, container} + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/subsimple") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatalf("unexpected response: %#v", resp) + } + var itemOut SimpleXGSubresource + body, err := extractBody(resp, &itemOut) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Test if the returned object has the expected group, version and kind + // We are directly unmarshaling JSON here because TypeMeta cannot be decoded through the + // installed decoders. TypeMeta cannot be decoded because it is added to the ignored + // conversion type list in API scheme and hence cannot be converted from input type object + // to output type object. So it's values don't appear in the decoded output object. + decoder := json.NewDecoder(strings.NewReader(body)) + var itemFromBody SimpleXGSubresource + err = decoder.Decode(&itemFromBody) + if err != nil { + t.Errorf("unexpected JSON decoding error: %v", err) + } + if want := fmt.Sprintf("%s/%s", testGroup2Version.Group, testGroup2Version.Version); itemFromBody.APIVersion != want { + t.Errorf("unexpected APIVersion got: %+v want: %+v", itemFromBody.APIVersion, want) + } + if itemFromBody.Kind != "SimpleXGSubresource" { + t.Errorf("unexpected Kind got: %+v want: SimpleXGSubresource", itemFromBody.Kind) + } + + if itemOut.Name != subresourceStorage.item.Name { + t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, subresourceStorage.item, string(body)) + } +} + +func readBodyOrDie(r io.Reader) []byte { + body, err := ioutil.ReadAll(r) + if err != nil { + panic(err) + } + return body +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/authenticator/authn.go b/vendor/k8s.io/kubernetes/pkg/apiserver/authenticator/authn.go new file mode 100644 index 000000000..a2d1a7167 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/authenticator/authn.go @@ -0,0 +1,189 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 authenticator + +import ( + "crypto/rsa" + + "k8s.io/kubernetes/pkg/auth/authenticator" + "k8s.io/kubernetes/pkg/auth/authenticator/bearertoken" + "k8s.io/kubernetes/pkg/serviceaccount" + "k8s.io/kubernetes/pkg/util/crypto" + "k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/keystone" + "k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile" + "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth" + "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/union" + "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509" + "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/oidc" + "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/tokenfile" +) + +type AuthenticatorConfig struct { + BasicAuthFile string + ClientCAFile string + TokenAuthFile string + OIDCIssuerURL string + OIDCClientID string + OIDCCAFile string + OIDCUsernameClaim string + OIDCGroupsClaim string + ServiceAccountKeyFile string + ServiceAccountLookup bool + ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter + KeystoneURL string +} + +// New returns an authenticator.Request or an error that supports the standard +// Kubernetes authentication mechanisms. +func New(config AuthenticatorConfig) (authenticator.Request, error) { + var authenticators []authenticator.Request + + if len(config.BasicAuthFile) > 0 { + basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile) + if err != nil { + return nil, err + } + authenticators = append(authenticators, basicAuth) + } + + if len(config.ClientCAFile) > 0 { + certAuth, err := newAuthenticatorFromClientCAFile(config.ClientCAFile) + if err != nil { + return nil, err + } + authenticators = append(authenticators, certAuth) + } + + if len(config.TokenAuthFile) > 0 { + tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile) + if err != nil { + return nil, err + } + authenticators = append(authenticators, tokenAuth) + } + + if len(config.OIDCIssuerURL) > 0 && len(config.OIDCClientID) > 0 { + oidcAuth, err := newAuthenticatorFromOIDCIssuerURL(config.OIDCIssuerURL, config.OIDCClientID, config.OIDCCAFile, config.OIDCUsernameClaim, config.OIDCGroupsClaim) + if err != nil { + return nil, err + } + authenticators = append(authenticators, oidcAuth) + } + + if len(config.ServiceAccountKeyFile) > 0 { + serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountKeyFile, config.ServiceAccountLookup, config.ServiceAccountTokenGetter) + if err != nil { + return nil, err + } + authenticators = append(authenticators, serviceAccountAuth) + } + + if len(config.KeystoneURL) > 0 { + keystoneAuth, err := newAuthenticatorFromKeystoneURL(config.KeystoneURL) + if err != nil { + return nil, err + } + authenticators = append(authenticators, keystoneAuth) + } + + switch len(authenticators) { + case 0: + return nil, nil + case 1: + return authenticators[0], nil + default: + return union.New(authenticators...), nil + } +} + +// IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file +func IsValidServiceAccountKeyFile(file string) bool { + _, err := serviceaccount.ReadPublicKey(file) + return err == nil +} + +// newAuthenticatorFromBasicAuthFile returns an authenticator.Request or an error +func newAuthenticatorFromBasicAuthFile(basicAuthFile string) (authenticator.Request, error) { + basicAuthenticator, err := passwordfile.NewCSV(basicAuthFile) + if err != nil { + return nil, err + } + + return basicauth.New(basicAuthenticator), nil +} + +// newAuthenticatorFromTokenFile returns an authenticator.Request or an error +func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Request, error) { + tokenAuthenticator, err := tokenfile.NewCSV(tokenAuthFile) + if err != nil { + return nil, err + } + + return bearertoken.New(tokenAuthenticator), nil +} + +// newAuthenticatorFromOIDCIssuerURL returns an authenticator.Request or an error. +func newAuthenticatorFromOIDCIssuerURL(issuerURL, clientID, caFile, usernameClaim, groupsClaim string) (authenticator.Request, error) { + tokenAuthenticator, err := oidc.New(oidc.OIDCOptions{ + IssuerURL: issuerURL, + ClientID: clientID, + CAFile: caFile, + UsernameClaim: usernameClaim, + GroupsClaim: groupsClaim, + MaxRetries: oidc.DefaultRetries, + RetryBackoff: oidc.DefaultBackoff, + }) + if err != nil { + return nil, err + } + + return bearertoken.New(tokenAuthenticator), nil +} + +// newServiceAccountAuthenticator returns an authenticator.Request or an error +func newServiceAccountAuthenticator(keyfile string, lookup bool, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Request, error) { + publicKey, err := serviceaccount.ReadPublicKey(keyfile) + if err != nil { + return nil, err + } + + tokenAuthenticator := serviceaccount.JWTTokenAuthenticator([]*rsa.PublicKey{publicKey}, lookup, serviceAccountGetter) + return bearertoken.New(tokenAuthenticator), nil +} + +// newAuthenticatorFromClientCAFile returns an authenticator.Request or an error +func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Request, error) { + roots, err := crypto.CertPoolFromFile(clientCAFile) + if err != nil { + return nil, err + } + + opts := x509.DefaultVerifyOptions() + opts.Roots = roots + + return x509.New(opts, x509.CommonNameUserConversion), nil +} + +// newAuthenticatorFromTokenFile returns an authenticator.Request or an error +func newAuthenticatorFromKeystoneURL(keystoneConfigFile string) (authenticator.Request, error) { + keystoneAuthenticator, err := keystone.NewKeystoneAuthenticator(keystoneConfigFile) + if err != nil { + return nil, err + } + + return basicauth.New(keystoneAuthenticator), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/authz.go b/vendor/k8s.io/kubernetes/pkg/apiserver/authz.go new file mode 100644 index 000000000..88e8f6284 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/authz.go @@ -0,0 +1,136 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "errors" + "fmt" + + "k8s.io/kubernetes/pkg/auth/authorizer" + "k8s.io/kubernetes/pkg/auth/authorizer/abac" + "k8s.io/kubernetes/pkg/auth/authorizer/union" + "k8s.io/kubernetes/plugin/pkg/auth/authorizer/webhook" +) + +// Attributes implements authorizer.Attributes interface. +type Attributes struct { + // TODO: add fields and methods when authorizer.Attributes is completed. +} + +// alwaysAllowAuthorizer is an implementation of authorizer.Attributes +// which always says yes to an authorization request. +// It is useful in tests and when using kubernetes in an open manner. +type alwaysAllowAuthorizer struct{} + +func (alwaysAllowAuthorizer) Authorize(a authorizer.Attributes) (err error) { + return nil +} + +func NewAlwaysAllowAuthorizer() authorizer.Authorizer { + return new(alwaysAllowAuthorizer) +} + +// alwaysDenyAuthorizer is an implementation of authorizer.Attributes +// which always says no to an authorization request. +// It is useful in unit tests to force an operation to be forbidden. +type alwaysDenyAuthorizer struct{} + +func (alwaysDenyAuthorizer) Authorize(a authorizer.Attributes) (err error) { + return errors.New("Everything is forbidden.") +} + +func NewAlwaysDenyAuthorizer() authorizer.Authorizer { + return new(alwaysDenyAuthorizer) +} + +const ( + ModeAlwaysAllow string = "AlwaysAllow" + ModeAlwaysDeny string = "AlwaysDeny" + ModeABAC string = "ABAC" + ModeWebhook string = "Webhook" +) + +// Keep this list in sync with constant list above. +var AuthorizationModeChoices = []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC, ModeWebhook} + +type AuthorizationConfig struct { + // Options for ModeABAC + + // Path to a ABAC policy file. + PolicyFile string + + // Options for ModeWebhook + + // Kubeconfig file for Webhook authorization plugin. + WebhookConfigFile string +} + +// NewAuthorizerFromAuthorizationConfig returns the right sort of union of multiple authorizer.Authorizer objects +// based on the authorizationMode or an error. authorizationMode should be a comma separated values +// of AuthorizationModeChoices. +func NewAuthorizerFromAuthorizationConfig(authorizationModes []string, config AuthorizationConfig) (authorizer.Authorizer, error) { + + if len(authorizationModes) == 0 { + return nil, errors.New("Atleast one authorization mode should be passed") + } + + var authorizers []authorizer.Authorizer + authorizerMap := make(map[string]bool) + + for _, authorizationMode := range authorizationModes { + if authorizerMap[authorizationMode] { + return nil, fmt.Errorf("Authorization mode %s specified more than once", authorizationMode) + } + // Keep cases in sync with constant list above. + switch authorizationMode { + case ModeAlwaysAllow: + authorizers = append(authorizers, NewAlwaysAllowAuthorizer()) + case ModeAlwaysDeny: + authorizers = append(authorizers, NewAlwaysDenyAuthorizer()) + case ModeABAC: + if config.PolicyFile == "" { + return nil, errors.New("ABAC's authorization policy file not passed") + } + abacAuthorizer, err := abac.NewFromFile(config.PolicyFile) + if err != nil { + return nil, err + } + authorizers = append(authorizers, abacAuthorizer) + case ModeWebhook: + if config.WebhookConfigFile == "" { + return nil, errors.New("Webhook's configuration file not passed") + } + webhookAuthorizer, err := webhook.New(config.WebhookConfigFile) + if err != nil { + return nil, err + } + authorizers = append(authorizers, webhookAuthorizer) + default: + return nil, fmt.Errorf("Unknown authorization mode %s specified", authorizationMode) + } + authorizerMap[authorizationMode] = true + } + + if !authorizerMap[ModeABAC] && config.PolicyFile != "" { + return nil, errors.New("Cannot specify --authorization-policy-file without mode ABAC") + } + if !authorizerMap[ModeWebhook] && config.WebhookConfigFile != "" { + return nil, errors.New("Cannot specify --authorization-webhook-config-file without mode Webhook") + } + + return union.New(authorizers...), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/authz_test.go b/vendor/k8s.io/kubernetes/pkg/apiserver/authz_test.go new file mode 100644 index 000000000..5ea6045a7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/authz_test.go @@ -0,0 +1,115 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "testing" +) + +// NewAlwaysAllowAuthorizer must return a struct which implements authorizer.Authorizer +// and always return nil. +func TestNewAlwaysAllowAuthorizer(t *testing.T) { + aaa := NewAlwaysAllowAuthorizer() + if result := aaa.Authorize(nil); result != nil { + t.Errorf("AlwaysAllowAuthorizer.Authorize did not return nil. (%s)", result) + } +} + +// NewAlwaysDenyAuthorizer must return a struct which implements authorizer.Authorizer +// and always return an error as everything is forbidden. +func TestNewAlwaysDenyAuthorizer(t *testing.T) { + ada := NewAlwaysDenyAuthorizer() + if result := ada.Authorize(nil); result == nil { + t.Errorf("AlwaysDenyAuthorizer.Authorize returned nil instead of error.") + } +} + +// NewAuthorizerFromAuthorizationConfig has multiple return possibilities. This test +// validates that errors are returned only when proper. +func TestNewAuthorizerFromAuthorizationConfig(t *testing.T) { + + examplePolicyFile := "../auth/authorizer/abac/example_policy_file.jsonl" + + tests := []struct { + modes []string + config AuthorizationConfig + wantErr bool + msg string + }{ + { + // Unknown modes should return errors + modes: []string{"DoesNotExist"}, + wantErr: true, + msg: "using a fake mode should have returned an error", + }, + { + // ModeAlwaysAllow and ModeAlwaysDeny should return without authorizationPolicyFile + // but error if one is given + modes: []string{ModeAlwaysAllow, ModeAlwaysDeny}, + msg: "returned an error for valid config", + }, + { + // ModeABAC requires a policy file + modes: []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC}, + wantErr: true, + msg: "specifying ABAC with no policy file should return an error", + }, + { + // ModeABAC should not error if a valid policy path is provided + modes: []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC}, + config: AuthorizationConfig{PolicyFile: examplePolicyFile}, + msg: "errored while using a valid policy file", + }, + { + + // Authorization Policy file cannot be used without ModeABAC + modes: []string{ModeAlwaysAllow, ModeAlwaysDeny}, + config: AuthorizationConfig{PolicyFile: examplePolicyFile}, + wantErr: true, + msg: "should have errored when Authorization Policy File is used without ModeABAC", + }, + { + // Atleast one authorizationMode is necessary + modes: []string{}, + config: AuthorizationConfig{PolicyFile: examplePolicyFile}, + wantErr: true, + msg: "should have errored when no authorization modes are passed", + }, + { + // ModeWebhook requires at minimum a target. + modes: []string{ModeWebhook}, + wantErr: true, + msg: "should have errored when config was empty with ModeWebhook", + }, + { + // Cannot provide webhook flags without ModeWebhook + modes: []string{ModeAlwaysAllow}, + config: AuthorizationConfig{WebhookConfigFile: "authz_webhook_config.yml"}, + wantErr: true, + msg: "should have errored when Webhook config file is used without ModeWebhook", + }, + } + + for _, tt := range tests { + _, err := NewAuthorizerFromAuthorizationConfig(tt.modes, tt.config) + if tt.wantErr && (err == nil) { + t.Errorf("NewAuthorizerFromAuthorizationConfig %s", tt.msg) + } else if !tt.wantErr && (err != nil) { + t.Errorf("NewAuthorizerFromAuthorizationConfig %s: %v", tt.msg, err) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/doc.go b/vendor/k8s.io/kubernetes/pkg/apiserver/doc.go new file mode 100644 index 000000000..7a919d2ca --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver contains the code that provides a rest.ful api service. +package apiserver diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/errors.go b/vendor/k8s.io/kubernetes/pkg/apiserver/errors.go new file mode 100644 index 000000000..4f9d16a26 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/errors.go @@ -0,0 +1,146 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "fmt" + "net/http" + "strings" + + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/storage" + "k8s.io/kubernetes/pkg/util/runtime" +) + +// statusError is an object that can be converted into an unversioned.Status +type statusError interface { + Status() unversioned.Status +} + +// errToAPIStatus converts an error to an unversioned.Status object. +func errToAPIStatus(err error) *unversioned.Status { + switch t := err.(type) { + case statusError: + status := t.Status() + if len(status.Status) == 0 { + status.Status = unversioned.StatusFailure + } + if status.Code == 0 { + switch status.Status { + case unversioned.StatusSuccess: + status.Code = http.StatusOK + case unversioned.StatusFailure: + status.Code = http.StatusInternalServerError + } + } + //TODO: check for invalid responses + return &status + default: + status := http.StatusInternalServerError + switch { + //TODO: replace me with NewConflictErr + case storage.IsTestFailed(err): + status = http.StatusConflict + } + // Log errors that were not converted to an error status + // by REST storage - these typically indicate programmer + // error by not using pkg/api/errors, or unexpected failure + // cases. + runtime.HandleError(fmt.Errorf("apiserver received an error that is not an unversioned.Status: %v", err)) + return &unversioned.Status{ + Status: unversioned.StatusFailure, + Code: int32(status), + Reason: unversioned.StatusReasonUnknown, + Message: err.Error(), + } + } +} + +// notFound renders a simple not found error. +func notFound(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintf(w, "Not Found: %#v", req.RequestURI) +} + +// badGatewayError renders a simple bad gateway error. +func badGatewayError(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusBadGateway) + fmt.Fprintf(w, "Bad Gateway: %#v", req.RequestURI) +} + +// forbidden renders a simple forbidden error +func forbidden(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusForbidden) + fmt.Fprintf(w, "Forbidden: %#v", req.RequestURI) +} + +// errAPIPrefixNotFound indicates that a RequestInfo resolution failed because the request isn't under +// any known API prefixes +type errAPIPrefixNotFound struct { + SpecifiedPrefix string +} + +func (e *errAPIPrefixNotFound) Error() string { + return fmt.Sprintf("no valid API prefix found matching %v", e.SpecifiedPrefix) +} + +func IsAPIPrefixNotFound(err error) bool { + if err == nil { + return false + } + + _, ok := err.(*errAPIPrefixNotFound) + return ok +} + +// errNotAcceptable indicates Accept negotiation has failed +// TODO: move to api/errors if other code needs to return this +type errNotAcceptable struct { + accepted []string +} + +func (e errNotAcceptable) Error() string { + return fmt.Sprintf("only the following media types are accepted: %v", strings.Join(e.accepted, ", ")) +} + +func (e errNotAcceptable) Status() unversioned.Status { + return unversioned.Status{ + Status: unversioned.StatusFailure, + Code: http.StatusNotAcceptable, + Reason: unversioned.StatusReason("NotAcceptable"), + Message: e.Error(), + } +} + +// errNotAcceptable indicates Content-Type is not recognized +// TODO: move to api/errors if other code needs to return this +type errUnsupportedMediaType struct { + accepted []string +} + +func (e errUnsupportedMediaType) Error() string { + return fmt.Sprintf("the body of the request was in an unknown format - accepted media types include: %v", strings.Join(e.accepted, ", ")) +} + +func (e errUnsupportedMediaType) Status() unversioned.Status { + return unversioned.Status{ + Status: unversioned.StatusFailure, + Code: http.StatusUnsupportedMediaType, + Reason: unversioned.StatusReason("UnsupportedMediaType"), + Message: e.Error(), + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/errors_test.go b/vendor/k8s.io/kubernetes/pkg/apiserver/errors_test.go new file mode 100644 index 000000000..0b83f4122 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/errors_test.go @@ -0,0 +1,72 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + stderrs "errors" + "net/http" + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/unversioned" +) + +func TestErrorsToAPIStatus(t *testing.T) { + cases := map[error]unversioned.Status{ + errors.NewNotFound(unversioned.GroupResource{Group: "legacy.kubernetes.io", Resource: "foos"}, "bar"): { + Status: unversioned.StatusFailure, + Code: http.StatusNotFound, + Reason: unversioned.StatusReasonNotFound, + Message: "foos.legacy.kubernetes.io \"bar\" not found", + Details: &unversioned.StatusDetails{ + Group: "legacy.kubernetes.io", + Kind: "foos", + Name: "bar", + }, + }, + errors.NewAlreadyExists(api.Resource("foos"), "bar"): { + Status: unversioned.StatusFailure, + Code: http.StatusConflict, + Reason: "AlreadyExists", + Message: "foos \"bar\" already exists", + Details: &unversioned.StatusDetails{ + Group: "", + Kind: "foos", + Name: "bar", + }, + }, + errors.NewConflict(api.Resource("foos"), "bar", stderrs.New("failure")): { + Status: unversioned.StatusFailure, + Code: http.StatusConflict, + Reason: "Conflict", + Message: "Operation cannot be fulfilled on foos \"bar\": failure", + Details: &unversioned.StatusDetails{ + Group: "", + Kind: "foos", + Name: "bar", + }, + }, + } + for k, v := range cases { + actual := errToAPIStatus(k) + if !reflect.DeepEqual(actual, &v) { + t.Errorf("%s: Expected %#v, Got %#v", k, v, actual) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/handlers.go b/vendor/k8s.io/kubernetes/pkg/apiserver/handlers.go new file mode 100644 index 000000000..2bfd7a416 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/handlers.go @@ -0,0 +1,587 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "bufio" + "encoding/json" + "fmt" + "net" + "net/http" + "regexp" + "runtime/debug" + "strings" + "sync" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/auth/authorizer" + "k8s.io/kubernetes/pkg/httplog" + "k8s.io/kubernetes/pkg/util/sets" +) + +// specialVerbs contains just strings which are used in REST paths for special actions that don't fall under the normal +// CRUDdy GET/POST/PUT/DELETE actions on REST objects. +// TODO: find a way to keep this up to date automatically. Maybe dynamically populate list as handlers added to +// master's Mux. +var specialVerbs = sets.NewString("proxy", "redirect", "watch") + +// specialVerbsNoSubresources contains root verbs which do not allow subresources +var specialVerbsNoSubresources = sets.NewString("proxy", "redirect") + +// namespaceSubresources contains subresources of namespace +// this list allows the parser to distinguish between a namespace subresource, and a namespaced resource +var namespaceSubresources = sets.NewString("status", "finalize") + +// NamespaceSubResourcesForTest exports namespaceSubresources for testing in pkg/master/master_test.go, so we never drift +var NamespaceSubResourcesForTest = sets.NewString(namespaceSubresources.List()...) + +// Constant for the retry-after interval on rate limiting. +// TODO: maybe make this dynamic? or user-adjustable? +const RetryAfter = "1" + +// IsReadOnlyReq() is true for any (or at least many) request which has no observable +// side effects on state of apiserver (though there may be internal side effects like +// caching and logging). +func IsReadOnlyReq(req http.Request) bool { + if req.Method == "GET" { + // TODO: add OPTIONS and HEAD if we ever support those. + return true + } + return false +} + +// ReadOnly passes all GET requests on to handler, and returns an error on all other requests. +func ReadOnly(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if IsReadOnlyReq(*req) { + handler.ServeHTTP(w, req) + return + } + w.WriteHeader(http.StatusForbidden) + fmt.Fprintf(w, "This is a read-only endpoint.") + }) +} + +type LongRunningRequestCheck func(r *http.Request) bool + +// BasicLongRunningRequestCheck pathRegex operates against the url path, the queryParams match is case insensitive. +// Any one match flags the request. +// TODO tighten this check to eliminate the abuse potential by malicious clients that start setting queryParameters +// to bypass the rate limitter. This could be done using a full parse and special casing the bits we need. +func BasicLongRunningRequestCheck(pathRegex *regexp.Regexp, queryParams map[string]string) LongRunningRequestCheck { + return func(r *http.Request) bool { + if pathRegex.MatchString(r.URL.Path) { + return true + } + + for key, expectedValue := range queryParams { + if strings.ToLower(expectedValue) == strings.ToLower(r.URL.Query().Get(key)) { + return true + } + } + + return false + } +} + +// MaxInFlight limits the number of in-flight requests to buffer size of the passed in channel. +func MaxInFlightLimit(c chan bool, longRunningRequestCheck LongRunningRequestCheck, handler http.Handler) http.Handler { + if c == nil { + return handler + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if longRunningRequestCheck(r) { + // Skip tracking long running events. + handler.ServeHTTP(w, r) + return + } + select { + case c <- true: + defer func() { <-c }() + handler.ServeHTTP(w, r) + default: + tooManyRequests(w) + } + }) +} + +func tooManyRequests(w http.ResponseWriter) { + // Return a 429 status indicating "Too Many Requests" + w.Header().Set("Retry-After", RetryAfter) + http.Error(w, "Too many requests, please try again later.", errors.StatusTooManyRequests) +} + +// RecoverPanics wraps an http Handler to recover and log panics. +func RecoverPanics(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + defer func() { + if x := recover(); x != nil { + http.Error(w, "apis panic. Look in log for details.", http.StatusInternalServerError) + glog.Errorf("APIServer panic'd on %v %v: %v\n%s\n", req.Method, req.RequestURI, x, debug.Stack()) + } + }() + defer httplog.NewLogged(req, &w).StacktraceWhen( + httplog.StatusIsNot( + http.StatusOK, + http.StatusCreated, + http.StatusAccepted, + http.StatusBadRequest, + http.StatusMovedPermanently, + http.StatusTemporaryRedirect, + http.StatusConflict, + http.StatusNotFound, + http.StatusUnauthorized, + http.StatusForbidden, + errors.StatusUnprocessableEntity, + http.StatusSwitchingProtocols, + ), + ).Log() + + // Dispatch to the internal handler + handler.ServeHTTP(w, req) + }) +} + +// TimeoutHandler returns an http.Handler that runs h with a timeout +// determined by timeoutFunc. The new http.Handler calls h.ServeHTTP to handle +// each request, but if a call runs for longer than its time limit, the +// handler responds with a 503 Service Unavailable error and the message +// provided. (If msg is empty, a suitable default message with be sent.) After +// the handler times out, writes by h to its http.ResponseWriter will return +// http.ErrHandlerTimeout. If timeoutFunc returns a nil timeout channel, no +// timeout will be enforced. +func TimeoutHandler(h http.Handler, timeoutFunc func(*http.Request) (timeout <-chan time.Time, msg string)) http.Handler { + return &timeoutHandler{h, timeoutFunc} +} + +type timeoutHandler struct { + handler http.Handler + timeout func(*http.Request) (<-chan time.Time, string) +} + +func (t *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + after, msg := t.timeout(r) + if after == nil { + t.handler.ServeHTTP(w, r) + return + } + + done := make(chan struct{}, 1) + tw := newTimeoutWriter(w) + go func() { + t.handler.ServeHTTP(tw, r) + done <- struct{}{} + }() + select { + case <-done: + return + case <-after: + tw.timeout(msg) + } +} + +type timeoutWriter interface { + http.ResponseWriter + timeout(string) +} + +func newTimeoutWriter(w http.ResponseWriter) timeoutWriter { + base := &baseTimeoutWriter{w: w} + + _, notifiable := w.(http.CloseNotifier) + _, hijackable := w.(http.Hijacker) + + switch { + case notifiable && hijackable: + return &closeHijackTimeoutWriter{base} + case notifiable: + return &closeTimeoutWriter{base} + case hijackable: + return &hijackTimeoutWriter{base} + default: + return base + } +} + +type baseTimeoutWriter struct { + w http.ResponseWriter + + mu sync.Mutex + timedOut bool + wroteHeader bool + hijacked bool +} + +func (tw *baseTimeoutWriter) Header() http.Header { + return tw.w.Header() +} + +func (tw *baseTimeoutWriter) Write(p []byte) (int, error) { + tw.mu.Lock() + defer tw.mu.Unlock() + tw.wroteHeader = true + if tw.hijacked { + return 0, http.ErrHijacked + } + if tw.timedOut { + return 0, http.ErrHandlerTimeout + } + return tw.w.Write(p) +} + +func (tw *baseTimeoutWriter) Flush() { + tw.mu.Lock() + defer tw.mu.Unlock() + + if flusher, ok := tw.w.(http.Flusher); ok { + flusher.Flush() + } +} + +func (tw *baseTimeoutWriter) WriteHeader(code int) { + tw.mu.Lock() + defer tw.mu.Unlock() + if tw.timedOut || tw.wroteHeader || tw.hijacked { + return + } + tw.wroteHeader = true + tw.w.WriteHeader(code) +} + +func (tw *baseTimeoutWriter) timeout(msg string) { + tw.mu.Lock() + defer tw.mu.Unlock() + if !tw.wroteHeader && !tw.hijacked { + tw.w.WriteHeader(http.StatusGatewayTimeout) + if msg != "" { + tw.w.Write([]byte(msg)) + } else { + enc := json.NewEncoder(tw.w) + enc.Encode(errors.NewServerTimeout(api.Resource(""), "", 0)) + } + } + tw.timedOut = true +} + +func (tw *baseTimeoutWriter) closeNotify() <-chan bool { + return tw.w.(http.CloseNotifier).CloseNotify() +} + +func (tw *baseTimeoutWriter) hijack() (net.Conn, *bufio.ReadWriter, error) { + tw.mu.Lock() + defer tw.mu.Unlock() + if tw.timedOut { + return nil, nil, http.ErrHandlerTimeout + } + conn, rw, err := tw.w.(http.Hijacker).Hijack() + if err == nil { + tw.hijacked = true + } + return conn, rw, err +} + +type closeTimeoutWriter struct { + *baseTimeoutWriter +} + +func (tw *closeTimeoutWriter) CloseNotify() <-chan bool { + return tw.closeNotify() +} + +type hijackTimeoutWriter struct { + *baseTimeoutWriter +} + +func (tw *hijackTimeoutWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return tw.hijack() +} + +type closeHijackTimeoutWriter struct { + *baseTimeoutWriter +} + +func (tw *closeHijackTimeoutWriter) CloseNotify() <-chan bool { + return tw.closeNotify() +} + +func (tw *closeHijackTimeoutWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return tw.hijack() +} + +// TODO: use restful.CrossOriginResourceSharing +// Simple CORS implementation that wraps an http Handler +// For a more detailed implementation use https://github.com/martini-contrib/cors +// or implement CORS at your proxy layer +// Pass nil for allowedMethods and allowedHeaders to use the defaults +func CORS(handler http.Handler, allowedOriginPatterns []*regexp.Regexp, allowedMethods []string, allowedHeaders []string, allowCredentials string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + origin := req.Header.Get("Origin") + if origin != "" { + allowed := false + for _, pattern := range allowedOriginPatterns { + if allowed = pattern.MatchString(origin); allowed { + break + } + } + if allowed { + w.Header().Set("Access-Control-Allow-Origin", origin) + // Set defaults for methods and headers if nothing was passed + if allowedMethods == nil { + allowedMethods = []string{"POST", "GET", "OPTIONS", "PUT", "DELETE"} + } + if allowedHeaders == nil { + allowedHeaders = []string{"Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization", "X-Requested-With", "If-Modified-Since"} + } + w.Header().Set("Access-Control-Allow-Methods", strings.Join(allowedMethods, ", ")) + w.Header().Set("Access-Control-Allow-Headers", strings.Join(allowedHeaders, ", ")) + w.Header().Set("Access-Control-Allow-Credentials", allowCredentials) + + // Stop here if its a preflight OPTIONS request + if req.Method == "OPTIONS" { + w.WriteHeader(http.StatusNoContent) + return + } + } + } + // Dispatch to the next handler + handler.ServeHTTP(w, req) + }) +} + +// RequestAttributeGetter is a function that extracts authorizer.Attributes from an http.Request +type RequestAttributeGetter interface { + GetAttribs(req *http.Request) (attribs authorizer.Attributes) +} + +type requestAttributeGetter struct { + requestContextMapper api.RequestContextMapper + requestInfoResolver *RequestInfoResolver +} + +// NewAttributeGetter returns an object which implements the RequestAttributeGetter interface. +func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper, requestInfoResolver *RequestInfoResolver) RequestAttributeGetter { + return &requestAttributeGetter{requestContextMapper, requestInfoResolver} +} + +func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes { + attribs := authorizer.AttributesRecord{} + + ctx, ok := r.requestContextMapper.Get(req) + if ok { + user, ok := api.UserFrom(ctx) + if ok { + attribs.User = user + } + } + + requestInfo, _ := r.requestInfoResolver.GetRequestInfo(req) + + // Start with common attributes that apply to resource and non-resource requests + attribs.ResourceRequest = requestInfo.IsResourceRequest + attribs.Path = requestInfo.Path + attribs.Verb = requestInfo.Verb + + attribs.APIGroup = requestInfo.APIGroup + attribs.APIVersion = requestInfo.APIVersion + attribs.Resource = requestInfo.Resource + attribs.Subresource = requestInfo.Subresource + attribs.Namespace = requestInfo.Namespace + attribs.Name = requestInfo.Name + + return &attribs +} + +// WithAuthorizationCheck passes all authorized requests on to handler, and returns a forbidden error otherwise. +func WithAuthorizationCheck(handler http.Handler, getAttribs RequestAttributeGetter, a authorizer.Authorizer) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + err := a.Authorize(getAttribs.GetAttribs(req)) + if err == nil { + handler.ServeHTTP(w, req) + return + } + forbidden(w, req) + }) +} + +// RequestInfo holds information parsed from the http.Request +type RequestInfo struct { + // IsResourceRequest indicates whether or not the request is for an API resource or subresource + IsResourceRequest bool + // Path is the URL path of the request + Path string + // Verb is the kube verb associated with the request for API requests, not the http verb. This includes things like list and watch. + // for non-resource requests, this is the lowercase http verb + Verb string + + APIPrefix string + APIGroup string + APIVersion string + Namespace string + // Resource is the name of the resource being requested. This is not the kind. For example: pods + Resource string + // Subresource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind. + // For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" + // (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding". + Subresource string + // Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in. + Name string + // Parts are the path parts for the request, always starting with /{resource}/{name} + Parts []string +} + +type RequestInfoResolver struct { + APIPrefixes sets.String + GrouplessAPIPrefixes sets.String +} + +// TODO write an integration test against the swagger doc to test the RequestInfo and match up behavior to responses +// GetRequestInfo returns the information from the http request. If error is not nil, RequestInfo holds the information as best it is known before the failure +// It handles both resource and non-resource requests and fills in all the pertinent information for each. +// Valid Inputs: +// Resource paths +// /apis/{api-group}/{version}/namespaces +// /api/{version}/namespaces +// /api/{version}/namespaces/{namespace} +// /api/{version}/namespaces/{namespace}/{resource} +// /api/{version}/namespaces/{namespace}/{resource}/{resourceName} +// /api/{version}/{resource} +// /api/{version}/{resource}/{resourceName} +// +// Special verbs without subresources: +// /api/{version}/proxy/{resource}/{resourceName} +// /api/{version}/proxy/namespaces/{namespace}/{resource}/{resourceName} +// /api/{version}/redirect/namespaces/{namespace}/{resource}/{resourceName} +// /api/{version}/redirect/{resource}/{resourceName} +// +// Special verbs with subresources: +// /api/{version}/watch/{resource} +// /api/{version}/watch/namespaces/{namespace}/{resource} +// +// NonResource paths +// /apis/{api-group}/{version} +// /apis/{api-group} +// /apis +// /api/{version} +// /api +// /healthz +// / +func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (RequestInfo, error) { + // start with a non-resource request until proven otherwise + requestInfo := RequestInfo{ + IsResourceRequest: false, + Path: req.URL.Path, + Verb: strings.ToLower(req.Method), + } + + currentParts := splitPath(req.URL.Path) + if len(currentParts) < 3 { + // return a non-resource request + return requestInfo, nil + } + + if !r.APIPrefixes.Has(currentParts[0]) { + // return a non-resource request + return requestInfo, nil + } + requestInfo.APIPrefix = currentParts[0] + currentParts = currentParts[1:] + + if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) { + // one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?" + if len(currentParts) < 3 { + // return a non-resource request + return requestInfo, nil + } + + requestInfo.APIGroup = currentParts[0] + currentParts = currentParts[1:] + } + + requestInfo.IsResourceRequest = true + requestInfo.APIVersion = currentParts[0] + currentParts = currentParts[1:] + + // handle input of form /{specialVerb}/* + if specialVerbs.Has(currentParts[0]) { + if len(currentParts) < 2 { + return requestInfo, fmt.Errorf("unable to determine kind and namespace from url, %v", req.URL) + } + + requestInfo.Verb = currentParts[0] + currentParts = currentParts[1:] + + } else { + switch req.Method { + case "POST": + requestInfo.Verb = "create" + case "GET", "HEAD": + requestInfo.Verb = "get" + case "PUT": + requestInfo.Verb = "update" + case "PATCH": + requestInfo.Verb = "patch" + case "DELETE": + requestInfo.Verb = "delete" + default: + requestInfo.Verb = "" + } + } + + // URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind + if currentParts[0] == "namespaces" { + if len(currentParts) > 1 { + requestInfo.Namespace = currentParts[1] + + // if there is another step after the namespace name and it is not a known namespace subresource + // move currentParts to include it as a resource in its own right + if len(currentParts) > 2 && !namespaceSubresources.Has(currentParts[2]) { + currentParts = currentParts[2:] + } + } + } else { + requestInfo.Namespace = api.NamespaceNone + } + + // parsing successful, so we now know the proper value for .Parts + requestInfo.Parts = currentParts + + // parts look like: resource/resourceName/subresource/other/stuff/we/don't/interpret + switch { + case len(requestInfo.Parts) >= 3 && !specialVerbsNoSubresources.Has(requestInfo.Verb): + requestInfo.Subresource = requestInfo.Parts[2] + fallthrough + case len(requestInfo.Parts) >= 2: + requestInfo.Name = requestInfo.Parts[1] + fallthrough + case len(requestInfo.Parts) >= 1: + requestInfo.Resource = requestInfo.Parts[0] + } + + // if there's no name on the request and we thought it was a get before, then the actual verb is a list + if len(requestInfo.Name) == 0 && requestInfo.Verb == "get" { + requestInfo.Verb = "list" + } + // if there's no name on the request and we thought it was a delete before, then the actual verb is deletecollection + if len(requestInfo.Name) == 0 && requestInfo.Verb == "delete" { + requestInfo.Verb = "deletecollection" + } + + return requestInfo, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/handlers_test.go b/vendor/k8s.io/kubernetes/pkg/apiserver/handlers_test.go new file mode 100644 index 000000000..7eddc2e18 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/handlers_test.go @@ -0,0 +1,479 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "reflect" + "regexp" + "strings" + "sync" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/auth/authorizer" + "k8s.io/kubernetes/pkg/util/sets" +) + +type fakeRL bool + +func (fakeRL) Stop() {} +func (f fakeRL) TryAccept() bool { return bool(f) } +func (f fakeRL) Accept() {} + +func expectHTTP(url string, code int) error { + r, err := http.Get(url) + if err != nil { + return fmt.Errorf("unexpected error: %v", err) + } + if r.StatusCode != code { + return fmt.Errorf("unexpected response: %v", r.StatusCode) + } + return nil +} + +func getPath(resource, namespace, name string) string { + return testapi.Default.ResourcePath(resource, namespace, name) +} + +func pathWithPrefix(prefix, resource, namespace, name string) string { + return testapi.Default.ResourcePathWithPrefix(prefix, resource, namespace, name) +} + +// Tests that MaxInFlightLimit works, i.e. +// - "long" requests such as proxy or watch, identified by regexp are not accounted despite +// hanging for the long time, +// - "short" requests are correctly accounted, i.e. there can be only size of channel passed to the +// constructor in flight at any given moment, +// - subsequent "short" requests are rejected instantly with appropriate error, +// - subsequent "long" requests are handled normally, +// - we correctly recover after some "short" requests finish, i.e. we can process new ones. +func TestMaxInFlight(t *testing.T) { + const AllowedInflightRequestsNo = 3 + // Size of inflightRequestsChannel determines how many concurent inflight requests + // are allowed. + inflightRequestsChannel := make(chan bool, AllowedInflightRequestsNo) + // notAccountedPathsRegexp specifies paths requests to which we don't account into + // requests in flight. + notAccountedPathsRegexp := regexp.MustCompile(".*\\/watch") + longRunningRequestCheck := BasicLongRunningRequestCheck(notAccountedPathsRegexp, map[string]string{"watch": "true"}) + + // Calls is used to wait until all server calls are received. We are sending + // AllowedInflightRequestsNo of 'long' not-accounted requests and the same number of + // 'short' accounted ones. + calls := &sync.WaitGroup{} + calls.Add(AllowedInflightRequestsNo * 2) + + // Responses is used to wait until all responses are + // received. This prevents some async requests getting EOF + // errors from prematurely closing the server + responses := sync.WaitGroup{} + responses.Add(AllowedInflightRequestsNo * 2) + + // Block is used to keep requests in flight for as long as we need to. All requests will + // be unblocked at the same time. + block := sync.WaitGroup{} + block.Add(1) + + server := httptest.NewServer( + MaxInFlightLimit( + inflightRequestsChannel, + longRunningRequestCheck, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // A short, accounted request that does not wait for block WaitGroup. + if strings.Contains(r.URL.Path, "dontwait") { + return + } + if calls != nil { + calls.Done() + } + block.Wait() + }), + ), + ) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + // These should hang, but not affect accounting. use a query param match + for i := 0; i < AllowedInflightRequestsNo; i++ { + // These should hang waiting on block... + go func() { + if err := expectHTTP(server.URL+"/foo/bar?watch=true", http.StatusOK); err != nil { + t.Error(err) + } + responses.Done() + }() + } + // Check that sever is not saturated by not-accounted calls + if err := expectHTTP(server.URL+"/dontwait", http.StatusOK); err != nil { + t.Error(err) + } + + // These should hang and be accounted, i.e. saturate the server + for i := 0; i < AllowedInflightRequestsNo; i++ { + // These should hang waiting on block... + go func() { + if err := expectHTTP(server.URL, http.StatusOK); err != nil { + t.Error(err) + } + responses.Done() + }() + } + // We wait for all calls to be received by the server + calls.Wait() + // Disable calls notifications in the server + calls = nil + + // Do this multiple times to show that it rate limit rejected requests don't block. + for i := 0; i < 2; i++ { + if err := expectHTTP(server.URL, errors.StatusTooManyRequests); err != nil { + t.Error(err) + } + } + // Validate that non-accounted URLs still work. use a path regex match + if err := expectHTTP(server.URL+"/dontwait/watch", http.StatusOK); err != nil { + t.Error(err) + } + + // Let all hanging requests finish + block.Done() + + // Show that we recover from being blocked up. + // Too avoid flakyness we need to wait until at least one of the requests really finishes. + responses.Wait() + if err := expectHTTP(server.URL, http.StatusOK); err != nil { + t.Error(err) + } +} + +func TestReadOnly(t *testing.T) { + server := httptest.NewServer(ReadOnly(http.HandlerFunc( + func(w http.ResponseWriter, req *http.Request) { + if req.Method != "GET" { + t.Errorf("Unexpected call: %v", req.Method) + } + }, + ))) + // TODO: Uncomment when fix #19254 + // defer server.Close() + for _, verb := range []string{"GET", "POST", "PUT", "DELETE", "CREATE"} { + req, err := http.NewRequest(verb, server.URL, nil) + if err != nil { + t.Fatalf("Couldn't make request: %v", err) + } + http.DefaultClient.Do(req) + } +} + +func TestTimeout(t *testing.T) { + sendResponse := make(chan struct{}, 1) + writeErrors := make(chan error, 1) + timeout := make(chan time.Time, 1) + resp := "test response" + timeoutResp := "test timeout" + + ts := httptest.NewServer(TimeoutHandler(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + <-sendResponse + _, err := w.Write([]byte(resp)) + writeErrors <- err + }), + func(*http.Request) (<-chan time.Time, string) { + return timeout, timeoutResp + })) + // TODO: Uncomment when fix #19254 + // defer ts.Close() + + // No timeouts + sendResponse <- struct{}{} + res, err := http.Get(ts.URL) + if err != nil { + t.Error(err) + } + if res.StatusCode != http.StatusOK { + t.Errorf("got res.StatusCode %d; expected %d", res.StatusCode, http.StatusOK) + } + body, _ := ioutil.ReadAll(res.Body) + if string(body) != resp { + t.Errorf("got body %q; expected %q", string(body), resp) + } + if err := <-writeErrors; err != nil { + t.Errorf("got unexpected Write error on first request: %v", err) + } + + // Times out + timeout <- time.Time{} + res, err = http.Get(ts.URL) + if err != nil { + t.Error(err) + } + if res.StatusCode != http.StatusGatewayTimeout { + t.Errorf("got res.StatusCode %d; expected %d", res.StatusCode, http.StatusServiceUnavailable) + } + body, _ = ioutil.ReadAll(res.Body) + if string(body) != timeoutResp { + t.Errorf("got body %q; expected %q", string(body), timeoutResp) + } + + // Now try to send a response + sendResponse <- struct{}{} + if err := <-writeErrors; err != http.ErrHandlerTimeout { + t.Errorf("got Write error of %v; expected %v", err, http.ErrHandlerTimeout) + } +} + +func TestGetAttribs(t *testing.T) { + r := &requestAttributeGetter{api.NewRequestContextMapper(), &RequestInfoResolver{sets.NewString("api", "apis"), sets.NewString("api")}} + + testcases := map[string]struct { + Verb string + Path string + ExpectedAttributes *authorizer.AttributesRecord + }{ + "non-resource root": { + Verb: "POST", + Path: "/", + ExpectedAttributes: &authorizer.AttributesRecord{ + Verb: "post", + Path: "/", + }, + }, + "non-resource api prefix": { + Verb: "GET", + Path: "/api/", + ExpectedAttributes: &authorizer.AttributesRecord{ + Verb: "get", + Path: "/api/", + }, + }, + "non-resource group api prefix": { + Verb: "GET", + Path: "/apis/extensions/", + ExpectedAttributes: &authorizer.AttributesRecord{ + Verb: "get", + Path: "/apis/extensions/", + }, + }, + + "resource": { + Verb: "POST", + Path: "/api/v1/nodes/mynode", + ExpectedAttributes: &authorizer.AttributesRecord{ + Verb: "create", + Path: "/api/v1/nodes/mynode", + ResourceRequest: true, + Resource: "nodes", + APIVersion: "v1", + Name: "mynode", + }, + }, + "namespaced resource": { + Verb: "PUT", + Path: "/api/v1/namespaces/myns/pods/mypod", + ExpectedAttributes: &authorizer.AttributesRecord{ + Verb: "update", + Path: "/api/v1/namespaces/myns/pods/mypod", + ResourceRequest: true, + Namespace: "myns", + Resource: "pods", + APIVersion: "v1", + Name: "mypod", + }, + }, + "API group resource": { + Verb: "GET", + Path: "/apis/extensions/v1beta1/namespaces/myns/jobs", + ExpectedAttributes: &authorizer.AttributesRecord{ + Verb: "list", + Path: "/apis/extensions/v1beta1/namespaces/myns/jobs", + ResourceRequest: true, + APIGroup: extensions.GroupName, + APIVersion: "v1beta1", + Namespace: "myns", + Resource: "jobs", + }, + }, + } + + for k, tc := range testcases { + req, _ := http.NewRequest(tc.Verb, tc.Path, nil) + attribs := r.GetAttribs(req) + if !reflect.DeepEqual(attribs, tc.ExpectedAttributes) { + t.Errorf("%s: expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedAttributes, attribs) + } + } +} + +func TestGetAPIRequestInfo(t *testing.T) { + successCases := []struct { + method string + url string + expectedVerb string + expectedAPIPrefix string + expectedAPIGroup string + expectedAPIVersion string + expectedNamespace string + expectedResource string + expectedSubresource string + expectedName string + expectedParts []string + }{ + + // resource paths + {"GET", "/api/v1/namespaces", "list", "api", "", "v1", "", "namespaces", "", "", []string{"namespaces"}}, + {"GET", "/api/v1/namespaces/other", "get", "api", "", "v1", "other", "namespaces", "", "other", []string{"namespaces", "other"}}, + + {"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "", []string{"pods"}}, + {"GET", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}}, + {"HEAD", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}}, + {"GET", "/api/v1/pods", "list", "api", "", "v1", api.NamespaceAll, "pods", "", "", []string{"pods"}}, + {"HEAD", "/api/v1/pods", "list", "api", "", "v1", api.NamespaceAll, "pods", "", "", []string{"pods"}}, + {"GET", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}}, + {"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "", []string{"pods"}}, + + // special verbs + {"GET", "/api/v1/proxy/namespaces/other/pods/foo", "proxy", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}}, + {"GET", "/api/v1/proxy/namespaces/other/pods/foo/subpath/not/a/subresource", "proxy", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo", "subpath", "not", "a", "subresource"}}, + {"GET", "/api/v1/redirect/namespaces/other/pods/foo", "redirect", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}}, + {"GET", "/api/v1/redirect/namespaces/other/pods/foo/subpath/not/a/subresource", "redirect", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo", "subpath", "not", "a", "subresource"}}, + {"GET", "/api/v1/watch/pods", "watch", "api", "", "v1", api.NamespaceAll, "pods", "", "", []string{"pods"}}, + {"GET", "/api/v1/watch/namespaces/other/pods", "watch", "api", "", "v1", "other", "pods", "", "", []string{"pods"}}, + + // subresource identification + {"GET", "/api/v1/namespaces/other/pods/foo/status", "get", "api", "", "v1", "other", "pods", "status", "foo", []string{"pods", "foo", "status"}}, + {"GET", "/api/v1/namespaces/other/pods/foo/proxy/subpath", "get", "api", "", "v1", "other", "pods", "proxy", "foo", []string{"pods", "foo", "proxy", "subpath"}}, + {"PUT", "/api/v1/namespaces/other/finalize", "update", "api", "", "v1", "other", "namespaces", "finalize", "other", []string{"namespaces", "other", "finalize"}}, + {"PUT", "/api/v1/namespaces/other/status", "update", "api", "", "v1", "other", "namespaces", "status", "other", []string{"namespaces", "other", "status"}}, + + // verb identification + {"PATCH", "/api/v1/namespaces/other/pods/foo", "patch", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}}, + {"DELETE", "/api/v1/namespaces/other/pods/foo", "delete", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}}, + {"POST", "/api/v1/namespaces/other/pods", "create", "api", "", "v1", "other", "pods", "", "", []string{"pods"}}, + + // deletecollection verb identification + {"DELETE", "/api/v1/nodes", "deletecollection", "api", "", "v1", "", "nodes", "", "", []string{"nodes"}}, + {"DELETE", "/api/v1/namespaces", "deletecollection", "api", "", "v1", "", "namespaces", "", "", []string{"namespaces"}}, + {"DELETE", "/api/v1/namespaces/other/pods", "deletecollection", "api", "", "v1", "other", "pods", "", "", []string{"pods"}}, + {"DELETE", "/apis/extensions/v1/namespaces/other/pods", "deletecollection", "api", "extensions", "v1", "other", "pods", "", "", []string{"pods"}}, + + // api group identification + {"POST", "/apis/extensions/v1/namespaces/other/pods", "create", "api", "extensions", "v1", "other", "pods", "", "", []string{"pods"}}, + + // api version identification + {"POST", "/apis/extensions/v1beta3/namespaces/other/pods", "create", "api", "extensions", "v1beta3", "other", "pods", "", "", []string{"pods"}}, + } + + requestInfoResolver := newTestRequestInfoResolver() + + for _, successCase := range successCases { + req, _ := http.NewRequest(successCase.method, successCase.url, nil) + + apiRequestInfo, err := requestInfoResolver.GetRequestInfo(req) + if err != nil { + t.Errorf("Unexpected error for url: %s %v", successCase.url, err) + } + if !apiRequestInfo.IsResourceRequest { + t.Errorf("Expected resource request") + } + if successCase.expectedVerb != apiRequestInfo.Verb { + t.Errorf("Unexpected verb for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedVerb, apiRequestInfo.Verb) + } + if successCase.expectedAPIVersion != apiRequestInfo.APIVersion { + t.Errorf("Unexpected apiVersion for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedAPIVersion, apiRequestInfo.APIVersion) + } + if successCase.expectedNamespace != apiRequestInfo.Namespace { + t.Errorf("Unexpected namespace for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedNamespace, apiRequestInfo.Namespace) + } + if successCase.expectedResource != apiRequestInfo.Resource { + t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedResource, apiRequestInfo.Resource) + } + if successCase.expectedSubresource != apiRequestInfo.Subresource { + t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedSubresource, apiRequestInfo.Subresource) + } + if successCase.expectedName != apiRequestInfo.Name { + t.Errorf("Unexpected name for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedName, apiRequestInfo.Name) + } + if !reflect.DeepEqual(successCase.expectedParts, apiRequestInfo.Parts) { + t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, apiRequestInfo.Parts) + } + } + + errorCases := map[string]string{ + "no resource path": "/", + "just apiversion": "/api/version/", + "just prefix, group, version": "/apis/group/version/", + "apiversion with no resource": "/api/version/", + "bad prefix": "/badprefix/version/resource", + "missing api group": "/apis/version/resource", + } + for k, v := range errorCases { + req, err := http.NewRequest("GET", v, nil) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + apiRequestInfo, err := requestInfoResolver.GetRequestInfo(req) + if err != nil { + t.Errorf("%s: Unexpected error %v", k, err) + } + if apiRequestInfo.IsResourceRequest { + t.Errorf("%s: expected non-resource request", k) + } + } +} + +func TestGetNonAPIRequestInfo(t *testing.T) { + tests := map[string]struct { + url string + expected bool + }{ + "simple groupless": {"/api/version/resource", true}, + "simple group": {"/apis/group/version/resource/name/subresource", true}, + "more steps": {"/api/version/resource/name/subresource", true}, + "group list": {"/apis/extensions/v1beta1/job", true}, + "group get": {"/apis/extensions/v1beta1/job/foo", true}, + "group subresource": {"/apis/extensions/v1beta1/job/foo/scale", true}, + + "bad root": {"/not-api/version/resource", false}, + "group without enough steps": {"/apis/extensions/v1beta1", false}, + "group without enough steps 2": {"/apis/extensions/v1beta1/", false}, + "not enough steps": {"/api/version", false}, + "one step": {"/api", false}, + "zero step": {"/", false}, + "empty": {"", false}, + } + + requestInfoResolver := newTestRequestInfoResolver() + + for testName, tc := range tests { + req, _ := http.NewRequest("GET", tc.url, nil) + + apiRequestInfo, err := requestInfoResolver.GetRequestInfo(req) + if err != nil { + t.Errorf("%s: Unexpected error %v", testName, err) + } + if e, a := tc.expected, apiRequestInfo.IsResourceRequest; e != a { + t.Errorf("%s: expected %v, actual %v", testName, e, a) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/index.go b/vendor/k8s.io/kubernetes/pkg/apiserver/index.go new file mode 100644 index 000000000..c01174d75 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/index.go @@ -0,0 +1,46 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "net/http" + "sort" + + "k8s.io/kubernetes/pkg/api/unversioned" + + "github.com/emicklei/go-restful" +) + +func IndexHandler(container *restful.Container, muxHelper *MuxHelper) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + status := http.StatusOK + if r.URL.Path != "/" && r.URL.Path != "/index.html" { + // Since "/" matches all paths, handleIndex is called for all paths for which there is no handler registered. + // We want to to return a 404 status with a list of all valid paths, incase of an invalid URL request. + status = http.StatusNotFound + } + var handledPaths []string + // Extract the paths handled using restful.WebService + for _, ws := range container.RegisteredWebServices() { + handledPaths = append(handledPaths, ws.RootPath()) + } + // Extract the paths handled using mux handler. + handledPaths = append(handledPaths, muxHelper.RegisteredPaths...) + sort.Strings(handledPaths) + writeRawJSON(status, unversioned.RootPaths{Paths: handledPaths}, w) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/metrics/metrics.go b/vendor/k8s.io/kubernetes/pkg/apiserver/metrics/metrics.go new file mode 100644 index 000000000..fb7736b06 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/metrics/metrics.go @@ -0,0 +1,245 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 metrics + +import ( + "bufio" + "net" + "net/http" + "strconv" + "time" + + utilnet "k8s.io/kubernetes/pkg/util/net" + + "github.com/emicklei/go-restful" + "github.com/prometheus/client_golang/prometheus" +) + +var ( + // TODO(a-robinson): Add unit tests for the handling of these metrics once + // the upstream library supports it. + requestCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "apiserver_request_count", + Help: "Counter of apiserver requests broken out for each verb, API resource, client, and HTTP response code.", + }, + []string{"verb", "resource", "client", "code"}, + ) + requestLatencies = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "apiserver_request_latencies", + Help: "Response latency distribution in microseconds for each verb, resource and client.", + // Use buckets ranging from 125 ms to 8 seconds. + Buckets: prometheus.ExponentialBuckets(125000, 2.0, 7), + }, + []string{"verb", "resource"}, + ) + requestLatenciesSummary = prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Name: "apiserver_request_latencies_summary", + Help: "Response latency summary in microseconds for each verb and resource.", + // Make the sliding window of 1h. + MaxAge: time.Hour, + }, + []string{"verb", "resource"}, + ) +) + +// Register all metrics. +func Register() { + prometheus.MustRegister(requestCounter) + prometheus.MustRegister(requestLatencies) + prometheus.MustRegister(requestLatenciesSummary) +} + +func Monitor(verb, resource *string, client string, httpCode int, reqStart time.Time) { + elapsed := float64((time.Since(reqStart)) / time.Microsecond) + requestCounter.WithLabelValues(*verb, *resource, client, codeToString(httpCode)).Inc() + requestLatencies.WithLabelValues(*verb, *resource).Observe(elapsed) + requestLatenciesSummary.WithLabelValues(*verb, *resource).Observe(elapsed) +} + +func Reset() { + requestCounter.Reset() + requestLatencies.Reset() + requestLatenciesSummary.Reset() +} + +// InstrumentRouteFunc works like Prometheus' InstrumentHandlerFunc but wraps +// the go-restful RouteFunction instead of a HandlerFunc +func InstrumentRouteFunc(verb, resource string, routeFunc restful.RouteFunction) restful.RouteFunction { + return restful.RouteFunction(func(request *restful.Request, response *restful.Response) { + now := time.Now() + + delegate := &responseWriterDelegator{ResponseWriter: response.ResponseWriter} + + _, cn := response.ResponseWriter.(http.CloseNotifier) + _, fl := response.ResponseWriter.(http.Flusher) + _, hj := response.ResponseWriter.(http.Hijacker) + var rw http.ResponseWriter + if cn && fl && hj { + rw = &fancyResponseWriterDelegator{delegate} + } else { + rw = delegate + } + response.ResponseWriter = rw + + routeFunc(request, response) + Monitor(&verb, &resource, utilnet.GetHTTPClient(request.Request), delegate.status, now) + }) +} + +type responseWriterDelegator struct { + http.ResponseWriter + + status int + written int64 + wroteHeader bool +} + +func (r *responseWriterDelegator) WriteHeader(code int) { + r.status = code + r.wroteHeader = true + r.ResponseWriter.WriteHeader(code) +} + +func (r *responseWriterDelegator) Write(b []byte) (int, error) { + if !r.wroteHeader { + r.WriteHeader(http.StatusOK) + } + n, err := r.ResponseWriter.Write(b) + r.written += int64(n) + return n, err +} + +type fancyResponseWriterDelegator struct { + *responseWriterDelegator +} + +func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool { + return f.ResponseWriter.(http.CloseNotifier).CloseNotify() +} + +func (f *fancyResponseWriterDelegator) Flush() { + f.ResponseWriter.(http.Flusher).Flush() +} + +func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return f.ResponseWriter.(http.Hijacker).Hijack() +} + +// Small optimization over Itoa +func codeToString(s int) string { + switch s { + case 100: + return "100" + case 101: + return "101" + + case 200: + return "200" + case 201: + return "201" + case 202: + return "202" + case 203: + return "203" + case 204: + return "204" + case 205: + return "205" + case 206: + return "206" + + case 300: + return "300" + case 301: + return "301" + case 302: + return "302" + case 304: + return "304" + case 305: + return "305" + case 307: + return "307" + + case 400: + return "400" + case 401: + return "401" + case 402: + return "402" + case 403: + return "403" + case 404: + return "404" + case 405: + return "405" + case 406: + return "406" + case 407: + return "407" + case 408: + return "408" + case 409: + return "409" + case 410: + return "410" + case 411: + return "411" + case 412: + return "412" + case 413: + return "413" + case 414: + return "414" + case 415: + return "415" + case 416: + return "416" + case 417: + return "417" + case 418: + return "418" + + case 500: + return "500" + case 501: + return "501" + case 502: + return "502" + case 503: + return "503" + case 504: + return "504" + case 505: + return "505" + + case 428: + return "428" + case 429: + return "429" + case 431: + return "431" + case 511: + return "511" + + default: + return strconv.Itoa(s) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/mux_helper.go b/vendor/k8s.io/kubernetes/pkg/apiserver/mux_helper.go new file mode 100644 index 000000000..48953321c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/mux_helper.go @@ -0,0 +1,37 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "net/http" +) + +// Offers additional functionality over ServeMux, for ex: supports listing registered paths. +type MuxHelper struct { + Mux Mux + RegisteredPaths []string +} + +func (m *MuxHelper) Handle(path string, handler http.Handler) { + m.RegisteredPaths = append(m.RegisteredPaths, path) + m.Mux.Handle(path, handler) +} + +func (m *MuxHelper) HandleFunc(path string, handler func(http.ResponseWriter, *http.Request)) { + m.RegisteredPaths = append(m.RegisteredPaths, path) + m.Mux.HandleFunc(path, handler) +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/negotiate.go b/vendor/k8s.io/kubernetes/pkg/apiserver/negotiate.go new file mode 100644 index 000000000..1457addbf --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/negotiate.go @@ -0,0 +1,116 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "mime" + "net/http" + "strconv" + "strings" + + "bitbucket.org/ww/goautoneg" + + "k8s.io/kubernetes/pkg/runtime" +) + +func negotiateOutputSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.Serializer, string, error) { + acceptHeader := req.Header.Get("Accept") + supported := ns.SupportedMediaTypes() + if len(acceptHeader) == 0 && len(supported) > 0 { + acceptHeader = supported[0] + } + accept, ok := negotiate(acceptHeader, supported) + if !ok { + return nil, "", errNotAcceptable{supported} + } + + pretty := isPrettyPrint(req) + if _, ok := accept.Params["pretty"]; !ok && pretty { + accept.Params["pretty"] = "1" + } + mediaType := accept.Type + if len(accept.SubType) > 0 { + mediaType += "/" + accept.SubType + } + if s, ok := ns.SerializerForMediaType(mediaType, accept.Params); ok { + return s, mediaType, nil + } + + return nil, "", errNotAcceptable{supported} +} + +func negotiateInputSerializer(req *http.Request, s runtime.NegotiatedSerializer) (runtime.Serializer, error) { + supported := s.SupportedMediaTypes() + mediaType := req.Header.Get("Content-Type") + if len(mediaType) == 0 { + mediaType = supported[0] + } + mediaType, options, err := mime.ParseMediaType(mediaType) + if err != nil { + return nil, errUnsupportedMediaType{supported} + } + out, ok := s.SerializerForMediaType(mediaType, options) + if !ok { + return nil, errUnsupportedMediaType{supported} + } + return out, nil +} + +// isPrettyPrint returns true if the "pretty" query parameter is true or if the User-Agent +// matches known "human" clients. +func isPrettyPrint(req *http.Request) bool { + // DEPRECATED: should be part of the content type + if req.URL != nil { + pp := req.URL.Query().Get("pretty") + if len(pp) > 0 { + pretty, _ := strconv.ParseBool(pp) + return pretty + } + } + userAgent := req.UserAgent() + // This covers basic all browers and cli http tools + if strings.HasPrefix(userAgent, "curl") || strings.HasPrefix(userAgent, "Wget") || strings.HasPrefix(userAgent, "Mozilla/5.0") { + return true + } + return false +} + +// negotiate the most appropriate content type given the accept header and a list of +// alternatives. +func negotiate(header string, alternatives []string) (goautoneg.Accept, bool) { + alternates := make([][]string, 0, len(alternatives)) + for _, alternate := range alternatives { + alternates = append(alternates, strings.SplitN(alternate, "/", 2)) + } + for _, clause := range goautoneg.ParseAccept(header) { + for _, alternate := range alternates { + if clause.Type == alternate[0] && clause.SubType == alternate[1] { + return clause, true + } + if clause.Type == alternate[0] && clause.SubType == "*" { + clause.SubType = alternate[1] + return clause, true + } + if clause.Type == "*" && clause.SubType == "*" { + clause.Type = alternate[0] + clause.SubType = alternate[1] + return clause, true + } + } + } + return goautoneg.Accept{}, false +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/negotiate_test.go b/vendor/k8s.io/kubernetes/pkg/apiserver/negotiate_test.go new file mode 100644 index 000000000..8e59d6d69 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/negotiate_test.go @@ -0,0 +1,252 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "net/http" + "net/url" + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/runtime" +) + +type fakeNegotiater struct { + serializer runtime.Serializer + types []string + mediaType string + options map[string]string +} + +func (n *fakeNegotiater) SupportedMediaTypes() []string { + return n.types +} + +func (n *fakeNegotiater) SerializerForMediaType(mediaType string, options map[string]string) (runtime.Serializer, bool) { + n.mediaType = mediaType + if len(options) > 0 { + n.options = options + } + return n.serializer, n.serializer != nil +} + +func (n *fakeNegotiater) EncoderForVersion(serializer runtime.Serializer, gv unversioned.GroupVersion) runtime.Encoder { + return n.serializer +} + +func (n *fakeNegotiater) DecoderToVersion(serializer runtime.Serializer, gv unversioned.GroupVersion) runtime.Decoder { + return n.serializer +} + +var fakeCodec = runtime.NewCodec(runtime.NoopEncoder{}, runtime.NoopDecoder{}) + +func TestNegotiate(t *testing.T) { + testCases := []struct { + accept string + req *http.Request + ns *fakeNegotiater + serializer runtime.Serializer + contentType string + params map[string]string + errFn func(error) bool + }{ + // pick a default + { + req: &http.Request{}, + contentType: "application/json", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}}, + serializer: fakeCodec, + }, + { + accept: "", + contentType: "application/json", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}}, + serializer: fakeCodec, + }, + { + accept: "*/*", + contentType: "application/json", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}}, + serializer: fakeCodec, + }, + { + accept: "application/*", + contentType: "application/json", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}}, + serializer: fakeCodec, + }, + { + accept: "application/json", + contentType: "application/json", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}}, + serializer: fakeCodec, + }, + { + accept: "application/json", + contentType: "application/json", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json", "application/protobuf"}}, + serializer: fakeCodec, + }, + { + accept: "application/protobuf", + contentType: "application/protobuf", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json", "application/protobuf"}}, + serializer: fakeCodec, + }, + { + accept: "application/json; pretty=1", + contentType: "application/json", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}}, + serializer: fakeCodec, + params: map[string]string{"pretty": "1"}, + }, + { + accept: "unrecognized/stuff,application/json; pretty=1", + contentType: "application/json", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}}, + serializer: fakeCodec, + params: map[string]string{"pretty": "1"}, + }, + + // query param triggers pretty + { + req: &http.Request{ + Header: http.Header{"Accept": []string{"application/json"}}, + URL: &url.URL{RawQuery: "pretty=1"}, + }, + contentType: "application/json", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}}, + serializer: fakeCodec, + params: map[string]string{"pretty": "1"}, + }, + + // certain user agents trigger pretty + { + req: &http.Request{ + Header: http.Header{ + "Accept": []string{"application/json"}, + "User-Agent": []string{"curl"}, + }, + }, + contentType: "application/json", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}}, + serializer: fakeCodec, + params: map[string]string{"pretty": "1"}, + }, + { + req: &http.Request{ + Header: http.Header{ + "Accept": []string{"application/json"}, + "User-Agent": []string{"Wget"}, + }, + }, + contentType: "application/json", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}}, + serializer: fakeCodec, + params: map[string]string{"pretty": "1"}, + }, + { + req: &http.Request{ + Header: http.Header{ + "Accept": []string{"application/json"}, + "User-Agent": []string{"Mozilla/5.0"}, + }, + }, + contentType: "application/json", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json"}}, + serializer: fakeCodec, + params: map[string]string{"pretty": "1"}, + }, + + // "application" is not a valid media type, so the server will reject the response during + // negotiation (the server, in error, has specified an invalid media type) + { + accept: "application", + ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application"}}, + errFn: func(err error) bool { + return err.Error() == "only the following media types are accepted: application" + }, + }, + { + ns: &fakeNegotiater{types: []string{"a/b/c"}}, + errFn: func(err error) bool { + return err.Error() == "only the following media types are accepted: a/b/c" + }, + }, + { + ns: &fakeNegotiater{}, + errFn: func(err error) bool { + return err.Error() == "only the following media types are accepted: " + }, + }, + { + accept: "*/*", + ns: &fakeNegotiater{}, + errFn: func(err error) bool { + return err.Error() == "only the following media types are accepted: " + }, + }, + { + accept: "application/json", + ns: &fakeNegotiater{types: []string{"application/json"}}, + errFn: func(err error) bool { + return err.Error() == "only the following media types are accepted: application/json" + }, + }, + } + + for i, test := range testCases { + req := test.req + if req == nil { + req = &http.Request{Header: http.Header{}} + req.Header.Set("Accept", test.accept) + } + s, contentType, err := negotiateOutputSerializer(req, test.ns) + switch { + case err == nil && test.errFn != nil: + t.Errorf("%d: failed: expected error", i) + continue + case err != nil && test.errFn == nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil: + if !test.errFn(err) { + t.Errorf("%d: failed: %v", i, err) + } + status, ok := err.(statusError) + if !ok { + t.Errorf("%d: failed, error should be statusError: %v", i, err) + continue + } + if status.Status().Status != unversioned.StatusFailure || status.Status().Code != http.StatusNotAcceptable { + t.Errorf("%d: failed: %v", i, err) + continue + } + continue + } + if test.contentType != contentType { + t.Errorf("%d: unexpected %s %s", i, test.contentType, contentType) + } + if s != test.serializer { + t.Errorf("%d: unexpected %s %s", i, test.serializer, s) + } + if !reflect.DeepEqual(test.params, test.ns.options) { + t.Errorf("%d: unexpected %#v %#v", i, test.params, test.ns.options) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/proxy.go b/vendor/k8s.io/kubernetes/pkg/apiserver/proxy.go new file mode 100644 index 000000000..5c581032b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/proxy.go @@ -0,0 +1,268 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "io" + "math/rand" + "net/http" + "net/http/httputil" + "net/url" + "path" + "strings" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apiserver/metrics" + "k8s.io/kubernetes/pkg/httplog" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/httpstream" + "k8s.io/kubernetes/pkg/util/net" + proxyutil "k8s.io/kubernetes/pkg/util/proxy" + + "github.com/golang/glog" +) + +// ProxyHandler provides a http.Handler which will proxy traffic to locations +// specified by items implementing Redirector. +type ProxyHandler struct { + prefix string + storage map[string]rest.Storage + serializer runtime.NegotiatedSerializer + context api.RequestContextMapper + requestInfoResolver *RequestInfoResolver +} + +func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + proxyHandlerTraceID := rand.Int63() + + var verb string + var apiResource string + var httpCode int + reqStart := time.Now() + defer metrics.Monitor(&verb, &apiResource, net.GetHTTPClient(req), httpCode, reqStart) + + requestInfo, err := r.requestInfoResolver.GetRequestInfo(req) + if err != nil || !requestInfo.IsResourceRequest { + notFound(w, req) + httpCode = http.StatusNotFound + return + } + verb = requestInfo.Verb + namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts + + ctx, ok := r.context.Get(req) + if !ok { + ctx = api.NewContext() + } + ctx = api.WithNamespace(ctx, namespace) + if len(parts) < 2 { + notFound(w, req) + httpCode = http.StatusNotFound + return + } + id := parts[1] + remainder := "" + if len(parts) > 2 { + proxyParts := parts[2:] + remainder = strings.Join(proxyParts, "/") + if strings.HasSuffix(req.URL.Path, "/") { + // The original path had a trailing slash, which has been stripped + // by KindAndNamespace(). We should add it back because some + // servers (like etcd) require it. + remainder = remainder + "/" + } + } + storage, ok := r.storage[resource] + if !ok { + httplog.LogOf(req, w).Addf("'%v' has no storage object", resource) + notFound(w, req) + httpCode = http.StatusNotFound + return + } + apiResource = resource + + gv := unversioned.GroupVersion{Group: requestInfo.APIGroup, Version: requestInfo.APIVersion} + + redirector, ok := storage.(rest.Redirector) + if !ok { + httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource) + httpCode = errorNegotiated(errors.NewMethodNotSupported(api.Resource(resource), "proxy"), r.serializer, gv, w, req) + return + } + + location, roundTripper, err := redirector.ResourceLocation(ctx, id) + if err != nil { + httplog.LogOf(req, w).Addf("Error getting ResourceLocation: %v", err) + httpCode = errorNegotiated(err, r.serializer, gv, w, req) + return + } + if location == nil { + httplog.LogOf(req, w).Addf("ResourceLocation for %v returned nil", id) + notFound(w, req) + httpCode = http.StatusNotFound + return + } + + if roundTripper != nil { + glog.V(5).Infof("[%x: %v] using transport %T...", proxyHandlerTraceID, req.URL, roundTripper) + } + + // Default to http + if location.Scheme == "" { + location.Scheme = "http" + } + // Add the subpath + if len(remainder) > 0 { + location.Path = singleJoiningSlash(location.Path, remainder) + } + // Start with anything returned from the storage, and add the original request's parameters + values := location.Query() + for k, vs := range req.URL.Query() { + for _, v := range vs { + values.Add(k, v) + } + } + location.RawQuery = values.Encode() + + newReq, err := http.NewRequest(req.Method, location.String(), req.Body) + if err != nil { + httpCode = errorNegotiated(err, r.serializer, gv, w, req) + return + } + httpCode = http.StatusOK + newReq.Header = req.Header + newReq.ContentLength = req.ContentLength + // Copy the TransferEncoding is for future-proofing. Currently Go only supports "chunked" and + // it can determine the TransferEncoding based on ContentLength and the Body. + newReq.TransferEncoding = req.TransferEncoding + + // TODO convert this entire proxy to an UpgradeAwareProxy similar to + // https://github.com/openshift/origin/blob/master/pkg/util/httpproxy/upgradeawareproxy.go. + // That proxy needs to be modified to support multiple backends, not just 1. + if r.tryUpgrade(w, req, newReq, location, roundTripper, gv) { + return + } + + // Redirect requests of the form "/{resource}/{name}" to "/{resource}/{name}/" + // This is essentially a hack for http://issue.k8s.io/4958. + // Note: Keep this code after tryUpgrade to not break that flow. + if len(parts) == 2 && !strings.HasSuffix(req.URL.Path, "/") { + var queryPart string + if len(req.URL.RawQuery) > 0 { + queryPart = "?" + req.URL.RawQuery + } + w.Header().Set("Location", req.URL.Path+"/"+queryPart) + w.WriteHeader(http.StatusMovedPermanently) + return + } + + start := time.Now() + glog.V(4).Infof("[%x] Beginning proxy %s...", proxyHandlerTraceID, req.URL) + defer func() { + glog.V(4).Infof("[%x] Proxy %v finished %v.", proxyHandlerTraceID, req.URL, time.Now().Sub(start)) + }() + + proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: location.Scheme, Host: location.Host}) + alreadyRewriting := false + if roundTripper != nil { + _, alreadyRewriting = roundTripper.(*proxyutil.Transport) + glog.V(5).Infof("[%x] Not making a reriting transport for proxy %s...", proxyHandlerTraceID, req.URL) + } + if !alreadyRewriting { + glog.V(5).Infof("[%x] making a transport for proxy %s...", proxyHandlerTraceID, req.URL) + prepend := path.Join(r.prefix, resource, id) + if len(namespace) > 0 { + prepend = path.Join(r.prefix, "namespaces", namespace, resource, id) + } + pTransport := &proxyutil.Transport{ + Scheme: req.URL.Scheme, + Host: req.URL.Host, + PathPrepend: prepend, + RoundTripper: roundTripper, + } + roundTripper = pTransport + } + proxy.Transport = roundTripper + proxy.FlushInterval = 200 * time.Millisecond + proxy.ServeHTTP(w, newReq) +} + +// tryUpgrade returns true if the request was handled. +func (r *ProxyHandler) tryUpgrade(w http.ResponseWriter, req, newReq *http.Request, location *url.URL, transport http.RoundTripper, gv unversioned.GroupVersion) bool { + if !httpstream.IsUpgradeRequest(req) { + return false + } + backendConn, err := proxyutil.DialURL(location, transport) + if err != nil { + errorNegotiated(err, r.serializer, gv, w, req) + return true + } + defer backendConn.Close() + + // TODO should we use _ (a bufio.ReadWriter) instead of requestHijackedConn + // when copying between the client and the backend? Docker doesn't when they + // hijack, just for reference... + requestHijackedConn, _, err := w.(http.Hijacker).Hijack() + if err != nil { + errorNegotiated(err, r.serializer, gv, w, req) + return true + } + defer requestHijackedConn.Close() + + if err = newReq.Write(backendConn); err != nil { + errorNegotiated(err, r.serializer, gv, w, req) + return true + } + + done := make(chan struct{}, 2) + + go func() { + _, err := io.Copy(backendConn, requestHijackedConn) + if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { + glog.Errorf("Error proxying data from client to backend: %v", err) + } + done <- struct{}{} + }() + + go func() { + _, err := io.Copy(requestHijackedConn, backendConn) + if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { + glog.Errorf("Error proxying data from backend to client: %v", err) + } + done <- struct{}{} + }() + + <-done + return true +} + +// borrowed from net/http/httputil/reverseproxy.go +func singleJoiningSlash(a, b string) string { + aslash := strings.HasSuffix(a, "/") + bslash := strings.HasPrefix(b, "/") + switch { + case aslash && bslash: + return a + b[1:] + case !aslash && !bslash: + return a + "/" + b + } + return a + b +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/proxy_test.go b/vendor/k8s.io/kubernetes/pkg/apiserver/proxy_test.go new file mode 100644 index 000000000..657bd0192 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/proxy_test.go @@ -0,0 +1,577 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "bytes" + "compress/gzip" + "crypto/tls" + "crypto/x509" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/httptest" + "net/http/httputil" + "net/url" + "reflect" + "strconv" + "strings" + "testing" + + "golang.org/x/net/websocket" + "k8s.io/kubernetes/pkg/api/rest" + utilnet "k8s.io/kubernetes/pkg/util/net" +) + +func TestProxyRequestContentLengthAndTransferEncoding(t *testing.T) { + chunk := func(data []byte) []byte { + out := &bytes.Buffer{} + chunker := httputil.NewChunkedWriter(out) + for _, b := range data { + if _, err := chunker.Write([]byte{b}); err != nil { + panic(err) + } + } + chunker.Close() + out.Write([]byte("\r\n")) + return out.Bytes() + } + + zip := func(data []byte) []byte { + out := &bytes.Buffer{} + zipper := gzip.NewWriter(out) + if _, err := zipper.Write(data); err != nil { + panic(err) + } + zipper.Close() + return out.Bytes() + } + + sampleData := []byte("abcde") + + table := map[string]struct { + reqHeaders http.Header + reqBody []byte + + expectedHeaders http.Header + expectedBody []byte + }{ + "content-length": { + reqHeaders: http.Header{ + "Content-Length": []string{"5"}, + }, + reqBody: sampleData, + + expectedHeaders: http.Header{ + "Content-Length": []string{"5"}, + "Content-Encoding": nil, // none set + "Transfer-Encoding": nil, // none set + }, + expectedBody: sampleData, + }, + + "content-length + identity transfer-encoding": { + reqHeaders: http.Header{ + "Content-Length": []string{"5"}, + "Transfer-Encoding": []string{"identity"}, + }, + reqBody: sampleData, + + expectedHeaders: http.Header{ + "Content-Length": []string{"5"}, + "Content-Encoding": nil, // none set + "Transfer-Encoding": nil, // gets removed + }, + expectedBody: sampleData, + }, + + "content-length + gzip content-encoding": { + reqHeaders: http.Header{ + "Content-Length": []string{strconv.Itoa(len(zip(sampleData)))}, + "Content-Encoding": []string{"gzip"}, + }, + reqBody: zip(sampleData), + + expectedHeaders: http.Header{ + "Content-Length": []string{strconv.Itoa(len(zip(sampleData)))}, + "Content-Encoding": []string{"gzip"}, + "Transfer-Encoding": nil, // none set + }, + expectedBody: zip(sampleData), + }, + + "chunked transfer-encoding": { + reqHeaders: http.Header{ + "Transfer-Encoding": []string{"chunked"}, + }, + reqBody: chunk(sampleData), + + expectedHeaders: http.Header{ + "Content-Length": nil, // none set + "Content-Encoding": nil, // none set + "Transfer-Encoding": nil, // Transfer-Encoding gets removed + }, + expectedBody: sampleData, // sample data is unchunked + }, + + "chunked transfer-encoding + gzip content-encoding": { + reqHeaders: http.Header{ + "Content-Encoding": []string{"gzip"}, + "Transfer-Encoding": []string{"chunked"}, + }, + reqBody: chunk(zip(sampleData)), + + expectedHeaders: http.Header{ + "Content-Length": nil, // none set + "Content-Encoding": []string{"gzip"}, + "Transfer-Encoding": nil, // gets removed + }, + expectedBody: zip(sampleData), // sample data is unchunked, but content-encoding is preserved + }, + + // "Transfer-Encoding: gzip" is not supported by go + // See http/transfer.go#fixTransferEncoding (https://golang.org/src/net/http/transfer.go#L427) + // Once it is supported, this test case should succeed + // + // "gzip+chunked transfer-encoding": { + // reqHeaders: http.Header{ + // "Transfer-Encoding": []string{"chunked,gzip"}, + // }, + // reqBody: chunk(zip(sampleData)), + // + // expectedHeaders: http.Header{ + // "Content-Length": nil, // no content-length headers + // "Transfer-Encoding": nil, // Transfer-Encoding gets removed + // }, + // expectedBody: sampleData, + // }, + } + + successfulResponse := "backend passed tests" + for k, item := range table { + // Start the downstream server + downstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // Verify headers + for header, v := range item.expectedHeaders { + if !reflect.DeepEqual(v, req.Header[header]) { + t.Errorf("%s: Expected headers for %s to be %v, got %v", k, header, v, req.Header[header]) + } + } + + // Read body + body, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Errorf("%s: unexpected error %v", k, err) + } + req.Body.Close() + + // Verify length + if req.ContentLength > 0 && req.ContentLength != int64(len(body)) { + t.Errorf("%s: ContentLength was %d, len(data) was %d", k, req.ContentLength, len(body)) + } + + // Verify content + if !bytes.Equal(item.expectedBody, body) { + t.Errorf("%s: Expected %q, got %q", k, string(item.expectedBody), string(body)) + } + + // Write successful response + w.Write([]byte(successfulResponse)) + })) + // TODO: Uncomment when fix #19254 + // defer downstreamServer.Close() + + // Start the proxy server + serverURL, _ := url.Parse(downstreamServer.URL) + simpleStorage := &SimpleRESTStorage{ + errors: map[string]error{}, + resourceLocation: serverURL, + expectedResourceNamespace: "default", + } + namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage}) + server := httptest.NewServer(namespaceHandler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + // Dial the proxy server + conn, err := net.Dial(server.Listener.Addr().Network(), server.Listener.Addr().String()) + if err != nil { + t.Errorf("%s: unexpected error %v", k, err) + continue + } + defer conn.Close() + + // Add standard http 1.1 headers + if item.reqHeaders == nil { + item.reqHeaders = http.Header{} + } + item.reqHeaders.Add("Connection", "close") + item.reqHeaders.Add("Host", server.Listener.Addr().String()) + + // We directly write to the connection to bypass the Go library's manipulation of the Request.Header. + // Write the request headers + post := fmt.Sprintf("POST /%s/%s/%s/proxy/namespaces/default/foo/id/some/dir HTTP/1.1\r\n", prefix, newGroupVersion.Group, newGroupVersion.Version) + if _, err := fmt.Fprint(conn, post); err != nil { + t.Fatalf("%s: unexpected error %v", k, err) + } + for header, values := range item.reqHeaders { + for _, value := range values { + if _, err := fmt.Fprintf(conn, "%s: %s\r\n", header, value); err != nil { + t.Fatalf("%s: unexpected error %v", k, err) + } + } + } + // Header separator + if _, err := fmt.Fprint(conn, "\r\n"); err != nil { + t.Fatalf("%s: unexpected error %v", k, err) + } + // Body + if _, err := conn.Write(item.reqBody); err != nil { + t.Fatalf("%s: unexpected error %v", k, err) + } + + // Read response + response, err := ioutil.ReadAll(conn) + if err != nil { + t.Errorf("%s: unexpected error %v", k, err) + continue + } + if !strings.HasSuffix(string(response), successfulResponse) { + t.Errorf("%s: Did not get successful response: %s", k, string(response)) + continue + } + } +} + +func TestProxy(t *testing.T) { + table := []struct { + method string + path string + reqBody string + respBody string + respContentType string + reqNamespace string + }{ + {"GET", "/some/dir", "", "answer", "text/css", "default"}, + {"GET", "/some/dir", "", "answer", "text/html", "default"}, + {"POST", "/some/other/dir", "question", "answer", "text/css", "default"}, + {"PUT", "/some/dir/id", "different question", "answer", "text/css", "default"}, + {"DELETE", "/some/dir/id", "", "ok", "text/css", "default"}, + {"GET", "/some/dir/id", "", "answer", "text/css", "other"}, + {"GET", "/trailing/slash/", "", "answer", "text/css", "default"}, + {"GET", "/", "", "answer", "text/css", "default"}, + } + + for _, item := range table { + downstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + gotBody, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Errorf("%v - unexpected error %v", item.method, err) + } + if e, a := item.reqBody, string(gotBody); e != a { + t.Errorf("%v - expected %v, got %v", item.method, e, a) + } + if e, a := item.path, req.URL.Path; e != a { + t.Errorf("%v - expected %v, got %v", item.method, e, a) + } + w.Header().Set("Content-Type", item.respContentType) + var out io.Writer = w + if strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") { + // The proxier can ask for gzip'd data; we need to provide it with that + // in order to test our processing of that data. + w.Header().Set("Content-Encoding", "gzip") + gzw := gzip.NewWriter(w) + out = gzw + defer gzw.Close() + } + fmt.Fprint(out, item.respBody) + })) + // TODO: Uncomment when fix #19254 + // defer downstreamServer.Close() + + serverURL, _ := url.Parse(downstreamServer.URL) + simpleStorage := &SimpleRESTStorage{ + errors: map[string]error{}, + resourceLocation: serverURL, + expectedResourceNamespace: item.reqNamespace, + } + + namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage}) + namespaceServer := httptest.NewServer(namespaceHandler) + // TODO: Uncomment when fix #19254 + // defer namespaceServer.Close() + + // test each supported URL pattern for finding the redirection resource in the proxy in a particular namespace + serverPatterns := []struct { + server *httptest.Server + proxyTestPattern string + }{ + {namespaceServer, "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/proxy/namespaces/" + item.reqNamespace + "/foo/id" + item.path}, + } + + for _, serverPattern := range serverPatterns { + server := serverPattern.server + proxyTestPattern := serverPattern.proxyTestPattern + req, err := http.NewRequest( + item.method, + server.URL+proxyTestPattern, + strings.NewReader(item.reqBody), + ) + if err != nil { + t.Errorf("%v - unexpected error %v", item.method, err) + continue + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Errorf("%v - unexpected error %v", item.method, err) + continue + } + gotResp, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("%v - unexpected error %v", item.method, err) + } + resp.Body.Close() + if e, a := item.respBody, string(gotResp); e != a { + t.Errorf("%v - expected %v, got %v. url: %#v", item.method, e, a, req.URL) + } + } + } +} + +func TestProxyUpgrade(t *testing.T) { + + localhostPool := x509.NewCertPool() + if !localhostPool.AppendCertsFromPEM(localhostCert) { + t.Errorf("error setting up localhostCert pool") + } + + testcases := map[string]struct { + ServerFunc func(http.Handler) *httptest.Server + ProxyTransport http.RoundTripper + }{ + "http": { + ServerFunc: httptest.NewServer, + ProxyTransport: nil, + }, + "https (invalid hostname + InsecureSkipVerify)": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(exampleCert, exampleKey) + if err != nil { + t.Errorf("https (invalid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}), + }, + "https (valid hostname + RootCAs)": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(localhostCert, localhostKey) + if err != nil { + t.Errorf("https (valid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: &tls.Config{RootCAs: localhostPool}}), + }, + "https (valid hostname + RootCAs + custom dialer)": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(localhostCert, localhostKey) + if err != nil { + t.Errorf("https (valid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{Dial: net.Dial, TLSClientConfig: &tls.Config{RootCAs: localhostPool}}), + }, + } + + for k, tc := range testcases { + + backendServer := tc.ServerFunc(websocket.Handler(func(ws *websocket.Conn) { + defer ws.Close() + body := make([]byte, 5) + ws.Read(body) + ws.Write([]byte("hello " + string(body))) + })) + // TODO: Uncomment when fix #19254 + // defer backendServer.Close() + + serverURL, _ := url.Parse(backendServer.URL) + simpleStorage := &SimpleRESTStorage{ + errors: map[string]error{}, + resourceLocation: serverURL, + resourceLocationTransport: tc.ProxyTransport, + expectedResourceNamespace: "myns", + } + + namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage}) + + server := httptest.NewServer(namespaceHandler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + ws, err := websocket.Dial("ws://"+server.Listener.Addr().String()+"/"+prefix+"/"+newGroupVersion.Group+"/"+newGroupVersion.Version+"/proxy/namespaces/myns/foo/123", "", "http://127.0.0.1/") + if err != nil { + t.Errorf("%s: websocket dial err: %s", k, err) + continue + } + defer ws.Close() + + if _, err := ws.Write([]byte("world")); err != nil { + t.Errorf("%s: write err: %s", k, err) + continue + } + + response := make([]byte, 20) + n, err := ws.Read(response) + if err != nil { + t.Errorf("%s: read err: %s", k, err) + continue + } + if e, a := "hello world", string(response[0:n]); e != a { + t.Errorf("%s: expected '%#v', got '%#v'", k, e, a) + continue + } + } +} + +func TestRedirectOnMissingTrailingSlash(t *testing.T) { + table := []struct { + // The requested path + path string + // The path requested on the proxy server. + proxyServerPath string + // query string + query string + }{ + {"/trailing/slash/", "/trailing/slash/", ""}, + {"/", "/", "test1=value1&test2=value2"}, + // "/" should be added at the end. + {"", "/", "test1=value1&test2=value2"}, + // "/" should not be added at a non-root path. + {"/some/path", "/some/path", ""}, + } + + for _, item := range table { + downstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.Path != item.proxyServerPath { + t.Errorf("Unexpected request on path: %s, expected path: %s, item: %v", req.URL.Path, item.proxyServerPath, item) + } + if req.URL.RawQuery != item.query { + t.Errorf("Unexpected query on url: %s, expected: %s", req.URL.RawQuery, item.query) + } + })) + // TODO: Uncomment when fix #19254 + // defer downstreamServer.Close() + + serverURL, _ := url.Parse(downstreamServer.URL) + simpleStorage := &SimpleRESTStorage{ + errors: map[string]error{}, + resourceLocation: serverURL, + expectedResourceNamespace: "ns", + } + + handler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage}) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + proxyTestPattern := "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/proxy/namespaces/ns/foo/id" + item.path + req, err := http.NewRequest( + "GET", + server.URL+proxyTestPattern+"?"+item.query, + strings.NewReader(""), + ) + if err != nil { + t.Errorf("unexpected error %v", err) + continue + } + // Note: We are using a default client here, that follows redirects. + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Errorf("unexpected error %v", err) + continue + } + if resp.StatusCode != http.StatusOK { + t.Errorf("Unexpected errorCode: %v, expected: 200. Response: %v, item: %v", resp.StatusCode, resp, item) + } + } +} + +// exampleCert was generated from crypto/tls/generate_cert.go with the following command: +// go run generate_cert.go --rsa-bits 512 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var exampleCert = []byte(`-----BEGIN CERTIFICATE----- +MIIBcjCCAR6gAwIBAgIQBOUTYowZaENkZi0faI9DgTALBgkqhkiG9w0BAQswEjEQ +MA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2MDAw +MFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCZ +xfR3sgeHBraGFfF/24tTn4PRVAHOf2UOOxSQRs+aYjNqimFqf/SRIblQgeXdBJDR +gVK5F1Js2zwlehw0bHxRAgMBAAGjUDBOMA4GA1UdDwEB/wQEAwIApDATBgNVHSUE +DDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MBYGA1UdEQQPMA2CC2V4YW1w +bGUuY29tMAsGCSqGSIb3DQEBCwNBAI/mfBB8dm33IpUl+acSyWfL6gX5Wc0FFyVj +dKeesE1XBuPX1My/rzU6Oy/YwX7LOL4FaeNUS6bbL4axSLPKYSs= +-----END CERTIFICATE-----`) + +var exampleKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAJnF9HeyB4cGtoYV8X/bi1Ofg9FUAc5/ZQ47FJBGz5piM2qKYWp/ +9JEhuVCB5d0EkNGBUrkXUmzbPCV6HDRsfFECAwEAAQJBAJLH9yPuButniACTn5L5 +IJQw1mWQt6zBw9eCo41YWkA0866EgjC53aPZaRjXMp0uNJGdIsys2V5rCOOLWN2C +ODECIQDICHsi8QQQ9wpuJy8X5l8MAfxHL+DIqI84wQTeVM91FQIhAMTME8A18/7h +1Ad6drdnxAkuC0tX6Sx0LDozrmen+HFNAiAlcEDrt0RVkIcpOrg7tuhPLQf0oudl +Zvb3Xlj069awSQIgcT15E/43w2+RASifzVNhQ2MCTr1sSA8lL+xzK+REmnUCIBhQ +j4139pf8Re1J50zBxS/JlQfgDQi9sO9pYeiHIxNs +-----END RSA PRIVATE KEY-----`) + +// localhostCert was generated from crypto/tls/generate_cert.go with the following command: +// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var localhostCert = []byte(`-----BEGIN CERTIFICATE----- +MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD +bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj +bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa +IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA +AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud +EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA +AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk +Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA== +-----END CERTIFICATE-----`) + +// localhostKey is the private key for localhostCert. +var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0 +0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV +NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d +AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW +MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD +EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA +1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE= +-----END RSA PRIVATE KEY-----`) diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/resthandler.go b/vendor/k8s.io/kubernetes/pkg/apiserver/resthandler.go new file mode 100644 index 000000000..a292f1520 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/resthandler.go @@ -0,0 +1,1041 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "encoding/json" + "fmt" + "math/rand" + "net/http" + "net/url" + gpath "path" + "strings" + "time" + + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/strategicpatch" + + "github.com/emicklei/go-restful" + "github.com/evanphx/json-patch" + "github.com/golang/glog" +) + +// ContextFunc returns a Context given a request - a context must be returned +type ContextFunc func(req *restful.Request) api.Context + +// ScopeNamer handles accessing names from requests and objects +type ScopeNamer interface { + // Namespace returns the appropriate namespace value from the request (may be empty) or an + // error. + Namespace(req *restful.Request) (namespace string, err error) + // Name returns the name from the request, and an optional namespace value if this is a namespace + // scoped call. An error is returned if the name is not available. + Name(req *restful.Request) (namespace, name string, err error) + // ObjectName returns the namespace and name from an object if they exist, or an error if the object + // does not support names. + ObjectName(obj runtime.Object) (namespace, name string, err error) + // SetSelfLink sets the provided URL onto the object. The method should return nil if the object + // does not support selfLinks. + SetSelfLink(obj runtime.Object, url string) error + // GenerateLink creates a path and query for a given runtime object that represents the canonical path. + GenerateLink(req *restful.Request, obj runtime.Object) (path, query string, err error) + // GenerateLink creates a path and query for a list that represents the canonical path. + GenerateListLink(req *restful.Request) (path, query string, err error) +} + +// RequestScope encapsulates common fields across all RESTful handler methods. +type RequestScope struct { + Namer ScopeNamer + ContextFunc + + Serializer runtime.NegotiatedSerializer + StreamSerializer runtime.NegotiatedSerializer + runtime.ParameterCodec + + Creater runtime.ObjectCreater + Convertor runtime.ObjectConvertor + + Resource unversioned.GroupVersionResource + Kind unversioned.GroupVersionKind + Subresource string +} + +func (scope *RequestScope) err(err error, w http.ResponseWriter, req *http.Request) { + errorNegotiated(err, scope.Serializer, scope.Kind.GroupVersion(), w, req) +} + +// getterFunc performs a get request with the given context and object name. The request +// may be used to deserialize an options object to pass to the getter. +type getterFunc func(ctx api.Context, name string, req *restful.Request) (runtime.Object, error) + +// MaxPatchConflicts is the maximum number of conflicts retry for during a patch operation before returning failure +const MaxPatchConflicts = 5 + +// getResourceHandler is an HTTP handler function for get requests. It delegates to the +// passed-in getterFunc to perform the actual get. +func getResourceHandler(scope RequestScope, getter getterFunc) restful.RouteFunction { + return func(req *restful.Request, res *restful.Response) { + w := res.ResponseWriter + namespace, name, err := scope.Namer.Name(req) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + ctx := scope.ContextFunc(req) + ctx = api.WithNamespace(ctx, namespace) + + result, err := getter(ctx, name, req) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + if err := setSelfLink(result, req, scope.Namer); err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + write(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) + } +} + +// GetResource returns a function that handles retrieving a single resource from a rest.Storage object. +func GetResource(r rest.Getter, e rest.Exporter, scope RequestScope) restful.RouteFunction { + return getResourceHandler(scope, + func(ctx api.Context, name string, req *restful.Request) (runtime.Object, error) { + // For performance tracking purposes. + trace := util.NewTrace("Get " + req.Request.URL.Path) + defer trace.LogIfLong(250 * time.Millisecond) + + // check for export + if values := req.Request.URL.Query(); len(values) > 0 { + // TODO: this is internal version, not unversioned + exports := unversioned.ExportOptions{} + if err := scope.ParameterCodec.DecodeParameters(values, unversioned.GroupVersion{Version: "v1"}, &exports); err != nil { + return nil, err + } + if exports.Export { + if e == nil { + return nil, errors.NewBadRequest(fmt.Sprintf("export of %q is not supported", scope.Resource.Resource)) + } + return e.Export(ctx, name, exports) + } + } + + return r.Get(ctx, name) + }) +} + +// GetResourceWithOptions returns a function that handles retrieving a single resource from a rest.Storage object. +func GetResourceWithOptions(r rest.GetterWithOptions, scope RequestScope) restful.RouteFunction { + return getResourceHandler(scope, + func(ctx api.Context, name string, req *restful.Request) (runtime.Object, error) { + opts, subpath, subpathKey := r.NewGetOptions() + if err := getRequestOptions(req, scope, opts, subpath, subpathKey); err != nil { + return nil, err + } + return r.Get(ctx, name, opts) + }) +} + +func getRequestOptions(req *restful.Request, scope RequestScope, into runtime.Object, subpath bool, subpathKey string) error { + if into == nil { + return nil + } + + query := req.Request.URL.Query() + if subpath { + newQuery := make(url.Values) + for k, v := range query { + newQuery[k] = v + } + newQuery[subpathKey] = []string{req.PathParameter("path")} + query = newQuery + } + return scope.ParameterCodec.DecodeParameters(query, scope.Kind.GroupVersion(), into) +} + +// ConnectResource returns a function that handles a connect request on a rest.Storage object. +func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admission.Interface, restPath string) restful.RouteFunction { + return func(req *restful.Request, res *restful.Response) { + w := res.ResponseWriter + namespace, name, err := scope.Namer.Name(req) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + ctx := scope.ContextFunc(req) + ctx = api.WithNamespace(ctx, namespace) + opts, subpath, subpathKey := connecter.NewConnectOptions() + if err := getRequestOptions(req, scope, opts, subpath, subpathKey); err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + if admit.Handles(admission.Connect) { + connectRequest := &rest.ConnectRequest{ + Name: name, + Options: opts, + ResourcePath: restPath, + } + userInfo, _ := api.UserFrom(ctx) + + err = admit.Admit(admission.NewAttributesRecord(connectRequest, scope.Kind.GroupKind(), namespace, name, scope.Resource.GroupResource(), scope.Subresource, admission.Connect, userInfo)) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + } + handler, err := connecter.Connect(ctx, name, opts, &responder{scope: scope, req: req, res: res}) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + handler.ServeHTTP(w, req.Request) + } +} + +// responder implements rest.Responder for assisting a connector in writing objects or errors. +type responder struct { + scope RequestScope + req *restful.Request + res *restful.Response +} + +func (r *responder) Object(statusCode int, obj runtime.Object) { + write(statusCode, r.scope.Kind.GroupVersion(), r.scope.Serializer, obj, r.res.ResponseWriter, r.req.Request) +} + +func (r *responder) Error(err error) { + r.scope.err(err, r.res.ResponseWriter, r.req.Request) +} + +// ListResource returns a function that handles retrieving a list of resources from a rest.Storage object. +func ListResource(r rest.Lister, rw rest.Watcher, scope RequestScope, forceWatch bool, minRequestTimeout time.Duration) restful.RouteFunction { + return func(req *restful.Request, res *restful.Response) { + // For performance tracking purposes. + trace := util.NewTrace("List " + req.Request.URL.Path) + + w := res.ResponseWriter + + namespace, err := scope.Namer.Namespace(req) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + + // Watches for single objects are routed to this function. + // Treat a /name parameter the same as a field selector entry. + hasName := true + _, name, err := scope.Namer.Name(req) + if err != nil { + hasName = false + } + + ctx := scope.ContextFunc(req) + ctx = api.WithNamespace(ctx, namespace) + + opts := api.ListOptions{} + if err := scope.ParameterCodec.DecodeParameters(req.Request.URL.Query(), scope.Kind.GroupVersion(), &opts); err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + + // transform fields + // TODO: DecodeParametersInto should do this. + if opts.FieldSelector != nil { + fn := func(label, value string) (newLabel, newValue string, err error) { + return scope.Convertor.ConvertFieldLabel(scope.Kind.GroupVersion().String(), scope.Kind.Kind, label, value) + } + if opts.FieldSelector, err = opts.FieldSelector.Transform(fn); err != nil { + // TODO: allow bad request to set field causes based on query parameters + err = errors.NewBadRequest(err.Error()) + scope.err(err, res.ResponseWriter, req.Request) + return + } + } + + if hasName { + // metadata.name is the canonical internal name. + // generic.SelectionPredicate will notice that this is + // a request for a single object and optimize the + // storage query accordingly. + nameSelector := fields.OneTermEqualSelector("metadata.name", name) + if opts.FieldSelector != nil && !opts.FieldSelector.Empty() { + // It doesn't make sense to ask for both a name + // and a field selector, since just the name is + // sufficient to narrow down the request to a + // single object. + scope.err(errors.NewBadRequest("both a name and a field selector provided; please provide one or the other."), res.ResponseWriter, req.Request) + return + } + opts.FieldSelector = nameSelector + } + + if (opts.Watch || forceWatch) && rw != nil { + watcher, err := rw.Watch(ctx, &opts) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + // TODO: Currently we explicitly ignore ?timeout= and use only ?timeoutSeconds=. + timeout := time.Duration(0) + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + if timeout == 0 && minRequestTimeout > 0 { + timeout = time.Duration(float64(minRequestTimeout) * (rand.Float64() + 1.0)) + } + serveWatch(watcher, scope, req, res, timeout) + return + } + + // Log only long List requests (ignore Watch). + defer trace.LogIfLong(500 * time.Millisecond) + trace.Step("About to List from storage") + result, err := r.List(ctx, &opts) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + trace.Step("Listing from storage done") + numberOfItems, err := setListSelfLink(result, req, scope.Namer) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + trace.Step("Self-linking done") + write(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) + trace.Step(fmt.Sprintf("Writing http response done (%d items)", numberOfItems)) + } +} + +func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, includeName bool) restful.RouteFunction { + return func(req *restful.Request, res *restful.Response) { + // For performance tracking purposes. + trace := util.NewTrace("Create " + req.Request.URL.Path) + defer trace.LogIfLong(250 * time.Millisecond) + + w := res.ResponseWriter + + // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) + timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) + + var ( + namespace, name string + err error + ) + if includeName { + namespace, name, err = scope.Namer.Name(req) + } else { + namespace, err = scope.Namer.Namespace(req) + } + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + + ctx := scope.ContextFunc(req) + ctx = api.WithNamespace(ctx, namespace) + + gv := scope.Kind.GroupVersion() + s, err := negotiateInputSerializer(req.Request, scope.Serializer) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + decoder := scope.Serializer.DecoderToVersion(s, unversioned.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal}) + + body, err := readBody(req.Request) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + + defaultGVK := scope.Kind + original := r.New() + trace.Step("About to convert to expected version") + obj, gvk, err := decoder.Decode(body, &defaultGVK, original) + if err != nil { + err = transformDecodeError(typer, err, original, gvk) + scope.err(err, res.ResponseWriter, req.Request) + return + } + if gvk.GroupVersion() != gv { + err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%v)", gvk.GroupVersion().String(), gv.String())) + scope.err(err, res.ResponseWriter, req.Request) + return + } + trace.Step("Conversion done") + + if admit != nil && admit.Handles(admission.Create) { + userInfo, _ := api.UserFrom(ctx) + + err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind.GroupKind(), namespace, name, scope.Resource.GroupResource(), scope.Subresource, admission.Create, userInfo)) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + } + + trace.Step("About to store object in database") + result, err := finishRequest(timeout, func() (runtime.Object, error) { + out, err := r.Create(ctx, name, obj) + if status, ok := out.(*unversioned.Status); ok && err == nil && status.Code == 0 { + status.Code = http.StatusCreated + } + return out, err + }) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + trace.Step("Object stored in database") + + if err := setSelfLink(result, req, scope.Namer); err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + trace.Step("Self-link added") + + write(http.StatusCreated, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) + } +} + +// CreateNamedResource returns a function that will handle a resource creation with name. +func CreateNamedResource(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction { + return createHandler(r, scope, typer, admit, true) +} + +// CreateResource returns a function that will handle a resource creation. +func CreateResource(r rest.Creater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction { + return createHandler(&namedCreaterAdapter{r}, scope, typer, admit, false) +} + +type namedCreaterAdapter struct { + rest.Creater +} + +func (c *namedCreaterAdapter) Create(ctx api.Context, name string, obj runtime.Object) (runtime.Object, error) { + return c.Creater.Create(ctx, obj) +} + +// PatchResource returns a function that will handle a resource patch +// TODO: Eventually PatchResource should just use GuaranteedUpdate and this routine should be a bit cleaner +func PatchResource(r rest.Patcher, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, converter runtime.ObjectConvertor) restful.RouteFunction { + return func(req *restful.Request, res *restful.Response) { + w := res.ResponseWriter + + // TODO: we either want to remove timeout or document it (if we + // document, move timeout out of this function and declare it in + // api_installer) + timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) + + namespace, name, err := scope.Namer.Name(req) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + + ctx := scope.ContextFunc(req) + ctx = api.WithNamespace(ctx, namespace) + + versionedObj, err := converter.ConvertToVersion(r.New(), scope.Kind.GroupVersion().String()) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + + // TODO: handle this in negotiation + contentType := req.HeaderParameter("Content-Type") + // Remove "; charset=" if included in header. + if idx := strings.Index(contentType, ";"); idx > 0 { + contentType = contentType[:idx] + } + patchType := api.PatchType(contentType) + + patchJS, err := readBody(req.Request) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + + s, ok := scope.Serializer.SerializerForMediaType("application/json", nil) + if !ok { + scope.err(fmt.Errorf("no serializer defined for JSON"), res.ResponseWriter, req.Request) + return + } + gv := scope.Kind.GroupVersion() + codec := runtime.NewCodec( + scope.Serializer.EncoderForVersion(s, gv), + scope.Serializer.DecoderToVersion(s, unversioned.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal}), + ) + + updateAdmit := func(updatedObject runtime.Object) error { + if admit != nil && admit.Handles(admission.Update) { + userInfo, _ := api.UserFrom(ctx) + return admit.Admit(admission.NewAttributesRecord(updatedObject, scope.Kind.GroupKind(), namespace, name, scope.Resource.GroupResource(), scope.Subresource, admission.Update, userInfo)) + } + + return nil + } + + result, err := patchResource(ctx, updateAdmit, timeout, versionedObj, r, name, patchType, patchJS, scope.Namer, codec) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + + if err := setSelfLink(result, req, scope.Namer); err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + + write(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) + } + +} + +type updateAdmissionFunc func(updatedObject runtime.Object) error + +// patchResource divides PatchResource for easier unit testing +func patchResource(ctx api.Context, admit updateAdmissionFunc, timeout time.Duration, versionedObj runtime.Object, patcher rest.Patcher, name string, patchType api.PatchType, patchJS []byte, namer ScopeNamer, codec runtime.Codec) (runtime.Object, error) { + namespace := api.NamespaceValue(ctx) + + original, err := patcher.Get(ctx, name) + if err != nil { + return nil, err + } + + originalObjJS, err := runtime.Encode(codec, original) + if err != nil { + return nil, err + } + originalPatchedObjJS, err := getPatchedJS(patchType, originalObjJS, patchJS, versionedObj) + if err != nil { + return nil, err + } + + objToUpdate := patcher.New() + if err := runtime.DecodeInto(codec, originalPatchedObjJS, objToUpdate); err != nil { + return nil, err + } + if err := checkName(objToUpdate, name, namespace, namer); err != nil { + return nil, err + } + + return finishRequest(timeout, func() (runtime.Object, error) { + if err := admit(objToUpdate); err != nil { + return nil, err + } + + // update should never create as previous get would fail + updateObject, _, updateErr := patcher.Update(ctx, objToUpdate) + for i := 0; i < MaxPatchConflicts && (errors.IsConflict(updateErr)); i++ { + + // on a conflict, + // 1. build a strategic merge patch from originalJS and the patchedJS. Different patch types can + // be specified, but a strategic merge patch should be expressive enough handle them. Build the + // patch with this type to handle those cases. + // 2. build a strategic merge patch from originalJS and the currentJS + // 3. ensure no conflicts between the two patches + // 4. apply the #1 patch to the currentJS object + // 5. retry the update + currentObject, err := patcher.Get(ctx, name) + if err != nil { + return nil, err + } + currentObjectJS, err := runtime.Encode(codec, currentObject) + if err != nil { + return nil, err + } + + currentPatch, err := strategicpatch.CreateStrategicMergePatch(originalObjJS, currentObjectJS, versionedObj) + if err != nil { + return nil, err + } + originalPatch, err := strategicpatch.CreateStrategicMergePatch(originalObjJS, originalPatchedObjJS, versionedObj) + if err != nil { + return nil, err + } + + diff1 := make(map[string]interface{}) + if err := json.Unmarshal(originalPatch, &diff1); err != nil { + return nil, err + } + diff2 := make(map[string]interface{}) + if err := json.Unmarshal(currentPatch, &diff2); err != nil { + return nil, err + } + hasConflicts, err := strategicpatch.HasConflicts(diff1, diff2) + if err != nil { + return nil, err + } + if hasConflicts { + glog.V(4).Infof("patchResource failed for resource %s, becauase there is a meaningful conflict.\n diff1=%v\n, diff2=%v\n", name, diff1, diff2) + return updateObject, updateErr + } + + newlyPatchedObjJS, err := getPatchedJS(api.StrategicMergePatchType, currentObjectJS, originalPatch, versionedObj) + if err != nil { + return nil, err + } + if err := runtime.DecodeInto(codec, newlyPatchedObjJS, objToUpdate); err != nil { + return nil, err + } + + if err := admit(objToUpdate); err != nil { + return nil, err + } + + updateObject, _, updateErr = patcher.Update(ctx, objToUpdate) + } + + return updateObject, updateErr + }) +} + +// UpdateResource returns a function that will handle a resource update +func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction { + return func(req *restful.Request, res *restful.Response) { + // For performance tracking purposes. + trace := util.NewTrace("Update " + req.Request.URL.Path) + defer trace.LogIfLong(250 * time.Millisecond) + + w := res.ResponseWriter + + // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) + timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) + + namespace, name, err := scope.Namer.Name(req) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + ctx := scope.ContextFunc(req) + ctx = api.WithNamespace(ctx, namespace) + + body, err := readBody(req.Request) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + + s, err := negotiateInputSerializer(req.Request, scope.Serializer) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + defaultGVK := scope.Kind + original := r.New() + trace.Step("About to convert to expected version") + obj, gvk, err := scope.Serializer.DecoderToVersion(s, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, original) + if err != nil { + err = transformDecodeError(typer, err, original, gvk) + scope.err(err, res.ResponseWriter, req.Request) + return + } + if gvk.GroupVersion() != defaultGVK.GroupVersion() { + err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%s)", gvk.GroupVersion(), defaultGVK.GroupVersion())) + scope.err(err, res.ResponseWriter, req.Request) + return + } + trace.Step("Conversion done") + + if err := checkName(obj, name, namespace, scope.Namer); err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + + if admit != nil && admit.Handles(admission.Update) { + userInfo, _ := api.UserFrom(ctx) + + err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind.GroupKind(), namespace, name, scope.Resource.GroupResource(), scope.Subresource, admission.Update, userInfo)) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + } + + trace.Step("About to store object in database") + wasCreated := false + result, err := finishRequest(timeout, func() (runtime.Object, error) { + obj, created, err := r.Update(ctx, obj) + wasCreated = created + return obj, err + }) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + trace.Step("Object stored in database") + + if err := setSelfLink(result, req, scope.Namer); err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + trace.Step("Self-link added") + + status := http.StatusOK + if wasCreated { + status = http.StatusCreated + } + write(status, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) + } +} + +// DeleteResource returns a function that will handle a resource deletion +func DeleteResource(r rest.GracefulDeleter, checkBody bool, scope RequestScope, admit admission.Interface) restful.RouteFunction { + return func(req *restful.Request, res *restful.Response) { + // For performance tracking purposes. + trace := util.NewTrace("Delete " + req.Request.URL.Path) + defer trace.LogIfLong(250 * time.Millisecond) + + w := res.ResponseWriter + + // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) + timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) + + namespace, name, err := scope.Namer.Name(req) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + ctx := scope.ContextFunc(req) + ctx = api.WithNamespace(ctx, namespace) + + options := &api.DeleteOptions{} + if checkBody { + body, err := readBody(req.Request) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + if len(body) > 0 { + s, err := negotiateInputSerializer(req.Request, scope.Serializer) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + defaultGVK := scope.Kind.GroupVersion().WithKind("DeleteOptions") + obj, _, err := scope.Serializer.DecoderToVersion(s, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + if obj != options { + scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), res.ResponseWriter, req.Request) + return + } + } + } + + if admit != nil && admit.Handles(admission.Delete) { + userInfo, _ := api.UserFrom(ctx) + + err = admit.Admit(admission.NewAttributesRecord(nil, scope.Kind.GroupKind(), namespace, name, scope.Resource.GroupResource(), scope.Subresource, admission.Delete, userInfo)) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + } + + trace.Step("About do delete object from database") + result, err := finishRequest(timeout, func() (runtime.Object, error) { + return r.Delete(ctx, name, options) + }) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + trace.Step("Object deleted from database") + + // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid + // object with the response. + if result == nil { + result = &unversioned.Status{ + Status: unversioned.StatusSuccess, + Code: http.StatusOK, + Details: &unversioned.StatusDetails{ + Name: name, + Kind: scope.Kind.Kind, + }, + } + } else { + // when a non-status response is returned, set the self link + if _, ok := result.(*unversioned.Status); !ok { + if err := setSelfLink(result, req, scope.Namer); err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + } + } + write(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) + } +} + +// DeleteCollection returns a function that will handle a collection deletion +func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestScope, admit admission.Interface) restful.RouteFunction { + return func(req *restful.Request, res *restful.Response) { + w := res.ResponseWriter + + // TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer) + timeout := parseTimeout(req.Request.URL.Query().Get("timeout")) + + namespace, err := scope.Namer.Namespace(req) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + + ctx := scope.ContextFunc(req) + ctx = api.WithNamespace(ctx, namespace) + + if admit != nil && admit.Handles(admission.Delete) { + userInfo, _ := api.UserFrom(ctx) + + err = admit.Admit(admission.NewAttributesRecord(nil, scope.Kind.GroupKind(), namespace, "", scope.Resource.GroupResource(), scope.Subresource, admission.Delete, userInfo)) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + } + + listOptions := api.ListOptions{} + if err := scope.ParameterCodec.DecodeParameters(req.Request.URL.Query(), scope.Kind.GroupVersion(), &listOptions); err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + + // transform fields + // TODO: DecodeParametersInto should do this. + if listOptions.FieldSelector != nil { + fn := func(label, value string) (newLabel, newValue string, err error) { + return scope.Convertor.ConvertFieldLabel(scope.Kind.GroupVersion().String(), scope.Kind.Kind, label, value) + } + if listOptions.FieldSelector, err = listOptions.FieldSelector.Transform(fn); err != nil { + // TODO: allow bad request to set field causes based on query parameters + err = errors.NewBadRequest(err.Error()) + scope.err(err, res.ResponseWriter, req.Request) + return + } + } + + options := &api.DeleteOptions{} + if checkBody { + body, err := readBody(req.Request) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + if len(body) > 0 { + s, err := negotiateInputSerializer(req.Request, scope.Serializer) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + defaultGVK := scope.Kind.GroupVersion().WithKind("DeleteOptions") + obj, _, err := scope.Serializer.DecoderToVersion(s, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + if obj != options { + scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), res.ResponseWriter, req.Request) + return + } + } + } + + result, err := finishRequest(timeout, func() (runtime.Object, error) { + return r.DeleteCollection(ctx, options, &listOptions) + }) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + + // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid + // object with the response. + if result == nil { + result = &unversioned.Status{ + Status: unversioned.StatusSuccess, + Code: http.StatusOK, + Details: &unversioned.StatusDetails{ + Kind: scope.Kind.Kind, + }, + } + } else { + // when a non-status response is returned, set the self link + if _, ok := result.(*unversioned.Status); !ok { + if _, err := setListSelfLink(result, req, scope.Namer); err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + } + } + writeNegotiated(scope.Serializer, scope.Kind.GroupVersion(), w, req.Request, http.StatusOK, result) + } +} + +// resultFunc is a function that returns a rest result and can be run in a goroutine +type resultFunc func() (runtime.Object, error) + +// finishRequest makes a given resultFunc asynchronous and handles errors returned by the response. +// Any api.Status object returned is considered an "error", which interrupts the normal response flow. +func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object, err error) { + // these channels need to be buffered to prevent the goroutine below from hanging indefinitely + // when the select statement reads something other than the one the goroutine sends on. + ch := make(chan runtime.Object, 1) + errCh := make(chan error, 1) + panicCh := make(chan interface{}, 1) + go func() { + // panics don't cross goroutine boundaries, so we have to handle ourselves + defer utilruntime.HandleCrash(func(panicReason interface{}) { + // Propagate to parent goroutine + panicCh <- panicReason + }) + + if result, err := fn(); err != nil { + errCh <- err + } else { + ch <- result + } + }() + + select { + case result = <-ch: + if status, ok := result.(*unversioned.Status); ok { + return nil, errors.FromObject(status) + } + return result, nil + case err = <-errCh: + return nil, err + case p := <-panicCh: + panic(p) + case <-time.After(timeout): + return nil, errors.NewTimeoutError("request did not complete within allowed duration", 0) + } +} + +// transformDecodeError adds additional information when a decode fails. +func transformDecodeError(typer runtime.ObjectTyper, baseErr error, into runtime.Object, gvk *unversioned.GroupVersionKind) error { + objGVK, err := typer.ObjectKind(into) + if err != nil { + return err + } + if gvk != nil && len(gvk.Kind) > 0 { + return errors.NewBadRequest(fmt.Sprintf("%s in version %q cannot be handled as a %s: %v", gvk.Kind, gvk.Version, objGVK.Kind, baseErr)) + } + return errors.NewBadRequest(fmt.Sprintf("the object provided is unrecognized (must be of type %s): %v", objGVK.Kind, baseErr)) +} + +// setSelfLink sets the self link of an object (or the child items in a list) to the base URL of the request +// plus the path and query generated by the provided linkFunc +func setSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer) error { + // TODO: SelfLink generation should return a full URL? + path, query, err := namer.GenerateLink(req, obj) + if err != nil { + return nil + } + + newURL := *req.Request.URL + // use only canonical paths + newURL.Path = gpath.Clean(path) + newURL.RawQuery = query + newURL.Fragment = "" + + return namer.SetSelfLink(obj, newURL.String()) +} + +// checkName checks the provided name against the request +func checkName(obj runtime.Object, name, namespace string, namer ScopeNamer) error { + if objNamespace, objName, err := namer.ObjectName(obj); err == nil { + if err != nil { + return err + } + if objName != name { + return errors.NewBadRequest(fmt.Sprintf( + "the name of the object (%s) does not match the name on the URL (%s)", objName, name)) + } + if len(namespace) > 0 { + if len(objNamespace) > 0 && objNamespace != namespace { + return errors.NewBadRequest(fmt.Sprintf( + "the namespace of the object (%s) does not match the namespace on the request (%s)", objNamespace, namespace)) + } + } + } + return nil +} + +// setListSelfLink sets the self link of a list to the base URL, then sets the self links +// on all child objects returned. Returns the number of items in the list. +func setListSelfLink(obj runtime.Object, req *restful.Request, namer ScopeNamer) (int, error) { + if !meta.IsListType(obj) { + return 0, nil + } + + // TODO: List SelfLink generation should return a full URL? + path, query, err := namer.GenerateListLink(req) + if err != nil { + return 0, err + } + newURL := *req.Request.URL + newURL.Path = path + newURL.RawQuery = query + // use the path that got us here + newURL.Fragment = "" + if err := namer.SetSelfLink(obj, newURL.String()); err != nil { + glog.V(4).Infof("Unable to set self link on object: %v", err) + } + + // Set self-link of objects in the list. + items, err := meta.ExtractList(obj) + if err != nil { + return 0, err + } + for i := range items { + if err := setSelfLink(items[i], req, namer); err != nil { + return len(items), err + } + } + return len(items), meta.SetList(obj, items) +} + +func getPatchedJS(patchType api.PatchType, originalJS, patchJS []byte, obj runtime.Object) ([]byte, error) { + switch patchType { + case api.JSONPatchType: + patchObj, err := jsonpatch.DecodePatch(patchJS) + if err != nil { + return nil, err + } + return patchObj.Apply(originalJS) + case api.MergePatchType: + return jsonpatch.MergePatch(originalJS, patchJS) + case api.StrategicMergePatchType: + return strategicpatch.StrategicMergePatchData(originalJS, patchJS, obj) + default: + // only here as a safety net - go-restful filters content-type + return nil, fmt.Errorf("unknown Content-Type header for patch: %v", patchType) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/resthandler_test.go b/vendor/k8s.io/kubernetes/pkg/apiserver/resthandler_test.go new file mode 100644 index 000000000..30e8b9c2c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/resthandler_test.go @@ -0,0 +1,425 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "errors" + "fmt" + "reflect" + "testing" + "time" + + "github.com/emicklei/go-restful" + "github.com/evanphx/json-patch" + + "k8s.io/kubernetes/pkg/api" + apierrors "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/diff" + "k8s.io/kubernetes/pkg/util/strategicpatch" +) + +type testPatchType struct { + unversioned.TypeMeta `json:",inline"` + + TestPatchSubType `json:",inline"` +} + +// We explicitly make it public as private types doesn't +// work correctly with json inlined types. +type TestPatchSubType struct { + StringField string `json:"theField"` +} + +func (obj *testPatchType) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } + +func TestPatchAnonymousField(t *testing.T) { + originalJS := `{"kind":"testPatchType","theField":"my-value"}` + patch := `{"theField": "changed!"}` + expectedJS := `{"kind":"testPatchType","theField":"changed!"}` + + actualBytes, err := getPatchedJS(api.StrategicMergePatchType, []byte(originalJS), []byte(patch), &testPatchType{}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if string(actualBytes) != expectedJS { + t.Errorf("expected %v, got %v", expectedJS, string(actualBytes)) + } +} + +type testPatcher struct { + // startingPod is used for the first Get + startingPod *api.Pod + + // updatePod is the pod that is used for conflict comparison and returned for the SECOND Get + updatePod *api.Pod + + numGets int +} + +func (p *testPatcher) New() runtime.Object { + return &api.Pod{} +} + +func (p *testPatcher) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + inPod := obj.(*api.Pod) + if inPod.ResourceVersion != p.updatePod.ResourceVersion { + return nil, false, apierrors.NewConflict(api.Resource("pods"), inPod.Name, fmt.Errorf("existing %v, new %v", p.updatePod.ResourceVersion, inPod.ResourceVersion)) + } + + return inPod, false, nil +} + +func (p *testPatcher) Get(ctx api.Context, name string) (runtime.Object, error) { + if p.numGets > 0 { + return p.updatePod, nil + } + p.numGets++ + + return p.startingPod, nil +} + +type testNamer struct { + namespace string + name string +} + +func (p *testNamer) Namespace(req *restful.Request) (namespace string, err error) { + return p.namespace, nil +} + +// Name returns the name from the request, and an optional namespace value if this is a namespace +// scoped call. An error is returned if the name is not available. +func (p *testNamer) Name(req *restful.Request) (namespace, name string, err error) { + return p.namespace, p.name, nil +} + +// ObjectName returns the namespace and name from an object if they exist, or an error if the object +// does not support names. +func (p *testNamer) ObjectName(obj runtime.Object) (namespace, name string, err error) { + return p.namespace, p.name, nil +} + +// SetSelfLink sets the provided URL onto the object. The method should return nil if the object +// does not support selfLinks. +func (p *testNamer) SetSelfLink(obj runtime.Object, url string) error { + return errors.New("not implemented") +} + +// GenerateLink creates a path and query for a given runtime object that represents the canonical path. +func (p *testNamer) GenerateLink(req *restful.Request, obj runtime.Object) (path, query string, err error) { + return "", "", errors.New("not implemented") +} + +// GenerateLink creates a path and query for a list that represents the canonical path. +func (p *testNamer) GenerateListLink(req *restful.Request) (path, query string, err error) { + return "", "", errors.New("not implemented") +} + +type patchTestCase struct { + name string + + // admission chain to use, nil is fine + admit updateAdmissionFunc + + // startingPod is used for the first Get + startingPod *api.Pod + // changedPod is the "destination" pod for the patch. The test will create a patch from the startingPod to the changedPod + // to use when calling the patch operation + changedPod *api.Pod + // updatePod is the pod that is used for conflict comparison and returned for the SECOND Get + updatePod *api.Pod + + // expectedPod is the pod that you expect to get back after the patch is complete + expectedPod *api.Pod + expectedError string +} + +func (tc *patchTestCase) Run(t *testing.T) { + t.Logf("Starting test %s", tc.name) + + namespace := tc.startingPod.Namespace + name := tc.startingPod.Name + + codec := testapi.Default.Codec() + admit := tc.admit + if admit == nil { + admit = func(updatedObject runtime.Object) error { + return nil + } + } + + testPatcher := &testPatcher{} + testPatcher.startingPod = tc.startingPod + testPatcher.updatePod = tc.updatePod + + ctx := api.NewDefaultContext() + ctx = api.WithNamespace(ctx, namespace) + + namer := &testNamer{namespace, name} + + versionedObj, err := api.Scheme.ConvertToVersion(&api.Pod{}, "v1") + if err != nil { + t.Errorf("%s: unexpected error: %v", tc.name, err) + return + } + + for _, patchType := range []api.PatchType{api.JSONPatchType, api.MergePatchType, api.StrategicMergePatchType} { + // TODO SUPPORT THIS! + if patchType == api.JSONPatchType { + continue + } + t.Logf("Working with patchType %v", patchType) + + originalObjJS, err := runtime.Encode(codec, tc.startingPod) + if err != nil { + t.Errorf("%s: unexpected error: %v", tc.name, err) + return + } + changedJS, err := runtime.Encode(codec, tc.changedPod) + if err != nil { + t.Errorf("%s: unexpected error: %v", tc.name, err) + return + } + + patch := []byte{} + switch patchType { + case api.JSONPatchType: + continue + + case api.StrategicMergePatchType: + patch, err = strategicpatch.CreateStrategicMergePatch(originalObjJS, changedJS, versionedObj) + if err != nil { + t.Errorf("%s: unexpected error: %v", tc.name, err) + return + } + + case api.MergePatchType: + patch, err = jsonpatch.CreateMergePatch(originalObjJS, changedJS) + if err != nil { + t.Errorf("%s: unexpected error: %v", tc.name, err) + return + } + + } + + resultObj, err := patchResource(ctx, admit, 1*time.Second, versionedObj, testPatcher, name, patchType, patch, namer, codec) + if len(tc.expectedError) != 0 { + if err == nil || err.Error() != tc.expectedError { + t.Errorf("%s: expected error %v, but got %v", tc.name, tc.expectedError, err) + return + } + } else { + if err != nil { + t.Errorf("%s: unexpected error: %v", tc.name, err) + return + } + } + + if tc.expectedPod == nil { + if resultObj != nil { + t.Errorf("%s: unexpected result: %v", tc.name, resultObj) + } + return + } + + resultPod := resultObj.(*api.Pod) + + // roundtrip to get defaulting + expectedJS, err := runtime.Encode(codec, tc.expectedPod) + if err != nil { + t.Errorf("%s: unexpected error: %v", tc.name, err) + return + } + expectedObj, err := runtime.Decode(codec, expectedJS) + if err != nil { + t.Errorf("%s: unexpected error: %v", tc.name, err) + return + } + reallyExpectedPod := expectedObj.(*api.Pod) + + if !reflect.DeepEqual(*reallyExpectedPod, *resultPod) { + t.Errorf("%s mismatch: %v\n", tc.name, diff.ObjectGoPrintDiff(reallyExpectedPod, resultPod)) + return + } + } + +} + +func TestPatchResourceWithVersionConflict(t *testing.T) { + namespace := "bar" + name := "foo" + fifteen := int64(15) + thirty := int64(30) + + tc := &patchTestCase{ + name: "TestPatchResourceWithVersionConflict", + + startingPod: &api.Pod{}, + changedPod: &api.Pod{}, + updatePod: &api.Pod{}, + + expectedPod: &api.Pod{}, + } + + tc.startingPod.Name = name + tc.startingPod.Namespace = namespace + tc.startingPod.ResourceVersion = "1" + tc.startingPod.APIVersion = "v1" + tc.startingPod.Spec.ActiveDeadlineSeconds = &fifteen + + tc.changedPod.Name = name + tc.changedPod.Namespace = namespace + tc.changedPod.ResourceVersion = "1" + tc.changedPod.APIVersion = "v1" + tc.changedPod.Spec.ActiveDeadlineSeconds = &thirty + + tc.updatePod.Name = name + tc.updatePod.Namespace = namespace + tc.updatePod.ResourceVersion = "2" + tc.updatePod.APIVersion = "v1" + tc.updatePod.Spec.ActiveDeadlineSeconds = &fifteen + tc.updatePod.Spec.NodeName = "anywhere" + + tc.expectedPod.Name = name + tc.expectedPod.Namespace = namespace + tc.expectedPod.ResourceVersion = "2" + tc.expectedPod.Spec.ActiveDeadlineSeconds = &thirty + tc.expectedPod.Spec.NodeName = "anywhere" + + tc.Run(t) +} + +func TestPatchResourceWithConflict(t *testing.T) { + namespace := "bar" + name := "foo" + + tc := &patchTestCase{ + name: "TestPatchResourceWithConflict", + + startingPod: &api.Pod{}, + changedPod: &api.Pod{}, + updatePod: &api.Pod{}, + + expectedError: `Operation cannot be fulfilled on pods "foo": existing 2, new 1`, + } + + tc.startingPod.Name = name + tc.startingPod.Namespace = namespace + tc.startingPod.ResourceVersion = "1" + tc.startingPod.APIVersion = "v1" + tc.startingPod.Spec.NodeName = "here" + + tc.changedPod.Name = name + tc.changedPod.Namespace = namespace + tc.changedPod.ResourceVersion = "1" + tc.changedPod.APIVersion = "v1" + tc.changedPod.Spec.NodeName = "there" + + tc.updatePod.Name = name + tc.updatePod.Namespace = namespace + tc.updatePod.ResourceVersion = "2" + tc.updatePod.APIVersion = "v1" + tc.updatePod.Spec.NodeName = "anywhere" + + tc.Run(t) +} + +func TestPatchWithAdmissionRejection(t *testing.T) { + namespace := "bar" + name := "foo" + fifteen := int64(15) + thirty := int64(30) + + tc := &patchTestCase{ + name: "TestPatchWithAdmissionRejection", + + admit: func(updatedObject runtime.Object) error { + return errors.New("admission failure") + }, + + startingPod: &api.Pod{}, + changedPod: &api.Pod{}, + updatePod: &api.Pod{}, + + expectedError: "admission failure", + } + + tc.startingPod.Name = name + tc.startingPod.Namespace = namespace + tc.startingPod.ResourceVersion = "1" + tc.startingPod.APIVersion = "v1" + tc.startingPod.Spec.ActiveDeadlineSeconds = &fifteen + + tc.changedPod.Name = name + tc.changedPod.Namespace = namespace + tc.changedPod.ResourceVersion = "1" + tc.changedPod.APIVersion = "v1" + tc.changedPod.Spec.ActiveDeadlineSeconds = &thirty + + tc.Run(t) +} + +func TestPatchWithVersionConflictThenAdmissionFailure(t *testing.T) { + namespace := "bar" + name := "foo" + fifteen := int64(15) + thirty := int64(30) + seen := false + + tc := &patchTestCase{ + name: "TestPatchWithVersionConflictThenAdmissionFailure", + + admit: func(updatedObject runtime.Object) error { + if seen { + return errors.New("admission failure") + } + + seen = true + return nil + }, + + startingPod: &api.Pod{}, + changedPod: &api.Pod{}, + updatePod: &api.Pod{}, + + expectedError: "admission failure", + } + + tc.startingPod.Name = name + tc.startingPod.Namespace = namespace + tc.startingPod.ResourceVersion = "1" + tc.startingPod.APIVersion = "v1" + tc.startingPod.Spec.ActiveDeadlineSeconds = &fifteen + + tc.changedPod.Name = name + tc.changedPod.Namespace = namespace + tc.changedPod.ResourceVersion = "1" + tc.changedPod.APIVersion = "v1" + tc.changedPod.Spec.ActiveDeadlineSeconds = &thirty + + tc.updatePod.Name = name + tc.updatePod.Namespace = namespace + tc.updatePod.ResourceVersion = "2" + tc.updatePod.APIVersion = "v1" + tc.updatePod.Spec.ActiveDeadlineSeconds = &fifteen + tc.updatePod.Spec.NodeName = "anywhere" + + tc.Run(t) +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/testing/types.generated.go b/vendor/k8s.io/kubernetes/pkg/apiserver/testing/types.generated.go new file mode 100644 index 000000000..ea4db3df8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/testing/types.generated.go @@ -0,0 +1,1611 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// ************************************************************ +// DO NOT EDIT. +// THIS FILE IS AUTO-GENERATED BY codecgen. +// ************************************************************ + +package testing + +import ( + "errors" + "fmt" + codec1978 "github.com/ugorji/go/codec" + pkg2_api "k8s.io/kubernetes/pkg/api" + pkg1_unversioned "k8s.io/kubernetes/pkg/api/unversioned" + pkg3_types "k8s.io/kubernetes/pkg/types" + "reflect" + "runtime" + time "time" +) + +const ( + // ----- content types ---- + codecSelferC_UTF81234 = 1 + codecSelferC_RAW1234 = 0 + // ----- value types used ---- + codecSelferValueTypeArray1234 = 10 + codecSelferValueTypeMap1234 = 9 + // ----- containerStateValues ---- + codecSelfer_containerMapKey1234 = 2 + codecSelfer_containerMapValue1234 = 3 + codecSelfer_containerMapEnd1234 = 4 + codecSelfer_containerArrayElem1234 = 6 + codecSelfer_containerArrayEnd1234 = 7 +) + +var ( + codecSelferBitsize1234 = uint8(reflect.TypeOf(uint(0)).Bits()) + codecSelferOnlyMapOrArrayEncodeToStructErr1234 = errors.New(`only encoded map or array can be decoded into a struct`) +) + +type codecSelfer1234 struct{} + +func init() { + if codec1978.GenVersion != 5 { + _, file, _, _ := runtime.Caller(0) + err := fmt.Errorf("codecgen version mismatch: current: %v, need %v. Re-generate file: %v", + 5, codec1978.GenVersion, file) + panic(err) + } + if false { // reference the types, but skip this branch at build/run time + var v0 pkg2_api.ObjectMeta + var v1 pkg1_unversioned.TypeMeta + var v2 pkg3_types.UID + var v3 time.Time + _, _, _, _ = v0, v1, v2, v3 + } +} + +func (x *Simple) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [5]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[1] = x.Other != "" + yyq2[2] = len(x.Labels) != 0 + yyq2[3] = x.Kind != "" + yyq2[4] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(5) + } else { + yynn2 = 1 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy4 := &x.ObjectMeta + yy4.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("metadata")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy6 := &x.ObjectMeta + yy6.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + yym9 := z.EncBinary() + _ = yym9 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Other)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("other")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Other)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + if x.Labels == nil { + r.EncodeNil() + } else { + yym12 := z.EncBinary() + _ = yym12 + if false { + } else { + z.F.EncMapStringStringV(x.Labels, false, e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("labels")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Labels == nil { + r.EncodeNil() + } else { + yym13 := z.EncBinary() + _ = yym13 + if false { + } else { + z.F.EncMapStringStringV(x.Labels, false, e) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym15 := z.EncBinary() + _ = yym15 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym16 := z.EncBinary() + _ = yym16 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[4] { + yym18 := z.EncBinary() + _ = yym18 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym19 := z.EncBinary() + _ = yym19 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *Simple) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *Simple) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "metadata": + if r.TryDecodeAsNil() { + x.ObjectMeta = pkg2_api.ObjectMeta{} + } else { + yyv4 := &x.ObjectMeta + yyv4.CodecDecodeSelf(d) + } + case "other": + if r.TryDecodeAsNil() { + x.Other = "" + } else { + x.Other = string(r.DecodeString()) + } + case "labels": + if r.TryDecodeAsNil() { + x.Labels = nil + } else { + yyv6 := &x.Labels + yym7 := z.DecBinary() + _ = yym7 + if false { + } else { + z.F.DecMapStringStringX(yyv6, false, d) + } + } + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *Simple) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj10 int + var yyb10 bool + var yyhl10 bool = l >= 0 + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ObjectMeta = pkg2_api.ObjectMeta{} + } else { + yyv11 := &x.ObjectMeta + yyv11.CodecDecodeSelf(d) + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Other = "" + } else { + x.Other = string(r.DecodeString()) + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Labels = nil + } else { + yyv13 := &x.Labels + yym14 := z.DecBinary() + _ = yym14 + if false { + } else { + z.F.DecMapStringStringX(yyv13, false, d) + } + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj10-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *SimpleRoot) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [5]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[1] = x.Other != "" + yyq2[2] = len(x.Labels) != 0 + yyq2[3] = x.Kind != "" + yyq2[4] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(5) + } else { + yynn2 = 1 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy4 := &x.ObjectMeta + yy4.CodecEncodeSelf(e) + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("metadata")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy6 := &x.ObjectMeta + yy6.CodecEncodeSelf(e) + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + yym9 := z.EncBinary() + _ = yym9 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Other)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("other")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Other)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + if x.Labels == nil { + r.EncodeNil() + } else { + yym12 := z.EncBinary() + _ = yym12 + if false { + } else { + z.F.EncMapStringStringV(x.Labels, false, e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("labels")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Labels == nil { + r.EncodeNil() + } else { + yym13 := z.EncBinary() + _ = yym13 + if false { + } else { + z.F.EncMapStringStringV(x.Labels, false, e) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym15 := z.EncBinary() + _ = yym15 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym16 := z.EncBinary() + _ = yym16 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[4] { + yym18 := z.EncBinary() + _ = yym18 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym19 := z.EncBinary() + _ = yym19 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *SimpleRoot) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *SimpleRoot) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "metadata": + if r.TryDecodeAsNil() { + x.ObjectMeta = pkg2_api.ObjectMeta{} + } else { + yyv4 := &x.ObjectMeta + yyv4.CodecDecodeSelf(d) + } + case "other": + if r.TryDecodeAsNil() { + x.Other = "" + } else { + x.Other = string(r.DecodeString()) + } + case "labels": + if r.TryDecodeAsNil() { + x.Labels = nil + } else { + yyv6 := &x.Labels + yym7 := z.DecBinary() + _ = yym7 + if false { + } else { + z.F.DecMapStringStringX(yyv6, false, d) + } + } + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *SimpleRoot) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj10 int + var yyb10 bool + var yyhl10 bool = l >= 0 + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ObjectMeta = pkg2_api.ObjectMeta{} + } else { + yyv11 := &x.ObjectMeta + yyv11.CodecDecodeSelf(d) + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Other = "" + } else { + x.Other = string(r.DecodeString()) + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Labels = nil + } else { + yyv13 := &x.Labels + yym14 := z.DecBinary() + _ = yym14 + if false { + } else { + z.F.DecMapStringStringX(yyv13, false, d) + } + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj10-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *SimpleGetOptions) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [5]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[3] = x.Kind != "" + yyq2[4] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(5) + } else { + yynn2 = 3 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym4 := z.EncBinary() + _ = yym4 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Param1)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("param1")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym5 := z.EncBinary() + _ = yym5 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Param1)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym7 := z.EncBinary() + _ = yym7 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Param2)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("param2")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym8 := z.EncBinary() + _ = yym8 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Param2)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Path)) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("atAPath")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym11 := z.EncBinary() + _ = yym11 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Path)) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym13 := z.EncBinary() + _ = yym13 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym14 := z.EncBinary() + _ = yym14 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[4] { + yym16 := z.EncBinary() + _ = yym16 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[4] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym17 := z.EncBinary() + _ = yym17 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *SimpleGetOptions) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *SimpleGetOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "param1": + if r.TryDecodeAsNil() { + x.Param1 = "" + } else { + x.Param1 = string(r.DecodeString()) + } + case "param2": + if r.TryDecodeAsNil() { + x.Param2 = "" + } else { + x.Param2 = string(r.DecodeString()) + } + case "atAPath": + if r.TryDecodeAsNil() { + x.Path = "" + } else { + x.Path = string(r.DecodeString()) + } + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *SimpleGetOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj9 int + var yyb9 bool + var yyhl9 bool = l >= 0 + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Param1 = "" + } else { + x.Param1 = string(r.DecodeString()) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Param2 = "" + } else { + x.Param2 = string(r.DecodeString()) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Path = "" + } else { + x.Path = string(r.DecodeString()) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj9++ + if yyhl9 { + yyb9 = yyj9 > l + } else { + yyb9 = r.CheckBreak() + } + if yyb9 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj9-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x *SimpleList) CodecEncodeSelf(e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + if x == nil { + r.EncodeNil() + } else { + yym1 := z.EncBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.EncExt(x) { + } else { + yysep2 := !z.EncBinary() + yy2arr2 := z.EncBasicHandle().StructToArray + var yyq2 [4]bool + _, _, _ = yysep2, yyq2, yy2arr2 + const yyr2 bool = false + yyq2[1] = len(x.Items) != 0 + yyq2[2] = x.Kind != "" + yyq2[3] = x.APIVersion != "" + var yynn2 int + if yyr2 || yy2arr2 { + r.EncodeArrayStart(4) + } else { + yynn2 = 1 + for _, b := range yyq2 { + if b { + yynn2++ + } + } + r.EncodeMapStart(yynn2) + yynn2 = 0 + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy4 := &x.ListMeta + yym5 := z.EncBinary() + _ = yym5 + if false { + } else if z.HasExtensions() && z.EncExt(yy4) { + } else { + z.EncFallback(yy4) + } + } else { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("metadata")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yy6 := &x.ListMeta + yym7 := z.EncBinary() + _ = yym7 + if false { + } else if z.HasExtensions() && z.EncExt(yy6) { + } else { + z.EncFallback(yy6) + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[1] { + if x.Items == nil { + r.EncodeNil() + } else { + yym9 := z.EncBinary() + _ = yym9 + if false { + } else { + h.encSliceSimple(([]Simple)(x.Items), e) + } + } + } else { + r.EncodeNil() + } + } else { + if yyq2[1] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("items")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + if x.Items == nil { + r.EncodeNil() + } else { + yym10 := z.EncBinary() + _ = yym10 + if false { + } else { + h.encSliceSimple(([]Simple)(x.Items), e) + } + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[2] { + yym12 := z.EncBinary() + _ = yym12 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[2] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("kind")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym13 := z.EncBinary() + _ = yym13 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.Kind)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym15 := z.EncBinary() + _ = yym15 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("apiVersion")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym16 := z.EncBinary() + _ = yym16 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion)) + } + } + } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + z.EncSendContainerState(codecSelfer_containerMapEnd1234) + } + } + } +} + +func (x *SimpleList) CodecDecodeSelf(d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + yym1 := z.DecBinary() + _ = yym1 + if false { + } else if z.HasExtensions() && z.DecExt(x) { + } else { + yyct2 := r.ContainerType() + if yyct2 == codecSelferValueTypeMap1234 { + yyl2 := r.ReadMapStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerMapEnd1234) + } else { + x.codecDecodeSelfFromMap(yyl2, d) + } + } else if yyct2 == codecSelferValueTypeArray1234 { + yyl2 := r.ReadArrayStart() + if yyl2 == 0 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + } else { + x.codecDecodeSelfFromArray(yyl2, d) + } + } else { + panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234) + } + } +} + +func (x *SimpleList) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yys3Slc = z.DecScratchBuffer() // default slice to decode into + _ = yys3Slc + var yyhl3 bool = l >= 0 + for yyj3 := 0; ; yyj3++ { + if yyhl3 { + if yyj3 >= l { + break + } + } else { + if r.CheckBreak() { + break + } + } + z.DecSendContainerState(codecSelfer_containerMapKey1234) + yys3Slc = r.DecodeBytes(yys3Slc, true, true) + yys3 := string(yys3Slc) + z.DecSendContainerState(codecSelfer_containerMapValue1234) + switch yys3 { + case "metadata": + if r.TryDecodeAsNil() { + x.ListMeta = pkg1_unversioned.ListMeta{} + } else { + yyv4 := &x.ListMeta + yym5 := z.DecBinary() + _ = yym5 + if false { + } else if z.HasExtensions() && z.DecExt(yyv4) { + } else { + z.DecFallback(yyv4, false) + } + } + case "items": + if r.TryDecodeAsNil() { + x.Items = nil + } else { + yyv6 := &x.Items + yym7 := z.DecBinary() + _ = yym7 + if false { + } else { + h.decSliceSimple((*[]Simple)(yyv6), d) + } + } + case "kind": + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + case "apiVersion": + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + default: + z.DecStructFieldNotFound(-1, yys3) + } // end switch yys3 + } // end for yyj3 + z.DecSendContainerState(codecSelfer_containerMapEnd1234) +} + +func (x *SimpleList) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + var yyj10 int + var yyb10 bool + var yyhl10 bool = l >= 0 + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.ListMeta = pkg1_unversioned.ListMeta{} + } else { + yyv11 := &x.ListMeta + yym12 := z.DecBinary() + _ = yym12 + if false { + } else if z.HasExtensions() && z.DecExt(yyv11) { + } else { + z.DecFallback(yyv11, false) + } + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Items = nil + } else { + yyv13 := &x.Items + yym14 := z.DecBinary() + _ = yym14 + if false { + } else { + h.decSliceSimple((*[]Simple)(yyv13), d) + } + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.Kind = "" + } else { + x.Kind = string(r.DecodeString()) + } + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.APIVersion = "" + } else { + x.APIVersion = string(r.DecodeString()) + } + for { + yyj10++ + if yyhl10 { + yyb10 = yyj10 > l + } else { + yyb10 = r.CheckBreak() + } + if yyb10 { + break + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + z.DecStructFieldNotFound(yyj10-1, "") + } + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x codecSelfer1234) encSliceSimple(v []Simple, e *codec1978.Encoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperEncoder(e) + _, _, _ = h, z, r + r.EncodeArrayStart(len(v)) + for _, yyv1 := range v { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + yy2 := &yyv1 + yy2.CodecEncodeSelf(e) + } + z.EncSendContainerState(codecSelfer_containerArrayEnd1234) +} + +func (x codecSelfer1234) decSliceSimple(v *[]Simple, d *codec1978.Decoder) { + var h codecSelfer1234 + z, r := codec1978.GenHelperDecoder(d) + _, _, _ = h, z, r + + yyv1 := *v + yyh1, yyl1 := z.DecSliceHelperStart() + var yyc1 bool + _ = yyc1 + if yyl1 == 0 { + if yyv1 == nil { + yyv1 = []Simple{} + yyc1 = true + } else if len(yyv1) != 0 { + yyv1 = yyv1[:0] + yyc1 = true + } + } else if yyl1 > 0 { + var yyrr1, yyrl1 int + var yyrt1 bool + _, _ = yyrl1, yyrt1 + yyrr1 = yyl1 // len(yyv1) + if yyl1 > cap(yyv1) { + + yyrg1 := len(yyv1) > 0 + yyv21 := yyv1 + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 216) + if yyrt1 { + if yyrl1 <= cap(yyv1) { + yyv1 = yyv1[:yyrl1] + } else { + yyv1 = make([]Simple, yyrl1) + } + } else { + yyv1 = make([]Simple, yyrl1) + } + yyc1 = true + yyrr1 = len(yyv1) + if yyrg1 { + copy(yyv1, yyv21) + } + } else if yyl1 != len(yyv1) { + yyv1 = yyv1[:yyl1] + yyc1 = true + } + yyj1 := 0 + for ; yyj1 < yyrr1; yyj1++ { + yyh1.ElemContainerState(yyj1) + if r.TryDecodeAsNil() { + yyv1[yyj1] = Simple{} + } else { + yyv2 := &yyv1[yyj1] + yyv2.CodecDecodeSelf(d) + } + + } + if yyrt1 { + for ; yyj1 < yyl1; yyj1++ { + yyv1 = append(yyv1, Simple{}) + yyh1.ElemContainerState(yyj1) + if r.TryDecodeAsNil() { + yyv1[yyj1] = Simple{} + } else { + yyv3 := &yyv1[yyj1] + yyv3.CodecDecodeSelf(d) + } + + } + } + + } else { + yyj1 := 0 + for ; !r.CheckBreak(); yyj1++ { + + if yyj1 >= len(yyv1) { + yyv1 = append(yyv1, Simple{}) // var yyz1 Simple + yyc1 = true + } + yyh1.ElemContainerState(yyj1) + if yyj1 < len(yyv1) { + if r.TryDecodeAsNil() { + yyv1[yyj1] = Simple{} + } else { + yyv4 := &yyv1[yyj1] + yyv4.CodecDecodeSelf(d) + } + + } else { + z.DecSwallow() + } + + } + if yyj1 < len(yyv1) { + yyv1 = yyv1[:yyj1] + yyc1 = true + } else if yyj1 == 0 && yyv1 == nil { + yyv1 = []Simple{} + yyc1 = true + } + } + yyh1.End() + if yyc1 { + *v = yyv1 + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/testing/types.go b/vendor/k8s.io/kubernetes/pkg/apiserver/testing/types.go new file mode 100644 index 000000000..c6a3df3cd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/testing/types.go @@ -0,0 +1,64 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 testing + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" +) + +type Simple struct { + unversioned.TypeMeta `json:",inline"` + api.ObjectMeta `json:"metadata"` + Other string `json:"other,omitempty"` + Labels map[string]string `json:"labels,omitempty"` +} + +func (obj *Simple) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } + +type SimpleRoot struct { + unversioned.TypeMeta `json:",inline"` + api.ObjectMeta `json:"metadata"` + Other string `json:"other,omitempty"` + Labels map[string]string `json:"labels,omitempty"` +} + +func (obj *SimpleRoot) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } + +type SimpleGetOptions struct { + unversioned.TypeMeta `json:",inline"` + Param1 string `json:"param1"` + Param2 string `json:"param2"` + Path string `json:"atAPath"` +} + +func (SimpleGetOptions) SwaggerDoc() map[string]string { + return map[string]string{ + "param1": "description for param1", + "param2": "description for param2", + } +} + +func (obj *SimpleGetOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } + +type SimpleList struct { + unversioned.TypeMeta `json:",inline"` + unversioned.ListMeta `json:"metadata,inline"` + Items []Simple `json:"items,omitempty"` +} + +func (obj *SimpleList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/validator.go b/vendor/k8s.io/kubernetes/pkg/apiserver/validator.go new file mode 100644 index 000000000..05fa79bf2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/validator.go @@ -0,0 +1,76 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "net/http" + + "k8s.io/kubernetes/pkg/probe" + httpprober "k8s.io/kubernetes/pkg/probe/http" + utilnet "k8s.io/kubernetes/pkg/util/net" + "time" +) + +const ( + probeTimeOut = 20 * time.Second +) + +// TODO: this basic interface is duplicated in N places. consolidate? +type httpGet interface { + Get(url string) (*http.Response, error) +} + +type ValidatorFn func([]byte) error + +type Server struct { + Addr string + Port int + Path string + EnableHTTPS bool + Validate ValidatorFn +} + +type ServerStatus struct { + Component string `json:"component,omitempty"` + Health string `json:"health,omitempty"` + HealthCode probe.Result `json:"healthCode,omitempty"` + Msg string `json:"msg,omitempty"` + Err string `json:"err,omitempty"` +} + +func (server *Server) DoServerCheck(prober httpprober.HTTPProber) (probe.Result, string, error) { + scheme := "http" + if server.EnableHTTPS { + scheme = "https" + } + url := utilnet.FormatURL(scheme, server.Addr, server.Port, server.Path) + + result, data, err := prober.Probe(url, nil, probeTimeOut) + + if err != nil { + return probe.Unknown, "", err + } + if result == probe.Failure { + return probe.Failure, string(data), err + } + if server.Validate != nil { + if err := server.Validate([]byte(data)); err != nil { + return probe.Failure, string(data), err + } + } + return result, string(data), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/validator_test.go b/vendor/k8s.io/kubernetes/pkg/apiserver/validator_test.go new file mode 100644 index 000000000..f02c16300 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/validator_test.go @@ -0,0 +1,91 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "errors" + "fmt" + "testing" + + "k8s.io/kubernetes/pkg/probe" + "net/http" + "net/url" + "time" +) + +type fakeHttpProber struct { + result probe.Result + body string + err error +} + +func (f *fakeHttpProber) Probe(*url.URL, http.Header, time.Duration) (probe.Result, string, error) { + return f.result, f.body, f.err +} + +func alwaysError([]byte) error { return errors.New("test error") } + +func matchError(data []byte) error { + if string(data) != "bar" { + return errors.New("match error") + } + return nil +} + +func TestValidate(t *testing.T) { + tests := []struct { + probeResult probe.Result + probeData string + probeErr error + + expectResult probe.Result + expectData string + expectErr bool + + validator ValidatorFn + }{ + {probe.Unknown, "", fmt.Errorf("probe error"), probe.Unknown, "", true, nil}, + {probe.Failure, "", nil, probe.Failure, "", false, nil}, + {probe.Success, "foo", nil, probe.Failure, "foo", true, matchError}, + {probe.Success, "foo", nil, probe.Success, "foo", false, nil}, + } + + s := Server{Addr: "foo.com", Port: 8080, Path: "/healthz"} + + for _, test := range tests { + fakeProber := &fakeHttpProber{ + result: test.probeResult, + body: test.probeData, + err: test.probeErr, + } + + s.Validate = test.validator + result, data, err := s.DoServerCheck(fakeProber) + if test.expectErr && err == nil { + t.Error("unexpected non-error") + } + if !test.expectErr && err != nil { + t.Errorf("unexpected error: %v", err) + } + if data != test.expectData { + t.Errorf("expected %s, got %s", test.expectData, data) + } + if result != test.expectResult { + t.Errorf("expected %s, got %s", test.expectResult, result) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/watch.go b/vendor/k8s.io/kubernetes/pkg/apiserver/watch.go new file mode 100644 index 000000000..e316d7a18 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/watch.go @@ -0,0 +1,276 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "bytes" + "fmt" + "net/http" + "reflect" + "time" + + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/httplog" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/runtime/serializer/streaming" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wsstream" + "k8s.io/kubernetes/pkg/watch" + "k8s.io/kubernetes/pkg/watch/versioned" + + "github.com/emicklei/go-restful" + "golang.org/x/net/websocket" +) + +// nothing will ever be sent down this channel +var neverExitWatch <-chan time.Time = make(chan time.Time) + +// timeoutFactory abstracts watch timeout logic for testing +type timeoutFactory interface { + TimeoutCh() (<-chan time.Time, func() bool) +} + +// realTimeoutFactory implements timeoutFactory +type realTimeoutFactory struct { + timeout time.Duration +} + +// TimeoutChan returns a channel which will receive something when the watch times out, +// and a cleanup function to call when this happens. +func (w *realTimeoutFactory) TimeoutCh() (<-chan time.Time, func() bool) { + if w.timeout == 0 { + return neverExitWatch, func() bool { return false } + } + t := time.NewTimer(w.timeout) + return t.C, t.Stop +} + +type textEncodable interface { + // EncodesAsText should return true if objects should be transmitted as a WebSocket Text + // frame (otherwise, they will be sent as a Binary frame). + EncodesAsText() bool +} + +// serveWatch handles serving requests to the server +// TODO: the functionality in this method and in WatchServer.Serve is not cleanly decoupled. +func serveWatch(watcher watch.Interface, scope RequestScope, req *restful.Request, res *restful.Response, timeout time.Duration) { + // negotiate for the stream serializer + serializer, mediaType, err := negotiateOutputSerializer(req.Request, scope.StreamSerializer) + if err != nil { + scope.err(err, res.ResponseWriter, req.Request) + return + } + encoder := scope.StreamSerializer.EncoderForVersion(serializer, scope.Kind.GroupVersion()) + + useTextFraming := false + if encodable, ok := encoder.(textEncodable); ok && encodable.EncodesAsText() { + useTextFraming = true + } + + // find the embedded serializer matching the media type + embeddedSerializer, ok := scope.Serializer.SerializerForMediaType(mediaType, nil) + if !ok { + scope.err(fmt.Errorf("no serializer defined for %q available for embedded encoding", mediaType), res.ResponseWriter, req.Request) + return + } + embeddedEncoder := scope.Serializer.EncoderForVersion(embeddedSerializer, scope.Kind.GroupVersion()) + + server := &WatchServer{ + watching: watcher, + scope: scope, + + useTextFraming: useTextFraming, + mediaType: mediaType, + encoder: encoder, + embeddedEncoder: embeddedEncoder, + fixup: func(obj runtime.Object) { + if err := setSelfLink(obj, req, scope.Namer); err != nil { + utilruntime.HandleError(fmt.Errorf("failed to set link for object %v: %v", reflect.TypeOf(obj), err)) + } + }, + + t: &realTimeoutFactory{timeout}, + } + + server.ServeHTTP(res.ResponseWriter, req.Request) +} + +// WatchServer serves a watch.Interface over a websocket or vanilla HTTP. +type WatchServer struct { + watching watch.Interface + scope RequestScope + + // true if websocket messages should use text framing (as opposed to binary framing) + useTextFraming bool + // the media type this watch is being served with + mediaType string + // used to encode the watch stream event itself + encoder runtime.Encoder + // used to encode the nested object in the watch stream + embeddedEncoder runtime.Encoder + fixup func(runtime.Object) + + t timeoutFactory +} + +// Serve serves a series of encoded events via HTTP with Transfer-Encoding: chunked +// or over a websocket connection. +func (s *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { + w = httplog.Unlogged(w) + + if wsstream.IsWebSocketRequest(req) { + w.Header().Set("Content-Type", s.mediaType) + websocket.Handler(s.HandleWS).ServeHTTP(w, req) + return + } + + cn, ok := w.(http.CloseNotifier) + if !ok { + err := fmt.Errorf("unable to start watch - can't get http.CloseNotifier: %#v", w) + utilruntime.HandleError(err) + s.scope.err(errors.NewInternalError(err), w, req) + return + } + flusher, ok := w.(http.Flusher) + if !ok { + err := fmt.Errorf("unable to start watch - can't get http.Flusher: %#v", w) + utilruntime.HandleError(err) + s.scope.err(errors.NewInternalError(err), w, req) + return + } + + // get a framed encoder + f, ok := s.encoder.(streaming.Framer) + if !ok { + // programmer error + err := fmt.Errorf("no streaming support is available for media type %q", s.mediaType) + utilruntime.HandleError(err) + s.scope.err(errors.NewBadRequest(err.Error()), w, req) + return + } + framer := f.NewFrameWriter(w) + if framer == nil { + // programmer error + err := fmt.Errorf("no stream framing support is available for media type %q", s.mediaType) + utilruntime.HandleError(err) + s.scope.err(errors.NewBadRequest(err.Error()), w, req) + return + } + e := streaming.NewEncoder(framer, s.encoder) + + // ensure the connection times out + timeoutCh, cleanup := s.t.TimeoutCh() + defer cleanup() + defer s.watching.Stop() + + // begin the stream + w.Header().Set("Content-Type", s.mediaType) + w.Header().Set("Transfer-Encoding", "chunked") + w.WriteHeader(http.StatusOK) + flusher.Flush() + + buf := &bytes.Buffer{} + for { + select { + case <-cn.CloseNotify(): + return + case <-timeoutCh: + return + case event, ok := <-s.watching.ResultChan(): + if !ok { + // End of results. + return + } + obj := event.Object + s.fixup(obj) + if err := s.embeddedEncoder.EncodeToStream(obj, buf); err != nil { + // unexpected error + utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err)) + return + } + event.Object = &runtime.Unknown{ + Raw: buf.Bytes(), + // ContentType is not required here because we are defaulting to the serializer + // type + } + if err := e.Encode((*versioned.InternalEvent)(&event)); err != nil { + utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err)) + // client disconnect. + return + } + flusher.Flush() + + buf.Reset() + } + } +} + +// HandleWS implements a websocket handler. +func (s *WatchServer) HandleWS(ws *websocket.Conn) { + defer ws.Close() + done := make(chan struct{}) + go wsstream.IgnoreReceives(ws, 0) + buf := &bytes.Buffer{} + streamBuf := &bytes.Buffer{} + for { + select { + case <-done: + s.watching.Stop() + return + case event, ok := <-s.watching.ResultChan(): + if !ok { + // End of results. + return + } + obj := event.Object + s.fixup(obj) + if err := s.embeddedEncoder.EncodeToStream(obj, buf); err != nil { + // unexpected error + utilruntime.HandleError(fmt.Errorf("unable to encode watch object: %v", err)) + return + } + event.Object = &runtime.Unknown{ + Raw: buf.Bytes(), + // ContentType is not required here because we are defaulting to the serializer + // type + } + // the internal event will be versioned by the encoder + internalEvent := versioned.InternalEvent(event) + if err := s.encoder.EncodeToStream(&internalEvent, streamBuf); err != nil { + // encoding error + utilruntime.HandleError(fmt.Errorf("unable to encode event: %v", err)) + s.watching.Stop() + return + } + if s.useTextFraming { + if err := websocket.Message.Send(ws, streamBuf.String()); err != nil { + // Client disconnect. + s.watching.Stop() + return + } + } else { + if err := websocket.Message.Send(ws, streamBuf.Bytes()); err != nil { + // Client disconnect. + s.watching.Stop() + return + } + } + buf.Reset() + streamBuf.Reset() + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/apiserver/watch_test.go b/vendor/k8s.io/kubernetes/pkg/apiserver/watch_test.go new file mode 100644 index 000000000..0faf1f8d2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/apiserver/watch_test.go @@ -0,0 +1,581 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 apiserver + +import ( + "encoding/json" + "io" + "io/ioutil" + "math/rand" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "sync" + "testing" + "time" + + "golang.org/x/net/websocket" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/api/unversioned" + apiservertesting "k8s.io/kubernetes/pkg/apiserver/testing" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/watch" +) + +// watchJSON defines the expected JSON wire equivalent of watch.Event +type watchJSON struct { + Type watch.EventType `json:"type,omitempty"` + Object json.RawMessage `json:"object,omitempty"` +} + +var watchTestTable = []struct { + t watch.EventType + obj runtime.Object +}{ + {watch.Added, &apiservertesting.Simple{ObjectMeta: api.ObjectMeta{Name: "foo"}}}, + {watch.Modified, &apiservertesting.Simple{ObjectMeta: api.ObjectMeta{Name: "bar"}}}, + {watch.Deleted, &apiservertesting.Simple{ObjectMeta: api.ObjectMeta{Name: "bar"}}}, +} + +func TestWatchWebsocket(t *testing.T) { + simpleStorage := &SimpleRESTStorage{} + _ = rest.Watcher(simpleStorage) // Give compile error if this doesn't work. + handler := handle(map[string]rest.Storage{"simples": simpleStorage}) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + dest, _ := url.Parse(server.URL) + dest.Scheme = "ws" // Required by websocket, though the server never sees it. + dest.Path = "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simples" + dest.RawQuery = "" + + ws, err := websocket.Dial(dest.String(), "", "http://localhost") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + try := func(action watch.EventType, object runtime.Object) { + // Send + simpleStorage.fakeWatch.Action(action, object) + // Test receive + var got watchJSON + err := websocket.JSON.Receive(ws, &got) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if got.Type != action { + t.Errorf("Unexpected type: %v", got.Type) + } + gotObj, err := runtime.Decode(codec, got.Object) + if err != nil { + t.Fatalf("Decode error: %v\n%v", err, got) + } + if _, err := api.GetReference(gotObj); err != nil { + t.Errorf("Unable to construct reference: %v", err) + } + if e, a := object, gotObj; !reflect.DeepEqual(e, a) { + t.Errorf("Expected %#v, got %#v", e, a) + } + } + + for _, item := range watchTestTable { + try(item.t, item.obj) + } + simpleStorage.fakeWatch.Stop() + + var got watchJSON + err = websocket.JSON.Receive(ws, &got) + if err == nil { + t.Errorf("Unexpected non-error") + } +} + +func TestWatchHTTP(t *testing.T) { + simpleStorage := &SimpleRESTStorage{} + handler := handle(map[string]rest.Storage{"simples": simpleStorage}) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{} + + dest, _ := url.Parse(server.URL) + dest.Path = "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simples" + dest.RawQuery = "" + + request, err := http.NewRequest("GET", dest.String(), nil) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if response.StatusCode != http.StatusOK { + t.Errorf("Unexpected response %#v", response) + } + + decoder := json.NewDecoder(response.Body) + + for i, item := range watchTestTable { + // Send + simpleStorage.fakeWatch.Action(item.t, item.obj) + // Test receive + var got watchJSON + err := decoder.Decode(&got) + if err != nil { + t.Fatalf("%d: Unexpected error: %v", i, err) + } + if got.Type != item.t { + t.Errorf("%d: Unexpected type: %v", i, got.Type) + } + t.Logf("obj: %v", string(got.Object)) + gotObj, err := runtime.Decode(codec, got.Object) + if err != nil { + t.Fatalf("Decode error: %v", err) + } + t.Logf("obj: %#v", gotObj) + if _, err := api.GetReference(gotObj); err != nil { + t.Errorf("Unable to construct reference: %v", err) + } + if e, a := item.obj, gotObj; !reflect.DeepEqual(e, a) { + t.Errorf("Expected %#v, got %#v", e, a) + } + } + simpleStorage.fakeWatch.Stop() + + var got watchJSON + err = decoder.Decode(&got) + if err == nil { + t.Errorf("Unexpected non-error") + } +} + +func TestWatchHTTPAccept(t *testing.T) { + simpleStorage := &SimpleRESTStorage{} + handler := handle(map[string]rest.Storage{"simples": simpleStorage}) + server := httptest.NewServer(handler) + defer server.Close() + client := http.Client{} + + dest, _ := url.Parse(server.URL) + dest.Path = "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simples" + dest.RawQuery = "" + + request, err := http.NewRequest("GET", dest.String(), nil) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + request.Header.Set("Accept", "application/yaml") + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // TODO: once this is fixed, this test will change + if response.StatusCode != http.StatusNotAcceptable { + t.Errorf("Unexpected response %#v", response) + } +} + +func TestWatchParamParsing(t *testing.T) { + simpleStorage := &SimpleRESTStorage{} + handler := handle(map[string]rest.Storage{ + "simples": simpleStorage, + "simpleroots": simpleStorage, + }) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + + dest, _ := url.Parse(server.URL) + + rootPath := "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simples" + namespacedPath := "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/namespaces/other/simpleroots" + + table := []struct { + path string + rawQuery string + resourceVersion string + labelSelector string + fieldSelector string + namespace string + }{ + { + path: rootPath, + rawQuery: "resourceVersion=1234", + resourceVersion: "1234", + labelSelector: "", + fieldSelector: "", + namespace: api.NamespaceAll, + }, { + path: rootPath, + rawQuery: "resourceVersion=314159&fieldSelector=Host%3D&labelSelector=name%3Dfoo", + resourceVersion: "314159", + labelSelector: "name=foo", + fieldSelector: "Host=", + namespace: api.NamespaceAll, + }, { + path: rootPath, + rawQuery: "fieldSelector=id%3dfoo&resourceVersion=1492", + resourceVersion: "1492", + labelSelector: "", + fieldSelector: "id=foo", + namespace: api.NamespaceAll, + }, { + path: rootPath, + rawQuery: "", + resourceVersion: "", + labelSelector: "", + fieldSelector: "", + namespace: api.NamespaceAll, + }, + { + path: namespacedPath, + rawQuery: "resourceVersion=1234", + resourceVersion: "1234", + labelSelector: "", + fieldSelector: "", + namespace: "other", + }, { + path: namespacedPath, + rawQuery: "resourceVersion=314159&fieldSelector=Host%3D&labelSelector=name%3Dfoo", + resourceVersion: "314159", + labelSelector: "name=foo", + fieldSelector: "Host=", + namespace: "other", + }, { + path: namespacedPath, + rawQuery: "fieldSelector=id%3dfoo&resourceVersion=1492", + resourceVersion: "1492", + labelSelector: "", + fieldSelector: "id=foo", + namespace: "other", + }, { + path: namespacedPath, + rawQuery: "", + resourceVersion: "", + labelSelector: "", + fieldSelector: "", + namespace: "other", + }, + } + + for _, item := range table { + simpleStorage.requestedLabelSelector = labels.Everything() + simpleStorage.requestedFieldSelector = fields.Everything() + simpleStorage.requestedResourceVersion = "5" // Prove this is set in all cases + simpleStorage.requestedResourceNamespace = "" + dest.Path = item.path + dest.RawQuery = item.rawQuery + resp, err := http.Get(dest.String()) + if err != nil { + t.Errorf("%v: unexpected error: %v", item.rawQuery, err) + continue + } + resp.Body.Close() + if e, a := item.namespace, simpleStorage.requestedResourceNamespace; e != a { + t.Errorf("%v: expected %v, got %v", item.rawQuery, e, a) + } + if e, a := item.resourceVersion, simpleStorage.requestedResourceVersion; e != a { + t.Errorf("%v: expected %v, got %v", item.rawQuery, e, a) + } + if e, a := item.labelSelector, simpleStorage.requestedLabelSelector.String(); e != a { + t.Errorf("%v: expected %v, got %v", item.rawQuery, e, a) + } + if e, a := item.fieldSelector, simpleStorage.requestedFieldSelector.String(); e != a { + t.Errorf("%v: expected %v, got %v", item.rawQuery, e, a) + } + } +} + +func TestWatchProtocolSelection(t *testing.T) { + simpleStorage := &SimpleRESTStorage{} + handler := handle(map[string]rest.Storage{"simples": simpleStorage}) + server := httptest.NewServer(handler) + // TODO: Uncomment when fix #19254 + // defer server.Close() + defer server.CloseClientConnections() + client := http.Client{} + + dest, _ := url.Parse(server.URL) + dest.Path = "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/simples" + dest.RawQuery = "" + + table := []struct { + isWebsocket bool + connHeader string + }{ + {true, "Upgrade"}, + {true, "keep-alive, Upgrade"}, + {true, "upgrade"}, + {false, "keep-alive"}, + } + + for _, item := range table { + request, err := http.NewRequest("GET", dest.String(), nil) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + request.Header.Set("Connection", item.connHeader) + request.Header.Set("Upgrade", "websocket") + + response, err := client.Do(request) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // The requests recognized as websocket requests based on connection + // and upgrade headers will not also have the necessary Sec-Websocket-* + // headers so it is expected to throw a 400 + if item.isWebsocket && response.StatusCode != http.StatusBadRequest { + t.Errorf("Unexpected response %#v", response) + } + + if !item.isWebsocket && response.StatusCode != http.StatusOK { + t.Errorf("Unexpected response %#v", response) + } + } + +} + +type fakeTimeoutFactory struct { + timeoutCh chan time.Time + done chan struct{} +} + +func (t *fakeTimeoutFactory) TimeoutCh() (<-chan time.Time, func() bool) { + return t.timeoutCh, func() bool { + defer close(t.done) + return true + } +} + +func TestWatchHTTPTimeout(t *testing.T) { + watcher := watch.NewFake() + timeoutCh := make(chan time.Time) + done := make(chan struct{}) + + // Setup a new watchserver + watchServer := &WatchServer{ + watching: watcher, + + mediaType: "testcase/json", + encoder: newCodec, + embeddedEncoder: newCodec, + + fixup: func(obj runtime.Object) {}, + t: &fakeTimeoutFactory{timeoutCh, done}, + } + + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + watchServer.ServeHTTP(w, req) + })) + // TODO: Uncomment when fix #19254 + // defer s.Close() + + // Setup a client + dest, _ := url.Parse(s.URL) + dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/simple" + dest.RawQuery = "watch=true" + + req, _ := http.NewRequest("GET", dest.String(), nil) + client := http.Client{} + resp, err := client.Do(req) + watcher.Add(&apiservertesting.Simple{TypeMeta: unversioned.TypeMeta{APIVersion: newGroupVersion.String()}}) + + // Make sure we can actually watch an endpoint + decoder := json.NewDecoder(resp.Body) + var got watchJSON + err = decoder.Decode(&got) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Timeout and check for leaks + close(timeoutCh) + select { + case <-done: + if !watcher.Stopped { + t.Errorf("Leaked watch on timeout") + } + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Failed to stop watcher after %s of timeout signal", wait.ForeverTestTimeout.String()) + } + + // Make sure we can't receive any more events through the timeout watch + err = decoder.Decode(&got) + if err != io.EOF { + t.Errorf("Unexpected non-error") + } +} + +const benchmarkSeed = 100 + +func benchmarkItems() []api.Pod { + apiObjectFuzzer := apitesting.FuzzerFor(nil, api.SchemeGroupVersion, rand.NewSource(benchmarkSeed)) + items := make([]api.Pod, 3) + for i := range items { + apiObjectFuzzer.Fuzz(&items[i]) + } + return items +} + +// BenchmarkWatchHTTP measures the cost of serving a watch. +func BenchmarkWatchHTTP(b *testing.B) { + items := benchmarkItems() + + simpleStorage := &SimpleRESTStorage{} + handler := handle(map[string]rest.Storage{"simples": simpleStorage}) + server := httptest.NewServer(handler) + defer server.Close() + client := http.Client{} + + dest, _ := url.Parse(server.URL) + dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/watch/simples" + dest.RawQuery = "" + + request, err := http.NewRequest("GET", dest.String(), nil) + if err != nil { + b.Fatalf("unexpected error: %v", err) + } + response, err := client.Do(request) + if err != nil { + b.Fatalf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusOK { + b.Fatalf("Unexpected response %#v", response) + } + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer response.Body.Close() + if _, err := io.Copy(ioutil.Discard, response.Body); err != nil { + b.Fatal(err) + } + wg.Done() + }() + + actions := []watch.EventType{watch.Added, watch.Modified, watch.Deleted} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + simpleStorage.fakeWatch.Action(actions[i%len(actions)], &items[i%len(items)]) + } + simpleStorage.fakeWatch.Stop() + wg.Wait() + b.StopTimer() +} + +// BenchmarkWatchWebsocket measures the cost of serving a watch. +func BenchmarkWatchWebsocket(b *testing.B) { + items := benchmarkItems() + + simpleStorage := &SimpleRESTStorage{} + handler := handle(map[string]rest.Storage{"simples": simpleStorage}) + server := httptest.NewServer(handler) + defer server.Close() + + dest, _ := url.Parse(server.URL) + dest.Scheme = "ws" // Required by websocket, though the server never sees it. + dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/watch/simples" + dest.RawQuery = "" + + ws, err := websocket.Dial(dest.String(), "", "http://localhost") + if err != nil { + b.Fatalf("unexpected error: %v", err) + } + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer ws.Close() + if _, err := io.Copy(ioutil.Discard, ws); err != nil { + b.Fatal(err) + } + wg.Done() + }() + + actions := []watch.EventType{watch.Added, watch.Modified, watch.Deleted} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + simpleStorage.fakeWatch.Action(actions[i%len(actions)], &items[i%len(items)]) + } + simpleStorage.fakeWatch.Stop() + wg.Wait() + b.StopTimer() +} + +// BenchmarkWatchProtobuf measures the cost of serving a watch. +func BenchmarkWatchProtobuf(b *testing.B) { + items := benchmarkItems() + + simpleStorage := &SimpleRESTStorage{} + handler := handle(map[string]rest.Storage{"simples": simpleStorage}) + server := httptest.NewServer(handler) + defer server.Close() + client := http.Client{} + + dest, _ := url.Parse(server.URL) + dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/watch/simples" + dest.RawQuery = "" + + request, err := http.NewRequest("GET", dest.String(), nil) + if err != nil { + b.Fatalf("unexpected error: %v", err) + } + request.Header.Set("Accept", "application/vnd.kubernetes.protobuf") + response, err := client.Do(request) + if err != nil { + b.Fatalf("unexpected error: %v", err) + } + if response.StatusCode != http.StatusOK { + body, _ := ioutil.ReadAll(response.Body) + b.Fatalf("Unexpected response %#v\n%s", response, body) + } + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer response.Body.Close() + if _, err := io.Copy(ioutil.Discard, response.Body); err != nil { + b.Fatal(err) + } + wg.Done() + }() + + actions := []watch.EventType{watch.Added, watch.Modified, watch.Deleted} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + simpleStorage.fakeWatch.Action(actions[i%len(actions)], &items[i%len(items)]) + } + simpleStorage.fakeWatch.Stop() + wg.Wait() + b.StopTimer() +} diff --git a/vendor/k8s.io/kubernetes/pkg/auth/OWNERS b/vendor/k8s.io/kubernetes/pkg/auth/OWNERS new file mode 100644 index 000000000..766c481bd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/auth/OWNERS @@ -0,0 +1,3 @@ +assignees: + - erictune + - liggitt diff --git a/vendor/k8s.io/kubernetes/pkg/auth/authenticator/bearertoken/bearertoken.go b/vendor/k8s.io/kubernetes/pkg/auth/authenticator/bearertoken/bearertoken.go new file mode 100644 index 000000000..eff338bfb --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/auth/authenticator/bearertoken/bearertoken.go @@ -0,0 +1,47 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 bearertoken + +import ( + "net/http" + "strings" + + "k8s.io/kubernetes/pkg/auth/authenticator" + "k8s.io/kubernetes/pkg/auth/user" +) + +type Authenticator struct { + auth authenticator.Token +} + +func New(auth authenticator.Token) *Authenticator { + return &Authenticator{auth} +} + +func (a *Authenticator) AuthenticateRequest(req *http.Request) (user.Info, bool, error) { + auth := strings.TrimSpace(req.Header.Get("Authorization")) + if auth == "" { + return nil, false, nil + } + parts := strings.Split(auth, " ") + if len(parts) < 2 || strings.ToLower(parts[0]) != "bearer" { + return nil, false, nil + } + + token := parts[1] + return a.auth.AuthenticateToken(token) +} diff --git a/vendor/k8s.io/kubernetes/pkg/auth/authenticator/bearertoken/bearertoken_test.go b/vendor/k8s.io/kubernetes/pkg/auth/authenticator/bearertoken/bearertoken_test.go new file mode 100644 index 000000000..6b9f67662 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/auth/authenticator/bearertoken/bearertoken_test.go @@ -0,0 +1,86 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 bearertoken + +import ( + "errors" + "net/http" + "testing" + + "k8s.io/kubernetes/pkg/auth/authenticator" + "k8s.io/kubernetes/pkg/auth/user" +) + +func TestAuthenticateRequest(t *testing.T) { + auth := New(authenticator.TokenFunc(func(token string) (user.Info, bool, error) { + if token != "token" { + t.Errorf("unexpected token: %s", token) + } + return &user.DefaultInfo{Name: "user"}, true, nil + })) + user, ok, err := auth.AuthenticateRequest(&http.Request{ + Header: http.Header{"Authorization": []string{"Bearer token"}}, + }) + if !ok || user == nil || err != nil { + t.Errorf("expected valid user") + } +} + +func TestAuthenticateRequestTokenInvalid(t *testing.T) { + auth := New(authenticator.TokenFunc(func(token string) (user.Info, bool, error) { + return nil, false, nil + })) + user, ok, err := auth.AuthenticateRequest(&http.Request{ + Header: http.Header{"Authorization": []string{"Bearer token"}}, + }) + if ok || user != nil || err != nil { + t.Errorf("expected not authenticated user") + } +} + +func TestAuthenticateRequestTokenError(t *testing.T) { + auth := New(authenticator.TokenFunc(func(token string) (user.Info, bool, error) { + return nil, false, errors.New("error") + })) + user, ok, err := auth.AuthenticateRequest(&http.Request{ + Header: http.Header{"Authorization": []string{"Bearer token"}}, + }) + if ok || user != nil || err == nil { + t.Errorf("expected error") + } +} + +func TestAuthenticateRequestBadValue(t *testing.T) { + testCases := []struct { + Req *http.Request + }{ + {Req: &http.Request{}}, + {Req: &http.Request{Header: http.Header{"Authorization": []string{"Bearer"}}}}, + {Req: &http.Request{Header: http.Header{"Authorization": []string{"bear token"}}}}, + {Req: &http.Request{Header: http.Header{"Authorization": []string{"Bearer: token"}}}}, + } + for i, testCase := range testCases { + auth := New(authenticator.TokenFunc(func(token string) (user.Info, bool, error) { + t.Errorf("authentication should not have been called") + return nil, false, nil + })) + user, ok, err := auth.AuthenticateRequest(testCase.Req) + if ok || user != nil || err != nil { + t.Errorf("%d: expected not authenticated (no token)", i) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/auth/authenticator/interfaces.go b/vendor/k8s.io/kubernetes/pkg/auth/authenticator/interfaces.go new file mode 100644 index 000000000..2da820cc0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/auth/authenticator/interfaces.go @@ -0,0 +1,68 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 authenticator + +import ( + "net/http" + + "k8s.io/kubernetes/pkg/auth/user" +) + +// Token checks a string value against a backing authentication store and returns +// information about the current user and true if successful, false if not successful, +// or an error if the token could not be checked. +type Token interface { + AuthenticateToken(token string) (user.Info, bool, error) +} + +// Request attempts to extract authentication information from a request and returns +// information about the current user and true if successful, false if not successful, +// or an error if the request could not be checked. +type Request interface { + AuthenticateRequest(req *http.Request) (user.Info, bool, error) +} + +// Password checks a username and password against a backing authentication store and +// returns information about the user and true if successful, false if not successful, +// or an error if the username and password could not be checked +type Password interface { + AuthenticatePassword(user, password string) (user.Info, bool, error) +} + +// TokenFunc is a function that implements the Token interface. +type TokenFunc func(token string) (user.Info, bool, error) + +// AuthenticateToken implements authenticator.Token. +func (f TokenFunc) AuthenticateToken(token string) (user.Info, bool, error) { + return f(token) +} + +// RequestFunc is a function that implements the Request interface. +type RequestFunc func(req *http.Request) (user.Info, bool, error) + +// AuthenticateRequest implements authenticator.Request. +func (f RequestFunc) AuthenticateRequest(req *http.Request) (user.Info, bool, error) { + return f(req) +} + +// PasswordFunc is a function that implements the Password interface. +type PasswordFunc func(user, password string) (user.Info, bool, error) + +// AuthenticatePassword implements authenticator.Password. +func (f PasswordFunc) AuthenticatePassword(user, password string) (user.Info, bool, error) { + return f(user, password) +} diff --git a/vendor/k8s.io/kubernetes/pkg/auth/authorizer/abac/abac.go b/vendor/k8s.io/kubernetes/pkg/auth/authorizer/abac/abac.go new file mode 100644 index 000000000..c3bfedcc4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/auth/authorizer/abac/abac.go @@ -0,0 +1,228 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 abac + +// Policy authorizes Kubernetes API actions using an Attribute-based access +// control scheme. + +import ( + "bufio" + "errors" + "fmt" + "os" + "strings" + + "github.com/golang/glog" + + api "k8s.io/kubernetes/pkg/apis/abac" + _ "k8s.io/kubernetes/pkg/apis/abac/latest" + "k8s.io/kubernetes/pkg/apis/abac/v0" + "k8s.io/kubernetes/pkg/auth/authorizer" + "k8s.io/kubernetes/pkg/runtime" +) + +type policyLoadError struct { + path string + line int + data []byte + err error +} + +func (p policyLoadError) Error() string { + if p.line >= 0 { + return fmt.Sprintf("error reading policy file %s, line %d: %s: %v", p.path, p.line, string(p.data), p.err) + } + return fmt.Sprintf("error reading policy file %s: %v", p.path, p.err) +} + +type policyList []*api.Policy + +// TODO: Have policies be created via an API call and stored in REST storage. +func NewFromFile(path string) (policyList, error) { + // File format is one map per line. This allows easy concatentation of files, + // comments in files, and identification of errors by line number. + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + pl := make(policyList, 0) + + decoder := api.Codecs.UniversalDecoder() + + i := 0 + unversionedLines := 0 + for scanner.Scan() { + i++ + p := &api.Policy{} + b := scanner.Bytes() + + // skip comment lines and blank lines + trimmed := strings.TrimSpace(string(b)) + if len(trimmed) == 0 || strings.HasPrefix(trimmed, "#") { + continue + } + + decodedObj, _, err := decoder.Decode(b, nil, nil) + if err != nil { + if !(runtime.IsMissingVersion(err) || runtime.IsMissingKind(err) || runtime.IsNotRegisteredError(err)) { + return nil, policyLoadError{path, i, b, err} + } + unversionedLines++ + // Migrate unversioned policy object + oldPolicy := &v0.Policy{} + if err := runtime.DecodeInto(decoder, b, oldPolicy); err != nil { + return nil, policyLoadError{path, i, b, err} + } + if err := api.Scheme.Convert(oldPolicy, p); err != nil { + return nil, policyLoadError{path, i, b, err} + } + pl = append(pl, p) + continue + } + + decodedPolicy, ok := decodedObj.(*api.Policy) + if !ok { + return nil, policyLoadError{path, i, b, fmt.Errorf("unrecognized object: %#v", decodedObj)} + } + pl = append(pl, decodedPolicy) + } + + if unversionedLines > 0 { + glog.Warningf(`Policy file %s contained unversioned rules. See docs/admin/authorization.md#abac-mode for ABAC file format details.`, path) + } + + if err := scanner.Err(); err != nil { + return nil, policyLoadError{path, -1, nil, err} + } + return pl, nil +} + +func matches(p api.Policy, a authorizer.Attributes) bool { + if subjectMatches(p, a) { + if verbMatches(p, a) { + // Resource and non-resource requests are mutually exclusive, at most one will match a policy + if resourceMatches(p, a) { + return true + } + if nonResourceMatches(p, a) { + return true + } + } + } + return false +} + +// subjectMatches returns true if specified user and group properties in the policy match the attributes +func subjectMatches(p api.Policy, a authorizer.Attributes) bool { + matched := false + + // If the policy specified a user, ensure it matches + if len(p.Spec.User) > 0 { + if p.Spec.User == "*" { + matched = true + } else { + matched = p.Spec.User == a.GetUserName() + if !matched { + return false + } + } + } + + // If the policy specified a group, ensure it matches + if len(p.Spec.Group) > 0 { + if p.Spec.Group == "*" { + matched = true + } else { + matched = false + for _, group := range a.GetGroups() { + if p.Spec.Group == group { + matched = true + } + } + if !matched { + return false + } + } + } + + return matched +} + +func verbMatches(p api.Policy, a authorizer.Attributes) bool { + // TODO: match on verb + + // All policies allow read only requests + if a.IsReadOnly() { + return true + } + + // Allow if policy is not readonly + if !p.Spec.Readonly { + return true + } + + return false +} + +func nonResourceMatches(p api.Policy, a authorizer.Attributes) bool { + // A non-resource policy cannot match a resource request + if !a.IsResourceRequest() { + // Allow wildcard match + if p.Spec.NonResourcePath == "*" { + return true + } + // Allow exact match + if p.Spec.NonResourcePath == a.GetPath() { + return true + } + // Allow a trailing * subpath match + if strings.HasSuffix(p.Spec.NonResourcePath, "*") && strings.HasPrefix(a.GetPath(), strings.TrimRight(p.Spec.NonResourcePath, "*")) { + return true + } + } + return false +} + +func resourceMatches(p api.Policy, a authorizer.Attributes) bool { + // A resource policy cannot match a non-resource request + if a.IsResourceRequest() { + if p.Spec.Namespace == "*" || p.Spec.Namespace == a.GetNamespace() { + if p.Spec.Resource == "*" || p.Spec.Resource == a.GetResource() { + if p.Spec.APIGroup == "*" || p.Spec.APIGroup == a.GetAPIGroup() { + return true + } + } + } + } + return false +} + +// Authorizer implements authorizer.Authorize +func (pl policyList) Authorize(a authorizer.Attributes) error { + for _, p := range pl { + if matches(*p, a) { + return nil + } + } + return errors.New("No policy matched.") + // TODO: Benchmark how much time policy matching takes with a medium size + // policy file, compared to other steps such as encoding/decoding. + // Then, add Caching only if needed. +} diff --git a/vendor/k8s.io/kubernetes/pkg/auth/authorizer/abac/abac_test.go b/vendor/k8s.io/kubernetes/pkg/auth/authorizer/abac/abac_test.go new file mode 100644 index 000000000..8b4e3b75b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/auth/authorizer/abac/abac_test.go @@ -0,0 +1,965 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 abac + +import ( + "io/ioutil" + "os" + "testing" + + api "k8s.io/kubernetes/pkg/apis/abac" + "k8s.io/kubernetes/pkg/apis/abac/v0" + "k8s.io/kubernetes/pkg/apis/abac/v1beta1" + "k8s.io/kubernetes/pkg/auth/authorizer" + "k8s.io/kubernetes/pkg/auth/user" + "k8s.io/kubernetes/pkg/runtime" +) + +func TestEmptyFile(t *testing.T) { + _, err := newWithContents(t, "") + if err != nil { + t.Errorf("unable to read policy file: %v", err) + } +} + +func TestOneLineFileNoNewLine(t *testing.T) { + _, err := newWithContents(t, `{"user":"scheduler", "readonly": true, "resource": "pods", "namespace":"ns1"}`) + if err != nil { + t.Errorf("unable to read policy file: %v", err) + } +} + +func TestTwoLineFile(t *testing.T) { + _, err := newWithContents(t, `{"user":"scheduler", "readonly": true, "resource": "pods"} +{"user":"scheduler", "readonly": true, "resource": "services"} +`) + if err != nil { + t.Errorf("unable to read policy file: %v", err) + } +} + +// Test the file that we will point users at as an example. +func TestExampleFile(t *testing.T) { + _, err := NewFromFile("./example_policy_file.jsonl") + if err != nil { + t.Errorf("unable to read policy file: %v", err) + } +} + +func TestAuthorizeV0(t *testing.T) { + a, err := newWithContents(t, `{ "readonly": true, "resource": "events" } +{"user":"scheduler", "readonly": true, "resource": "pods" } +{"user":"scheduler", "resource": "bindings" } +{"user":"kubelet", "readonly": true, "resource": "bindings" } +{"user":"kubelet", "resource": "events" } +{"user":"alice", "namespace": "projectCaribou"} +{"user":"bob", "readonly": true, "namespace": "projectCaribou"} +`) + if err != nil { + t.Fatalf("unable to read policy file: %v", err) + } + + uScheduler := user.DefaultInfo{Name: "scheduler", UID: "uid1"} + uAlice := user.DefaultInfo{Name: "alice", UID: "uid3"} + uChuck := user.DefaultInfo{Name: "chuck", UID: "uid5"} + + testCases := []struct { + User user.DefaultInfo + Verb string + Resource string + NS string + APIGroup string + Path string + ExpectAllow bool + }{ + // Scheduler can read pods + {User: uScheduler, Verb: "list", Resource: "pods", NS: "ns1", ExpectAllow: true}, + {User: uScheduler, Verb: "list", Resource: "pods", NS: "", ExpectAllow: true}, + // Scheduler cannot write pods + {User: uScheduler, Verb: "create", Resource: "pods", NS: "ns1", ExpectAllow: false}, + {User: uScheduler, Verb: "create", Resource: "pods", NS: "", ExpectAllow: false}, + // Scheduler can write bindings + {User: uScheduler, Verb: "get", Resource: "bindings", NS: "ns1", ExpectAllow: true}, + {User: uScheduler, Verb: "get", Resource: "bindings", NS: "", ExpectAllow: true}, + + // Alice can read and write anything in the right namespace. + {User: uAlice, Verb: "get", Resource: "pods", NS: "projectCaribou", ExpectAllow: true}, + {User: uAlice, Verb: "get", Resource: "widgets", NS: "projectCaribou", ExpectAllow: true}, + {User: uAlice, Verb: "get", Resource: "", NS: "projectCaribou", ExpectAllow: true}, + {User: uAlice, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectAllow: true}, + {User: uAlice, Verb: "update", Resource: "widgets", NS: "projectCaribou", ExpectAllow: true}, + {User: uAlice, Verb: "update", Resource: "", NS: "projectCaribou", ExpectAllow: true}, + {User: uAlice, Verb: "update", Resource: "foo", NS: "projectCaribou", APIGroup: "bar", ExpectAllow: true}, + // .. but not the wrong namespace. + {User: uAlice, Verb: "get", Resource: "pods", NS: "ns1", ExpectAllow: false}, + {User: uAlice, Verb: "get", Resource: "widgets", NS: "ns1", ExpectAllow: false}, + {User: uAlice, Verb: "get", Resource: "", NS: "ns1", ExpectAllow: false}, + + // Chuck can read events, since anyone can. + {User: uChuck, Verb: "get", Resource: "events", NS: "ns1", ExpectAllow: true}, + {User: uChuck, Verb: "get", Resource: "events", NS: "", ExpectAllow: true}, + // Chuck can't do other things. + {User: uChuck, Verb: "update", Resource: "events", NS: "ns1", ExpectAllow: false}, + {User: uChuck, Verb: "get", Resource: "pods", NS: "ns1", ExpectAllow: false}, + {User: uChuck, Verb: "get", Resource: "floop", NS: "ns1", ExpectAllow: false}, + // Chunk can't access things with no kind or namespace + {User: uChuck, Verb: "get", Path: "/", Resource: "", NS: "", ExpectAllow: false}, + } + for i, tc := range testCases { + attr := authorizer.AttributesRecord{ + User: &tc.User, + Verb: tc.Verb, + Resource: tc.Resource, + Namespace: tc.NS, + APIGroup: tc.APIGroup, + Path: tc.Path, + + ResourceRequest: len(tc.NS) > 0 || len(tc.Resource) > 0, + } + err := a.Authorize(attr) + actualAllow := bool(err == nil) + if tc.ExpectAllow != actualAllow { + t.Logf("tc: %v -> attr %v", tc, attr) + t.Errorf("%d: Expected allowed=%v but actually allowed=%v\n\t%v", + i, tc.ExpectAllow, actualAllow, tc) + } + } +} + +func TestAuthorizeV1beta1(t *testing.T) { + a, err := newWithContents(t, + ` + # Comment line, after a blank line + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"*", "readonly": true, "nonResourcePath": "/api"}} + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"*", "nonResourcePath": "/custom"}} + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"*", "nonResourcePath": "/root/*"}} + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"noresource", "nonResourcePath": "*"}} + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"*", "readonly": true, "resource": "events", "namespace": "*"}} + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"scheduler", "readonly": true, "resource": "pods", "namespace": "*"}} + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"scheduler", "resource": "bindings", "namespace": "*"}} + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"kubelet", "readonly": true, "resource": "bindings", "namespace": "*"}} + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"kubelet", "resource": "events", "namespace": "*"}} + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"alice", "resource": "*", "namespace": "projectCaribou"}} + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"bob", "readonly": true, "resource": "*", "namespace": "projectCaribou"}} + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"debbie", "resource": "pods", "namespace": "projectCaribou"}} + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"apigroupuser", "resource": "*", "namespace": "projectAnyGroup", "apiGroup": "*"}} + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"apigroupuser", "resource": "*", "namespace": "projectEmptyGroup", "apiGroup": "" }} + {"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"apigroupuser", "resource": "*", "namespace": "projectXGroup", "apiGroup": "x"}}`) + + if err != nil { + t.Fatalf("unable to read policy file: %v", err) + } + + uScheduler := user.DefaultInfo{Name: "scheduler", UID: "uid1"} + uAlice := user.DefaultInfo{Name: "alice", UID: "uid3"} + uChuck := user.DefaultInfo{Name: "chuck", UID: "uid5"} + uDebbie := user.DefaultInfo{Name: "debbie", UID: "uid6"} + uNoResource := user.DefaultInfo{Name: "noresource", UID: "uid7"} + uAPIGroup := user.DefaultInfo{Name: "apigroupuser", UID: "uid8"} + + testCases := []struct { + User user.DefaultInfo + Verb string + Resource string + APIGroup string + NS string + Path string + ExpectAllow bool + }{ + // Scheduler can read pods + {User: uScheduler, Verb: "list", Resource: "pods", NS: "ns1", ExpectAllow: true}, + {User: uScheduler, Verb: "list", Resource: "pods", NS: "", ExpectAllow: true}, + // Scheduler cannot write pods + {User: uScheduler, Verb: "create", Resource: "pods", NS: "ns1", ExpectAllow: false}, + {User: uScheduler, Verb: "create", Resource: "pods", NS: "", ExpectAllow: false}, + // Scheduler can write bindings + {User: uScheduler, Verb: "get", Resource: "bindings", NS: "ns1", ExpectAllow: true}, + {User: uScheduler, Verb: "get", Resource: "bindings", NS: "", ExpectAllow: true}, + + // Alice can read and write anything in the right namespace. + {User: uAlice, Verb: "get", Resource: "pods", NS: "projectCaribou", ExpectAllow: true}, + {User: uAlice, Verb: "get", Resource: "widgets", NS: "projectCaribou", ExpectAllow: true}, + {User: uAlice, Verb: "get", Resource: "", NS: "projectCaribou", ExpectAllow: true}, + {User: uAlice, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectAllow: true}, + {User: uAlice, Verb: "update", Resource: "widgets", NS: "projectCaribou", ExpectAllow: true}, + {User: uAlice, Verb: "update", Resource: "", NS: "projectCaribou", ExpectAllow: true}, + // .. but not the wrong namespace. + {User: uAlice, Verb: "get", Resource: "pods", NS: "ns1", ExpectAllow: false}, + {User: uAlice, Verb: "get", Resource: "widgets", NS: "ns1", ExpectAllow: false}, + {User: uAlice, Verb: "get", Resource: "", NS: "ns1", ExpectAllow: false}, + + // Debbie can write to pods in the right namespace + {User: uDebbie, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectAllow: true}, + + // Chuck can read events, since anyone can. + {User: uChuck, Verb: "get", Resource: "events", NS: "ns1", ExpectAllow: true}, + {User: uChuck, Verb: "get", Resource: "events", NS: "", ExpectAllow: true}, + // Chuck can't do other things. + {User: uChuck, Verb: "update", Resource: "events", NS: "ns1", ExpectAllow: false}, + {User: uChuck, Verb: "get", Resource: "pods", NS: "ns1", ExpectAllow: false}, + {User: uChuck, Verb: "get", Resource: "floop", NS: "ns1", ExpectAllow: false}, + // Chuck can't access things with no resource or namespace + {User: uChuck, Verb: "get", Path: "/", Resource: "", NS: "", ExpectAllow: false}, + // but can access /api + {User: uChuck, Verb: "get", Path: "/api", Resource: "", NS: "", ExpectAllow: true}, + // though he cannot write to it + {User: uChuck, Verb: "create", Path: "/api", Resource: "", NS: "", ExpectAllow: false}, + // while he can write to /custom + {User: uChuck, Verb: "update", Path: "/custom", Resource: "", NS: "", ExpectAllow: true}, + // he cannot get "/root" + {User: uChuck, Verb: "get", Path: "/root", Resource: "", NS: "", ExpectAllow: false}, + // but can get any subpath + {User: uChuck, Verb: "get", Path: "/root/", Resource: "", NS: "", ExpectAllow: true}, + {User: uChuck, Verb: "get", Path: "/root/test/1/2/3", Resource: "", NS: "", ExpectAllow: true}, + + // the user "noresource" can get any non-resource request + {User: uNoResource, Verb: "get", Path: "", Resource: "", NS: "", ExpectAllow: true}, + {User: uNoResource, Verb: "get", Path: "/", Resource: "", NS: "", ExpectAllow: true}, + {User: uNoResource, Verb: "get", Path: "/foo/bar/baz", Resource: "", NS: "", ExpectAllow: true}, + // but cannot get any request where IsResourceRequest() == true + {User: uNoResource, Verb: "get", Path: "/", Resource: "", NS: "bar", ExpectAllow: false}, + {User: uNoResource, Verb: "get", Path: "/foo/bar/baz", Resource: "foo", NS: "bar", ExpectAllow: false}, + + // Test APIGroup matching + {User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectAnyGroup", ExpectAllow: true}, + {User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectEmptyGroup", ExpectAllow: false}, + {User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectXGroup", ExpectAllow: true}, + } + for i, tc := range testCases { + attr := authorizer.AttributesRecord{ + User: &tc.User, + Verb: tc.Verb, + Resource: tc.Resource, + APIGroup: tc.APIGroup, + Namespace: tc.NS, + ResourceRequest: len(tc.NS) > 0 || len(tc.Resource) > 0, + Path: tc.Path, + } + // t.Logf("tc %2v: %v -> attr %v", i, tc, attr) + err := a.Authorize(attr) + actualAllow := bool(err == nil) + if tc.ExpectAllow != actualAllow { + t.Errorf("%d: Expected allowed=%v but actually allowed=%v, for case %+v & %+v", + i, tc.ExpectAllow, actualAllow, tc, attr) + } + } +} + +func TestSubjectMatches(t *testing.T) { + testCases := map[string]struct { + User user.DefaultInfo + Policy runtime.Object + ExpectMatch bool + }{ + "v0 empty policy matches unauthed user": { + User: user.DefaultInfo{}, + Policy: &v0.Policy{ + User: "", + Group: "", + }, + ExpectMatch: true, + }, + "v0 empty policy matches authed user": { + User: user.DefaultInfo{Name: "Foo"}, + Policy: &v0.Policy{ + User: "", + Group: "", + }, + ExpectMatch: true, + }, + "v0 empty policy matches authed user with groups": { + User: user.DefaultInfo{Name: "Foo", Groups: []string{"a", "b"}}, + Policy: &v0.Policy{ + User: "", + Group: "", + }, + ExpectMatch: true, + }, + + "v0 user policy does not match unauthed user": { + User: user.DefaultInfo{}, + Policy: &v0.Policy{ + User: "Foo", + Group: "", + }, + ExpectMatch: false, + }, + "v0 user policy does not match different user": { + User: user.DefaultInfo{Name: "Bar"}, + Policy: &v0.Policy{ + User: "Foo", + Group: "", + }, + ExpectMatch: false, + }, + "v0 user policy is case-sensitive": { + User: user.DefaultInfo{Name: "foo"}, + Policy: &v0.Policy{ + User: "Foo", + Group: "", + }, + ExpectMatch: false, + }, + "v0 user policy does not match substring": { + User: user.DefaultInfo{Name: "FooBar"}, + Policy: &v0.Policy{ + User: "Foo", + Group: "", + }, + ExpectMatch: false, + }, + "v0 user policy matches username": { + User: user.DefaultInfo{Name: "Foo"}, + Policy: &v0.Policy{ + User: "Foo", + Group: "", + }, + ExpectMatch: true, + }, + + "v0 group policy does not match unauthed user": { + User: user.DefaultInfo{}, + Policy: &v0.Policy{ + User: "", + Group: "Foo", + }, + ExpectMatch: false, + }, + "v0 group policy does not match user in different group": { + User: user.DefaultInfo{Name: "FooBar", Groups: []string{"B"}}, + Policy: &v0.Policy{ + User: "", + Group: "A", + }, + ExpectMatch: false, + }, + "v0 group policy is case-sensitive": { + User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}}, + Policy: &v0.Policy{ + User: "", + Group: "b", + }, + ExpectMatch: false, + }, + "v0 group policy does not match substring": { + User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "BBB", "C"}}, + Policy: &v0.Policy{ + User: "", + Group: "B", + }, + ExpectMatch: false, + }, + "v0 group policy matches user in group": { + User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}}, + Policy: &v0.Policy{ + User: "", + Group: "B", + }, + ExpectMatch: true, + }, + + "v0 user and group policy requires user match": { + User: user.DefaultInfo{Name: "Bar", Groups: []string{"A", "B", "C"}}, + Policy: &v0.Policy{ + User: "Foo", + Group: "B", + }, + ExpectMatch: false, + }, + "v0 user and group policy requires group match": { + User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}}, + Policy: &v0.Policy{ + User: "Foo", + Group: "D", + }, + ExpectMatch: false, + }, + "v0 user and group policy matches": { + User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}}, + Policy: &v0.Policy{ + User: "Foo", + Group: "B", + }, + ExpectMatch: true, + }, + + "v1 empty policy does not match unauthed user": { + User: user.DefaultInfo{}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "", + Group: "", + }, + }, + ExpectMatch: false, + }, + "v1 empty policy does not match authed user": { + User: user.DefaultInfo{Name: "Foo"}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "", + Group: "", + }, + }, + ExpectMatch: false, + }, + "v1 empty policy does not match authed user with groups": { + User: user.DefaultInfo{Name: "Foo", Groups: []string{"a", "b"}}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "", + Group: "", + }, + }, + ExpectMatch: false, + }, + + "v1 user policy does not match unauthed user": { + User: user.DefaultInfo{}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "Foo", + Group: "", + }, + }, + ExpectMatch: false, + }, + "v1 user policy does not match different user": { + User: user.DefaultInfo{Name: "Bar"}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "Foo", + Group: "", + }, + }, + ExpectMatch: false, + }, + "v1 user policy is case-sensitive": { + User: user.DefaultInfo{Name: "foo"}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "Foo", + Group: "", + }, + }, + ExpectMatch: false, + }, + "v1 user policy does not match substring": { + User: user.DefaultInfo{Name: "FooBar"}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "Foo", + Group: "", + }, + }, + ExpectMatch: false, + }, + "v1 user policy matches username": { + User: user.DefaultInfo{Name: "Foo"}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "Foo", + Group: "", + }, + }, + ExpectMatch: true, + }, + + "v1 group policy does not match unauthed user": { + User: user.DefaultInfo{}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "", + Group: "Foo", + }, + }, + ExpectMatch: false, + }, + "v1 group policy does not match user in different group": { + User: user.DefaultInfo{Name: "FooBar", Groups: []string{"B"}}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "", + Group: "A", + }, + }, + ExpectMatch: false, + }, + "v1 group policy is case-sensitive": { + User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "", + Group: "b", + }, + }, + ExpectMatch: false, + }, + "v1 group policy does not match substring": { + User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "BBB", "C"}}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "", + Group: "B", + }, + }, + ExpectMatch: false, + }, + "v1 group policy matches user in group": { + User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "", + Group: "B", + }, + }, + ExpectMatch: true, + }, + + "v1 user and group policy requires user match": { + User: user.DefaultInfo{Name: "Bar", Groups: []string{"A", "B", "C"}}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "Foo", + Group: "B", + }, + }, + ExpectMatch: false, + }, + "v1 user and group policy requires group match": { + User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "Foo", + Group: "D", + }, + }, + ExpectMatch: false, + }, + "v1 user and group policy matches": { + User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}}, + Policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "Foo", + Group: "B", + }, + }, + ExpectMatch: true, + }, + } + + for k, tc := range testCases { + policy := &api.Policy{} + if err := api.Scheme.Convert(tc.Policy, policy); err != nil { + t.Errorf("%s: error converting: %v", k, err) + continue + } + attr := authorizer.AttributesRecord{ + User: &tc.User, + } + actualMatch := subjectMatches(*policy, attr) + if tc.ExpectMatch != actualMatch { + t.Errorf("%v: Expected actorMatches=%v but actually got=%v", + k, tc.ExpectMatch, actualMatch) + } + } +} + +func newWithContents(t *testing.T, contents string) (authorizer.Authorizer, error) { + f, err := ioutil.TempFile("", "abac_test") + if err != nil { + t.Fatalf("unexpected error creating policyfile: %v", err) + } + f.Close() + defer os.Remove(f.Name()) + + if err := ioutil.WriteFile(f.Name(), []byte(contents), 0700); err != nil { + t.Fatalf("unexpected error writing policyfile: %v", err) + } + + pl, err := NewFromFile(f.Name()) + return pl, err +} + +func TestPolicy(t *testing.T) { + tests := []struct { + policy runtime.Object + attr authorizer.Attributes + matches bool + name string + }{ + // v0 + { + policy: &v0.Policy{}, + attr: authorizer.AttributesRecord{}, + matches: true, + name: "v0 null", + }, + + // v0 mismatches + { + policy: &v0.Policy{ + Readonly: true, + }, + attr: authorizer.AttributesRecord{}, + matches: false, + name: "v0 read-only mismatch", + }, + { + policy: &v0.Policy{ + User: "foo", + }, + attr: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "bar", + }, + }, + matches: false, + name: "v0 user name mis-match", + }, + { + policy: &v0.Policy{ + Resource: "foo", + }, + attr: authorizer.AttributesRecord{ + Resource: "bar", + ResourceRequest: true, + }, + matches: false, + name: "v0 resource mis-match", + }, + { + policy: &v0.Policy{ + User: "foo", + Resource: "foo", + Namespace: "foo", + }, + attr: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "foo", + }, + Resource: "foo", + Namespace: "foo", + ResourceRequest: true, + }, + matches: true, + name: "v0 namespace mis-match", + }, + + // v0 matches + { + policy: &v0.Policy{}, + attr: authorizer.AttributesRecord{ResourceRequest: true}, + matches: true, + name: "v0 null resource", + }, + { + policy: &v0.Policy{ + Readonly: true, + }, + attr: authorizer.AttributesRecord{ + Verb: "get", + }, + matches: true, + name: "v0 read-only match", + }, + { + policy: &v0.Policy{ + User: "foo", + }, + attr: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "foo", + }, + }, + matches: true, + name: "v0 user name match", + }, + { + policy: &v0.Policy{ + Resource: "foo", + }, + attr: authorizer.AttributesRecord{ + Resource: "foo", + ResourceRequest: true, + }, + matches: true, + name: "v0 resource match", + }, + + // v1 mismatches + { + policy: &v1beta1.Policy{}, + attr: authorizer.AttributesRecord{ + ResourceRequest: true, + }, + matches: false, + name: "v1 null", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "foo", + }, + }, + attr: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "bar", + }, + ResourceRequest: true, + }, + matches: false, + name: "v1 user name mis-match", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "*", + Readonly: true, + }, + }, + attr: authorizer.AttributesRecord{ + ResourceRequest: true, + }, + matches: false, + name: "v1 read-only mismatch", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "*", + Resource: "foo", + }, + }, + attr: authorizer.AttributesRecord{ + Resource: "bar", + ResourceRequest: true, + }, + matches: false, + name: "v1 resource mis-match", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "foo", + Namespace: "barr", + Resource: "baz", + }, + }, + attr: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "foo", + }, + Namespace: "bar", + Resource: "baz", + ResourceRequest: true, + }, + matches: false, + name: "v1 namespace mis-match", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "*", + NonResourcePath: "/api", + }, + }, + attr: authorizer.AttributesRecord{ + Path: "/api2", + ResourceRequest: false, + }, + matches: false, + name: "v1 non-resource mis-match", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "*", + NonResourcePath: "/api/*", + }, + }, + attr: authorizer.AttributesRecord{ + Path: "/api2/foo", + ResourceRequest: false, + }, + matches: false, + name: "v1 non-resource wildcard subpath mis-match", + }, + + // v1 matches + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "foo", + }, + }, + attr: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "foo", + }, + ResourceRequest: true, + }, + matches: true, + name: "v1 user match", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "*", + }, + }, + attr: authorizer.AttributesRecord{ + ResourceRequest: true, + }, + matches: true, + name: "v1 user wildcard match", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + Group: "bar", + }, + }, + attr: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "foo", + Groups: []string{"bar"}, + }, + ResourceRequest: true, + }, + matches: true, + name: "v1 group match", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + Group: "*", + }, + }, + attr: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "foo", + Groups: []string{"bar"}, + }, + ResourceRequest: true, + }, + matches: true, + name: "v1 group wildcard match", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "*", + Readonly: true, + }, + }, + attr: authorizer.AttributesRecord{ + Verb: "get", + ResourceRequest: true, + }, + matches: true, + name: "v1 read-only match", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "*", + Resource: "foo", + }, + }, + attr: authorizer.AttributesRecord{ + Resource: "foo", + ResourceRequest: true, + }, + matches: true, + name: "v1 resource match", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "foo", + Namespace: "bar", + Resource: "baz", + }, + }, + attr: authorizer.AttributesRecord{ + User: &user.DefaultInfo{ + Name: "foo", + }, + Namespace: "bar", + Resource: "baz", + ResourceRequest: true, + }, + matches: true, + name: "v1 namespace match", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "*", + NonResourcePath: "/api", + }, + }, + attr: authorizer.AttributesRecord{ + Path: "/api", + ResourceRequest: false, + }, + matches: true, + name: "v1 non-resource match", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "*", + NonResourcePath: "*", + }, + }, + attr: authorizer.AttributesRecord{ + Path: "/api", + ResourceRequest: false, + }, + matches: true, + name: "v1 non-resource wildcard match", + }, + { + policy: &v1beta1.Policy{ + Spec: v1beta1.PolicySpec{ + User: "*", + NonResourcePath: "/api/*", + }, + }, + attr: authorizer.AttributesRecord{ + Path: "/api/foo", + ResourceRequest: false, + }, + matches: true, + name: "v1 non-resource wildcard subpath match", + }, + } + for _, test := range tests { + policy := &api.Policy{} + if err := api.Scheme.Convert(test.policy, policy); err != nil { + t.Errorf("%s: error converting: %v", test.name, err) + continue + } + matches := matches(*policy, test.attr) + if test.matches != matches { + t.Errorf("%s: expected: %t, saw: %t", test.name, test.matches, matches) + continue + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/auth/authorizer/abac/example_policy_file.jsonl b/vendor/k8s.io/kubernetes/pkg/auth/authorizer/abac/example_policy_file.jsonl new file mode 100644 index 000000000..755145a1d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/auth/authorizer/abac/example_policy_file.jsonl @@ -0,0 +1,10 @@ +{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"*", "nonResourcePath": "*", "readonly": true}} +{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"admin", "namespace": "*", "resource": "*", "apiGroup": "*" }} +{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"scheduler", "namespace": "*", "resource": "pods", "readonly": true }} +{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"scheduler", "namespace": "*", "resource": "bindings" }} +{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"kubelet", "namespace": "*", "resource": "pods", "readonly": true }} +{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"kubelet", "namespace": "*", "resource": "services", "readonly": true }} +{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"kubelet", "namespace": "*", "resource": "endpoints", "readonly": true }} +{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"kubelet", "namespace": "*", "resource": "events" }} +{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"alice", "namespace": "projectCaribou", "resource": "*", "apiGroup": "*" }} +{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"bob", "namespace": "projectCaribou", "resource": "*", "apiGroup": "*", "readonly": true }} \ No newline at end of file diff --git a/vendor/k8s.io/kubernetes/pkg/auth/authorizer/interfaces.go b/vendor/k8s.io/kubernetes/pkg/auth/authorizer/interfaces.go new file mode 100644 index 000000000..d4f02efbd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/auth/authorizer/interfaces.go @@ -0,0 +1,150 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 authorizer + +import ( + "net/http" + + "k8s.io/kubernetes/pkg/auth/user" +) + +// Attributes is an interface used by an Authorizer to get information about a request +// that is used to make an authorization decision. +type Attributes interface { + // The user string which the request was authenticated as, or empty if + // no authentication occurred and the request was allowed to proceed. + GetUserName() string + + // The list of group names the authenticated user is a member of. Can be + // empty if the authenticated user is not in any groups, or if no + // authentication occurred. + GetGroups() []string + + // GetVerb returns the kube verb associated with API requests (this includes get, list, watch, create, update, patch, delete, and proxy), + // or the lowercased HTTP verb associated with non-API requests (this includes get, put, post, patch, and delete) + GetVerb() string + + // When IsReadOnly() == true, the request has no side effects, other than + // caching, logging, and other incidentals. + IsReadOnly() bool + + // The namespace of the object, if a request is for a REST object. + GetNamespace() string + + // The kind of object, if a request is for a REST object. + GetResource() string + + // GetSubresource returns the subresource being requested, if present + GetSubresource() string + + // GetName returns the name of the object as parsed off the request. This will not be present for all request types, but + // will be present for: get, update, delete + GetName() string + + // The group of the resource, if a request is for a REST object. + GetAPIGroup() string + + // GetAPIVersion returns the version of the group requested, if a request is for a REST object. + GetAPIVersion() string + + // IsResourceRequest returns true for requests to API resources, like /api/v1/nodes, + // and false for non-resource endpoints like /api, /healthz, and /swaggerapi + IsResourceRequest() bool + + // GetPath returns the path of the request + GetPath() string +} + +// Authorizer makes an authorization decision based on information gained by making +// zero or more calls to methods of the Attributes interface. It returns nil when an action is +// authorized, otherwise it returns an error. +type Authorizer interface { + Authorize(a Attributes) (err error) +} + +type AuthorizerFunc func(a Attributes) error + +func (f AuthorizerFunc) Authorize(a Attributes) error { + return f(a) +} + +// RequestAttributesGetter provides a function that extracts Attributes from an http.Request +type RequestAttributesGetter interface { + GetRequestAttributes(user.Info, *http.Request) Attributes +} + +// AttributesRecord implements Attributes interface. +type AttributesRecord struct { + User user.Info + Verb string + Namespace string + APIGroup string + APIVersion string + Resource string + Subresource string + Name string + ResourceRequest bool + Path string +} + +func (a AttributesRecord) GetUserName() string { + return a.User.GetName() +} + +func (a AttributesRecord) GetGroups() []string { + return a.User.GetGroups() +} + +func (a AttributesRecord) GetVerb() string { + return a.Verb +} + +func (a AttributesRecord) IsReadOnly() bool { + return a.Verb == "get" || a.Verb == "list" || a.Verb == "watch" +} + +func (a AttributesRecord) GetNamespace() string { + return a.Namespace +} + +func (a AttributesRecord) GetResource() string { + return a.Resource +} + +func (a AttributesRecord) GetSubresource() string { + return a.Subresource +} + +func (a AttributesRecord) GetName() string { + return a.Name +} + +func (a AttributesRecord) GetAPIGroup() string { + return a.APIGroup +} + +func (a AttributesRecord) GetAPIVersion() string { + return a.APIVersion +} + +func (a AttributesRecord) IsResourceRequest() bool { + return a.ResourceRequest +} + +func (a AttributesRecord) GetPath() string { + return a.Path +} diff --git a/vendor/k8s.io/kubernetes/pkg/auth/authorizer/union/union.go b/vendor/k8s.io/kubernetes/pkg/auth/authorizer/union/union.go new file mode 100644 index 000000000..255ad0823 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/auth/authorizer/union/union.go @@ -0,0 +1,45 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 union + +import ( + "k8s.io/kubernetes/pkg/auth/authorizer" + utilerrors "k8s.io/kubernetes/pkg/util/errors" +) + +// unionAuthzHandler authorizer against a chain of authorizer.Authorizer +type unionAuthzHandler []authorizer.Authorizer + +// New returns an authorizer that authorizes against a chain of authorizer.Authorizer objects +func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer { + return unionAuthzHandler(authorizationHandlers) +} + +// Authorizes against a chain of authorizer.Authorizer objects and returns nil if successful and returns error if unsuccessful +func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) error { + var errlist []error + for _, currAuthzHandler := range authzHandler { + err := currAuthzHandler.Authorize(a) + if err != nil { + errlist = append(errlist, err) + continue + } + return nil + } + + return utilerrors.NewAggregate(errlist) +} diff --git a/vendor/k8s.io/kubernetes/pkg/auth/authorizer/union/union_test.go b/vendor/k8s.io/kubernetes/pkg/auth/authorizer/union/union_test.go new file mode 100644 index 000000000..1a01676af --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/auth/authorizer/union/union_test.go @@ -0,0 +1,73 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 union + +import ( + "errors" + "testing" + + "k8s.io/kubernetes/pkg/auth/authorizer" +) + +type mockAuthzHandler struct { + isAuthorized bool + err error +} + +func (mock *mockAuthzHandler) Authorize(a authorizer.Attributes) error { + if mock.err != nil { + return mock.err + } + if !mock.isAuthorized { + return errors.New("Request unauthorized") + } else { + return nil + } +} + +func TestAuthorizationSecondPasses(t *testing.T) { + handler1 := &mockAuthzHandler{isAuthorized: false} + handler2 := &mockAuthzHandler{isAuthorized: true} + authzHandler := New(handler1, handler2) + + err := authzHandler.Authorize(nil) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } +} + +func TestAuthorizationFirstPasses(t *testing.T) { + handler1 := &mockAuthzHandler{isAuthorized: true} + handler2 := &mockAuthzHandler{isAuthorized: false} + authzHandler := New(handler1, handler2) + + err := authzHandler.Authorize(nil) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } +} + +func TestAuthorizationNonePasses(t *testing.T) { + handler1 := &mockAuthzHandler{isAuthorized: false} + handler2 := &mockAuthzHandler{isAuthorized: false} + authzHandler := New(handler1, handler2) + + err := authzHandler.Authorize(nil) + if err == nil { + t.Errorf("Expected error: %v", err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/auth/handlers/handlers.go b/vendor/k8s.io/kubernetes/pkg/auth/handlers/handlers.go new file mode 100644 index 000000000..ac316763a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/auth/handlers/handlers.go @@ -0,0 +1,68 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 handlers + +import ( + "net/http" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/auth/authenticator" +) + +// NewRequestAuthenticator creates an http handler that tries to authenticate the given request as a user, and then +// stores any such user found onto the provided context for the request. If authentication fails or returns an error +// the failed handler is used. On success, handler is invoked to serve the request. +func NewRequestAuthenticator(mapper api.RequestContextMapper, auth authenticator.Request, failed http.Handler, handler http.Handler) (http.Handler, error) { + return api.NewRequestContextFilter( + mapper, + http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + user, ok, err := auth.AuthenticateRequest(req) + if err != nil || !ok { + if err != nil { + glog.Errorf("Unable to authenticate the request due to an error: %v", err) + } + failed.ServeHTTP(w, req) + return + } + + if ctx, ok := mapper.Get(req); ok { + mapper.Update(req, api.WithUser(ctx, user)) + } + + handler.ServeHTTP(w, req) + }), + ) +} + +func Unauthorized(supportsBasicAuth bool) http.HandlerFunc { + if supportsBasicAuth { + return unauthorizedBasicAuth + } + return unauthorized +} + +// unauthorizedBasicAuth serves an unauthorized message to clients. +func unauthorizedBasicAuth(w http.ResponseWriter, req *http.Request) { + w.Header().Set("WWW-Authenticate", `Basic realm="kubernetes-master"`) + http.Error(w, "Unauthorized", http.StatusUnauthorized) +} + +// unauthorized serves an unauthorized message to clients. +func unauthorized(w http.ResponseWriter, req *http.Request) { + http.Error(w, "Unauthorized", http.StatusUnauthorized) +} diff --git a/vendor/k8s.io/kubernetes/pkg/auth/handlers/handlers_test.go b/vendor/k8s.io/kubernetes/pkg/auth/handlers/handlers_test.go new file mode 100644 index 000000000..6ad0e67b5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/auth/handlers/handlers_test.go @@ -0,0 +1,120 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 handlers + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/auth/authenticator" + "k8s.io/kubernetes/pkg/auth/user" +) + +func TestAuthenticateRequest(t *testing.T) { + success := make(chan struct{}) + contextMapper := api.NewRequestContextMapper() + auth, err := NewRequestAuthenticator( + contextMapper, + authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) { + return &user.DefaultInfo{Name: "user"}, true, nil + }), + http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + t.Errorf("unexpected call to failed") + }), + http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) { + ctx, ok := contextMapper.Get(req) + if ctx == nil || !ok { + t.Errorf("no context stored on contextMapper: %#v", contextMapper) + } + user, ok := api.UserFrom(ctx) + if user == nil || !ok { + t.Errorf("no user stored in context: %#v", ctx) + } + close(success) + }), + ) + + auth.ServeHTTP(httptest.NewRecorder(), &http.Request{}) + + <-success + empty, err := api.IsEmpty(contextMapper) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !empty { + t.Fatalf("contextMapper should have no stored requests: %v", contextMapper) + } +} + +func TestAuthenticateRequestFailed(t *testing.T) { + failed := make(chan struct{}) + contextMapper := api.NewRequestContextMapper() + auth, err := NewRequestAuthenticator( + contextMapper, + authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) { + return nil, false, nil + }), + http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + close(failed) + }), + http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) { + t.Errorf("unexpected call to handler") + }), + ) + + auth.ServeHTTP(httptest.NewRecorder(), &http.Request{}) + + <-failed + empty, err := api.IsEmpty(contextMapper) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !empty { + t.Fatalf("contextMapper should have no stored requests: %v", contextMapper) + } +} + +func TestAuthenticateRequestError(t *testing.T) { + failed := make(chan struct{}) + contextMapper := api.NewRequestContextMapper() + auth, err := NewRequestAuthenticator( + contextMapper, + authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) { + return nil, false, errors.New("failure") + }), + http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + close(failed) + }), + http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) { + t.Errorf("unexpected call to handler") + }), + ) + + auth.ServeHTTP(httptest.NewRecorder(), &http.Request{}) + + <-failed + empty, err := api.IsEmpty(contextMapper) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !empty { + t.Fatalf("contextMapper should have no stored requests: %v", contextMapper) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/OWNERS b/vendor/k8s.io/kubernetes/pkg/client/OWNERS new file mode 100644 index 000000000..e3fcb227a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/OWNERS @@ -0,0 +1,6 @@ +assignees: + - caesarxuchao + - deads2k + - krousey + - lavalamp + - smarterclayton diff --git a/vendor/k8s.io/kubernetes/pkg/client/cache/delta_fifo.go b/vendor/k8s.io/kubernetes/pkg/client/cache/delta_fifo.go index e7cc1aad1..e5dce16b6 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/cache/delta_fifo.go +++ b/vendor/k8s.io/kubernetes/pkg/client/cache/delta_fifo.go @@ -82,7 +82,7 @@ func NewDeltaFIFO(keyFunc KeyFunc, compressor DeltaCompressor, knownObjects KeyL // different versions of the same object. // // A note on the KeyLister used by the DeltaFIFO: It's main purpose is -// to list keys that are "known", for the puspose of figuring out which +// to list keys that are "known", for the purpose of figuring out which // items have been deleted when Replace() or Delete() are called. The deleted // objet will be included in the DeleteFinalStateUnknown markers. These objects // could be stale. diff --git a/vendor/k8s.io/kubernetes/pkg/client/cache/expiration_cache.go b/vendor/k8s.io/kubernetes/pkg/client/cache/expiration_cache.go index 5eb996b66..964deda07 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/cache/expiration_cache.go +++ b/vendor/k8s.io/kubernetes/pkg/client/cache/expiration_cache.go @@ -17,11 +17,11 @@ limitations under the License. package cache import ( + "sync" "time" "github.com/golang/glog" "k8s.io/kubernetes/pkg/util" - "k8s.io/kubernetes/pkg/util/runtime" ) // ExpirationCache implements the store interface @@ -29,12 +29,20 @@ import ( // a. The key is computed based off the original item/keyFunc // b. The value inserted under that key is the timestamped item // 2. Expiration happens lazily on read based on the expiration policy +// a. No item can be inserted into the store while we're expiring +// *any* item in the cache. // 3. Time-stamps are stripped off unexpired entries before return +// Note that the ExpirationCache is inherently slower than a normal +// threadSafeStore because it takes a write lock every time it checks if +// an item has expired. type ExpirationCache struct { cacheStorage ThreadSafeStore keyFunc KeyFunc clock util.Clock expirationPolicy ExpirationPolicy + // expirationLock is a write lock used to guarantee that we don't clobber + // newly inserted objects because of a stale expiration timestamp comparison + expirationLock sync.Mutex } // ExpirationPolicy dictates when an object expires. Currently only abstracted out @@ -68,7 +76,6 @@ type timestampedEntry struct { // getTimestampedEntry returnes the timestampedEntry stored under the given key. func (c *ExpirationCache) getTimestampedEntry(key string) (*timestampedEntry, bool) { item, _ := c.cacheStorage.Get(key) - // TODO: Check the cast instead if tsEntry, ok := item.(*timestampedEntry); ok { return tsEntry, true } @@ -76,24 +83,20 @@ func (c *ExpirationCache) getTimestampedEntry(key string) (*timestampedEntry, bo } // getOrExpire retrieves the object from the timestampedEntry if and only if it hasn't -// already expired. It kicks-off a go routine to delete expired objects from -// the store and sets exists=false. +// already expired. It holds a write lock across deletion. func (c *ExpirationCache) getOrExpire(key string) (interface{}, bool) { + // Prevent all inserts from the time we deem an item as "expired" to when we + // delete it, so an un-expired item doesn't sneak in under the same key, just + // before the Delete. + c.expirationLock.Lock() + defer c.expirationLock.Unlock() timestampedItem, exists := c.getTimestampedEntry(key) if !exists { return nil, false } if c.expirationPolicy.IsExpired(timestampedItem) { glog.V(4).Infof("Entry %v: %+v has expired", key, timestampedItem.obj) - // Since expiration happens lazily on read, don't hold up - // the reader trying to acquire a write lock for the delete. - // The next reader will retry the delete even if this one - // fails; as long as we only return un-expired entries a - // reader doesn't need to wait for the result of the delete. - go func() { - defer runtime.HandleCrash() - c.cacheStorage.Delete(key) - }() + c.cacheStorage.Delete(key) return nil, false } return timestampedItem.obj, true @@ -141,6 +144,8 @@ func (c *ExpirationCache) ListKeys() []string { // Add timestamps an item and inserts it into the cache, overwriting entries // that might exist under the same key. func (c *ExpirationCache) Add(obj interface{}) error { + c.expirationLock.Lock() + defer c.expirationLock.Unlock() key, err := c.keyFunc(obj) if err != nil { return KeyError{obj, err} @@ -157,6 +162,8 @@ func (c *ExpirationCache) Update(obj interface{}) error { // Delete removes an item from the cache. func (c *ExpirationCache) Delete(obj interface{}) error { + c.expirationLock.Lock() + defer c.expirationLock.Unlock() key, err := c.keyFunc(obj) if err != nil { return KeyError{obj, err} @@ -169,6 +176,8 @@ func (c *ExpirationCache) Delete(obj interface{}) error { // before attempting the replace operation. The replace operation will // delete the contents of the ExpirationCache `c`. func (c *ExpirationCache) Replace(list []interface{}, resourceVersion string) error { + c.expirationLock.Lock() + defer c.expirationLock.Unlock() items := map[string]interface{}{} ts := c.clock.Now() for _, item := range list { diff --git a/vendor/k8s.io/kubernetes/pkg/client/cache/expiration_cache_fakes.go b/vendor/k8s.io/kubernetes/pkg/client/cache/expiration_cache_fakes.go index 2e9a25d12..3b9597705 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/cache/expiration_cache_fakes.go +++ b/vendor/k8s.io/kubernetes/pkg/client/cache/expiration_cache_fakes.go @@ -28,6 +28,7 @@ type fakeThreadSafeMap struct { func (c *fakeThreadSafeMap) Delete(key string) { if c.deletedKeys != nil { + c.ThreadSafeStore.Delete(key) c.deletedKeys <- key } } diff --git a/vendor/k8s.io/kubernetes/pkg/client/cache/expiration_cache_test.go b/vendor/k8s.io/kubernetes/pkg/client/cache/expiration_cache_test.go index 2e8cc5b57..04a05786f 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/cache/expiration_cache_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/cache/expiration_cache_test.go @@ -28,7 +28,7 @@ import ( func TestTTLExpirationBasic(t *testing.T) { testObj := testStoreObject{id: "foo", val: "bar"} - deleteChan := make(chan string) + deleteChan := make(chan string, 1) ttlStore := NewFakeExpirationStore( testStoreKeyFunc, deleteChan, &FakeExpirationPolicy{ @@ -62,6 +62,59 @@ func TestTTLExpirationBasic(t *testing.T) { close(deleteChan) } +func TestReAddExpiredItem(t *testing.T) { + deleteChan := make(chan string, 1) + exp := &FakeExpirationPolicy{ + NeverExpire: sets.NewString(), + RetrieveKeyFunc: func(obj interface{}) (string, error) { + return obj.(*timestampedEntry).obj.(testStoreObject).id, nil + }, + } + ttlStore := NewFakeExpirationStore( + testStoreKeyFunc, deleteChan, exp, util.RealClock{}) + testKey := "foo" + testObj := testStoreObject{id: testKey, val: "bar"} + err := ttlStore.Add(testObj) + if err != nil { + t.Errorf("Unable to add obj %#v", testObj) + } + + // This get will expire the item. + item, exists, err := ttlStore.Get(testObj) + if err != nil { + t.Errorf("Failed to get from store, %v", err) + } + if exists || item != nil { + t.Errorf("Got unexpected item %#v", item) + } + + key, _ := testStoreKeyFunc(testObj) + differentValue := "different_bar" + err = ttlStore.Add( + testStoreObject{id: testKey, val: differentValue}) + if err != nil { + t.Errorf("Failed to add second value") + } + + select { + case delKey := <-deleteChan: + if delKey != key { + t.Errorf("Unexpected delete for key %s", key) + } + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Unexpected timeout waiting on delete") + } + exp.NeverExpire = sets.NewString(testKey) + item, exists, err = ttlStore.GetByKey(testKey) + if err != nil { + t.Errorf("Failed to get from store, %v", err) + } + if !exists || item == nil || item.(testStoreObject).val != differentValue { + t.Errorf("Got unexpected item %#v", item) + } + close(deleteChan) +} + func TestTTLList(t *testing.T) { testObjs := []testStoreObject{ {id: "foo", val: "bar"}, @@ -69,7 +122,7 @@ func TestTTLList(t *testing.T) { {id: "foo2", val: "bar2"}, } expireKeys := sets.NewString(testObjs[0].id, testObjs[2].id) - deleteChan := make(chan string) + deleteChan := make(chan string, len(testObjs)) defer close(deleteChan) ttlStore := NewFakeExpirationStore( diff --git a/vendor/k8s.io/kubernetes/pkg/client/cache/listers.go b/vendor/k8s.io/kubernetes/pkg/client/cache/listers.go index 81bac838e..3963a6941 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/cache/listers.go +++ b/vendor/k8s.io/kubernetes/pkg/client/cache/listers.go @@ -252,7 +252,7 @@ func (s *StoreToDeploymentLister) GetDeploymentsForReplicaSet(rs *extensions.Rep continue } - selector, err := unversioned.LabelSelectorAsSelector(rs.Spec.Selector) + selector, err := unversioned.LabelSelectorAsSelector(d.Spec.Selector) if err != nil { return nil, fmt.Errorf("invalid label selector: %v", err) } diff --git a/vendor/k8s.io/kubernetes/pkg/client/cache/listwatch.go b/vendor/k8s.io/kubernetes/pkg/client/cache/listwatch.go index 18528d236..06c2f611b 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/cache/listwatch.go +++ b/vendor/k8s.io/kubernetes/pkg/client/cache/listwatch.go @@ -20,7 +20,7 @@ import ( "time" "k8s.io/kubernetes/pkg/api" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/watch" @@ -42,7 +42,7 @@ type ListWatch struct { // Getter interface knows how to access Get method from RESTClient. type Getter interface { - Get() *client.Request + Get() *restclient.Request } // NewListWatchFromClient creates a new ListWatch from the specified client, resource, namespace and field selector. diff --git a/vendor/k8s.io/kubernetes/pkg/client/cache/listwatch_test.go b/vendor/k8s.io/kubernetes/pkg/client/cache/listwatch_test.go index a0a72f5d8..0008bd0ff 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/cache/listwatch_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/cache/listwatch_test.go @@ -24,6 +24,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/fields" utiltesting "k8s.io/kubernetes/pkg/util/testing" @@ -97,7 +98,7 @@ func TestListWatchesCanList(t *testing.T) { server := httptest.NewServer(&handler) // TODO: Uncomment when fix #19254 // defer server.Close() - client := client.NewOrDie(&client.Config{Host: server.URL, ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + client := client.NewOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) lw := NewListWatchFromClient(client, item.resource, item.namespace, item.fieldSelector) // This test merely tests that the correct request is made. lw.List(api.ListOptions{}) @@ -164,7 +165,7 @@ func TestListWatchesCanWatch(t *testing.T) { server := httptest.NewServer(&handler) // TODO: Uncomment when fix #19254 // defer server.Close() - client := client.NewOrDie(&client.Config{Host: server.URL, ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + client := client.NewOrDie(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) lw := NewListWatchFromClient(client, item.resource, item.namespace, item.fieldSelector) // This test merely tests that the correct request is made. lw.Watch(api.ListOptions{ResourceVersion: item.rv}) diff --git a/vendor/k8s.io/kubernetes/pkg/client/cache/reflector.go b/vendor/k8s.io/kubernetes/pkg/client/cache/reflector.go index fa761d5c5..392c6fbd9 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/cache/reflector.go +++ b/vendor/k8s.io/kubernetes/pkg/client/cache/reflector.go @@ -24,7 +24,10 @@ import ( "net" "net/url" "reflect" + "regexp" goruntime "runtime" + "runtime/debug" + "strconv" "strings" "sync" "syscall" @@ -83,7 +86,7 @@ var ( // TCP connection. minWatchTimeout = 5 * time.Minute // If we are within 'forceResyncThreshold' from the next planned resync - // and are just before issueing Watch(), resync will be forced now. + // and are just before issuing Watch(), resync will be forced now. forceResyncThreshold = 3 * time.Second // We try to set timeouts for Watch() so that we will finish about // than 'timeoutThreshold' from next planned periodic resync. @@ -124,45 +127,86 @@ func NewNamedReflector(name string, lw ListerWatcher, expectedType interface{}, // internalPackages are packages that ignored when creating a default reflector name. These packages are in the common // call chains to NewReflector, so they'd be low entropy names for reflectors -var internalPackages = []string{"kubernetes/pkg/client/cache/", "kubernetes/pkg/controller/framework/"} +var internalPackages = []string{"kubernetes/pkg/client/cache/", "kubernetes/pkg/controller/framework/", "/runtime/asm_"} // getDefaultReflectorName walks back through the call stack until we find a caller from outside of the ignoredPackages // it returns back a shortpath/filename:line to aid in identification of this reflector when it starts logging func getDefaultReflectorName(ignoredPackages ...string) string { name := "????" -outer: - for i := 1; i < 10; i++ { + const maxStack = 10 + for i := 1; i < maxStack; i++ { _, file, line, ok := goruntime.Caller(i) if !ok { - break - } - for _, ignoredPackage := range ignoredPackages { - if strings.Contains(file, ignoredPackage) { - continue outer + file, line, ok = extractStackCreator() + if !ok { + break } - + i += maxStack + } + if hasPackage(file, ignoredPackages) { + continue } - pkgLocation := strings.LastIndex(file, "/pkg/") - if pkgLocation >= 0 { - file = file[pkgLocation+1:] - } + file = trimPackagePrefix(file) name = fmt.Sprintf("%s:%d", file, line) break } - return name } +// hasPackage returns true if the file is in one of the ignored packages. +func hasPackage(file string, ignoredPackages []string) bool { + for _, ignoredPackage := range ignoredPackages { + if strings.Contains(file, ignoredPackage) { + return true + } + } + return false +} + +// trimPackagePrefix reduces dulpicate values off the front of a package name. +func trimPackagePrefix(file string) string { + if l := strings.LastIndex(file, "k8s.io/kubernetes/pkg/"); l >= 0 { + return file[l+len("k8s.io/kubernetes/"):] + } + if l := strings.LastIndex(file, "/src/"); l >= 0 { + return file[l+5:] + } + if l := strings.LastIndex(file, "/pkg/"); l >= 0 { + return file[l+1:] + } + return file +} + +var stackCreator = regexp.MustCompile(`(?m)^created by (.*)\n\s+(.*):(\d+) \+0x[[:xdigit:]]+$`) + +// extractStackCreator retrieves the goroutine file and line that launched this stack. Returns false +// if the creator cannot be located. +// TODO: Go does not expose this via runtime https://github.com/golang/go/issues/11440 +func extractStackCreator() (string, int, bool) { + stack := debug.Stack() + matches := stackCreator.FindStringSubmatch(string(stack)) + if matches == nil || len(matches) != 4 { + return "", 0, false + } + line, err := strconv.Atoi(matches[3]) + if err != nil { + return "", 0, false + } + return matches[2], line, true +} + // Run starts a watch and handles watch events. Will restart the watch if it is closed. // Run starts a goroutine and returns immediately. func (r *Reflector) Run() { + glog.V(3).Infof("Starting reflector %v (%s) from %s", r.expectedType, r.resyncPeriod, r.name) go wait.Until(func() { r.ListAndWatch(wait.NeverStop) }, r.period, wait.NeverStop) } // RunUntil starts a watch and handles watch events. Will restart the watch if it is closed. // RunUntil starts a goroutine and returns immediately. It will exit when stopCh is closed. func (r *Reflector) RunUntil(stopCh <-chan struct{}) { + glog.V(3).Infof("Starting reflector %v (%s) from %s", r.expectedType, r.resyncPeriod, r.name) go wait.Until(func() { r.ListAndWatch(stopCh) }, r.period, stopCh) } @@ -227,6 +271,7 @@ func (r *Reflector) canForceResyncNow() bool { // and then use the resource version to watch. // It returns error if ListAndWatch didn't even try to initialize watch. func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error { + glog.V(3).Infof("Listing and watching %v from %s", r.expectedType, r.name) var resourceVersion string resyncCh, cleanup := r.resyncChan() defer cleanup() diff --git a/vendor/k8s.io/kubernetes/pkg/client/chaosclient/chaosclient.go b/vendor/k8s.io/kubernetes/pkg/client/chaosclient/chaosclient.go new file mode 100644 index 000000000..a0ed4b4c1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/chaosclient/chaosclient.go @@ -0,0 +1,156 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 chaosclient makes it easy to simulate network latency, misbehaving +// servers, and random errors from servers. It is intended to stress test components +// under failure conditions and expose weaknesses in the error handling logic +// of the codebase. +package chaosclient + +import ( + "errors" + "fmt" + "log" + "math/rand" + "net/http" + "reflect" + "runtime" + + "k8s.io/kubernetes/pkg/util/net" +) + +// chaosrt provides the ability to perform simulations of HTTP client failures +// under the Golang http.Transport interface. +type chaosrt struct { + rt http.RoundTripper + notify ChaosNotifier + c []Chaos +} + +// Chaos intercepts requests to a remote HTTP endpoint and can inject arbitrary +// failures. +type Chaos interface { + // Intercept should return true if the normal flow should be skipped, and the + // return response and error used instead. Modifications to the request will + // be ignored, but may be used to make decisions about types of failures. + Intercept(req *http.Request) (bool, *http.Response, error) +} + +// ChaosNotifier notifies another component that the ChaosRoundTripper has simulated +// a failure. +type ChaosNotifier interface { + // OnChaos is invoked when a chaotic outcome was triggered. fn is the + // source of Chaos and req was the outgoing request + OnChaos(req *http.Request, c Chaos) +} + +// ChaosFunc takes an http.Request and decides whether to alter the response. It +// returns true if it wishes to mutate the response, with a http.Response or +// error. +type ChaosFunc func(req *http.Request) (bool, *http.Response, error) + +func (fn ChaosFunc) Intercept(req *http.Request) (bool, *http.Response, error) { + return fn.Intercept(req) +} +func (fn ChaosFunc) String() string { + return runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name() +} + +// NewChaosRoundTripper creates an http.RoundTripper that will intercept requests +// based on the provided Chaos functions. The notifier is invoked when a Chaos +// Intercept is fired. +func NewChaosRoundTripper(rt http.RoundTripper, notify ChaosNotifier, c ...Chaos) http.RoundTripper { + return &chaosrt{rt, notify, c} +} + +// RoundTrip gives each ChaosFunc an opportunity to intercept the request. The first +// interceptor wins. +func (rt *chaosrt) RoundTrip(req *http.Request) (*http.Response, error) { + for _, c := range rt.c { + if intercept, resp, err := c.Intercept(req); intercept { + rt.notify.OnChaos(req, c) + return resp, err + } + } + return rt.rt.RoundTrip(req) +} + +var _ = net.RoundTripperWrapper(&chaosrt{}) + +func (rt *chaosrt) WrappedRoundTripper() http.RoundTripper { + return rt.rt +} + +// Seed represents a consistent stream of chaos. +type Seed struct { + *rand.Rand +} + +// NewSeed creates an object that assists in generating random chaotic events +// based on a deterministic seed. +func NewSeed(seed int64) Seed { + return Seed{rand.New(rand.NewSource(seed))} +} + +type pIntercept struct { + Chaos + s Seed + p float64 +} + +// P returns a ChaosFunc that fires with a probability of p (p between 0.0 +// and 1.0 with 0.0 meaning never and 1.0 meaning always). +func (s Seed) P(p float64, c Chaos) Chaos { + return pIntercept{c, s, p} +} + +// Intercept intercepts requests with the provided probability p. +func (c pIntercept) Intercept(req *http.Request) (bool, *http.Response, error) { + if c.s.Float64() < c.p { + return c.Chaos.Intercept(req) + } + return false, nil, nil +} + +func (c pIntercept) String() string { + return fmt.Sprintf("P{%f %s}", c.p, c.Chaos) +} + +// ErrSimulatedConnectionResetByPeer emulates the golang net error when a connection +// is reset by a peer. +// TODO: make this more accurate +// TODO: add other error types +// TODO: add a helper for returning multiple errors randomly. +var ErrSimulatedConnectionResetByPeer = Error{errors.New("connection reset by peer")} + +// Error returns the nested error when C() is invoked. +type Error struct { + error +} + +// C returns the nested error +func (e Error) Intercept(_ *http.Request) (bool, *http.Response, error) { + return true, nil, e.error +} + +// LogChaos is the default ChaosNotifier and writes a message to the Golang log. +var LogChaos = ChaosNotifier(logChaos{}) + +type logChaos struct{} + +func (logChaos) OnChaos(req *http.Request, c Chaos) { + log.Printf("Triggered chaotic behavior for %s %s: %v", req.Method, req.URL.String(), c) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/chaosclient/chaosclient_test.go b/vendor/k8s.io/kubernetes/pkg/client/chaosclient/chaosclient_test.go new file mode 100644 index 000000000..fd2cf876b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/chaosclient/chaosclient_test.go @@ -0,0 +1,84 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 chaosclient + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" +) + +type TestLogChaos struct { + *testing.T +} + +func (t TestLogChaos) OnChaos(req *http.Request, c Chaos) { + t.Logf("CHAOS: chaotic behavior for %s %s: %v", req.Method, req.URL.String(), c) +} + +func unwrapURLError(err error) error { + if urlErr, ok := err.(*url.Error); ok && urlErr != nil { + return urlErr.Err + } + return err +} + +func TestChaos(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + })) + // TODO: Uncomment when fix #19254 + // defer server.Close() + client := http.Client{ + Transport: NewChaosRoundTripper(http.DefaultTransport, TestLogChaos{t}, ErrSimulatedConnectionResetByPeer), + } + resp, err := client.Get(server.URL) + if unwrapURLError(err) != ErrSimulatedConnectionResetByPeer.error { + t.Fatalf("expected reset by peer: %v", err) + } + if resp != nil { + t.Fatalf("expected no response object: %#v", resp) + } +} + +func TestPartialChaos(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + })) + // TODO: Uncomment when fix #19254 + // defer server.Close() + seed := NewSeed(1) + client := http.Client{ + Transport: NewChaosRoundTripper( + http.DefaultTransport, TestLogChaos{t}, + seed.P(0.5, ErrSimulatedConnectionResetByPeer), + ), + } + success, fail := 0, 0 + for { + _, err := client.Get(server.URL) + if err != nil { + fail++ + } else { + success++ + } + if success > 1 && fail > 1 { + break + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/clientset.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/clientset.go index 3fe018b9c..8ba8558e9 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/clientset.go +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/clientset.go @@ -18,54 +18,55 @@ package internalclientset import ( "github.com/golang/glog" - core_unversioned "k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned" - extensions_unversioned "k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned" - unversioned "k8s.io/kubernetes/pkg/client/unversioned" + unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + unversionedextensions "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned" + restclient "k8s.io/kubernetes/pkg/client/restclient" + discovery "k8s.io/kubernetes/pkg/client/typed/discovery" ) type Interface interface { - Discovery() unversioned.DiscoveryInterface - Core() core_unversioned.CoreInterface - Extensions() extensions_unversioned.ExtensionsInterface + Discovery() discovery.DiscoveryInterface + Core() unversionedcore.CoreInterface + Extensions() unversionedextensions.ExtensionsInterface } // Clientset contains the clients for groups. Each group has exactly one // version included in a Clientset. type Clientset struct { - *unversioned.DiscoveryClient - *core_unversioned.CoreClient - *extensions_unversioned.ExtensionsClient + *discovery.DiscoveryClient + *unversionedcore.CoreClient + *unversionedextensions.ExtensionsClient } // Core retrieves the CoreClient -func (c *Clientset) Core() core_unversioned.CoreInterface { +func (c *Clientset) Core() unversionedcore.CoreInterface { return c.CoreClient } // Extensions retrieves the ExtensionsClient -func (c *Clientset) Extensions() extensions_unversioned.ExtensionsInterface { +func (c *Clientset) Extensions() unversionedextensions.ExtensionsInterface { return c.ExtensionsClient } // Discovery retrieves the DiscoveryClient -func (c *Clientset) Discovery() unversioned.DiscoveryInterface { +func (c *Clientset) Discovery() discovery.DiscoveryInterface { return c.DiscoveryClient } // NewForConfig creates a new Clientset for the given config. -func NewForConfig(c *unversioned.Config) (*Clientset, error) { +func NewForConfig(c *restclient.Config) (*Clientset, error) { var clientset Clientset var err error - clientset.CoreClient, err = core_unversioned.NewForConfig(c) + clientset.CoreClient, err = unversionedcore.NewForConfig(c) if err != nil { return &clientset, err } - clientset.ExtensionsClient, err = extensions_unversioned.NewForConfig(c) + clientset.ExtensionsClient, err = unversionedextensions.NewForConfig(c) if err != nil { return &clientset, err } - clientset.DiscoveryClient, err = unversioned.NewDiscoveryClientForConfig(c) + clientset.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(c) if err != nil { glog.Errorf("failed to create the DiscoveryClient: %v", err) } @@ -74,21 +75,21 @@ func NewForConfig(c *unversioned.Config) (*Clientset, error) { // NewForConfigOrDie creates a new Clientset for the given config and // panics if there is an error in the config. -func NewForConfigOrDie(c *unversioned.Config) *Clientset { +func NewForConfigOrDie(c *restclient.Config) *Clientset { var clientset Clientset - clientset.CoreClient = core_unversioned.NewForConfigOrDie(c) - clientset.ExtensionsClient = extensions_unversioned.NewForConfigOrDie(c) + clientset.CoreClient = unversionedcore.NewForConfigOrDie(c) + clientset.ExtensionsClient = unversionedextensions.NewForConfigOrDie(c) - clientset.DiscoveryClient = unversioned.NewDiscoveryClientForConfigOrDie(c) + clientset.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) return &clientset } // New creates a new Clientset for the given RESTClient. -func New(c *unversioned.RESTClient) *Clientset { +func New(c *restclient.RESTClient) *Clientset { var clientset Clientset - clientset.CoreClient = core_unversioned.New(c) - clientset.ExtensionsClient = extensions_unversioned.New(c) + clientset.CoreClient = unversionedcore.New(c) + clientset.ExtensionsClient = unversionedextensions.New(c) - clientset.DiscoveryClient = unversioned.NewDiscoveryClient(c) + clientset.DiscoveryClient = discovery.NewDiscoveryClient(c) return &clientset } diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/clientset_adaption.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/clientset_adaption.go deleted file mode 100644 index f16573a93..000000000 --- a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/clientset_adaption.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors All rights reserved. - -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 internalclientset - -import ( - core_unversioned "k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned" - extensions_unversioned "k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned" -) - -// FromUnversionedClient adapts a pkg/client/unversioned#Client to a Clientset. -// This function is temporary. We will remove it when everyone has moved to using -// Clientset. New code should NOT use this function. -func FromUnversionedClient(c *unversioned.Client) *Clientset { - var clientset Clientset - if c != nil { - clientset.CoreClient = core_unversioned.New(c.RESTClient) - } else { - clientset.CoreClient = core_unversioned.New(nil) - } - if c != nil && c.ExtensionsClient != nil { - clientset.ExtensionsClient = extensions_unversioned.New(c.ExtensionsClient.RESTClient) - } else { - clientset.ExtensionsClient = extensions_unversioned.New(nil) - } - - return &clientset -} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/doc.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/doc.go new file mode 100644 index 000000000..3934caa42 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This package is generated by client-gen with the default arguments. + +// This package has the automatically generated clientset. +package internalclientset diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake/clientset_generated.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake/clientset_generated.go new file mode 100644 index 000000000..a51f1d422 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake/clientset_generated.go @@ -0,0 +1,72 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apimachinery/registered" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + fakeunversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake" + unversionedextensions "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned" + fakeunversionedextensions "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/client/typed/discovery" + fakediscovery "k8s.io/kubernetes/pkg/client/typed/discovery/fake" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/watch" +) + +// Clientset returns a clientset that will respond with the provided objects +func NewSimpleClientset(objects ...runtime.Object) *Clientset { + o := core.NewObjects(api.Scheme, api.Codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + fakePtr := core.Fake{} + fakePtr.AddReactor("*", "*", core.ObjectReaction(o, registered.RESTMapper())) + + fakePtr.AddWatchReactor("*", core.DefaultWatchReactor(watch.NewFake(), nil)) + + return &Clientset{fakePtr} +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type Clientset struct { + core.Fake +} + +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + return &fakediscovery.FakeDiscovery{&c.Fake} +} + +var _ clientset.Interface = &Clientset{} + +// Core retrieves the CoreClient +func (c *Clientset) Core() unversionedcore.CoreInterface { + return &fakeunversionedcore.FakeCore{&c.Fake} +} + +// Extensions retrieves the ExtensionsClient +func (c *Clientset) Extensions() unversionedextensions.ExtensionsInterface { + return &fakeunversionedextensions.FakeExtensions{&c.Fake} +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake/doc.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake/doc.go new file mode 100644 index 000000000..559cf8914 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This package is generated by client-gen with the default arguments. + +// This package has the automatically generated fake clientset. +package fake diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/import_known_versions.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/import_known_versions.go new file mode 100644 index 000000000..a74dbc5d2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/import_known_versions.go @@ -0,0 +1,37 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 internalclientset + +// These imports are the API groups the client will support. +import ( + "fmt" + + _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/apimachinery/registered" + _ "k8s.io/kubernetes/pkg/apis/authorization/install" + _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" + _ "k8s.io/kubernetes/pkg/apis/batch/install" + _ "k8s.io/kubernetes/pkg/apis/componentconfig/install" + _ "k8s.io/kubernetes/pkg/apis/extensions/install" + _ "k8s.io/kubernetes/pkg/apis/metrics/install" +) + +func init() { + if missingVersions := registered.ValidateEnvRequestedVersions(); len(missingVersions) != 0 { + panic(fmt.Sprintf("KUBE_API_VERSIONS contains versions that are not installed: %q.", missingVersions)) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/componentstatus.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/componentstatus.go new file mode 100644 index 000000000..0ef0667da --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/componentstatus.go @@ -0,0 +1,126 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ComponentStatusesGetter has a method to return a ComponentStatusInterface. +// A group's client should implement this interface. +type ComponentStatusesGetter interface { + ComponentStatuses() ComponentStatusInterface +} + +// ComponentStatusInterface has methods to work with ComponentStatus resources. +type ComponentStatusInterface interface { + Create(*api.ComponentStatus) (*api.ComponentStatus, error) + Update(*api.ComponentStatus) (*api.ComponentStatus, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.ComponentStatus, error) + List(opts api.ListOptions) (*api.ComponentStatusList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ComponentStatusExpansion +} + +// componentStatuses implements ComponentStatusInterface +type componentStatuses struct { + client *CoreClient +} + +// newComponentStatuses returns a ComponentStatuses +func newComponentStatuses(c *CoreClient) *componentStatuses { + return &componentStatuses{ + client: c, + } +} + +// Create takes the representation of a componentStatus and creates it. Returns the server's representation of the componentStatus, and an error, if there is any. +func (c *componentStatuses) Create(componentStatus *api.ComponentStatus) (result *api.ComponentStatus, err error) { + result = &api.ComponentStatus{} + err = c.client.Post(). + Resource("componentstatuses"). + Body(componentStatus). + Do(). + Into(result) + return +} + +// Update takes the representation of a componentStatus and updates it. Returns the server's representation of the componentStatus, and an error, if there is any. +func (c *componentStatuses) Update(componentStatus *api.ComponentStatus) (result *api.ComponentStatus, err error) { + result = &api.ComponentStatus{} + err = c.client.Put(). + Resource("componentstatuses"). + Name(componentStatus.Name). + Body(componentStatus). + Do(). + Into(result) + return +} + +// Delete takes name of the componentStatus and deletes it. Returns an error if one occurs. +func (c *componentStatuses) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("componentstatuses"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *componentStatuses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("componentstatuses"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the componentStatus, and returns the corresponding componentStatus object, and an error if there is any. +func (c *componentStatuses) Get(name string) (result *api.ComponentStatus, err error) { + result = &api.ComponentStatus{} + err = c.client.Get(). + Resource("componentstatuses"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ComponentStatuses that match those selectors. +func (c *componentStatuses) List(opts api.ListOptions) (result *api.ComponentStatusList, err error) { + result = &api.ComponentStatusList{} + err = c.client.Get(). + Resource("componentstatuses"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested componentStatuses. +func (c *componentStatuses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("componentstatuses"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/configmap.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/configmap.go new file mode 100644 index 000000000..b43e53d6c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/configmap.go @@ -0,0 +1,135 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ConfigMapsGetter has a method to return a ConfigMapInterface. +// A group's client should implement this interface. +type ConfigMapsGetter interface { + ConfigMaps(namespace string) ConfigMapInterface +} + +// ConfigMapInterface has methods to work with ConfigMap resources. +type ConfigMapInterface interface { + Create(*api.ConfigMap) (*api.ConfigMap, error) + Update(*api.ConfigMap) (*api.ConfigMap, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.ConfigMap, error) + List(opts api.ListOptions) (*api.ConfigMapList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ConfigMapExpansion +} + +// configMaps implements ConfigMapInterface +type configMaps struct { + client *CoreClient + ns string +} + +// newConfigMaps returns a ConfigMaps +func newConfigMaps(c *CoreClient, namespace string) *configMaps { + return &configMaps{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a configMap and creates it. Returns the server's representation of the configMap, and an error, if there is any. +func (c *configMaps) Create(configMap *api.ConfigMap) (result *api.ConfigMap, err error) { + result = &api.ConfigMap{} + err = c.client.Post(). + Namespace(c.ns). + Resource("configmaps"). + Body(configMap). + Do(). + Into(result) + return +} + +// Update takes the representation of a configMap and updates it. Returns the server's representation of the configMap, and an error, if there is any. +func (c *configMaps) Update(configMap *api.ConfigMap) (result *api.ConfigMap, err error) { + result = &api.ConfigMap{} + err = c.client.Put(). + Namespace(c.ns). + Resource("configmaps"). + Name(configMap.Name). + Body(configMap). + Do(). + Into(result) + return +} + +// Delete takes name of the configMap and deletes it. Returns an error if one occurs. +func (c *configMaps) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("configmaps"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *configMaps) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("configmaps"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the configMap, and returns the corresponding configMap object, and an error if there is any. +func (c *configMaps) Get(name string) (result *api.ConfigMap, err error) { + result = &api.ConfigMap{} + err = c.client.Get(). + Namespace(c.ns). + Resource("configmaps"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ConfigMaps that match those selectors. +func (c *configMaps) List(opts api.ListOptions) (result *api.ConfigMapList, err error) { + result = &api.ConfigMapList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("configmaps"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested configMaps. +func (c *configMaps) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("configmaps"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/core_client.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/core_client.go new file mode 100644 index 000000000..dc3561c0b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/core_client.go @@ -0,0 +1,165 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + registered "k8s.io/kubernetes/pkg/apimachinery/registered" + restclient "k8s.io/kubernetes/pkg/client/restclient" +) + +type CoreInterface interface { + ComponentStatusesGetter + ConfigMapsGetter + EndpointsGetter + EventsGetter + LimitRangesGetter + NamespacesGetter + NodesGetter + PersistentVolumesGetter + PersistentVolumeClaimsGetter + PodsGetter + PodTemplatesGetter + ReplicationControllersGetter + ResourceQuotasGetter + SecretsGetter + ServicesGetter + ServiceAccountsGetter +} + +// CoreClient is used to interact with features provided by the Core group. +type CoreClient struct { + *restclient.RESTClient +} + +func (c *CoreClient) ComponentStatuses() ComponentStatusInterface { + return newComponentStatuses(c) +} + +func (c *CoreClient) ConfigMaps(namespace string) ConfigMapInterface { + return newConfigMaps(c, namespace) +} + +func (c *CoreClient) Endpoints(namespace string) EndpointsInterface { + return newEndpoints(c, namespace) +} + +func (c *CoreClient) Events(namespace string) EventInterface { + return newEvents(c, namespace) +} + +func (c *CoreClient) LimitRanges(namespace string) LimitRangeInterface { + return newLimitRanges(c, namespace) +} + +func (c *CoreClient) Namespaces() NamespaceInterface { + return newNamespaces(c) +} + +func (c *CoreClient) Nodes() NodeInterface { + return newNodes(c) +} + +func (c *CoreClient) PersistentVolumes() PersistentVolumeInterface { + return newPersistentVolumes(c) +} + +func (c *CoreClient) PersistentVolumeClaims(namespace string) PersistentVolumeClaimInterface { + return newPersistentVolumeClaims(c, namespace) +} + +func (c *CoreClient) Pods(namespace string) PodInterface { + return newPods(c, namespace) +} + +func (c *CoreClient) PodTemplates(namespace string) PodTemplateInterface { + return newPodTemplates(c, namespace) +} + +func (c *CoreClient) ReplicationControllers(namespace string) ReplicationControllerInterface { + return newReplicationControllers(c, namespace) +} + +func (c *CoreClient) ResourceQuotas(namespace string) ResourceQuotaInterface { + return newResourceQuotas(c, namespace) +} + +func (c *CoreClient) Secrets(namespace string) SecretInterface { + return newSecrets(c, namespace) +} + +func (c *CoreClient) Services(namespace string) ServiceInterface { + return newServices(c, namespace) +} + +func (c *CoreClient) ServiceAccounts(namespace string) ServiceAccountInterface { + return newServiceAccounts(c, namespace) +} + +// NewForConfig creates a new CoreClient for the given config. +func NewForConfig(c *restclient.Config) (*CoreClient, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := restclient.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &CoreClient{client}, nil +} + +// NewForConfigOrDie creates a new CoreClient for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *restclient.Config) *CoreClient { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new CoreClient for the given RESTClient. +func New(c *restclient.RESTClient) *CoreClient { + return &CoreClient{c} +} + +func setConfigDefaults(config *restclient.Config) error { + // if core group is not registered, return an error + g, err := registered.Group("") + if err != nil { + return err + } + config.APIPath = "/api" + if config.UserAgent == "" { + config.UserAgent = restclient.DefaultKubernetesUserAgent() + } + // TODO: Unconditionally set the config.Version, until we fix the config. + //if config.Version == "" { + copyGroupVersion := g.GroupVersion + config.GroupVersion = ©GroupVersion + //} + + config.Codec = api.Codecs.LegacyCodec(*config.GroupVersion) + if config.QPS == 0 { + config.QPS = 5 + } + if config.Burst == 0 { + config.Burst = 10 + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/doc.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/doc.go new file mode 100644 index 000000000..47517b642 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This package is generated by client-gen with the default arguments. + +// This package has the automatically generated typed clients. +package unversioned diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/endpoints.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/endpoints.go new file mode 100644 index 000000000..78e2a0878 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/endpoints.go @@ -0,0 +1,135 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// EndpointsGetter has a method to return a EndpointsInterface. +// A group's client should implement this interface. +type EndpointsGetter interface { + Endpoints(namespace string) EndpointsInterface +} + +// EndpointsInterface has methods to work with Endpoints resources. +type EndpointsInterface interface { + Create(*api.Endpoints) (*api.Endpoints, error) + Update(*api.Endpoints) (*api.Endpoints, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.Endpoints, error) + List(opts api.ListOptions) (*api.EndpointsList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + EndpointsExpansion +} + +// endpoints implements EndpointsInterface +type endpoints struct { + client *CoreClient + ns string +} + +// newEndpoints returns a Endpoints +func newEndpoints(c *CoreClient, namespace string) *endpoints { + return &endpoints{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a endpoints and creates it. Returns the server's representation of the endpoints, and an error, if there is any. +func (c *endpoints) Create(endpoints *api.Endpoints) (result *api.Endpoints, err error) { + result = &api.Endpoints{} + err = c.client.Post(). + Namespace(c.ns). + Resource("endpoints"). + Body(endpoints). + Do(). + Into(result) + return +} + +// Update takes the representation of a endpoints and updates it. Returns the server's representation of the endpoints, and an error, if there is any. +func (c *endpoints) Update(endpoints *api.Endpoints) (result *api.Endpoints, err error) { + result = &api.Endpoints{} + err = c.client.Put(). + Namespace(c.ns). + Resource("endpoints"). + Name(endpoints.Name). + Body(endpoints). + Do(). + Into(result) + return +} + +// Delete takes name of the endpoints and deletes it. Returns an error if one occurs. +func (c *endpoints) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("endpoints"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *endpoints) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("endpoints"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the endpoints, and returns the corresponding endpoints object, and an error if there is any. +func (c *endpoints) Get(name string) (result *api.Endpoints, err error) { + result = &api.Endpoints{} + err = c.client.Get(). + Namespace(c.ns). + Resource("endpoints"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Endpoints that match those selectors. +func (c *endpoints) List(opts api.ListOptions) (result *api.EndpointsList, err error) { + result = &api.EndpointsList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("endpoints"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested endpoints. +func (c *endpoints) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("endpoints"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/event.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/event.go new file mode 100644 index 000000000..5627690a6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/event.go @@ -0,0 +1,135 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// EventsGetter has a method to return a EventInterface. +// A group's client should implement this interface. +type EventsGetter interface { + Events(namespace string) EventInterface +} + +// EventInterface has methods to work with Event resources. +type EventInterface interface { + Create(*api.Event) (*api.Event, error) + Update(*api.Event) (*api.Event, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.Event, error) + List(opts api.ListOptions) (*api.EventList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + EventExpansion +} + +// events implements EventInterface +type events struct { + client *CoreClient + ns string +} + +// newEvents returns a Events +func newEvents(c *CoreClient, namespace string) *events { + return &events{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a event and creates it. Returns the server's representation of the event, and an error, if there is any. +func (c *events) Create(event *api.Event) (result *api.Event, err error) { + result = &api.Event{} + err = c.client.Post(). + Namespace(c.ns). + Resource("events"). + Body(event). + Do(). + Into(result) + return +} + +// Update takes the representation of a event and updates it. Returns the server's representation of the event, and an error, if there is any. +func (c *events) Update(event *api.Event) (result *api.Event, err error) { + result = &api.Event{} + err = c.client.Put(). + Namespace(c.ns). + Resource("events"). + Name(event.Name). + Body(event). + Do(). + Into(result) + return +} + +// Delete takes name of the event and deletes it. Returns an error if one occurs. +func (c *events) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("events"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *events) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("events"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the event, and returns the corresponding event object, and an error if there is any. +func (c *events) Get(name string) (result *api.Event, err error) { + result = &api.Event{} + err = c.client.Get(). + Namespace(c.ns). + Resource("events"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Events that match those selectors. +func (c *events) List(opts api.ListOptions) (result *api.EventList, err error) { + result = &api.EventList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("events"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested events. +func (c *events) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("events"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/event_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/event_expansion.go new file mode 100644 index 000000000..abdf89aa1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/event_expansion.go @@ -0,0 +1,157 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/runtime" +) + +// The EventExpansion interface allows manually adding extra methods to the EventInterface. +type EventExpansion interface { + // CreateWithEventNamespace is the same as a Create, except that it sends the request to the event.Namespace. + CreateWithEventNamespace(event *api.Event) (*api.Event, error) + // UpdateWithEventNamespace is the same as a Update, except that it sends the request to the event.Namespace. + UpdateWithEventNamespace(event *api.Event) (*api.Event, error) + Patch(event *api.Event, data []byte) (*api.Event, error) + // Search finds events about the specified object + Search(objOrRef runtime.Object) (*api.EventList, error) + // Returns the appropriate field selector based on the API version being used to communicate with the server. + // The returned field selector can be used with List and Watch to filter desired events. + GetFieldSelector(involvedObjectName, involvedObjectNamespace, involvedObjectKind, involvedObjectUID *string) fields.Selector +} + +// CreateWithEventNamespace makes a new event. Returns the copy of the event the server returns, +// or an error. The namespace to create the event within is deduced from the +// event; it must either match this event client's namespace, or this event +// client must have been created with the "" namespace. +func (e *events) CreateWithEventNamespace(event *api.Event) (*api.Event, error) { + if e.ns != "" && event.Namespace != e.ns { + return nil, fmt.Errorf("can't create an event with namespace '%v' in namespace '%v'", event.Namespace, e.ns) + } + result := &api.Event{} + err := e.client.Post(). + NamespaceIfScoped(event.Namespace, len(event.Namespace) > 0). + Resource("events"). + Body(event). + Do(). + Into(result) + return result, err +} + +// UpdateWithEventNamespace modifies an existing event. It returns the copy of the event that the server returns, +// or an error. The namespace and key to update the event within is deduced from the event. The +// namespace must either match this event client's namespace, or this event client must have been +// created with the "" namespace. Update also requires the ResourceVersion to be set in the event +// object. +func (e *events) UpdateWithEventNamespace(event *api.Event) (*api.Event, error) { + result := &api.Event{} + err := e.client.Put(). + NamespaceIfScoped(event.Namespace, len(event.Namespace) > 0). + Resource("events"). + Name(event.Name). + Body(event). + Do(). + Into(result) + return result, err +} + +// Patch modifies an existing event. It returns the copy of the event that the server returns, or an +// error. The namespace and name of the target event is deduced from the incompleteEvent. The +// namespace must either match this event client's namespace, or this event client must have been +// created with the "" namespace. +func (e *events) Patch(incompleteEvent *api.Event, data []byte) (*api.Event, error) { + result := &api.Event{} + err := e.client.Patch(api.StrategicMergePatchType). + NamespaceIfScoped(incompleteEvent.Namespace, len(incompleteEvent.Namespace) > 0). + Resource("events"). + Name(incompleteEvent.Name). + Body(data). + Do(). + Into(result) + return result, err +} + +// Search finds events about the specified object. The namespace of the +// object must match this event's client namespace unless the event client +// was made with the "" namespace. +func (e *events) Search(objOrRef runtime.Object) (*api.EventList, error) { + ref, err := api.GetReference(objOrRef) + if err != nil { + return nil, err + } + if e.ns != "" && ref.Namespace != e.ns { + return nil, fmt.Errorf("won't be able to find any events of namespace '%v' in namespace '%v'", ref.Namespace, e.ns) + } + stringRefKind := string(ref.Kind) + var refKind *string + if stringRefKind != "" { + refKind = &stringRefKind + } + stringRefUID := string(ref.UID) + var refUID *string + if stringRefUID != "" { + refUID = &stringRefUID + } + fieldSelector := e.GetFieldSelector(&ref.Name, &ref.Namespace, refKind, refUID) + return e.List(api.ListOptions{FieldSelector: fieldSelector}) +} + +// Returns the appropriate field selector based on the API version being used to communicate with the server. +// The returned field selector can be used with List and Watch to filter desired events. +func (e *events) GetFieldSelector(involvedObjectName, involvedObjectNamespace, involvedObjectKind, involvedObjectUID *string) fields.Selector { + apiVersion := e.client.APIVersion().String() + field := fields.Set{} + if involvedObjectName != nil { + field[GetInvolvedObjectNameFieldLabel(apiVersion)] = *involvedObjectName + } + if involvedObjectNamespace != nil { + field["involvedObject.namespace"] = *involvedObjectNamespace + } + if involvedObjectKind != nil { + field["involvedObject.kind"] = *involvedObjectKind + } + if involvedObjectUID != nil { + field["involvedObject.uid"] = *involvedObjectUID + } + return field.AsSelector() +} + +// Returns the appropriate field label to use for name of the involved object as per the given API version. +func GetInvolvedObjectNameFieldLabel(version string) string { + return "involvedObject.name" +} + +// TODO: This is a temporary arrangement and will be removed once all clients are moved to use the clientset. +type EventSinkImpl struct { + Interface EventInterface +} + +func (e *EventSinkImpl) Create(event *api.Event) (*api.Event, error) { + return e.Interface.CreateWithEventNamespace(event) +} + +func (e *EventSinkImpl) Update(event *api.Event) (*api.Event, error) { + return e.Interface.UpdateWithEventNamespace(event) +} + +func (e *EventSinkImpl) Patch(event *api.Event, data []byte) (*api.Event, error) { + return e.Interface.Patch(event, data) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/doc.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/doc.go new file mode 100644 index 000000000..eb358c26c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This package is generated by client-gen with the default arguments. + +// Package fake has the automatically generated clients. +package fake diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_componentstatus.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_componentstatus.go new file mode 100644 index 000000000..478dd9dbf --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_componentstatus.go @@ -0,0 +1,95 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeComponentStatuses implements ComponentStatusInterface +type FakeComponentStatuses struct { + Fake *FakeCore +} + +func (c *FakeComponentStatuses) Create(componentStatus *api.ComponentStatus) (result *api.ComponentStatus, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction("componentstatuses", componentStatus), &api.ComponentStatus{}) + if obj == nil { + return nil, err + } + return obj.(*api.ComponentStatus), err +} + +func (c *FakeComponentStatuses) Update(componentStatus *api.ComponentStatus) (result *api.ComponentStatus, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction("componentstatuses", componentStatus), &api.ComponentStatus{}) + if obj == nil { + return nil, err + } + return obj.(*api.ComponentStatus), err +} + +func (c *FakeComponentStatuses) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction("componentstatuses", name), &api.ComponentStatus{}) + return err +} + +func (c *FakeComponentStatuses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction("componentstatuses", listOptions) + + _, err := c.Fake.Invokes(action, &api.ComponentStatusList{}) + return err +} + +func (c *FakeComponentStatuses) Get(name string) (result *api.ComponentStatus, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction("componentstatuses", name), &api.ComponentStatus{}) + if obj == nil { + return nil, err + } + return obj.(*api.ComponentStatus), err +} + +func (c *FakeComponentStatuses) List(opts api.ListOptions) (result *api.ComponentStatusList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction("componentstatuses", opts), &api.ComponentStatusList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.ComponentStatusList{} + for _, item := range obj.(*api.ComponentStatusList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested componentStatuses. +func (c *FakeComponentStatuses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction("componentstatuses", opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_configmap.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_configmap.go new file mode 100644 index 000000000..34fa0b229 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_configmap.go @@ -0,0 +1,102 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeConfigMaps implements ConfigMapInterface +type FakeConfigMaps struct { + Fake *FakeCore + ns string +} + +func (c *FakeConfigMaps) Create(configMap *api.ConfigMap) (result *api.ConfigMap, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("configmaps", c.ns, configMap), &api.ConfigMap{}) + + if obj == nil { + return nil, err + } + return obj.(*api.ConfigMap), err +} + +func (c *FakeConfigMaps) Update(configMap *api.ConfigMap) (result *api.ConfigMap, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("configmaps", c.ns, configMap), &api.ConfigMap{}) + + if obj == nil { + return nil, err + } + return obj.(*api.ConfigMap), err +} + +func (c *FakeConfigMaps) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("configmaps", c.ns, name), &api.ConfigMap{}) + + return err +} + +func (c *FakeConfigMaps) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("configmaps", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &api.ConfigMapList{}) + return err +} + +func (c *FakeConfigMaps) Get(name string) (result *api.ConfigMap, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("configmaps", c.ns, name), &api.ConfigMap{}) + + if obj == nil { + return nil, err + } + return obj.(*api.ConfigMap), err +} + +func (c *FakeConfigMaps) List(opts api.ListOptions) (result *api.ConfigMapList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("configmaps", c.ns, opts), &api.ConfigMapList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.ConfigMapList{} + for _, item := range obj.(*api.ConfigMapList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested configMaps. +func (c *FakeConfigMaps) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("configmaps", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_core_client.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_core_client.go new file mode 100644 index 000000000..632ec173e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_core_client.go @@ -0,0 +1,90 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + unversioned "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + core "k8s.io/kubernetes/pkg/client/testing/core" +) + +type FakeCore struct { + *core.Fake +} + +func (c *FakeCore) ComponentStatuses() unversioned.ComponentStatusInterface { + return &FakeComponentStatuses{c} +} + +func (c *FakeCore) ConfigMaps(namespace string) unversioned.ConfigMapInterface { + return &FakeConfigMaps{c, namespace} +} + +func (c *FakeCore) Endpoints(namespace string) unversioned.EndpointsInterface { + return &FakeEndpoints{c, namespace} +} + +func (c *FakeCore) Events(namespace string) unversioned.EventInterface { + return &FakeEvents{c, namespace} +} + +func (c *FakeCore) LimitRanges(namespace string) unversioned.LimitRangeInterface { + return &FakeLimitRanges{c, namespace} +} + +func (c *FakeCore) Namespaces() unversioned.NamespaceInterface { + return &FakeNamespaces{c} +} + +func (c *FakeCore) Nodes() unversioned.NodeInterface { + return &FakeNodes{c} +} + +func (c *FakeCore) PersistentVolumes() unversioned.PersistentVolumeInterface { + return &FakePersistentVolumes{c} +} + +func (c *FakeCore) PersistentVolumeClaims(namespace string) unversioned.PersistentVolumeClaimInterface { + return &FakePersistentVolumeClaims{c, namespace} +} + +func (c *FakeCore) Pods(namespace string) unversioned.PodInterface { + return &FakePods{c, namespace} +} + +func (c *FakeCore) PodTemplates(namespace string) unversioned.PodTemplateInterface { + return &FakePodTemplates{c, namespace} +} + +func (c *FakeCore) ReplicationControllers(namespace string) unversioned.ReplicationControllerInterface { + return &FakeReplicationControllers{c, namespace} +} + +func (c *FakeCore) ResourceQuotas(namespace string) unversioned.ResourceQuotaInterface { + return &FakeResourceQuotas{c, namespace} +} + +func (c *FakeCore) Secrets(namespace string) unversioned.SecretInterface { + return &FakeSecrets{c, namespace} +} + +func (c *FakeCore) Services(namespace string) unversioned.ServiceInterface { + return &FakeServices{c, namespace} +} + +func (c *FakeCore) ServiceAccounts(namespace string) unversioned.ServiceAccountInterface { + return &FakeServiceAccounts{c, namespace} +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_endpoints.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_endpoints.go new file mode 100644 index 000000000..cc25b6e06 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_endpoints.go @@ -0,0 +1,102 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeEndpoints implements EndpointsInterface +type FakeEndpoints struct { + Fake *FakeCore + ns string +} + +func (c *FakeEndpoints) Create(endpoints *api.Endpoints) (result *api.Endpoints, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("endpoints", c.ns, endpoints), &api.Endpoints{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Endpoints), err +} + +func (c *FakeEndpoints) Update(endpoints *api.Endpoints) (result *api.Endpoints, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("endpoints", c.ns, endpoints), &api.Endpoints{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Endpoints), err +} + +func (c *FakeEndpoints) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("endpoints", c.ns, name), &api.Endpoints{}) + + return err +} + +func (c *FakeEndpoints) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("endpoints", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &api.EndpointsList{}) + return err +} + +func (c *FakeEndpoints) Get(name string) (result *api.Endpoints, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("endpoints", c.ns, name), &api.Endpoints{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Endpoints), err +} + +func (c *FakeEndpoints) List(opts api.ListOptions) (result *api.EndpointsList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("endpoints", c.ns, opts), &api.EndpointsList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.EndpointsList{} + for _, item := range obj.(*api.EndpointsList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested endpoints. +func (c *FakeEndpoints) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("endpoints", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_event.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_event.go new file mode 100644 index 000000000..a9f88153b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_event.go @@ -0,0 +1,102 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeEvents implements EventInterface +type FakeEvents struct { + Fake *FakeCore + ns string +} + +func (c *FakeEvents) Create(event *api.Event) (result *api.Event, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("events", c.ns, event), &api.Event{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Event), err +} + +func (c *FakeEvents) Update(event *api.Event) (result *api.Event, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("events", c.ns, event), &api.Event{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Event), err +} + +func (c *FakeEvents) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("events", c.ns, name), &api.Event{}) + + return err +} + +func (c *FakeEvents) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("events", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &api.EventList{}) + return err +} + +func (c *FakeEvents) Get(name string) (result *api.Event, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("events", c.ns, name), &api.Event{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Event), err +} + +func (c *FakeEvents) List(opts api.ListOptions) (result *api.EventList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("events", c.ns, opts), &api.EventList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.EventList{} + for _, item := range obj.(*api.EventList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested events. +func (c *FakeEvents) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("events", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_event_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_event_expansion.go new file mode 100644 index 000000000..bc514d505 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_event_expansion.go @@ -0,0 +1,88 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/runtime" +) + +func (c *FakeEvents) CreateWithEventNamespace(event *api.Event) (*api.Event, error) { + action := core.NewRootCreateAction("events", event) + if c.ns != "" { + action = core.NewCreateAction("events", c.ns, event) + } + obj, err := c.Fake.Invokes(action, event) + if obj == nil { + return nil, err + } + + return obj.(*api.Event), err +} + +// Update replaces an existing event. Returns the copy of the event the server returns, or an error. +func (c *FakeEvents) UpdateWithEventNamespace(event *api.Event) (*api.Event, error) { + action := core.NewRootUpdateAction("events", event) + if c.ns != "" { + action = core.NewUpdateAction("events", c.ns, event) + } + obj, err := c.Fake.Invokes(action, event) + if obj == nil { + return nil, err + } + + return obj.(*api.Event), err +} + +// Patch patches an existing event. Returns the copy of the event the server returns, or an error. +func (c *FakeEvents) Patch(event *api.Event, data []byte) (*api.Event, error) { + action := core.NewRootPatchAction("events", event) + if c.ns != "" { + action = core.NewPatchAction("events", c.ns, event) + } + obj, err := c.Fake.Invokes(action, event) + if obj == nil { + return nil, err + } + + return obj.(*api.Event), err +} + +// Search returns a list of events matching the specified object. +func (c *FakeEvents) Search(objOrRef runtime.Object) (*api.EventList, error) { + action := core.NewRootListAction("events", api.ListOptions{}) + if c.ns != "" { + action = core.NewListAction("events", c.ns, api.ListOptions{}) + } + obj, err := c.Fake.Invokes(action, &api.EventList{}) + if obj == nil { + return nil, err + } + + return obj.(*api.EventList), err +} + +func (c *FakeEvents) GetFieldSelector(involvedObjectName, involvedObjectNamespace, involvedObjectKind, involvedObjectUID *string) fields.Selector { + action := core.GenericActionImpl{} + action.Verb = "get-field-selector" + action.Resource = "events" + + c.Fake.Invokes(action, nil) + return fields.Everything() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_limitrange.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_limitrange.go new file mode 100644 index 000000000..cab44ce4e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_limitrange.go @@ -0,0 +1,102 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeLimitRanges implements LimitRangeInterface +type FakeLimitRanges struct { + Fake *FakeCore + ns string +} + +func (c *FakeLimitRanges) Create(limitRange *api.LimitRange) (result *api.LimitRange, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("limitranges", c.ns, limitRange), &api.LimitRange{}) + + if obj == nil { + return nil, err + } + return obj.(*api.LimitRange), err +} + +func (c *FakeLimitRanges) Update(limitRange *api.LimitRange) (result *api.LimitRange, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("limitranges", c.ns, limitRange), &api.LimitRange{}) + + if obj == nil { + return nil, err + } + return obj.(*api.LimitRange), err +} + +func (c *FakeLimitRanges) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("limitranges", c.ns, name), &api.LimitRange{}) + + return err +} + +func (c *FakeLimitRanges) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("limitranges", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &api.LimitRangeList{}) + return err +} + +func (c *FakeLimitRanges) Get(name string) (result *api.LimitRange, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("limitranges", c.ns, name), &api.LimitRange{}) + + if obj == nil { + return nil, err + } + return obj.(*api.LimitRange), err +} + +func (c *FakeLimitRanges) List(opts api.ListOptions) (result *api.LimitRangeList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("limitranges", c.ns, opts), &api.LimitRangeList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.LimitRangeList{} + for _, item := range obj.(*api.LimitRangeList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested limitRanges. +func (c *FakeLimitRanges) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("limitranges", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_namespace.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_namespace.go new file mode 100644 index 000000000..78933814f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_namespace.go @@ -0,0 +1,104 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeNamespaces implements NamespaceInterface +type FakeNamespaces struct { + Fake *FakeCore +} + +func (c *FakeNamespaces) Create(namespace *api.Namespace) (result *api.Namespace, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction("namespaces", namespace), &api.Namespace{}) + if obj == nil { + return nil, err + } + return obj.(*api.Namespace), err +} + +func (c *FakeNamespaces) Update(namespace *api.Namespace) (result *api.Namespace, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction("namespaces", namespace), &api.Namespace{}) + if obj == nil { + return nil, err + } + return obj.(*api.Namespace), err +} + +func (c *FakeNamespaces) UpdateStatus(namespace *api.Namespace) (*api.Namespace, error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateSubresourceAction("namespaces", "status", namespace), &api.Namespace{}) + if obj == nil { + return nil, err + } + return obj.(*api.Namespace), err +} + +func (c *FakeNamespaces) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction("namespaces", name), &api.Namespace{}) + return err +} + +func (c *FakeNamespaces) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction("namespaces", listOptions) + + _, err := c.Fake.Invokes(action, &api.NamespaceList{}) + return err +} + +func (c *FakeNamespaces) Get(name string) (result *api.Namespace, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction("namespaces", name), &api.Namespace{}) + if obj == nil { + return nil, err + } + return obj.(*api.Namespace), err +} + +func (c *FakeNamespaces) List(opts api.ListOptions) (result *api.NamespaceList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction("namespaces", opts), &api.NamespaceList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.NamespaceList{} + for _, item := range obj.(*api.NamespaceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested namespaces. +func (c *FakeNamespaces) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction("namespaces", opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_namespace_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_namespace_expansion.go new file mode 100644 index 000000000..8bb49ff2b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_namespace_expansion.go @@ -0,0 +1,37 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeNamespaces) Finalize(namespace *api.Namespace) (*api.Namespace, error) { + action := core.CreateActionImpl{} + action.Verb = "create" + action.Resource = "namespaces" + action.Subresource = "finalize" + action.Object = namespace + + obj, err := c.Fake.Invokes(action, namespace) + if obj == nil { + return nil, err + } + + return obj.(*api.Namespace), err +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_node.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_node.go new file mode 100644 index 000000000..8761c8772 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_node.go @@ -0,0 +1,104 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeNodes implements NodeInterface +type FakeNodes struct { + Fake *FakeCore +} + +func (c *FakeNodes) Create(node *api.Node) (result *api.Node, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction("nodes", node), &api.Node{}) + if obj == nil { + return nil, err + } + return obj.(*api.Node), err +} + +func (c *FakeNodes) Update(node *api.Node) (result *api.Node, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction("nodes", node), &api.Node{}) + if obj == nil { + return nil, err + } + return obj.(*api.Node), err +} + +func (c *FakeNodes) UpdateStatus(node *api.Node) (*api.Node, error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateSubresourceAction("nodes", "status", node), &api.Node{}) + if obj == nil { + return nil, err + } + return obj.(*api.Node), err +} + +func (c *FakeNodes) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction("nodes", name), &api.Node{}) + return err +} + +func (c *FakeNodes) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction("nodes", listOptions) + + _, err := c.Fake.Invokes(action, &api.NodeList{}) + return err +} + +func (c *FakeNodes) Get(name string) (result *api.Node, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction("nodes", name), &api.Node{}) + if obj == nil { + return nil, err + } + return obj.(*api.Node), err +} + +func (c *FakeNodes) List(opts api.ListOptions) (result *api.NodeList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction("nodes", opts), &api.NodeList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.NodeList{} + for _, item := range obj.(*api.NodeList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested nodes. +func (c *FakeNodes) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction("nodes", opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_persistentvolume.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_persistentvolume.go new file mode 100644 index 000000000..d3d8c79f5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_persistentvolume.go @@ -0,0 +1,104 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakePersistentVolumes implements PersistentVolumeInterface +type FakePersistentVolumes struct { + Fake *FakeCore +} + +func (c *FakePersistentVolumes) Create(persistentVolume *api.PersistentVolume) (result *api.PersistentVolume, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction("persistentvolumes", persistentVolume), &api.PersistentVolume{}) + if obj == nil { + return nil, err + } + return obj.(*api.PersistentVolume), err +} + +func (c *FakePersistentVolumes) Update(persistentVolume *api.PersistentVolume) (result *api.PersistentVolume, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction("persistentvolumes", persistentVolume), &api.PersistentVolume{}) + if obj == nil { + return nil, err + } + return obj.(*api.PersistentVolume), err +} + +func (c *FakePersistentVolumes) UpdateStatus(persistentVolume *api.PersistentVolume) (*api.PersistentVolume, error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateSubresourceAction("persistentvolumes", "status", persistentVolume), &api.PersistentVolume{}) + if obj == nil { + return nil, err + } + return obj.(*api.PersistentVolume), err +} + +func (c *FakePersistentVolumes) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction("persistentvolumes", name), &api.PersistentVolume{}) + return err +} + +func (c *FakePersistentVolumes) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction("persistentvolumes", listOptions) + + _, err := c.Fake.Invokes(action, &api.PersistentVolumeList{}) + return err +} + +func (c *FakePersistentVolumes) Get(name string) (result *api.PersistentVolume, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction("persistentvolumes", name), &api.PersistentVolume{}) + if obj == nil { + return nil, err + } + return obj.(*api.PersistentVolume), err +} + +func (c *FakePersistentVolumes) List(opts api.ListOptions) (result *api.PersistentVolumeList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction("persistentvolumes", opts), &api.PersistentVolumeList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.PersistentVolumeList{} + for _, item := range obj.(*api.PersistentVolumeList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested persistentVolumes. +func (c *FakePersistentVolumes) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction("persistentvolumes", opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_persistentvolumeclaim.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_persistentvolumeclaim.go new file mode 100644 index 000000000..ba674f269 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_persistentvolumeclaim.go @@ -0,0 +1,112 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakePersistentVolumeClaims implements PersistentVolumeClaimInterface +type FakePersistentVolumeClaims struct { + Fake *FakeCore + ns string +} + +func (c *FakePersistentVolumeClaims) Create(persistentVolumeClaim *api.PersistentVolumeClaim) (result *api.PersistentVolumeClaim, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("persistentvolumeclaims", c.ns, persistentVolumeClaim), &api.PersistentVolumeClaim{}) + + if obj == nil { + return nil, err + } + return obj.(*api.PersistentVolumeClaim), err +} + +func (c *FakePersistentVolumeClaims) Update(persistentVolumeClaim *api.PersistentVolumeClaim) (result *api.PersistentVolumeClaim, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("persistentvolumeclaims", c.ns, persistentVolumeClaim), &api.PersistentVolumeClaim{}) + + if obj == nil { + return nil, err + } + return obj.(*api.PersistentVolumeClaim), err +} + +func (c *FakePersistentVolumeClaims) UpdateStatus(persistentVolumeClaim *api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("persistentvolumeclaims", "status", c.ns, persistentVolumeClaim), &api.PersistentVolumeClaim{}) + + if obj == nil { + return nil, err + } + return obj.(*api.PersistentVolumeClaim), err +} + +func (c *FakePersistentVolumeClaims) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("persistentvolumeclaims", c.ns, name), &api.PersistentVolumeClaim{}) + + return err +} + +func (c *FakePersistentVolumeClaims) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("persistentvolumeclaims", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &api.PersistentVolumeClaimList{}) + return err +} + +func (c *FakePersistentVolumeClaims) Get(name string) (result *api.PersistentVolumeClaim, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("persistentvolumeclaims", c.ns, name), &api.PersistentVolumeClaim{}) + + if obj == nil { + return nil, err + } + return obj.(*api.PersistentVolumeClaim), err +} + +func (c *FakePersistentVolumeClaims) List(opts api.ListOptions) (result *api.PersistentVolumeClaimList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("persistentvolumeclaims", c.ns, opts), &api.PersistentVolumeClaimList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.PersistentVolumeClaimList{} + for _, item := range obj.(*api.PersistentVolumeClaimList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested persistentVolumeClaims. +func (c *FakePersistentVolumeClaims) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("persistentvolumeclaims", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_pod.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_pod.go new file mode 100644 index 000000000..6488c021d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_pod.go @@ -0,0 +1,112 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakePods implements PodInterface +type FakePods struct { + Fake *FakeCore + ns string +} + +func (c *FakePods) Create(pod *api.Pod) (result *api.Pod, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("pods", c.ns, pod), &api.Pod{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Pod), err +} + +func (c *FakePods) Update(pod *api.Pod) (result *api.Pod, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("pods", c.ns, pod), &api.Pod{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Pod), err +} + +func (c *FakePods) UpdateStatus(pod *api.Pod) (*api.Pod, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("pods", "status", c.ns, pod), &api.Pod{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Pod), err +} + +func (c *FakePods) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("pods", c.ns, name), &api.Pod{}) + + return err +} + +func (c *FakePods) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("pods", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &api.PodList{}) + return err +} + +func (c *FakePods) Get(name string) (result *api.Pod, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("pods", c.ns, name), &api.Pod{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Pod), err +} + +func (c *FakePods) List(opts api.ListOptions) (result *api.PodList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("pods", c.ns, opts), &api.PodList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.PodList{} + for _, item := range obj.(*api.PodList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested pods. +func (c *FakePods) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("pods", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_pod_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_pod_expansion.go new file mode 100644 index 000000000..53fe93221 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_pod_expansion.go @@ -0,0 +1,46 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakePods) Bind(binding *api.Binding) error { + action := core.CreateActionImpl{} + action.Verb = "create" + action.Resource = "pods" + action.Subresource = "bindings" + action.Object = binding + + _, err := c.Fake.Invokes(action, binding) + return err +} + +func (c *FakePods) GetLogs(name string, opts *api.PodLogOptions) *restclient.Request { + action := core.GenericActionImpl{} + action.Verb = "get" + action.Namespace = c.ns + action.Resource = "pod" + action.Subresource = "logs" + action.Value = opts + + _, _ = c.Fake.Invokes(action, &api.Pod{}) + return &restclient.Request{} +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_podtemplate.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_podtemplate.go new file mode 100644 index 000000000..b900a113c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_podtemplate.go @@ -0,0 +1,102 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakePodTemplates implements PodTemplateInterface +type FakePodTemplates struct { + Fake *FakeCore + ns string +} + +func (c *FakePodTemplates) Create(podTemplate *api.PodTemplate) (result *api.PodTemplate, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("podtemplates", c.ns, podTemplate), &api.PodTemplate{}) + + if obj == nil { + return nil, err + } + return obj.(*api.PodTemplate), err +} + +func (c *FakePodTemplates) Update(podTemplate *api.PodTemplate) (result *api.PodTemplate, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("podtemplates", c.ns, podTemplate), &api.PodTemplate{}) + + if obj == nil { + return nil, err + } + return obj.(*api.PodTemplate), err +} + +func (c *FakePodTemplates) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("podtemplates", c.ns, name), &api.PodTemplate{}) + + return err +} + +func (c *FakePodTemplates) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("podtemplates", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &api.PodTemplateList{}) + return err +} + +func (c *FakePodTemplates) Get(name string) (result *api.PodTemplate, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("podtemplates", c.ns, name), &api.PodTemplate{}) + + if obj == nil { + return nil, err + } + return obj.(*api.PodTemplate), err +} + +func (c *FakePodTemplates) List(opts api.ListOptions) (result *api.PodTemplateList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("podtemplates", c.ns, opts), &api.PodTemplateList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.PodTemplateList{} + for _, item := range obj.(*api.PodTemplateList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested podTemplates. +func (c *FakePodTemplates) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("podtemplates", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_replicationcontroller.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_replicationcontroller.go new file mode 100644 index 000000000..205f09456 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_replicationcontroller.go @@ -0,0 +1,112 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeReplicationControllers implements ReplicationControllerInterface +type FakeReplicationControllers struct { + Fake *FakeCore + ns string +} + +func (c *FakeReplicationControllers) Create(replicationController *api.ReplicationController) (result *api.ReplicationController, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("replicationcontrollers", c.ns, replicationController), &api.ReplicationController{}) + + if obj == nil { + return nil, err + } + return obj.(*api.ReplicationController), err +} + +func (c *FakeReplicationControllers) Update(replicationController *api.ReplicationController) (result *api.ReplicationController, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("replicationcontrollers", c.ns, replicationController), &api.ReplicationController{}) + + if obj == nil { + return nil, err + } + return obj.(*api.ReplicationController), err +} + +func (c *FakeReplicationControllers) UpdateStatus(replicationController *api.ReplicationController) (*api.ReplicationController, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("replicationcontrollers", "status", c.ns, replicationController), &api.ReplicationController{}) + + if obj == nil { + return nil, err + } + return obj.(*api.ReplicationController), err +} + +func (c *FakeReplicationControllers) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("replicationcontrollers", c.ns, name), &api.ReplicationController{}) + + return err +} + +func (c *FakeReplicationControllers) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("replicationcontrollers", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &api.ReplicationControllerList{}) + return err +} + +func (c *FakeReplicationControllers) Get(name string) (result *api.ReplicationController, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("replicationcontrollers", c.ns, name), &api.ReplicationController{}) + + if obj == nil { + return nil, err + } + return obj.(*api.ReplicationController), err +} + +func (c *FakeReplicationControllers) List(opts api.ListOptions) (result *api.ReplicationControllerList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("replicationcontrollers", c.ns, opts), &api.ReplicationControllerList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.ReplicationControllerList{} + for _, item := range obj.(*api.ReplicationControllerList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested replicationControllers. +func (c *FakeReplicationControllers) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("replicationcontrollers", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_resourcequota.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_resourcequota.go new file mode 100644 index 000000000..056e61ed5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_resourcequota.go @@ -0,0 +1,112 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeResourceQuotas implements ResourceQuotaInterface +type FakeResourceQuotas struct { + Fake *FakeCore + ns string +} + +func (c *FakeResourceQuotas) Create(resourceQuota *api.ResourceQuota) (result *api.ResourceQuota, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("resourcequotas", c.ns, resourceQuota), &api.ResourceQuota{}) + + if obj == nil { + return nil, err + } + return obj.(*api.ResourceQuota), err +} + +func (c *FakeResourceQuotas) Update(resourceQuota *api.ResourceQuota) (result *api.ResourceQuota, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("resourcequotas", c.ns, resourceQuota), &api.ResourceQuota{}) + + if obj == nil { + return nil, err + } + return obj.(*api.ResourceQuota), err +} + +func (c *FakeResourceQuotas) UpdateStatus(resourceQuota *api.ResourceQuota) (*api.ResourceQuota, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("resourcequotas", "status", c.ns, resourceQuota), &api.ResourceQuota{}) + + if obj == nil { + return nil, err + } + return obj.(*api.ResourceQuota), err +} + +func (c *FakeResourceQuotas) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("resourcequotas", c.ns, name), &api.ResourceQuota{}) + + return err +} + +func (c *FakeResourceQuotas) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("resourcequotas", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &api.ResourceQuotaList{}) + return err +} + +func (c *FakeResourceQuotas) Get(name string) (result *api.ResourceQuota, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("resourcequotas", c.ns, name), &api.ResourceQuota{}) + + if obj == nil { + return nil, err + } + return obj.(*api.ResourceQuota), err +} + +func (c *FakeResourceQuotas) List(opts api.ListOptions) (result *api.ResourceQuotaList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("resourcequotas", c.ns, opts), &api.ResourceQuotaList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.ResourceQuotaList{} + for _, item := range obj.(*api.ResourceQuotaList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested resourceQuotas. +func (c *FakeResourceQuotas) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("resourcequotas", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_secret.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_secret.go new file mode 100644 index 000000000..2f09be6e5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_secret.go @@ -0,0 +1,102 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeSecrets implements SecretInterface +type FakeSecrets struct { + Fake *FakeCore + ns string +} + +func (c *FakeSecrets) Create(secret *api.Secret) (result *api.Secret, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("secrets", c.ns, secret), &api.Secret{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Secret), err +} + +func (c *FakeSecrets) Update(secret *api.Secret) (result *api.Secret, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("secrets", c.ns, secret), &api.Secret{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Secret), err +} + +func (c *FakeSecrets) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("secrets", c.ns, name), &api.Secret{}) + + return err +} + +func (c *FakeSecrets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("secrets", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &api.SecretList{}) + return err +} + +func (c *FakeSecrets) Get(name string) (result *api.Secret, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("secrets", c.ns, name), &api.Secret{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Secret), err +} + +func (c *FakeSecrets) List(opts api.ListOptions) (result *api.SecretList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("secrets", c.ns, opts), &api.SecretList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.SecretList{} + for _, item := range obj.(*api.SecretList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested secrets. +func (c *FakeSecrets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("secrets", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_service.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_service.go new file mode 100644 index 000000000..2cf38901c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_service.go @@ -0,0 +1,112 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeServices implements ServiceInterface +type FakeServices struct { + Fake *FakeCore + ns string +} + +func (c *FakeServices) Create(service *api.Service) (result *api.Service, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("services", c.ns, service), &api.Service{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Service), err +} + +func (c *FakeServices) Update(service *api.Service) (result *api.Service, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("services", c.ns, service), &api.Service{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Service), err +} + +func (c *FakeServices) UpdateStatus(service *api.Service) (*api.Service, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("services", "status", c.ns, service), &api.Service{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Service), err +} + +func (c *FakeServices) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("services", c.ns, name), &api.Service{}) + + return err +} + +func (c *FakeServices) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("services", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &api.ServiceList{}) + return err +} + +func (c *FakeServices) Get(name string) (result *api.Service, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("services", c.ns, name), &api.Service{}) + + if obj == nil { + return nil, err + } + return obj.(*api.Service), err +} + +func (c *FakeServices) List(opts api.ListOptions) (result *api.ServiceList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("services", c.ns, opts), &api.ServiceList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.ServiceList{} + for _, item := range obj.(*api.ServiceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested services. +func (c *FakeServices) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("services", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_service_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_service_expansion.go new file mode 100644 index 000000000..18f1b7803 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_service_expansion.go @@ -0,0 +1,26 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeServices) ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper { + return c.Fake.InvokesProxy(core.NewProxyGetAction("services", c.ns, scheme, name, port, path, params)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_serviceaccount.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_serviceaccount.go new file mode 100644 index 000000000..61d7a04f5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/fake/fake_serviceaccount.go @@ -0,0 +1,102 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeServiceAccounts implements ServiceAccountInterface +type FakeServiceAccounts struct { + Fake *FakeCore + ns string +} + +func (c *FakeServiceAccounts) Create(serviceAccount *api.ServiceAccount) (result *api.ServiceAccount, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("serviceaccounts", c.ns, serviceAccount), &api.ServiceAccount{}) + + if obj == nil { + return nil, err + } + return obj.(*api.ServiceAccount), err +} + +func (c *FakeServiceAccounts) Update(serviceAccount *api.ServiceAccount) (result *api.ServiceAccount, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("serviceaccounts", c.ns, serviceAccount), &api.ServiceAccount{}) + + if obj == nil { + return nil, err + } + return obj.(*api.ServiceAccount), err +} + +func (c *FakeServiceAccounts) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("serviceaccounts", c.ns, name), &api.ServiceAccount{}) + + return err +} + +func (c *FakeServiceAccounts) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("serviceaccounts", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &api.ServiceAccountList{}) + return err +} + +func (c *FakeServiceAccounts) Get(name string) (result *api.ServiceAccount, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("serviceaccounts", c.ns, name), &api.ServiceAccount{}) + + if obj == nil { + return nil, err + } + return obj.(*api.ServiceAccount), err +} + +func (c *FakeServiceAccounts) List(opts api.ListOptions) (result *api.ServiceAccountList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("serviceaccounts", c.ns, opts), &api.ServiceAccountList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &api.ServiceAccountList{} + for _, item := range obj.(*api.ServiceAccountList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested serviceAccounts. +func (c *FakeServiceAccounts) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("serviceaccounts", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/generated_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/generated_expansion.go new file mode 100644 index 000000000..eeb51962e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/generated_expansion.go @@ -0,0 +1,41 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +type ComponentStatusExpansion interface{} + +type EndpointsExpansion interface{} + +type LimitRangeExpansion interface{} + +type NodeExpansion interface{} + +type PersistentVolumeExpansion interface{} + +type PersistentVolumeClaimExpansion interface{} + +type PodTemplateExpansion interface{} + +type ReplicationControllerExpansion interface{} + +type ResourceQuotaExpansion interface{} + +type SecretExpansion interface{} + +type ServiceAccountExpansion interface{} + +type ConfigMapExpansion interface{} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/limitrange.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/limitrange.go new file mode 100644 index 000000000..86cc9b07f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/limitrange.go @@ -0,0 +1,135 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// LimitRangesGetter has a method to return a LimitRangeInterface. +// A group's client should implement this interface. +type LimitRangesGetter interface { + LimitRanges(namespace string) LimitRangeInterface +} + +// LimitRangeInterface has methods to work with LimitRange resources. +type LimitRangeInterface interface { + Create(*api.LimitRange) (*api.LimitRange, error) + Update(*api.LimitRange) (*api.LimitRange, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.LimitRange, error) + List(opts api.ListOptions) (*api.LimitRangeList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + LimitRangeExpansion +} + +// limitRanges implements LimitRangeInterface +type limitRanges struct { + client *CoreClient + ns string +} + +// newLimitRanges returns a LimitRanges +func newLimitRanges(c *CoreClient, namespace string) *limitRanges { + return &limitRanges{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a limitRange and creates it. Returns the server's representation of the limitRange, and an error, if there is any. +func (c *limitRanges) Create(limitRange *api.LimitRange) (result *api.LimitRange, err error) { + result = &api.LimitRange{} + err = c.client.Post(). + Namespace(c.ns). + Resource("limitranges"). + Body(limitRange). + Do(). + Into(result) + return +} + +// Update takes the representation of a limitRange and updates it. Returns the server's representation of the limitRange, and an error, if there is any. +func (c *limitRanges) Update(limitRange *api.LimitRange) (result *api.LimitRange, err error) { + result = &api.LimitRange{} + err = c.client.Put(). + Namespace(c.ns). + Resource("limitranges"). + Name(limitRange.Name). + Body(limitRange). + Do(). + Into(result) + return +} + +// Delete takes name of the limitRange and deletes it. Returns an error if one occurs. +func (c *limitRanges) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("limitranges"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *limitRanges) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("limitranges"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the limitRange, and returns the corresponding limitRange object, and an error if there is any. +func (c *limitRanges) Get(name string) (result *api.LimitRange, err error) { + result = &api.LimitRange{} + err = c.client.Get(). + Namespace(c.ns). + Resource("limitranges"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of LimitRanges that match those selectors. +func (c *limitRanges) List(opts api.ListOptions) (result *api.LimitRangeList, err error) { + result = &api.LimitRangeList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("limitranges"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested limitRanges. +func (c *limitRanges) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("limitranges"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/namespace.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/namespace.go new file mode 100644 index 000000000..c1c8b4506 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/namespace.go @@ -0,0 +1,139 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// NamespacesGetter has a method to return a NamespaceInterface. +// A group's client should implement this interface. +type NamespacesGetter interface { + Namespaces() NamespaceInterface +} + +// NamespaceInterface has methods to work with Namespace resources. +type NamespaceInterface interface { + Create(*api.Namespace) (*api.Namespace, error) + Update(*api.Namespace) (*api.Namespace, error) + UpdateStatus(*api.Namespace) (*api.Namespace, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.Namespace, error) + List(opts api.ListOptions) (*api.NamespaceList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + NamespaceExpansion +} + +// namespaces implements NamespaceInterface +type namespaces struct { + client *CoreClient +} + +// newNamespaces returns a Namespaces +func newNamespaces(c *CoreClient) *namespaces { + return &namespaces{ + client: c, + } +} + +// Create takes the representation of a namespace and creates it. Returns the server's representation of the namespace, and an error, if there is any. +func (c *namespaces) Create(namespace *api.Namespace) (result *api.Namespace, err error) { + result = &api.Namespace{} + err = c.client.Post(). + Resource("namespaces"). + Body(namespace). + Do(). + Into(result) + return +} + +// Update takes the representation of a namespace and updates it. Returns the server's representation of the namespace, and an error, if there is any. +func (c *namespaces) Update(namespace *api.Namespace) (result *api.Namespace, err error) { + result = &api.Namespace{} + err = c.client.Put(). + Resource("namespaces"). + Name(namespace.Name). + Body(namespace). + Do(). + Into(result) + return +} + +func (c *namespaces) UpdateStatus(namespace *api.Namespace) (result *api.Namespace, err error) { + result = &api.Namespace{} + err = c.client.Put(). + Resource("namespaces"). + Name(namespace.Name). + SubResource("status"). + Body(namespace). + Do(). + Into(result) + return +} + +// Delete takes name of the namespace and deletes it. Returns an error if one occurs. +func (c *namespaces) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("namespaces"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *namespaces) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("namespaces"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the namespace, and returns the corresponding namespace object, and an error if there is any. +func (c *namespaces) Get(name string) (result *api.Namespace, err error) { + result = &api.Namespace{} + err = c.client.Get(). + Resource("namespaces"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Namespaces that match those selectors. +func (c *namespaces) List(opts api.ListOptions) (result *api.NamespaceList, err error) { + result = &api.NamespaceList{} + err = c.client.Get(). + Resource("namespaces"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested namespaces. +func (c *namespaces) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("namespaces"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/namespace_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/namespace_expansion.go new file mode 100644 index 000000000..8f47aec48 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/namespace_expansion.go @@ -0,0 +1,31 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import "k8s.io/kubernetes/pkg/api" + +// The NamespaceExpansion interface allows manually adding extra methods to the NamespaceInterface. +type NamespaceExpansion interface { + Finalize(item *api.Namespace) (*api.Namespace, error) +} + +// Finalize takes the representation of a namespace to update. Returns the server's representation of the namespace, and an error, if it occurs. +func (c *namespaces) Finalize(namespace *api.Namespace) (result *api.Namespace, err error) { + result = &api.Namespace{} + err = c.client.Put().Resource("namespaces").Name(namespace.Name).SubResource("finalize").Body(namespace).Do().Into(result) + return +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/node.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/node.go new file mode 100644 index 000000000..b0c53ef1d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/node.go @@ -0,0 +1,139 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// NodesGetter has a method to return a NodeInterface. +// A group's client should implement this interface. +type NodesGetter interface { + Nodes() NodeInterface +} + +// NodeInterface has methods to work with Node resources. +type NodeInterface interface { + Create(*api.Node) (*api.Node, error) + Update(*api.Node) (*api.Node, error) + UpdateStatus(*api.Node) (*api.Node, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.Node, error) + List(opts api.ListOptions) (*api.NodeList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + NodeExpansion +} + +// nodes implements NodeInterface +type nodes struct { + client *CoreClient +} + +// newNodes returns a Nodes +func newNodes(c *CoreClient) *nodes { + return &nodes{ + client: c, + } +} + +// Create takes the representation of a node and creates it. Returns the server's representation of the node, and an error, if there is any. +func (c *nodes) Create(node *api.Node) (result *api.Node, err error) { + result = &api.Node{} + err = c.client.Post(). + Resource("nodes"). + Body(node). + Do(). + Into(result) + return +} + +// Update takes the representation of a node and updates it. Returns the server's representation of the node, and an error, if there is any. +func (c *nodes) Update(node *api.Node) (result *api.Node, err error) { + result = &api.Node{} + err = c.client.Put(). + Resource("nodes"). + Name(node.Name). + Body(node). + Do(). + Into(result) + return +} + +func (c *nodes) UpdateStatus(node *api.Node) (result *api.Node, err error) { + result = &api.Node{} + err = c.client.Put(). + Resource("nodes"). + Name(node.Name). + SubResource("status"). + Body(node). + Do(). + Into(result) + return +} + +// Delete takes name of the node and deletes it. Returns an error if one occurs. +func (c *nodes) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("nodes"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *nodes) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("nodes"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the node, and returns the corresponding node object, and an error if there is any. +func (c *nodes) Get(name string) (result *api.Node, err error) { + result = &api.Node{} + err = c.client.Get(). + Resource("nodes"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Nodes that match those selectors. +func (c *nodes) List(opts api.ListOptions) (result *api.NodeList, err error) { + result = &api.NodeList{} + err = c.client.Get(). + Resource("nodes"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested nodes. +func (c *nodes) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("nodes"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/persistentvolume.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/persistentvolume.go new file mode 100644 index 000000000..6b4d0f017 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/persistentvolume.go @@ -0,0 +1,139 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// PersistentVolumesGetter has a method to return a PersistentVolumeInterface. +// A group's client should implement this interface. +type PersistentVolumesGetter interface { + PersistentVolumes() PersistentVolumeInterface +} + +// PersistentVolumeInterface has methods to work with PersistentVolume resources. +type PersistentVolumeInterface interface { + Create(*api.PersistentVolume) (*api.PersistentVolume, error) + Update(*api.PersistentVolume) (*api.PersistentVolume, error) + UpdateStatus(*api.PersistentVolume) (*api.PersistentVolume, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.PersistentVolume, error) + List(opts api.ListOptions) (*api.PersistentVolumeList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + PersistentVolumeExpansion +} + +// persistentVolumes implements PersistentVolumeInterface +type persistentVolumes struct { + client *CoreClient +} + +// newPersistentVolumes returns a PersistentVolumes +func newPersistentVolumes(c *CoreClient) *persistentVolumes { + return &persistentVolumes{ + client: c, + } +} + +// Create takes the representation of a persistentVolume and creates it. Returns the server's representation of the persistentVolume, and an error, if there is any. +func (c *persistentVolumes) Create(persistentVolume *api.PersistentVolume) (result *api.PersistentVolume, err error) { + result = &api.PersistentVolume{} + err = c.client.Post(). + Resource("persistentvolumes"). + Body(persistentVolume). + Do(). + Into(result) + return +} + +// Update takes the representation of a persistentVolume and updates it. Returns the server's representation of the persistentVolume, and an error, if there is any. +func (c *persistentVolumes) Update(persistentVolume *api.PersistentVolume) (result *api.PersistentVolume, err error) { + result = &api.PersistentVolume{} + err = c.client.Put(). + Resource("persistentvolumes"). + Name(persistentVolume.Name). + Body(persistentVolume). + Do(). + Into(result) + return +} + +func (c *persistentVolumes) UpdateStatus(persistentVolume *api.PersistentVolume) (result *api.PersistentVolume, err error) { + result = &api.PersistentVolume{} + err = c.client.Put(). + Resource("persistentvolumes"). + Name(persistentVolume.Name). + SubResource("status"). + Body(persistentVolume). + Do(). + Into(result) + return +} + +// Delete takes name of the persistentVolume and deletes it. Returns an error if one occurs. +func (c *persistentVolumes) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("persistentvolumes"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *persistentVolumes) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("persistentvolumes"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the persistentVolume, and returns the corresponding persistentVolume object, and an error if there is any. +func (c *persistentVolumes) Get(name string) (result *api.PersistentVolume, err error) { + result = &api.PersistentVolume{} + err = c.client.Get(). + Resource("persistentvolumes"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of PersistentVolumes that match those selectors. +func (c *persistentVolumes) List(opts api.ListOptions) (result *api.PersistentVolumeList, err error) { + result = &api.PersistentVolumeList{} + err = c.client.Get(). + Resource("persistentvolumes"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested persistentVolumes. +func (c *persistentVolumes) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("persistentvolumes"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/persistentvolumeclaim.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/persistentvolumeclaim.go new file mode 100644 index 000000000..2f5b17437 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/persistentvolumeclaim.go @@ -0,0 +1,149 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// PersistentVolumeClaimsGetter has a method to return a PersistentVolumeClaimInterface. +// A group's client should implement this interface. +type PersistentVolumeClaimsGetter interface { + PersistentVolumeClaims(namespace string) PersistentVolumeClaimInterface +} + +// PersistentVolumeClaimInterface has methods to work with PersistentVolumeClaim resources. +type PersistentVolumeClaimInterface interface { + Create(*api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) + Update(*api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) + UpdateStatus(*api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.PersistentVolumeClaim, error) + List(opts api.ListOptions) (*api.PersistentVolumeClaimList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + PersistentVolumeClaimExpansion +} + +// persistentVolumeClaims implements PersistentVolumeClaimInterface +type persistentVolumeClaims struct { + client *CoreClient + ns string +} + +// newPersistentVolumeClaims returns a PersistentVolumeClaims +func newPersistentVolumeClaims(c *CoreClient, namespace string) *persistentVolumeClaims { + return &persistentVolumeClaims{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a persistentVolumeClaim and creates it. Returns the server's representation of the persistentVolumeClaim, and an error, if there is any. +func (c *persistentVolumeClaims) Create(persistentVolumeClaim *api.PersistentVolumeClaim) (result *api.PersistentVolumeClaim, err error) { + result = &api.PersistentVolumeClaim{} + err = c.client.Post(). + Namespace(c.ns). + Resource("persistentvolumeclaims"). + Body(persistentVolumeClaim). + Do(). + Into(result) + return +} + +// Update takes the representation of a persistentVolumeClaim and updates it. Returns the server's representation of the persistentVolumeClaim, and an error, if there is any. +func (c *persistentVolumeClaims) Update(persistentVolumeClaim *api.PersistentVolumeClaim) (result *api.PersistentVolumeClaim, err error) { + result = &api.PersistentVolumeClaim{} + err = c.client.Put(). + Namespace(c.ns). + Resource("persistentvolumeclaims"). + Name(persistentVolumeClaim.Name). + Body(persistentVolumeClaim). + Do(). + Into(result) + return +} + +func (c *persistentVolumeClaims) UpdateStatus(persistentVolumeClaim *api.PersistentVolumeClaim) (result *api.PersistentVolumeClaim, err error) { + result = &api.PersistentVolumeClaim{} + err = c.client.Put(). + Namespace(c.ns). + Resource("persistentvolumeclaims"). + Name(persistentVolumeClaim.Name). + SubResource("status"). + Body(persistentVolumeClaim). + Do(). + Into(result) + return +} + +// Delete takes name of the persistentVolumeClaim and deletes it. Returns an error if one occurs. +func (c *persistentVolumeClaims) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("persistentvolumeclaims"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *persistentVolumeClaims) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("persistentvolumeclaims"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the persistentVolumeClaim, and returns the corresponding persistentVolumeClaim object, and an error if there is any. +func (c *persistentVolumeClaims) Get(name string) (result *api.PersistentVolumeClaim, err error) { + result = &api.PersistentVolumeClaim{} + err = c.client.Get(). + Namespace(c.ns). + Resource("persistentvolumeclaims"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of PersistentVolumeClaims that match those selectors. +func (c *persistentVolumeClaims) List(opts api.ListOptions) (result *api.PersistentVolumeClaimList, err error) { + result = &api.PersistentVolumeClaimList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("persistentvolumeclaims"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested persistentVolumeClaims. +func (c *persistentVolumeClaims) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("persistentvolumeclaims"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/pod.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/pod.go new file mode 100644 index 000000000..1cdfc8e71 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/pod.go @@ -0,0 +1,149 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// PodsGetter has a method to return a PodInterface. +// A group's client should implement this interface. +type PodsGetter interface { + Pods(namespace string) PodInterface +} + +// PodInterface has methods to work with Pod resources. +type PodInterface interface { + Create(*api.Pod) (*api.Pod, error) + Update(*api.Pod) (*api.Pod, error) + UpdateStatus(*api.Pod) (*api.Pod, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.Pod, error) + List(opts api.ListOptions) (*api.PodList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + PodExpansion +} + +// pods implements PodInterface +type pods struct { + client *CoreClient + ns string +} + +// newPods returns a Pods +func newPods(c *CoreClient, namespace string) *pods { + return &pods{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a pod and creates it. Returns the server's representation of the pod, and an error, if there is any. +func (c *pods) Create(pod *api.Pod) (result *api.Pod, err error) { + result = &api.Pod{} + err = c.client.Post(). + Namespace(c.ns). + Resource("pods"). + Body(pod). + Do(). + Into(result) + return +} + +// Update takes the representation of a pod and updates it. Returns the server's representation of the pod, and an error, if there is any. +func (c *pods) Update(pod *api.Pod) (result *api.Pod, err error) { + result = &api.Pod{} + err = c.client.Put(). + Namespace(c.ns). + Resource("pods"). + Name(pod.Name). + Body(pod). + Do(). + Into(result) + return +} + +func (c *pods) UpdateStatus(pod *api.Pod) (result *api.Pod, err error) { + result = &api.Pod{} + err = c.client.Put(). + Namespace(c.ns). + Resource("pods"). + Name(pod.Name). + SubResource("status"). + Body(pod). + Do(). + Into(result) + return +} + +// Delete takes name of the pod and deletes it. Returns an error if one occurs. +func (c *pods) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("pods"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *pods) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("pods"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the pod, and returns the corresponding pod object, and an error if there is any. +func (c *pods) Get(name string) (result *api.Pod, err error) { + result = &api.Pod{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pods"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Pods that match those selectors. +func (c *pods) List(opts api.ListOptions) (result *api.PodList, err error) { + result = &api.PodList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pods"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested pods. +func (c *pods) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("pods"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/pod_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/pod_expansion.go new file mode 100644 index 000000000..8ebd29d30 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/pod_expansion.go @@ -0,0 +1,38 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/restclient" +) + +// The PodExpansion interface allows manually adding extra methods to the PodInterface. +type PodExpansion interface { + Bind(binding *api.Binding) error + GetLogs(name string, opts *api.PodLogOptions) *restclient.Request +} + +// Bind applies the provided binding to the named pod in the current namespace (binding.Namespace is ignored). +func (c *pods) Bind(binding *api.Binding) error { + return c.client.Post().Namespace(c.ns).Resource("pods").Name(binding.Name).SubResource("binding").Body(binding).Do().Error() +} + +// Get constructs a request for getting the logs for a pod +func (c *pods) GetLogs(name string, opts *api.PodLogOptions) *restclient.Request { + return c.client.Get().Namespace(c.ns).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, api.ParameterCodec) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/podtemplate.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/podtemplate.go new file mode 100644 index 000000000..cccef29f7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/podtemplate.go @@ -0,0 +1,135 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// PodTemplatesGetter has a method to return a PodTemplateInterface. +// A group's client should implement this interface. +type PodTemplatesGetter interface { + PodTemplates(namespace string) PodTemplateInterface +} + +// PodTemplateInterface has methods to work with PodTemplate resources. +type PodTemplateInterface interface { + Create(*api.PodTemplate) (*api.PodTemplate, error) + Update(*api.PodTemplate) (*api.PodTemplate, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.PodTemplate, error) + List(opts api.ListOptions) (*api.PodTemplateList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + PodTemplateExpansion +} + +// podTemplates implements PodTemplateInterface +type podTemplates struct { + client *CoreClient + ns string +} + +// newPodTemplates returns a PodTemplates +func newPodTemplates(c *CoreClient, namespace string) *podTemplates { + return &podTemplates{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a podTemplate and creates it. Returns the server's representation of the podTemplate, and an error, if there is any. +func (c *podTemplates) Create(podTemplate *api.PodTemplate) (result *api.PodTemplate, err error) { + result = &api.PodTemplate{} + err = c.client.Post(). + Namespace(c.ns). + Resource("podtemplates"). + Body(podTemplate). + Do(). + Into(result) + return +} + +// Update takes the representation of a podTemplate and updates it. Returns the server's representation of the podTemplate, and an error, if there is any. +func (c *podTemplates) Update(podTemplate *api.PodTemplate) (result *api.PodTemplate, err error) { + result = &api.PodTemplate{} + err = c.client.Put(). + Namespace(c.ns). + Resource("podtemplates"). + Name(podTemplate.Name). + Body(podTemplate). + Do(). + Into(result) + return +} + +// Delete takes name of the podTemplate and deletes it. Returns an error if one occurs. +func (c *podTemplates) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("podtemplates"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *podTemplates) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("podtemplates"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the podTemplate, and returns the corresponding podTemplate object, and an error if there is any. +func (c *podTemplates) Get(name string) (result *api.PodTemplate, err error) { + result = &api.PodTemplate{} + err = c.client.Get(). + Namespace(c.ns). + Resource("podtemplates"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of PodTemplates that match those selectors. +func (c *podTemplates) List(opts api.ListOptions) (result *api.PodTemplateList, err error) { + result = &api.PodTemplateList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("podtemplates"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested podTemplates. +func (c *podTemplates) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("podtemplates"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/replicationcontroller.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/replicationcontroller.go new file mode 100644 index 000000000..6f9f06625 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/replicationcontroller.go @@ -0,0 +1,149 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ReplicationControllersGetter has a method to return a ReplicationControllerInterface. +// A group's client should implement this interface. +type ReplicationControllersGetter interface { + ReplicationControllers(namespace string) ReplicationControllerInterface +} + +// ReplicationControllerInterface has methods to work with ReplicationController resources. +type ReplicationControllerInterface interface { + Create(*api.ReplicationController) (*api.ReplicationController, error) + Update(*api.ReplicationController) (*api.ReplicationController, error) + UpdateStatus(*api.ReplicationController) (*api.ReplicationController, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.ReplicationController, error) + List(opts api.ListOptions) (*api.ReplicationControllerList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ReplicationControllerExpansion +} + +// replicationControllers implements ReplicationControllerInterface +type replicationControllers struct { + client *CoreClient + ns string +} + +// newReplicationControllers returns a ReplicationControllers +func newReplicationControllers(c *CoreClient, namespace string) *replicationControllers { + return &replicationControllers{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a replicationController and creates it. Returns the server's representation of the replicationController, and an error, if there is any. +func (c *replicationControllers) Create(replicationController *api.ReplicationController) (result *api.ReplicationController, err error) { + result = &api.ReplicationController{} + err = c.client.Post(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Body(replicationController). + Do(). + Into(result) + return +} + +// Update takes the representation of a replicationController and updates it. Returns the server's representation of the replicationController, and an error, if there is any. +func (c *replicationControllers) Update(replicationController *api.ReplicationController) (result *api.ReplicationController, err error) { + result = &api.ReplicationController{} + err = c.client.Put(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Name(replicationController.Name). + Body(replicationController). + Do(). + Into(result) + return +} + +func (c *replicationControllers) UpdateStatus(replicationController *api.ReplicationController) (result *api.ReplicationController, err error) { + result = &api.ReplicationController{} + err = c.client.Put(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Name(replicationController.Name). + SubResource("status"). + Body(replicationController). + Do(). + Into(result) + return +} + +// Delete takes name of the replicationController and deletes it. Returns an error if one occurs. +func (c *replicationControllers) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *replicationControllers) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("replicationcontrollers"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the replicationController, and returns the corresponding replicationController object, and an error if there is any. +func (c *replicationControllers) Get(name string) (result *api.ReplicationController, err error) { + result = &api.ReplicationController{} + err = c.client.Get(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ReplicationControllers that match those selectors. +func (c *replicationControllers) List(opts api.ListOptions) (result *api.ReplicationControllerList, err error) { + result = &api.ReplicationControllerList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("replicationcontrollers"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested replicationControllers. +func (c *replicationControllers) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("replicationcontrollers"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/resourcequota.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/resourcequota.go new file mode 100644 index 000000000..2d0da73fb --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/resourcequota.go @@ -0,0 +1,149 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ResourceQuotasGetter has a method to return a ResourceQuotaInterface. +// A group's client should implement this interface. +type ResourceQuotasGetter interface { + ResourceQuotas(namespace string) ResourceQuotaInterface +} + +// ResourceQuotaInterface has methods to work with ResourceQuota resources. +type ResourceQuotaInterface interface { + Create(*api.ResourceQuota) (*api.ResourceQuota, error) + Update(*api.ResourceQuota) (*api.ResourceQuota, error) + UpdateStatus(*api.ResourceQuota) (*api.ResourceQuota, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.ResourceQuota, error) + List(opts api.ListOptions) (*api.ResourceQuotaList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ResourceQuotaExpansion +} + +// resourceQuotas implements ResourceQuotaInterface +type resourceQuotas struct { + client *CoreClient + ns string +} + +// newResourceQuotas returns a ResourceQuotas +func newResourceQuotas(c *CoreClient, namespace string) *resourceQuotas { + return &resourceQuotas{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a resourceQuota and creates it. Returns the server's representation of the resourceQuota, and an error, if there is any. +func (c *resourceQuotas) Create(resourceQuota *api.ResourceQuota) (result *api.ResourceQuota, err error) { + result = &api.ResourceQuota{} + err = c.client.Post(). + Namespace(c.ns). + Resource("resourcequotas"). + Body(resourceQuota). + Do(). + Into(result) + return +} + +// Update takes the representation of a resourceQuota and updates it. Returns the server's representation of the resourceQuota, and an error, if there is any. +func (c *resourceQuotas) Update(resourceQuota *api.ResourceQuota) (result *api.ResourceQuota, err error) { + result = &api.ResourceQuota{} + err = c.client.Put(). + Namespace(c.ns). + Resource("resourcequotas"). + Name(resourceQuota.Name). + Body(resourceQuota). + Do(). + Into(result) + return +} + +func (c *resourceQuotas) UpdateStatus(resourceQuota *api.ResourceQuota) (result *api.ResourceQuota, err error) { + result = &api.ResourceQuota{} + err = c.client.Put(). + Namespace(c.ns). + Resource("resourcequotas"). + Name(resourceQuota.Name). + SubResource("status"). + Body(resourceQuota). + Do(). + Into(result) + return +} + +// Delete takes name of the resourceQuota and deletes it. Returns an error if one occurs. +func (c *resourceQuotas) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("resourcequotas"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *resourceQuotas) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("resourcequotas"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the resourceQuota, and returns the corresponding resourceQuota object, and an error if there is any. +func (c *resourceQuotas) Get(name string) (result *api.ResourceQuota, err error) { + result = &api.ResourceQuota{} + err = c.client.Get(). + Namespace(c.ns). + Resource("resourcequotas"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ResourceQuotas that match those selectors. +func (c *resourceQuotas) List(opts api.ListOptions) (result *api.ResourceQuotaList, err error) { + result = &api.ResourceQuotaList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("resourcequotas"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested resourceQuotas. +func (c *resourceQuotas) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("resourcequotas"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/secret.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/secret.go new file mode 100644 index 000000000..101fbdb54 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/secret.go @@ -0,0 +1,135 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// SecretsGetter has a method to return a SecretInterface. +// A group's client should implement this interface. +type SecretsGetter interface { + Secrets(namespace string) SecretInterface +} + +// SecretInterface has methods to work with Secret resources. +type SecretInterface interface { + Create(*api.Secret) (*api.Secret, error) + Update(*api.Secret) (*api.Secret, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.Secret, error) + List(opts api.ListOptions) (*api.SecretList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + SecretExpansion +} + +// secrets implements SecretInterface +type secrets struct { + client *CoreClient + ns string +} + +// newSecrets returns a Secrets +func newSecrets(c *CoreClient, namespace string) *secrets { + return &secrets{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a secret and creates it. Returns the server's representation of the secret, and an error, if there is any. +func (c *secrets) Create(secret *api.Secret) (result *api.Secret, err error) { + result = &api.Secret{} + err = c.client.Post(). + Namespace(c.ns). + Resource("secrets"). + Body(secret). + Do(). + Into(result) + return +} + +// Update takes the representation of a secret and updates it. Returns the server's representation of the secret, and an error, if there is any. +func (c *secrets) Update(secret *api.Secret) (result *api.Secret, err error) { + result = &api.Secret{} + err = c.client.Put(). + Namespace(c.ns). + Resource("secrets"). + Name(secret.Name). + Body(secret). + Do(). + Into(result) + return +} + +// Delete takes name of the secret and deletes it. Returns an error if one occurs. +func (c *secrets) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("secrets"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *secrets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("secrets"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the secret, and returns the corresponding secret object, and an error if there is any. +func (c *secrets) Get(name string) (result *api.Secret, err error) { + result = &api.Secret{} + err = c.client.Get(). + Namespace(c.ns). + Resource("secrets"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Secrets that match those selectors. +func (c *secrets) List(opts api.ListOptions) (result *api.SecretList, err error) { + result = &api.SecretList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("secrets"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested secrets. +func (c *secrets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("secrets"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/service.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/service.go new file mode 100644 index 000000000..006f601c2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/service.go @@ -0,0 +1,149 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ServicesGetter has a method to return a ServiceInterface. +// A group's client should implement this interface. +type ServicesGetter interface { + Services(namespace string) ServiceInterface +} + +// ServiceInterface has methods to work with Service resources. +type ServiceInterface interface { + Create(*api.Service) (*api.Service, error) + Update(*api.Service) (*api.Service, error) + UpdateStatus(*api.Service) (*api.Service, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.Service, error) + List(opts api.ListOptions) (*api.ServiceList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ServiceExpansion +} + +// services implements ServiceInterface +type services struct { + client *CoreClient + ns string +} + +// newServices returns a Services +func newServices(c *CoreClient, namespace string) *services { + return &services{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a service and creates it. Returns the server's representation of the service, and an error, if there is any. +func (c *services) Create(service *api.Service) (result *api.Service, err error) { + result = &api.Service{} + err = c.client.Post(). + Namespace(c.ns). + Resource("services"). + Body(service). + Do(). + Into(result) + return +} + +// Update takes the representation of a service and updates it. Returns the server's representation of the service, and an error, if there is any. +func (c *services) Update(service *api.Service) (result *api.Service, err error) { + result = &api.Service{} + err = c.client.Put(). + Namespace(c.ns). + Resource("services"). + Name(service.Name). + Body(service). + Do(). + Into(result) + return +} + +func (c *services) UpdateStatus(service *api.Service) (result *api.Service, err error) { + result = &api.Service{} + err = c.client.Put(). + Namespace(c.ns). + Resource("services"). + Name(service.Name). + SubResource("status"). + Body(service). + Do(). + Into(result) + return +} + +// Delete takes name of the service and deletes it. Returns an error if one occurs. +func (c *services) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("services"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *services) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("services"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the service, and returns the corresponding service object, and an error if there is any. +func (c *services) Get(name string) (result *api.Service, err error) { + result = &api.Service{} + err = c.client.Get(). + Namespace(c.ns). + Resource("services"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Services that match those selectors. +func (c *services) List(opts api.ListOptions) (result *api.ServiceList, err error) { + result = &api.ServiceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("services"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested services. +func (c *services) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("services"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/service_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/service_expansion.go new file mode 100644 index 000000000..89266e6cd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/service_expansion.go @@ -0,0 +1,41 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/util/net" +) + +// The ServiceExpansion interface allows manually adding extra methods to the ServiceInterface. +type ServiceExpansion interface { + ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper +} + +// ProxyGet returns a response of the service by calling it through the proxy. +func (c *services) ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper { + request := c.client.Get(). + Prefix("proxy"). + Namespace(c.ns). + Resource("services"). + Name(net.JoinSchemeNamePort(scheme, name, port)). + Suffix(path) + for k, v := range params { + request = request.Param(k, v) + } + return request +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/serviceaccount.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/serviceaccount.go new file mode 100644 index 000000000..65f7df263 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned/serviceaccount.go @@ -0,0 +1,135 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ServiceAccountsGetter has a method to return a ServiceAccountInterface. +// A group's client should implement this interface. +type ServiceAccountsGetter interface { + ServiceAccounts(namespace string) ServiceAccountInterface +} + +// ServiceAccountInterface has methods to work with ServiceAccount resources. +type ServiceAccountInterface interface { + Create(*api.ServiceAccount) (*api.ServiceAccount, error) + Update(*api.ServiceAccount) (*api.ServiceAccount, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*api.ServiceAccount, error) + List(opts api.ListOptions) (*api.ServiceAccountList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ServiceAccountExpansion +} + +// serviceAccounts implements ServiceAccountInterface +type serviceAccounts struct { + client *CoreClient + ns string +} + +// newServiceAccounts returns a ServiceAccounts +func newServiceAccounts(c *CoreClient, namespace string) *serviceAccounts { + return &serviceAccounts{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a serviceAccount and creates it. Returns the server's representation of the serviceAccount, and an error, if there is any. +func (c *serviceAccounts) Create(serviceAccount *api.ServiceAccount) (result *api.ServiceAccount, err error) { + result = &api.ServiceAccount{} + err = c.client.Post(). + Namespace(c.ns). + Resource("serviceaccounts"). + Body(serviceAccount). + Do(). + Into(result) + return +} + +// Update takes the representation of a serviceAccount and updates it. Returns the server's representation of the serviceAccount, and an error, if there is any. +func (c *serviceAccounts) Update(serviceAccount *api.ServiceAccount) (result *api.ServiceAccount, err error) { + result = &api.ServiceAccount{} + err = c.client.Put(). + Namespace(c.ns). + Resource("serviceaccounts"). + Name(serviceAccount.Name). + Body(serviceAccount). + Do(). + Into(result) + return +} + +// Delete takes name of the serviceAccount and deletes it. Returns an error if one occurs. +func (c *serviceAccounts) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("serviceaccounts"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *serviceAccounts) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("serviceaccounts"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the serviceAccount, and returns the corresponding serviceAccount object, and an error if there is any. +func (c *serviceAccounts) Get(name string) (result *api.ServiceAccount, err error) { + result = &api.ServiceAccount{} + err = c.client.Get(). + Namespace(c.ns). + Resource("serviceaccounts"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ServiceAccounts that match those selectors. +func (c *serviceAccounts) List(opts api.ListOptions) (result *api.ServiceAccountList, err error) { + result = &api.ServiceAccountList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("serviceaccounts"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested serviceAccounts. +func (c *serviceAccounts) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("serviceaccounts"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/daemonset.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/daemonset.go new file mode 100644 index 000000000..96dae5835 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/daemonset.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + watch "k8s.io/kubernetes/pkg/watch" +) + +// DaemonSetsGetter has a method to return a DaemonSetInterface. +// A group's client should implement this interface. +type DaemonSetsGetter interface { + DaemonSets(namespace string) DaemonSetInterface +} + +// DaemonSetInterface has methods to work with DaemonSet resources. +type DaemonSetInterface interface { + Create(*extensions.DaemonSet) (*extensions.DaemonSet, error) + Update(*extensions.DaemonSet) (*extensions.DaemonSet, error) + UpdateStatus(*extensions.DaemonSet) (*extensions.DaemonSet, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*extensions.DaemonSet, error) + List(opts api.ListOptions) (*extensions.DaemonSetList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + DaemonSetExpansion +} + +// daemonSets implements DaemonSetInterface +type daemonSets struct { + client *ExtensionsClient + ns string +} + +// newDaemonSets returns a DaemonSets +func newDaemonSets(c *ExtensionsClient, namespace string) *daemonSets { + return &daemonSets{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a daemonSet and creates it. Returns the server's representation of the daemonSet, and an error, if there is any. +func (c *daemonSets) Create(daemonSet *extensions.DaemonSet) (result *extensions.DaemonSet, err error) { + result = &extensions.DaemonSet{} + err = c.client.Post(). + Namespace(c.ns). + Resource("daemonsets"). + Body(daemonSet). + Do(). + Into(result) + return +} + +// Update takes the representation of a daemonSet and updates it. Returns the server's representation of the daemonSet, and an error, if there is any. +func (c *daemonSets) Update(daemonSet *extensions.DaemonSet) (result *extensions.DaemonSet, err error) { + result = &extensions.DaemonSet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("daemonsets"). + Name(daemonSet.Name). + Body(daemonSet). + Do(). + Into(result) + return +} + +func (c *daemonSets) UpdateStatus(daemonSet *extensions.DaemonSet) (result *extensions.DaemonSet, err error) { + result = &extensions.DaemonSet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("daemonsets"). + Name(daemonSet.Name). + SubResource("status"). + Body(daemonSet). + Do(). + Into(result) + return +} + +// Delete takes name of the daemonSet and deletes it. Returns an error if one occurs. +func (c *daemonSets) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("daemonsets"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *daemonSets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("daemonsets"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the daemonSet, and returns the corresponding daemonSet object, and an error if there is any. +func (c *daemonSets) Get(name string) (result *extensions.DaemonSet, err error) { + result = &extensions.DaemonSet{} + err = c.client.Get(). + Namespace(c.ns). + Resource("daemonsets"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of DaemonSets that match those selectors. +func (c *daemonSets) List(opts api.ListOptions) (result *extensions.DaemonSetList, err error) { + result = &extensions.DaemonSetList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("daemonsets"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested daemonSets. +func (c *daemonSets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("daemonsets"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/deployment.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/deployment.go new file mode 100644 index 000000000..3b995c021 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/deployment.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + watch "k8s.io/kubernetes/pkg/watch" +) + +// DeploymentsGetter has a method to return a DeploymentInterface. +// A group's client should implement this interface. +type DeploymentsGetter interface { + Deployments(namespace string) DeploymentInterface +} + +// DeploymentInterface has methods to work with Deployment resources. +type DeploymentInterface interface { + Create(*extensions.Deployment) (*extensions.Deployment, error) + Update(*extensions.Deployment) (*extensions.Deployment, error) + UpdateStatus(*extensions.Deployment) (*extensions.Deployment, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*extensions.Deployment, error) + List(opts api.ListOptions) (*extensions.DeploymentList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + DeploymentExpansion +} + +// deployments implements DeploymentInterface +type deployments struct { + client *ExtensionsClient + ns string +} + +// newDeployments returns a Deployments +func newDeployments(c *ExtensionsClient, namespace string) *deployments { + return &deployments{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a deployment and creates it. Returns the server's representation of the deployment, and an error, if there is any. +func (c *deployments) Create(deployment *extensions.Deployment) (result *extensions.Deployment, err error) { + result = &extensions.Deployment{} + err = c.client.Post(). + Namespace(c.ns). + Resource("deployments"). + Body(deployment). + Do(). + Into(result) + return +} + +// Update takes the representation of a deployment and updates it. Returns the server's representation of the deployment, and an error, if there is any. +func (c *deployments) Update(deployment *extensions.Deployment) (result *extensions.Deployment, err error) { + result = &extensions.Deployment{} + err = c.client.Put(). + Namespace(c.ns). + Resource("deployments"). + Name(deployment.Name). + Body(deployment). + Do(). + Into(result) + return +} + +func (c *deployments) UpdateStatus(deployment *extensions.Deployment) (result *extensions.Deployment, err error) { + result = &extensions.Deployment{} + err = c.client.Put(). + Namespace(c.ns). + Resource("deployments"). + Name(deployment.Name). + SubResource("status"). + Body(deployment). + Do(). + Into(result) + return +} + +// Delete takes name of the deployment and deletes it. Returns an error if one occurs. +func (c *deployments) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("deployments"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *deployments) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("deployments"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the deployment, and returns the corresponding deployment object, and an error if there is any. +func (c *deployments) Get(name string) (result *extensions.Deployment, err error) { + result = &extensions.Deployment{} + err = c.client.Get(). + Namespace(c.ns). + Resource("deployments"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Deployments that match those selectors. +func (c *deployments) List(opts api.ListOptions) (result *extensions.DeploymentList, err error) { + result = &extensions.DeploymentList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("deployments"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested deployments. +func (c *deployments) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("deployments"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/deployment_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/deployment_expansion.go new file mode 100644 index 000000000..9969aecc9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/deployment_expansion.go @@ -0,0 +1,29 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import "k8s.io/kubernetes/pkg/apis/extensions" + +// The DeploymentExpansion interface allows manually adding extra methods to the DeploymentInterface. +type DeploymentExpansion interface { + Rollback(*extensions.DeploymentRollback) error +} + +// Rollback applied the provided DeploymentRollback to the named deployment in the current namespace. +func (c *deployments) Rollback(deploymentRollback *extensions.DeploymentRollback) error { + return c.client.Post().Namespace(c.ns).Resource("deployments").Name(deploymentRollback.Name).SubResource("rollback").Body(deploymentRollback).Do().Error() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/doc.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/doc.go new file mode 100644 index 000000000..47517b642 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This package is generated by client-gen with the default arguments. + +// This package has the automatically generated typed clients. +package unversioned diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/extensions_client.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/extensions_client.go new file mode 100644 index 000000000..220dd69b5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/extensions_client.go @@ -0,0 +1,125 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + registered "k8s.io/kubernetes/pkg/apimachinery/registered" + restclient "k8s.io/kubernetes/pkg/client/restclient" +) + +type ExtensionsInterface interface { + DaemonSetsGetter + DeploymentsGetter + HorizontalPodAutoscalersGetter + IngressesGetter + JobsGetter + ReplicaSetsGetter + ScalesGetter + ThirdPartyResourcesGetter +} + +// ExtensionsClient is used to interact with features provided by the Extensions group. +type ExtensionsClient struct { + *restclient.RESTClient +} + +func (c *ExtensionsClient) DaemonSets(namespace string) DaemonSetInterface { + return newDaemonSets(c, namespace) +} + +func (c *ExtensionsClient) Deployments(namespace string) DeploymentInterface { + return newDeployments(c, namespace) +} + +func (c *ExtensionsClient) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface { + return newHorizontalPodAutoscalers(c, namespace) +} + +func (c *ExtensionsClient) Ingresses(namespace string) IngressInterface { + return newIngresses(c, namespace) +} + +func (c *ExtensionsClient) Jobs(namespace string) JobInterface { + return newJobs(c, namespace) +} + +func (c *ExtensionsClient) ReplicaSets(namespace string) ReplicaSetInterface { + return newReplicaSets(c, namespace) +} + +func (c *ExtensionsClient) Scales(namespace string) ScaleInterface { + return newScales(c, namespace) +} + +func (c *ExtensionsClient) ThirdPartyResources(namespace string) ThirdPartyResourceInterface { + return newThirdPartyResources(c, namespace) +} + +// NewForConfig creates a new ExtensionsClient for the given config. +func NewForConfig(c *restclient.Config) (*ExtensionsClient, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := restclient.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &ExtensionsClient{client}, nil +} + +// NewForConfigOrDie creates a new ExtensionsClient for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *restclient.Config) *ExtensionsClient { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new ExtensionsClient for the given RESTClient. +func New(c *restclient.RESTClient) *ExtensionsClient { + return &ExtensionsClient{c} +} + +func setConfigDefaults(config *restclient.Config) error { + // if extensions group is not registered, return an error + g, err := registered.Group("extensions") + if err != nil { + return err + } + config.APIPath = "/apis" + if config.UserAgent == "" { + config.UserAgent = restclient.DefaultKubernetesUserAgent() + } + // TODO: Unconditionally set the config.Version, until we fix the config. + //if config.Version == "" { + copyGroupVersion := g.GroupVersion + config.GroupVersion = ©GroupVersion + //} + + config.Codec = api.Codecs.LegacyCodec(*config.GroupVersion) + if config.QPS == 0 { + config.QPS = 5 + } + if config.Burst == 0 { + config.Burst = 10 + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/doc.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/doc.go new file mode 100644 index 000000000..eb358c26c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This package is generated by client-gen with the default arguments. + +// Package fake has the automatically generated clients. +package fake diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_daemonset.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_daemonset.go new file mode 100644 index 000000000..7de9f927f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_daemonset.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeDaemonSets implements DaemonSetInterface +type FakeDaemonSets struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeDaemonSets) Create(daemonSet *extensions.DaemonSet) (result *extensions.DaemonSet, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("daemonsets", c.ns, daemonSet), &extensions.DaemonSet{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.DaemonSet), err +} + +func (c *FakeDaemonSets) Update(daemonSet *extensions.DaemonSet) (result *extensions.DaemonSet, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("daemonsets", c.ns, daemonSet), &extensions.DaemonSet{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.DaemonSet), err +} + +func (c *FakeDaemonSets) UpdateStatus(daemonSet *extensions.DaemonSet) (*extensions.DaemonSet, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("daemonsets", "status", c.ns, daemonSet), &extensions.DaemonSet{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.DaemonSet), err +} + +func (c *FakeDaemonSets) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("daemonsets", c.ns, name), &extensions.DaemonSet{}) + + return err +} + +func (c *FakeDaemonSets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("daemonsets", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &extensions.DaemonSetList{}) + return err +} + +func (c *FakeDaemonSets) Get(name string) (result *extensions.DaemonSet, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("daemonsets", c.ns, name), &extensions.DaemonSet{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.DaemonSet), err +} + +func (c *FakeDaemonSets) List(opts api.ListOptions) (result *extensions.DaemonSetList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("daemonsets", c.ns, opts), &extensions.DaemonSetList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &extensions.DaemonSetList{} + for _, item := range obj.(*extensions.DaemonSetList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested daemonSets. +func (c *FakeDaemonSets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("daemonsets", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_deployment.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_deployment.go new file mode 100644 index 000000000..748968a9d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_deployment.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeDeployments implements DeploymentInterface +type FakeDeployments struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeDeployments) Create(deployment *extensions.Deployment) (result *extensions.Deployment, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("deployments", c.ns, deployment), &extensions.Deployment{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.Deployment), err +} + +func (c *FakeDeployments) Update(deployment *extensions.Deployment) (result *extensions.Deployment, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("deployments", c.ns, deployment), &extensions.Deployment{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.Deployment), err +} + +func (c *FakeDeployments) UpdateStatus(deployment *extensions.Deployment) (*extensions.Deployment, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("deployments", "status", c.ns, deployment), &extensions.Deployment{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.Deployment), err +} + +func (c *FakeDeployments) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("deployments", c.ns, name), &extensions.Deployment{}) + + return err +} + +func (c *FakeDeployments) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("deployments", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &extensions.DeploymentList{}) + return err +} + +func (c *FakeDeployments) Get(name string) (result *extensions.Deployment, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("deployments", c.ns, name), &extensions.Deployment{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.Deployment), err +} + +func (c *FakeDeployments) List(opts api.ListOptions) (result *extensions.DeploymentList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("deployments", c.ns, opts), &extensions.DeploymentList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &extensions.DeploymentList{} + for _, item := range obj.(*extensions.DeploymentList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested deployments. +func (c *FakeDeployments) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("deployments", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_deployment_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_deployment_expansion.go new file mode 100644 index 000000000..3edc64c01 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_deployment_expansion.go @@ -0,0 +1,33 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeDeployments) Rollback(deploymentRollback *extensions.DeploymentRollback) error { + action := core.CreateActionImpl{} + action.Verb = "create" + action.Resource = "deployments" + action.Subresource = "rollback" + action.Object = deploymentRollback + + _, err := c.Fake.Invokes(action, deploymentRollback) + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_extensions_client.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_extensions_client.go new file mode 100644 index 000000000..51c403c76 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_extensions_client.go @@ -0,0 +1,58 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + unversioned "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned" + core "k8s.io/kubernetes/pkg/client/testing/core" +) + +type FakeExtensions struct { + *core.Fake +} + +func (c *FakeExtensions) DaemonSets(namespace string) unversioned.DaemonSetInterface { + return &FakeDaemonSets{c, namespace} +} + +func (c *FakeExtensions) Deployments(namespace string) unversioned.DeploymentInterface { + return &FakeDeployments{c, namespace} +} + +func (c *FakeExtensions) HorizontalPodAutoscalers(namespace string) unversioned.HorizontalPodAutoscalerInterface { + return &FakeHorizontalPodAutoscalers{c, namespace} +} + +func (c *FakeExtensions) Ingresses(namespace string) unversioned.IngressInterface { + return &FakeIngresses{c, namespace} +} + +func (c *FakeExtensions) Jobs(namespace string) unversioned.JobInterface { + return &FakeJobs{c, namespace} +} + +func (c *FakeExtensions) ReplicaSets(namespace string) unversioned.ReplicaSetInterface { + return &FakeReplicaSets{c, namespace} +} + +func (c *FakeExtensions) Scales(namespace string) unversioned.ScaleInterface { + return &FakeScales{c, namespace} +} + +func (c *FakeExtensions) ThirdPartyResources(namespace string) unversioned.ThirdPartyResourceInterface { + return &FakeThirdPartyResources{c, namespace} +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_horizontalpodautoscaler.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_horizontalpodautoscaler.go new file mode 100644 index 000000000..71b5cf322 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_horizontalpodautoscaler.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeHorizontalPodAutoscalers implements HorizontalPodAutoscalerInterface +type FakeHorizontalPodAutoscalers struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeHorizontalPodAutoscalers) Create(horizontalPodAutoscaler *extensions.HorizontalPodAutoscaler) (result *extensions.HorizontalPodAutoscaler, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("horizontalpodautoscalers", c.ns, horizontalPodAutoscaler), &extensions.HorizontalPodAutoscaler{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalers) Update(horizontalPodAutoscaler *extensions.HorizontalPodAutoscaler) (result *extensions.HorizontalPodAutoscaler, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("horizontalpodautoscalers", c.ns, horizontalPodAutoscaler), &extensions.HorizontalPodAutoscaler{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalers) UpdateStatus(horizontalPodAutoscaler *extensions.HorizontalPodAutoscaler) (*extensions.HorizontalPodAutoscaler, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("horizontalpodautoscalers", "status", c.ns, horizontalPodAutoscaler), &extensions.HorizontalPodAutoscaler{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalers) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("horizontalpodautoscalers", c.ns, name), &extensions.HorizontalPodAutoscaler{}) + + return err +} + +func (c *FakeHorizontalPodAutoscalers) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("horizontalpodautoscalers", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &extensions.HorizontalPodAutoscalerList{}) + return err +} + +func (c *FakeHorizontalPodAutoscalers) Get(name string) (result *extensions.HorizontalPodAutoscaler, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("horizontalpodautoscalers", c.ns, name), &extensions.HorizontalPodAutoscaler{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalers) List(opts api.ListOptions) (result *extensions.HorizontalPodAutoscalerList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("horizontalpodautoscalers", c.ns, opts), &extensions.HorizontalPodAutoscalerList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &extensions.HorizontalPodAutoscalerList{} + for _, item := range obj.(*extensions.HorizontalPodAutoscalerList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers. +func (c *FakeHorizontalPodAutoscalers) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("horizontalpodautoscalers", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_ingress.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_ingress.go new file mode 100644 index 000000000..a331644e4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_ingress.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeIngresses implements IngressInterface +type FakeIngresses struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeIngresses) Create(ingress *extensions.Ingress) (result *extensions.Ingress, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("ingresses", c.ns, ingress), &extensions.Ingress{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.Ingress), err +} + +func (c *FakeIngresses) Update(ingress *extensions.Ingress) (result *extensions.Ingress, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("ingresses", c.ns, ingress), &extensions.Ingress{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.Ingress), err +} + +func (c *FakeIngresses) UpdateStatus(ingress *extensions.Ingress) (*extensions.Ingress, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("ingresses", "status", c.ns, ingress), &extensions.Ingress{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.Ingress), err +} + +func (c *FakeIngresses) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("ingresses", c.ns, name), &extensions.Ingress{}) + + return err +} + +func (c *FakeIngresses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("ingresses", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &extensions.IngressList{}) + return err +} + +func (c *FakeIngresses) Get(name string) (result *extensions.Ingress, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("ingresses", c.ns, name), &extensions.Ingress{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.Ingress), err +} + +func (c *FakeIngresses) List(opts api.ListOptions) (result *extensions.IngressList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("ingresses", c.ns, opts), &extensions.IngressList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &extensions.IngressList{} + for _, item := range obj.(*extensions.IngressList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested ingresses. +func (c *FakeIngresses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("ingresses", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_job.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_job.go new file mode 100644 index 000000000..c1875c006 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_job.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeJobs implements JobInterface +type FakeJobs struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeJobs) Create(job *extensions.Job) (result *extensions.Job, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("jobs", c.ns, job), &extensions.Job{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.Job), err +} + +func (c *FakeJobs) Update(job *extensions.Job) (result *extensions.Job, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("jobs", c.ns, job), &extensions.Job{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.Job), err +} + +func (c *FakeJobs) UpdateStatus(job *extensions.Job) (*extensions.Job, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("jobs", "status", c.ns, job), &extensions.Job{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.Job), err +} + +func (c *FakeJobs) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("jobs", c.ns, name), &extensions.Job{}) + + return err +} + +func (c *FakeJobs) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("jobs", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &extensions.JobList{}) + return err +} + +func (c *FakeJobs) Get(name string) (result *extensions.Job, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("jobs", c.ns, name), &extensions.Job{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.Job), err +} + +func (c *FakeJobs) List(opts api.ListOptions) (result *extensions.JobList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("jobs", c.ns, opts), &extensions.JobList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &extensions.JobList{} + for _, item := range obj.(*extensions.JobList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested jobs. +func (c *FakeJobs) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("jobs", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_replicaset.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_replicaset.go new file mode 100644 index 000000000..d861326b7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_replicaset.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeReplicaSets implements ReplicaSetInterface +type FakeReplicaSets struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeReplicaSets) Create(replicaSet *extensions.ReplicaSet) (result *extensions.ReplicaSet, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("replicasets", c.ns, replicaSet), &extensions.ReplicaSet{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.ReplicaSet), err +} + +func (c *FakeReplicaSets) Update(replicaSet *extensions.ReplicaSet) (result *extensions.ReplicaSet, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("replicasets", c.ns, replicaSet), &extensions.ReplicaSet{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.ReplicaSet), err +} + +func (c *FakeReplicaSets) UpdateStatus(replicaSet *extensions.ReplicaSet) (*extensions.ReplicaSet, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("replicasets", "status", c.ns, replicaSet), &extensions.ReplicaSet{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.ReplicaSet), err +} + +func (c *FakeReplicaSets) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("replicasets", c.ns, name), &extensions.ReplicaSet{}) + + return err +} + +func (c *FakeReplicaSets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("replicasets", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &extensions.ReplicaSetList{}) + return err +} + +func (c *FakeReplicaSets) Get(name string) (result *extensions.ReplicaSet, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("replicasets", c.ns, name), &extensions.ReplicaSet{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.ReplicaSet), err +} + +func (c *FakeReplicaSets) List(opts api.ListOptions) (result *extensions.ReplicaSetList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("replicasets", c.ns, opts), &extensions.ReplicaSetList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &extensions.ReplicaSetList{} + for _, item := range obj.(*extensions.ReplicaSetList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested replicaSets. +func (c *FakeReplicaSets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("replicasets", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_scale.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_scale.go new file mode 100644 index 000000000..d2cfc5f7b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_scale.go @@ -0,0 +1,23 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +// FakeScales implements ScaleInterface +type FakeScales struct { + Fake *FakeExtensions + ns string +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_scale_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_scale_expansion.go new file mode 100644 index 000000000..8c52e409b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_scale_expansion.go @@ -0,0 +1,46 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeScales) Get(kind string, name string) (result *extensions.Scale, err error) { + action := core.GetActionImpl{} + action.Verb = "get" + action.Namespace = c.ns + action.Resource = kind + action.Subresource = "scale" + action.Name = name + obj, err := c.Fake.Invokes(action, &extensions.Scale{}) + result = obj.(*extensions.Scale) + return +} + +func (c *FakeScales) Update(kind string, scale *extensions.Scale) (result *extensions.Scale, err error) { + action := core.UpdateActionImpl{} + action.Verb = "update" + action.Namespace = c.ns + action.Resource = kind + action.Subresource = "scale" + action.Object = scale + obj, err := c.Fake.Invokes(action, scale) + result = obj.(*extensions.Scale) + return +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_thirdpartyresource.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_thirdpartyresource.go new file mode 100644 index 000000000..9a005d570 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/fake/fake_thirdpartyresource.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeThirdPartyResources implements ThirdPartyResourceInterface +type FakeThirdPartyResources struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeThirdPartyResources) Create(thirdPartyResource *extensions.ThirdPartyResource) (result *extensions.ThirdPartyResource, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("thirdpartyresources", c.ns, thirdPartyResource), &extensions.ThirdPartyResource{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.ThirdPartyResource), err +} + +func (c *FakeThirdPartyResources) Update(thirdPartyResource *extensions.ThirdPartyResource) (result *extensions.ThirdPartyResource, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("thirdpartyresources", c.ns, thirdPartyResource), &extensions.ThirdPartyResource{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.ThirdPartyResource), err +} + +func (c *FakeThirdPartyResources) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("thirdpartyresources", c.ns, name), &extensions.ThirdPartyResource{}) + + return err +} + +func (c *FakeThirdPartyResources) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("thirdpartyresources", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &extensions.ThirdPartyResourceList{}) + return err +} + +func (c *FakeThirdPartyResources) Get(name string) (result *extensions.ThirdPartyResource, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("thirdpartyresources", c.ns, name), &extensions.ThirdPartyResource{}) + + if obj == nil { + return nil, err + } + return obj.(*extensions.ThirdPartyResource), err +} + +func (c *FakeThirdPartyResources) List(opts api.ListOptions) (result *extensions.ThirdPartyResourceList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("thirdpartyresources", c.ns, opts), &extensions.ThirdPartyResourceList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &extensions.ThirdPartyResourceList{} + for _, item := range obj.(*extensions.ThirdPartyResourceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested thirdPartyResources. +func (c *FakeThirdPartyResources) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("thirdpartyresources", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/generated_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/generated_expansion.go new file mode 100644 index 000000000..0690e0c8c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/generated_expansion.go @@ -0,0 +1,29 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +type DaemonSetExpansion interface{} + +type HorizontalPodAutoscalerExpansion interface{} + +type IngressExpansion interface{} + +type JobExpansion interface{} + +type ThirdPartyResourceExpansion interface{} + +type ReplicaSetExpansion interface{} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/horizontalpodautoscaler.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/horizontalpodautoscaler.go new file mode 100644 index 000000000..2cffcee46 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/horizontalpodautoscaler.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + watch "k8s.io/kubernetes/pkg/watch" +) + +// HorizontalPodAutoscalersGetter has a method to return a HorizontalPodAutoscalerInterface. +// A group's client should implement this interface. +type HorizontalPodAutoscalersGetter interface { + HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface +} + +// HorizontalPodAutoscalerInterface has methods to work with HorizontalPodAutoscaler resources. +type HorizontalPodAutoscalerInterface interface { + Create(*extensions.HorizontalPodAutoscaler) (*extensions.HorizontalPodAutoscaler, error) + Update(*extensions.HorizontalPodAutoscaler) (*extensions.HorizontalPodAutoscaler, error) + UpdateStatus(*extensions.HorizontalPodAutoscaler) (*extensions.HorizontalPodAutoscaler, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*extensions.HorizontalPodAutoscaler, error) + List(opts api.ListOptions) (*extensions.HorizontalPodAutoscalerList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + HorizontalPodAutoscalerExpansion +} + +// horizontalPodAutoscalers implements HorizontalPodAutoscalerInterface +type horizontalPodAutoscalers struct { + client *ExtensionsClient + ns string +} + +// newHorizontalPodAutoscalers returns a HorizontalPodAutoscalers +func newHorizontalPodAutoscalers(c *ExtensionsClient, namespace string) *horizontalPodAutoscalers { + return &horizontalPodAutoscalers{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a horizontalPodAutoscaler and creates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any. +func (c *horizontalPodAutoscalers) Create(horizontalPodAutoscaler *extensions.HorizontalPodAutoscaler) (result *extensions.HorizontalPodAutoscaler, err error) { + result = &extensions.HorizontalPodAutoscaler{} + err = c.client.Post(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Body(horizontalPodAutoscaler). + Do(). + Into(result) + return +} + +// Update takes the representation of a horizontalPodAutoscaler and updates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any. +func (c *horizontalPodAutoscalers) Update(horizontalPodAutoscaler *extensions.HorizontalPodAutoscaler) (result *extensions.HorizontalPodAutoscaler, err error) { + result = &extensions.HorizontalPodAutoscaler{} + err = c.client.Put(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Name(horizontalPodAutoscaler.Name). + Body(horizontalPodAutoscaler). + Do(). + Into(result) + return +} + +func (c *horizontalPodAutoscalers) UpdateStatus(horizontalPodAutoscaler *extensions.HorizontalPodAutoscaler) (result *extensions.HorizontalPodAutoscaler, err error) { + result = &extensions.HorizontalPodAutoscaler{} + err = c.client.Put(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Name(horizontalPodAutoscaler.Name). + SubResource("status"). + Body(horizontalPodAutoscaler). + Do(). + Into(result) + return +} + +// Delete takes name of the horizontalPodAutoscaler and deletes it. Returns an error if one occurs. +func (c *horizontalPodAutoscalers) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *horizontalPodAutoscalers) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the horizontalPodAutoscaler, and returns the corresponding horizontalPodAutoscaler object, and an error if there is any. +func (c *horizontalPodAutoscalers) Get(name string) (result *extensions.HorizontalPodAutoscaler, err error) { + result = &extensions.HorizontalPodAutoscaler{} + err = c.client.Get(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of HorizontalPodAutoscalers that match those selectors. +func (c *horizontalPodAutoscalers) List(opts api.ListOptions) (result *extensions.HorizontalPodAutoscalerList, err error) { + result = &extensions.HorizontalPodAutoscalerList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers. +func (c *horizontalPodAutoscalers) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/ingress.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/ingress.go new file mode 100644 index 000000000..a9d950eae --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/ingress.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + watch "k8s.io/kubernetes/pkg/watch" +) + +// IngressesGetter has a method to return a IngressInterface. +// A group's client should implement this interface. +type IngressesGetter interface { + Ingresses(namespace string) IngressInterface +} + +// IngressInterface has methods to work with Ingress resources. +type IngressInterface interface { + Create(*extensions.Ingress) (*extensions.Ingress, error) + Update(*extensions.Ingress) (*extensions.Ingress, error) + UpdateStatus(*extensions.Ingress) (*extensions.Ingress, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*extensions.Ingress, error) + List(opts api.ListOptions) (*extensions.IngressList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + IngressExpansion +} + +// ingresses implements IngressInterface +type ingresses struct { + client *ExtensionsClient + ns string +} + +// newIngresses returns a Ingresses +func newIngresses(c *ExtensionsClient, namespace string) *ingresses { + return &ingresses{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a ingress and creates it. Returns the server's representation of the ingress, and an error, if there is any. +func (c *ingresses) Create(ingress *extensions.Ingress) (result *extensions.Ingress, err error) { + result = &extensions.Ingress{} + err = c.client.Post(). + Namespace(c.ns). + Resource("ingresses"). + Body(ingress). + Do(). + Into(result) + return +} + +// Update takes the representation of a ingress and updates it. Returns the server's representation of the ingress, and an error, if there is any. +func (c *ingresses) Update(ingress *extensions.Ingress) (result *extensions.Ingress, err error) { + result = &extensions.Ingress{} + err = c.client.Put(). + Namespace(c.ns). + Resource("ingresses"). + Name(ingress.Name). + Body(ingress). + Do(). + Into(result) + return +} + +func (c *ingresses) UpdateStatus(ingress *extensions.Ingress) (result *extensions.Ingress, err error) { + result = &extensions.Ingress{} + err = c.client.Put(). + Namespace(c.ns). + Resource("ingresses"). + Name(ingress.Name). + SubResource("status"). + Body(ingress). + Do(). + Into(result) + return +} + +// Delete takes name of the ingress and deletes it. Returns an error if one occurs. +func (c *ingresses) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("ingresses"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *ingresses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("ingresses"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the ingress, and returns the corresponding ingress object, and an error if there is any. +func (c *ingresses) Get(name string) (result *extensions.Ingress, err error) { + result = &extensions.Ingress{} + err = c.client.Get(). + Namespace(c.ns). + Resource("ingresses"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Ingresses that match those selectors. +func (c *ingresses) List(opts api.ListOptions) (result *extensions.IngressList, err error) { + result = &extensions.IngressList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("ingresses"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested ingresses. +func (c *ingresses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("ingresses"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/job.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/job.go new file mode 100644 index 000000000..04d0d1282 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/job.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + watch "k8s.io/kubernetes/pkg/watch" +) + +// JobsGetter has a method to return a JobInterface. +// A group's client should implement this interface. +type JobsGetter interface { + Jobs(namespace string) JobInterface +} + +// JobInterface has methods to work with Job resources. +type JobInterface interface { + Create(*extensions.Job) (*extensions.Job, error) + Update(*extensions.Job) (*extensions.Job, error) + UpdateStatus(*extensions.Job) (*extensions.Job, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*extensions.Job, error) + List(opts api.ListOptions) (*extensions.JobList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + JobExpansion +} + +// jobs implements JobInterface +type jobs struct { + client *ExtensionsClient + ns string +} + +// newJobs returns a Jobs +func newJobs(c *ExtensionsClient, namespace string) *jobs { + return &jobs{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a job and creates it. Returns the server's representation of the job, and an error, if there is any. +func (c *jobs) Create(job *extensions.Job) (result *extensions.Job, err error) { + result = &extensions.Job{} + err = c.client.Post(). + Namespace(c.ns). + Resource("jobs"). + Body(job). + Do(). + Into(result) + return +} + +// Update takes the representation of a job and updates it. Returns the server's representation of the job, and an error, if there is any. +func (c *jobs) Update(job *extensions.Job) (result *extensions.Job, err error) { + result = &extensions.Job{} + err = c.client.Put(). + Namespace(c.ns). + Resource("jobs"). + Name(job.Name). + Body(job). + Do(). + Into(result) + return +} + +func (c *jobs) UpdateStatus(job *extensions.Job) (result *extensions.Job, err error) { + result = &extensions.Job{} + err = c.client.Put(). + Namespace(c.ns). + Resource("jobs"). + Name(job.Name). + SubResource("status"). + Body(job). + Do(). + Into(result) + return +} + +// Delete takes name of the job and deletes it. Returns an error if one occurs. +func (c *jobs) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("jobs"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *jobs) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("jobs"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the job, and returns the corresponding job object, and an error if there is any. +func (c *jobs) Get(name string) (result *extensions.Job, err error) { + result = &extensions.Job{} + err = c.client.Get(). + Namespace(c.ns). + Resource("jobs"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Jobs that match those selectors. +func (c *jobs) List(opts api.ListOptions) (result *extensions.JobList, err error) { + result = &extensions.JobList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("jobs"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested jobs. +func (c *jobs) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("jobs"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/replicaset.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/replicaset.go new file mode 100644 index 000000000..6257fd898 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/replicaset.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ReplicaSetsGetter has a method to return a ReplicaSetInterface. +// A group's client should implement this interface. +type ReplicaSetsGetter interface { + ReplicaSets(namespace string) ReplicaSetInterface +} + +// ReplicaSetInterface has methods to work with ReplicaSet resources. +type ReplicaSetInterface interface { + Create(*extensions.ReplicaSet) (*extensions.ReplicaSet, error) + Update(*extensions.ReplicaSet) (*extensions.ReplicaSet, error) + UpdateStatus(*extensions.ReplicaSet) (*extensions.ReplicaSet, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*extensions.ReplicaSet, error) + List(opts api.ListOptions) (*extensions.ReplicaSetList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ReplicaSetExpansion +} + +// replicaSets implements ReplicaSetInterface +type replicaSets struct { + client *ExtensionsClient + ns string +} + +// newReplicaSets returns a ReplicaSets +func newReplicaSets(c *ExtensionsClient, namespace string) *replicaSets { + return &replicaSets{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a replicaSet and creates it. Returns the server's representation of the replicaSet, and an error, if there is any. +func (c *replicaSets) Create(replicaSet *extensions.ReplicaSet) (result *extensions.ReplicaSet, err error) { + result = &extensions.ReplicaSet{} + err = c.client.Post(). + Namespace(c.ns). + Resource("replicasets"). + Body(replicaSet). + Do(). + Into(result) + return +} + +// Update takes the representation of a replicaSet and updates it. Returns the server's representation of the replicaSet, and an error, if there is any. +func (c *replicaSets) Update(replicaSet *extensions.ReplicaSet) (result *extensions.ReplicaSet, err error) { + result = &extensions.ReplicaSet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("replicasets"). + Name(replicaSet.Name). + Body(replicaSet). + Do(). + Into(result) + return +} + +func (c *replicaSets) UpdateStatus(replicaSet *extensions.ReplicaSet) (result *extensions.ReplicaSet, err error) { + result = &extensions.ReplicaSet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("replicasets"). + Name(replicaSet.Name). + SubResource("status"). + Body(replicaSet). + Do(). + Into(result) + return +} + +// Delete takes name of the replicaSet and deletes it. Returns an error if one occurs. +func (c *replicaSets) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("replicasets"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *replicaSets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("replicasets"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the replicaSet, and returns the corresponding replicaSet object, and an error if there is any. +func (c *replicaSets) Get(name string) (result *extensions.ReplicaSet, err error) { + result = &extensions.ReplicaSet{} + err = c.client.Get(). + Namespace(c.ns). + Resource("replicasets"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ReplicaSets that match those selectors. +func (c *replicaSets) List(opts api.ListOptions) (result *extensions.ReplicaSetList, err error) { + result = &extensions.ReplicaSetList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("replicasets"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested replicaSets. +func (c *replicaSets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("replicasets"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/scale.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/scale.go new file mode 100644 index 000000000..7e54bc347 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/scale.go @@ -0,0 +1,42 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +// ScalesGetter has a method to return a ScaleInterface. +// A group's client should implement this interface. +type ScalesGetter interface { + Scales(namespace string) ScaleInterface +} + +// ScaleInterface has methods to work with Scale resources. +type ScaleInterface interface { + ScaleExpansion +} + +// scales implements ScaleInterface +type scales struct { + client *ExtensionsClient + ns string +} + +// newScales returns a Scales +func newScales(c *ExtensionsClient, namespace string) *scales { + return &scales{ + client: c, + ns: namespace, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/scale_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/scale_expansion.go new file mode 100644 index 000000000..61a77f260 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/scale_expansion.go @@ -0,0 +1,65 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +// The ScaleExpansion interface allows manually adding extra methods to the ScaleInterface. +type ScaleExpansion interface { + Get(kind string, name string) (*extensions.Scale, error) + Update(kind string, scale *extensions.Scale) (*extensions.Scale, error) +} + +// Get takes the reference to scale subresource and returns the subresource or error, if one occurs. +func (c *scales) Get(kind string, name string) (result *extensions.Scale, err error) { + result = &extensions.Scale{} + + // TODO this method needs to take a proper unambiguous kind + fullyQualifiedKind := unversioned.GroupVersionKind{Kind: kind} + resource, _ := meta.KindToResource(fullyQualifiedKind) + + err = c.client.Get(). + Namespace(c.ns). + Resource(resource.Resource). + Name(name). + SubResource("scale"). + Do(). + Into(result) + return +} + +func (c *scales) Update(kind string, scale *extensions.Scale) (result *extensions.Scale, err error) { + result = &extensions.Scale{} + + // TODO this method needs to take a proper unambiguous kind + fullyQualifiedKind := unversioned.GroupVersionKind{Kind: kind} + resource, _ := meta.KindToResource(fullyQualifiedKind) + + err = c.client.Put(). + Namespace(scale.Namespace). + Resource(resource.Resource). + Name(scale.Name). + SubResource("scale"). + Body(scale). + Do(). + Into(result) + return +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/thirdpartyresource.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/thirdpartyresource.go new file mode 100644 index 000000000..0f1026fab --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned/thirdpartyresource.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + api "k8s.io/kubernetes/pkg/api" + extensions "k8s.io/kubernetes/pkg/apis/extensions" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ThirdPartyResourcesGetter has a method to return a ThirdPartyResourceInterface. +// A group's client should implement this interface. +type ThirdPartyResourcesGetter interface { + ThirdPartyResources(namespace string) ThirdPartyResourceInterface +} + +// ThirdPartyResourceInterface has methods to work with ThirdPartyResource resources. +type ThirdPartyResourceInterface interface { + Create(*extensions.ThirdPartyResource) (*extensions.ThirdPartyResource, error) + Update(*extensions.ThirdPartyResource) (*extensions.ThirdPartyResource, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*extensions.ThirdPartyResource, error) + List(opts api.ListOptions) (*extensions.ThirdPartyResourceList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ThirdPartyResourceExpansion +} + +// thirdPartyResources implements ThirdPartyResourceInterface +type thirdPartyResources struct { + client *ExtensionsClient + ns string +} + +// newThirdPartyResources returns a ThirdPartyResources +func newThirdPartyResources(c *ExtensionsClient, namespace string) *thirdPartyResources { + return &thirdPartyResources{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a thirdPartyResource and creates it. Returns the server's representation of the thirdPartyResource, and an error, if there is any. +func (c *thirdPartyResources) Create(thirdPartyResource *extensions.ThirdPartyResource) (result *extensions.ThirdPartyResource, err error) { + result = &extensions.ThirdPartyResource{} + err = c.client.Post(). + Namespace(c.ns). + Resource("thirdpartyresources"). + Body(thirdPartyResource). + Do(). + Into(result) + return +} + +// Update takes the representation of a thirdPartyResource and updates it. Returns the server's representation of the thirdPartyResource, and an error, if there is any. +func (c *thirdPartyResources) Update(thirdPartyResource *extensions.ThirdPartyResource) (result *extensions.ThirdPartyResource, err error) { + result = &extensions.ThirdPartyResource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("thirdpartyresources"). + Name(thirdPartyResource.Name). + Body(thirdPartyResource). + Do(). + Into(result) + return +} + +// Delete takes name of the thirdPartyResource and deletes it. Returns an error if one occurs. +func (c *thirdPartyResources) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("thirdpartyresources"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *thirdPartyResources) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("thirdpartyresources"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the thirdPartyResource, and returns the corresponding thirdPartyResource object, and an error if there is any. +func (c *thirdPartyResources) Get(name string) (result *extensions.ThirdPartyResource, err error) { + result = &extensions.ThirdPartyResource{} + err = c.client.Get(). + Namespace(c.ns). + Resource("thirdpartyresources"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ThirdPartyResources that match those selectors. +func (c *thirdPartyResources) List(opts api.ListOptions) (result *extensions.ThirdPartyResourceList, err error) { + result = &extensions.ThirdPartyResourceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("thirdpartyresources"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested thirdPartyResources. +func (c *thirdPartyResources) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("thirdpartyresources"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/clientset.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/clientset.go new file mode 100644 index 000000000..2fbae3028 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/clientset.go @@ -0,0 +1,95 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 release_1_2 + +import ( + "github.com/golang/glog" + v1core "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1" + v1beta1extensions "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1" + restclient "k8s.io/kubernetes/pkg/client/restclient" + discovery "k8s.io/kubernetes/pkg/client/typed/discovery" +) + +type Interface interface { + Discovery() discovery.DiscoveryInterface + Core() v1core.CoreInterface + Extensions() v1beta1extensions.ExtensionsInterface +} + +// Clientset contains the clients for groups. Each group has exactly one +// version included in a Clientset. +type Clientset struct { + *discovery.DiscoveryClient + *v1core.CoreClient + *v1beta1extensions.ExtensionsClient +} + +// Core retrieves the CoreClient +func (c *Clientset) Core() v1core.CoreInterface { + return c.CoreClient +} + +// Extensions retrieves the ExtensionsClient +func (c *Clientset) Extensions() v1beta1extensions.ExtensionsInterface { + return c.ExtensionsClient +} + +// Discovery retrieves the DiscoveryClient +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + return c.DiscoveryClient +} + +// NewForConfig creates a new Clientset for the given config. +func NewForConfig(c *restclient.Config) (*Clientset, error) { + var clientset Clientset + var err error + clientset.CoreClient, err = v1core.NewForConfig(c) + if err != nil { + return &clientset, err + } + clientset.ExtensionsClient, err = v1beta1extensions.NewForConfig(c) + if err != nil { + return &clientset, err + } + + clientset.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(c) + if err != nil { + glog.Errorf("failed to create the DiscoveryClient: %v", err) + } + return &clientset, err +} + +// NewForConfigOrDie creates a new Clientset for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *restclient.Config) *Clientset { + var clientset Clientset + clientset.CoreClient = v1core.NewForConfigOrDie(c) + clientset.ExtensionsClient = v1beta1extensions.NewForConfigOrDie(c) + + clientset.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) + return &clientset +} + +// New creates a new Clientset for the given RESTClient. +func New(c *restclient.RESTClient) *Clientset { + var clientset Clientset + clientset.CoreClient = v1core.New(c) + clientset.ExtensionsClient = v1beta1extensions.New(c) + + clientset.DiscoveryClient = discovery.NewDiscoveryClient(c) + return &clientset +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/doc.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/doc.go new file mode 100644 index 000000000..01f164f17 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This package is generated by client-gen with arguments: --clientset-name=release_1_2 --input=[api/v1,extensions/v1beta1] + +// This package has the automatically generated clientset. +package release_1_2 diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/fake/clientset_generated.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/fake/clientset_generated.go new file mode 100644 index 000000000..3299d95d3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/fake/clientset_generated.go @@ -0,0 +1,72 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apimachinery/registered" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2" + v1core "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1" + fakev1core "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake" + v1beta1extensions "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1" + fakev1beta1extensions "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/client/typed/discovery" + fakediscovery "k8s.io/kubernetes/pkg/client/typed/discovery/fake" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/watch" +) + +// Clientset returns a clientset that will respond with the provided objects +func NewSimpleClientset(objects ...runtime.Object) *Clientset { + o := core.NewObjects(api.Scheme, api.Codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + fakePtr := core.Fake{} + fakePtr.AddReactor("*", "*", core.ObjectReaction(o, registered.RESTMapper())) + + fakePtr.AddWatchReactor("*", core.DefaultWatchReactor(watch.NewFake(), nil)) + + return &Clientset{fakePtr} +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type Clientset struct { + core.Fake +} + +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + return &fakediscovery.FakeDiscovery{&c.Fake} +} + +var _ clientset.Interface = &Clientset{} + +// Core retrieves the CoreClient +func (c *Clientset) Core() v1core.CoreInterface { + return &fakev1core.FakeCore{&c.Fake} +} + +// Extensions retrieves the ExtensionsClient +func (c *Clientset) Extensions() v1beta1extensions.ExtensionsInterface { + return &fakev1beta1extensions.FakeExtensions{&c.Fake} +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/fake/doc.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/fake/doc.go new file mode 100644 index 000000000..d2e2dcd1d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This package is generated by client-gen with arguments: --clientset-name=release_1_2 --input=[api/v1,extensions/v1beta1] + +// This package has the automatically generated fake clientset. +package fake diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/import_known_versions.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/import_known_versions.go new file mode 100644 index 000000000..e69af18cd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/import_known_versions.go @@ -0,0 +1,37 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 release_1_2 + +// These imports are the API groups the client will support. +import ( + "fmt" + + _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/apimachinery/registered" + _ "k8s.io/kubernetes/pkg/apis/authorization/install" + _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" + _ "k8s.io/kubernetes/pkg/apis/batch/install" + _ "k8s.io/kubernetes/pkg/apis/componentconfig/install" + _ "k8s.io/kubernetes/pkg/apis/extensions/install" + _ "k8s.io/kubernetes/pkg/apis/metrics/install" +) + +func init() { + if missingVersions := registered.ValidateEnvRequestedVersions(); len(missingVersions) != 0 { + panic(fmt.Sprintf("KUBE_API_VERSIONS contains versions that are not installed: %q.", missingVersions)) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/componentstatus.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/componentstatus.go new file mode 100644 index 000000000..23363f530 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/componentstatus.go @@ -0,0 +1,127 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ComponentStatusesGetter has a method to return a ComponentStatusInterface. +// A group's client should implement this interface. +type ComponentStatusesGetter interface { + ComponentStatuses() ComponentStatusInterface +} + +// ComponentStatusInterface has methods to work with ComponentStatus resources. +type ComponentStatusInterface interface { + Create(*v1.ComponentStatus) (*v1.ComponentStatus, error) + Update(*v1.ComponentStatus) (*v1.ComponentStatus, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.ComponentStatus, error) + List(opts api.ListOptions) (*v1.ComponentStatusList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ComponentStatusExpansion +} + +// componentStatuses implements ComponentStatusInterface +type componentStatuses struct { + client *CoreClient +} + +// newComponentStatuses returns a ComponentStatuses +func newComponentStatuses(c *CoreClient) *componentStatuses { + return &componentStatuses{ + client: c, + } +} + +// Create takes the representation of a componentStatus and creates it. Returns the server's representation of the componentStatus, and an error, if there is any. +func (c *componentStatuses) Create(componentStatus *v1.ComponentStatus) (result *v1.ComponentStatus, err error) { + result = &v1.ComponentStatus{} + err = c.client.Post(). + Resource("componentstatuses"). + Body(componentStatus). + Do(). + Into(result) + return +} + +// Update takes the representation of a componentStatus and updates it. Returns the server's representation of the componentStatus, and an error, if there is any. +func (c *componentStatuses) Update(componentStatus *v1.ComponentStatus) (result *v1.ComponentStatus, err error) { + result = &v1.ComponentStatus{} + err = c.client.Put(). + Resource("componentstatuses"). + Name(componentStatus.Name). + Body(componentStatus). + Do(). + Into(result) + return +} + +// Delete takes name of the componentStatus and deletes it. Returns an error if one occurs. +func (c *componentStatuses) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("componentstatuses"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *componentStatuses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("componentstatuses"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the componentStatus, and returns the corresponding componentStatus object, and an error if there is any. +func (c *componentStatuses) Get(name string) (result *v1.ComponentStatus, err error) { + result = &v1.ComponentStatus{} + err = c.client.Get(). + Resource("componentstatuses"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ComponentStatuses that match those selectors. +func (c *componentStatuses) List(opts api.ListOptions) (result *v1.ComponentStatusList, err error) { + result = &v1.ComponentStatusList{} + err = c.client.Get(). + Resource("componentstatuses"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested componentStatuses. +func (c *componentStatuses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("componentstatuses"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/configmap.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/configmap.go new file mode 100644 index 000000000..4fbb31328 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/configmap.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ConfigMapsGetter has a method to return a ConfigMapInterface. +// A group's client should implement this interface. +type ConfigMapsGetter interface { + ConfigMaps(namespace string) ConfigMapInterface +} + +// ConfigMapInterface has methods to work with ConfigMap resources. +type ConfigMapInterface interface { + Create(*v1.ConfigMap) (*v1.ConfigMap, error) + Update(*v1.ConfigMap) (*v1.ConfigMap, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.ConfigMap, error) + List(opts api.ListOptions) (*v1.ConfigMapList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ConfigMapExpansion +} + +// configMaps implements ConfigMapInterface +type configMaps struct { + client *CoreClient + ns string +} + +// newConfigMaps returns a ConfigMaps +func newConfigMaps(c *CoreClient, namespace string) *configMaps { + return &configMaps{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a configMap and creates it. Returns the server's representation of the configMap, and an error, if there is any. +func (c *configMaps) Create(configMap *v1.ConfigMap) (result *v1.ConfigMap, err error) { + result = &v1.ConfigMap{} + err = c.client.Post(). + Namespace(c.ns). + Resource("configmaps"). + Body(configMap). + Do(). + Into(result) + return +} + +// Update takes the representation of a configMap and updates it. Returns the server's representation of the configMap, and an error, if there is any. +func (c *configMaps) Update(configMap *v1.ConfigMap) (result *v1.ConfigMap, err error) { + result = &v1.ConfigMap{} + err = c.client.Put(). + Namespace(c.ns). + Resource("configmaps"). + Name(configMap.Name). + Body(configMap). + Do(). + Into(result) + return +} + +// Delete takes name of the configMap and deletes it. Returns an error if one occurs. +func (c *configMaps) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("configmaps"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *configMaps) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("configmaps"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the configMap, and returns the corresponding configMap object, and an error if there is any. +func (c *configMaps) Get(name string) (result *v1.ConfigMap, err error) { + result = &v1.ConfigMap{} + err = c.client.Get(). + Namespace(c.ns). + Resource("configmaps"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ConfigMaps that match those selectors. +func (c *configMaps) List(opts api.ListOptions) (result *v1.ConfigMapList, err error) { + result = &v1.ConfigMapList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("configmaps"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested configMaps. +func (c *configMaps) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("configmaps"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/core_client.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/core_client.go new file mode 100644 index 000000000..886d556b5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/core_client.go @@ -0,0 +1,160 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + registered "k8s.io/kubernetes/pkg/apimachinery/registered" + restclient "k8s.io/kubernetes/pkg/client/restclient" +) + +type CoreInterface interface { + ComponentStatusesGetter + ConfigMapsGetter + EndpointsGetter + EventsGetter + LimitRangesGetter + NamespacesGetter + NodesGetter + PersistentVolumesGetter + PodsGetter + PodTemplatesGetter + ReplicationControllersGetter + ResourceQuotasGetter + SecretsGetter + ServicesGetter + ServiceAccountsGetter +} + +// CoreClient is used to interact with features provided by the Core group. +type CoreClient struct { + *restclient.RESTClient +} + +func (c *CoreClient) ComponentStatuses() ComponentStatusInterface { + return newComponentStatuses(c) +} + +func (c *CoreClient) ConfigMaps(namespace string) ConfigMapInterface { + return newConfigMaps(c, namespace) +} + +func (c *CoreClient) Endpoints(namespace string) EndpointsInterface { + return newEndpoints(c, namespace) +} + +func (c *CoreClient) Events(namespace string) EventInterface { + return newEvents(c, namespace) +} + +func (c *CoreClient) LimitRanges(namespace string) LimitRangeInterface { + return newLimitRanges(c, namespace) +} + +func (c *CoreClient) Namespaces() NamespaceInterface { + return newNamespaces(c) +} + +func (c *CoreClient) Nodes() NodeInterface { + return newNodes(c) +} + +func (c *CoreClient) PersistentVolumes() PersistentVolumeInterface { + return newPersistentVolumes(c) +} + +func (c *CoreClient) Pods(namespace string) PodInterface { + return newPods(c, namespace) +} + +func (c *CoreClient) PodTemplates(namespace string) PodTemplateInterface { + return newPodTemplates(c, namespace) +} + +func (c *CoreClient) ReplicationControllers(namespace string) ReplicationControllerInterface { + return newReplicationControllers(c, namespace) +} + +func (c *CoreClient) ResourceQuotas(namespace string) ResourceQuotaInterface { + return newResourceQuotas(c, namespace) +} + +func (c *CoreClient) Secrets(namespace string) SecretInterface { + return newSecrets(c, namespace) +} + +func (c *CoreClient) Services(namespace string) ServiceInterface { + return newServices(c, namespace) +} + +func (c *CoreClient) ServiceAccounts(namespace string) ServiceAccountInterface { + return newServiceAccounts(c, namespace) +} + +// NewForConfig creates a new CoreClient for the given config. +func NewForConfig(c *restclient.Config) (*CoreClient, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := restclient.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &CoreClient{client}, nil +} + +// NewForConfigOrDie creates a new CoreClient for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *restclient.Config) *CoreClient { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new CoreClient for the given RESTClient. +func New(c *restclient.RESTClient) *CoreClient { + return &CoreClient{c} +} + +func setConfigDefaults(config *restclient.Config) error { + // if core group is not registered, return an error + g, err := registered.Group("") + if err != nil { + return err + } + config.APIPath = "/api" + if config.UserAgent == "" { + config.UserAgent = restclient.DefaultKubernetesUserAgent() + } + // TODO: Unconditionally set the config.Version, until we fix the config. + //if config.Version == "" { + copyGroupVersion := g.GroupVersion + config.GroupVersion = ©GroupVersion + //} + + config.Codec = api.Codecs.LegacyCodec(*config.GroupVersion) + if config.QPS == 0 { + config.QPS = 5 + } + if config.Burst == 0 { + config.Burst = 10 + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/doc.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/doc.go new file mode 100644 index 000000000..30d096852 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This package is generated by client-gen with arguments: --clientset-name=release_1_2 --input=[api/v1,extensions/v1beta1] + +// This package has the automatically generated typed clients. +package v1 diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/endpoints.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/endpoints.go new file mode 100644 index 000000000..409b044c7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/endpoints.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// EndpointsGetter has a method to return a EndpointsInterface. +// A group's client should implement this interface. +type EndpointsGetter interface { + Endpoints(namespace string) EndpointsInterface +} + +// EndpointsInterface has methods to work with Endpoints resources. +type EndpointsInterface interface { + Create(*v1.Endpoints) (*v1.Endpoints, error) + Update(*v1.Endpoints) (*v1.Endpoints, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.Endpoints, error) + List(opts api.ListOptions) (*v1.EndpointsList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + EndpointsExpansion +} + +// endpoints implements EndpointsInterface +type endpoints struct { + client *CoreClient + ns string +} + +// newEndpoints returns a Endpoints +func newEndpoints(c *CoreClient, namespace string) *endpoints { + return &endpoints{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a endpoints and creates it. Returns the server's representation of the endpoints, and an error, if there is any. +func (c *endpoints) Create(endpoints *v1.Endpoints) (result *v1.Endpoints, err error) { + result = &v1.Endpoints{} + err = c.client.Post(). + Namespace(c.ns). + Resource("endpoints"). + Body(endpoints). + Do(). + Into(result) + return +} + +// Update takes the representation of a endpoints and updates it. Returns the server's representation of the endpoints, and an error, if there is any. +func (c *endpoints) Update(endpoints *v1.Endpoints) (result *v1.Endpoints, err error) { + result = &v1.Endpoints{} + err = c.client.Put(). + Namespace(c.ns). + Resource("endpoints"). + Name(endpoints.Name). + Body(endpoints). + Do(). + Into(result) + return +} + +// Delete takes name of the endpoints and deletes it. Returns an error if one occurs. +func (c *endpoints) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("endpoints"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *endpoints) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("endpoints"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the endpoints, and returns the corresponding endpoints object, and an error if there is any. +func (c *endpoints) Get(name string) (result *v1.Endpoints, err error) { + result = &v1.Endpoints{} + err = c.client.Get(). + Namespace(c.ns). + Resource("endpoints"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Endpoints that match those selectors. +func (c *endpoints) List(opts api.ListOptions) (result *v1.EndpointsList, err error) { + result = &v1.EndpointsList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("endpoints"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested endpoints. +func (c *endpoints) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("endpoints"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/event.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/event.go new file mode 100644 index 000000000..92266c98b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/event.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// EventsGetter has a method to return a EventInterface. +// A group's client should implement this interface. +type EventsGetter interface { + Events(namespace string) EventInterface +} + +// EventInterface has methods to work with Event resources. +type EventInterface interface { + Create(*v1.Event) (*v1.Event, error) + Update(*v1.Event) (*v1.Event, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.Event, error) + List(opts api.ListOptions) (*v1.EventList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + EventExpansion +} + +// events implements EventInterface +type events struct { + client *CoreClient + ns string +} + +// newEvents returns a Events +func newEvents(c *CoreClient, namespace string) *events { + return &events{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a event and creates it. Returns the server's representation of the event, and an error, if there is any. +func (c *events) Create(event *v1.Event) (result *v1.Event, err error) { + result = &v1.Event{} + err = c.client.Post(). + Namespace(c.ns). + Resource("events"). + Body(event). + Do(). + Into(result) + return +} + +// Update takes the representation of a event and updates it. Returns the server's representation of the event, and an error, if there is any. +func (c *events) Update(event *v1.Event) (result *v1.Event, err error) { + result = &v1.Event{} + err = c.client.Put(). + Namespace(c.ns). + Resource("events"). + Name(event.Name). + Body(event). + Do(). + Into(result) + return +} + +// Delete takes name of the event and deletes it. Returns an error if one occurs. +func (c *events) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("events"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *events) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("events"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the event, and returns the corresponding event object, and an error if there is any. +func (c *events) Get(name string) (result *v1.Event, err error) { + result = &v1.Event{} + err = c.client.Get(). + Namespace(c.ns). + Resource("events"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Events that match those selectors. +func (c *events) List(opts api.ListOptions) (result *v1.EventList, err error) { + result = &v1.EventList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("events"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested events. +func (c *events) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("events"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/event_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/event_expansion.go new file mode 100644 index 000000000..971c850c7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/event_expansion.go @@ -0,0 +1,158 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/runtime" +) + +// The EventExpansion interface allows manually adding extra methods to the EventInterface. +type EventExpansion interface { + // CreateWithEventNamespace is the same as a Create, except that it sends the request to the event.Namespace. + CreateWithEventNamespace(event *v1.Event) (*v1.Event, error) + // UpdateWithEventNamespace is the same as a Update, except that it sends the request to the event.Namespace. + UpdateWithEventNamespace(event *v1.Event) (*v1.Event, error) + Patch(event *v1.Event, data []byte) (*v1.Event, error) + // Search finds events about the specified object + Search(objOrRef runtime.Object) (*v1.EventList, error) + // Returns the appropriate field selector based on the API version being used to communicate with the server. + // The returned field selector can be used with List and Watch to filter desired events. + GetFieldSelector(involvedObjectName, involvedObjectNamespace, involvedObjectKind, involvedObjectUID *string) fields.Selector +} + +// CreateWithEventNamespace makes a new event. Returns the copy of the event the server returns, +// or an error. The namespace to create the event within is deduced from the +// event; it must either match this event client's namespace, or this event +// client must have been created with the "" namespace. +func (e *events) CreateWithEventNamespace(event *v1.Event) (*v1.Event, error) { + if e.ns != "" && event.Namespace != e.ns { + return nil, fmt.Errorf("can't create an event with namespace '%v' in namespace '%v'", event.Namespace, e.ns) + } + result := &v1.Event{} + err := e.client.Post(). + NamespaceIfScoped(event.Namespace, len(event.Namespace) > 0). + Resource("events"). + Body(event). + Do(). + Into(result) + return result, err +} + +// UpdateWithEventNamespace modifies an existing event. It returns the copy of the event that the server returns, +// or an error. The namespace and key to update the event within is deduced from the event. The +// namespace must either match this event client's namespace, or this event client must have been +// created with the "" namespace. Update also requires the ResourceVersion to be set in the event +// object. +func (e *events) UpdateWithEventNamespace(event *v1.Event) (*v1.Event, error) { + result := &v1.Event{} + err := e.client.Put(). + NamespaceIfScoped(event.Namespace, len(event.Namespace) > 0). + Resource("events"). + Name(event.Name). + Body(event). + Do(). + Into(result) + return result, err +} + +// Patch modifies an existing event. It returns the copy of the event that the server returns, or an +// error. The namespace and name of the target event is deduced from the incompleteEvent. The +// namespace must either match this event client's namespace, or this event client must have been +// created with the "" namespace. +func (e *events) Patch(incompleteEvent *v1.Event, data []byte) (*v1.Event, error) { + result := &v1.Event{} + err := e.client.Patch(api.StrategicMergePatchType). + NamespaceIfScoped(incompleteEvent.Namespace, len(incompleteEvent.Namespace) > 0). + Resource("events"). + Name(incompleteEvent.Name). + Body(data). + Do(). + Into(result) + return result, err +} + +// Search finds events about the specified object. The namespace of the +// object must match this event's client namespace unless the event client +// was made with the "" namespace. +func (e *events) Search(objOrRef runtime.Object) (*v1.EventList, error) { + ref, err := api.GetReference(objOrRef) + if err != nil { + return nil, err + } + if e.ns != "" && ref.Namespace != e.ns { + return nil, fmt.Errorf("won't be able to find any events of namespace '%v' in namespace '%v'", ref.Namespace, e.ns) + } + stringRefKind := string(ref.Kind) + var refKind *string + if stringRefKind != "" { + refKind = &stringRefKind + } + stringRefUID := string(ref.UID) + var refUID *string + if stringRefUID != "" { + refUID = &stringRefUID + } + fieldSelector := e.GetFieldSelector(&ref.Name, &ref.Namespace, refKind, refUID) + return e.List(api.ListOptions{FieldSelector: fieldSelector}) +} + +// Returns the appropriate field selector based on the API version being used to communicate with the server. +// The returned field selector can be used with List and Watch to filter desired events. +func (e *events) GetFieldSelector(involvedObjectName, involvedObjectNamespace, involvedObjectKind, involvedObjectUID *string) fields.Selector { + apiVersion := e.client.APIVersion().String() + field := fields.Set{} + if involvedObjectName != nil { + field[GetInvolvedObjectNameFieldLabel(apiVersion)] = *involvedObjectName + } + if involvedObjectNamespace != nil { + field["involvedObject.namespace"] = *involvedObjectNamespace + } + if involvedObjectKind != nil { + field["involvedObject.kind"] = *involvedObjectKind + } + if involvedObjectUID != nil { + field["involvedObject.uid"] = *involvedObjectUID + } + return field.AsSelector() +} + +// Returns the appropriate field label to use for name of the involved object as per the given API version. +func GetInvolvedObjectNameFieldLabel(version string) string { + return "involvedObject.name" +} + +// TODO: This is a temporary arrangement and will be removed once all clients are moved to use the clientset. +type EventSinkImpl struct { + Interface EventInterface +} + +func (e *EventSinkImpl) Create(event *v1.Event) (*v1.Event, error) { + return e.Interface.CreateWithEventNamespace(event) +} + +func (e *EventSinkImpl) Update(event *v1.Event) (*v1.Event, error) { + return e.Interface.UpdateWithEventNamespace(event) +} + +func (e *EventSinkImpl) Patch(event *v1.Event, data []byte) (*v1.Event, error) { + return e.Interface.Patch(event, data) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/doc.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/doc.go new file mode 100644 index 000000000..bafa0bfe4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This package is generated by client-gen with arguments: --clientset-name=release_1_2 --input=[api/v1,extensions/v1beta1] + +// Package fake has the automatically generated clients. +package fake diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_componentstatus.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_componentstatus.go new file mode 100644 index 000000000..05c820073 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_componentstatus.go @@ -0,0 +1,96 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeComponentStatuses implements ComponentStatusInterface +type FakeComponentStatuses struct { + Fake *FakeCore +} + +func (c *FakeComponentStatuses) Create(componentStatus *v1.ComponentStatus) (result *v1.ComponentStatus, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction("componentstatuses", componentStatus), &v1.ComponentStatus{}) + if obj == nil { + return nil, err + } + return obj.(*v1.ComponentStatus), err +} + +func (c *FakeComponentStatuses) Update(componentStatus *v1.ComponentStatus) (result *v1.ComponentStatus, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction("componentstatuses", componentStatus), &v1.ComponentStatus{}) + if obj == nil { + return nil, err + } + return obj.(*v1.ComponentStatus), err +} + +func (c *FakeComponentStatuses) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction("componentstatuses", name), &v1.ComponentStatus{}) + return err +} + +func (c *FakeComponentStatuses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction("componentstatuses", listOptions) + + _, err := c.Fake.Invokes(action, &v1.ComponentStatusList{}) + return err +} + +func (c *FakeComponentStatuses) Get(name string) (result *v1.ComponentStatus, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction("componentstatuses", name), &v1.ComponentStatus{}) + if obj == nil { + return nil, err + } + return obj.(*v1.ComponentStatus), err +} + +func (c *FakeComponentStatuses) List(opts api.ListOptions) (result *v1.ComponentStatusList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction("componentstatuses", opts), &v1.ComponentStatusList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.ComponentStatusList{} + for _, item := range obj.(*v1.ComponentStatusList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested componentStatuses. +func (c *FakeComponentStatuses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction("componentstatuses", opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_configmap.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_configmap.go new file mode 100644 index 000000000..79a9a20ec --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_configmap.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeConfigMaps implements ConfigMapInterface +type FakeConfigMaps struct { + Fake *FakeCore + ns string +} + +func (c *FakeConfigMaps) Create(configMap *v1.ConfigMap) (result *v1.ConfigMap, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("configmaps", c.ns, configMap), &v1.ConfigMap{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ConfigMap), err +} + +func (c *FakeConfigMaps) Update(configMap *v1.ConfigMap) (result *v1.ConfigMap, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("configmaps", c.ns, configMap), &v1.ConfigMap{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ConfigMap), err +} + +func (c *FakeConfigMaps) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("configmaps", c.ns, name), &v1.ConfigMap{}) + + return err +} + +func (c *FakeConfigMaps) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("configmaps", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.ConfigMapList{}) + return err +} + +func (c *FakeConfigMaps) Get(name string) (result *v1.ConfigMap, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("configmaps", c.ns, name), &v1.ConfigMap{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ConfigMap), err +} + +func (c *FakeConfigMaps) List(opts api.ListOptions) (result *v1.ConfigMapList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("configmaps", c.ns, opts), &v1.ConfigMapList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.ConfigMapList{} + for _, item := range obj.(*v1.ConfigMapList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested configMaps. +func (c *FakeConfigMaps) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("configmaps", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_core_client.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_core_client.go new file mode 100644 index 000000000..2f2c23454 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_core_client.go @@ -0,0 +1,86 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + v1 "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" +) + +type FakeCore struct { + *core.Fake +} + +func (c *FakeCore) ComponentStatuses() v1.ComponentStatusInterface { + return &FakeComponentStatuses{c} +} + +func (c *FakeCore) ConfigMaps(namespace string) v1.ConfigMapInterface { + return &FakeConfigMaps{c, namespace} +} + +func (c *FakeCore) Endpoints(namespace string) v1.EndpointsInterface { + return &FakeEndpoints{c, namespace} +} + +func (c *FakeCore) Events(namespace string) v1.EventInterface { + return &FakeEvents{c, namespace} +} + +func (c *FakeCore) LimitRanges(namespace string) v1.LimitRangeInterface { + return &FakeLimitRanges{c, namespace} +} + +func (c *FakeCore) Namespaces() v1.NamespaceInterface { + return &FakeNamespaces{c} +} + +func (c *FakeCore) Nodes() v1.NodeInterface { + return &FakeNodes{c} +} + +func (c *FakeCore) PersistentVolumes() v1.PersistentVolumeInterface { + return &FakePersistentVolumes{c} +} + +func (c *FakeCore) Pods(namespace string) v1.PodInterface { + return &FakePods{c, namespace} +} + +func (c *FakeCore) PodTemplates(namespace string) v1.PodTemplateInterface { + return &FakePodTemplates{c, namespace} +} + +func (c *FakeCore) ReplicationControllers(namespace string) v1.ReplicationControllerInterface { + return &FakeReplicationControllers{c, namespace} +} + +func (c *FakeCore) ResourceQuotas(namespace string) v1.ResourceQuotaInterface { + return &FakeResourceQuotas{c, namespace} +} + +func (c *FakeCore) Secrets(namespace string) v1.SecretInterface { + return &FakeSecrets{c, namespace} +} + +func (c *FakeCore) Services(namespace string) v1.ServiceInterface { + return &FakeServices{c, namespace} +} + +func (c *FakeCore) ServiceAccounts(namespace string) v1.ServiceAccountInterface { + return &FakeServiceAccounts{c, namespace} +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_endpoints.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_endpoints.go new file mode 100644 index 000000000..7bc9304a8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_endpoints.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeEndpoints implements EndpointsInterface +type FakeEndpoints struct { + Fake *FakeCore + ns string +} + +func (c *FakeEndpoints) Create(endpoints *v1.Endpoints) (result *v1.Endpoints, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("endpoints", c.ns, endpoints), &v1.Endpoints{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Endpoints), err +} + +func (c *FakeEndpoints) Update(endpoints *v1.Endpoints) (result *v1.Endpoints, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("endpoints", c.ns, endpoints), &v1.Endpoints{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Endpoints), err +} + +func (c *FakeEndpoints) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("endpoints", c.ns, name), &v1.Endpoints{}) + + return err +} + +func (c *FakeEndpoints) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("endpoints", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.EndpointsList{}) + return err +} + +func (c *FakeEndpoints) Get(name string) (result *v1.Endpoints, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("endpoints", c.ns, name), &v1.Endpoints{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Endpoints), err +} + +func (c *FakeEndpoints) List(opts api.ListOptions) (result *v1.EndpointsList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("endpoints", c.ns, opts), &v1.EndpointsList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.EndpointsList{} + for _, item := range obj.(*v1.EndpointsList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested endpoints. +func (c *FakeEndpoints) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("endpoints", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_event.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_event.go new file mode 100644 index 000000000..53a62c693 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_event.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeEvents implements EventInterface +type FakeEvents struct { + Fake *FakeCore + ns string +} + +func (c *FakeEvents) Create(event *v1.Event) (result *v1.Event, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("events", c.ns, event), &v1.Event{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Event), err +} + +func (c *FakeEvents) Update(event *v1.Event) (result *v1.Event, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("events", c.ns, event), &v1.Event{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Event), err +} + +func (c *FakeEvents) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("events", c.ns, name), &v1.Event{}) + + return err +} + +func (c *FakeEvents) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("events", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.EventList{}) + return err +} + +func (c *FakeEvents) Get(name string) (result *v1.Event, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("events", c.ns, name), &v1.Event{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Event), err +} + +func (c *FakeEvents) List(opts api.ListOptions) (result *v1.EventList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("events", c.ns, opts), &v1.EventList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.EventList{} + for _, item := range obj.(*v1.EventList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested events. +func (c *FakeEvents) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("events", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_event_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_event_expansion.go new file mode 100644 index 000000000..f6585b481 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_event_expansion.go @@ -0,0 +1,89 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/runtime" +) + +func (c *FakeEvents) CreateWithEventNamespace(event *v1.Event) (*v1.Event, error) { + action := core.NewRootCreateAction("events", event) + if c.ns != "" { + action = core.NewCreateAction("events", c.ns, event) + } + obj, err := c.Fake.Invokes(action, event) + if obj == nil { + return nil, err + } + + return obj.(*v1.Event), err +} + +// Update replaces an existing event. Returns the copy of the event the server returns, or an error. +func (c *FakeEvents) UpdateWithEventNamespace(event *v1.Event) (*v1.Event, error) { + action := core.NewRootUpdateAction("events", event) + if c.ns != "" { + action = core.NewUpdateAction("events", c.ns, event) + } + obj, err := c.Fake.Invokes(action, event) + if obj == nil { + return nil, err + } + + return obj.(*v1.Event), err +} + +// Patch patches an existing event. Returns the copy of the event the server returns, or an error. +func (c *FakeEvents) Patch(event *v1.Event, data []byte) (*v1.Event, error) { + action := core.NewRootPatchAction("events", event) + if c.ns != "" { + action = core.NewPatchAction("events", c.ns, event) + } + obj, err := c.Fake.Invokes(action, event) + if obj == nil { + return nil, err + } + + return obj.(*v1.Event), err +} + +// Search returns a list of events matching the specified object. +func (c *FakeEvents) Search(objOrRef runtime.Object) (*v1.EventList, error) { + action := core.NewRootListAction("events", api.ListOptions{}) + if c.ns != "" { + action = core.NewListAction("events", c.ns, api.ListOptions{}) + } + obj, err := c.Fake.Invokes(action, &v1.EventList{}) + if obj == nil { + return nil, err + } + + return obj.(*v1.EventList), err +} + +func (c *FakeEvents) GetFieldSelector(involvedObjectName, involvedObjectNamespace, involvedObjectKind, involvedObjectUID *string) fields.Selector { + action := core.GenericActionImpl{} + action.Verb = "get-field-selector" + action.Resource = "events" + + c.Fake.Invokes(action, nil) + return fields.Everything() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_limitrange.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_limitrange.go new file mode 100644 index 000000000..26a096ef2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_limitrange.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeLimitRanges implements LimitRangeInterface +type FakeLimitRanges struct { + Fake *FakeCore + ns string +} + +func (c *FakeLimitRanges) Create(limitRange *v1.LimitRange) (result *v1.LimitRange, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("limitranges", c.ns, limitRange), &v1.LimitRange{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.LimitRange), err +} + +func (c *FakeLimitRanges) Update(limitRange *v1.LimitRange) (result *v1.LimitRange, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("limitranges", c.ns, limitRange), &v1.LimitRange{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.LimitRange), err +} + +func (c *FakeLimitRanges) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("limitranges", c.ns, name), &v1.LimitRange{}) + + return err +} + +func (c *FakeLimitRanges) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("limitranges", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.LimitRangeList{}) + return err +} + +func (c *FakeLimitRanges) Get(name string) (result *v1.LimitRange, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("limitranges", c.ns, name), &v1.LimitRange{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.LimitRange), err +} + +func (c *FakeLimitRanges) List(opts api.ListOptions) (result *v1.LimitRangeList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("limitranges", c.ns, opts), &v1.LimitRangeList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.LimitRangeList{} + for _, item := range obj.(*v1.LimitRangeList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested limitRanges. +func (c *FakeLimitRanges) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("limitranges", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_namespace.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_namespace.go new file mode 100644 index 000000000..5c26cca47 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_namespace.go @@ -0,0 +1,105 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeNamespaces implements NamespaceInterface +type FakeNamespaces struct { + Fake *FakeCore +} + +func (c *FakeNamespaces) Create(namespace *v1.Namespace) (result *v1.Namespace, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction("namespaces", namespace), &v1.Namespace{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Namespace), err +} + +func (c *FakeNamespaces) Update(namespace *v1.Namespace) (result *v1.Namespace, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction("namespaces", namespace), &v1.Namespace{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Namespace), err +} + +func (c *FakeNamespaces) UpdateStatus(namespace *v1.Namespace) (*v1.Namespace, error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateSubresourceAction("namespaces", "status", namespace), &v1.Namespace{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Namespace), err +} + +func (c *FakeNamespaces) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction("namespaces", name), &v1.Namespace{}) + return err +} + +func (c *FakeNamespaces) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction("namespaces", listOptions) + + _, err := c.Fake.Invokes(action, &v1.NamespaceList{}) + return err +} + +func (c *FakeNamespaces) Get(name string) (result *v1.Namespace, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction("namespaces", name), &v1.Namespace{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Namespace), err +} + +func (c *FakeNamespaces) List(opts api.ListOptions) (result *v1.NamespaceList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction("namespaces", opts), &v1.NamespaceList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.NamespaceList{} + for _, item := range obj.(*v1.NamespaceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested namespaces. +func (c *FakeNamespaces) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction("namespaces", opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_namespace_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_namespace_expansion.go new file mode 100644 index 000000000..255cad05d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_namespace_expansion.go @@ -0,0 +1,37 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeNamespaces) Finalize(namespace *v1.Namespace) (*v1.Namespace, error) { + action := core.CreateActionImpl{} + action.Verb = "create" + action.Resource = "namespaces" + action.Subresource = "finalize" + action.Object = namespace + + obj, err := c.Fake.Invokes(action, namespace) + if obj == nil { + return nil, err + } + + return obj.(*v1.Namespace), err +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_node.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_node.go new file mode 100644 index 000000000..d4351794c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_node.go @@ -0,0 +1,105 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeNodes implements NodeInterface +type FakeNodes struct { + Fake *FakeCore +} + +func (c *FakeNodes) Create(node *v1.Node) (result *v1.Node, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction("nodes", node), &v1.Node{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Node), err +} + +func (c *FakeNodes) Update(node *v1.Node) (result *v1.Node, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction("nodes", node), &v1.Node{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Node), err +} + +func (c *FakeNodes) UpdateStatus(node *v1.Node) (*v1.Node, error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateSubresourceAction("nodes", "status", node), &v1.Node{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Node), err +} + +func (c *FakeNodes) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction("nodes", name), &v1.Node{}) + return err +} + +func (c *FakeNodes) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction("nodes", listOptions) + + _, err := c.Fake.Invokes(action, &v1.NodeList{}) + return err +} + +func (c *FakeNodes) Get(name string) (result *v1.Node, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction("nodes", name), &v1.Node{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Node), err +} + +func (c *FakeNodes) List(opts api.ListOptions) (result *v1.NodeList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction("nodes", opts), &v1.NodeList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.NodeList{} + for _, item := range obj.(*v1.NodeList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested nodes. +func (c *FakeNodes) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction("nodes", opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_persistentvolume.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_persistentvolume.go new file mode 100644 index 000000000..579c589e8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_persistentvolume.go @@ -0,0 +1,105 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakePersistentVolumes implements PersistentVolumeInterface +type FakePersistentVolumes struct { + Fake *FakeCore +} + +func (c *FakePersistentVolumes) Create(persistentVolume *v1.PersistentVolume) (result *v1.PersistentVolume, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction("persistentvolumes", persistentVolume), &v1.PersistentVolume{}) + if obj == nil { + return nil, err + } + return obj.(*v1.PersistentVolume), err +} + +func (c *FakePersistentVolumes) Update(persistentVolume *v1.PersistentVolume) (result *v1.PersistentVolume, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction("persistentvolumes", persistentVolume), &v1.PersistentVolume{}) + if obj == nil { + return nil, err + } + return obj.(*v1.PersistentVolume), err +} + +func (c *FakePersistentVolumes) UpdateStatus(persistentVolume *v1.PersistentVolume) (*v1.PersistentVolume, error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateSubresourceAction("persistentvolumes", "status", persistentVolume), &v1.PersistentVolume{}) + if obj == nil { + return nil, err + } + return obj.(*v1.PersistentVolume), err +} + +func (c *FakePersistentVolumes) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction("persistentvolumes", name), &v1.PersistentVolume{}) + return err +} + +func (c *FakePersistentVolumes) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction("persistentvolumes", listOptions) + + _, err := c.Fake.Invokes(action, &v1.PersistentVolumeList{}) + return err +} + +func (c *FakePersistentVolumes) Get(name string) (result *v1.PersistentVolume, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction("persistentvolumes", name), &v1.PersistentVolume{}) + if obj == nil { + return nil, err + } + return obj.(*v1.PersistentVolume), err +} + +func (c *FakePersistentVolumes) List(opts api.ListOptions) (result *v1.PersistentVolumeList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction("persistentvolumes", opts), &v1.PersistentVolumeList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.PersistentVolumeList{} + for _, item := range obj.(*v1.PersistentVolumeList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested persistentVolumes. +func (c *FakePersistentVolumes) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction("persistentvolumes", opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_pod.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_pod.go new file mode 100644 index 000000000..a5fab0d81 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_pod.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakePods implements PodInterface +type FakePods struct { + Fake *FakeCore + ns string +} + +func (c *FakePods) Create(pod *v1.Pod) (result *v1.Pod, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("pods", c.ns, pod), &v1.Pod{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Pod), err +} + +func (c *FakePods) Update(pod *v1.Pod) (result *v1.Pod, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("pods", c.ns, pod), &v1.Pod{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Pod), err +} + +func (c *FakePods) UpdateStatus(pod *v1.Pod) (*v1.Pod, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("pods", "status", c.ns, pod), &v1.Pod{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Pod), err +} + +func (c *FakePods) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("pods", c.ns, name), &v1.Pod{}) + + return err +} + +func (c *FakePods) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("pods", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.PodList{}) + return err +} + +func (c *FakePods) Get(name string) (result *v1.Pod, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("pods", c.ns, name), &v1.Pod{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Pod), err +} + +func (c *FakePods) List(opts api.ListOptions) (result *v1.PodList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("pods", c.ns, opts), &v1.PodList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.PodList{} + for _, item := range obj.(*v1.PodList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested pods. +func (c *FakePods) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("pods", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_pod_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_pod_expansion.go new file mode 100644 index 000000000..c4ad84c6c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_pod_expansion.go @@ -0,0 +1,46 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakePods) Bind(binding *v1.Binding) error { + action := core.CreateActionImpl{} + action.Verb = "create" + action.Resource = "pods" + action.Subresource = "bindings" + action.Object = binding + + _, err := c.Fake.Invokes(action, binding) + return err +} + +func (c *FakePods) GetLogs(name string, opts *v1.PodLogOptions) *restclient.Request { + action := core.GenericActionImpl{} + action.Verb = "get" + action.Namespace = c.ns + action.Resource = "pod" + action.Subresource = "logs" + action.Value = opts + + _, _ = c.Fake.Invokes(action, &v1.Pod{}) + return &restclient.Request{} +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_podtemplate.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_podtemplate.go new file mode 100644 index 000000000..b9ac44952 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_podtemplate.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakePodTemplates implements PodTemplateInterface +type FakePodTemplates struct { + Fake *FakeCore + ns string +} + +func (c *FakePodTemplates) Create(podTemplate *v1.PodTemplate) (result *v1.PodTemplate, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("podtemplates", c.ns, podTemplate), &v1.PodTemplate{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.PodTemplate), err +} + +func (c *FakePodTemplates) Update(podTemplate *v1.PodTemplate) (result *v1.PodTemplate, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("podtemplates", c.ns, podTemplate), &v1.PodTemplate{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.PodTemplate), err +} + +func (c *FakePodTemplates) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("podtemplates", c.ns, name), &v1.PodTemplate{}) + + return err +} + +func (c *FakePodTemplates) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("podtemplates", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.PodTemplateList{}) + return err +} + +func (c *FakePodTemplates) Get(name string) (result *v1.PodTemplate, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("podtemplates", c.ns, name), &v1.PodTemplate{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.PodTemplate), err +} + +func (c *FakePodTemplates) List(opts api.ListOptions) (result *v1.PodTemplateList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("podtemplates", c.ns, opts), &v1.PodTemplateList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.PodTemplateList{} + for _, item := range obj.(*v1.PodTemplateList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested podTemplates. +func (c *FakePodTemplates) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("podtemplates", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_replicationcontroller.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_replicationcontroller.go new file mode 100644 index 000000000..c390efb60 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_replicationcontroller.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeReplicationControllers implements ReplicationControllerInterface +type FakeReplicationControllers struct { + Fake *FakeCore + ns string +} + +func (c *FakeReplicationControllers) Create(replicationController *v1.ReplicationController) (result *v1.ReplicationController, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("replicationcontrollers", c.ns, replicationController), &v1.ReplicationController{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ReplicationController), err +} + +func (c *FakeReplicationControllers) Update(replicationController *v1.ReplicationController) (result *v1.ReplicationController, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("replicationcontrollers", c.ns, replicationController), &v1.ReplicationController{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ReplicationController), err +} + +func (c *FakeReplicationControllers) UpdateStatus(replicationController *v1.ReplicationController) (*v1.ReplicationController, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("replicationcontrollers", "status", c.ns, replicationController), &v1.ReplicationController{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ReplicationController), err +} + +func (c *FakeReplicationControllers) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("replicationcontrollers", c.ns, name), &v1.ReplicationController{}) + + return err +} + +func (c *FakeReplicationControllers) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("replicationcontrollers", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.ReplicationControllerList{}) + return err +} + +func (c *FakeReplicationControllers) Get(name string) (result *v1.ReplicationController, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("replicationcontrollers", c.ns, name), &v1.ReplicationController{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ReplicationController), err +} + +func (c *FakeReplicationControllers) List(opts api.ListOptions) (result *v1.ReplicationControllerList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("replicationcontrollers", c.ns, opts), &v1.ReplicationControllerList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.ReplicationControllerList{} + for _, item := range obj.(*v1.ReplicationControllerList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested replicationControllers. +func (c *FakeReplicationControllers) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("replicationcontrollers", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_resourcequota.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_resourcequota.go new file mode 100644 index 000000000..5adf58187 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_resourcequota.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeResourceQuotas implements ResourceQuotaInterface +type FakeResourceQuotas struct { + Fake *FakeCore + ns string +} + +func (c *FakeResourceQuotas) Create(resourceQuota *v1.ResourceQuota) (result *v1.ResourceQuota, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("resourcequotas", c.ns, resourceQuota), &v1.ResourceQuota{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ResourceQuota), err +} + +func (c *FakeResourceQuotas) Update(resourceQuota *v1.ResourceQuota) (result *v1.ResourceQuota, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("resourcequotas", c.ns, resourceQuota), &v1.ResourceQuota{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ResourceQuota), err +} + +func (c *FakeResourceQuotas) UpdateStatus(resourceQuota *v1.ResourceQuota) (*v1.ResourceQuota, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("resourcequotas", "status", c.ns, resourceQuota), &v1.ResourceQuota{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ResourceQuota), err +} + +func (c *FakeResourceQuotas) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("resourcequotas", c.ns, name), &v1.ResourceQuota{}) + + return err +} + +func (c *FakeResourceQuotas) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("resourcequotas", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.ResourceQuotaList{}) + return err +} + +func (c *FakeResourceQuotas) Get(name string) (result *v1.ResourceQuota, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("resourcequotas", c.ns, name), &v1.ResourceQuota{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ResourceQuota), err +} + +func (c *FakeResourceQuotas) List(opts api.ListOptions) (result *v1.ResourceQuotaList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("resourcequotas", c.ns, opts), &v1.ResourceQuotaList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.ResourceQuotaList{} + for _, item := range obj.(*v1.ResourceQuotaList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested resourceQuotas. +func (c *FakeResourceQuotas) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("resourcequotas", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_secret.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_secret.go new file mode 100644 index 000000000..989bdf8a5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_secret.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeSecrets implements SecretInterface +type FakeSecrets struct { + Fake *FakeCore + ns string +} + +func (c *FakeSecrets) Create(secret *v1.Secret) (result *v1.Secret, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("secrets", c.ns, secret), &v1.Secret{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Secret), err +} + +func (c *FakeSecrets) Update(secret *v1.Secret) (result *v1.Secret, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("secrets", c.ns, secret), &v1.Secret{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Secret), err +} + +func (c *FakeSecrets) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("secrets", c.ns, name), &v1.Secret{}) + + return err +} + +func (c *FakeSecrets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("secrets", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.SecretList{}) + return err +} + +func (c *FakeSecrets) Get(name string) (result *v1.Secret, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("secrets", c.ns, name), &v1.Secret{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Secret), err +} + +func (c *FakeSecrets) List(opts api.ListOptions) (result *v1.SecretList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("secrets", c.ns, opts), &v1.SecretList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.SecretList{} + for _, item := range obj.(*v1.SecretList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested secrets. +func (c *FakeSecrets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("secrets", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_service.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_service.go new file mode 100644 index 000000000..303dff9c6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_service.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeServices implements ServiceInterface +type FakeServices struct { + Fake *FakeCore + ns string +} + +func (c *FakeServices) Create(service *v1.Service) (result *v1.Service, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("services", c.ns, service), &v1.Service{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Service), err +} + +func (c *FakeServices) Update(service *v1.Service) (result *v1.Service, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("services", c.ns, service), &v1.Service{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Service), err +} + +func (c *FakeServices) UpdateStatus(service *v1.Service) (*v1.Service, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("services", "status", c.ns, service), &v1.Service{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Service), err +} + +func (c *FakeServices) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("services", c.ns, name), &v1.Service{}) + + return err +} + +func (c *FakeServices) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("services", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.ServiceList{}) + return err +} + +func (c *FakeServices) Get(name string) (result *v1.Service, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("services", c.ns, name), &v1.Service{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Service), err +} + +func (c *FakeServices) List(opts api.ListOptions) (result *v1.ServiceList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("services", c.ns, opts), &v1.ServiceList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.ServiceList{} + for _, item := range obj.(*v1.ServiceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested services. +func (c *FakeServices) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("services", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_service_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_service_expansion.go new file mode 100644 index 000000000..18f1b7803 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_service_expansion.go @@ -0,0 +1,26 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeServices) ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper { + return c.Fake.InvokesProxy(core.NewProxyGetAction("services", c.ns, scheme, name, port, path, params)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_serviceaccount.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_serviceaccount.go new file mode 100644 index 000000000..b08488f64 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/fake/fake_serviceaccount.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeServiceAccounts implements ServiceAccountInterface +type FakeServiceAccounts struct { + Fake *FakeCore + ns string +} + +func (c *FakeServiceAccounts) Create(serviceAccount *v1.ServiceAccount) (result *v1.ServiceAccount, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("serviceaccounts", c.ns, serviceAccount), &v1.ServiceAccount{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ServiceAccount), err +} + +func (c *FakeServiceAccounts) Update(serviceAccount *v1.ServiceAccount) (result *v1.ServiceAccount, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("serviceaccounts", c.ns, serviceAccount), &v1.ServiceAccount{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ServiceAccount), err +} + +func (c *FakeServiceAccounts) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("serviceaccounts", c.ns, name), &v1.ServiceAccount{}) + + return err +} + +func (c *FakeServiceAccounts) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("serviceaccounts", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.ServiceAccountList{}) + return err +} + +func (c *FakeServiceAccounts) Get(name string) (result *v1.ServiceAccount, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("serviceaccounts", c.ns, name), &v1.ServiceAccount{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ServiceAccount), err +} + +func (c *FakeServiceAccounts) List(opts api.ListOptions) (result *v1.ServiceAccountList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("serviceaccounts", c.ns, opts), &v1.ServiceAccountList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.ServiceAccountList{} + for _, item := range obj.(*v1.ServiceAccountList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested serviceAccounts. +func (c *FakeServiceAccounts) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("serviceaccounts", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/generated_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/generated_expansion.go new file mode 100644 index 000000000..9974ef5c6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/generated_expansion.go @@ -0,0 +1,41 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +type ComponentStatusExpansion interface{} + +type EndpointsExpansion interface{} + +type LimitRangeExpansion interface{} + +type NodeExpansion interface{} + +type PersistentVolumeExpansion interface{} + +type PersistentVolumeClaimExpansion interface{} + +type PodTemplateExpansion interface{} + +type ReplicationControllerExpansion interface{} + +type ResourceQuotaExpansion interface{} + +type SecretExpansion interface{} + +type ServiceAccountExpansion interface{} + +type ConfigMapExpansion interface{} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/limitrange.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/limitrange.go new file mode 100644 index 000000000..a44c61fa2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/limitrange.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// LimitRangesGetter has a method to return a LimitRangeInterface. +// A group's client should implement this interface. +type LimitRangesGetter interface { + LimitRanges(namespace string) LimitRangeInterface +} + +// LimitRangeInterface has methods to work with LimitRange resources. +type LimitRangeInterface interface { + Create(*v1.LimitRange) (*v1.LimitRange, error) + Update(*v1.LimitRange) (*v1.LimitRange, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.LimitRange, error) + List(opts api.ListOptions) (*v1.LimitRangeList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + LimitRangeExpansion +} + +// limitRanges implements LimitRangeInterface +type limitRanges struct { + client *CoreClient + ns string +} + +// newLimitRanges returns a LimitRanges +func newLimitRanges(c *CoreClient, namespace string) *limitRanges { + return &limitRanges{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a limitRange and creates it. Returns the server's representation of the limitRange, and an error, if there is any. +func (c *limitRanges) Create(limitRange *v1.LimitRange) (result *v1.LimitRange, err error) { + result = &v1.LimitRange{} + err = c.client.Post(). + Namespace(c.ns). + Resource("limitranges"). + Body(limitRange). + Do(). + Into(result) + return +} + +// Update takes the representation of a limitRange and updates it. Returns the server's representation of the limitRange, and an error, if there is any. +func (c *limitRanges) Update(limitRange *v1.LimitRange) (result *v1.LimitRange, err error) { + result = &v1.LimitRange{} + err = c.client.Put(). + Namespace(c.ns). + Resource("limitranges"). + Name(limitRange.Name). + Body(limitRange). + Do(). + Into(result) + return +} + +// Delete takes name of the limitRange and deletes it. Returns an error if one occurs. +func (c *limitRanges) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("limitranges"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *limitRanges) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("limitranges"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the limitRange, and returns the corresponding limitRange object, and an error if there is any. +func (c *limitRanges) Get(name string) (result *v1.LimitRange, err error) { + result = &v1.LimitRange{} + err = c.client.Get(). + Namespace(c.ns). + Resource("limitranges"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of LimitRanges that match those selectors. +func (c *limitRanges) List(opts api.ListOptions) (result *v1.LimitRangeList, err error) { + result = &v1.LimitRangeList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("limitranges"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested limitRanges. +func (c *limitRanges) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("limitranges"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/namespace.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/namespace.go new file mode 100644 index 000000000..3d2cff144 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/namespace.go @@ -0,0 +1,140 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// NamespacesGetter has a method to return a NamespaceInterface. +// A group's client should implement this interface. +type NamespacesGetter interface { + Namespaces() NamespaceInterface +} + +// NamespaceInterface has methods to work with Namespace resources. +type NamespaceInterface interface { + Create(*v1.Namespace) (*v1.Namespace, error) + Update(*v1.Namespace) (*v1.Namespace, error) + UpdateStatus(*v1.Namespace) (*v1.Namespace, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.Namespace, error) + List(opts api.ListOptions) (*v1.NamespaceList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + NamespaceExpansion +} + +// namespaces implements NamespaceInterface +type namespaces struct { + client *CoreClient +} + +// newNamespaces returns a Namespaces +func newNamespaces(c *CoreClient) *namespaces { + return &namespaces{ + client: c, + } +} + +// Create takes the representation of a namespace and creates it. Returns the server's representation of the namespace, and an error, if there is any. +func (c *namespaces) Create(namespace *v1.Namespace) (result *v1.Namespace, err error) { + result = &v1.Namespace{} + err = c.client.Post(). + Resource("namespaces"). + Body(namespace). + Do(). + Into(result) + return +} + +// Update takes the representation of a namespace and updates it. Returns the server's representation of the namespace, and an error, if there is any. +func (c *namespaces) Update(namespace *v1.Namespace) (result *v1.Namespace, err error) { + result = &v1.Namespace{} + err = c.client.Put(). + Resource("namespaces"). + Name(namespace.Name). + Body(namespace). + Do(). + Into(result) + return +} + +func (c *namespaces) UpdateStatus(namespace *v1.Namespace) (result *v1.Namespace, err error) { + result = &v1.Namespace{} + err = c.client.Put(). + Resource("namespaces"). + Name(namespace.Name). + SubResource("status"). + Body(namespace). + Do(). + Into(result) + return +} + +// Delete takes name of the namespace and deletes it. Returns an error if one occurs. +func (c *namespaces) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("namespaces"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *namespaces) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("namespaces"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the namespace, and returns the corresponding namespace object, and an error if there is any. +func (c *namespaces) Get(name string) (result *v1.Namespace, err error) { + result = &v1.Namespace{} + err = c.client.Get(). + Resource("namespaces"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Namespaces that match those selectors. +func (c *namespaces) List(opts api.ListOptions) (result *v1.NamespaceList, err error) { + result = &v1.NamespaceList{} + err = c.client.Get(). + Resource("namespaces"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested namespaces. +func (c *namespaces) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("namespaces"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/namespace_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/namespace_expansion.go new file mode 100644 index 000000000..7b5cf683d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/namespace_expansion.go @@ -0,0 +1,31 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import "k8s.io/kubernetes/pkg/api/v1" + +// The NamespaceExpansion interface allows manually adding extra methods to the NamespaceInterface. +type NamespaceExpansion interface { + Finalize(item *v1.Namespace) (*v1.Namespace, error) +} + +// Finalize takes the representation of a namespace to update. Returns the server's representation of the namespace, and an error, if it occurs. +func (c *namespaces) Finalize(namespace *v1.Namespace) (result *v1.Namespace, err error) { + result = &v1.Namespace{} + err = c.client.Put().Resource("namespaces").Name(namespace.Name).SubResource("finalize").Body(namespace).Do().Into(result) + return +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/node.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/node.go new file mode 100644 index 000000000..464eb8d6d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/node.go @@ -0,0 +1,140 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// NodesGetter has a method to return a NodeInterface. +// A group's client should implement this interface. +type NodesGetter interface { + Nodes() NodeInterface +} + +// NodeInterface has methods to work with Node resources. +type NodeInterface interface { + Create(*v1.Node) (*v1.Node, error) + Update(*v1.Node) (*v1.Node, error) + UpdateStatus(*v1.Node) (*v1.Node, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.Node, error) + List(opts api.ListOptions) (*v1.NodeList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + NodeExpansion +} + +// nodes implements NodeInterface +type nodes struct { + client *CoreClient +} + +// newNodes returns a Nodes +func newNodes(c *CoreClient) *nodes { + return &nodes{ + client: c, + } +} + +// Create takes the representation of a node and creates it. Returns the server's representation of the node, and an error, if there is any. +func (c *nodes) Create(node *v1.Node) (result *v1.Node, err error) { + result = &v1.Node{} + err = c.client.Post(). + Resource("nodes"). + Body(node). + Do(). + Into(result) + return +} + +// Update takes the representation of a node and updates it. Returns the server's representation of the node, and an error, if there is any. +func (c *nodes) Update(node *v1.Node) (result *v1.Node, err error) { + result = &v1.Node{} + err = c.client.Put(). + Resource("nodes"). + Name(node.Name). + Body(node). + Do(). + Into(result) + return +} + +func (c *nodes) UpdateStatus(node *v1.Node) (result *v1.Node, err error) { + result = &v1.Node{} + err = c.client.Put(). + Resource("nodes"). + Name(node.Name). + SubResource("status"). + Body(node). + Do(). + Into(result) + return +} + +// Delete takes name of the node and deletes it. Returns an error if one occurs. +func (c *nodes) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("nodes"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *nodes) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("nodes"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the node, and returns the corresponding node object, and an error if there is any. +func (c *nodes) Get(name string) (result *v1.Node, err error) { + result = &v1.Node{} + err = c.client.Get(). + Resource("nodes"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Nodes that match those selectors. +func (c *nodes) List(opts api.ListOptions) (result *v1.NodeList, err error) { + result = &v1.NodeList{} + err = c.client.Get(). + Resource("nodes"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested nodes. +func (c *nodes) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("nodes"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/persistentvolume.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/persistentvolume.go new file mode 100644 index 000000000..85ddf060e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/persistentvolume.go @@ -0,0 +1,140 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// PersistentVolumesGetter has a method to return a PersistentVolumeInterface. +// A group's client should implement this interface. +type PersistentVolumesGetter interface { + PersistentVolumes() PersistentVolumeInterface +} + +// PersistentVolumeInterface has methods to work with PersistentVolume resources. +type PersistentVolumeInterface interface { + Create(*v1.PersistentVolume) (*v1.PersistentVolume, error) + Update(*v1.PersistentVolume) (*v1.PersistentVolume, error) + UpdateStatus(*v1.PersistentVolume) (*v1.PersistentVolume, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.PersistentVolume, error) + List(opts api.ListOptions) (*v1.PersistentVolumeList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + PersistentVolumeExpansion +} + +// persistentVolumes implements PersistentVolumeInterface +type persistentVolumes struct { + client *CoreClient +} + +// newPersistentVolumes returns a PersistentVolumes +func newPersistentVolumes(c *CoreClient) *persistentVolumes { + return &persistentVolumes{ + client: c, + } +} + +// Create takes the representation of a persistentVolume and creates it. Returns the server's representation of the persistentVolume, and an error, if there is any. +func (c *persistentVolumes) Create(persistentVolume *v1.PersistentVolume) (result *v1.PersistentVolume, err error) { + result = &v1.PersistentVolume{} + err = c.client.Post(). + Resource("persistentvolumes"). + Body(persistentVolume). + Do(). + Into(result) + return +} + +// Update takes the representation of a persistentVolume and updates it. Returns the server's representation of the persistentVolume, and an error, if there is any. +func (c *persistentVolumes) Update(persistentVolume *v1.PersistentVolume) (result *v1.PersistentVolume, err error) { + result = &v1.PersistentVolume{} + err = c.client.Put(). + Resource("persistentvolumes"). + Name(persistentVolume.Name). + Body(persistentVolume). + Do(). + Into(result) + return +} + +func (c *persistentVolumes) UpdateStatus(persistentVolume *v1.PersistentVolume) (result *v1.PersistentVolume, err error) { + result = &v1.PersistentVolume{} + err = c.client.Put(). + Resource("persistentvolumes"). + Name(persistentVolume.Name). + SubResource("status"). + Body(persistentVolume). + Do(). + Into(result) + return +} + +// Delete takes name of the persistentVolume and deletes it. Returns an error if one occurs. +func (c *persistentVolumes) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("persistentvolumes"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *persistentVolumes) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("persistentvolumes"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the persistentVolume, and returns the corresponding persistentVolume object, and an error if there is any. +func (c *persistentVolumes) Get(name string) (result *v1.PersistentVolume, err error) { + result = &v1.PersistentVolume{} + err = c.client.Get(). + Resource("persistentvolumes"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of PersistentVolumes that match those selectors. +func (c *persistentVolumes) List(opts api.ListOptions) (result *v1.PersistentVolumeList, err error) { + result = &v1.PersistentVolumeList{} + err = c.client.Get(). + Resource("persistentvolumes"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested persistentVolumes. +func (c *persistentVolumes) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("persistentvolumes"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/pod.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/pod.go new file mode 100644 index 000000000..d2ed5faaa --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/pod.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// PodsGetter has a method to return a PodInterface. +// A group's client should implement this interface. +type PodsGetter interface { + Pods(namespace string) PodInterface +} + +// PodInterface has methods to work with Pod resources. +type PodInterface interface { + Create(*v1.Pod) (*v1.Pod, error) + Update(*v1.Pod) (*v1.Pod, error) + UpdateStatus(*v1.Pod) (*v1.Pod, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.Pod, error) + List(opts api.ListOptions) (*v1.PodList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + PodExpansion +} + +// pods implements PodInterface +type pods struct { + client *CoreClient + ns string +} + +// newPods returns a Pods +func newPods(c *CoreClient, namespace string) *pods { + return &pods{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a pod and creates it. Returns the server's representation of the pod, and an error, if there is any. +func (c *pods) Create(pod *v1.Pod) (result *v1.Pod, err error) { + result = &v1.Pod{} + err = c.client.Post(). + Namespace(c.ns). + Resource("pods"). + Body(pod). + Do(). + Into(result) + return +} + +// Update takes the representation of a pod and updates it. Returns the server's representation of the pod, and an error, if there is any. +func (c *pods) Update(pod *v1.Pod) (result *v1.Pod, err error) { + result = &v1.Pod{} + err = c.client.Put(). + Namespace(c.ns). + Resource("pods"). + Name(pod.Name). + Body(pod). + Do(). + Into(result) + return +} + +func (c *pods) UpdateStatus(pod *v1.Pod) (result *v1.Pod, err error) { + result = &v1.Pod{} + err = c.client.Put(). + Namespace(c.ns). + Resource("pods"). + Name(pod.Name). + SubResource("status"). + Body(pod). + Do(). + Into(result) + return +} + +// Delete takes name of the pod and deletes it. Returns an error if one occurs. +func (c *pods) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("pods"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *pods) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("pods"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the pod, and returns the corresponding pod object, and an error if there is any. +func (c *pods) Get(name string) (result *v1.Pod, err error) { + result = &v1.Pod{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pods"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Pods that match those selectors. +func (c *pods) List(opts api.ListOptions) (result *v1.PodList, err error) { + result = &v1.PodList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pods"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested pods. +func (c *pods) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("pods"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/pod_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/pod_expansion.go new file mode 100644 index 000000000..f061b5d92 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/pod_expansion.go @@ -0,0 +1,39 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/client/restclient" +) + +// The PodExpansion interface allows manually adding extra methods to the PodInterface. +type PodExpansion interface { + Bind(binding *v1.Binding) error + GetLogs(name string, opts *v1.PodLogOptions) *restclient.Request +} + +// Bind applies the provided binding to the named pod in the current namespace (binding.Namespace is ignored). +func (c *pods) Bind(binding *v1.Binding) error { + return c.client.Post().Namespace(c.ns).Resource("pods").Name(binding.Name).SubResource("binding").Body(binding).Do().Error() +} + +// Get constructs a request for getting the logs for a pod +func (c *pods) GetLogs(name string, opts *v1.PodLogOptions) *restclient.Request { + return c.client.Get().Namespace(c.ns).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, api.ParameterCodec) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/podtemplate.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/podtemplate.go new file mode 100644 index 000000000..1b95106d1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/podtemplate.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// PodTemplatesGetter has a method to return a PodTemplateInterface. +// A group's client should implement this interface. +type PodTemplatesGetter interface { + PodTemplates(namespace string) PodTemplateInterface +} + +// PodTemplateInterface has methods to work with PodTemplate resources. +type PodTemplateInterface interface { + Create(*v1.PodTemplate) (*v1.PodTemplate, error) + Update(*v1.PodTemplate) (*v1.PodTemplate, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.PodTemplate, error) + List(opts api.ListOptions) (*v1.PodTemplateList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + PodTemplateExpansion +} + +// podTemplates implements PodTemplateInterface +type podTemplates struct { + client *CoreClient + ns string +} + +// newPodTemplates returns a PodTemplates +func newPodTemplates(c *CoreClient, namespace string) *podTemplates { + return &podTemplates{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a podTemplate and creates it. Returns the server's representation of the podTemplate, and an error, if there is any. +func (c *podTemplates) Create(podTemplate *v1.PodTemplate) (result *v1.PodTemplate, err error) { + result = &v1.PodTemplate{} + err = c.client.Post(). + Namespace(c.ns). + Resource("podtemplates"). + Body(podTemplate). + Do(). + Into(result) + return +} + +// Update takes the representation of a podTemplate and updates it. Returns the server's representation of the podTemplate, and an error, if there is any. +func (c *podTemplates) Update(podTemplate *v1.PodTemplate) (result *v1.PodTemplate, err error) { + result = &v1.PodTemplate{} + err = c.client.Put(). + Namespace(c.ns). + Resource("podtemplates"). + Name(podTemplate.Name). + Body(podTemplate). + Do(). + Into(result) + return +} + +// Delete takes name of the podTemplate and deletes it. Returns an error if one occurs. +func (c *podTemplates) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("podtemplates"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *podTemplates) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("podtemplates"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the podTemplate, and returns the corresponding podTemplate object, and an error if there is any. +func (c *podTemplates) Get(name string) (result *v1.PodTemplate, err error) { + result = &v1.PodTemplate{} + err = c.client.Get(). + Namespace(c.ns). + Resource("podtemplates"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of PodTemplates that match those selectors. +func (c *podTemplates) List(opts api.ListOptions) (result *v1.PodTemplateList, err error) { + result = &v1.PodTemplateList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("podtemplates"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested podTemplates. +func (c *podTemplates) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("podtemplates"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/replicationcontroller.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/replicationcontroller.go new file mode 100644 index 000000000..20bcc90c3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/replicationcontroller.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ReplicationControllersGetter has a method to return a ReplicationControllerInterface. +// A group's client should implement this interface. +type ReplicationControllersGetter interface { + ReplicationControllers(namespace string) ReplicationControllerInterface +} + +// ReplicationControllerInterface has methods to work with ReplicationController resources. +type ReplicationControllerInterface interface { + Create(*v1.ReplicationController) (*v1.ReplicationController, error) + Update(*v1.ReplicationController) (*v1.ReplicationController, error) + UpdateStatus(*v1.ReplicationController) (*v1.ReplicationController, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.ReplicationController, error) + List(opts api.ListOptions) (*v1.ReplicationControllerList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ReplicationControllerExpansion +} + +// replicationControllers implements ReplicationControllerInterface +type replicationControllers struct { + client *CoreClient + ns string +} + +// newReplicationControllers returns a ReplicationControllers +func newReplicationControllers(c *CoreClient, namespace string) *replicationControllers { + return &replicationControllers{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a replicationController and creates it. Returns the server's representation of the replicationController, and an error, if there is any. +func (c *replicationControllers) Create(replicationController *v1.ReplicationController) (result *v1.ReplicationController, err error) { + result = &v1.ReplicationController{} + err = c.client.Post(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Body(replicationController). + Do(). + Into(result) + return +} + +// Update takes the representation of a replicationController and updates it. Returns the server's representation of the replicationController, and an error, if there is any. +func (c *replicationControllers) Update(replicationController *v1.ReplicationController) (result *v1.ReplicationController, err error) { + result = &v1.ReplicationController{} + err = c.client.Put(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Name(replicationController.Name). + Body(replicationController). + Do(). + Into(result) + return +} + +func (c *replicationControllers) UpdateStatus(replicationController *v1.ReplicationController) (result *v1.ReplicationController, err error) { + result = &v1.ReplicationController{} + err = c.client.Put(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Name(replicationController.Name). + SubResource("status"). + Body(replicationController). + Do(). + Into(result) + return +} + +// Delete takes name of the replicationController and deletes it. Returns an error if one occurs. +func (c *replicationControllers) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *replicationControllers) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("replicationcontrollers"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the replicationController, and returns the corresponding replicationController object, and an error if there is any. +func (c *replicationControllers) Get(name string) (result *v1.ReplicationController, err error) { + result = &v1.ReplicationController{} + err = c.client.Get(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ReplicationControllers that match those selectors. +func (c *replicationControllers) List(opts api.ListOptions) (result *v1.ReplicationControllerList, err error) { + result = &v1.ReplicationControllerList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("replicationcontrollers"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested replicationControllers. +func (c *replicationControllers) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("replicationcontrollers"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/resourcequota.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/resourcequota.go new file mode 100644 index 000000000..466e963d6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/resourcequota.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ResourceQuotasGetter has a method to return a ResourceQuotaInterface. +// A group's client should implement this interface. +type ResourceQuotasGetter interface { + ResourceQuotas(namespace string) ResourceQuotaInterface +} + +// ResourceQuotaInterface has methods to work with ResourceQuota resources. +type ResourceQuotaInterface interface { + Create(*v1.ResourceQuota) (*v1.ResourceQuota, error) + Update(*v1.ResourceQuota) (*v1.ResourceQuota, error) + UpdateStatus(*v1.ResourceQuota) (*v1.ResourceQuota, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.ResourceQuota, error) + List(opts api.ListOptions) (*v1.ResourceQuotaList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ResourceQuotaExpansion +} + +// resourceQuotas implements ResourceQuotaInterface +type resourceQuotas struct { + client *CoreClient + ns string +} + +// newResourceQuotas returns a ResourceQuotas +func newResourceQuotas(c *CoreClient, namespace string) *resourceQuotas { + return &resourceQuotas{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a resourceQuota and creates it. Returns the server's representation of the resourceQuota, and an error, if there is any. +func (c *resourceQuotas) Create(resourceQuota *v1.ResourceQuota) (result *v1.ResourceQuota, err error) { + result = &v1.ResourceQuota{} + err = c.client.Post(). + Namespace(c.ns). + Resource("resourcequotas"). + Body(resourceQuota). + Do(). + Into(result) + return +} + +// Update takes the representation of a resourceQuota and updates it. Returns the server's representation of the resourceQuota, and an error, if there is any. +func (c *resourceQuotas) Update(resourceQuota *v1.ResourceQuota) (result *v1.ResourceQuota, err error) { + result = &v1.ResourceQuota{} + err = c.client.Put(). + Namespace(c.ns). + Resource("resourcequotas"). + Name(resourceQuota.Name). + Body(resourceQuota). + Do(). + Into(result) + return +} + +func (c *resourceQuotas) UpdateStatus(resourceQuota *v1.ResourceQuota) (result *v1.ResourceQuota, err error) { + result = &v1.ResourceQuota{} + err = c.client.Put(). + Namespace(c.ns). + Resource("resourcequotas"). + Name(resourceQuota.Name). + SubResource("status"). + Body(resourceQuota). + Do(). + Into(result) + return +} + +// Delete takes name of the resourceQuota and deletes it. Returns an error if one occurs. +func (c *resourceQuotas) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("resourcequotas"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *resourceQuotas) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("resourcequotas"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the resourceQuota, and returns the corresponding resourceQuota object, and an error if there is any. +func (c *resourceQuotas) Get(name string) (result *v1.ResourceQuota, err error) { + result = &v1.ResourceQuota{} + err = c.client.Get(). + Namespace(c.ns). + Resource("resourcequotas"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ResourceQuotas that match those selectors. +func (c *resourceQuotas) List(opts api.ListOptions) (result *v1.ResourceQuotaList, err error) { + result = &v1.ResourceQuotaList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("resourcequotas"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested resourceQuotas. +func (c *resourceQuotas) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("resourcequotas"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/secret.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/secret.go new file mode 100644 index 000000000..a95aa84f4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/secret.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// SecretsGetter has a method to return a SecretInterface. +// A group's client should implement this interface. +type SecretsGetter interface { + Secrets(namespace string) SecretInterface +} + +// SecretInterface has methods to work with Secret resources. +type SecretInterface interface { + Create(*v1.Secret) (*v1.Secret, error) + Update(*v1.Secret) (*v1.Secret, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.Secret, error) + List(opts api.ListOptions) (*v1.SecretList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + SecretExpansion +} + +// secrets implements SecretInterface +type secrets struct { + client *CoreClient + ns string +} + +// newSecrets returns a Secrets +func newSecrets(c *CoreClient, namespace string) *secrets { + return &secrets{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a secret and creates it. Returns the server's representation of the secret, and an error, if there is any. +func (c *secrets) Create(secret *v1.Secret) (result *v1.Secret, err error) { + result = &v1.Secret{} + err = c.client.Post(). + Namespace(c.ns). + Resource("secrets"). + Body(secret). + Do(). + Into(result) + return +} + +// Update takes the representation of a secret and updates it. Returns the server's representation of the secret, and an error, if there is any. +func (c *secrets) Update(secret *v1.Secret) (result *v1.Secret, err error) { + result = &v1.Secret{} + err = c.client.Put(). + Namespace(c.ns). + Resource("secrets"). + Name(secret.Name). + Body(secret). + Do(). + Into(result) + return +} + +// Delete takes name of the secret and deletes it. Returns an error if one occurs. +func (c *secrets) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("secrets"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *secrets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("secrets"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the secret, and returns the corresponding secret object, and an error if there is any. +func (c *secrets) Get(name string) (result *v1.Secret, err error) { + result = &v1.Secret{} + err = c.client.Get(). + Namespace(c.ns). + Resource("secrets"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Secrets that match those selectors. +func (c *secrets) List(opts api.ListOptions) (result *v1.SecretList, err error) { + result = &v1.SecretList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("secrets"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested secrets. +func (c *secrets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("secrets"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/service.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/service.go new file mode 100644 index 000000000..cd62b5d94 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/service.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ServicesGetter has a method to return a ServiceInterface. +// A group's client should implement this interface. +type ServicesGetter interface { + Services(namespace string) ServiceInterface +} + +// ServiceInterface has methods to work with Service resources. +type ServiceInterface interface { + Create(*v1.Service) (*v1.Service, error) + Update(*v1.Service) (*v1.Service, error) + UpdateStatus(*v1.Service) (*v1.Service, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.Service, error) + List(opts api.ListOptions) (*v1.ServiceList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ServiceExpansion +} + +// services implements ServiceInterface +type services struct { + client *CoreClient + ns string +} + +// newServices returns a Services +func newServices(c *CoreClient, namespace string) *services { + return &services{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a service and creates it. Returns the server's representation of the service, and an error, if there is any. +func (c *services) Create(service *v1.Service) (result *v1.Service, err error) { + result = &v1.Service{} + err = c.client.Post(). + Namespace(c.ns). + Resource("services"). + Body(service). + Do(). + Into(result) + return +} + +// Update takes the representation of a service and updates it. Returns the server's representation of the service, and an error, if there is any. +func (c *services) Update(service *v1.Service) (result *v1.Service, err error) { + result = &v1.Service{} + err = c.client.Put(). + Namespace(c.ns). + Resource("services"). + Name(service.Name). + Body(service). + Do(). + Into(result) + return +} + +func (c *services) UpdateStatus(service *v1.Service) (result *v1.Service, err error) { + result = &v1.Service{} + err = c.client.Put(). + Namespace(c.ns). + Resource("services"). + Name(service.Name). + SubResource("status"). + Body(service). + Do(). + Into(result) + return +} + +// Delete takes name of the service and deletes it. Returns an error if one occurs. +func (c *services) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("services"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *services) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("services"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the service, and returns the corresponding service object, and an error if there is any. +func (c *services) Get(name string) (result *v1.Service, err error) { + result = &v1.Service{} + err = c.client.Get(). + Namespace(c.ns). + Resource("services"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Services that match those selectors. +func (c *services) List(opts api.ListOptions) (result *v1.ServiceList, err error) { + result = &v1.ServiceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("services"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested services. +func (c *services) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("services"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/service_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/service_expansion.go new file mode 100644 index 000000000..b4300483b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/service_expansion.go @@ -0,0 +1,41 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/util/net" +) + +// The ServiceExpansion interface allows manually adding extra methods to the ServiceInterface. +type ServiceExpansion interface { + ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper +} + +// ProxyGet returns a response of the service by calling it through the proxy. +func (c *services) ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper { + request := c.client.Get(). + Prefix("proxy"). + Namespace(c.ns). + Resource("services"). + Name(net.JoinSchemeNamePort(scheme, name, port)). + Suffix(path) + for k, v := range params { + request = request.Param(k, v) + } + return request +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/serviceaccount.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/serviceaccount.go new file mode 100644 index 000000000..eb0b258fa --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1/serviceaccount.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ServiceAccountsGetter has a method to return a ServiceAccountInterface. +// A group's client should implement this interface. +type ServiceAccountsGetter interface { + ServiceAccounts(namespace string) ServiceAccountInterface +} + +// ServiceAccountInterface has methods to work with ServiceAccount resources. +type ServiceAccountInterface interface { + Create(*v1.ServiceAccount) (*v1.ServiceAccount, error) + Update(*v1.ServiceAccount) (*v1.ServiceAccount, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.ServiceAccount, error) + List(opts api.ListOptions) (*v1.ServiceAccountList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ServiceAccountExpansion +} + +// serviceAccounts implements ServiceAccountInterface +type serviceAccounts struct { + client *CoreClient + ns string +} + +// newServiceAccounts returns a ServiceAccounts +func newServiceAccounts(c *CoreClient, namespace string) *serviceAccounts { + return &serviceAccounts{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a serviceAccount and creates it. Returns the server's representation of the serviceAccount, and an error, if there is any. +func (c *serviceAccounts) Create(serviceAccount *v1.ServiceAccount) (result *v1.ServiceAccount, err error) { + result = &v1.ServiceAccount{} + err = c.client.Post(). + Namespace(c.ns). + Resource("serviceaccounts"). + Body(serviceAccount). + Do(). + Into(result) + return +} + +// Update takes the representation of a serviceAccount and updates it. Returns the server's representation of the serviceAccount, and an error, if there is any. +func (c *serviceAccounts) Update(serviceAccount *v1.ServiceAccount) (result *v1.ServiceAccount, err error) { + result = &v1.ServiceAccount{} + err = c.client.Put(). + Namespace(c.ns). + Resource("serviceaccounts"). + Name(serviceAccount.Name). + Body(serviceAccount). + Do(). + Into(result) + return +} + +// Delete takes name of the serviceAccount and deletes it. Returns an error if one occurs. +func (c *serviceAccounts) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("serviceaccounts"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *serviceAccounts) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("serviceaccounts"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the serviceAccount, and returns the corresponding serviceAccount object, and an error if there is any. +func (c *serviceAccounts) Get(name string) (result *v1.ServiceAccount, err error) { + result = &v1.ServiceAccount{} + err = c.client.Get(). + Namespace(c.ns). + Resource("serviceaccounts"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ServiceAccounts that match those selectors. +func (c *serviceAccounts) List(opts api.ListOptions) (result *v1.ServiceAccountList, err error) { + result = &v1.ServiceAccountList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("serviceaccounts"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested serviceAccounts. +func (c *serviceAccounts) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("serviceaccounts"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/daemonset.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/daemonset.go new file mode 100644 index 000000000..ecbece591 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/daemonset.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// DaemonSetsGetter has a method to return a DaemonSetInterface. +// A group's client should implement this interface. +type DaemonSetsGetter interface { + DaemonSets(namespace string) DaemonSetInterface +} + +// DaemonSetInterface has methods to work with DaemonSet resources. +type DaemonSetInterface interface { + Create(*v1beta1.DaemonSet) (*v1beta1.DaemonSet, error) + Update(*v1beta1.DaemonSet) (*v1beta1.DaemonSet, error) + UpdateStatus(*v1beta1.DaemonSet) (*v1beta1.DaemonSet, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.DaemonSet, error) + List(opts api.ListOptions) (*v1beta1.DaemonSetList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + DaemonSetExpansion +} + +// daemonSets implements DaemonSetInterface +type daemonSets struct { + client *ExtensionsClient + ns string +} + +// newDaemonSets returns a DaemonSets +func newDaemonSets(c *ExtensionsClient, namespace string) *daemonSets { + return &daemonSets{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a daemonSet and creates it. Returns the server's representation of the daemonSet, and an error, if there is any. +func (c *daemonSets) Create(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { + result = &v1beta1.DaemonSet{} + err = c.client.Post(). + Namespace(c.ns). + Resource("daemonsets"). + Body(daemonSet). + Do(). + Into(result) + return +} + +// Update takes the representation of a daemonSet and updates it. Returns the server's representation of the daemonSet, and an error, if there is any. +func (c *daemonSets) Update(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { + result = &v1beta1.DaemonSet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("daemonsets"). + Name(daemonSet.Name). + Body(daemonSet). + Do(). + Into(result) + return +} + +func (c *daemonSets) UpdateStatus(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { + result = &v1beta1.DaemonSet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("daemonsets"). + Name(daemonSet.Name). + SubResource("status"). + Body(daemonSet). + Do(). + Into(result) + return +} + +// Delete takes name of the daemonSet and deletes it. Returns an error if one occurs. +func (c *daemonSets) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("daemonsets"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *daemonSets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("daemonsets"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the daemonSet, and returns the corresponding daemonSet object, and an error if there is any. +func (c *daemonSets) Get(name string) (result *v1beta1.DaemonSet, err error) { + result = &v1beta1.DaemonSet{} + err = c.client.Get(). + Namespace(c.ns). + Resource("daemonsets"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of DaemonSets that match those selectors. +func (c *daemonSets) List(opts api.ListOptions) (result *v1beta1.DaemonSetList, err error) { + result = &v1beta1.DaemonSetList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("daemonsets"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested daemonSets. +func (c *daemonSets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("daemonsets"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/deployment.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/deployment.go new file mode 100644 index 000000000..7cc3ff9d3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/deployment.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// DeploymentsGetter has a method to return a DeploymentInterface. +// A group's client should implement this interface. +type DeploymentsGetter interface { + Deployments(namespace string) DeploymentInterface +} + +// DeploymentInterface has methods to work with Deployment resources. +type DeploymentInterface interface { + Create(*v1beta1.Deployment) (*v1beta1.Deployment, error) + Update(*v1beta1.Deployment) (*v1beta1.Deployment, error) + UpdateStatus(*v1beta1.Deployment) (*v1beta1.Deployment, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.Deployment, error) + List(opts api.ListOptions) (*v1beta1.DeploymentList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + DeploymentExpansion +} + +// deployments implements DeploymentInterface +type deployments struct { + client *ExtensionsClient + ns string +} + +// newDeployments returns a Deployments +func newDeployments(c *ExtensionsClient, namespace string) *deployments { + return &deployments{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a deployment and creates it. Returns the server's representation of the deployment, and an error, if there is any. +func (c *deployments) Create(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { + result = &v1beta1.Deployment{} + err = c.client.Post(). + Namespace(c.ns). + Resource("deployments"). + Body(deployment). + Do(). + Into(result) + return +} + +// Update takes the representation of a deployment and updates it. Returns the server's representation of the deployment, and an error, if there is any. +func (c *deployments) Update(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { + result = &v1beta1.Deployment{} + err = c.client.Put(). + Namespace(c.ns). + Resource("deployments"). + Name(deployment.Name). + Body(deployment). + Do(). + Into(result) + return +} + +func (c *deployments) UpdateStatus(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { + result = &v1beta1.Deployment{} + err = c.client.Put(). + Namespace(c.ns). + Resource("deployments"). + Name(deployment.Name). + SubResource("status"). + Body(deployment). + Do(). + Into(result) + return +} + +// Delete takes name of the deployment and deletes it. Returns an error if one occurs. +func (c *deployments) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("deployments"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *deployments) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("deployments"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the deployment, and returns the corresponding deployment object, and an error if there is any. +func (c *deployments) Get(name string) (result *v1beta1.Deployment, err error) { + result = &v1beta1.Deployment{} + err = c.client.Get(). + Namespace(c.ns). + Resource("deployments"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Deployments that match those selectors. +func (c *deployments) List(opts api.ListOptions) (result *v1beta1.DeploymentList, err error) { + result = &v1beta1.DeploymentList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("deployments"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested deployments. +func (c *deployments) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("deployments"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/deployment_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/deployment_expansion.go new file mode 100644 index 000000000..0c3ff6367 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/deployment_expansion.go @@ -0,0 +1,29 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + +// The DeploymentExpansion interface allows manually adding extra methods to the DeploymentInterface. +type DeploymentExpansion interface { + Rollback(*v1beta1.DeploymentRollback) error +} + +// Rollback applied the provided DeploymentRollback to the named deployment in the current namespace. +func (c *deployments) Rollback(deploymentRollback *v1beta1.DeploymentRollback) error { + return c.client.Post().Namespace(c.ns).Resource("deployments").Name(deploymentRollback.Name).SubResource("rollback").Body(deploymentRollback).Do().Error() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/doc.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/doc.go new file mode 100644 index 000000000..ffd3806e8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This package is generated by client-gen with arguments: --clientset-name=release_1_2 --input=[api/v1,extensions/v1beta1] + +// This package has the automatically generated typed clients. +package v1beta1 diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/extensions_client.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/extensions_client.go new file mode 100644 index 000000000..af3348a33 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/extensions_client.go @@ -0,0 +1,125 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + registered "k8s.io/kubernetes/pkg/apimachinery/registered" + restclient "k8s.io/kubernetes/pkg/client/restclient" +) + +type ExtensionsInterface interface { + DaemonSetsGetter + DeploymentsGetter + HorizontalPodAutoscalersGetter + IngressesGetter + JobsGetter + ReplicaSetsGetter + ScalesGetter + ThirdPartyResourcesGetter +} + +// ExtensionsClient is used to interact with features provided by the Extensions group. +type ExtensionsClient struct { + *restclient.RESTClient +} + +func (c *ExtensionsClient) DaemonSets(namespace string) DaemonSetInterface { + return newDaemonSets(c, namespace) +} + +func (c *ExtensionsClient) Deployments(namespace string) DeploymentInterface { + return newDeployments(c, namespace) +} + +func (c *ExtensionsClient) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface { + return newHorizontalPodAutoscalers(c, namespace) +} + +func (c *ExtensionsClient) Ingresses(namespace string) IngressInterface { + return newIngresses(c, namespace) +} + +func (c *ExtensionsClient) Jobs(namespace string) JobInterface { + return newJobs(c, namespace) +} + +func (c *ExtensionsClient) ReplicaSets(namespace string) ReplicaSetInterface { + return newReplicaSets(c, namespace) +} + +func (c *ExtensionsClient) Scales(namespace string) ScaleInterface { + return newScales(c, namespace) +} + +func (c *ExtensionsClient) ThirdPartyResources(namespace string) ThirdPartyResourceInterface { + return newThirdPartyResources(c, namespace) +} + +// NewForConfig creates a new ExtensionsClient for the given config. +func NewForConfig(c *restclient.Config) (*ExtensionsClient, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := restclient.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &ExtensionsClient{client}, nil +} + +// NewForConfigOrDie creates a new ExtensionsClient for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *restclient.Config) *ExtensionsClient { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new ExtensionsClient for the given RESTClient. +func New(c *restclient.RESTClient) *ExtensionsClient { + return &ExtensionsClient{c} +} + +func setConfigDefaults(config *restclient.Config) error { + // if extensions group is not registered, return an error + g, err := registered.Group("extensions") + if err != nil { + return err + } + config.APIPath = "/apis" + if config.UserAgent == "" { + config.UserAgent = restclient.DefaultKubernetesUserAgent() + } + // TODO: Unconditionally set the config.Version, until we fix the config. + //if config.Version == "" { + copyGroupVersion := g.GroupVersion + config.GroupVersion = ©GroupVersion + //} + + config.Codec = api.Codecs.LegacyCodec(*config.GroupVersion) + if config.QPS == 0 { + config.QPS = 5 + } + if config.Burst == 0 { + config.Burst = 10 + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/doc.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/doc.go new file mode 100644 index 000000000..bafa0bfe4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This package is generated by client-gen with arguments: --clientset-name=release_1_2 --input=[api/v1,extensions/v1beta1] + +// Package fake has the automatically generated clients. +package fake diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_daemonset.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_daemonset.go new file mode 100644 index 000000000..8ae468dc5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_daemonset.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeDaemonSets implements DaemonSetInterface +type FakeDaemonSets struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeDaemonSets) Create(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("daemonsets", c.ns, daemonSet), &v1beta1.DaemonSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.DaemonSet), err +} + +func (c *FakeDaemonSets) Update(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("daemonsets", c.ns, daemonSet), &v1beta1.DaemonSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.DaemonSet), err +} + +func (c *FakeDaemonSets) UpdateStatus(daemonSet *v1beta1.DaemonSet) (*v1beta1.DaemonSet, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("daemonsets", "status", c.ns, daemonSet), &v1beta1.DaemonSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.DaemonSet), err +} + +func (c *FakeDaemonSets) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("daemonsets", c.ns, name), &v1beta1.DaemonSet{}) + + return err +} + +func (c *FakeDaemonSets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("daemonsets", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.DaemonSetList{}) + return err +} + +func (c *FakeDaemonSets) Get(name string) (result *v1beta1.DaemonSet, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("daemonsets", c.ns, name), &v1beta1.DaemonSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.DaemonSet), err +} + +func (c *FakeDaemonSets) List(opts api.ListOptions) (result *v1beta1.DaemonSetList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("daemonsets", c.ns, opts), &v1beta1.DaemonSetList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.DaemonSetList{} + for _, item := range obj.(*v1beta1.DaemonSetList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested daemonSets. +func (c *FakeDaemonSets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("daemonsets", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_deployment.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_deployment.go new file mode 100644 index 000000000..739a4f31d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_deployment.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeDeployments implements DeploymentInterface +type FakeDeployments struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeDeployments) Create(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("deployments", c.ns, deployment), &v1beta1.Deployment{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Deployment), err +} + +func (c *FakeDeployments) Update(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("deployments", c.ns, deployment), &v1beta1.Deployment{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Deployment), err +} + +func (c *FakeDeployments) UpdateStatus(deployment *v1beta1.Deployment) (*v1beta1.Deployment, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("deployments", "status", c.ns, deployment), &v1beta1.Deployment{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Deployment), err +} + +func (c *FakeDeployments) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("deployments", c.ns, name), &v1beta1.Deployment{}) + + return err +} + +func (c *FakeDeployments) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("deployments", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.DeploymentList{}) + return err +} + +func (c *FakeDeployments) Get(name string) (result *v1beta1.Deployment, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("deployments", c.ns, name), &v1beta1.Deployment{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Deployment), err +} + +func (c *FakeDeployments) List(opts api.ListOptions) (result *v1beta1.DeploymentList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("deployments", c.ns, opts), &v1beta1.DeploymentList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.DeploymentList{} + for _, item := range obj.(*v1beta1.DeploymentList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested deployments. +func (c *FakeDeployments) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("deployments", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_deployment_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_deployment_expansion.go new file mode 100644 index 000000000..5d0aff06d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_deployment_expansion.go @@ -0,0 +1,33 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeDeployments) Rollback(deploymentRollback *v1beta1.DeploymentRollback) error { + action := core.CreateActionImpl{} + action.Verb = "create" + action.Resource = "deployments" + action.Subresource = "rollback" + action.Object = deploymentRollback + + _, err := c.Fake.Invokes(action, deploymentRollback) + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_extensions_client.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_extensions_client.go new file mode 100644 index 000000000..f092a6f61 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_extensions_client.go @@ -0,0 +1,58 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + v1beta1 "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" +) + +type FakeExtensions struct { + *core.Fake +} + +func (c *FakeExtensions) DaemonSets(namespace string) v1beta1.DaemonSetInterface { + return &FakeDaemonSets{c, namespace} +} + +func (c *FakeExtensions) Deployments(namespace string) v1beta1.DeploymentInterface { + return &FakeDeployments{c, namespace} +} + +func (c *FakeExtensions) HorizontalPodAutoscalers(namespace string) v1beta1.HorizontalPodAutoscalerInterface { + return &FakeHorizontalPodAutoscalers{c, namespace} +} + +func (c *FakeExtensions) Ingresses(namespace string) v1beta1.IngressInterface { + return &FakeIngresses{c, namespace} +} + +func (c *FakeExtensions) Jobs(namespace string) v1beta1.JobInterface { + return &FakeJobs{c, namespace} +} + +func (c *FakeExtensions) ReplicaSets(namespace string) v1beta1.ReplicaSetInterface { + return &FakeReplicaSets{c, namespace} +} + +func (c *FakeExtensions) Scales(namespace string) v1beta1.ScaleInterface { + return &FakeScales{c, namespace} +} + +func (c *FakeExtensions) ThirdPartyResources(namespace string) v1beta1.ThirdPartyResourceInterface { + return &FakeThirdPartyResources{c, namespace} +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_horizontalpodautoscaler.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_horizontalpodautoscaler.go new file mode 100644 index 000000000..1fef97ea5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_horizontalpodautoscaler.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeHorizontalPodAutoscalers implements HorizontalPodAutoscalerInterface +type FakeHorizontalPodAutoscalers struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeHorizontalPodAutoscalers) Create(horizontalPodAutoscaler *v1beta1.HorizontalPodAutoscaler) (result *v1beta1.HorizontalPodAutoscaler, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("horizontalpodautoscalers", c.ns, horizontalPodAutoscaler), &v1beta1.HorizontalPodAutoscaler{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalers) Update(horizontalPodAutoscaler *v1beta1.HorizontalPodAutoscaler) (result *v1beta1.HorizontalPodAutoscaler, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("horizontalpodautoscalers", c.ns, horizontalPodAutoscaler), &v1beta1.HorizontalPodAutoscaler{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalers) UpdateStatus(horizontalPodAutoscaler *v1beta1.HorizontalPodAutoscaler) (*v1beta1.HorizontalPodAutoscaler, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("horizontalpodautoscalers", "status", c.ns, horizontalPodAutoscaler), &v1beta1.HorizontalPodAutoscaler{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalers) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("horizontalpodautoscalers", c.ns, name), &v1beta1.HorizontalPodAutoscaler{}) + + return err +} + +func (c *FakeHorizontalPodAutoscalers) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("horizontalpodautoscalers", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.HorizontalPodAutoscalerList{}) + return err +} + +func (c *FakeHorizontalPodAutoscalers) Get(name string) (result *v1beta1.HorizontalPodAutoscaler, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("horizontalpodautoscalers", c.ns, name), &v1beta1.HorizontalPodAutoscaler{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalers) List(opts api.ListOptions) (result *v1beta1.HorizontalPodAutoscalerList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("horizontalpodautoscalers", c.ns, opts), &v1beta1.HorizontalPodAutoscalerList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.HorizontalPodAutoscalerList{} + for _, item := range obj.(*v1beta1.HorizontalPodAutoscalerList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers. +func (c *FakeHorizontalPodAutoscalers) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("horizontalpodautoscalers", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_ingress.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_ingress.go new file mode 100644 index 000000000..b9c1ab096 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_ingress.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeIngresses implements IngressInterface +type FakeIngresses struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeIngresses) Create(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("ingresses", c.ns, ingress), &v1beta1.Ingress{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Ingress), err +} + +func (c *FakeIngresses) Update(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("ingresses", c.ns, ingress), &v1beta1.Ingress{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Ingress), err +} + +func (c *FakeIngresses) UpdateStatus(ingress *v1beta1.Ingress) (*v1beta1.Ingress, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("ingresses", "status", c.ns, ingress), &v1beta1.Ingress{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Ingress), err +} + +func (c *FakeIngresses) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("ingresses", c.ns, name), &v1beta1.Ingress{}) + + return err +} + +func (c *FakeIngresses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("ingresses", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.IngressList{}) + return err +} + +func (c *FakeIngresses) Get(name string) (result *v1beta1.Ingress, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("ingresses", c.ns, name), &v1beta1.Ingress{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Ingress), err +} + +func (c *FakeIngresses) List(opts api.ListOptions) (result *v1beta1.IngressList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("ingresses", c.ns, opts), &v1beta1.IngressList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.IngressList{} + for _, item := range obj.(*v1beta1.IngressList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested ingresses. +func (c *FakeIngresses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("ingresses", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_job.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_job.go new file mode 100644 index 000000000..21610e2bc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_job.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeJobs implements JobInterface +type FakeJobs struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeJobs) Create(job *v1beta1.Job) (result *v1beta1.Job, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("jobs", c.ns, job), &v1beta1.Job{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Job), err +} + +func (c *FakeJobs) Update(job *v1beta1.Job) (result *v1beta1.Job, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("jobs", c.ns, job), &v1beta1.Job{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Job), err +} + +func (c *FakeJobs) UpdateStatus(job *v1beta1.Job) (*v1beta1.Job, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("jobs", "status", c.ns, job), &v1beta1.Job{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Job), err +} + +func (c *FakeJobs) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("jobs", c.ns, name), &v1beta1.Job{}) + + return err +} + +func (c *FakeJobs) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("jobs", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.JobList{}) + return err +} + +func (c *FakeJobs) Get(name string) (result *v1beta1.Job, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("jobs", c.ns, name), &v1beta1.Job{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Job), err +} + +func (c *FakeJobs) List(opts api.ListOptions) (result *v1beta1.JobList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("jobs", c.ns, opts), &v1beta1.JobList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.JobList{} + for _, item := range obj.(*v1beta1.JobList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested jobs. +func (c *FakeJobs) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("jobs", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_replicaset.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_replicaset.go new file mode 100644 index 000000000..f785deced --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_replicaset.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeReplicaSets implements ReplicaSetInterface +type FakeReplicaSets struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeReplicaSets) Create(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("replicasets", c.ns, replicaSet), &v1beta1.ReplicaSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ReplicaSet), err +} + +func (c *FakeReplicaSets) Update(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("replicasets", c.ns, replicaSet), &v1beta1.ReplicaSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ReplicaSet), err +} + +func (c *FakeReplicaSets) UpdateStatus(replicaSet *v1beta1.ReplicaSet) (*v1beta1.ReplicaSet, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("replicasets", "status", c.ns, replicaSet), &v1beta1.ReplicaSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ReplicaSet), err +} + +func (c *FakeReplicaSets) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("replicasets", c.ns, name), &v1beta1.ReplicaSet{}) + + return err +} + +func (c *FakeReplicaSets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("replicasets", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.ReplicaSetList{}) + return err +} + +func (c *FakeReplicaSets) Get(name string) (result *v1beta1.ReplicaSet, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("replicasets", c.ns, name), &v1beta1.ReplicaSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ReplicaSet), err +} + +func (c *FakeReplicaSets) List(opts api.ListOptions) (result *v1beta1.ReplicaSetList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("replicasets", c.ns, opts), &v1beta1.ReplicaSetList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.ReplicaSetList{} + for _, item := range obj.(*v1beta1.ReplicaSetList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested replicaSets. +func (c *FakeReplicaSets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("replicasets", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_scale.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_scale.go new file mode 100644 index 000000000..d2cfc5f7b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_scale.go @@ -0,0 +1,23 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +// FakeScales implements ScaleInterface +type FakeScales struct { + Fake *FakeExtensions + ns string +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_scale_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_scale_expansion.go new file mode 100644 index 000000000..ea6f5ab31 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_scale_expansion.go @@ -0,0 +1,46 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeScales) Get(kind string, name string) (result *v1beta1.Scale, err error) { + action := core.GetActionImpl{} + action.Verb = "get" + action.Namespace = c.ns + action.Resource = kind + action.Subresource = "scale" + action.Name = name + obj, err := c.Fake.Invokes(action, &v1beta1.Scale{}) + result = obj.(*v1beta1.Scale) + return +} + +func (c *FakeScales) Update(kind string, scale *v1beta1.Scale) (result *v1beta1.Scale, err error) { + action := core.UpdateActionImpl{} + action.Verb = "update" + action.Namespace = c.ns + action.Resource = kind + action.Subresource = "scale" + action.Object = scale + obj, err := c.Fake.Invokes(action, scale) + result = obj.(*v1beta1.Scale) + return +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_thirdpartyresource.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_thirdpartyresource.go new file mode 100644 index 000000000..364d1efb9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/fake/fake_thirdpartyresource.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeThirdPartyResources implements ThirdPartyResourceInterface +type FakeThirdPartyResources struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeThirdPartyResources) Create(thirdPartyResource *v1beta1.ThirdPartyResource) (result *v1beta1.ThirdPartyResource, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("thirdpartyresources", c.ns, thirdPartyResource), &v1beta1.ThirdPartyResource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ThirdPartyResource), err +} + +func (c *FakeThirdPartyResources) Update(thirdPartyResource *v1beta1.ThirdPartyResource) (result *v1beta1.ThirdPartyResource, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("thirdpartyresources", c.ns, thirdPartyResource), &v1beta1.ThirdPartyResource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ThirdPartyResource), err +} + +func (c *FakeThirdPartyResources) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("thirdpartyresources", c.ns, name), &v1beta1.ThirdPartyResource{}) + + return err +} + +func (c *FakeThirdPartyResources) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("thirdpartyresources", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.ThirdPartyResourceList{}) + return err +} + +func (c *FakeThirdPartyResources) Get(name string) (result *v1beta1.ThirdPartyResource, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("thirdpartyresources", c.ns, name), &v1beta1.ThirdPartyResource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ThirdPartyResource), err +} + +func (c *FakeThirdPartyResources) List(opts api.ListOptions) (result *v1beta1.ThirdPartyResourceList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("thirdpartyresources", c.ns, opts), &v1beta1.ThirdPartyResourceList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.ThirdPartyResourceList{} + for _, item := range obj.(*v1beta1.ThirdPartyResourceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested thirdPartyResources. +func (c *FakeThirdPartyResources) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("thirdpartyresources", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/generated_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/generated_expansion.go new file mode 100644 index 000000000..97c6a1c06 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/generated_expansion.go @@ -0,0 +1,29 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +type DaemonSetExpansion interface{} + +type HorizontalPodAutoscalerExpansion interface{} + +type IngressExpansion interface{} + +type JobExpansion interface{} + +type ThirdPartyResourceExpansion interface{} + +type ReplicaSetExpansion interface{} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/horizontalpodautoscaler.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/horizontalpodautoscaler.go new file mode 100644 index 000000000..93b486b89 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/horizontalpodautoscaler.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// HorizontalPodAutoscalersGetter has a method to return a HorizontalPodAutoscalerInterface. +// A group's client should implement this interface. +type HorizontalPodAutoscalersGetter interface { + HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface +} + +// HorizontalPodAutoscalerInterface has methods to work with HorizontalPodAutoscaler resources. +type HorizontalPodAutoscalerInterface interface { + Create(*v1beta1.HorizontalPodAutoscaler) (*v1beta1.HorizontalPodAutoscaler, error) + Update(*v1beta1.HorizontalPodAutoscaler) (*v1beta1.HorizontalPodAutoscaler, error) + UpdateStatus(*v1beta1.HorizontalPodAutoscaler) (*v1beta1.HorizontalPodAutoscaler, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.HorizontalPodAutoscaler, error) + List(opts api.ListOptions) (*v1beta1.HorizontalPodAutoscalerList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + HorizontalPodAutoscalerExpansion +} + +// horizontalPodAutoscalers implements HorizontalPodAutoscalerInterface +type horizontalPodAutoscalers struct { + client *ExtensionsClient + ns string +} + +// newHorizontalPodAutoscalers returns a HorizontalPodAutoscalers +func newHorizontalPodAutoscalers(c *ExtensionsClient, namespace string) *horizontalPodAutoscalers { + return &horizontalPodAutoscalers{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a horizontalPodAutoscaler and creates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any. +func (c *horizontalPodAutoscalers) Create(horizontalPodAutoscaler *v1beta1.HorizontalPodAutoscaler) (result *v1beta1.HorizontalPodAutoscaler, err error) { + result = &v1beta1.HorizontalPodAutoscaler{} + err = c.client.Post(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Body(horizontalPodAutoscaler). + Do(). + Into(result) + return +} + +// Update takes the representation of a horizontalPodAutoscaler and updates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any. +func (c *horizontalPodAutoscalers) Update(horizontalPodAutoscaler *v1beta1.HorizontalPodAutoscaler) (result *v1beta1.HorizontalPodAutoscaler, err error) { + result = &v1beta1.HorizontalPodAutoscaler{} + err = c.client.Put(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Name(horizontalPodAutoscaler.Name). + Body(horizontalPodAutoscaler). + Do(). + Into(result) + return +} + +func (c *horizontalPodAutoscalers) UpdateStatus(horizontalPodAutoscaler *v1beta1.HorizontalPodAutoscaler) (result *v1beta1.HorizontalPodAutoscaler, err error) { + result = &v1beta1.HorizontalPodAutoscaler{} + err = c.client.Put(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Name(horizontalPodAutoscaler.Name). + SubResource("status"). + Body(horizontalPodAutoscaler). + Do(). + Into(result) + return +} + +// Delete takes name of the horizontalPodAutoscaler and deletes it. Returns an error if one occurs. +func (c *horizontalPodAutoscalers) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *horizontalPodAutoscalers) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the horizontalPodAutoscaler, and returns the corresponding horizontalPodAutoscaler object, and an error if there is any. +func (c *horizontalPodAutoscalers) Get(name string) (result *v1beta1.HorizontalPodAutoscaler, err error) { + result = &v1beta1.HorizontalPodAutoscaler{} + err = c.client.Get(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of HorizontalPodAutoscalers that match those selectors. +func (c *horizontalPodAutoscalers) List(opts api.ListOptions) (result *v1beta1.HorizontalPodAutoscalerList, err error) { + result = &v1beta1.HorizontalPodAutoscalerList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers. +func (c *horizontalPodAutoscalers) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/ingress.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/ingress.go new file mode 100644 index 000000000..96b4d0439 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/ingress.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// IngressesGetter has a method to return a IngressInterface. +// A group's client should implement this interface. +type IngressesGetter interface { + Ingresses(namespace string) IngressInterface +} + +// IngressInterface has methods to work with Ingress resources. +type IngressInterface interface { + Create(*v1beta1.Ingress) (*v1beta1.Ingress, error) + Update(*v1beta1.Ingress) (*v1beta1.Ingress, error) + UpdateStatus(*v1beta1.Ingress) (*v1beta1.Ingress, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.Ingress, error) + List(opts api.ListOptions) (*v1beta1.IngressList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + IngressExpansion +} + +// ingresses implements IngressInterface +type ingresses struct { + client *ExtensionsClient + ns string +} + +// newIngresses returns a Ingresses +func newIngresses(c *ExtensionsClient, namespace string) *ingresses { + return &ingresses{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a ingress and creates it. Returns the server's representation of the ingress, and an error, if there is any. +func (c *ingresses) Create(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { + result = &v1beta1.Ingress{} + err = c.client.Post(). + Namespace(c.ns). + Resource("ingresses"). + Body(ingress). + Do(). + Into(result) + return +} + +// Update takes the representation of a ingress and updates it. Returns the server's representation of the ingress, and an error, if there is any. +func (c *ingresses) Update(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { + result = &v1beta1.Ingress{} + err = c.client.Put(). + Namespace(c.ns). + Resource("ingresses"). + Name(ingress.Name). + Body(ingress). + Do(). + Into(result) + return +} + +func (c *ingresses) UpdateStatus(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { + result = &v1beta1.Ingress{} + err = c.client.Put(). + Namespace(c.ns). + Resource("ingresses"). + Name(ingress.Name). + SubResource("status"). + Body(ingress). + Do(). + Into(result) + return +} + +// Delete takes name of the ingress and deletes it. Returns an error if one occurs. +func (c *ingresses) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("ingresses"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *ingresses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("ingresses"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the ingress, and returns the corresponding ingress object, and an error if there is any. +func (c *ingresses) Get(name string) (result *v1beta1.Ingress, err error) { + result = &v1beta1.Ingress{} + err = c.client.Get(). + Namespace(c.ns). + Resource("ingresses"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Ingresses that match those selectors. +func (c *ingresses) List(opts api.ListOptions) (result *v1beta1.IngressList, err error) { + result = &v1beta1.IngressList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("ingresses"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested ingresses. +func (c *ingresses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("ingresses"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/job.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/job.go new file mode 100644 index 000000000..c518c5abd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/job.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// JobsGetter has a method to return a JobInterface. +// A group's client should implement this interface. +type JobsGetter interface { + Jobs(namespace string) JobInterface +} + +// JobInterface has methods to work with Job resources. +type JobInterface interface { + Create(*v1beta1.Job) (*v1beta1.Job, error) + Update(*v1beta1.Job) (*v1beta1.Job, error) + UpdateStatus(*v1beta1.Job) (*v1beta1.Job, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.Job, error) + List(opts api.ListOptions) (*v1beta1.JobList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + JobExpansion +} + +// jobs implements JobInterface +type jobs struct { + client *ExtensionsClient + ns string +} + +// newJobs returns a Jobs +func newJobs(c *ExtensionsClient, namespace string) *jobs { + return &jobs{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a job and creates it. Returns the server's representation of the job, and an error, if there is any. +func (c *jobs) Create(job *v1beta1.Job) (result *v1beta1.Job, err error) { + result = &v1beta1.Job{} + err = c.client.Post(). + Namespace(c.ns). + Resource("jobs"). + Body(job). + Do(). + Into(result) + return +} + +// Update takes the representation of a job and updates it. Returns the server's representation of the job, and an error, if there is any. +func (c *jobs) Update(job *v1beta1.Job) (result *v1beta1.Job, err error) { + result = &v1beta1.Job{} + err = c.client.Put(). + Namespace(c.ns). + Resource("jobs"). + Name(job.Name). + Body(job). + Do(). + Into(result) + return +} + +func (c *jobs) UpdateStatus(job *v1beta1.Job) (result *v1beta1.Job, err error) { + result = &v1beta1.Job{} + err = c.client.Put(). + Namespace(c.ns). + Resource("jobs"). + Name(job.Name). + SubResource("status"). + Body(job). + Do(). + Into(result) + return +} + +// Delete takes name of the job and deletes it. Returns an error if one occurs. +func (c *jobs) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("jobs"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *jobs) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("jobs"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the job, and returns the corresponding job object, and an error if there is any. +func (c *jobs) Get(name string) (result *v1beta1.Job, err error) { + result = &v1beta1.Job{} + err = c.client.Get(). + Namespace(c.ns). + Resource("jobs"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Jobs that match those selectors. +func (c *jobs) List(opts api.ListOptions) (result *v1beta1.JobList, err error) { + result = &v1beta1.JobList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("jobs"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested jobs. +func (c *jobs) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("jobs"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/replicaset.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/replicaset.go new file mode 100644 index 000000000..1822f052c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/replicaset.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ReplicaSetsGetter has a method to return a ReplicaSetInterface. +// A group's client should implement this interface. +type ReplicaSetsGetter interface { + ReplicaSets(namespace string) ReplicaSetInterface +} + +// ReplicaSetInterface has methods to work with ReplicaSet resources. +type ReplicaSetInterface interface { + Create(*v1beta1.ReplicaSet) (*v1beta1.ReplicaSet, error) + Update(*v1beta1.ReplicaSet) (*v1beta1.ReplicaSet, error) + UpdateStatus(*v1beta1.ReplicaSet) (*v1beta1.ReplicaSet, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.ReplicaSet, error) + List(opts api.ListOptions) (*v1beta1.ReplicaSetList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ReplicaSetExpansion +} + +// replicaSets implements ReplicaSetInterface +type replicaSets struct { + client *ExtensionsClient + ns string +} + +// newReplicaSets returns a ReplicaSets +func newReplicaSets(c *ExtensionsClient, namespace string) *replicaSets { + return &replicaSets{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a replicaSet and creates it. Returns the server's representation of the replicaSet, and an error, if there is any. +func (c *replicaSets) Create(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { + result = &v1beta1.ReplicaSet{} + err = c.client.Post(). + Namespace(c.ns). + Resource("replicasets"). + Body(replicaSet). + Do(). + Into(result) + return +} + +// Update takes the representation of a replicaSet and updates it. Returns the server's representation of the replicaSet, and an error, if there is any. +func (c *replicaSets) Update(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { + result = &v1beta1.ReplicaSet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("replicasets"). + Name(replicaSet.Name). + Body(replicaSet). + Do(). + Into(result) + return +} + +func (c *replicaSets) UpdateStatus(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { + result = &v1beta1.ReplicaSet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("replicasets"). + Name(replicaSet.Name). + SubResource("status"). + Body(replicaSet). + Do(). + Into(result) + return +} + +// Delete takes name of the replicaSet and deletes it. Returns an error if one occurs. +func (c *replicaSets) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("replicasets"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *replicaSets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("replicasets"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the replicaSet, and returns the corresponding replicaSet object, and an error if there is any. +func (c *replicaSets) Get(name string) (result *v1beta1.ReplicaSet, err error) { + result = &v1beta1.ReplicaSet{} + err = c.client.Get(). + Namespace(c.ns). + Resource("replicasets"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ReplicaSets that match those selectors. +func (c *replicaSets) List(opts api.ListOptions) (result *v1beta1.ReplicaSetList, err error) { + result = &v1beta1.ReplicaSetList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("replicasets"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested replicaSets. +func (c *replicaSets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("replicasets"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/scale.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/scale.go new file mode 100644 index 000000000..231fe5ccf --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/scale.go @@ -0,0 +1,42 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +// ScalesGetter has a method to return a ScaleInterface. +// A group's client should implement this interface. +type ScalesGetter interface { + Scales(namespace string) ScaleInterface +} + +// ScaleInterface has methods to work with Scale resources. +type ScaleInterface interface { + ScaleExpansion +} + +// scales implements ScaleInterface +type scales struct { + client *ExtensionsClient + ns string +} + +// newScales returns a Scales +func newScales(c *ExtensionsClient, namespace string) *scales { + return &scales{ + client: c, + ns: namespace, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/scale_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/scale_expansion.go new file mode 100644 index 000000000..488863d9f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/scale_expansion.go @@ -0,0 +1,65 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" +) + +// The ScaleExpansion interface allows manually adding extra methods to the ScaleInterface. +type ScaleExpansion interface { + Get(kind string, name string) (*v1beta1.Scale, error) + Update(kind string, scale *v1beta1.Scale) (*v1beta1.Scale, error) +} + +// Get takes the reference to scale subresource and returns the subresource or error, if one occurs. +func (c *scales) Get(kind string, name string) (result *v1beta1.Scale, err error) { + result = &v1beta1.Scale{} + + // TODO this method needs to take a proper unambiguous kind + fullyQualifiedKind := unversioned.GroupVersionKind{Kind: kind} + resource, _ := meta.KindToResource(fullyQualifiedKind) + + err = c.client.Get(). + Namespace(c.ns). + Resource(resource.Resource). + Name(name). + SubResource("scale"). + Do(). + Into(result) + return +} + +func (c *scales) Update(kind string, scale *v1beta1.Scale) (result *v1beta1.Scale, err error) { + result = &v1beta1.Scale{} + + // TODO this method needs to take a proper unambiguous kind + fullyQualifiedKind := unversioned.GroupVersionKind{Kind: kind} + resource, _ := meta.KindToResource(fullyQualifiedKind) + + err = c.client.Put(). + Namespace(scale.Namespace). + Resource(resource.Resource). + Name(scale.Name). + SubResource("scale"). + Body(scale). + Do(). + Into(result) + return +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/thirdpartyresource.go b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/thirdpartyresource.go new file mode 100644 index 000000000..cfd128dc3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1/thirdpartyresource.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ThirdPartyResourcesGetter has a method to return a ThirdPartyResourceInterface. +// A group's client should implement this interface. +type ThirdPartyResourcesGetter interface { + ThirdPartyResources(namespace string) ThirdPartyResourceInterface +} + +// ThirdPartyResourceInterface has methods to work with ThirdPartyResource resources. +type ThirdPartyResourceInterface interface { + Create(*v1beta1.ThirdPartyResource) (*v1beta1.ThirdPartyResource, error) + Update(*v1beta1.ThirdPartyResource) (*v1beta1.ThirdPartyResource, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.ThirdPartyResource, error) + List(opts api.ListOptions) (*v1beta1.ThirdPartyResourceList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ThirdPartyResourceExpansion +} + +// thirdPartyResources implements ThirdPartyResourceInterface +type thirdPartyResources struct { + client *ExtensionsClient + ns string +} + +// newThirdPartyResources returns a ThirdPartyResources +func newThirdPartyResources(c *ExtensionsClient, namespace string) *thirdPartyResources { + return &thirdPartyResources{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a thirdPartyResource and creates it. Returns the server's representation of the thirdPartyResource, and an error, if there is any. +func (c *thirdPartyResources) Create(thirdPartyResource *v1beta1.ThirdPartyResource) (result *v1beta1.ThirdPartyResource, err error) { + result = &v1beta1.ThirdPartyResource{} + err = c.client.Post(). + Namespace(c.ns). + Resource("thirdpartyresources"). + Body(thirdPartyResource). + Do(). + Into(result) + return +} + +// Update takes the representation of a thirdPartyResource and updates it. Returns the server's representation of the thirdPartyResource, and an error, if there is any. +func (c *thirdPartyResources) Update(thirdPartyResource *v1beta1.ThirdPartyResource) (result *v1beta1.ThirdPartyResource, err error) { + result = &v1beta1.ThirdPartyResource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("thirdpartyresources"). + Name(thirdPartyResource.Name). + Body(thirdPartyResource). + Do(). + Into(result) + return +} + +// Delete takes name of the thirdPartyResource and deletes it. Returns an error if one occurs. +func (c *thirdPartyResources) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("thirdpartyresources"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *thirdPartyResources) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("thirdpartyresources"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the thirdPartyResource, and returns the corresponding thirdPartyResource object, and an error if there is any. +func (c *thirdPartyResources) Get(name string) (result *v1beta1.ThirdPartyResource, err error) { + result = &v1beta1.ThirdPartyResource{} + err = c.client.Get(). + Namespace(c.ns). + Resource("thirdpartyresources"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ThirdPartyResources that match those selectors. +func (c *thirdPartyResources) List(opts api.ListOptions) (result *v1beta1.ThirdPartyResourceList, err error) { + result = &v1beta1.ThirdPartyResourceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("thirdpartyresources"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested thirdPartyResources. +func (c *thirdPartyResources) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("thirdpartyresources"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/leaderelection/OWNERS b/vendor/k8s.io/kubernetes/pkg/client/leaderelection/OWNERS new file mode 100644 index 000000000..ac1004ec6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/leaderelection/OWNERS @@ -0,0 +1,2 @@ +assignees: + - mikedanese diff --git a/vendor/k8s.io/kubernetes/pkg/client/leaderelection/leaderelection.go b/vendor/k8s.io/kubernetes/pkg/client/leaderelection/leaderelection.go new file mode 100644 index 000000000..fd8d09c9f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/leaderelection/leaderelection.go @@ -0,0 +1,363 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 leaderelection implements leader election of a set of endpoints. +// It uses an annotation in the endpoints object to store the record of the +// election state. +// +// This implementation does not guarantee that only one client is acting as a +// leader (a.k.a. fencing). A client observes timestamps captured locally to +// infer the state of the leader election. Thus the implementation is tolerant +// to arbitrary clock skew, but is not tolerant to arbitrary clock skew rate. +// +// However the level of tolerance to skew rate can be configured by setting +// RenewDeadline and LeaseDuration appropriately. The tolerance expressed as a +// maximum tolerated ratio of time passed on the fastest node to time passed on +// the slowest node can be approximately achieved with a configuration that sets +// the same ratio of LeaseDuration to RenewDeadline. For example if a user wanted +// to tolerate some nodes progressing forward in time twice as fast as other nodes, +// the user could set LeaseDuration to 60 seconds and RenewDeadline to 30 seconds. +// +// While not required, some method of clock synchronization between nodes in the +// cluster is highly recommended. It's important to keep in mind when configuring +// this client that the tolerance to skew rate varies inversely to master +// availability. +// +// Larger clusters often have a more lenient SLA for API latency. This should be +// taken into account when configuring the client. The rate of leader transitions +// should be monitored and RetryPeriod and LeaseDuration should be increased +// until the rate is stable and acceptably low. It's important to keep in mind +// when configuring this client that the tolerance to API latency varies inversely +// to master availability. +// +// DISCLAIMER: this is an alpha API. This library will likely change significantly +// or even be removed entirely in subsequent releases. Depend on this API at +// your own risk. +package leaderelection + +import ( + "encoding/json" + "fmt" + "reflect" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/componentconfig" + "k8s.io/kubernetes/pkg/client/record" + client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" + + "github.com/golang/glog" + "github.com/spf13/pflag" +) + +const ( + JitterFactor = 1.2 + + LeaderElectionRecordAnnotationKey = "control-plane.alpha.kubernetes.io/leader" + + DefaultLeaseDuration = 15 * time.Second + DefaultRenewDeadline = 10 * time.Second + DefaultRetryPeriod = 2 * time.Second +) + +// NewLeadereElector creates a LeaderElector from a LeaderElecitionConfig +func NewLeaderElector(lec LeaderElectionConfig) (*LeaderElector, error) { + if lec.LeaseDuration <= lec.RenewDeadline { + return nil, fmt.Errorf("leaseDuration must be greater than renewDeadline") + } + if lec.RenewDeadline <= time.Duration(JitterFactor*float64(lec.RetryPeriod)) { + return nil, fmt.Errorf("renewDeadline must be greater than retryPeriod*JitterFactor") + } + if lec.Client == nil { + return nil, fmt.Errorf("Client must not be nil.") + } + if lec.EventRecorder == nil { + return nil, fmt.Errorf("EventRecorder must not be nil.") + } + return &LeaderElector{ + config: lec, + }, nil +} + +type LeaderElectionConfig struct { + // EndpointsMeta should contain a Name and a Namespace of an + // Endpoints object that the LeaderElector will attempt to lead. + EndpointsMeta api.ObjectMeta + // Identity is a unique identifier of the leader elector. + Identity string + + Client client.Interface + EventRecorder record.EventRecorder + + // LeaseDuration is the duration that non-leader candidates will + // wait to force acquire leadership. This is measured against time of + // last observed ack. + LeaseDuration time.Duration + // RenewDeadline is the duration that the acting master will retry + // refreshing leadership before giving up. + RenewDeadline time.Duration + // RetryPeriod is the duration the LeaderElector clients should wait + // between tries of actions. + RetryPeriod time.Duration + + // Callbacks are callbacks that are triggered during certain lifecycle + // events of the LeaderElector + Callbacks LeaderCallbacks +} + +// LeaderCallbacks are callbacks that are triggered during certain +// lifecycle events of the LeaderElector. These are invoked asynchronously. +// +// possible future callbacks: +// * OnChallenge() +type LeaderCallbacks struct { + // OnStartedLeading is called when a LeaderElector client starts leading + OnStartedLeading func(stop <-chan struct{}) + // OnStoppedLeading is called when a LeaderElector client stops leading + OnStoppedLeading func() + // OnNewLeader is called when the client observes a leader that is + // not the previously observed leader. This includes the first observed + // leader when the client starts. + OnNewLeader func(identity string) +} + +// LeaderElector is a leader election client. +// +// possible future methods: +// * (le *LeaderElector) IsLeader() +// * (le *LeaderElector) GetLeader() +type LeaderElector struct { + config LeaderElectionConfig + // internal bookkeeping + observedRecord LeaderElectionRecord + observedTime time.Time + // used to implement OnNewLeader(), may lag slightly from the + // value observedRecord.HolderIdentity if the transition has + // not yet been reported. + reportedLeader string +} + +// LeaderElectionRecord is the record that is stored in the leader election annotation. +// This information should be used for observational purposes only and could be replaced +// with a random string (e.g. UUID) with only slight modification of this code. +// TODO(mikedanese): this should potentially be versioned +type LeaderElectionRecord struct { + HolderIdentity string `json:"holderIdentity"` + LeaseDurationSeconds int `json:"leaseDurationSeconds"` + AcquireTime unversioned.Time `json:"acquireTime"` + RenewTime unversioned.Time `json:"renewTime"` + LeaderTransitions int `json:"leaderTransitions"` +} + +// Run starts the leader election loop +func (le *LeaderElector) Run() { + defer func() { + runtime.HandleCrash() + le.config.Callbacks.OnStoppedLeading() + }() + le.acquire() + stop := make(chan struct{}) + go le.config.Callbacks.OnStartedLeading(stop) + le.renew() + close(stop) +} + +// RunOrDie starts a client with the provided config or panics if the config +// fails to validate. +func RunOrDie(lec LeaderElectionConfig) { + le, err := NewLeaderElector(lec) + if err != nil { + panic(err) + } + le.Run() +} + +// GetLeader returns the identity of the last observed leader or returns the empty string if +// no leader has yet been observed. +func (le *LeaderElector) GetLeader() string { + return le.observedRecord.HolderIdentity +} + +// IsLeader returns true if the last observed leader was this client else returns false. +func (le *LeaderElector) IsLeader() bool { + return le.observedRecord.HolderIdentity == le.config.Identity +} + +// acquire loops calling tryAcquireOrRenew and returns immediately when tryAcquireOrRenew succeeds. +func (le *LeaderElector) acquire() { + stop := make(chan struct{}) + wait.Until(func() { + succeeded := le.tryAcquireOrRenew() + le.maybeReportTransition() + if !succeeded { + glog.V(4).Infof("failed to renew lease %v/%v", le.config.EndpointsMeta.Namespace, le.config.EndpointsMeta.Name) + time.Sleep(wait.Jitter(le.config.RetryPeriod, JitterFactor)) + return + } + le.config.EventRecorder.Eventf(&api.Endpoints{ObjectMeta: le.config.EndpointsMeta}, api.EventTypeNormal, "%v became leader", le.config.Identity) + glog.Infof("sucessfully acquired lease %v/%v", le.config.EndpointsMeta.Namespace, le.config.EndpointsMeta.Name) + close(stop) + }, 0, stop) +} + +// renew loops calling tryAcquireOrRenew and returns immediately when tryAcquireOrRenew fails. +func (le *LeaderElector) renew() { + stop := make(chan struct{}) + wait.Until(func() { + err := wait.Poll(le.config.RetryPeriod, le.config.RenewDeadline, func() (bool, error) { + return le.tryAcquireOrRenew(), nil + }) + le.maybeReportTransition() + if err == nil { + glog.V(4).Infof("succesfully renewed lease %v/%v", le.config.EndpointsMeta.Namespace, le.config.EndpointsMeta.Name) + return + } + le.config.EventRecorder.Eventf(&api.Endpoints{ObjectMeta: le.config.EndpointsMeta}, api.EventTypeNormal, "%v stopped leading", le.config.Identity) + glog.Infof("failed to renew lease %v/%v", le.config.EndpointsMeta.Namespace, le.config.EndpointsMeta.Name) + close(stop) + }, 0, stop) +} + +// tryAcquireOrRenew tries to acquire a leader lease if it is not already acquired, +// else it tries to renew the lease if it has already been acquired. Returns true +// on success else returns false. +func (le *LeaderElector) tryAcquireOrRenew() bool { + now := unversioned.Now() + leaderElectionRecord := LeaderElectionRecord{ + HolderIdentity: le.config.Identity, + LeaseDurationSeconds: int(le.config.LeaseDuration / time.Second), + RenewTime: now, + AcquireTime: now, + } + + e, err := le.config.Client.Endpoints(le.config.EndpointsMeta.Namespace).Get(le.config.EndpointsMeta.Name) + if err != nil { + if !errors.IsNotFound(err) { + return false + } + + leaderElectionRecordBytes, err := json.Marshal(leaderElectionRecord) + if err != nil { + return false + } + _, err = le.config.Client.Endpoints(le.config.EndpointsMeta.Namespace).Create(&api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: le.config.EndpointsMeta.Name, + Namespace: le.config.EndpointsMeta.Namespace, + Annotations: map[string]string{ + LeaderElectionRecordAnnotationKey: string(leaderElectionRecordBytes), + }, + }, + }) + if err != nil { + glog.Errorf("error initially creating endpoints: %v", err) + return false + } + le.observedRecord = leaderElectionRecord + le.observedTime = time.Now() + return true + } + + if e.Annotations == nil { + e.Annotations = make(map[string]string) + } + + var oldLeaderElectionRecord LeaderElectionRecord + + if oldLeaderElectionRecordBytes, found := e.Annotations[LeaderElectionRecordAnnotationKey]; found { + if err := json.Unmarshal([]byte(oldLeaderElectionRecordBytes), &oldLeaderElectionRecord); err != nil { + glog.Errorf("error unmarshaling leader election record: %v", err) + return false + } + if !reflect.DeepEqual(le.observedRecord, oldLeaderElectionRecord) { + le.observedRecord = oldLeaderElectionRecord + le.observedTime = time.Now() + } + if le.observedTime.Add(le.config.LeaseDuration).After(now.Time) && + oldLeaderElectionRecord.HolderIdentity != le.config.Identity { + glog.Infof("lock is held by %v and has not yet expired", oldLeaderElectionRecord.HolderIdentity) + return false + } + } + + // We're going to try to update. The leaderElectionRecord is set to it's default + // here. Let's correct it before updating. + if oldLeaderElectionRecord.HolderIdentity == le.config.Identity { + leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTime + } else { + leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1 + } + + leaderElectionRecordBytes, err := json.Marshal(leaderElectionRecord) + if err != nil { + glog.Errorf("err marshaling leader election record: %v", err) + return false + } + e.Annotations[LeaderElectionRecordAnnotationKey] = string(leaderElectionRecordBytes) + + _, err = le.config.Client.Endpoints(le.config.EndpointsMeta.Namespace).Update(e) + if err != nil { + glog.Errorf("err: %v", err) + return false + } + le.observedRecord = leaderElectionRecord + le.observedTime = time.Now() + return true +} + +func (l *LeaderElector) maybeReportTransition() { + if l.observedRecord.HolderIdentity == l.reportedLeader { + return + } + l.reportedLeader = l.observedRecord.HolderIdentity + if l.config.Callbacks.OnNewLeader != nil { + go l.config.Callbacks.OnNewLeader(l.reportedLeader) + } +} + +func DefaultLeaderElectionConfiguration() componentconfig.LeaderElectionConfiguration { + return componentconfig.LeaderElectionConfiguration{ + LeaderElect: false, + LeaseDuration: unversioned.Duration{Duration: DefaultLeaseDuration}, + RenewDeadline: unversioned.Duration{Duration: DefaultRenewDeadline}, + RetryPeriod: unversioned.Duration{Duration: DefaultRetryPeriod}, + } +} + +// BindFlags binds the common LeaderElectionCLIConfig flags to a flagset +func BindFlags(l *componentconfig.LeaderElectionConfiguration, fs *pflag.FlagSet) { + fs.BoolVar(&l.LeaderElect, "leader-elect", l.LeaderElect, ""+ + "Start a leader election client and gain leadership before "+ + "executing the main loop. Enable this when running replicated "+ + "components for high availability.") + fs.DurationVar(&l.LeaseDuration.Duration, "leader-elect-lease-duration", l.LeaseDuration.Duration, ""+ + "The duration that non-leader candidates will wait after observing a leadership "+ + "renewal until attempting to acquire leadership of a led but unrenewed leader "+ + "slot. This is effectively the maximum duration that a leader can be stopped "+ + "before it is replaced by another candidate. This is only applicable if leader "+ + "election is enabled.") + fs.DurationVar(&l.RenewDeadline.Duration, "leader-elect-renew-deadline", l.RenewDeadline.Duration, ""+ + "The interval between attempts by the acting master to renew a leadership slot "+ + "before it stops leading. This must be less than or equal to the lease duration. "+ + "This is only applicable if leader election is enabled.") + fs.DurationVar(&l.RetryPeriod.Duration, "leader-elect-retry-period", l.RetryPeriod.Duration, ""+ + "The duration the clients should wait between attempting acquisition and renewal "+ + "of a leadership. This is only applicable if leader election is enabled.") +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/leaderelection/leaderelection_test.go b/vendor/k8s.io/kubernetes/pkg/client/leaderelection/leaderelection_test.go new file mode 100644 index 000000000..cd880f73e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/leaderelection/leaderelection_test.go @@ -0,0 +1,258 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 leaderelection implements leader election of a set of endpoints. +// It uses an annotation in the endpoints object to store the record of the +// election state. + +package leaderelection + +import ( + "fmt" + "sync" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/client/unversioned/testclient" + "k8s.io/kubernetes/pkg/runtime" +) + +func TestTryAcquireOrRenew(t *testing.T) { + future := time.Now().Add(1000 * time.Hour) + past := time.Now().Add(-1000 * time.Hour) + + tests := []struct { + observedRecord LeaderElectionRecord + observedTime time.Time + reactors []struct { + verb string + reaction testclient.ReactionFunc + } + + expectSuccess bool + transitionLeader bool + outHolder string + }{ + // acquire from no endpoints + { + reactors: []struct { + verb string + reaction testclient.ReactionFunc + }{ + { + verb: "get", + reaction: func(action testclient.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, errors.NewNotFound(api.Resource(action.(testclient.GetAction).GetResource()), action.(testclient.GetAction).GetName()) + }, + }, + { + verb: "create", + reaction: func(action testclient.Action) (handled bool, ret runtime.Object, err error) { + return true, action.(testclient.CreateAction).GetObject().(*api.Endpoints), nil + }, + }, + }, + expectSuccess: true, + outHolder: "baz", + }, + // acquire from unled endpoints + { + reactors: []struct { + verb string + reaction testclient.ReactionFunc + }{ + { + verb: "get", + reaction: func(action testclient.Action) (handled bool, ret runtime.Object, err error) { + return true, &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Namespace: action.GetNamespace(), + Name: action.(testclient.GetAction).GetName(), + }, + }, nil + }, + }, + { + verb: "update", + reaction: func(action testclient.Action) (handled bool, ret runtime.Object, err error) { + return true, action.(testclient.CreateAction).GetObject().(*api.Endpoints), nil + }, + }, + }, + + expectSuccess: true, + transitionLeader: true, + outHolder: "baz", + }, + // acquire from led, unacked endpoints + { + reactors: []struct { + verb string + reaction testclient.ReactionFunc + }{ + { + verb: "get", + reaction: func(action testclient.Action) (handled bool, ret runtime.Object, err error) { + return true, &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Namespace: action.GetNamespace(), + Name: action.(testclient.GetAction).GetName(), + Annotations: map[string]string{ + LeaderElectionRecordAnnotationKey: `{"holderIdentity":"bing"}`, + }, + }, + }, nil + }, + }, + { + verb: "update", + reaction: func(action testclient.Action) (handled bool, ret runtime.Object, err error) { + return true, action.(testclient.CreateAction).GetObject().(*api.Endpoints), nil + }, + }, + }, + observedRecord: LeaderElectionRecord{HolderIdentity: "bing"}, + observedTime: past, + + expectSuccess: true, + transitionLeader: true, + outHolder: "baz", + }, + // don't acquire from led, acked endpoints + { + reactors: []struct { + verb string + reaction testclient.ReactionFunc + }{ + { + verb: "get", + reaction: func(action testclient.Action) (handled bool, ret runtime.Object, err error) { + return true, &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Namespace: action.GetNamespace(), + Name: action.(testclient.GetAction).GetName(), + Annotations: map[string]string{ + LeaderElectionRecordAnnotationKey: `{"holderIdentity":"bing"}`, + }, + }, + }, nil + }, + }, + }, + observedTime: future, + + expectSuccess: false, + outHolder: "bing", + }, + // renew already acquired endpoints + { + reactors: []struct { + verb string + reaction testclient.ReactionFunc + }{ + { + verb: "get", + reaction: func(action testclient.Action) (handled bool, ret runtime.Object, err error) { + return true, &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Namespace: action.GetNamespace(), + Name: action.(testclient.GetAction).GetName(), + Annotations: map[string]string{ + LeaderElectionRecordAnnotationKey: `{"holderIdentity":"baz"}`, + }, + }, + }, nil + }, + }, + { + verb: "update", + reaction: func(action testclient.Action) (handled bool, ret runtime.Object, err error) { + return true, action.(testclient.CreateAction).GetObject().(*api.Endpoints), nil + }, + }, + }, + observedTime: future, + observedRecord: LeaderElectionRecord{HolderIdentity: "baz"}, + + expectSuccess: true, + outHolder: "baz", + }, + } + + for i, test := range tests { + // OnNewLeader is called async so we have to wait for it. + var wg sync.WaitGroup + wg.Add(1) + var reportedLeader string + + lec := LeaderElectionConfig{ + EndpointsMeta: api.ObjectMeta{Namespace: "foo", Name: "bar"}, + Identity: "baz", + EventRecorder: &record.FakeRecorder{}, + LeaseDuration: 10 * time.Second, + Callbacks: LeaderCallbacks{ + OnNewLeader: func(l string) { + defer wg.Done() + reportedLeader = l + }, + }, + } + c := &testclient.Fake{} + for _, reactor := range test.reactors { + c.AddReactor(reactor.verb, "endpoints", reactor.reaction) + } + c.AddReactor("*", "*", func(action testclient.Action) (bool, runtime.Object, error) { + t.Errorf("[%v] unreachable action. testclient called too many times: %+v", i, action) + return true, nil, fmt.Errorf("uncreachable action") + }) + + le := &LeaderElector{ + config: lec, + observedRecord: test.observedRecord, + observedTime: test.observedTime, + } + le.config.Client = c + + if test.expectSuccess != le.tryAcquireOrRenew() { + t.Errorf("[%v]unexpected result of tryAcquireOrRenew: [succeded=%v]", i, !test.expectSuccess) + } + + le.observedRecord.AcquireTime = unversioned.Time{} + le.observedRecord.RenewTime = unversioned.Time{} + if le.observedRecord.HolderIdentity != test.outHolder { + t.Errorf("[%v]expected holder:\n\t%+v\ngot:\n\t%+v", i, test.outHolder, le.observedRecord.HolderIdentity) + } + if len(test.reactors) != len(c.Actions()) { + t.Errorf("[%v]wrong number of api interactions", i) + } + if test.transitionLeader && le.observedRecord.LeaderTransitions != 1 { + t.Errorf("[%v]leader should have transitioned but did not", i) + } + if !test.transitionLeader && le.observedRecord.LeaderTransitions != 0 { + t.Errorf("[%v]leader should not have transitioned but did", i) + } + + le.maybeReportTransition() + wg.Wait() + if reportedLeader != test.outHolder { + t.Errorf("[%v]reported leader was not the new leader. expected %q, got %q", i, test.outHolder, reportedLeader) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/record/doc.go b/vendor/k8s.io/kubernetes/pkg/client/record/doc.go new file mode 100644 index 000000000..d95515432 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/record/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 record has all client logic for recording and reporting events. +package record diff --git a/vendor/k8s.io/kubernetes/pkg/client/record/event.go b/vendor/k8s.io/kubernetes/pkg/client/record/event.go new file mode 100644 index 000000000..a2ff6cd0b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/record/event.go @@ -0,0 +1,315 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 record + +import ( + "fmt" + "math/rand" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/watch" + + "github.com/golang/glog" +) + +const maxTriesPerEvent = 12 + +var defaultSleepDuration = 10 * time.Second + +const maxQueuedEvents = 1000 + +// EventSink knows how to store events (client.Client implements it.) +// EventSink must respect the namespace that will be embedded in 'event'. +// It is assumed that EventSink will return the same sorts of errors as +// pkg/client's REST client. +type EventSink interface { + Create(event *api.Event) (*api.Event, error) + Update(event *api.Event) (*api.Event, error) + Patch(oldEvent *api.Event, data []byte) (*api.Event, error) +} + +// EventRecorder knows how to record events on behalf of an EventSource. +type EventRecorder interface { + // Event constructs an event from the given information and puts it in the queue for sending. + // 'object' is the object this event is about. Event will make a reference-- or you may also + // pass a reference to the object directly. + // 'type' of this event, and can be one of Normal, Warning. New types could be added in future + // 'reason' is the reason this event is generated. 'reason' should be short and unique; it + // should be in UpperCamelCase format (starting with a capital letter). "reason" will be used + // to automate handling of events, so imagine people writing switch statements to handle them. + // You want to make that easy. + // 'message' is intended to be human readable. + // + // The resulting event will be created in the same namespace as the reference object. + Event(object runtime.Object, eventtype, reason, message string) + + // Eventf is just like Event, but with Sprintf for the message field. + Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) + + // PastEventf is just like Eventf, but with an option to specify the event's 'timestamp' field. + PastEventf(object runtime.Object, timestamp unversioned.Time, eventtype, reason, messageFmt string, args ...interface{}) +} + +// EventBroadcaster knows how to receive events and send them to any EventSink, watcher, or log. +type EventBroadcaster interface { + // StartEventWatcher starts sending events received from this EventBroadcaster to the given + // event handler function. The return value can be ignored or used to stop recording, if + // desired. + StartEventWatcher(eventHandler func(*api.Event)) watch.Interface + + // StartRecordingToSink starts sending events received from this EventBroadcaster to the given + // sink. The return value can be ignored or used to stop recording, if desired. + StartRecordingToSink(sink EventSink) watch.Interface + + // StartLogging starts sending events received from this EventBroadcaster to the given logging + // function. The return value can be ignored or used to stop recording, if desired. + StartLogging(logf func(format string, args ...interface{})) watch.Interface + + // NewRecorder returns an EventRecorder that can be used to send events to this EventBroadcaster + // with the event source set to the given event source. + NewRecorder(source api.EventSource) EventRecorder +} + +// Creates a new event broadcaster. +func NewBroadcaster() EventBroadcaster { + return &eventBroadcasterImpl{watch.NewBroadcaster(maxQueuedEvents, watch.DropIfChannelFull), defaultSleepDuration} +} + +func NewBroadcasterForTests(sleepDuration time.Duration) EventBroadcaster { + return &eventBroadcasterImpl{watch.NewBroadcaster(maxQueuedEvents, watch.DropIfChannelFull), sleepDuration} +} + +type eventBroadcasterImpl struct { + *watch.Broadcaster + sleepDuration time.Duration +} + +// StartRecordingToSink starts sending events received from the specified eventBroadcaster to the given sink. +// The return value can be ignored or used to stop recording, if desired. +// TODO: make me an object with parameterizable queue length and retry interval +func (eventBroadcaster *eventBroadcasterImpl) StartRecordingToSink(sink EventSink) watch.Interface { + // The default math/rand package functions aren't thread safe, so create a + // new Rand object for each StartRecording call. + randGen := rand.New(rand.NewSource(time.Now().UnixNano())) + eventCorrelator := NewEventCorrelator(util.RealClock{}) + return eventBroadcaster.StartEventWatcher( + func(event *api.Event) { + recordToSink(sink, event, eventCorrelator, randGen, eventBroadcaster.sleepDuration) + }) +} + +func recordToSink(sink EventSink, event *api.Event, eventCorrelator *EventCorrelator, randGen *rand.Rand, sleepDuration time.Duration) { + // Make a copy before modification, because there could be multiple listeners. + // Events are safe to copy like this. + eventCopy := *event + event = &eventCopy + result, err := eventCorrelator.EventCorrelate(event) + if err != nil { + utilruntime.HandleError(err) + } + if result.Skip { + return + } + tries := 0 + for { + if recordEvent(sink, result.Event, result.Patch, result.Event.Count > 1, eventCorrelator) { + break + } + tries++ + if tries >= maxTriesPerEvent { + glog.Errorf("Unable to write event '%#v' (retry limit exceeded!)", event) + break + } + // Randomize the first sleep so that various clients won't all be + // synced up if the master goes down. + if tries == 1 { + time.Sleep(time.Duration(float64(sleepDuration) * randGen.Float64())) + } else { + time.Sleep(sleepDuration) + } + } +} + +func isKeyNotFoundError(err error) bool { + statusErr, _ := err.(*errors.StatusError) + // At the moment the server is returning 500 instead of a more specific + // error. When changing this remember that it should be backward compatible + // with old api servers that may be still returning 500. + if statusErr != nil && statusErr.Status().Code == 500 { + return true + } + return false +} + +// recordEvent attempts to write event to a sink. It returns true if the event +// was successfully recorded or discarded, false if it should be retried. +// If updateExistingEvent is false, it creates a new event, otherwise it updates +// existing event. +func recordEvent(sink EventSink, event *api.Event, patch []byte, updateExistingEvent bool, eventCorrelator *EventCorrelator) bool { + var newEvent *api.Event + var err error + if updateExistingEvent { + newEvent, err = sink.Patch(event, patch) + } + // Update can fail because the event may have been removed and it no longer exists. + if !updateExistingEvent || (updateExistingEvent && isKeyNotFoundError(err)) { + // Making sure that ResourceVersion is empty on creation + event.ResourceVersion = "" + newEvent, err = sink.Create(event) + } + if err == nil { + // we need to update our event correlator with the server returned state to handle name/resourceversion + eventCorrelator.UpdateState(newEvent) + return true + } + + // If we can't contact the server, then hold everything while we keep trying. + // Otherwise, something about the event is malformed and we should abandon it. + switch err.(type) { + case *restclient.RequestConstructionError: + // We will construct the request the same next time, so don't keep trying. + glog.Errorf("Unable to construct event '%#v': '%v' (will not retry!)", event, err) + return true + case *errors.StatusError: + if errors.IsAlreadyExists(err) { + glog.V(5).Infof("Server rejected event '%#v': '%v' (will not retry!)", event, err) + } else { + glog.Errorf("Server rejected event '%#v': '%v' (will not retry!)", event, err) + } + return true + case *errors.UnexpectedObjectError: + // We don't expect this; it implies the server's response didn't match a + // known pattern. Go ahead and retry. + default: + // This case includes actual http transport errors. Go ahead and retry. + } + glog.Errorf("Unable to write event: '%v' (may retry after sleeping)", err) + return false +} + +// StartLogging starts sending events received from this EventBroadcaster to the given logging function. +// The return value can be ignored or used to stop recording, if desired. +func (eventBroadcaster *eventBroadcasterImpl) StartLogging(logf func(format string, args ...interface{})) watch.Interface { + return eventBroadcaster.StartEventWatcher( + func(e *api.Event) { + logf("Event(%#v): type: '%v' reason: '%v' %v", e.InvolvedObject, e.Type, e.Reason, e.Message) + }) +} + +// StartEventWatcher starts sending events received from this EventBroadcaster to the given event handler function. +// The return value can be ignored or used to stop recording, if desired. +func (eventBroadcaster *eventBroadcasterImpl) StartEventWatcher(eventHandler func(*api.Event)) watch.Interface { + watcher := eventBroadcaster.Watch() + go func() { + defer utilruntime.HandleCrash() + for { + watchEvent, open := <-watcher.ResultChan() + if !open { + return + } + event, ok := watchEvent.Object.(*api.Event) + if !ok { + // This is all local, so there's no reason this should + // ever happen. + continue + } + eventHandler(event) + } + }() + return watcher +} + +// NewRecorder returns an EventRecorder that records events with the given event source. +func (eventBroadcaster *eventBroadcasterImpl) NewRecorder(source api.EventSource) EventRecorder { + return &recorderImpl{source, eventBroadcaster.Broadcaster, util.RealClock{}} +} + +type recorderImpl struct { + source api.EventSource + *watch.Broadcaster + clock util.Clock +} + +func (recorder *recorderImpl) generateEvent(object runtime.Object, timestamp unversioned.Time, eventtype, reason, message string) { + ref, err := api.GetReference(object) + if err != nil { + glog.Errorf("Could not construct reference to: '%#v' due to: '%v'. Will not report event: '%v' '%v' '%v'", object, err, eventtype, reason, message) + return + } + + if !validateEventType(eventtype) { + glog.Errorf("Unsupported event type: '%v'", eventtype) + return + } + + event := recorder.makeEvent(ref, eventtype, reason, message) + event.Source = recorder.source + + go func() { + // NOTE: events should be a non-blocking operation + defer utilruntime.HandleCrash() + recorder.Action(watch.Added, event) + }() +} + +func validateEventType(eventtype string) bool { + switch eventtype { + case api.EventTypeNormal, api.EventTypeWarning: + return true + } + return false +} + +func (recorder *recorderImpl) Event(object runtime.Object, eventtype, reason, message string) { + recorder.generateEvent(object, unversioned.Now(), eventtype, reason, message) +} + +func (recorder *recorderImpl) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) { + recorder.Event(object, eventtype, reason, fmt.Sprintf(messageFmt, args...)) +} + +func (recorder *recorderImpl) PastEventf(object runtime.Object, timestamp unversioned.Time, eventtype, reason, messageFmt string, args ...interface{}) { + recorder.generateEvent(object, timestamp, eventtype, reason, fmt.Sprintf(messageFmt, args...)) +} + +func (recorder *recorderImpl) makeEvent(ref *api.ObjectReference, eventtype, reason, message string) *api.Event { + t := unversioned.Time{Time: recorder.clock.Now()} + namespace := ref.Namespace + if namespace == "" { + namespace = api.NamespaceDefault + } + return &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()), + Namespace: namespace, + }, + InvolvedObject: *ref, + Reason: reason, + Message: message, + FirstTimestamp: t, + LastTimestamp: t, + Count: 1, + Type: eventtype, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/record/event_test.go b/vendor/k8s.io/kubernetes/pkg/client/record/event_test.go new file mode 100644 index 000000000..57959c215 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/record/event_test.go @@ -0,0 +1,889 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 record + +import ( + "encoding/json" + "fmt" + "math/rand" + "strconv" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + _ "k8s.io/kubernetes/pkg/api/install" // To register api.Pod used in tests below + "k8s.io/kubernetes/pkg/client/restclient" + k8sruntime "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/strategicpatch" +) + +type testEventSink struct { + OnCreate func(e *api.Event) (*api.Event, error) + OnUpdate func(e *api.Event) (*api.Event, error) + OnPatch func(e *api.Event, p []byte) (*api.Event, error) +} + +// CreateEvent records the event for testing. +func (t *testEventSink) Create(e *api.Event) (*api.Event, error) { + if t.OnCreate != nil { + return t.OnCreate(e) + } + return e, nil +} + +// UpdateEvent records the event for testing. +func (t *testEventSink) Update(e *api.Event) (*api.Event, error) { + if t.OnUpdate != nil { + return t.OnUpdate(e) + } + return e, nil +} + +// PatchEvent records the event for testing. +func (t *testEventSink) Patch(e *api.Event, p []byte) (*api.Event, error) { + if t.OnPatch != nil { + return t.OnPatch(e, p) + } + return e, nil +} + +type OnCreateFunc func(*api.Event) (*api.Event, error) + +func OnCreateFactory(testCache map[string]*api.Event, createEvent chan<- *api.Event) OnCreateFunc { + return func(event *api.Event) (*api.Event, error) { + testCache[getEventKey(event)] = event + createEvent <- event + return event, nil + } +} + +type OnPatchFunc func(*api.Event, []byte) (*api.Event, error) + +func OnPatchFactory(testCache map[string]*api.Event, patchEvent chan<- *api.Event) OnPatchFunc { + return func(event *api.Event, patch []byte) (*api.Event, error) { + cachedEvent, found := testCache[getEventKey(event)] + if !found { + return nil, fmt.Errorf("unexpected error: couldn't find Event in testCache.") + } + originalData, err := json.Marshal(cachedEvent) + if err != nil { + return nil, fmt.Errorf("unexpected error: %v", err) + } + patched, err := strategicpatch.StrategicMergePatch(originalData, patch, event) + if err != nil { + return nil, fmt.Errorf("unexpected error: %v", err) + } + patchedObj := &api.Event{} + err = json.Unmarshal(patched, patchedObj) + if err != nil { + return nil, fmt.Errorf("unexpected error: %v", err) + } + patchEvent <- patchedObj + return patchedObj, nil + } +} + +func TestEventf(t *testing.T) { + testPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + SelfLink: "/api/version/pods/foo", + Name: "foo", + Namespace: "baz", + UID: "bar", + }, + } + testPod2 := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + SelfLink: "/api/version/pods/foo", + Name: "foo", + Namespace: "baz", + UID: "differentUid", + }, + } + testRef, err := api.GetPartialReference(testPod, "spec.containers[2]") + testRef2, err := api.GetPartialReference(testPod2, "spec.containers[3]") + if err != nil { + t.Fatal(err) + } + table := []struct { + obj k8sruntime.Object + eventtype string + reason string + messageFmt string + elements []interface{} + expect *api.Event + expectLog string + expectUpdate bool + }{ + { + obj: testRef, + eventtype: api.EventTypeNormal, + reason: "Started", + messageFmt: "some verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "bar", + APIVersion: "version", + FieldPath: "spec.containers[2]", + }, + Reason: "Started", + Message: "some verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 1, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`, + expectUpdate: false, + }, + { + obj: testPod, + eventtype: api.EventTypeNormal, + reason: "Killed", + messageFmt: "some other verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "bar", + APIVersion: "version", + }, + Reason: "Killed", + Message: "some other verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 1, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"version", ResourceVersion:"", FieldPath:""}): type: 'Normal' reason: 'Killed' some other verbose message: 1`, + expectUpdate: false, + }, + { + obj: testRef, + eventtype: api.EventTypeNormal, + reason: "Started", + messageFmt: "some verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "bar", + APIVersion: "version", + FieldPath: "spec.containers[2]", + }, + Reason: "Started", + Message: "some verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 2, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`, + expectUpdate: true, + }, + { + obj: testRef2, + eventtype: api.EventTypeNormal, + reason: "Started", + messageFmt: "some verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "differentUid", + APIVersion: "version", + FieldPath: "spec.containers[3]", + }, + Reason: "Started", + Message: "some verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 1, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Started' some verbose message: 1`, + expectUpdate: false, + }, + { + obj: testRef, + eventtype: api.EventTypeNormal, + reason: "Started", + messageFmt: "some verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "bar", + APIVersion: "version", + FieldPath: "spec.containers[2]", + }, + Reason: "Started", + Message: "some verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 3, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`, + expectUpdate: true, + }, + { + obj: testRef2, + eventtype: api.EventTypeNormal, + reason: "Stopped", + messageFmt: "some verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "differentUid", + APIVersion: "version", + FieldPath: "spec.containers[3]", + }, + Reason: "Stopped", + Message: "some verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 1, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1`, + expectUpdate: false, + }, + { + obj: testRef2, + eventtype: api.EventTypeNormal, + reason: "Stopped", + messageFmt: "some verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "differentUid", + APIVersion: "version", + FieldPath: "spec.containers[3]", + }, + Reason: "Stopped", + Message: "some verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 2, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1`, + expectUpdate: true, + }, + } + + testCache := map[string]*api.Event{} + logCalled := make(chan struct{}) + createEvent := make(chan *api.Event) + updateEvent := make(chan *api.Event) + patchEvent := make(chan *api.Event) + testEvents := testEventSink{ + OnCreate: OnCreateFactory(testCache, createEvent), + OnUpdate: func(event *api.Event) (*api.Event, error) { + updateEvent <- event + return event, nil + }, + OnPatch: OnPatchFactory(testCache, patchEvent), + } + eventBroadcaster := NewBroadcasterForTests(0) + sinkWatcher := eventBroadcaster.StartRecordingToSink(&testEvents) + + clock := util.NewFakeClock(time.Now()) + recorder := recorderWithFakeClock(api.EventSource{Component: "eventTest"}, eventBroadcaster, clock) + for index, item := range table { + clock.Step(1 * time.Second) + // TODO: uncomment this after we upgrade to Go 1.6.1. + // testing.(*common).log() is racing with testing.(*T).report() in Go 1.6. + // See #23533 for more details. + // logWatcher1 := eventBroadcaster.StartLogging(t.Logf) // Prove that it is useful + logWatcher2 := eventBroadcaster.StartLogging(func(formatter string, args ...interface{}) { + if e, a := item.expectLog, fmt.Sprintf(formatter, args...); e != a { + t.Errorf("Expected '%v', got '%v'", e, a) + } + logCalled <- struct{}{} + }) + recorder.Eventf(item.obj, item.eventtype, item.reason, item.messageFmt, item.elements...) + + <-logCalled + + // validate event + if item.expectUpdate { + actualEvent := <-patchEvent + validateEvent(string(index), actualEvent, item.expect, t) + } else { + actualEvent := <-createEvent + validateEvent(string(index), actualEvent, item.expect, t) + } + // TODO: uncomment this after we upgrade to Go 1.6.1. + // logWatcher1.Stop() + logWatcher2.Stop() + } + sinkWatcher.Stop() +} + +func recorderWithFakeClock(eventSource api.EventSource, eventBroadcaster EventBroadcaster, clock util.Clock) EventRecorder { + return &recorderImpl{eventSource, eventBroadcaster.(*eventBroadcasterImpl).Broadcaster, clock} +} + +func TestWriteEventError(t *testing.T) { + type entry struct { + timesToSendError int + attemptsWanted int + err error + } + table := map[string]*entry{ + "giveUp1": { + timesToSendError: 1000, + attemptsWanted: 1, + err: &restclient.RequestConstructionError{}, + }, + "giveUp2": { + timesToSendError: 1000, + attemptsWanted: 1, + err: &errors.StatusError{}, + }, + "retry1": { + timesToSendError: 1000, + attemptsWanted: 12, + err: &errors.UnexpectedObjectError{}, + }, + "retry2": { + timesToSendError: 1000, + attemptsWanted: 12, + err: fmt.Errorf("A weird error"), + }, + "succeedEventually": { + timesToSendError: 2, + attemptsWanted: 2, + err: fmt.Errorf("A weird error"), + }, + } + + eventCorrelator := NewEventCorrelator(util.RealClock{}) + randGen := rand.New(rand.NewSource(time.Now().UnixNano())) + + for caseName, ent := range table { + attempts := 0 + sink := &testEventSink{ + OnCreate: func(event *api.Event) (*api.Event, error) { + attempts++ + if attempts < ent.timesToSendError { + return nil, ent.err + } + return event, nil + }, + } + ev := &api.Event{} + recordToSink(sink, ev, eventCorrelator, randGen, 0) + if attempts != ent.attemptsWanted { + t.Errorf("case %v: wanted %d, got %d attempts", caseName, ent.attemptsWanted, attempts) + } + } +} + +func TestLotsOfEvents(t *testing.T) { + recorderCalled := make(chan struct{}) + loggerCalled := make(chan struct{}) + + // Fail each event a few times to ensure there's some load on the tested code. + var counts [1000]int + testEvents := testEventSink{ + OnCreate: func(event *api.Event) (*api.Event, error) { + num, err := strconv.Atoi(event.Message) + if err != nil { + t.Error(err) + return event, nil + } + counts[num]++ + if counts[num] < 5 { + return nil, fmt.Errorf("fake error") + } + recorderCalled <- struct{}{} + return event, nil + }, + } + + eventBroadcaster := NewBroadcasterForTests(0) + sinkWatcher := eventBroadcaster.StartRecordingToSink(&testEvents) + logWatcher := eventBroadcaster.StartLogging(func(formatter string, args ...interface{}) { + loggerCalled <- struct{}{} + }) + recorder := eventBroadcaster.NewRecorder(api.EventSource{Component: "eventTest"}) + ref := &api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "bar", + APIVersion: "version", + } + for i := 0; i < maxQueuedEvents; i++ { + // we need to vary the reason to prevent aggregation + go recorder.Eventf(ref, api.EventTypeNormal, "Reason-"+string(i), strconv.Itoa(i)) + } + // Make sure no events were dropped by either of the listeners. + for i := 0; i < maxQueuedEvents; i++ { + <-recorderCalled + <-loggerCalled + } + // Make sure that every event was attempted 5 times + for i := 0; i < maxQueuedEvents; i++ { + if counts[i] < 5 { + t.Errorf("Only attempted to record event '%d' %d times.", i, counts[i]) + } + } + sinkWatcher.Stop() + logWatcher.Stop() +} + +func TestEventfNoNamespace(t *testing.T) { + testPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + SelfLink: "/api/version/pods/foo", + Name: "foo", + UID: "bar", + }, + } + testRef, err := api.GetPartialReference(testPod, "spec.containers[2]") + if err != nil { + t.Fatal(err) + } + table := []struct { + obj k8sruntime.Object + eventtype string + reason string + messageFmt string + elements []interface{} + expect *api.Event + expectLog string + expectUpdate bool + }{ + { + obj: testRef, + eventtype: api.EventTypeNormal, + reason: "Started", + messageFmt: "some verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "default", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "", + UID: "bar", + APIVersion: "version", + FieldPath: "spec.containers[2]", + }, + Reason: "Started", + Message: "some verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 1, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"", Name:"foo", UID:"bar", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`, + expectUpdate: false, + }, + } + + testCache := map[string]*api.Event{} + logCalled := make(chan struct{}) + createEvent := make(chan *api.Event) + updateEvent := make(chan *api.Event) + patchEvent := make(chan *api.Event) + testEvents := testEventSink{ + OnCreate: OnCreateFactory(testCache, createEvent), + OnUpdate: func(event *api.Event) (*api.Event, error) { + updateEvent <- event + return event, nil + }, + OnPatch: OnPatchFactory(testCache, patchEvent), + } + eventBroadcaster := NewBroadcasterForTests(0) + sinkWatcher := eventBroadcaster.StartRecordingToSink(&testEvents) + + clock := util.NewFakeClock(time.Now()) + recorder := recorderWithFakeClock(api.EventSource{Component: "eventTest"}, eventBroadcaster, clock) + + for index, item := range table { + clock.Step(1 * time.Second) + // TODO: uncomment this after we upgrade to Go 1.6.1. + // testing.(*common).log() is racing with testing.(*T).report() in Go 1.6. + // See #23533 for more details. + // logWatcher1 := eventBroadcaster.StartLogging(t.Logf) // Prove that it is useful + logWatcher2 := eventBroadcaster.StartLogging(func(formatter string, args ...interface{}) { + if e, a := item.expectLog, fmt.Sprintf(formatter, args...); e != a { + t.Errorf("Expected '%v', got '%v'", e, a) + } + logCalled <- struct{}{} + }) + recorder.Eventf(item.obj, item.eventtype, item.reason, item.messageFmt, item.elements...) + + <-logCalled + + // validate event + if item.expectUpdate { + actualEvent := <-patchEvent + validateEvent(string(index), actualEvent, item.expect, t) + } else { + actualEvent := <-createEvent + validateEvent(string(index), actualEvent, item.expect, t) + } + + // TODO: uncomment this after we upgrade to Go 1.6.1. + // logWatcher1.Stop() + logWatcher2.Stop() + } + sinkWatcher.Stop() +} + +func TestMultiSinkCache(t *testing.T) { + testPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + SelfLink: "/api/version/pods/foo", + Name: "foo", + Namespace: "baz", + UID: "bar", + }, + } + testPod2 := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + SelfLink: "/api/version/pods/foo", + Name: "foo", + Namespace: "baz", + UID: "differentUid", + }, + } + testRef, err := api.GetPartialReference(testPod, "spec.containers[2]") + testRef2, err := api.GetPartialReference(testPod2, "spec.containers[3]") + if err != nil { + t.Fatal(err) + } + table := []struct { + obj k8sruntime.Object + eventtype string + reason string + messageFmt string + elements []interface{} + expect *api.Event + expectLog string + expectUpdate bool + }{ + { + obj: testRef, + eventtype: api.EventTypeNormal, + reason: "Started", + messageFmt: "some verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "bar", + APIVersion: "version", + FieldPath: "spec.containers[2]", + }, + Reason: "Started", + Message: "some verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 1, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`, + expectUpdate: false, + }, + { + obj: testPod, + eventtype: api.EventTypeNormal, + reason: "Killed", + messageFmt: "some other verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "bar", + APIVersion: "version", + }, + Reason: "Killed", + Message: "some other verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 1, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"version", ResourceVersion:"", FieldPath:""}): type: 'Normal' reason: 'Killed' some other verbose message: 1`, + expectUpdate: false, + }, + { + obj: testRef, + eventtype: api.EventTypeNormal, + reason: "Started", + messageFmt: "some verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "bar", + APIVersion: "version", + FieldPath: "spec.containers[2]", + }, + Reason: "Started", + Message: "some verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 2, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`, + expectUpdate: true, + }, + { + obj: testRef2, + eventtype: api.EventTypeNormal, + reason: "Started", + messageFmt: "some verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "differentUid", + APIVersion: "version", + FieldPath: "spec.containers[3]", + }, + Reason: "Started", + Message: "some verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 1, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Started' some verbose message: 1`, + expectUpdate: false, + }, + { + obj: testRef, + eventtype: api.EventTypeNormal, + reason: "Started", + messageFmt: "some verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "bar", + APIVersion: "version", + FieldPath: "spec.containers[2]", + }, + Reason: "Started", + Message: "some verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 3, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`, + expectUpdate: true, + }, + { + obj: testRef2, + eventtype: api.EventTypeNormal, + reason: "Stopped", + messageFmt: "some verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "differentUid", + APIVersion: "version", + FieldPath: "spec.containers[3]", + }, + Reason: "Stopped", + Message: "some verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 1, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1`, + expectUpdate: false, + }, + { + obj: testRef2, + eventtype: api.EventTypeNormal, + reason: "Stopped", + messageFmt: "some verbose message: %v", + elements: []interface{}{1}, + expect: &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "baz", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "differentUid", + APIVersion: "version", + FieldPath: "spec.containers[3]", + }, + Reason: "Stopped", + Message: "some verbose message: 1", + Source: api.EventSource{Component: "eventTest"}, + Count: 2, + Type: api.EventTypeNormal, + }, + expectLog: `Event(api.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1`, + expectUpdate: true, + }, + } + + testCache := map[string]*api.Event{} + createEvent := make(chan *api.Event) + updateEvent := make(chan *api.Event) + patchEvent := make(chan *api.Event) + testEvents := testEventSink{ + OnCreate: OnCreateFactory(testCache, createEvent), + OnUpdate: func(event *api.Event) (*api.Event, error) { + updateEvent <- event + return event, nil + }, + OnPatch: OnPatchFactory(testCache, patchEvent), + } + + testCache2 := map[string]*api.Event{} + createEvent2 := make(chan *api.Event) + updateEvent2 := make(chan *api.Event) + patchEvent2 := make(chan *api.Event) + testEvents2 := testEventSink{ + OnCreate: OnCreateFactory(testCache2, createEvent2), + OnUpdate: func(event *api.Event) (*api.Event, error) { + updateEvent2 <- event + return event, nil + }, + OnPatch: OnPatchFactory(testCache2, patchEvent2), + } + + eventBroadcaster := NewBroadcasterForTests(0) + clock := util.NewFakeClock(time.Now()) + recorder := recorderWithFakeClock(api.EventSource{Component: "eventTest"}, eventBroadcaster, clock) + + sinkWatcher := eventBroadcaster.StartRecordingToSink(&testEvents) + for index, item := range table { + clock.Step(1 * time.Second) + recorder.Eventf(item.obj, item.eventtype, item.reason, item.messageFmt, item.elements...) + + // validate event + if item.expectUpdate { + actualEvent := <-patchEvent + validateEvent(string(index), actualEvent, item.expect, t) + } else { + actualEvent := <-createEvent + validateEvent(string(index), actualEvent, item.expect, t) + } + } + + // Another StartRecordingToSink call should start to record events with new clean cache. + sinkWatcher2 := eventBroadcaster.StartRecordingToSink(&testEvents2) + for index, item := range table { + clock.Step(1 * time.Second) + recorder.Eventf(item.obj, item.eventtype, item.reason, item.messageFmt, item.elements...) + + // validate event + if item.expectUpdate { + actualEvent := <-patchEvent2 + validateEvent(string(index), actualEvent, item.expect, t) + } else { + actualEvent := <-createEvent2 + validateEvent(string(index), actualEvent, item.expect, t) + } + } + + sinkWatcher.Stop() + sinkWatcher2.Stop() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/record/events_cache.go b/vendor/k8s.io/kubernetes/pkg/client/record/events_cache.go new file mode 100644 index 000000000..5d93ba6a6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/record/events_cache.go @@ -0,0 +1,360 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 record + +import ( + "encoding/json" + "fmt" + "strings" + "sync" + "time" + + "github.com/golang/groupcache/lru" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/strategicpatch" +) + +const ( + maxLruCacheEntries = 4096 + + // if we see the same event that varies only by message + // more than 10 times in a 10 minute period, aggregate the event + defaultAggregateMaxEvents = 10 + defaultAggregateIntervalInSeconds = 600 +) + +// getEventKey builds unique event key based on source, involvedObject, reason, message +func getEventKey(event *api.Event) string { + return strings.Join([]string{ + event.Source.Component, + event.Source.Host, + event.InvolvedObject.Kind, + event.InvolvedObject.Namespace, + event.InvolvedObject.Name, + string(event.InvolvedObject.UID), + event.InvolvedObject.APIVersion, + event.Type, + event.Reason, + event.Message, + }, + "") +} + +// EventFilterFunc is a function that returns true if the event should be skipped +type EventFilterFunc func(event *api.Event) bool + +// DefaultEventFilterFunc returns false for all incoming events +func DefaultEventFilterFunc(event *api.Event) bool { + return false +} + +// EventAggregatorKeyFunc is responsible for grouping events for aggregation +// It returns a tuple of the following: +// aggregateKey - key the identifies the aggregate group to bucket this event +// localKey - key that makes this event in the local group +type EventAggregatorKeyFunc func(event *api.Event) (aggregateKey string, localKey string) + +// EventAggregatorByReasonFunc aggregates events by exact match on event.Source, event.InvolvedObject, event.Type and event.Reason +func EventAggregatorByReasonFunc(event *api.Event) (string, string) { + return strings.Join([]string{ + event.Source.Component, + event.Source.Host, + event.InvolvedObject.Kind, + event.InvolvedObject.Namespace, + event.InvolvedObject.Name, + string(event.InvolvedObject.UID), + event.InvolvedObject.APIVersion, + event.Type, + event.Reason, + }, + ""), event.Message +} + +// EventAggregatorMessageFunc is responsible for producing an aggregation message +type EventAggregatorMessageFunc func(event *api.Event) string + +// EventAggregratorByReasonMessageFunc returns an aggregate message by prefixing the incoming message +func EventAggregatorByReasonMessageFunc(event *api.Event) string { + return "(events with common reason combined)" +} + +// EventAggregator identifies similar events and aggregates them into a single event +type EventAggregator struct { + sync.RWMutex + + // The cache that manages aggregation state + cache *lru.Cache + + // The function that groups events for aggregation + keyFunc EventAggregatorKeyFunc + + // The function that generates a message for an aggregate event + messageFunc EventAggregatorMessageFunc + + // The maximum number of events in the specified interval before aggregation occurs + maxEvents int + + // The amount of time in seconds that must transpire since the last occurrence of a similar event before it's considered new + maxIntervalInSeconds int + + // clock is used to allow for testing over a time interval + clock util.Clock +} + +// NewEventAggregator returns a new instance of an EventAggregator +func NewEventAggregator(lruCacheSize int, keyFunc EventAggregatorKeyFunc, messageFunc EventAggregatorMessageFunc, + maxEvents int, maxIntervalInSeconds int, clock util.Clock) *EventAggregator { + return &EventAggregator{ + cache: lru.New(lruCacheSize), + keyFunc: keyFunc, + messageFunc: messageFunc, + maxEvents: maxEvents, + maxIntervalInSeconds: maxIntervalInSeconds, + clock: clock, + } +} + +// aggregateRecord holds data used to perform aggregation decisions +type aggregateRecord struct { + // we track the number of unique local keys we have seen in the aggregate set to know when to actually aggregate + // if the size of this set exceeds the max, we know we need to aggregate + localKeys sets.String + // The last time at which the aggregate was recorded + lastTimestamp unversioned.Time +} + +// EventAggregate identifies similar events and groups into a common event if required +func (e *EventAggregator) EventAggregate(newEvent *api.Event) (*api.Event, error) { + aggregateKey, localKey := e.keyFunc(newEvent) + now := unversioned.NewTime(e.clock.Now()) + record := aggregateRecord{localKeys: sets.NewString(), lastTimestamp: now} + e.Lock() + defer e.Unlock() + value, found := e.cache.Get(aggregateKey) + if found { + record = value.(aggregateRecord) + } + + // if the last event was far enough in the past, it is not aggregated, and we must reset state + maxInterval := time.Duration(e.maxIntervalInSeconds) * time.Second + interval := now.Time.Sub(record.lastTimestamp.Time) + if interval > maxInterval { + record = aggregateRecord{localKeys: sets.NewString()} + } + record.localKeys.Insert(localKey) + record.lastTimestamp = now + e.cache.Add(aggregateKey, record) + + if record.localKeys.Len() < e.maxEvents { + return newEvent, nil + } + + // do not grow our local key set any larger than max + record.localKeys.PopAny() + + // create a new aggregate event + eventCopy := &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: fmt.Sprintf("%v.%x", newEvent.InvolvedObject.Name, now.UnixNano()), + Namespace: newEvent.Namespace, + }, + Count: 1, + FirstTimestamp: now, + InvolvedObject: newEvent.InvolvedObject, + LastTimestamp: now, + Message: e.messageFunc(newEvent), + Type: newEvent.Type, + Reason: newEvent.Reason, + Source: newEvent.Source, + } + return eventCopy, nil +} + +// eventLog records data about when an event was observed +type eventLog struct { + // The number of times the event has occurred since first occurrence. + count int + + // The time at which the event was first recorded. + firstTimestamp unversioned.Time + + // The unique name of the first occurrence of this event + name string + + // Resource version returned from previous interaction with server + resourceVersion string +} + +// eventLogger logs occurrences of an event +type eventLogger struct { + sync.RWMutex + cache *lru.Cache + clock util.Clock +} + +// newEventLogger observes events and counts their frequencies +func newEventLogger(lruCacheEntries int, clock util.Clock) *eventLogger { + return &eventLogger{cache: lru.New(lruCacheEntries), clock: clock} +} + +// eventObserve records the event, and determines if its frequency should update +func (e *eventLogger) eventObserve(newEvent *api.Event) (*api.Event, []byte, error) { + var ( + patch []byte + err error + ) + key := getEventKey(newEvent) + eventCopy := *newEvent + event := &eventCopy + + e.Lock() + defer e.Unlock() + + lastObservation := e.lastEventObservationFromCache(key) + + // we have seen this event before, so we must prepare a patch + if lastObservation.count > 0 { + // update the event based on the last observation so patch will work as desired + event.Name = lastObservation.name + event.ResourceVersion = lastObservation.resourceVersion + event.FirstTimestamp = lastObservation.firstTimestamp + event.Count = lastObservation.count + 1 + + eventCopy2 := *event + eventCopy2.Count = 0 + eventCopy2.LastTimestamp = unversioned.NewTime(time.Unix(0, 0)) + + newData, _ := json.Marshal(event) + oldData, _ := json.Marshal(eventCopy2) + patch, err = strategicpatch.CreateStrategicMergePatch(oldData, newData, event) + } + + // record our new observation + e.cache.Add( + key, + eventLog{ + count: event.Count, + firstTimestamp: event.FirstTimestamp, + name: event.Name, + resourceVersion: event.ResourceVersion, + }, + ) + return event, patch, err +} + +// updateState updates its internal tracking information based on latest server state +func (e *eventLogger) updateState(event *api.Event) { + key := getEventKey(event) + e.Lock() + defer e.Unlock() + // record our new observation + e.cache.Add( + key, + eventLog{ + count: event.Count, + firstTimestamp: event.FirstTimestamp, + name: event.Name, + resourceVersion: event.ResourceVersion, + }, + ) +} + +// lastEventObservationFromCache returns the event from the cache, reads must be protected via external lock +func (e *eventLogger) lastEventObservationFromCache(key string) eventLog { + value, ok := e.cache.Get(key) + if ok { + observationValue, ok := value.(eventLog) + if ok { + return observationValue + } + } + return eventLog{} +} + +// EventCorrelator processes all incoming events and performs analysis to avoid overwhelming the system. It can filter all +// incoming events to see if the event should be filtered from further processing. It can aggregate similar events that occur +// frequently to protect the system from spamming events that are difficult for users to distinguish. It performs de-duplication +// to ensure events that are observed multiple times are compacted into a single event with increasing counts. +type EventCorrelator struct { + // the function to filter the event + filterFunc EventFilterFunc + // the object that performs event aggregation + aggregator *EventAggregator + // the object that observes events as they come through + logger *eventLogger +} + +// EventCorrelateResult is the result of a Correlate +type EventCorrelateResult struct { + // the event after correlation + Event *api.Event + // if provided, perform a strategic patch when updating the record on the server + Patch []byte + // if true, do no further processing of the event + Skip bool +} + +// NewEventCorrelator returns an EventCorrelator configured with default values. +// +// The EventCorrelator is responsible for event filtering, aggregating, and counting +// prior to interacting with the API server to record the event. +// +// The default behavior is as follows: +// * No events are filtered from being recorded +// * Aggregation is performed if a similar event is recorded 10 times in a +// in a 10 minute rolling interval. A similar event is an event that varies only by +// the Event.Message field. Rather than recording the precise event, aggregation +// will create a new event whose message reports that it has combined events with +// the same reason. +// * Events are incrementally counted if the exact same event is encountered multiple +// times. +func NewEventCorrelator(clock util.Clock) *EventCorrelator { + cacheSize := maxLruCacheEntries + return &EventCorrelator{ + filterFunc: DefaultEventFilterFunc, + aggregator: NewEventAggregator( + cacheSize, + EventAggregatorByReasonFunc, + EventAggregatorByReasonMessageFunc, + defaultAggregateMaxEvents, + defaultAggregateIntervalInSeconds, + clock), + logger: newEventLogger(cacheSize, clock), + } +} + +// EventCorrelate filters, aggregates, counts, and de-duplicates all incoming events +func (c *EventCorrelator) EventCorrelate(newEvent *api.Event) (*EventCorrelateResult, error) { + if c.filterFunc(newEvent) { + return &EventCorrelateResult{Skip: true}, nil + } + aggregateEvent, err := c.aggregator.EventAggregate(newEvent) + if err != nil { + return &EventCorrelateResult{}, err + } + observedEvent, patch, err := c.logger.eventObserve(aggregateEvent) + return &EventCorrelateResult{Event: observedEvent, Patch: patch}, err +} + +// UpdateState based on the latest observed state from server +func (c *EventCorrelator) UpdateState(event *api.Event) { + c.logger.updateState(event) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/record/events_cache_test.go b/vendor/k8s.io/kubernetes/pkg/client/record/events_cache_test.go new file mode 100644 index 000000000..ab88e5e5f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/record/events_cache_test.go @@ -0,0 +1,254 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 record + +import ( + "reflect" + "strings" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" +) + +func makeObjectReference(kind, name, namespace string) api.ObjectReference { + return api.ObjectReference{ + Kind: kind, + Name: name, + Namespace: namespace, + UID: "C934D34AFB20242", + APIVersion: "version", + } +} + +func makeEvent(reason, message string, involvedObject api.ObjectReference) api.Event { + eventTime := unversioned.Now() + event := api.Event{ + Reason: reason, + Message: message, + InvolvedObject: involvedObject, + Source: api.EventSource{ + Component: "kubelet", + Host: "kublet.node1", + }, + Count: 1, + FirstTimestamp: eventTime, + LastTimestamp: eventTime, + Type: api.EventTypeNormal, + } + return event +} + +func makeEvents(num int, template api.Event) []api.Event { + events := []api.Event{} + for i := 0; i < num; i++ { + events = append(events, template) + } + return events +} + +func makeUniqueEvents(num int) []api.Event { + events := []api.Event{} + kind := "Pod" + for i := 0; i < num; i++ { + reason := strings.Join([]string{"reason", string(i)}, "-") + message := strings.Join([]string{"message", string(i)}, "-") + name := strings.Join([]string{"pod", string(i)}, "-") + namespace := strings.Join([]string{"ns", string(i)}, "-") + involvedObject := makeObjectReference(kind, name, namespace) + events = append(events, makeEvent(reason, message, involvedObject)) + } + return events +} + +func makeSimilarEvents(num int, template api.Event, messagePrefix string) []api.Event { + events := makeEvents(num, template) + for i := range events { + events[i].Message = strings.Join([]string{messagePrefix, string(i), events[i].Message}, "-") + } + return events +} + +func setCount(event api.Event, count int) api.Event { + event.Count = count + return event +} + +func validateEvent(messagePrefix string, actualEvent *api.Event, expectedEvent *api.Event, t *testing.T) (*api.Event, error) { + recvEvent := *actualEvent + expectCompression := expectedEvent.Count > 1 + t.Logf("%v - expectedEvent.Count is %d\n", messagePrefix, expectedEvent.Count) + // Just check that the timestamp was set. + if recvEvent.FirstTimestamp.IsZero() || recvEvent.LastTimestamp.IsZero() { + t.Errorf("%v - timestamp wasn't set: %#v", messagePrefix, recvEvent) + } + actualFirstTimestamp := recvEvent.FirstTimestamp + actualLastTimestamp := recvEvent.LastTimestamp + if actualFirstTimestamp.Equal(actualLastTimestamp) { + if expectCompression { + t.Errorf("%v - FirstTimestamp (%q) and LastTimestamp (%q) must be different to indicate event compression happened, but were the same. Actual Event: %#v", messagePrefix, actualFirstTimestamp, actualLastTimestamp, recvEvent) + } + } else { + if expectedEvent.Count == 1 { + t.Errorf("%v - FirstTimestamp (%q) and LastTimestamp (%q) must be equal to indicate only one occurrence of the event, but were different. Actual Event: %#v", messagePrefix, actualFirstTimestamp, actualLastTimestamp, recvEvent) + } + } + // Temp clear time stamps for comparison because actual values don't matter for comparison + recvEvent.FirstTimestamp = expectedEvent.FirstTimestamp + recvEvent.LastTimestamp = expectedEvent.LastTimestamp + // Check that name has the right prefix. + if n, en := recvEvent.Name, expectedEvent.Name; !strings.HasPrefix(n, en) { + t.Errorf("%v - Name '%v' does not contain prefix '%v'", messagePrefix, n, en) + } + recvEvent.Name = expectedEvent.Name + if e, a := expectedEvent, &recvEvent; !reflect.DeepEqual(e, a) { + t.Errorf("%v - diff: %s", messagePrefix, diff.ObjectGoPrintDiff(e, a)) + } + recvEvent.FirstTimestamp = actualFirstTimestamp + recvEvent.LastTimestamp = actualLastTimestamp + return actualEvent, nil +} + +// TestDefaultEventFilterFunc ensures that no events are filtered +func TestDefaultEventFilterFunc(t *testing.T) { + event := makeEvent("end-of-world", "it was fun", makeObjectReference("Pod", "pod1", "other")) + if DefaultEventFilterFunc(&event) { + t.Fatalf("DefaultEventFilterFunc should always return false") + } +} + +// TestEventAggregatorByReasonFunc ensures that two events are aggregated if they vary only by event.message +func TestEventAggregatorByReasonFunc(t *testing.T) { + event1 := makeEvent("end-of-world", "it was fun", makeObjectReference("Pod", "pod1", "other")) + event2 := makeEvent("end-of-world", "it was awful", makeObjectReference("Pod", "pod1", "other")) + event3 := makeEvent("nevermind", "it was a bug", makeObjectReference("Pod", "pod1", "other")) + + aggKey1, localKey1 := EventAggregatorByReasonFunc(&event1) + aggKey2, localKey2 := EventAggregatorByReasonFunc(&event2) + aggKey3, _ := EventAggregatorByReasonFunc(&event3) + + if aggKey1 != aggKey2 { + t.Errorf("Expected %v equal %v", aggKey1, aggKey2) + } + if localKey1 == localKey2 { + t.Errorf("Expected %v to not equal %v", aggKey1, aggKey3) + } + if aggKey1 == aggKey3 { + t.Errorf("Expected %v to not equal %v", aggKey1, aggKey3) + } +} + +// TestEventAggregatorByReasonMessageFunc validates the proper output for an aggregate message +func TestEventAggregatorByReasonMessageFunc(t *testing.T) { + expected := "(events with common reason combined)" + event1 := makeEvent("end-of-world", "it was fun", makeObjectReference("Pod", "pod1", "other")) + if actual := EventAggregatorByReasonMessageFunc(&event1); expected != actual { + t.Errorf("Expected %v got %v", expected, actual) + } +} + +// TestEventCorrelator validates proper counting, aggregation of events +func TestEventCorrelator(t *testing.T) { + firstEvent := makeEvent("first", "i am first", makeObjectReference("Pod", "my-pod", "my-ns")) + duplicateEvent := makeEvent("duplicate", "me again", makeObjectReference("Pod", "my-pod", "my-ns")) + uniqueEvent := makeEvent("unique", "snowflake", makeObjectReference("Pod", "my-pod", "my-ns")) + similarEvent := makeEvent("similar", "similar message", makeObjectReference("Pod", "my-pod", "my-ns")) + aggregateEvent := makeEvent(similarEvent.Reason, EventAggregatorByReasonMessageFunc(&similarEvent), similarEvent.InvolvedObject) + scenario := map[string]struct { + previousEvents []api.Event + newEvent api.Event + expectedEvent api.Event + intervalSeconds int + }{ + "create-a-single-event": { + previousEvents: []api.Event{}, + newEvent: firstEvent, + expectedEvent: setCount(firstEvent, 1), + intervalSeconds: 5, + }, + "the-same-event-should-just-count": { + previousEvents: makeEvents(1, duplicateEvent), + newEvent: duplicateEvent, + expectedEvent: setCount(duplicateEvent, 2), + intervalSeconds: 5, + }, + "the-same-event-should-just-count-even-if-more-than-aggregate": { + previousEvents: makeEvents(defaultAggregateMaxEvents, duplicateEvent), + newEvent: duplicateEvent, + expectedEvent: setCount(duplicateEvent, defaultAggregateMaxEvents+1), + intervalSeconds: 5, + }, + "create-many-unique-events": { + previousEvents: makeUniqueEvents(30), + newEvent: uniqueEvent, + expectedEvent: setCount(uniqueEvent, 1), + intervalSeconds: 5, + }, + "similar-events-should-aggregate-event": { + previousEvents: makeSimilarEvents(defaultAggregateMaxEvents-1, similarEvent, similarEvent.Message), + newEvent: similarEvent, + expectedEvent: setCount(aggregateEvent, 1), + intervalSeconds: 5, + }, + "similar-events-many-times-should-count-the-aggregate": { + previousEvents: makeSimilarEvents(defaultAggregateMaxEvents, similarEvent, similarEvent.Message), + newEvent: similarEvent, + expectedEvent: setCount(aggregateEvent, 2), + intervalSeconds: 5, + }, + "similar-events-whose-interval-is-greater-than-aggregate-interval-do-not-aggregate": { + previousEvents: makeSimilarEvents(defaultAggregateMaxEvents-1, similarEvent, similarEvent.Message), + newEvent: similarEvent, + expectedEvent: setCount(similarEvent, 1), + intervalSeconds: defaultAggregateIntervalInSeconds, + }, + } + + for testScenario, testInput := range scenario { + eventInterval := time.Duration(testInput.intervalSeconds) * time.Second + clock := util.IntervalClock{Time: time.Now(), Duration: eventInterval} + correlator := NewEventCorrelator(&clock) + for i := range testInput.previousEvents { + event := testInput.previousEvents[i] + now := unversioned.NewTime(clock.Now()) + event.FirstTimestamp = now + event.LastTimestamp = now + result, err := correlator.EventCorrelate(&event) + if err != nil { + t.Errorf("scenario %v: unexpected error playing back prevEvents %v", testScenario, err) + } + correlator.UpdateState(result.Event) + } + + // update the input to current clock value + now := unversioned.NewTime(clock.Now()) + testInput.newEvent.FirstTimestamp = now + testInput.newEvent.LastTimestamp = now + result, err := correlator.EventCorrelate(&testInput.newEvent) + if err != nil { + t.Errorf("scenario %v: unexpected error correlating input event %v", testScenario, err) + } + + _, err = validateEvent(testScenario, result.Event, &testInput.expectedEvent, t) + if err != nil { + t.Errorf("scenario %v: unexpected error validating result %v", testScenario, err) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/record/fake.go b/vendor/k8s.io/kubernetes/pkg/client/record/fake.go new file mode 100644 index 000000000..7afe1bab2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/record/fake.go @@ -0,0 +1,40 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 record + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/runtime" +) + +// FakeRecorder is used as a fake during tests. +type FakeRecorder struct { + Events []string +} + +func (f *FakeRecorder) Event(object runtime.Object, eventtype, reason, message string) { + f.Events = append(f.Events, fmt.Sprintf("%s %s %s", eventtype, reason, message)) +} + +func (f *FakeRecorder) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) { + f.Events = append(f.Events, fmt.Sprintf(eventtype+" "+reason+" "+messageFmt, args...)) +} + +func (f *FakeRecorder) PastEventf(object runtime.Object, timestamp unversioned.Time, eventtype, reason, messageFmt string, args ...interface{}) { +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/restclient.go b/vendor/k8s.io/kubernetes/pkg/client/restclient/client.go similarity index 93% rename from vendor/k8s.io/kubernetes/pkg/client/unversioned/restclient.go rename to vendor/k8s.io/kubernetes/pkg/client/restclient/client.go index 3946754f3..23842b628 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/restclient.go +++ b/vendor/k8s.io/kubernetes/pkg/client/restclient/client.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package unversioned +package restclient import ( "net/http" @@ -26,7 +26,8 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/flowcontrol" ) const ( @@ -53,7 +54,7 @@ type RESTClient struct { contentConfig ContentConfig // TODO extract this into a wrapper interface via the RESTClient interface in kubectl. - Throttle util.RateLimiter + Throttle flowcontrol.RateLimiter // Set specific behavior of the client. If not set http.DefaultClient will be used. Client *http.Client @@ -77,9 +78,9 @@ func NewRESTClient(baseURL *url.URL, versionedAPIPath string, config ContentConf config.ContentType = "application/json" } - var throttle util.RateLimiter + var throttle flowcontrol.RateLimiter if maxQPS > 0 { - throttle = util.NewTokenBucketRateLimiter(maxQPS, maxBurst) + throttle = flowcontrol.NewTokenBucketRateLimiter(maxQPS, maxBurst) } return &RESTClient{ base: &base, @@ -103,7 +104,7 @@ func readExpBackoffConfig() BackoffManager { return &NoBackoff{} } return &URLBackoff{ - Backoff: util.NewBackOff( + Backoff: flowcontrol.NewBackOff( time.Duration(backoffBaseInt)*time.Second, time.Duration(backoffDurationInt)*time.Second)} } @@ -158,3 +159,7 @@ func (c *RESTClient) Delete() *Request { func (c *RESTClient) APIVersion() unversioned.GroupVersion { return *c.contentConfig.GroupVersion } + +func (c *RESTClient) Codec() runtime.Codec { + return c.contentConfig.Codec +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/restclient_test.go b/vendor/k8s.io/kubernetes/pkg/client/restclient/client_test.go similarity index 98% rename from vendor/k8s.io/kubernetes/pkg/client/unversioned/restclient_test.go rename to vendor/k8s.io/kubernetes/pkg/client/restclient/client_test.go index 912611f4a..182b936b7 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/restclient_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/restclient/client_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package unversioned +package restclient import ( "net/http" @@ -29,7 +29,7 @@ import ( "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" utiltesting "k8s.io/kubernetes/pkg/util/testing" ) @@ -114,7 +114,7 @@ func TestDoRequestFailed(t *testing.T) { expected.APIVersion = "v1" expected.Kind = "Status" if !reflect.DeepEqual(&expected, &actual) { - t.Errorf("Unexpected mis-match: %s", util.ObjectDiff(status, &actual)) + t.Errorf("Unexpected mis-match: %s", diff.ObjectDiff(status, &actual)) } } diff --git a/vendor/k8s.io/kubernetes/pkg/client/restclient/config.go b/vendor/k8s.io/kubernetes/pkg/client/restclient/config.go new file mode 100644 index 000000000..cbddb2682 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/restclient/config.go @@ -0,0 +1,309 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 restclient + +import ( + "fmt" + "io/ioutil" + "net" + "net/http" + "os" + "path" + gruntime "runtime" + "strings" + + "github.com/golang/glog" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/crypto" + "k8s.io/kubernetes/pkg/version" +) + +// Config holds the common attributes that can be passed to a Kubernetes client on +// initialization. +type Config struct { + // Host must be a host string, a host:port pair, or a URL to the base of the apiserver. + // If a URL is given then the (optional) Path of that URL represents a prefix that must + // be appended to all request URIs used to access the apiserver. This allows a frontend + // proxy to easily relocate all of the apiserver endpoints. + Host string + // APIPath is a sub-path that points to an API root. + APIPath string + // Prefix is the sub path of the server. If not specified, the client will set + // a default value. Use "/" to indicate the server root should be used + Prefix string + + // ContentConfig contains settings that affect how objects are transformed when + // sent to the server. + ContentConfig + + // Server requires Basic authentication + Username string + Password string + + // Server requires Bearer authentication. This client will not attempt to use + // refresh tokens for an OAuth2 flow. + // TODO: demonstrate an OAuth2 compatible client. + BearerToken string + + // TLSClientConfig contains settings to enable transport layer security + TLSClientConfig + + // Server should be accessed without verifying the TLS + // certificate. For testing only. + Insecure bool + + // UserAgent is an optional field that specifies the caller of this request. + UserAgent string + + // Transport may be used for custom HTTP behavior. This attribute may not + // be specified with the TLS client certificate options. Use WrapTransport + // for most client level operations. + Transport http.RoundTripper + // WrapTransport will be invoked for custom HTTP behavior after the underlying + // transport is initialized (either the transport created from TLSClientConfig, + // Transport, or http.DefaultTransport). The config may layer other RoundTrippers + // on top of the returned RoundTripper. + WrapTransport func(rt http.RoundTripper) http.RoundTripper + + // QPS indicates the maximum QPS to the master from this client. If zero, QPS is unlimited. + QPS float32 + + // Maximum burst for throttle + Burst int +} + +// TLSClientConfig contains settings to enable transport layer security +type TLSClientConfig struct { + // Server requires TLS client certificate authentication + CertFile string + // Server requires TLS client certificate authentication + KeyFile string + // Trusted root certificates for server + CAFile string + + // CertData holds PEM-encoded bytes (typically read from a client certificate file). + // CertData takes precedence over CertFile + CertData []byte + // KeyData holds PEM-encoded bytes (typically read from a client certificate key file). + // KeyData takes precedence over KeyFile + KeyData []byte + // CAData holds PEM-encoded bytes (typically read from a root certificates bundle). + // CAData takes precedence over CAFile + CAData []byte +} + +type ContentConfig struct { + // ContentType specifies the wire format used to communicate with the server. + // This value will be set as the Accept header on requests made to the server, and + // as the default content type on any object sent to the server. If not set, + // "application/json" is used. + ContentType string + // GroupVersion is the API version to talk to. Must be provided when initializing + // a RESTClient directly. When initializing a Client, will be set with the default + // code version. + GroupVersion *unversioned.GroupVersion + // Codec specifies the encoding and decoding behavior for runtime.Objects passed + // to a RESTClient or Client. Required when initializing a RESTClient, optional + // when initializing a Client. + Codec runtime.Codec +} + +// RESTClientFor returns a RESTClient that satisfies the requested attributes on a client Config +// object. Note that a RESTClient may require fields that are optional when initializing a Client. +// A RESTClient created by this method is generic - it expects to operate on an API that follows +// the Kubernetes conventions, but may not be the Kubernetes API. +func RESTClientFor(config *Config) (*RESTClient, error) { + if config.GroupVersion == nil { + return nil, fmt.Errorf("GroupVersion is required when initializing a RESTClient") + } + if config.Codec == nil { + return nil, fmt.Errorf("Codec is required when initializing a RESTClient") + } + + baseURL, versionedAPIPath, err := defaultServerUrlFor(config) + if err != nil { + return nil, err + } + + transport, err := TransportFor(config) + if err != nil { + return nil, err + } + + var httpClient *http.Client + if transport != http.DefaultTransport { + httpClient = &http.Client{Transport: transport} + } + + client := NewRESTClient(baseURL, versionedAPIPath, config.ContentConfig, config.QPS, config.Burst, httpClient) + + return client, nil +} + +// UnversionedRESTClientFor is the same as RESTClientFor, except that it allows +// the config.Version to be empty. +func UnversionedRESTClientFor(config *Config) (*RESTClient, error) { + if config.Codec == nil { + return nil, fmt.Errorf("Codec is required when initializing a RESTClient") + } + + baseURL, versionedAPIPath, err := defaultServerUrlFor(config) + if err != nil { + return nil, err + } + + transport, err := TransportFor(config) + if err != nil { + return nil, err + } + + var httpClient *http.Client + if transport != http.DefaultTransport { + httpClient = &http.Client{Transport: transport} + } + + versionConfig := config.ContentConfig + if versionConfig.GroupVersion == nil { + v := unversioned.SchemeGroupVersion + versionConfig.GroupVersion = &v + } + + client := NewRESTClient(baseURL, versionedAPIPath, versionConfig, config.QPS, config.Burst, httpClient) + return client, nil +} + +// SetKubernetesDefaults sets default values on the provided client config for accessing the +// Kubernetes API or returns an error if any of the defaults are impossible or invalid. +func SetKubernetesDefaults(config *Config) error { + if len(config.UserAgent) == 0 { + config.UserAgent = DefaultKubernetesUserAgent() + } + if config.QPS == 0.0 { + config.QPS = 5.0 + } + if config.Burst == 0 { + config.Burst = 10 + } + return nil +} + +// DefaultKubernetesUserAgent returns the default user agent that clients can use. +func DefaultKubernetesUserAgent() string { + commit := version.Get().GitCommit + if len(commit) > 7 { + commit = commit[:7] + } + if len(commit) == 0 { + commit = "unknown" + } + version := version.Get().GitVersion + seg := strings.SplitN(version, "-", 2) + version = seg[0] + return fmt.Sprintf("%s/%s (%s/%s) kubernetes/%s", path.Base(os.Args[0]), version, gruntime.GOOS, gruntime.GOARCH, commit) +} + +// InClusterConfig returns a config object which uses the service account +// kubernetes gives to pods. It's intended for clients that expect to be +// running inside a pod running on kuberenetes. It will return an error if +// called from a process not running in a kubernetes environment. +func InClusterConfig() (*Config, error) { + host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT") + if len(host) == 0 || len(port) == 0 { + return nil, fmt.Errorf("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined") + } + + token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountTokenKey) + if err != nil { + return nil, err + } + tlsClientConfig := TLSClientConfig{} + rootCAFile := "/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountRootCAKey + if _, err := crypto.CertPoolFromFile(rootCAFile); err != nil { + glog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err) + } else { + tlsClientConfig.CAFile = rootCAFile + } + + return &Config{ + // TODO: switch to using cluster DNS. + Host: "https://" + net.JoinHostPort(host, port), + BearerToken: string(token), + TLSClientConfig: tlsClientConfig, + }, nil +} + +// IsConfigTransportTLS returns true if and only if the provided +// config will result in a protected connection to the server when it +// is passed to restclient.RESTClientFor(). Use to determine when to +// send credentials over the wire. +// +// Note: the Insecure flag is ignored when testing for this value, so MITM attacks are +// still possible. +func IsConfigTransportTLS(config Config) bool { + baseURL, _, err := defaultServerUrlFor(&config) + if err != nil { + return false + } + return baseURL.Scheme == "https" +} + +// LoadTLSFiles copies the data from the CertFile, KeyFile, and CAFile fields into the CertData, +// KeyData, and CAFile fields, or returns an error. If no error is returned, all three fields are +// either populated or were empty to start. +func LoadTLSFiles(c *Config) error { + var err error + c.CAData, err = dataFromSliceOrFile(c.CAData, c.CAFile) + if err != nil { + return err + } + + c.CertData, err = dataFromSliceOrFile(c.CertData, c.CertFile) + if err != nil { + return err + } + + c.KeyData, err = dataFromSliceOrFile(c.KeyData, c.KeyFile) + if err != nil { + return err + } + return nil +} + +// dataFromSliceOrFile returns data from the slice (if non-empty), or from the file, +// or an error if an error occurred reading the file +func dataFromSliceOrFile(data []byte, file string) ([]byte, error) { + if len(data) > 0 { + return data, nil + } + if len(file) > 0 { + fileData, err := ioutil.ReadFile(file) + if err != nil { + return []byte{}, err + } + return fileData, nil + } + return nil, nil +} + +func AddUserAgent(config *Config, userAgent string) *Config { + fullUserAgent := DefaultKubernetesUserAgent() + "/" + userAgent + config.UserAgent = fullUserAgent + return config +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/restclient/config_test.go b/vendor/k8s.io/kubernetes/pkg/client/restclient/config_test.go new file mode 100644 index 000000000..1a1dd422b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/restclient/config_test.go @@ -0,0 +1,99 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 restclient + +import ( + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api/testapi" +) + +func TestIsConfigTransportTLS(t *testing.T) { + testCases := []struct { + Config *Config + TransportTLS bool + }{ + { + Config: &Config{}, + TransportTLS: false, + }, + { + Config: &Config{ + Host: "https://localhost", + }, + TransportTLS: true, + }, + { + Config: &Config{ + Host: "localhost", + TLSClientConfig: TLSClientConfig{ + CertFile: "foo", + }, + }, + TransportTLS: true, + }, + { + Config: &Config{ + Host: "///:://localhost", + TLSClientConfig: TLSClientConfig{ + CertFile: "foo", + }, + }, + TransportTLS: false, + }, + { + Config: &Config{ + Host: "1.2.3.4:567", + Insecure: true, + }, + TransportTLS: true, + }, + } + for _, testCase := range testCases { + if err := SetKubernetesDefaults(testCase.Config); err != nil { + t.Errorf("setting defaults failed for %#v: %v", testCase.Config, err) + continue + } + useTLS := IsConfigTransportTLS(*testCase.Config) + if testCase.TransportTLS != useTLS { + t.Errorf("expected %v for %#v", testCase.TransportTLS, testCase.Config) + } + } +} + +func TestSetKubernetesDefaultsUserAgent(t *testing.T) { + config := &Config{} + if err := SetKubernetesDefaults(config); err != nil { + t.Errorf("unexpected error: %v", err) + } + if !strings.Contains(config.UserAgent, "kubernetes/") { + t.Errorf("no user agent set: %#v", config) + } +} + +func TestRESTClientRequires(t *testing.T) { + if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{Codec: testapi.Default.Codec()}}); err == nil { + t.Errorf("unexpected non-error") + } + if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}); err == nil { + t.Errorf("unexpected non-error") + } + if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.Codec()}}); err != nil { + t.Errorf("unexpected error: %v", err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/request.go b/vendor/k8s.io/kubernetes/pkg/client/restclient/request.go similarity index 92% rename from vendor/k8s.io/kubernetes/pkg/client/unversioned/request.go rename to vendor/k8s.io/kubernetes/pkg/client/restclient/request.go index c46961999..959318d7f 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/request.go +++ b/vendor/k8s.io/kubernetes/pkg/client/restclient/request.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package unversioned +package restclient import ( "bytes" @@ -39,16 +39,22 @@ import ( "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/flowcontrol" "k8s.io/kubernetes/pkg/util/net" "k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/watch" watchjson "k8s.io/kubernetes/pkg/watch/json" ) -// specialParams lists parameters that are handled specially and which users of Request -// are therefore not allowed to set manually. -var specialParams = sets.NewString("timeout") +var ( + // specialParams lists parameters that are handled specially and which users of Request + // are therefore not allowed to set manually. + specialParams = sets.NewString("timeout") + + // longThrottleLatency defines threshold for logging requests. All requests being + // throttle for more than longThrottleLatency will be logged. + longThrottleLatency = 50 * time.Millisecond +) func init() { metrics.Register() @@ -111,11 +117,11 @@ type Request struct { resp *http.Response backoffMgr BackoffManager - throttle util.RateLimiter + throttle flowcontrol.RateLimiter } // NewRequest creates a new request helper object for accessing runtime.Objects on a server. -func NewRequest(client HTTPClient, verb string, baseURL *url.URL, versionedAPIPath string, content ContentConfig, backoff BackoffManager, throttle util.RateLimiter) *Request { +func NewRequest(client HTTPClient, verb string, baseURL *url.URL, versionedAPIPath string, content ContentConfig, backoff BackoffManager, throttle flowcontrol.RateLimiter) *Request { if backoff == nil { glog.V(2).Infof("Not implementing request backoff strategy.") backoff = &NoBackoff{} @@ -286,22 +292,22 @@ func (r *Request) RequestURI(uri string) *Request { const ( // A constant that clients can use to refer in a field selector to the object name field. // Will be automatically emitted as the correct name for the API version. - NodeUnschedulable = "spec.unschedulable" - ObjectNameField = "metadata.name" - PodHost = "spec.nodeName" - PodStatus = "status.phase" - SecretType = "type" + nodeUnschedulable = "spec.unschedulable" + objectNameField = "metadata.name" + podHost = "spec.nodeName" + podStatus = "status.phase" + secretType = "type" - EventReason = "reason" - EventSource = "source" - EventType = "type" - EventInvolvedKind = "involvedObject.kind" - EventInvolvedNamespace = "involvedObject.namespace" - EventInvolvedName = "involvedObject.name" - EventInvolvedUID = "involvedObject.uid" - EventInvolvedAPIVersion = "involvedObject.apiVersion" - EventInvolvedResourceVersion = "involvedObject.resourceVersion" - EventInvolvedFieldPath = "involvedObject.fieldPath" + eventReason = "reason" + eventSource = "source" + eventType = "type" + eventInvolvedKind = "involvedObject.kind" + eventInvolvedNamespace = "involvedObject.namespace" + eventInvolvedName = "involvedObject.name" + eventInvolvedUID = "involvedObject.uid" + eventInvolvedAPIVersion = "involvedObject.apiVersion" + eventInvolvedResourceVersion = "involvedObject.resourceVersion" + eventInvolvedFieldPath = "involvedObject.fieldPath" ) type clientFieldNameToAPIVersionFieldName map[string]string @@ -344,34 +350,34 @@ func (v versionToResourceToFieldMapping) filterField(groupVersion *unversioned.G var fieldMappings = versionToResourceToFieldMapping{ v1.SchemeGroupVersion: resourceTypeToFieldMapping{ "nodes": clientFieldNameToAPIVersionFieldName{ - ObjectNameField: ObjectNameField, - NodeUnschedulable: NodeUnschedulable, + objectNameField: objectNameField, + nodeUnschedulable: nodeUnschedulable, }, "pods": clientFieldNameToAPIVersionFieldName{ - PodHost: PodHost, - PodStatus: PodStatus, + podHost: podHost, + podStatus: podStatus, }, "secrets": clientFieldNameToAPIVersionFieldName{ - SecretType: SecretType, + secretType: secretType, }, "serviceAccounts": clientFieldNameToAPIVersionFieldName{ - ObjectNameField: ObjectNameField, + objectNameField: objectNameField, }, "endpoints": clientFieldNameToAPIVersionFieldName{ - ObjectNameField: ObjectNameField, + objectNameField: objectNameField, }, "events": clientFieldNameToAPIVersionFieldName{ - ObjectNameField: ObjectNameField, - EventReason: EventReason, - EventSource: EventSource, - EventType: EventType, - EventInvolvedKind: EventInvolvedKind, - EventInvolvedNamespace: EventInvolvedNamespace, - EventInvolvedName: EventInvolvedName, - EventInvolvedUID: EventInvolvedUID, - EventInvolvedAPIVersion: EventInvolvedAPIVersion, - EventInvolvedResourceVersion: EventInvolvedResourceVersion, - EventInvolvedFieldPath: EventInvolvedFieldPath, + objectNameField: objectNameField, + eventReason: eventReason, + eventSource: eventSource, + eventType: eventType, + eventInvolvedKind: eventInvolvedKind, + eventInvolvedNamespace: eventInvolvedNamespace, + eventInvolvedName: eventInvolvedName, + eventInvolvedUID: eventInvolvedUID, + eventInvolvedAPIVersion: eventInvolvedAPIVersion, + eventInvolvedResourceVersion: eventInvolvedResourceVersion, + eventInvolvedFieldPath: eventInvolvedFieldPath, }, }, } @@ -612,6 +618,16 @@ func (r Request) finalURLTemplate() string { return r.URL().String() } +func (r *Request) tryThrottle() { + now := time.Now() + if r.throttle != nil { + r.throttle.Accept() + } + if latency := time.Since(now); latency > longThrottleLatency { + glog.V(4).Infof("Throttling request took %v, request: %s:%s", latency, r.verb, r.URL().String()) + } +} + // Watch attempts to begin watching the requested location. // Returns a watch.Interface, or an error. func (r *Request) Watch() (watch.Interface, error) { @@ -629,7 +645,7 @@ func (r *Request) Watch() (watch.Interface, error) { if client == nil { client = http.DefaultClient } - time.Sleep(r.backoffMgr.CalculateBackoff(r.URL())) + r.backoffMgr.Sleep(r.backoffMgr.CalculateBackoff(r.URL())) resp, err := client.Do(req) updateURLMetrics(r, resp, err) if r.baseURL != nil { @@ -683,9 +699,7 @@ func (r *Request) Stream() (io.ReadCloser, error) { return nil, r.err } - if r.throttle != nil { - r.throttle.Accept() - } + r.tryThrottle() url := r.URL().String() req, err := http.NewRequest(r.verb, url, nil) @@ -696,7 +710,7 @@ func (r *Request) Stream() (io.ReadCloser, error) { if client == nil { client = http.DefaultClient } - time.Sleep(r.backoffMgr.CalculateBackoff(r.URL())) + r.backoffMgr.Sleep(r.backoffMgr.CalculateBackoff(r.URL())) resp, err := client.Do(req) updateURLMetrics(r, resp, err) if r.baseURL != nil { @@ -778,7 +792,7 @@ func (r *Request) request(fn func(*http.Request, *http.Response)) error { } req.Header = r.headers - time.Sleep(r.backoffMgr.CalculateBackoff(r.URL())) + r.backoffMgr.Sleep(r.backoffMgr.CalculateBackoff(r.URL())) resp, err := client.Do(req) updateURLMetrics(r, resp, err) if err != nil { @@ -798,7 +812,7 @@ func (r *Request) request(fn func(*http.Request, *http.Response)) error { retries++ if seconds, wait := checkWait(resp); wait && retries < maxRetries { glog.V(4).Infof("Got a Retry-After %s response for attempt %d to %v", seconds, retries, url) - time.Sleep(time.Duration(seconds) * time.Second) + r.backoffMgr.Sleep(time.Duration(seconds) * time.Second) return false } fn(req, resp) @@ -819,9 +833,7 @@ func (r *Request) request(fn func(*http.Request, *http.Response)) error { // * If the server responds with a status: *errors.StatusError or *errors.UnexpectedObjectError // * http.Client.Do errors are returned directly. func (r *Request) Do() Result { - if r.throttle != nil { - r.throttle.Accept() - } + r.tryThrottle() var result Result err := r.request(func(req *http.Request, resp *http.Response) { @@ -835,9 +847,7 @@ func (r *Request) Do() Result { // DoRaw executes the request but does not process the response body. func (r *Request) DoRaw() ([]byte, error) { - if r.throttle != nil { - r.throttle.Accept() - } + r.tryThrottle() var result Result err := r.request(func(req *http.Request, resp *http.Response) { diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/request_test.go b/vendor/k8s.io/kubernetes/pkg/client/restclient/request_test.go similarity index 97% rename from vendor/k8s.io/kubernetes/pkg/client/unversioned/request_test.go rename to vendor/k8s.io/kubernetes/pkg/client/restclient/request_test.go index 266be6b24..ccf14cb7e 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/request_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/restclient/request_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package unversioned +package restclient import ( "bytes" @@ -38,6 +38,7 @@ import ( "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/flowcontrol" "k8s.io/kubernetes/pkg/util/httpstream" "k8s.io/kubernetes/pkg/util/intstr" utiltesting "k8s.io/kubernetes/pkg/util/testing" @@ -767,17 +768,26 @@ func TestBackoffLifecycle(t *testing.T) { // which are used in the server implementation returning StatusOK above. seconds := []int{0, 1, 2, 4, 8, 0, 1, 2, 4, 0} request := c.Verb("POST").Prefix("backofftest").Suffix("abc") + clock := util.FakeClock{} request.backoffMgr = &URLBackoff{ - Backoff: util.NewBackOff( + // Use a fake backoff here to avoid flakes and speed the test up. + Backoff: flowcontrol.NewFakeBackOff( time.Duration(1)*time.Second, - time.Duration(200)*time.Second)} + time.Duration(200)*time.Second, + &clock, + )} + for _, sec := range seconds { - start := time.Now() + thisBackoff := request.backoffMgr.CalculateBackoff(request.URL()) + t.Logf("Current backoff %v", thisBackoff) + if thisBackoff != time.Duration(sec)*time.Second { + t.Errorf("Backoff is %v instead of %v", thisBackoff, sec) + } + now := clock.Now() request.DoRaw() - finish := time.Since(start) - t.Logf("%v finished in %v", sec, finish) - if finish < time.Duration(sec)*time.Second || finish >= time.Duration(sec+5)*time.Second { - t.Fatalf("%v not in range %v", finish, sec) + elapsed := clock.Since(now) + if clock.Since(now) != thisBackoff { + t.Errorf("CalculatedBackoff not honored by clock: Expected time of %v, but got %v ", thisBackoff, elapsed) } } } @@ -1085,7 +1095,7 @@ func TestAbsPath(t *testing.T) { absPath string wantsAbsPath string }{ - {"", "", "", "/"}, + {"/", "", "", "/"}, {"", "", "/", "/"}, {"", "", "/api", "/api"}, {"", "", "/api/", "/api/"}, @@ -1105,8 +1115,8 @@ func TestAbsPath(t *testing.T) { {"/p1/api/p2", "/r1", "/api/", "/p1/api/p2/api/"}, {"/p1/api/p2", "/api/r1", "/api/", "/p1/api/p2/api/"}, } { - c := NewOrDie(&Config{Host: "http://localhost:123" + tc.configPrefix}) - r := c.Post().Prefix(tc.resourcePrefix).AbsPath(tc.absPath) + u, _ := url.Parse("http://localhost:123" + tc.configPrefix) + r := NewRequest(nil, "POST", u, "", ContentConfig{GroupVersion: &unversioned.GroupVersion{Group: "test"}}, nil, nil).Prefix(tc.resourcePrefix).AbsPath(tc.absPath) if r.pathPrefix != tc.wantsAbsPath { t.Errorf("test case %d failed, unexpected path: %q, expected %q", i, r.pathPrefix, tc.wantsAbsPath) } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/transport.go b/vendor/k8s.io/kubernetes/pkg/client/restclient/transport.go similarity index 99% rename from vendor/k8s.io/kubernetes/pkg/client/unversioned/transport.go rename to vendor/k8s.io/kubernetes/pkg/client/restclient/transport.go index 74469cae0..7d4b497c3 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/transport.go +++ b/vendor/k8s.io/kubernetes/pkg/client/restclient/transport.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package unversioned +package restclient import ( "crypto/tls" diff --git a/vendor/k8s.io/kubernetes/pkg/client/restclient/url_utils.go b/vendor/k8s.io/kubernetes/pkg/client/restclient/url_utils.go new file mode 100644 index 000000000..9a83d7874 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/restclient/url_utils.go @@ -0,0 +1,93 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 restclient + +import ( + "fmt" + "net/url" + "path" + + "k8s.io/kubernetes/pkg/api/unversioned" +) + +// DefaultServerURL converts a host, host:port, or URL string to the default base server API path +// to use with a Client at a given API version following the standard conventions for a +// Kubernetes API. +func DefaultServerURL(host, apiPath string, groupVersion unversioned.GroupVersion, defaultTLS bool) (*url.URL, string, error) { + if host == "" { + return nil, "", fmt.Errorf("host must be a URL or a host:port pair") + } + base := host + hostURL, err := url.Parse(base) + if err != nil { + return nil, "", err + } + if hostURL.Scheme == "" { + scheme := "http://" + if defaultTLS { + scheme = "https://" + } + hostURL, err = url.Parse(scheme + base) + if err != nil { + return nil, "", err + } + if hostURL.Path != "" && hostURL.Path != "/" { + return nil, "", fmt.Errorf("host must be a URL or a host:port pair: %q", base) + } + } + + // hostURL.Path is optional; a non-empty Path is treated as a prefix that is to be applied to + // all URIs used to access the host. this is useful when there's a proxy in front of the + // apiserver that has relocated the apiserver endpoints, forwarding all requests from, for + // example, /a/b/c to the apiserver. in this case the Path should be /a/b/c. + // + // if running without a frontend proxy (that changes the location of the apiserver), then + // hostURL.Path should be blank. + // + // versionedAPIPath, a path relative to baseURL.Path, points to a versioned API base + versionedAPIPath := path.Join("/", apiPath) + + // Add the version to the end of the path + if len(groupVersion.Group) > 0 { + versionedAPIPath = path.Join(versionedAPIPath, groupVersion.Group, groupVersion.Version) + + } else { + versionedAPIPath = path.Join(versionedAPIPath, groupVersion.Version) + + } + + return hostURL, versionedAPIPath, nil +} + +// defaultServerUrlFor is shared between IsConfigTransportTLS and RESTClientFor. It +// requires Host and Version to be set prior to being called. +func defaultServerUrlFor(config *Config) (*url.URL, string, error) { + // TODO: move the default to secure when the apiserver supports TLS by default + // config.Insecure is taken to mean "I want HTTPS but don't bother checking the certs against a CA." + hasCA := len(config.CAFile) != 0 || len(config.CAData) != 0 + hasCert := len(config.CertFile) != 0 || len(config.CertData) != 0 + defaultTLS := hasCA || hasCert || config.Insecure + host := config.Host + if host == "" { + host = "localhost" + } + + if config.GroupVersion != nil { + return DefaultServerURL(host, config.APIPath, *config.GroupVersion, defaultTLS) + } + return DefaultServerURL(host, config.APIPath, unversioned.GroupVersion{}, defaultTLS) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/restclient/url_utils_test.go b/vendor/k8s.io/kubernetes/pkg/client/restclient/url_utils_test.go new file mode 100644 index 000000000..4bf8c5423 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/restclient/url_utils_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 restclient + +import ( + "path" + "testing" + + "k8s.io/kubernetes/pkg/api/testapi" +) + +func TestValidatesHostParameter(t *testing.T) { + testCases := []struct { + Host string + APIPath string + + URL string + Err bool + }{ + {"127.0.0.1", "", "http://127.0.0.1/" + testapi.Default.GroupVersion().Version, false}, + {"127.0.0.1:8080", "", "http://127.0.0.1:8080/" + testapi.Default.GroupVersion().Version, false}, + {"foo.bar.com", "", "http://foo.bar.com/" + testapi.Default.GroupVersion().Version, false}, + {"http://host/prefix", "", "http://host/prefix/" + testapi.Default.GroupVersion().Version, false}, + {"http://host", "", "http://host/" + testapi.Default.GroupVersion().Version, false}, + {"http://host", "/", "http://host/" + testapi.Default.GroupVersion().Version, false}, + {"http://host", "/other", "http://host/other/" + testapi.Default.GroupVersion().Version, false}, + {"host/server", "", "", true}, + } + for i, testCase := range testCases { + u, versionedAPIPath, err := DefaultServerURL(testCase.Host, testCase.APIPath, *testapi.Default.GroupVersion(), false) + switch { + case err == nil && testCase.Err: + t.Errorf("expected error but was nil") + continue + case err != nil && !testCase.Err: + t.Errorf("unexpected error %v", err) + continue + case err != nil: + continue + } + u.Path = path.Join(u.Path, versionedAPIPath) + if e, a := testCase.URL, u.String(); e != a { + t.Errorf("%d: expected host %s, got %s", i, e, a) + continue + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/urlbackoff.go b/vendor/k8s.io/kubernetes/pkg/client/restclient/urlbackoff.go similarity index 89% rename from vendor/k8s.io/kubernetes/pkg/client/unversioned/urlbackoff.go rename to vendor/k8s.io/kubernetes/pkg/client/restclient/urlbackoff.go index 331079bd7..df453e65f 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/urlbackoff.go +++ b/vendor/k8s.io/kubernetes/pkg/client/restclient/urlbackoff.go @@ -14,14 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package unversioned +package restclient import ( "net/url" "time" "github.com/golang/glog" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/flowcontrol" "k8s.io/kubernetes/pkg/util/sets" ) @@ -35,13 +35,14 @@ var maxResponseCode = 499 type BackoffManager interface { UpdateBackoff(actualUrl *url.URL, err error, responseCode int) CalculateBackoff(actualUrl *url.URL) time.Duration + Sleep(d time.Duration) } // URLBackoff struct implements the semantics on top of Backoff which // we need for URL specific exponential backoff. type URLBackoff struct { // Uses backoff as underlying implementation. - Backoff *util.Backoff + Backoff *flowcontrol.Backoff } // NoBackoff is a stub implementation, can be used for mocking or else as a default. @@ -54,12 +55,15 @@ func (n *NoBackoff) UpdateBackoff(actualUrl *url.URL, err error, responseCode in func (n *NoBackoff) CalculateBackoff(actualUrl *url.URL) time.Duration { return 0 * time.Second } +func (n *NoBackoff) Sleep(d time.Duration) { + return +} // Disable makes the backoff trivial, i.e., sets it to zero. This might be used // by tests which want to run 1000s of mock requests without slowing down. func (b *URLBackoff) Disable() { glog.V(4).Infof("Disabling backoff strategy") - b.Backoff = util.NewBackOff(0*time.Second, 0*time.Second) + b.Backoff = flowcontrol.NewBackOff(0*time.Second, 0*time.Second) } // baseUrlKey returns the key which urls will be mapped to. @@ -80,7 +84,7 @@ func (b *URLBackoff) baseUrlKey(rawurl *url.URL) string { func (b *URLBackoff) UpdateBackoff(actualUrl *url.URL, err error, responseCode int) { // range for retry counts that we store is [0,13] if responseCode > maxResponseCode || serverIsOverloadedSet.Has(responseCode) { - b.Backoff.Next(b.baseUrlKey(actualUrl), time.Now()) + b.Backoff.Next(b.baseUrlKey(actualUrl), b.Backoff.Clock.Now()) return } else if responseCode >= 300 || err != nil { glog.V(4).Infof("Client is returning errors: code %v, error %v", responseCode, err) @@ -95,3 +99,7 @@ func (b *URLBackoff) UpdateBackoff(actualUrl *url.URL, err error, responseCode i func (b *URLBackoff) CalculateBackoff(actualUrl *url.URL) time.Duration { return b.Backoff.Get(b.baseUrlKey(actualUrl)) } + +func (b *URLBackoff) Sleep(d time.Duration) { + b.Backoff.Clock.Sleep(d) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/urlbackoff_test.go b/vendor/k8s.io/kubernetes/pkg/client/restclient/urlbackoff_test.go similarity index 92% rename from vendor/k8s.io/kubernetes/pkg/client/unversioned/urlbackoff_test.go rename to vendor/k8s.io/kubernetes/pkg/client/restclient/urlbackoff_test.go index 8457c977b..5b370dbe5 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/urlbackoff_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/restclient/urlbackoff_test.go @@ -14,13 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package unversioned +package restclient import ( - "k8s.io/kubernetes/pkg/util" "net/url" "testing" "time" + + "k8s.io/kubernetes/pkg/util/flowcontrol" ) func parse(raw string) *url.URL { @@ -30,7 +31,7 @@ func parse(raw string) *url.URL { func TestURLBackoffFunctionalityCollisions(t *testing.T) { myBackoff := &URLBackoff{ - Backoff: util.NewBackOff(1*time.Second, 60*time.Second), + Backoff: flowcontrol.NewBackOff(1*time.Second, 60*time.Second), } // Add some noise and make sure backoff for a clean URL is zero. @@ -46,7 +47,7 @@ func TestURLBackoffFunctionalityCollisions(t *testing.T) { // TestURLBackoffFunctionality generally tests the URLBackoff wrapper. We avoid duplicating tests from backoff and request. func TestURLBackoffFunctionality(t *testing.T) { myBackoff := &URLBackoff{ - Backoff: util.NewBackOff(1*time.Second, 60*time.Second), + Backoff: flowcontrol.NewBackOff(1*time.Second, 60*time.Second), } // Now test that backoff increases, then recovers. diff --git a/vendor/k8s.io/kubernetes/pkg/client/restclient/versions.go b/vendor/k8s.io/kubernetes/pkg/client/restclient/versions.go new file mode 100644 index 000000000..e12c05c10 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/restclient/versions.go @@ -0,0 +1,88 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 restclient + +import ( + "encoding/json" + "fmt" + "net/http" + "path" + + "k8s.io/kubernetes/pkg/api/unversioned" +) + +const ( + legacyAPIPath = "/api" + defaultAPIPath = "/apis" +) + +// TODO: Is this obsoleted by the discovery client? + +// ServerAPIVersions returns the GroupVersions supported by the API server. +// It creates a RESTClient based on the passed in config, but it doesn't rely +// on the Version and Codec of the config, because it uses AbsPath and +// takes the raw response. +func ServerAPIVersions(c *Config) (groupVersions []string, err error) { + transport, err := TransportFor(c) + if err != nil { + return nil, err + } + client := http.Client{Transport: transport} + + configCopy := *c + configCopy.GroupVersion = nil + configCopy.APIPath = "" + baseURL, _, err := defaultServerUrlFor(&configCopy) + if err != nil { + return nil, err + } + // Get the groupVersions exposed at /api + originalPath := baseURL.Path + baseURL.Path = path.Join(originalPath, legacyAPIPath) + resp, err := client.Get(baseURL.String()) + if err != nil { + return nil, err + } + var v unversioned.APIVersions + defer resp.Body.Close() + err = json.NewDecoder(resp.Body).Decode(&v) + if err != nil { + return nil, fmt.Errorf("unexpected error: %v", err) + } + + groupVersions = append(groupVersions, v.Versions...) + // Get the groupVersions exposed at /apis + baseURL.Path = path.Join(originalPath, defaultAPIPath) + resp2, err := client.Get(baseURL.String()) + if err != nil { + return nil, err + } + var apiGroupList unversioned.APIGroupList + defer resp2.Body.Close() + err = json.NewDecoder(resp2.Body).Decode(&apiGroupList) + if err != nil { + return nil, fmt.Errorf("unexpected error: %v", err) + } + + for _, g := range apiGroupList.Groups { + for _, gv := range g.Versions { + groupVersions = append(groupVersions, gv.GroupVersion) + } + } + + return groupVersions, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/testdata/myCA.cer b/vendor/k8s.io/kubernetes/pkg/client/testdata/myCA.cer new file mode 100644 index 000000000..11148cc6b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/testdata/myCA.cer @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDATCCAemgAwIBAgIJAJqYDB1GJyW2MA0GCSqGSIb3DQEBBQUAMBcxFTATBgNV +BAMMDCJrdWJlcm5ldGVzIjAeFw0xNDEyMTYwNjQ2MjVaFw0xNjEyMTUwNjQ2MjVa +MBcxFTATBgNVBAMMDCJrdWJlcm5ldGVzIjCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAM4C3mfmc2oyg6VIfwpxVOqHrD8VnGu2gxx73vGlC3QEEaMmPb+m +QcqVNGsK4bEKFgaFR1Eo+clFhWCGCIqoSMMcdV2Blpm/8g7lvtmPsYJyGo/eNjKz +b4Vl7Uyvh2M6reI2N67aXGpdp4UEhpAHZu8N+tWt7yhP2mggv4vUiYAoSZ+8+xMM +9YwX9FR02ybJkDQWPL5hjDG1vPU3FiQTlxS4LstFY1IO6apQQOmY5Jb7YXK7qVhJ +M2i/FczFKnPdMjPSs+Do0hBYG8cYVpUFm1dW/ZG/qVlPn5Huod1Qv4kqnX2E+pka +B5dcpyFYPVfKGMW1pP30Nl+AGkae8y4f3u0CAwEAAaNQME4wHQYDVR0OBBYEFJFC +Tyb1cweoRBXrbfxc53PqC4yTMB8GA1UdIwQYMBaAFJFCTyb1cweoRBXrbfxc53Pq +C4yTMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBALs+LfOEteZVTISX +dFA8+/KuxtzV2O+Hozx317VtgeyMQXX7BnMI4kPDPPrVTAZqG4xjMeMexkotXLdK +EFGm8dvnbhlmhvB6PNBTwUf0mgyVE21ajKh9wdWgeBvG+IHnth4izSUEhBXEN+bY +JlKwlgvlTtck8aLhMo5tOwwmjlYEd1jB4dQZeQdkJs2H8LY4jwlrJE+FyLS8K2/6 +fRb9REE0dDVEzQJn8OCFbrC3+HdLO3dzUZSup7gvs5dVD6Jj/PtZGLn8E96ETtT0 +aOrQMABadPgZ3nzkW8luxEes9PgSOselTR3ACnho0fUCut+PTjjsRHxDV+qJPN3A +7vL/tDs= +-----END CERTIFICATE----- diff --git a/vendor/k8s.io/kubernetes/pkg/client/testdata/myCA.key b/vendor/k8s.io/kubernetes/pkg/client/testdata/myCA.key new file mode 100644 index 000000000..b7ce3c788 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/testdata/myCA.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAzgLeZ+ZzajKDpUh/CnFU6oesPxWca7aDHHve8aULdAQRoyY9 +v6ZBypU0awrhsQoWBoVHUSj5yUWFYIYIiqhIwxx1XYGWmb/yDuW+2Y+xgnIaj942 +MrNvhWXtTK+HYzqt4jY3rtpcal2nhQSGkAdm7w361a3vKE/aaCC/i9SJgChJn7z7 +Ewz1jBf0VHTbJsmQNBY8vmGMMbW89TcWJBOXFLguy0VjUg7pqlBA6Zjklvthcrup +WEkzaL8VzMUqc90yM9Kz4OjSEFgbxxhWlQWbV1b9kb+pWU+fke6h3VC/iSqdfYT6 +mRoHl1ynIVg9V8oYxbWk/fQ2X4AaRp7zLh/e7QIDAQABAoIBAQDM1Etf0OEGQO1l +g/xUXLSKb5USMCGTcydPRdY4Otp1YqpKpfYVPHADxXAV0f7ucNHPb+qlxnD87rOb +cgjCHGokHIKREwyzGAbLSyED3fwnb937F3yZ0pDaeKqFaazaO3iyByg8IP5r/2xV +NFe6krGElEjG9iZo1WSZzZ3FoO+JzDvIUOKtlymmfF4Gcl2fJwljPTfrQSIx/z/r +Ag0xy53fl87wiq4ZC46uk78m9lJQs3R8ojp/9kP7TNr1YDlAs9mpwWq/pirw3v6M +1l0AisGI9sbOP5s665yLvPbc9EUHaDlfRe1gt1cNo9QrgfgvkZHZ+/DZk+S0P6RP +lJDsRUddAoGBAO7sYK62ov5a9MU01XbRb3MHpinxLperEaRmjyoywfDaIOa6IYuc +gUbShtH/VjeOTy8lnktXy8WDpg5EyWIKjXwucpHE7FAd4o7BPFbCyvVCh5s+02M9 +NYAlvRCthkjw6vl99noFLL0BFd/wLjI4O0MpHNKTJgW26mAdtmxwZ8wrAoGBANy8 +SrabkqSJaXmT30ndDz72qLCT1+KsW+bjpHGlU4VNJchGmIb+l/lCPPBiM+YcQh24 +4YMwxmTVQf1FuYrAD67dSVQzS7xqENIMI0hmpErBT8Ka71kicZINwro4+8vgfzha +YD0ohj7fIp9rkXTp/Jr9K35vQ/rrubtXascJED1HAoGAKH7gFDzYe4wnGJXP6Iev +ACw3ubwrTYGtR9QqR9i6jnwqP3Ek5mjscHiWaVmB34C7Yx5ZKiQDYcLijmCSUY/A +U1/8A0EBXMLz94ZBF+OESvWvzlxjr9pcCxBab006CXrsGMWE1UGzR4W7k20+Jzzo +roV1YSuXsjhCmW/vz4ltzmkCgYAlW+j5RxNmrasgXJqqEbQG4BBk8mDTiIB1b4nh +gi3Ene4LG4etMWHfWgqeVMCb7aRzC1t/rL2nS0DD8Q0aIq+E1QcYLSZgWUNHia5f +DqA31sf9E+P2nhHCunl+sy5Kr1BY5VLshvNRqMpfWQFhXEjYooi9+W70BPmGb6Eu +1qXc+QKBgE3mcE666Ep4d6dibMoCyea+ir5zl9PllLTpRg2OpBHdm5tk5D7Tfxx8 +Uxs2FI6oZ3G4IaeTGp6blLgyTdMLpuZGM6HdJNI16bk//E6PQV3m6jJ6duhn9Ezr +7l7eI4Y2s7H+SJixNtxtDitFOnjD+KI502ypsLEvlsXZpeXvFtaw +-----END RSA PRIVATE KEY----- diff --git a/vendor/k8s.io/kubernetes/pkg/client/testdata/mycertvalid.cer b/vendor/k8s.io/kubernetes/pkg/client/testdata/mycertvalid.cer new file mode 100644 index 000000000..8b8bf36bc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/testdata/mycertvalid.cer @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICpzCCAY8CCQDWu9ClTyE4ADANBgkqhkiG9w0BAQUFADAXMRUwEwYDVQQDDAwi +a3ViZXJuZXRlcyIwHhcNMTQxMjE2MDY0NjI1WhcNMTUxMjE2MDY0NjI1WjAUMRIw +EAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCogtUXHT0lvympI8FUU+wxjueCDQmNPtVuaW0LQ0tH1oQwAB7NuFUgPBZsiN8o +tI3P6EeuBM5nJwy1cP3x630ac1CIqb6zgmRsle15BYRfyVlIXfLYjjcCcMgfRIa/ +FFKAnX46fzL9I3re7ZntTv4XBp6dYm2zEIPureqgpJ369ewBNQ9T5wI+jg+EVryO +dRFTaihW6Ukz82djEY9HqHHDg0YbiAa918ipPZ4YECDPH2fX1grVxO1AqveTkw2i +LI/I7aqy4yqZCB1ar1wnrVzqNR0LcOFupFHj5WberwCao1yDd4C/yEK5tre6sq4v +hwF2II8NFVY7GFQP/V/V5ET7AgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAC891nLG +CiggNRJPOS5rKhUBQa3uCgmsCTuwSf/bSrBMzfTkK5fQsqWvMks+ILYv4q6yGWYj +eqCeNPetbRDTKAtfyI+J9rKGfmvP/cWMK1TVB7OFYGb31Ra6w05Cg9ngCPHvelBh +0t4flVjTBv5MaVYpHQlRB+cQre2prd7qkd3hVHrO3Wf1I3VtqYaXQxyleVHq5FBD +O2zFL2Y1zBb6SUmtK0C1CcUG5rUsasal3FvFkWqeqeN+EkP/7RvMDo4S5JOxbWQp +OoebfirEQcUhz1duIb5th6UKhsJminFozHo0hRwenvhL5Q5sDiXn+1pcolj1gBzm +Ivob4OleMUcIGTg= +-----END CERTIFICATE----- diff --git a/vendor/k8s.io/kubernetes/pkg/client/testdata/mycertvalid.key b/vendor/k8s.io/kubernetes/pkg/client/testdata/mycertvalid.key new file mode 100644 index 000000000..2828dfee3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/testdata/mycertvalid.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAqILVFx09Jb8pqSPBVFPsMY7ngg0JjT7VbmltC0NLR9aEMAAe +zbhVIDwWbIjfKLSNz+hHrgTOZycMtXD98et9GnNQiKm+s4JkbJXteQWEX8lZSF3y +2I43AnDIH0SGvxRSgJ1+On8y/SN63u2Z7U7+FwaenWJtsxCD7q3qoKSd+vXsATUP +U+cCPo4PhFa8jnURU2ooVulJM/NnYxGPR6hxw4NGG4gGvdfIqT2eGBAgzx9n19YK +1cTtQKr3k5MNoiyPyO2qsuMqmQgdWq9cJ61c6jUdC3DhbqRR4+Vm3q8AmqNcg3eA +v8hCuba3urKuL4cBdiCPDRVWOxhUD/1f1eRE+wIDAQABAoIBAQCGv4gSYakh5Ak2 +XYcdHbbDslhh4HcA4XvePKOb3AX4vgsaLx5ytrIrgqETzSdV73tvA3k+KE28ordA +58fJiduSKR//CG2cMeqIAiPRIJ5H0kR439dvX9mRNApzJmLxrRiEDGyB7nEhhxub +5DewUfhRBVQU2j6Kb+xwEdaK+tfxcyVCKnloAh2PwBoSXcpK41ii0fvDzPwEuTqc +LexUxEV2Z9ClxQ2sJ2MLE7x57TQK0Earrph/ew/MDSYfKnay1B5vcXPX8rAiQJdP +Rc0BgeXV+j5pH+s5zOFMJRXrvI/9m+trr8MCYDrKooyFkk2cmsrxz3HvmJ3+t52s +jSXd7RKBAoGBANH0eap41oDo4P9ZF/ngAu7l1Yu5Vk6vB7wGJhekavv6dl+lYpw1 +wUlKv32ZHmah8LvrRdyALHQRJ19V6NJiHlVwiJEEyXQWUsJTmvsvb7idEeU861iw +0bFelJlW7GLCIH/02enWKwMH6oR50Wa1xTbI3CtizbEoWCTnSK5iC1HbAoGBAM13 +kR8vNHhgWKv/AgIYKFrPJjMXmKBfv/jUyKUfcQi9kIZMdaYpN5yPKZIkBIFOVHbG +suH4/7cVA3ZCfQljY6PGLfZu7QPupvd5KrEbBuKGuIdxrUk6mmLjLEXhoYSAeaw/ +OsYKsGHdhWRstCB4R58jqpVcAr1pytxbx1oBxRNhAoGBAKv/pQBz1/5pSZHGsi6h +RqXhoYzCu6LgHuz4+JHbv01IRVtbyKoCG6NoWfGR0+bueaHpPyVB16kKOIAQiBh6 +CzGhbC+phUPV2dya01c96D+MZZGv03mn+VFeE0x/ek35jNhmhXLcYgYsoQIALfz/ +ol2cNUpRugKM85Df7Jn3diCLAoGAS8xNRDTU5Yedjq3/nqgs0vtSe0y8KIXKO1C8 +SHYl6/SKyZCRYmAYPPBvhJM2+kDcVgkNWuHR7EebRFhY6kq5KmTk9eGMHIRBIlCX +2EhBLPZIQudD5xzwcYSfA5SuUkRXHp0g4Ih281OWbyrO9J+KxIGS35DXDetmRA6z +p1e5zWECgYEAulYIXb4tV8zKxJ+5/lLzeOZxzrvLMWv5YLlygjt5HWtCLl9B02Q7 ++zGcMi9O5ASN1cuf5hiQNDvMOQnD5Pywe8/i8zP3QLVDcnlOY83n2Gl3Huh6w3O5 +l+hvRO3LAm0VZSFaJE8WBm45vm09vR0X+69pkcSl/cfyVHygMmhaZSs= +-----END RSA PRIVATE KEY----- diff --git a/vendor/k8s.io/kubernetes/pkg/client/testdata/mycertvalid.req b/vendor/k8s.io/kubernetes/pkg/client/testdata/mycertvalid.req new file mode 100644 index 000000000..efac2739e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/testdata/mycertvalid.req @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAqILVFx09Jb8pqSPBVFPsMY7ngg0JjT7VbmltC0NL +R9aEMAAezbhVIDwWbIjfKLSNz+hHrgTOZycMtXD98et9GnNQiKm+s4JkbJXteQWE +X8lZSF3y2I43AnDIH0SGvxRSgJ1+On8y/SN63u2Z7U7+FwaenWJtsxCD7q3qoKSd ++vXsATUPU+cCPo4PhFa8jnURU2ooVulJM/NnYxGPR6hxw4NGG4gGvdfIqT2eGBAg +zx9n19YK1cTtQKr3k5MNoiyPyO2qsuMqmQgdWq9cJ61c6jUdC3DhbqRR4+Vm3q8A +mqNcg3eAv8hCuba3urKuL4cBdiCPDRVWOxhUD/1f1eRE+wIDAQABoAAwDQYJKoZI +hvcNAQEFBQADggEBACPbB3L1oW5Ah61YiUiRIyT1i+T0aGZN30QmyTxGrahTqFFz +JFJE+PwNX4ET1K5j634ltnbn/9I03bLs8zXrzmdNDR7OXNdvoGVG8vyldxkqopeK +i7AwH4zKOoH7lFdcn8ISyTKFXERAOnQMbQvFP5ZW8h/nVljZ1NWh08HYE2uhiG6n +sudWIFnorun0tKWyqlnDiiGzoXJNp6X5QvluIP/a5ntSleNCWiJXKY6f0tx/rA+0 +syjk63lShz4eXN1aN5uL2z9borXkZKdFGKaLGqBIgMxM6gjJz3XTDoublTyOAO2n +T0f//nDSamgEQzCLDzPQr7v7diJ9gt9ueD/Q17U= +-----END CERTIFICATE REQUEST----- diff --git a/vendor/k8s.io/kubernetes/pkg/client/testing/core/actions.go b/vendor/k8s.io/kubernetes/pkg/client/testing/core/actions.go new file mode 100644 index 000000000..23229f6fb --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/testing/core/actions.go @@ -0,0 +1,455 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 core + +import ( + "strings" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" +) + +func NewRootGetAction(resource, name string) GetActionImpl { + action := GetActionImpl{} + action.Verb = "get" + action.Resource = resource + action.Name = name + + return action +} + +func NewGetAction(resource, namespace, name string) GetActionImpl { + action := GetActionImpl{} + action.Verb = "get" + action.Resource = resource + action.Namespace = namespace + action.Name = name + + return action +} + +func NewRootListAction(resource string, opts api.ListOptions) ListActionImpl { + action := ListActionImpl{} + action.Verb = "list" + action.Resource = resource + labelSelector := opts.LabelSelector + if labelSelector == nil { + labelSelector = labels.Everything() + } + fieldSelector := opts.FieldSelector + if fieldSelector == nil { + fieldSelector = fields.Everything() + } + action.ListRestrictions = ListRestrictions{labelSelector, fieldSelector} + + return action +} + +func NewListAction(resource, namespace string, opts api.ListOptions) ListActionImpl { + action := ListActionImpl{} + action.Verb = "list" + action.Resource = resource + action.Namespace = namespace + labelSelector := opts.LabelSelector + if labelSelector == nil { + labelSelector = labels.Everything() + } + fieldSelector := opts.FieldSelector + if fieldSelector == nil { + fieldSelector = fields.Everything() + } + action.ListRestrictions = ListRestrictions{labelSelector, fieldSelector} + + return action +} + +func NewRootCreateAction(resource string, object runtime.Object) CreateActionImpl { + action := CreateActionImpl{} + action.Verb = "create" + action.Resource = resource + action.Object = object + + return action +} + +func NewCreateAction(resource, namespace string, object runtime.Object) CreateActionImpl { + action := CreateActionImpl{} + action.Verb = "create" + action.Resource = resource + action.Namespace = namespace + action.Object = object + + return action +} + +func NewRootUpdateAction(resource string, object runtime.Object) UpdateActionImpl { + action := UpdateActionImpl{} + action.Verb = "update" + action.Resource = resource + action.Object = object + + return action +} + +func NewUpdateAction(resource, namespace string, object runtime.Object) UpdateActionImpl { + action := UpdateActionImpl{} + action.Verb = "update" + action.Resource = resource + action.Namespace = namespace + action.Object = object + + return action +} + +func NewRootPatchAction(resource string, object runtime.Object) PatchActionImpl { + action := PatchActionImpl{} + action.Verb = "patch" + action.Resource = resource + action.Object = object + + return action +} + +func NewPatchAction(resource, namespace string, object runtime.Object) PatchActionImpl { + action := PatchActionImpl{} + action.Verb = "patch" + action.Resource = resource + action.Namespace = namespace + action.Object = object + + return action +} + +func NewRootUpdateSubresourceAction(resource, subresource string, object runtime.Object) UpdateActionImpl { + action := UpdateActionImpl{} + action.Verb = "update" + action.Resource = resource + action.Subresource = subresource + action.Object = object + + return action +} +func NewUpdateSubresourceAction(resource, subresource, namespace string, object runtime.Object) UpdateActionImpl { + action := UpdateActionImpl{} + action.Verb = "update" + action.Resource = resource + action.Subresource = subresource + action.Namespace = namespace + action.Object = object + + return action +} + +func NewRootDeleteAction(resource, name string) DeleteActionImpl { + action := DeleteActionImpl{} + action.Verb = "delete" + action.Resource = resource + action.Name = name + + return action +} + +func NewDeleteAction(resource, namespace, name string) DeleteActionImpl { + action := DeleteActionImpl{} + action.Verb = "delete" + action.Resource = resource + action.Namespace = namespace + action.Name = name + + return action +} + +func NewRootDeleteCollectionAction(resource string, opts api.ListOptions) DeleteCollectionActionImpl { + action := DeleteCollectionActionImpl{} + action.Verb = "delete-collection" + action.Resource = resource + labelSelector := opts.LabelSelector + if labelSelector == nil { + labelSelector = labels.Everything() + } + fieldSelector := opts.FieldSelector + if fieldSelector == nil { + fieldSelector = fields.Everything() + } + action.ListRestrictions = ListRestrictions{labelSelector, fieldSelector} + + return action +} + +func NewDeleteCollectionAction(resource, namespace string, opts api.ListOptions) DeleteCollectionActionImpl { + action := DeleteCollectionActionImpl{} + action.Verb = "delete-collection" + action.Resource = resource + action.Namespace = namespace + labelSelector := opts.LabelSelector + if labelSelector == nil { + labelSelector = labels.Everything() + } + fieldSelector := opts.FieldSelector + if fieldSelector == nil { + fieldSelector = fields.Everything() + } + action.ListRestrictions = ListRestrictions{labelSelector, fieldSelector} + + return action +} + +func NewRootWatchAction(resource string, opts api.ListOptions) WatchActionImpl { + action := WatchActionImpl{} + action.Verb = "watch" + action.Resource = resource + labelSelector := opts.LabelSelector + if labelSelector == nil { + labelSelector = labels.Everything() + } + fieldSelector := opts.FieldSelector + if fieldSelector == nil { + fieldSelector = fields.Everything() + } + action.WatchRestrictions = WatchRestrictions{labelSelector, fieldSelector, opts.ResourceVersion} + + return action +} + +func NewWatchAction(resource, namespace string, opts api.ListOptions) WatchActionImpl { + action := WatchActionImpl{} + action.Verb = "watch" + action.Resource = resource + action.Namespace = namespace + labelSelector := opts.LabelSelector + if labelSelector == nil { + labelSelector = labels.Everything() + } + fieldSelector := opts.FieldSelector + if fieldSelector == nil { + fieldSelector = fields.Everything() + } + action.WatchRestrictions = WatchRestrictions{labelSelector, fieldSelector, opts.ResourceVersion} + + return action +} + +func NewProxyGetAction(resource, namespace, scheme, name, port, path string, params map[string]string) ProxyGetActionImpl { + action := ProxyGetActionImpl{} + action.Verb = "get" + action.Resource = resource + action.Namespace = namespace + action.Scheme = scheme + action.Name = name + action.Port = port + action.Path = path + action.Params = params + return action +} + +type ListRestrictions struct { + Labels labels.Selector + Fields fields.Selector +} +type WatchRestrictions struct { + Labels labels.Selector + Fields fields.Selector + ResourceVersion string +} + +type Action interface { + GetNamespace() string + GetVerb() string + GetResource() string + GetSubresource() string + Matches(verb, resource string) bool +} + +type GenericAction interface { + Action + GetValue() interface{} +} + +type GetAction interface { + Action + GetName() string +} + +type ListAction interface { + Action + GetListRestrictions() ListRestrictions +} + +type CreateAction interface { + Action + GetObject() runtime.Object +} + +type UpdateAction interface { + Action + GetObject() runtime.Object +} + +type DeleteAction interface { + Action + GetName() string +} + +type WatchAction interface { + Action + GetWatchRestrictions() WatchRestrictions +} + +type ProxyGetAction interface { + Action + GetScheme() string + GetName() string + GetPort() string + GetPath() string + GetParams() map[string]string +} + +type ActionImpl struct { + Namespace string + Verb string + Resource string + Subresource string +} + +func (a ActionImpl) GetNamespace() string { + return a.Namespace +} +func (a ActionImpl) GetVerb() string { + return a.Verb +} +func (a ActionImpl) GetResource() string { + return a.Resource +} +func (a ActionImpl) GetSubresource() string { + return a.Subresource +} +func (a ActionImpl) Matches(verb, resource string) bool { + return strings.ToLower(verb) == strings.ToLower(a.Verb) && + strings.ToLower(resource) == strings.ToLower(a.Resource) +} + +type GenericActionImpl struct { + ActionImpl + Value interface{} +} + +func (a GenericActionImpl) GetValue() interface{} { + return a.Value +} + +type GetActionImpl struct { + ActionImpl + Name string +} + +func (a GetActionImpl) GetName() string { + return a.Name +} + +type ListActionImpl struct { + ActionImpl + ListRestrictions ListRestrictions +} + +func (a ListActionImpl) GetListRestrictions() ListRestrictions { + return a.ListRestrictions +} + +type CreateActionImpl struct { + ActionImpl + Object runtime.Object +} + +func (a CreateActionImpl) GetObject() runtime.Object { + return a.Object +} + +type UpdateActionImpl struct { + ActionImpl + Object runtime.Object +} + +func (a UpdateActionImpl) GetObject() runtime.Object { + return a.Object +} + +type PatchActionImpl struct { + ActionImpl + Object runtime.Object +} + +func (a PatchActionImpl) GetObject() runtime.Object { + return a.Object +} + +type DeleteActionImpl struct { + ActionImpl + Name string +} + +func (a DeleteActionImpl) GetName() string { + return a.Name +} + +type DeleteCollectionActionImpl struct { + ActionImpl + ListRestrictions ListRestrictions +} + +func (a DeleteCollectionActionImpl) GetListRestrictions() ListRestrictions { + return a.ListRestrictions +} + +type WatchActionImpl struct { + ActionImpl + WatchRestrictions WatchRestrictions +} + +func (a WatchActionImpl) GetWatchRestrictions() WatchRestrictions { + return a.WatchRestrictions +} + +type ProxyGetActionImpl struct { + ActionImpl + Scheme string + Name string + Port string + Path string + Params map[string]string +} + +func (a ProxyGetActionImpl) GetScheme() string { + return a.Scheme +} + +func (a ProxyGetActionImpl) GetName() string { + return a.Name +} + +func (a ProxyGetActionImpl) GetPort() string { + return a.Port +} + +func (a ProxyGetActionImpl) GetPath() string { + return a.Path +} + +func (a ProxyGetActionImpl) GetParams() map[string]string { + return a.Params +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/testing/core/fake.go b/vendor/k8s.io/kubernetes/pkg/client/testing/core/fake.go new file mode 100644 index 000000000..148d5961f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/testing/core/fake.go @@ -0,0 +1,231 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 core + +import ( + "fmt" + "sync" + + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/version" + "k8s.io/kubernetes/pkg/watch" +) + +// Fake implements client.Interface. Meant to be embedded into a struct to get a default +// implementation. This makes faking out just the method you want to test easier. +type Fake struct { + sync.RWMutex + actions []Action // these may be castable to other types, but "Action" is the minimum + + // ReactionChain is the list of reactors that will be attempted for every request in the order they are tried + ReactionChain []Reactor + // WatchReactionChain is the list of watch reactors that will be attempted for every request in the order they are tried + WatchReactionChain []WatchReactor + // ProxyReactionChain is the list of proxy reactors that will be attempted for every request in the order they are tried + ProxyReactionChain []ProxyReactor + + Resources map[string]*unversioned.APIResourceList +} + +// Reactor is an interface to allow the composition of reaction functions. +type Reactor interface { + // Handles indicates whether or not this Reactor deals with a given action + Handles(action Action) bool + // React handles the action and returns results. It may choose to delegate by indicated handled=false + React(action Action) (handled bool, ret runtime.Object, err error) +} + +// WatchReactor is an interface to allow the composition of watch functions. +type WatchReactor interface { + // Handles indicates whether or not this Reactor deals with a given action + Handles(action Action) bool + // React handles a watch action and returns results. It may choose to delegate by indicated handled=false + React(action Action) (handled bool, ret watch.Interface, err error) +} + +// ProxyReactor is an interface to allow the composition of proxy get functions. +type ProxyReactor interface { + // Handles indicates whether or not this Reactor deals with a given action + Handles(action Action) bool + // React handles a watch action and returns results. It may choose to delegate by indicated handled=false + React(action Action) (handled bool, ret restclient.ResponseWrapper, err error) +} + +// ReactionFunc is a function that returns an object or error for a given Action. If "handled" is false, +// then the test client will continue ignore the results and continue to the next ReactionFunc +type ReactionFunc func(action Action) (handled bool, ret runtime.Object, err error) + +// WatchReactionFunc is a function that returns a watch interface. If "handled" is false, +// then the test client will continue ignore the results and continue to the next ReactionFunc +type WatchReactionFunc func(action Action) (handled bool, ret watch.Interface, err error) + +// ProxyReactionFunc is a function that returns a ResponseWrapper interface for a given Action. If "handled" is false, +// then the test client will continue ignore the results and continue to the next ProxyReactionFunc +type ProxyReactionFunc func(action Action) (handled bool, ret restclient.ResponseWrapper, err error) + +// AddReactor appends a reactor to the end of the chain +func (c *Fake) AddReactor(verb, resource string, reaction ReactionFunc) { + c.ReactionChain = append(c.ReactionChain, &SimpleReactor{verb, resource, reaction}) +} + +// PrependReactor adds a reactor to the beginning of the chain +func (c *Fake) PrependReactor(verb, resource string, reaction ReactionFunc) { + c.ReactionChain = append([]Reactor{&SimpleReactor{verb, resource, reaction}}, c.ReactionChain...) +} + +// AddWatchReactor appends a reactor to the end of the chain +func (c *Fake) AddWatchReactor(resource string, reaction WatchReactionFunc) { + c.WatchReactionChain = append(c.WatchReactionChain, &SimpleWatchReactor{resource, reaction}) +} + +// PrependWatchReactor adds a reactor to the beginning of the chain +func (c *Fake) PrependWatchReactor(resource string, reaction WatchReactionFunc) { + c.WatchReactionChain = append([]WatchReactor{&SimpleWatchReactor{resource, reaction}}, c.WatchReactionChain...) +} + +// AddProxyReactor appends a reactor to the end of the chain +func (c *Fake) AddProxyReactor(resource string, reaction ProxyReactionFunc) { + c.ProxyReactionChain = append(c.ProxyReactionChain, &SimpleProxyReactor{resource, reaction}) +} + +// PrependProxyReactor adds a reactor to the beginning of the chain +func (c *Fake) PrependProxyReactor(resource string, reaction ProxyReactionFunc) { + c.ProxyReactionChain = append([]ProxyReactor{&SimpleProxyReactor{resource, reaction}}, c.ProxyReactionChain...) +} + +// Invokes records the provided Action and then invokes the ReactFn (if provided). +// defaultReturnObj is expected to be of the same type a normal call would return. +func (c *Fake) Invokes(action Action, defaultReturnObj runtime.Object) (runtime.Object, error) { + c.Lock() + defer c.Unlock() + + c.actions = append(c.actions, action) + for _, reactor := range c.ReactionChain { + if !reactor.Handles(action) { + continue + } + + handled, ret, err := reactor.React(action) + if !handled { + continue + } + + return ret, err + } + + return defaultReturnObj, nil +} + +// InvokesWatch records the provided Action and then invokes the ReactFn (if provided). +func (c *Fake) InvokesWatch(action Action) (watch.Interface, error) { + c.Lock() + defer c.Unlock() + + c.actions = append(c.actions, action) + for _, reactor := range c.WatchReactionChain { + if !reactor.Handles(action) { + continue + } + + handled, ret, err := reactor.React(action) + if !handled { + continue + } + + return ret, err + } + + return nil, fmt.Errorf("unhandled watch: %#v", action) +} + +// InvokesProxy records the provided Action and then invokes the ReactFn (if provided). +func (c *Fake) InvokesProxy(action Action) restclient.ResponseWrapper { + c.Lock() + defer c.Unlock() + + c.actions = append(c.actions, action) + for _, reactor := range c.ProxyReactionChain { + if !reactor.Handles(action) { + continue + } + + handled, ret, err := reactor.React(action) + if !handled || err != nil { + continue + } + + return ret + } + + return nil +} + +// ClearActions clears the history of actions called on the fake client +func (c *Fake) ClearActions() { + c.Lock() + c.Unlock() + + c.actions = make([]Action, 0) +} + +// Actions returns a chronologically ordered slice fake actions called on the fake client +func (c *Fake) Actions() []Action { + c.RLock() + defer c.RUnlock() + fa := make([]Action, len(c.actions)) + copy(fa, c.actions) + return fa +} + +// TODO: this probably should be moved to somewhere else. +type FakeDiscovery struct { + *Fake +} + +func (c *FakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*unversioned.APIResourceList, error) { + action := ActionImpl{ + Verb: "get", + Resource: "resource", + } + c.Invokes(action, nil) + return c.Resources[groupVersion], nil +} + +func (c *FakeDiscovery) ServerResources() (map[string]*unversioned.APIResourceList, error) { + action := ActionImpl{ + Verb: "get", + Resource: "resource", + } + c.Invokes(action, nil) + return c.Resources, nil +} + +func (c *FakeDiscovery) ServerGroups() (*unversioned.APIGroupList, error) { + return nil, nil +} + +func (c *FakeDiscovery) ServerVersion() (*version.Info, error) { + action := ActionImpl{} + action.Verb = "get" + action.Resource = "version" + + c.Invokes(action, nil) + versionInfo := version.Get() + return &versionInfo, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/testing/core/fixture.go b/vendor/k8s.io/kubernetes/pkg/client/testing/core/fixture.go new file mode 100644 index 000000000..0406c4659 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/testing/core/fixture.go @@ -0,0 +1,311 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 core + +import ( + "fmt" + "io/ioutil" + "reflect" + "strings" + + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/yaml" + "k8s.io/kubernetes/pkg/watch" +) + +// ObjectRetriever abstracts the implementation for retrieving or setting generic +// objects. It is intended to be used to fake calls to a server by returning +// objects based on their kind and name. +type ObjectRetriever interface { + // Kind should return a resource or a list of resources (depending on the provided kind and + // name). It should return an error if the caller should communicate an error to the server. + Kind(gvk unversioned.GroupVersionKind, name string) (runtime.Object, error) + // Add adds a runtime object for test purposes into this object. + Add(runtime.Object) error +} + +// ObjectScheme abstracts the implementation of common operations on objects. +type ObjectScheme interface { + runtime.ObjectCreater + runtime.ObjectCopier + runtime.ObjectTyper +} + +// ObjectReaction returns a ReactionFunc that takes a generic action string of the form +// - or -- and attempts to return a runtime +// Object or error that matches the requested action. For instance, list-replicationControllers +// should attempt to return a list of replication controllers. This method delegates to the +// ObjectRetriever interface to satisfy retrieval of lists or retrieval of single items. +// TODO: add support for sub resources +func ObjectReaction(o ObjectRetriever, mapper meta.RESTMapper) ReactionFunc { + return func(action Action) (bool, runtime.Object, error) { + kind, err := mapper.KindFor(unversioned.GroupVersionResource{Resource: action.GetResource()}) + if err != nil { + return false, nil, fmt.Errorf("unrecognized action %s: %v", action.GetResource(), err) + } + + // TODO: have mapper return a Kind for a subresource? + switch castAction := action.(type) { + case ListAction: + kind.Kind += "List" + resource, err := o.Kind(kind, "") + return true, resource, err + + case GetAction: + resource, err := o.Kind(kind, castAction.GetName()) + return true, resource, err + + case DeleteAction: + resource, err := o.Kind(kind, castAction.GetName()) + return true, resource, err + + case CreateAction: + accessor, err := meta.Accessor(castAction.GetObject()) + if err != nil { + return true, nil, err + } + resource, err := o.Kind(kind, accessor.GetName()) + return true, resource, err + + case UpdateAction: + accessor, err := meta.Accessor(castAction.GetObject()) + if err != nil { + return true, nil, err + } + resource, err := o.Kind(kind, accessor.GetName()) + return true, resource, err + + default: + return false, nil, fmt.Errorf("no reaction implemented for %s", action) + } + } +} + +// AddObjectsFromPath loads the JSON or YAML file containing Kubernetes API resources +// and adds them to the provided ObjectRetriever. +func AddObjectsFromPath(path string, o ObjectRetriever, decoder runtime.Decoder) error { + data, err := ioutil.ReadFile(path) + if err != nil { + return err + } + data, err = yaml.ToJSON(data) + if err != nil { + return err + } + obj, err := runtime.Decode(decoder, data) + if err != nil { + return err + } + if err := o.Add(obj); err != nil { + return err + } + return nil +} + +type objects struct { + types map[string][]runtime.Object + last map[string]int + scheme ObjectScheme + decoder runtime.Decoder +} + +var _ ObjectRetriever = &objects{} + +// NewObjects implements the ObjectRetriever interface by introspecting the +// objects provided to Add() and returning them when the Kind method is invoked. +// If an api.List object is provided to Add(), each child item is added. If an +// object is added that is itself a list (PodList, ServiceList) then that is added +// to the "PodList" kind. If no PodList is added, the retriever will take any loaded +// Pods and return them in a list. If an api.Status is added, and the Details.Kind field +// is set, that status will be returned instead (as an error if Status != Success, or +// as a runtime.Object if Status == Success). If multiple PodLists are provided, they +// will be returned in order by the Kind call, and the last PodList will be reused for +// subsequent calls. +func NewObjects(scheme ObjectScheme, decoder runtime.Decoder) ObjectRetriever { + return objects{ + types: make(map[string][]runtime.Object), + last: make(map[string]int), + scheme: scheme, + decoder: decoder, + } +} + +func (o objects) Kind(kind unversioned.GroupVersionKind, name string) (runtime.Object, error) { + kind.Version = runtime.APIVersionInternal + empty, err := o.scheme.New(kind) + nilValue := reflect.Zero(reflect.TypeOf(empty)).Interface().(runtime.Object) + + arr, ok := o.types[kind.Kind] + if !ok { + if strings.HasSuffix(kind.Kind, "List") { + itemKind := kind.Kind[:len(kind.Kind)-4] + arr, ok := o.types[itemKind] + if !ok { + return empty, nil + } + out, err := o.scheme.New(kind) + if err != nil { + return nilValue, err + } + if err := meta.SetList(out, arr); err != nil { + return nilValue, err + } + if out, err = o.scheme.Copy(out); err != nil { + return nilValue, err + } + return out, nil + } + return nilValue, errors.NewNotFound(unversioned.GroupResource{Group: kind.Group, Resource: kind.Kind}, name) + } + + index := o.last[kind.Kind] + if index >= len(arr) { + index = len(arr) - 1 + } + if index < 0 { + return nilValue, errors.NewNotFound(unversioned.GroupResource{Group: kind.Group, Resource: kind.Kind}, name) + } + out, err := o.scheme.Copy(arr[index]) + if err != nil { + return nilValue, err + } + o.last[kind.Kind] = index + 1 + + if status, ok := out.(*unversioned.Status); ok { + if status.Details != nil { + status.Details.Kind = kind.Kind + } + if status.Status != unversioned.StatusSuccess { + return nilValue, &errors.StatusError{ErrStatus: *status} + } + } + + return out, nil +} + +func (o objects) Add(obj runtime.Object) error { + gvk, err := o.scheme.ObjectKind(obj) + if err != nil { + return err + } + kind := gvk.Kind + + switch { + case meta.IsListType(obj): + if kind != "List" { + o.types[kind] = append(o.types[kind], obj) + } + + list, err := meta.ExtractList(obj) + if err != nil { + return err + } + if errs := runtime.DecodeList(list, o.decoder); len(errs) > 0 { + return errs[0] + } + for _, obj := range list { + if err := o.Add(obj); err != nil { + return err + } + } + default: + if status, ok := obj.(*unversioned.Status); ok && status.Details != nil { + kind = status.Details.Kind + } + o.types[kind] = append(o.types[kind], obj) + } + + return nil +} + +func DefaultWatchReactor(watchInterface watch.Interface, err error) WatchReactionFunc { + return func(action Action) (bool, watch.Interface, error) { + return true, watchInterface, err + } +} + +// SimpleReactor is a Reactor. Each reaction function is attached to a given verb,resource tuple. "*" in either field matches everything for that value. +// For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions +type SimpleReactor struct { + Verb string + Resource string + + Reaction ReactionFunc +} + +func (r *SimpleReactor) Handles(action Action) bool { + verbCovers := r.Verb == "*" || r.Verb == action.GetVerb() + if !verbCovers { + return false + } + resourceCovers := r.Resource == "*" || r.Resource == action.GetResource() + if !resourceCovers { + return false + } + + return true +} + +func (r *SimpleReactor) React(action Action) (bool, runtime.Object, error) { + return r.Reaction(action) +} + +// SimpleWatchReactor is a WatchReactor. Each reaction function is attached to a given resource. "*" matches everything for that value. +// For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions +type SimpleWatchReactor struct { + Resource string + + Reaction WatchReactionFunc +} + +func (r *SimpleWatchReactor) Handles(action Action) bool { + resourceCovers := r.Resource == "*" || r.Resource == action.GetResource() + if !resourceCovers { + return false + } + + return true +} + +func (r *SimpleWatchReactor) React(action Action) (bool, watch.Interface, error) { + return r.Reaction(action) +} + +// SimpleProxyReactor is a ProxyReactor. Each reaction function is attached to a given resource. "*" matches everything for that value. +// For instance, *,pods matches all verbs on pods. This allows for easier composition of reaction functions. +type SimpleProxyReactor struct { + Resource string + + Reaction ProxyReactionFunc +} + +func (r *SimpleProxyReactor) Handles(action Action) bool { + resourceCovers := r.Resource == "*" || r.Resource == action.GetResource() + if !resourceCovers { + return false + } + + return true +} + +func (r *SimpleProxyReactor) React(action Action) (bool, restclient.ResponseWrapper, error) { + return r.Reaction(action) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/transport/cache.go b/vendor/k8s.io/kubernetes/pkg/client/transport/cache.go index f1068930d..90bd11902 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/transport/cache.go +++ b/vendor/k8s.io/kubernetes/pkg/client/transport/cache.go @@ -22,6 +22,8 @@ import ( "net/http" "sync" "time" + + utilnet "k8s.io/kubernetes/pkg/util/net" ) // TlsTransportCache caches TLS http.RoundTrippers different configurations. The @@ -60,7 +62,7 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) { } // Cache a single transport for these options - c.transports[key] = &http.Transport{ + c.transports[key] = utilnet.SetTransportDefaults(&http.Transport{ Proxy: http.ProxyFromEnvironment, TLSHandshakeTimeout: 10 * time.Second, TLSClientConfig: tlsConfig, @@ -68,7 +70,7 @@ func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) { Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).Dial, - } + }) return c.transports[key], nil } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/client_test.go b/vendor/k8s.io/kubernetes/pkg/client/typed/discovery/client_test.go similarity index 87% rename from vendor/k8s.io/kubernetes/pkg/client/unversioned/client_test.go rename to vendor/k8s.io/kubernetes/pkg/client/typed/discovery/client_test.go index 487591c5d..62d086920 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/client_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/discovery/client_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package unversioned +package discovery import ( "encoding/json" @@ -27,6 +27,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/version" ) @@ -48,9 +49,9 @@ func TestGetServerVersion(t *testing.T) { })) // TODO: Uncomment when fix #19254 // defer server.Close() - client := NewOrDie(&Config{Host: server.URL}) + client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL}) - got, err := client.Discovery().ServerVersion() + got, err := client.ServerVersion() if err != nil { t.Fatalf("unexpected encoding error: %v", err) } @@ -84,13 +85,13 @@ func TestGetServerGroupsWithV1Server(t *testing.T) { })) // TODO: Uncomment when fix #19254 // defer server.Close() - client := NewOrDie(&Config{Host: server.URL}) + client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL}) // ServerGroups should not return an error even if server returns error at /api and /apis - apiGroupList, err := client.Discovery().ServerGroups() + apiGroupList, err := client.ServerGroups() if err != nil { t.Fatalf("unexpected error: %v", err) } - groupVersions := ExtractGroupVersions(apiGroupList) + groupVersions := unversioned.ExtractGroupVersions(apiGroupList) if !reflect.DeepEqual(groupVersions, []string{"v1"}) { t.Errorf("expected: %q, got: %q", []string{"v1"}, groupVersions) } @@ -121,9 +122,9 @@ func TestGetServerResourcesWithV1Server(t *testing.T) { })) // TODO: Uncomment when fix #19254 // defer server.Close() - client := NewOrDie(&Config{Host: server.URL}) + client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL}) // ServerResources should not return an error even if server returns error at /api/v1. - resourceMap, err := client.Discovery().ServerResources() + resourceMap, err := client.ServerResources() if err != nil { t.Errorf("unexpected error: %v", err) } @@ -214,9 +215,9 @@ func TestGetServerResources(t *testing.T) { })) // TODO: Uncomment when fix #19254 // defer server.Close() - client := NewOrDie(&Config{Host: server.URL}) + client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL}) for _, test := range tests { - got, err := client.Discovery().ServerResourcesForGroupVersion(test.request) + got, err := client.ServerResourcesForGroupVersion(test.request) if test.expectErr { if err == nil { t.Error("unexpected non-error") @@ -232,7 +233,7 @@ func TestGetServerResources(t *testing.T) { } } - resourceMap, err := client.Discovery().ServerResources() + resourceMap, err := client.ServerResources() if err != nil { t.Errorf("unexpected error: %v", err) } @@ -277,8 +278,8 @@ func TestGetSwaggerSchema(t *testing.T) { // TODO: Uncomment when fix #19254 // defer server.Close() - client := NewOrDie(&Config{Host: server.URL}) - got, err := client.Discovery().SwaggerSchema(v1.SchemeGroupVersion) + client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL}) + got, err := client.SwaggerSchema(v1.SchemeGroupVersion) if err != nil { t.Fatalf("unexpected encoding error: %v", err) } @@ -297,8 +298,8 @@ func TestGetSwaggerSchemaFail(t *testing.T) { // TODO: Uncomment when fix #19254 // defer server.Close() - client := NewOrDie(&Config{Host: server.URL}) - got, err := client.Discovery().SwaggerSchema(unversioned.GroupVersion{Group: "api.group", Version: "v4"}) + client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL}) + got, err := client.SwaggerSchema(unversioned.GroupVersion{Group: "api.group", Version: "v4"}) if got != nil { t.Fatalf("unexpected response: %v", got) } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/discovery_client.go b/vendor/k8s.io/kubernetes/pkg/client/typed/discovery/discovery_client.go similarity index 89% rename from vendor/k8s.io/kubernetes/pkg/client/unversioned/discovery_client.go rename to vendor/k8s.io/kubernetes/pkg/client/typed/discovery/discovery_client.go index 890f37684..fa618f92f 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/discovery_client.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/discovery/discovery_client.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package unversioned +package discovery import ( "encoding/json" @@ -27,6 +27,7 @@ import ( "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/version" ) @@ -67,10 +68,10 @@ type SwaggerSchemaInterface interface { SwaggerSchema(version unversioned.GroupVersion) (*swagger.ApiDeclaration, error) } -// DiscoveryClient implements the functions that dicovery server-supported API groups, +// DiscoveryClient implements the functions that discover server-supported API groups, // versions and resources. type DiscoveryClient struct { - *RESTClient + *restclient.RESTClient } // Convert unversioned.APIVersions to unversioned.APIGroup. APIVersions is used by legacy v1, so @@ -147,7 +148,7 @@ func (d *DiscoveryClient) ServerResources() (map[string]*unversioned.APIResource if err != nil { return nil, err } - groupVersions := ExtractGroupVersions(apiGroups) + groupVersions := unversioned.ExtractGroupVersions(apiGroups) result := map[string]*unversioned.APIResourceList{} for _, groupVersion := range groupVersions { resources, err := d.ServerResourcesForGroupVersion(groupVersion) @@ -183,7 +184,7 @@ func (d *DiscoveryClient) SwaggerSchema(version unversioned.GroupVersion) (*swag if err != nil { return nil, err } - groupVersions := ExtractGroupVersions(groupList) + groupVersions := unversioned.ExtractGroupVersions(groupList) // This check also takes care the case that kubectl is newer than the running endpoint if stringDoesntExistIn(version.String(), groupVersions) { return nil, fmt.Errorf("API version: %v is not supported by the server. Use one of: %v", version, groupVersions) @@ -207,27 +208,30 @@ func (d *DiscoveryClient) SwaggerSchema(version unversioned.GroupVersion) (*swag return &schema, nil } -func setDiscoveryDefaults(config *Config) error { +func setDiscoveryDefaults(config *restclient.Config) error { config.APIPath = "" config.GroupVersion = nil - config.Codec = runtime.NoopEncoder{api.Codecs.UniversalDecoder()} + config.Codec = runtime.NoopEncoder{Decoder: api.Codecs.UniversalDecoder()} + if len(config.UserAgent) == 0 { + config.UserAgent = restclient.DefaultKubernetesUserAgent() + } return nil } // NewDiscoveryClientForConfig creates a new DiscoveryClient for the given config. This client // can be used to discover supported resources in the API server. -func NewDiscoveryClientForConfig(c *Config) (*DiscoveryClient, error) { +func NewDiscoveryClientForConfig(c *restclient.Config) (*DiscoveryClient, error) { config := *c if err := setDiscoveryDefaults(&config); err != nil { return nil, err } - client, err := UnversionedRESTClientFor(&config) + client, err := restclient.UnversionedRESTClientFor(&config) return &DiscoveryClient{client}, err } // NewDiscoveryClientForConfig creates a new DiscoveryClient for the given config. If // there is an error, it panics. -func NewDiscoveryClientForConfigOrDie(c *Config) *DiscoveryClient { +func NewDiscoveryClientForConfigOrDie(c *restclient.Config) *DiscoveryClient { client, err := NewDiscoveryClientForConfig(c) if err != nil { panic(err) @@ -237,6 +241,15 @@ func NewDiscoveryClientForConfigOrDie(c *Config) *DiscoveryClient { } // New creates a new DiscoveryClient for the given RESTClient. -func NewDiscoveryClient(c *RESTClient) *DiscoveryClient { +func NewDiscoveryClient(c *restclient.RESTClient) *DiscoveryClient { return &DiscoveryClient{c} } + +func stringDoesntExistIn(str string, slice []string) bool { + for _, s := range slice { + if s == str { + return false + } + } + return true +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/discovery/fake/discovery.go b/vendor/k8s.io/kubernetes/pkg/client/typed/discovery/fake/discovery.go new file mode 100644 index 000000000..76e672fd3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/discovery/fake/discovery.go @@ -0,0 +1,74 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "github.com/emicklei/go-restful/swagger" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/version" +) + +type FakeDiscovery struct { + *core.Fake +} + +func (c *FakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*unversioned.APIResourceList, error) { + action := core.ActionImpl{ + Verb: "get", + Resource: "resource", + } + c.Invokes(action, nil) + return c.Resources[groupVersion], nil +} + +func (c *FakeDiscovery) ServerResources() (map[string]*unversioned.APIResourceList, error) { + action := core.ActionImpl{ + Verb: "get", + Resource: "resource", + } + c.Invokes(action, nil) + return c.Resources, nil +} + +func (c *FakeDiscovery) ServerGroups() (*unversioned.APIGroupList, error) { + return nil, nil +} + +func (c *FakeDiscovery) ServerVersion() (*version.Info, error) { + action := core.ActionImpl{} + action.Verb = "get" + action.Resource = "version" + + c.Invokes(action, nil) + versionInfo := version.Get() + return &versionInfo, nil +} + +func (c *FakeDiscovery) SwaggerSchema(version unversioned.GroupVersion) (*swagger.ApiDeclaration, error) { + action := core.ActionImpl{} + action.Verb = "get" + if version == v1.SchemeGroupVersion { + action.Resource = "/swaggerapi/api/" + version.Version + } else { + action.Resource = "/swaggerapi/apis/" + version.Group + "/" + version.Version + } + + c.Invokes(action, nil) + return &swagger.ApiDeclaration{}, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/dynamic/client.go b/vendor/k8s.io/kubernetes/pkg/client/typed/dynamic/client.go new file mode 100644 index 000000000..b46c79b68 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/dynamic/client.go @@ -0,0 +1,217 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 dynamic provides a client interface to arbitrary Kubernetes +// APIs that exposes common high level operations and exposes common +// metadata. +package dynamic + +import ( + "encoding/json" + "errors" + "io" + "net/url" + "strings" + + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/conversion/queryparams" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/watch" +) + +// Client is a Kubernetes client that allows you to access metadata +// and manipulate metadata of a Kubernetes API group. +type Client struct { + cl *restclient.RESTClient +} + +// NewClient returns a new client based on the passed in config. The +// codec is ignored, as the dynamic client uses it's own codec. +func NewClient(conf *restclient.Config) (*Client, error) { + // avoid changing the original config + confCopy := *conf + conf = &confCopy + + conf.Codec = dynamicCodec{} + + if conf.APIPath == "" { + conf.APIPath = "/api" + } + + if len(conf.UserAgent) == 0 { + conf.UserAgent = restclient.DefaultKubernetesUserAgent() + } + + if conf.QPS == 0.0 { + conf.QPS = 5.0 + } + if conf.Burst == 0 { + conf.Burst = 10 + } + + cl, err := restclient.RESTClientFor(conf) + if err != nil { + return nil, err + } + + return &Client{cl: cl}, nil +} + +// Resource returns an API interface to the specified resource for +// this client's group and version. If resource is not a namespaced +// resource, then namespace is ignored. +func (c *Client) Resource(resource *unversioned.APIResource, namespace string) *ResourceClient { + return &ResourceClient{ + cl: c.cl, + resource: resource, + ns: namespace, + } +} + +// ResourceClient is an API interface to a specific resource under a +// dynamic client. +type ResourceClient struct { + cl *restclient.RESTClient + resource *unversioned.APIResource + ns string +} + +// namespace applies a namespace to the request if the configured +// resource is a namespaced resource. Otherwise, it just returns the +// passed in request. +func (rc *ResourceClient) namespace(req *restclient.Request) *restclient.Request { + if rc.resource.Namespaced { + return req.Namespace(rc.ns) + } + return req +} + +// List returns a list of objects for this resource. +func (rc *ResourceClient) List(opts v1.ListOptions) (*runtime.UnstructuredList, error) { + result := new(runtime.UnstructuredList) + err := rc.namespace(rc.cl.Get()). + Resource(rc.resource.Name). + VersionedParams(&opts, parameterEncoder). + Do(). + Into(result) + return result, err +} + +// Get gets the resource with the specified name. +func (rc *ResourceClient) Get(name string) (*runtime.Unstructured, error) { + result := new(runtime.Unstructured) + err := rc.namespace(rc.cl.Get()). + Resource(rc.resource.Name). + Name(name). + Do(). + Into(result) + return result, err +} + +// Delete deletes the resource with the specified name. +func (rc *ResourceClient) Delete(name string, opts *v1.DeleteOptions) error { + return rc.namespace(rc.cl.Delete()). + Resource(rc.resource.Name). + Name(name). + Body(opts). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (rc *ResourceClient) DeleteCollection(deleteOptions *v1.DeleteOptions, listOptions v1.ListOptions) error { + return rc.namespace(rc.cl.Delete()). + Resource(rc.resource.Name). + VersionedParams(&listOptions, parameterEncoder). + Body(deleteOptions). + Do(). + Error() +} + +// Create creates the provided resource. +func (rc *ResourceClient) Create(obj *runtime.Unstructured) (*runtime.Unstructured, error) { + result := new(runtime.Unstructured) + err := rc.namespace(rc.cl.Post()). + Resource(rc.resource.Name). + Body(obj). + Do(). + Into(result) + return result, err +} + +// Update updates the provided resource. +func (rc *ResourceClient) Update(obj *runtime.Unstructured) (*runtime.Unstructured, error) { + result := new(runtime.Unstructured) + if len(obj.Name) == 0 { + return result, errors.New("object missing name") + } + err := rc.namespace(rc.cl.Put()). + Resource(rc.resource.Name). + Name(obj.Name). + Body(obj). + Do(). + Into(result) + return result, err +} + +// Watch returns a watch.Interface that watches the resource. +func (rc *ResourceClient) Watch(opts v1.ListOptions) (watch.Interface, error) { + return rc.namespace(rc.cl.Get().Prefix("watch")). + Resource(rc.resource.Name). + VersionedParams(&opts, parameterEncoder). + Watch() +} + +// dynamicCodec is a codec that wraps the standard unstructured codec +// with special handling for Status objects. +type dynamicCodec struct{} + +func (dynamicCodec) Decode(data []byte, gvk *unversioned.GroupVersionKind, obj runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) { + obj, gvk, err := runtime.UnstructuredJSONScheme.Decode(data, gvk, obj) + if err != nil { + return nil, nil, err + } + + if _, ok := obj.(*unversioned.Status); !ok && strings.ToLower(gvk.Kind) == "status" { + obj = &unversioned.Status{} + err := json.Unmarshal(data, obj) + if err != nil { + return nil, nil, err + } + } + + return obj, gvk, nil +} + +func (dynamicCodec) EncodeToStream(obj runtime.Object, w io.Writer, overrides ...unversioned.GroupVersion) error { + return runtime.UnstructuredJSONScheme.EncodeToStream(obj, w, overrides...) +} + +// paramaterCodec is a codec converts an API object to query +// parameters without trying to convert to the target version. +type parameterCodec struct{} + +func (parameterCodec) EncodeParameters(obj runtime.Object, to unversioned.GroupVersion) (url.Values, error) { + return queryparams.Convert(obj) +} + +func (parameterCodec) DecodeParameters(parameters url.Values, from unversioned.GroupVersion, into runtime.Object) error { + return errors.New("DecodeParameters not implemented on dynamic parameterCodec") +} + +var parameterEncoder runtime.ParameterCodec = parameterCodec{} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/dynamic/client_pool.go b/vendor/k8s.io/kubernetes/pkg/client/typed/dynamic/client_pool.go new file mode 100644 index 000000000..4bdd3ecab --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/dynamic/client_pool.go @@ -0,0 +1,85 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 dynamic + +import ( + "sync" + + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" +) + +// ClientPool manages a pool of dynamic clients. +type ClientPool interface { + // ClientForGroupVersion returns a client configured for the specified groupVersion. + ClientForGroupVersion(groupVersion unversioned.GroupVersion) (*Client, error) +} + +// APIPathResolverFunc knows how to convert a groupVersion to its API path. +type APIPathResolverFunc func(groupVersion unversioned.GroupVersion) string + +// LegacyAPIPathResolverFunc can resolve paths properly with the legacy API. +func LegacyAPIPathResolverFunc(groupVersion unversioned.GroupVersion) string { + if len(groupVersion.Group) == 0 { + return "/api" + } + return "/apis" +} + +// clientPoolImpl implements Factory +type clientPoolImpl struct { + lock sync.RWMutex + config *restclient.Config + clients map[unversioned.GroupVersion]*Client + apiPathResolverFunc APIPathResolverFunc +} + +// NewClientPool returns a ClientPool from the specified config +func NewClientPool(config *restclient.Config, apiPathResolverFunc APIPathResolverFunc) ClientPool { + return &clientPoolImpl{ + config: config, + clients: map[unversioned.GroupVersion]*Client{}, + apiPathResolverFunc: apiPathResolverFunc, + } +} + +// ClientForGroupVersion returns a client for the specified groupVersion, creates one if none exists +func (c *clientPoolImpl) ClientForGroupVersion(groupVersion unversioned.GroupVersion) (*Client, error) { + c.lock.Lock() + defer c.lock.Unlock() + + // do we have a client already configured? + if existingClient, found := c.clients[groupVersion]; found { + return existingClient, nil + } + + // avoid changing the original config + confCopy := *c.config + conf := &confCopy + + // we need to set the api path based on group version, if no group, default to legacy path + conf.APIPath = c.apiPathResolverFunc(groupVersion) + + // we need to make a client + conf.GroupVersion = &groupVersion + dynamicClient, err := NewClient(conf) + if err != nil { + return nil, err + } + c.clients[groupVersion] = dynamicClient + return dynamicClient, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/dynamic/client_test.go b/vendor/k8s.io/kubernetes/pkg/client/typed/dynamic/client_test.go new file mode 100644 index 000000000..1c4965ec4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/dynamic/client_test.go @@ -0,0 +1,481 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 dynamic + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/watch" + watchjson "k8s.io/kubernetes/pkg/watch/json" +) + +func getJSON(version, kind, name string) []byte { + return []byte(fmt.Sprintf(`{"apiVersion": %q, "kind": %q, "metadata": {"name": %q}}`, version, kind, name)) +} + +func getListJSON(version, kind string, items ...[]byte) []byte { + json := fmt.Sprintf(`{"apiVersion": %q, "kind": %q, "items": [%s]}`, + version, kind, bytes.Join(items, []byte(","))) + return []byte(json) +} + +func getObject(version, kind, name string) *runtime.Unstructured { + return &runtime.Unstructured{ + TypeMeta: runtime.TypeMeta{ + APIVersion: version, + Kind: kind, + }, + Name: name, + Object: map[string]interface{}{ + "apiVersion": version, + "kind": kind, + "metadata": map[string]interface{}{ + "name": name, + }, + }, + } +} + +func getClientServer(gv *unversioned.GroupVersion, h func(http.ResponseWriter, *http.Request)) (*Client, *httptest.Server, error) { + srv := httptest.NewServer(http.HandlerFunc(h)) + cl, err := NewClient(&restclient.Config{ + Host: srv.URL, + ContentConfig: restclient.ContentConfig{GroupVersion: gv}, + }) + if err != nil { + srv.Close() + return nil, nil, err + } + return cl, srv, nil +} + +func TestList(t *testing.T) { + tcs := []struct { + name string + namespace string + path string + resp []byte + want *runtime.UnstructuredList + }{ + { + name: "normal_list", + path: "/api/gtest/vtest/rtest", + resp: getListJSON("vTest", "rTestList", + getJSON("vTest", "rTest", "item1"), + getJSON("vTest", "rTest", "item2")), + want: &runtime.UnstructuredList{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "vTest", + Kind: "rTestList", + }, + Items: []*runtime.Unstructured{ + getObject("vTest", "rTest", "item1"), + getObject("vTest", "rTest", "item2"), + }, + }, + }, + { + name: "namespaced_list", + namespace: "nstest", + path: "/api/gtest/vtest/namespaces/nstest/rtest", + resp: getListJSON("vTest", "rTestList", + getJSON("vTest", "rTest", "item1"), + getJSON("vTest", "rTest", "item2")), + want: &runtime.UnstructuredList{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "vTest", + Kind: "rTestList", + }, + Items: []*runtime.Unstructured{ + getObject("vTest", "rTest", "item1"), + getObject("vTest", "rTest", "item2"), + }, + }, + }, + } + for _, tc := range tcs { + gv := &unversioned.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &unversioned.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + t.Errorf("List(%q) got HTTP method %s. wanted GET", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("List(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + + w.Write(tc.resp) + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + got, err := cl.Resource(resource, tc.namespace).List(v1.ListOptions{}) + if err != nil { + t.Errorf("unexpected error when listing %q: %v", tc.name, err) + continue + } + + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("List(%q) want: %v\ngot: %v", tc.name, tc.want, got) + } + } +} + +func TestGet(t *testing.T) { + tcs := []struct { + namespace string + name string + path string + resp []byte + want *runtime.Unstructured + }{ + { + name: "normal_get", + path: "/api/gtest/vtest/rtest/normal_get", + resp: getJSON("vTest", "rTest", "normal_get"), + want: getObject("vTest", "rTest", "normal_get"), + }, + { + namespace: "nstest", + name: "namespaced_get", + path: "/api/gtest/vtest/namespaces/nstest/rtest/namespaced_get", + resp: getJSON("vTest", "rTest", "namespaced_get"), + want: getObject("vTest", "rTest", "namespaced_get"), + }, + } + for _, tc := range tcs { + gv := &unversioned.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &unversioned.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + t.Errorf("Get(%q) got HTTP method %s. wanted GET", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("Get(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + + w.Write(tc.resp) + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + got, err := cl.Resource(resource, tc.namespace).Get(tc.name) + if err != nil { + t.Errorf("unexpected error when getting %q: %v", tc.name, err) + continue + } + + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("Get(%q) want: %v\ngot: %v", tc.name, tc.want, got) + } + } +} + +func TestDelete(t *testing.T) { + statusOK := &unversioned.Status{ + TypeMeta: unversioned.TypeMeta{Kind: "Status"}, + Status: unversioned.StatusSuccess, + } + tcs := []struct { + namespace string + name string + path string + }{ + { + name: "normal_delete", + path: "/api/gtest/vtest/rtest/normal_delete", + }, + { + namespace: "nstest", + name: "namespaced_delete", + path: "/api/gtest/vtest/namespaces/nstest/rtest/namespaced_delete", + }, + } + for _, tc := range tcs { + gv := &unversioned.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &unversioned.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "DELETE" { + t.Errorf("Delete(%q) got HTTP method %s. wanted DELETE", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("Delete(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + + runtime.UnstructuredJSONScheme.EncodeToStream(statusOK, w) + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + err = cl.Resource(resource, tc.namespace).Delete(tc.name, nil) + if err != nil { + t.Errorf("unexpected error when deleting %q: %v", tc.name, err) + continue + } + } +} + +func TestDeleteCollection(t *testing.T) { + statusOK := &unversioned.Status{ + TypeMeta: unversioned.TypeMeta{Kind: "Status"}, + Status: unversioned.StatusSuccess, + } + tcs := []struct { + namespace string + name string + path string + }{ + { + name: "normal_delete_collection", + path: "/api/gtest/vtest/rtest", + }, + { + namespace: "nstest", + name: "namespaced_delete_collection", + path: "/api/gtest/vtest/namespaces/nstest/rtest", + }, + } + for _, tc := range tcs { + gv := &unversioned.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &unversioned.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "DELETE" { + t.Errorf("DeleteCollection(%q) got HTTP method %s. wanted DELETE", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("DeleteCollection(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + + runtime.UnstructuredJSONScheme.EncodeToStream(statusOK, w) + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + err = cl.Resource(resource, tc.namespace).DeleteCollection(nil, v1.ListOptions{}) + if err != nil { + t.Errorf("unexpected error when deleting collection %q: %v", tc.name, err) + continue + } + } +} + +func TestCreate(t *testing.T) { + tcs := []struct { + name string + namespace string + obj *runtime.Unstructured + path string + }{ + { + name: "normal_create", + path: "/api/gtest/vtest/rtest", + obj: getObject("vTest", "rTest", "normal_create"), + }, + { + name: "namespaced_create", + namespace: "nstest", + path: "/api/gtest/vtest/namespaces/nstest/rtest", + obj: getObject("vTest", "rTest", "namespaced_create"), + }, + } + for _, tc := range tcs { + gv := &unversioned.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &unversioned.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + t.Errorf("Create(%q) got HTTP method %s. wanted POST", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("Create(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + + data, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Create(%q) unexpected error reading body: %v", tc.name, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Write(data) + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + got, err := cl.Resource(resource, tc.namespace).Create(tc.obj) + if err != nil { + t.Errorf("unexpected error when creating %q: %v", tc.name, err) + continue + } + + if !reflect.DeepEqual(got, tc.obj) { + t.Errorf("Create(%q) want: %v\ngot: %v", tc.name, tc.obj, got) + } + } +} + +func TestUpdate(t *testing.T) { + tcs := []struct { + name string + namespace string + obj *runtime.Unstructured + path string + }{ + { + name: "normal_update", + path: "/api/gtest/vtest/rtest/normal_update", + obj: getObject("vTest", "rTest", "normal_update"), + }, + { + name: "namespaced_update", + namespace: "nstest", + path: "/api/gtest/vtest/namespaces/nstest/rtest/namespaced_update", + obj: getObject("vTest", "rTest", "namespaced_update"), + }, + } + for _, tc := range tcs { + gv := &unversioned.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &unversioned.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "PUT" { + t.Errorf("Update(%q) got HTTP method %s. wanted PUT", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("Update(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + + data, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("Update(%q) unexpected error reading body: %v", tc.name, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Write(data) + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + got, err := cl.Resource(resource, tc.namespace).Update(tc.obj) + if err != nil { + t.Errorf("unexpected error when updating %q: %v", tc.name, err) + continue + } + + if !reflect.DeepEqual(got, tc.obj) { + t.Errorf("Update(%q) want: %v\ngot: %v", tc.name, tc.obj, got) + } + } +} + +func TestWatch(t *testing.T) { + tcs := []struct { + name string + namespace string + events []watch.Event + path string + }{ + { + name: "normal_watch", + path: "/api/gtest/vtest/watch/rtest", + events: []watch.Event{ + {Type: watch.Added, Object: getObject("vTest", "rTest", "normal_watch")}, + {Type: watch.Modified, Object: getObject("vTest", "rTest", "normal_watch")}, + {Type: watch.Deleted, Object: getObject("vTest", "rTest", "normal_watch")}, + }, + }, + { + name: "namespaced_watch", + namespace: "nstest", + path: "/api/gtest/vtest/watch/namespaces/nstest/rtest", + events: []watch.Event{ + {Type: watch.Added, Object: getObject("vTest", "rTest", "namespaced_watch")}, + {Type: watch.Modified, Object: getObject("vTest", "rTest", "namespaced_watch")}, + {Type: watch.Deleted, Object: getObject("vTest", "rTest", "namespaced_watch")}, + }, + }, + } + for _, tc := range tcs { + gv := &unversioned.GroupVersion{Group: "gtest", Version: "vtest"} + resource := &unversioned.APIResource{Name: "rtest", Namespaced: len(tc.namespace) != 0} + cl, srv, err := getClientServer(gv, func(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + t.Errorf("Watch(%q) got HTTP method %s. wanted GET", tc.name, r.Method) + } + + if r.URL.Path != tc.path { + t.Errorf("Watch(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) + } + + enc := watchjson.NewEncoder(w, dynamicCodec{}) + for _, e := range tc.events { + enc.Encode(&e) + } + }) + if err != nil { + t.Errorf("unexpected error when creating client: %v", err) + continue + } + defer srv.Close() + + watcher, err := cl.Resource(resource, tc.namespace).Watch(v1.ListOptions{}) + if err != nil { + t.Errorf("unexpected error when watching %q: %v", tc.name, err) + continue + } + + for _, want := range tc.events { + got := <-watcher.ResultChan() + if !reflect.DeepEqual(got, want) { + t.Errorf("Watch(%q) want: %v\ngot: %v", tc.name, want, got) + } + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/componentstatus.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/componentstatus.go index 0ef0667da..a7cb903d4 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/componentstatus.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/componentstatus.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/configmap.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/configmap.go index b43e53d6c..a92a83fb5 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/configmap.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/configmap.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/core_client.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/core_client.go index c8119031e..24bb50ad3 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/core_client.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/core_client.go @@ -14,12 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( api "k8s.io/kubernetes/pkg/api" registered "k8s.io/kubernetes/pkg/apimachinery/registered" - unversioned "k8s.io/kubernetes/pkg/client/unversioned" + restclient "k8s.io/kubernetes/pkg/client/restclient" ) type CoreInterface interface { @@ -43,7 +45,7 @@ type CoreInterface interface { // CoreClient is used to interact with features provided by the Core group. type CoreClient struct { - *unversioned.RESTClient + *restclient.RESTClient } func (c *CoreClient) ComponentStatuses() ComponentStatusInterface { @@ -111,12 +113,12 @@ func (c *CoreClient) ServiceAccounts(namespace string) ServiceAccountInterface { } // NewForConfig creates a new CoreClient for the given config. -func NewForConfig(c *unversioned.Config) (*CoreClient, error) { +func NewForConfig(c *restclient.Config) (*CoreClient, error) { config := *c if err := setConfigDefaults(&config); err != nil { return nil, err } - client, err := unversioned.RESTClientFor(&config) + client, err := restclient.RESTClientFor(&config) if err != nil { return nil, err } @@ -125,7 +127,7 @@ func NewForConfig(c *unversioned.Config) (*CoreClient, error) { // NewForConfigOrDie creates a new CoreClient for the given config and // panics if there is an error in the config. -func NewForConfigOrDie(c *unversioned.Config) *CoreClient { +func NewForConfigOrDie(c *restclient.Config) *CoreClient { client, err := NewForConfig(c) if err != nil { panic(err) @@ -134,11 +136,11 @@ func NewForConfigOrDie(c *unversioned.Config) *CoreClient { } // New creates a new CoreClient for the given RESTClient. -func New(c *unversioned.RESTClient) *CoreClient { +func New(c *restclient.RESTClient) *CoreClient { return &CoreClient{c} } -func setConfigDefaults(config *unversioned.Config) error { +func setConfigDefaults(config *restclient.Config) error { // if core group is not registered, return an error g, err := registered.Group("") if err != nil { @@ -146,7 +148,7 @@ func setConfigDefaults(config *unversioned.Config) error { } config.APIPath = "/api" if config.UserAgent == "" { - config.UserAgent = unversioned.DefaultKubernetesUserAgent() + config.UserAgent = restclient.DefaultKubernetesUserAgent() } // TODO: Unconditionally set the config.Version, until we fix the config. //if config.Version == "" { diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/doc.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/doc.go index 930017040..3c8dbaac6 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/doc.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/doc.go @@ -14,5 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + // Package unversioned has the automatically generated clients for unversioned resources. package unversioned diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/endpoints.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/endpoints.go index 78e2a0878..2906afcac 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/endpoints.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/endpoints.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/event.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/event.go index 5627690a6..76dac8160 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/event.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/event.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/doc.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/doc.go index dd6d4da71..ea86647e2 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/doc.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/doc.go @@ -14,5 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + // Package fake has the automatically generated clients. package fake diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_componentstatus.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_componentstatus.go index 478dd9dbf..3166f3f16 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_componentstatus.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_componentstatus.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_configmap.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_configmap.go index 34fa0b229..5e0618869 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_configmap.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_configmap.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_core_client.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_core_client.go index afc6cf6f9..79dcfa930 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_core_client.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_core_client.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_endpoints.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_endpoints.go index cc25b6e06..1ef9ad727 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_endpoints.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_endpoints.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_event.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_event.go index a9f88153b..13f9ecc13 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_event.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_event.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_limitrange.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_limitrange.go index cab44ce4e..f57b279ff 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_limitrange.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_limitrange.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_namespace.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_namespace.go index 78933814f..d1ba1d56d 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_namespace.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_namespace.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_node.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_node.go index 8761c8772..631808aaf 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_node.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_node.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_persistentvolume.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_persistentvolume.go index d3d8c79f5..c4a05fb64 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_persistentvolume.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_persistentvolume.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_persistentvolumeclaim.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_persistentvolumeclaim.go index ba674f269..e00d2ce03 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_persistentvolumeclaim.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_persistentvolumeclaim.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_pod.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_pod.go index 6488c021d..096602f37 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_pod.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_pod.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_pod_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_pod_expansion.go index 034ecf2b2..53fe93221 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_pod_expansion.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_pod_expansion.go @@ -18,8 +18,8 @@ package fake import ( "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/testing/core" - client "k8s.io/kubernetes/pkg/client/unversioned" ) func (c *FakePods) Bind(binding *api.Binding) error { @@ -33,7 +33,7 @@ func (c *FakePods) Bind(binding *api.Binding) error { return err } -func (c *FakePods) GetLogs(name string, opts *api.PodLogOptions) *client.Request { +func (c *FakePods) GetLogs(name string, opts *api.PodLogOptions) *restclient.Request { action := core.GenericActionImpl{} action.Verb = "get" action.Namespace = c.ns @@ -42,5 +42,5 @@ func (c *FakePods) GetLogs(name string, opts *api.PodLogOptions) *client.Request action.Value = opts _, _ = c.Fake.Invokes(action, &api.Pod{}) - return &client.Request{} + return &restclient.Request{} } diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_podtemplate.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_podtemplate.go index b900a113c..9dd400aa6 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_podtemplate.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_podtemplate.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_replicationcontroller.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_replicationcontroller.go index 205f09456..dd9ac1605 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_replicationcontroller.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_replicationcontroller.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_resourcequota.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_resourcequota.go index 056e61ed5..663a884b6 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_resourcequota.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_resourcequota.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_secret.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_secret.go index 2f09be6e5..bf0b323b7 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_secret.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_secret.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_service.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_service.go index 2cf38901c..a8ab2d8a4 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_service.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_service.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_service_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_service_expansion.go index 7d72d1dfe..18f1b7803 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_service_expansion.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_service_expansion.go @@ -17,10 +17,10 @@ limitations under the License. package fake import ( + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/testing/core" - client "k8s.io/kubernetes/pkg/client/unversioned" ) -func (c *FakeServices) ProxyGet(scheme, name, port, path string, params map[string]string) client.ResponseWrapper { +func (c *FakeServices) ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper { return c.Fake.InvokesProxy(core.NewProxyGetAction("services", c.ns, scheme, name, port, path, params)) } diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_serviceaccount.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_serviceaccount.go index 61d7a04f5..db5672eaf 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_serviceaccount.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/fake/fake_serviceaccount.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/limitrange.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/limitrange.go index 86cc9b07f..c1dde4109 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/limitrange.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/limitrange.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/namespace.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/namespace.go index c1c8b4506..f4f82889e 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/namespace.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/namespace.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/node.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/node.go index b0c53ef1d..3e63de62e 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/node.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/node.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/persistentvolume.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/persistentvolume.go index 6b4d0f017..4093dd88f 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/persistentvolume.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/persistentvolume.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/persistentvolumeclaim.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/persistentvolumeclaim.go index 2f5b17437..cc9098789 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/persistentvolumeclaim.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/persistentvolumeclaim.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/pod.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/pod.go index 1cdfc8e71..e84dad717 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/pod.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/pod.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/pod_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/pod_expansion.go index 0a46431d5..8ebd29d30 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/pod_expansion.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/pod_expansion.go @@ -18,13 +18,13 @@ package unversioned import ( "k8s.io/kubernetes/pkg/api" - unversioned "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" ) // The PodExpansion interface allows manually adding extra methods to the PodInterface. type PodExpansion interface { Bind(binding *api.Binding) error - GetLogs(name string, opts *api.PodLogOptions) *unversioned.Request + GetLogs(name string, opts *api.PodLogOptions) *restclient.Request } // Bind applies the provided binding to the named pod in the current namespace (binding.Namespace is ignored). @@ -33,6 +33,6 @@ func (c *pods) Bind(binding *api.Binding) error { } // Get constructs a request for getting the logs for a pod -func (c *pods) GetLogs(name string, opts *api.PodLogOptions) *unversioned.Request { +func (c *pods) GetLogs(name string, opts *api.PodLogOptions) *restclient.Request { return c.client.Get().Namespace(c.ns).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, api.ParameterCodec) } diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/podtemplate.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/podtemplate.go index cccef29f7..426f2ff78 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/podtemplate.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/podtemplate.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/replicationcontroller.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/replicationcontroller.go index 6f9f06625..1d4dd0f4c 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/replicationcontroller.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/replicationcontroller.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/resourcequota.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/resourcequota.go index 2d0da73fb..7ccba4faf 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/resourcequota.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/resourcequota.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/secret.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/secret.go index 101fbdb54..c57374cf4 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/secret.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/secret.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/service.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/service.go index 006f601c2..6156418c7 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/service.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/service.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/service_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/service_expansion.go index b9fb6af86..89266e6cd 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/service_expansion.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/service_expansion.go @@ -17,17 +17,17 @@ limitations under the License. package unversioned import ( - "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/util/net" ) // The ServiceExpansion interface allows manually adding extra methods to the ServiceInterface. type ServiceExpansion interface { - ProxyGet(scheme, name, port, path string, params map[string]string) unversioned.ResponseWrapper + ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper } // ProxyGet returns a response of the service by calling it through the proxy. -func (c *services) ProxyGet(scheme, name, port, path string, params map[string]string) unversioned.ResponseWrapper { +func (c *services) ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper { request := c.client.Get(). Prefix("proxy"). Namespace(c.ns). diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/serviceaccount.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/serviceaccount.go index 65f7df263..26841f17d 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/serviceaccount.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/unversioned/serviceaccount.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/componentstatus.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/componentstatus.go new file mode 100644 index 000000000..23363f530 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/componentstatus.go @@ -0,0 +1,127 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ComponentStatusesGetter has a method to return a ComponentStatusInterface. +// A group's client should implement this interface. +type ComponentStatusesGetter interface { + ComponentStatuses() ComponentStatusInterface +} + +// ComponentStatusInterface has methods to work with ComponentStatus resources. +type ComponentStatusInterface interface { + Create(*v1.ComponentStatus) (*v1.ComponentStatus, error) + Update(*v1.ComponentStatus) (*v1.ComponentStatus, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.ComponentStatus, error) + List(opts api.ListOptions) (*v1.ComponentStatusList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ComponentStatusExpansion +} + +// componentStatuses implements ComponentStatusInterface +type componentStatuses struct { + client *CoreClient +} + +// newComponentStatuses returns a ComponentStatuses +func newComponentStatuses(c *CoreClient) *componentStatuses { + return &componentStatuses{ + client: c, + } +} + +// Create takes the representation of a componentStatus and creates it. Returns the server's representation of the componentStatus, and an error, if there is any. +func (c *componentStatuses) Create(componentStatus *v1.ComponentStatus) (result *v1.ComponentStatus, err error) { + result = &v1.ComponentStatus{} + err = c.client.Post(). + Resource("componentstatuses"). + Body(componentStatus). + Do(). + Into(result) + return +} + +// Update takes the representation of a componentStatus and updates it. Returns the server's representation of the componentStatus, and an error, if there is any. +func (c *componentStatuses) Update(componentStatus *v1.ComponentStatus) (result *v1.ComponentStatus, err error) { + result = &v1.ComponentStatus{} + err = c.client.Put(). + Resource("componentstatuses"). + Name(componentStatus.Name). + Body(componentStatus). + Do(). + Into(result) + return +} + +// Delete takes name of the componentStatus and deletes it. Returns an error if one occurs. +func (c *componentStatuses) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("componentstatuses"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *componentStatuses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("componentstatuses"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the componentStatus, and returns the corresponding componentStatus object, and an error if there is any. +func (c *componentStatuses) Get(name string) (result *v1.ComponentStatus, err error) { + result = &v1.ComponentStatus{} + err = c.client.Get(). + Resource("componentstatuses"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ComponentStatuses that match those selectors. +func (c *componentStatuses) List(opts api.ListOptions) (result *v1.ComponentStatusList, err error) { + result = &v1.ComponentStatusList{} + err = c.client.Get(). + Resource("componentstatuses"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested componentStatuses. +func (c *componentStatuses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("componentstatuses"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/configmap.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/configmap.go new file mode 100644 index 000000000..4fbb31328 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/configmap.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ConfigMapsGetter has a method to return a ConfigMapInterface. +// A group's client should implement this interface. +type ConfigMapsGetter interface { + ConfigMaps(namespace string) ConfigMapInterface +} + +// ConfigMapInterface has methods to work with ConfigMap resources. +type ConfigMapInterface interface { + Create(*v1.ConfigMap) (*v1.ConfigMap, error) + Update(*v1.ConfigMap) (*v1.ConfigMap, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.ConfigMap, error) + List(opts api.ListOptions) (*v1.ConfigMapList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ConfigMapExpansion +} + +// configMaps implements ConfigMapInterface +type configMaps struct { + client *CoreClient + ns string +} + +// newConfigMaps returns a ConfigMaps +func newConfigMaps(c *CoreClient, namespace string) *configMaps { + return &configMaps{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a configMap and creates it. Returns the server's representation of the configMap, and an error, if there is any. +func (c *configMaps) Create(configMap *v1.ConfigMap) (result *v1.ConfigMap, err error) { + result = &v1.ConfigMap{} + err = c.client.Post(). + Namespace(c.ns). + Resource("configmaps"). + Body(configMap). + Do(). + Into(result) + return +} + +// Update takes the representation of a configMap and updates it. Returns the server's representation of the configMap, and an error, if there is any. +func (c *configMaps) Update(configMap *v1.ConfigMap) (result *v1.ConfigMap, err error) { + result = &v1.ConfigMap{} + err = c.client.Put(). + Namespace(c.ns). + Resource("configmaps"). + Name(configMap.Name). + Body(configMap). + Do(). + Into(result) + return +} + +// Delete takes name of the configMap and deletes it. Returns an error if one occurs. +func (c *configMaps) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("configmaps"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *configMaps) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("configmaps"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the configMap, and returns the corresponding configMap object, and an error if there is any. +func (c *configMaps) Get(name string) (result *v1.ConfigMap, err error) { + result = &v1.ConfigMap{} + err = c.client.Get(). + Namespace(c.ns). + Resource("configmaps"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ConfigMaps that match those selectors. +func (c *configMaps) List(opts api.ListOptions) (result *v1.ConfigMapList, err error) { + result = &v1.ConfigMapList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("configmaps"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested configMaps. +func (c *configMaps) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("configmaps"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/core_client.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/core_client.go new file mode 100644 index 000000000..886d556b5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/core_client.go @@ -0,0 +1,160 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + registered "k8s.io/kubernetes/pkg/apimachinery/registered" + restclient "k8s.io/kubernetes/pkg/client/restclient" +) + +type CoreInterface interface { + ComponentStatusesGetter + ConfigMapsGetter + EndpointsGetter + EventsGetter + LimitRangesGetter + NamespacesGetter + NodesGetter + PersistentVolumesGetter + PodsGetter + PodTemplatesGetter + ReplicationControllersGetter + ResourceQuotasGetter + SecretsGetter + ServicesGetter + ServiceAccountsGetter +} + +// CoreClient is used to interact with features provided by the Core group. +type CoreClient struct { + *restclient.RESTClient +} + +func (c *CoreClient) ComponentStatuses() ComponentStatusInterface { + return newComponentStatuses(c) +} + +func (c *CoreClient) ConfigMaps(namespace string) ConfigMapInterface { + return newConfigMaps(c, namespace) +} + +func (c *CoreClient) Endpoints(namespace string) EndpointsInterface { + return newEndpoints(c, namespace) +} + +func (c *CoreClient) Events(namespace string) EventInterface { + return newEvents(c, namespace) +} + +func (c *CoreClient) LimitRanges(namespace string) LimitRangeInterface { + return newLimitRanges(c, namespace) +} + +func (c *CoreClient) Namespaces() NamespaceInterface { + return newNamespaces(c) +} + +func (c *CoreClient) Nodes() NodeInterface { + return newNodes(c) +} + +func (c *CoreClient) PersistentVolumes() PersistentVolumeInterface { + return newPersistentVolumes(c) +} + +func (c *CoreClient) Pods(namespace string) PodInterface { + return newPods(c, namespace) +} + +func (c *CoreClient) PodTemplates(namespace string) PodTemplateInterface { + return newPodTemplates(c, namespace) +} + +func (c *CoreClient) ReplicationControllers(namespace string) ReplicationControllerInterface { + return newReplicationControllers(c, namespace) +} + +func (c *CoreClient) ResourceQuotas(namespace string) ResourceQuotaInterface { + return newResourceQuotas(c, namespace) +} + +func (c *CoreClient) Secrets(namespace string) SecretInterface { + return newSecrets(c, namespace) +} + +func (c *CoreClient) Services(namespace string) ServiceInterface { + return newServices(c, namespace) +} + +func (c *CoreClient) ServiceAccounts(namespace string) ServiceAccountInterface { + return newServiceAccounts(c, namespace) +} + +// NewForConfig creates a new CoreClient for the given config. +func NewForConfig(c *restclient.Config) (*CoreClient, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := restclient.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &CoreClient{client}, nil +} + +// NewForConfigOrDie creates a new CoreClient for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *restclient.Config) *CoreClient { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new CoreClient for the given RESTClient. +func New(c *restclient.RESTClient) *CoreClient { + return &CoreClient{c} +} + +func setConfigDefaults(config *restclient.Config) error { + // if core group is not registered, return an error + g, err := registered.Group("") + if err != nil { + return err + } + config.APIPath = "/api" + if config.UserAgent == "" { + config.UserAgent = restclient.DefaultKubernetesUserAgent() + } + // TODO: Unconditionally set the config.Version, until we fix the config. + //if config.Version == "" { + copyGroupVersion := g.GroupVersion + config.GroupVersion = ©GroupVersion + //} + + config.Codec = api.Codecs.LegacyCodec(*config.GroupVersion) + if config.QPS == 0 { + config.QPS = 5 + } + if config.Burst == 0 { + config.Burst = 10 + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/doc.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/doc.go new file mode 100644 index 000000000..c562125f6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned has the automatically generated clients for unversioned resources. +package v1 diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/endpoints.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/endpoints.go new file mode 100644 index 000000000..409b044c7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/endpoints.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// EndpointsGetter has a method to return a EndpointsInterface. +// A group's client should implement this interface. +type EndpointsGetter interface { + Endpoints(namespace string) EndpointsInterface +} + +// EndpointsInterface has methods to work with Endpoints resources. +type EndpointsInterface interface { + Create(*v1.Endpoints) (*v1.Endpoints, error) + Update(*v1.Endpoints) (*v1.Endpoints, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.Endpoints, error) + List(opts api.ListOptions) (*v1.EndpointsList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + EndpointsExpansion +} + +// endpoints implements EndpointsInterface +type endpoints struct { + client *CoreClient + ns string +} + +// newEndpoints returns a Endpoints +func newEndpoints(c *CoreClient, namespace string) *endpoints { + return &endpoints{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a endpoints and creates it. Returns the server's representation of the endpoints, and an error, if there is any. +func (c *endpoints) Create(endpoints *v1.Endpoints) (result *v1.Endpoints, err error) { + result = &v1.Endpoints{} + err = c.client.Post(). + Namespace(c.ns). + Resource("endpoints"). + Body(endpoints). + Do(). + Into(result) + return +} + +// Update takes the representation of a endpoints and updates it. Returns the server's representation of the endpoints, and an error, if there is any. +func (c *endpoints) Update(endpoints *v1.Endpoints) (result *v1.Endpoints, err error) { + result = &v1.Endpoints{} + err = c.client.Put(). + Namespace(c.ns). + Resource("endpoints"). + Name(endpoints.Name). + Body(endpoints). + Do(). + Into(result) + return +} + +// Delete takes name of the endpoints and deletes it. Returns an error if one occurs. +func (c *endpoints) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("endpoints"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *endpoints) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("endpoints"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the endpoints, and returns the corresponding endpoints object, and an error if there is any. +func (c *endpoints) Get(name string) (result *v1.Endpoints, err error) { + result = &v1.Endpoints{} + err = c.client.Get(). + Namespace(c.ns). + Resource("endpoints"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Endpoints that match those selectors. +func (c *endpoints) List(opts api.ListOptions) (result *v1.EndpointsList, err error) { + result = &v1.EndpointsList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("endpoints"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested endpoints. +func (c *endpoints) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("endpoints"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/event.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/event.go new file mode 100644 index 000000000..92266c98b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/event.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// EventsGetter has a method to return a EventInterface. +// A group's client should implement this interface. +type EventsGetter interface { + Events(namespace string) EventInterface +} + +// EventInterface has methods to work with Event resources. +type EventInterface interface { + Create(*v1.Event) (*v1.Event, error) + Update(*v1.Event) (*v1.Event, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.Event, error) + List(opts api.ListOptions) (*v1.EventList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + EventExpansion +} + +// events implements EventInterface +type events struct { + client *CoreClient + ns string +} + +// newEvents returns a Events +func newEvents(c *CoreClient, namespace string) *events { + return &events{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a event and creates it. Returns the server's representation of the event, and an error, if there is any. +func (c *events) Create(event *v1.Event) (result *v1.Event, err error) { + result = &v1.Event{} + err = c.client.Post(). + Namespace(c.ns). + Resource("events"). + Body(event). + Do(). + Into(result) + return +} + +// Update takes the representation of a event and updates it. Returns the server's representation of the event, and an error, if there is any. +func (c *events) Update(event *v1.Event) (result *v1.Event, err error) { + result = &v1.Event{} + err = c.client.Put(). + Namespace(c.ns). + Resource("events"). + Name(event.Name). + Body(event). + Do(). + Into(result) + return +} + +// Delete takes name of the event and deletes it. Returns an error if one occurs. +func (c *events) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("events"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *events) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("events"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the event, and returns the corresponding event object, and an error if there is any. +func (c *events) Get(name string) (result *v1.Event, err error) { + result = &v1.Event{} + err = c.client.Get(). + Namespace(c.ns). + Resource("events"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Events that match those selectors. +func (c *events) List(opts api.ListOptions) (result *v1.EventList, err error) { + result = &v1.EventList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("events"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested events. +func (c *events) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("events"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/event_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/event_expansion.go new file mode 100644 index 000000000..971c850c7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/event_expansion.go @@ -0,0 +1,158 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/runtime" +) + +// The EventExpansion interface allows manually adding extra methods to the EventInterface. +type EventExpansion interface { + // CreateWithEventNamespace is the same as a Create, except that it sends the request to the event.Namespace. + CreateWithEventNamespace(event *v1.Event) (*v1.Event, error) + // UpdateWithEventNamespace is the same as a Update, except that it sends the request to the event.Namespace. + UpdateWithEventNamespace(event *v1.Event) (*v1.Event, error) + Patch(event *v1.Event, data []byte) (*v1.Event, error) + // Search finds events about the specified object + Search(objOrRef runtime.Object) (*v1.EventList, error) + // Returns the appropriate field selector based on the API version being used to communicate with the server. + // The returned field selector can be used with List and Watch to filter desired events. + GetFieldSelector(involvedObjectName, involvedObjectNamespace, involvedObjectKind, involvedObjectUID *string) fields.Selector +} + +// CreateWithEventNamespace makes a new event. Returns the copy of the event the server returns, +// or an error. The namespace to create the event within is deduced from the +// event; it must either match this event client's namespace, or this event +// client must have been created with the "" namespace. +func (e *events) CreateWithEventNamespace(event *v1.Event) (*v1.Event, error) { + if e.ns != "" && event.Namespace != e.ns { + return nil, fmt.Errorf("can't create an event with namespace '%v' in namespace '%v'", event.Namespace, e.ns) + } + result := &v1.Event{} + err := e.client.Post(). + NamespaceIfScoped(event.Namespace, len(event.Namespace) > 0). + Resource("events"). + Body(event). + Do(). + Into(result) + return result, err +} + +// UpdateWithEventNamespace modifies an existing event. It returns the copy of the event that the server returns, +// or an error. The namespace and key to update the event within is deduced from the event. The +// namespace must either match this event client's namespace, or this event client must have been +// created with the "" namespace. Update also requires the ResourceVersion to be set in the event +// object. +func (e *events) UpdateWithEventNamespace(event *v1.Event) (*v1.Event, error) { + result := &v1.Event{} + err := e.client.Put(). + NamespaceIfScoped(event.Namespace, len(event.Namespace) > 0). + Resource("events"). + Name(event.Name). + Body(event). + Do(). + Into(result) + return result, err +} + +// Patch modifies an existing event. It returns the copy of the event that the server returns, or an +// error. The namespace and name of the target event is deduced from the incompleteEvent. The +// namespace must either match this event client's namespace, or this event client must have been +// created with the "" namespace. +func (e *events) Patch(incompleteEvent *v1.Event, data []byte) (*v1.Event, error) { + result := &v1.Event{} + err := e.client.Patch(api.StrategicMergePatchType). + NamespaceIfScoped(incompleteEvent.Namespace, len(incompleteEvent.Namespace) > 0). + Resource("events"). + Name(incompleteEvent.Name). + Body(data). + Do(). + Into(result) + return result, err +} + +// Search finds events about the specified object. The namespace of the +// object must match this event's client namespace unless the event client +// was made with the "" namespace. +func (e *events) Search(objOrRef runtime.Object) (*v1.EventList, error) { + ref, err := api.GetReference(objOrRef) + if err != nil { + return nil, err + } + if e.ns != "" && ref.Namespace != e.ns { + return nil, fmt.Errorf("won't be able to find any events of namespace '%v' in namespace '%v'", ref.Namespace, e.ns) + } + stringRefKind := string(ref.Kind) + var refKind *string + if stringRefKind != "" { + refKind = &stringRefKind + } + stringRefUID := string(ref.UID) + var refUID *string + if stringRefUID != "" { + refUID = &stringRefUID + } + fieldSelector := e.GetFieldSelector(&ref.Name, &ref.Namespace, refKind, refUID) + return e.List(api.ListOptions{FieldSelector: fieldSelector}) +} + +// Returns the appropriate field selector based on the API version being used to communicate with the server. +// The returned field selector can be used with List and Watch to filter desired events. +func (e *events) GetFieldSelector(involvedObjectName, involvedObjectNamespace, involvedObjectKind, involvedObjectUID *string) fields.Selector { + apiVersion := e.client.APIVersion().String() + field := fields.Set{} + if involvedObjectName != nil { + field[GetInvolvedObjectNameFieldLabel(apiVersion)] = *involvedObjectName + } + if involvedObjectNamespace != nil { + field["involvedObject.namespace"] = *involvedObjectNamespace + } + if involvedObjectKind != nil { + field["involvedObject.kind"] = *involvedObjectKind + } + if involvedObjectUID != nil { + field["involvedObject.uid"] = *involvedObjectUID + } + return field.AsSelector() +} + +// Returns the appropriate field label to use for name of the involved object as per the given API version. +func GetInvolvedObjectNameFieldLabel(version string) string { + return "involvedObject.name" +} + +// TODO: This is a temporary arrangement and will be removed once all clients are moved to use the clientset. +type EventSinkImpl struct { + Interface EventInterface +} + +func (e *EventSinkImpl) Create(event *v1.Event) (*v1.Event, error) { + return e.Interface.CreateWithEventNamespace(event) +} + +func (e *EventSinkImpl) Update(event *v1.Event) (*v1.Event, error) { + return e.Interface.UpdateWithEventNamespace(event) +} + +func (e *EventSinkImpl) Patch(event *v1.Event, data []byte) (*v1.Event, error) { + return e.Interface.Patch(event, data) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/doc.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/doc.go new file mode 100644 index 000000000..dd6d4da71 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake has the automatically generated clients. +package fake diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_componentstatus.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_componentstatus.go new file mode 100644 index 000000000..05c820073 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_componentstatus.go @@ -0,0 +1,96 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeComponentStatuses implements ComponentStatusInterface +type FakeComponentStatuses struct { + Fake *FakeCore +} + +func (c *FakeComponentStatuses) Create(componentStatus *v1.ComponentStatus) (result *v1.ComponentStatus, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction("componentstatuses", componentStatus), &v1.ComponentStatus{}) + if obj == nil { + return nil, err + } + return obj.(*v1.ComponentStatus), err +} + +func (c *FakeComponentStatuses) Update(componentStatus *v1.ComponentStatus) (result *v1.ComponentStatus, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction("componentstatuses", componentStatus), &v1.ComponentStatus{}) + if obj == nil { + return nil, err + } + return obj.(*v1.ComponentStatus), err +} + +func (c *FakeComponentStatuses) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction("componentstatuses", name), &v1.ComponentStatus{}) + return err +} + +func (c *FakeComponentStatuses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction("componentstatuses", listOptions) + + _, err := c.Fake.Invokes(action, &v1.ComponentStatusList{}) + return err +} + +func (c *FakeComponentStatuses) Get(name string) (result *v1.ComponentStatus, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction("componentstatuses", name), &v1.ComponentStatus{}) + if obj == nil { + return nil, err + } + return obj.(*v1.ComponentStatus), err +} + +func (c *FakeComponentStatuses) List(opts api.ListOptions) (result *v1.ComponentStatusList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction("componentstatuses", opts), &v1.ComponentStatusList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.ComponentStatusList{} + for _, item := range obj.(*v1.ComponentStatusList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested componentStatuses. +func (c *FakeComponentStatuses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction("componentstatuses", opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_configmap.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_configmap.go new file mode 100644 index 000000000..79a9a20ec --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_configmap.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeConfigMaps implements ConfigMapInterface +type FakeConfigMaps struct { + Fake *FakeCore + ns string +} + +func (c *FakeConfigMaps) Create(configMap *v1.ConfigMap) (result *v1.ConfigMap, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("configmaps", c.ns, configMap), &v1.ConfigMap{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ConfigMap), err +} + +func (c *FakeConfigMaps) Update(configMap *v1.ConfigMap) (result *v1.ConfigMap, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("configmaps", c.ns, configMap), &v1.ConfigMap{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ConfigMap), err +} + +func (c *FakeConfigMaps) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("configmaps", c.ns, name), &v1.ConfigMap{}) + + return err +} + +func (c *FakeConfigMaps) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("configmaps", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.ConfigMapList{}) + return err +} + +func (c *FakeConfigMaps) Get(name string) (result *v1.ConfigMap, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("configmaps", c.ns, name), &v1.ConfigMap{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ConfigMap), err +} + +func (c *FakeConfigMaps) List(opts api.ListOptions) (result *v1.ConfigMapList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("configmaps", c.ns, opts), &v1.ConfigMapList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.ConfigMapList{} + for _, item := range obj.(*v1.ConfigMapList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested configMaps. +func (c *FakeConfigMaps) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("configmaps", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_core_client.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_core_client.go new file mode 100644 index 000000000..3e8af41ab --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_core_client.go @@ -0,0 +1,86 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + core "k8s.io/kubernetes/pkg/client/testing/core" + v1 "k8s.io/kubernetes/pkg/client/typed/generated/core/v1" +) + +type FakeCore struct { + *core.Fake +} + +func (c *FakeCore) ComponentStatuses() v1.ComponentStatusInterface { + return &FakeComponentStatuses{c} +} + +func (c *FakeCore) ConfigMaps(namespace string) v1.ConfigMapInterface { + return &FakeConfigMaps{c, namespace} +} + +func (c *FakeCore) Endpoints(namespace string) v1.EndpointsInterface { + return &FakeEndpoints{c, namespace} +} + +func (c *FakeCore) Events(namespace string) v1.EventInterface { + return &FakeEvents{c, namespace} +} + +func (c *FakeCore) LimitRanges(namespace string) v1.LimitRangeInterface { + return &FakeLimitRanges{c, namespace} +} + +func (c *FakeCore) Namespaces() v1.NamespaceInterface { + return &FakeNamespaces{c} +} + +func (c *FakeCore) Nodes() v1.NodeInterface { + return &FakeNodes{c} +} + +func (c *FakeCore) PersistentVolumes() v1.PersistentVolumeInterface { + return &FakePersistentVolumes{c} +} + +func (c *FakeCore) Pods(namespace string) v1.PodInterface { + return &FakePods{c, namespace} +} + +func (c *FakeCore) PodTemplates(namespace string) v1.PodTemplateInterface { + return &FakePodTemplates{c, namespace} +} + +func (c *FakeCore) ReplicationControllers(namespace string) v1.ReplicationControllerInterface { + return &FakeReplicationControllers{c, namespace} +} + +func (c *FakeCore) ResourceQuotas(namespace string) v1.ResourceQuotaInterface { + return &FakeResourceQuotas{c, namespace} +} + +func (c *FakeCore) Secrets(namespace string) v1.SecretInterface { + return &FakeSecrets{c, namespace} +} + +func (c *FakeCore) Services(namespace string) v1.ServiceInterface { + return &FakeServices{c, namespace} +} + +func (c *FakeCore) ServiceAccounts(namespace string) v1.ServiceAccountInterface { + return &FakeServiceAccounts{c, namespace} +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_endpoints.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_endpoints.go new file mode 100644 index 000000000..7bc9304a8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_endpoints.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeEndpoints implements EndpointsInterface +type FakeEndpoints struct { + Fake *FakeCore + ns string +} + +func (c *FakeEndpoints) Create(endpoints *v1.Endpoints) (result *v1.Endpoints, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("endpoints", c.ns, endpoints), &v1.Endpoints{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Endpoints), err +} + +func (c *FakeEndpoints) Update(endpoints *v1.Endpoints) (result *v1.Endpoints, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("endpoints", c.ns, endpoints), &v1.Endpoints{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Endpoints), err +} + +func (c *FakeEndpoints) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("endpoints", c.ns, name), &v1.Endpoints{}) + + return err +} + +func (c *FakeEndpoints) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("endpoints", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.EndpointsList{}) + return err +} + +func (c *FakeEndpoints) Get(name string) (result *v1.Endpoints, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("endpoints", c.ns, name), &v1.Endpoints{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Endpoints), err +} + +func (c *FakeEndpoints) List(opts api.ListOptions) (result *v1.EndpointsList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("endpoints", c.ns, opts), &v1.EndpointsList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.EndpointsList{} + for _, item := range obj.(*v1.EndpointsList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested endpoints. +func (c *FakeEndpoints) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("endpoints", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_event.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_event.go new file mode 100644 index 000000000..53a62c693 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_event.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeEvents implements EventInterface +type FakeEvents struct { + Fake *FakeCore + ns string +} + +func (c *FakeEvents) Create(event *v1.Event) (result *v1.Event, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("events", c.ns, event), &v1.Event{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Event), err +} + +func (c *FakeEvents) Update(event *v1.Event) (result *v1.Event, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("events", c.ns, event), &v1.Event{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Event), err +} + +func (c *FakeEvents) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("events", c.ns, name), &v1.Event{}) + + return err +} + +func (c *FakeEvents) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("events", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.EventList{}) + return err +} + +func (c *FakeEvents) Get(name string) (result *v1.Event, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("events", c.ns, name), &v1.Event{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Event), err +} + +func (c *FakeEvents) List(opts api.ListOptions) (result *v1.EventList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("events", c.ns, opts), &v1.EventList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.EventList{} + for _, item := range obj.(*v1.EventList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested events. +func (c *FakeEvents) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("events", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_event_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_event_expansion.go new file mode 100644 index 000000000..f6585b481 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_event_expansion.go @@ -0,0 +1,89 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/runtime" +) + +func (c *FakeEvents) CreateWithEventNamespace(event *v1.Event) (*v1.Event, error) { + action := core.NewRootCreateAction("events", event) + if c.ns != "" { + action = core.NewCreateAction("events", c.ns, event) + } + obj, err := c.Fake.Invokes(action, event) + if obj == nil { + return nil, err + } + + return obj.(*v1.Event), err +} + +// Update replaces an existing event. Returns the copy of the event the server returns, or an error. +func (c *FakeEvents) UpdateWithEventNamespace(event *v1.Event) (*v1.Event, error) { + action := core.NewRootUpdateAction("events", event) + if c.ns != "" { + action = core.NewUpdateAction("events", c.ns, event) + } + obj, err := c.Fake.Invokes(action, event) + if obj == nil { + return nil, err + } + + return obj.(*v1.Event), err +} + +// Patch patches an existing event. Returns the copy of the event the server returns, or an error. +func (c *FakeEvents) Patch(event *v1.Event, data []byte) (*v1.Event, error) { + action := core.NewRootPatchAction("events", event) + if c.ns != "" { + action = core.NewPatchAction("events", c.ns, event) + } + obj, err := c.Fake.Invokes(action, event) + if obj == nil { + return nil, err + } + + return obj.(*v1.Event), err +} + +// Search returns a list of events matching the specified object. +func (c *FakeEvents) Search(objOrRef runtime.Object) (*v1.EventList, error) { + action := core.NewRootListAction("events", api.ListOptions{}) + if c.ns != "" { + action = core.NewListAction("events", c.ns, api.ListOptions{}) + } + obj, err := c.Fake.Invokes(action, &v1.EventList{}) + if obj == nil { + return nil, err + } + + return obj.(*v1.EventList), err +} + +func (c *FakeEvents) GetFieldSelector(involvedObjectName, involvedObjectNamespace, involvedObjectKind, involvedObjectUID *string) fields.Selector { + action := core.GenericActionImpl{} + action.Verb = "get-field-selector" + action.Resource = "events" + + c.Fake.Invokes(action, nil) + return fields.Everything() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_limitrange.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_limitrange.go new file mode 100644 index 000000000..26a096ef2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_limitrange.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeLimitRanges implements LimitRangeInterface +type FakeLimitRanges struct { + Fake *FakeCore + ns string +} + +func (c *FakeLimitRanges) Create(limitRange *v1.LimitRange) (result *v1.LimitRange, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("limitranges", c.ns, limitRange), &v1.LimitRange{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.LimitRange), err +} + +func (c *FakeLimitRanges) Update(limitRange *v1.LimitRange) (result *v1.LimitRange, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("limitranges", c.ns, limitRange), &v1.LimitRange{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.LimitRange), err +} + +func (c *FakeLimitRanges) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("limitranges", c.ns, name), &v1.LimitRange{}) + + return err +} + +func (c *FakeLimitRanges) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("limitranges", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.LimitRangeList{}) + return err +} + +func (c *FakeLimitRanges) Get(name string) (result *v1.LimitRange, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("limitranges", c.ns, name), &v1.LimitRange{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.LimitRange), err +} + +func (c *FakeLimitRanges) List(opts api.ListOptions) (result *v1.LimitRangeList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("limitranges", c.ns, opts), &v1.LimitRangeList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.LimitRangeList{} + for _, item := range obj.(*v1.LimitRangeList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested limitRanges. +func (c *FakeLimitRanges) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("limitranges", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_namespace.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_namespace.go new file mode 100644 index 000000000..5c26cca47 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_namespace.go @@ -0,0 +1,105 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeNamespaces implements NamespaceInterface +type FakeNamespaces struct { + Fake *FakeCore +} + +func (c *FakeNamespaces) Create(namespace *v1.Namespace) (result *v1.Namespace, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction("namespaces", namespace), &v1.Namespace{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Namespace), err +} + +func (c *FakeNamespaces) Update(namespace *v1.Namespace) (result *v1.Namespace, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction("namespaces", namespace), &v1.Namespace{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Namespace), err +} + +func (c *FakeNamespaces) UpdateStatus(namespace *v1.Namespace) (*v1.Namespace, error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateSubresourceAction("namespaces", "status", namespace), &v1.Namespace{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Namespace), err +} + +func (c *FakeNamespaces) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction("namespaces", name), &v1.Namespace{}) + return err +} + +func (c *FakeNamespaces) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction("namespaces", listOptions) + + _, err := c.Fake.Invokes(action, &v1.NamespaceList{}) + return err +} + +func (c *FakeNamespaces) Get(name string) (result *v1.Namespace, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction("namespaces", name), &v1.Namespace{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Namespace), err +} + +func (c *FakeNamespaces) List(opts api.ListOptions) (result *v1.NamespaceList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction("namespaces", opts), &v1.NamespaceList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.NamespaceList{} + for _, item := range obj.(*v1.NamespaceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested namespaces. +func (c *FakeNamespaces) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction("namespaces", opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_namespace_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_namespace_expansion.go new file mode 100644 index 000000000..255cad05d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_namespace_expansion.go @@ -0,0 +1,37 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeNamespaces) Finalize(namespace *v1.Namespace) (*v1.Namespace, error) { + action := core.CreateActionImpl{} + action.Verb = "create" + action.Resource = "namespaces" + action.Subresource = "finalize" + action.Object = namespace + + obj, err := c.Fake.Invokes(action, namespace) + if obj == nil { + return nil, err + } + + return obj.(*v1.Namespace), err +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_node.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_node.go new file mode 100644 index 000000000..d4351794c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_node.go @@ -0,0 +1,105 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeNodes implements NodeInterface +type FakeNodes struct { + Fake *FakeCore +} + +func (c *FakeNodes) Create(node *v1.Node) (result *v1.Node, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction("nodes", node), &v1.Node{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Node), err +} + +func (c *FakeNodes) Update(node *v1.Node) (result *v1.Node, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction("nodes", node), &v1.Node{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Node), err +} + +func (c *FakeNodes) UpdateStatus(node *v1.Node) (*v1.Node, error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateSubresourceAction("nodes", "status", node), &v1.Node{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Node), err +} + +func (c *FakeNodes) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction("nodes", name), &v1.Node{}) + return err +} + +func (c *FakeNodes) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction("nodes", listOptions) + + _, err := c.Fake.Invokes(action, &v1.NodeList{}) + return err +} + +func (c *FakeNodes) Get(name string) (result *v1.Node, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction("nodes", name), &v1.Node{}) + if obj == nil { + return nil, err + } + return obj.(*v1.Node), err +} + +func (c *FakeNodes) List(opts api.ListOptions) (result *v1.NodeList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction("nodes", opts), &v1.NodeList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.NodeList{} + for _, item := range obj.(*v1.NodeList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested nodes. +func (c *FakeNodes) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction("nodes", opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_persistentvolume.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_persistentvolume.go new file mode 100644 index 000000000..579c589e8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_persistentvolume.go @@ -0,0 +1,105 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakePersistentVolumes implements PersistentVolumeInterface +type FakePersistentVolumes struct { + Fake *FakeCore +} + +func (c *FakePersistentVolumes) Create(persistentVolume *v1.PersistentVolume) (result *v1.PersistentVolume, err error) { + obj, err := c.Fake. + Invokes(core.NewRootCreateAction("persistentvolumes", persistentVolume), &v1.PersistentVolume{}) + if obj == nil { + return nil, err + } + return obj.(*v1.PersistentVolume), err +} + +func (c *FakePersistentVolumes) Update(persistentVolume *v1.PersistentVolume) (result *v1.PersistentVolume, err error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateAction("persistentvolumes", persistentVolume), &v1.PersistentVolume{}) + if obj == nil { + return nil, err + } + return obj.(*v1.PersistentVolume), err +} + +func (c *FakePersistentVolumes) UpdateStatus(persistentVolume *v1.PersistentVolume) (*v1.PersistentVolume, error) { + obj, err := c.Fake. + Invokes(core.NewRootUpdateSubresourceAction("persistentvolumes", "status", persistentVolume), &v1.PersistentVolume{}) + if obj == nil { + return nil, err + } + return obj.(*v1.PersistentVolume), err +} + +func (c *FakePersistentVolumes) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewRootDeleteAction("persistentvolumes", name), &v1.PersistentVolume{}) + return err +} + +func (c *FakePersistentVolumes) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewRootDeleteCollectionAction("persistentvolumes", listOptions) + + _, err := c.Fake.Invokes(action, &v1.PersistentVolumeList{}) + return err +} + +func (c *FakePersistentVolumes) Get(name string) (result *v1.PersistentVolume, err error) { + obj, err := c.Fake. + Invokes(core.NewRootGetAction("persistentvolumes", name), &v1.PersistentVolume{}) + if obj == nil { + return nil, err + } + return obj.(*v1.PersistentVolume), err +} + +func (c *FakePersistentVolumes) List(opts api.ListOptions) (result *v1.PersistentVolumeList, err error) { + obj, err := c.Fake. + Invokes(core.NewRootListAction("persistentvolumes", opts), &v1.PersistentVolumeList{}) + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.PersistentVolumeList{} + for _, item := range obj.(*v1.PersistentVolumeList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested persistentVolumes. +func (c *FakePersistentVolumes) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewRootWatchAction("persistentvolumes", opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_pod.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_pod.go new file mode 100644 index 000000000..a5fab0d81 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_pod.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakePods implements PodInterface +type FakePods struct { + Fake *FakeCore + ns string +} + +func (c *FakePods) Create(pod *v1.Pod) (result *v1.Pod, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("pods", c.ns, pod), &v1.Pod{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Pod), err +} + +func (c *FakePods) Update(pod *v1.Pod) (result *v1.Pod, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("pods", c.ns, pod), &v1.Pod{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Pod), err +} + +func (c *FakePods) UpdateStatus(pod *v1.Pod) (*v1.Pod, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("pods", "status", c.ns, pod), &v1.Pod{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Pod), err +} + +func (c *FakePods) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("pods", c.ns, name), &v1.Pod{}) + + return err +} + +func (c *FakePods) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("pods", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.PodList{}) + return err +} + +func (c *FakePods) Get(name string) (result *v1.Pod, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("pods", c.ns, name), &v1.Pod{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Pod), err +} + +func (c *FakePods) List(opts api.ListOptions) (result *v1.PodList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("pods", c.ns, opts), &v1.PodList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.PodList{} + for _, item := range obj.(*v1.PodList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested pods. +func (c *FakePods) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("pods", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_pod_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_pod_expansion.go new file mode 100644 index 000000000..c4ad84c6c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_pod_expansion.go @@ -0,0 +1,46 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakePods) Bind(binding *v1.Binding) error { + action := core.CreateActionImpl{} + action.Verb = "create" + action.Resource = "pods" + action.Subresource = "bindings" + action.Object = binding + + _, err := c.Fake.Invokes(action, binding) + return err +} + +func (c *FakePods) GetLogs(name string, opts *v1.PodLogOptions) *restclient.Request { + action := core.GenericActionImpl{} + action.Verb = "get" + action.Namespace = c.ns + action.Resource = "pod" + action.Subresource = "logs" + action.Value = opts + + _, _ = c.Fake.Invokes(action, &v1.Pod{}) + return &restclient.Request{} +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_podtemplate.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_podtemplate.go new file mode 100644 index 000000000..b9ac44952 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_podtemplate.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakePodTemplates implements PodTemplateInterface +type FakePodTemplates struct { + Fake *FakeCore + ns string +} + +func (c *FakePodTemplates) Create(podTemplate *v1.PodTemplate) (result *v1.PodTemplate, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("podtemplates", c.ns, podTemplate), &v1.PodTemplate{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.PodTemplate), err +} + +func (c *FakePodTemplates) Update(podTemplate *v1.PodTemplate) (result *v1.PodTemplate, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("podtemplates", c.ns, podTemplate), &v1.PodTemplate{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.PodTemplate), err +} + +func (c *FakePodTemplates) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("podtemplates", c.ns, name), &v1.PodTemplate{}) + + return err +} + +func (c *FakePodTemplates) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("podtemplates", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.PodTemplateList{}) + return err +} + +func (c *FakePodTemplates) Get(name string) (result *v1.PodTemplate, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("podtemplates", c.ns, name), &v1.PodTemplate{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.PodTemplate), err +} + +func (c *FakePodTemplates) List(opts api.ListOptions) (result *v1.PodTemplateList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("podtemplates", c.ns, opts), &v1.PodTemplateList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.PodTemplateList{} + for _, item := range obj.(*v1.PodTemplateList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested podTemplates. +func (c *FakePodTemplates) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("podtemplates", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_replicationcontroller.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_replicationcontroller.go new file mode 100644 index 000000000..c390efb60 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_replicationcontroller.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeReplicationControllers implements ReplicationControllerInterface +type FakeReplicationControllers struct { + Fake *FakeCore + ns string +} + +func (c *FakeReplicationControllers) Create(replicationController *v1.ReplicationController) (result *v1.ReplicationController, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("replicationcontrollers", c.ns, replicationController), &v1.ReplicationController{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ReplicationController), err +} + +func (c *FakeReplicationControllers) Update(replicationController *v1.ReplicationController) (result *v1.ReplicationController, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("replicationcontrollers", c.ns, replicationController), &v1.ReplicationController{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ReplicationController), err +} + +func (c *FakeReplicationControllers) UpdateStatus(replicationController *v1.ReplicationController) (*v1.ReplicationController, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("replicationcontrollers", "status", c.ns, replicationController), &v1.ReplicationController{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ReplicationController), err +} + +func (c *FakeReplicationControllers) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("replicationcontrollers", c.ns, name), &v1.ReplicationController{}) + + return err +} + +func (c *FakeReplicationControllers) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("replicationcontrollers", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.ReplicationControllerList{}) + return err +} + +func (c *FakeReplicationControllers) Get(name string) (result *v1.ReplicationController, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("replicationcontrollers", c.ns, name), &v1.ReplicationController{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ReplicationController), err +} + +func (c *FakeReplicationControllers) List(opts api.ListOptions) (result *v1.ReplicationControllerList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("replicationcontrollers", c.ns, opts), &v1.ReplicationControllerList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.ReplicationControllerList{} + for _, item := range obj.(*v1.ReplicationControllerList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested replicationControllers. +func (c *FakeReplicationControllers) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("replicationcontrollers", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_resourcequota.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_resourcequota.go new file mode 100644 index 000000000..5adf58187 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_resourcequota.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeResourceQuotas implements ResourceQuotaInterface +type FakeResourceQuotas struct { + Fake *FakeCore + ns string +} + +func (c *FakeResourceQuotas) Create(resourceQuota *v1.ResourceQuota) (result *v1.ResourceQuota, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("resourcequotas", c.ns, resourceQuota), &v1.ResourceQuota{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ResourceQuota), err +} + +func (c *FakeResourceQuotas) Update(resourceQuota *v1.ResourceQuota) (result *v1.ResourceQuota, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("resourcequotas", c.ns, resourceQuota), &v1.ResourceQuota{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ResourceQuota), err +} + +func (c *FakeResourceQuotas) UpdateStatus(resourceQuota *v1.ResourceQuota) (*v1.ResourceQuota, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("resourcequotas", "status", c.ns, resourceQuota), &v1.ResourceQuota{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ResourceQuota), err +} + +func (c *FakeResourceQuotas) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("resourcequotas", c.ns, name), &v1.ResourceQuota{}) + + return err +} + +func (c *FakeResourceQuotas) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("resourcequotas", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.ResourceQuotaList{}) + return err +} + +func (c *FakeResourceQuotas) Get(name string) (result *v1.ResourceQuota, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("resourcequotas", c.ns, name), &v1.ResourceQuota{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ResourceQuota), err +} + +func (c *FakeResourceQuotas) List(opts api.ListOptions) (result *v1.ResourceQuotaList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("resourcequotas", c.ns, opts), &v1.ResourceQuotaList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.ResourceQuotaList{} + for _, item := range obj.(*v1.ResourceQuotaList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested resourceQuotas. +func (c *FakeResourceQuotas) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("resourcequotas", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_secret.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_secret.go new file mode 100644 index 000000000..989bdf8a5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_secret.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeSecrets implements SecretInterface +type FakeSecrets struct { + Fake *FakeCore + ns string +} + +func (c *FakeSecrets) Create(secret *v1.Secret) (result *v1.Secret, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("secrets", c.ns, secret), &v1.Secret{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Secret), err +} + +func (c *FakeSecrets) Update(secret *v1.Secret) (result *v1.Secret, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("secrets", c.ns, secret), &v1.Secret{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Secret), err +} + +func (c *FakeSecrets) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("secrets", c.ns, name), &v1.Secret{}) + + return err +} + +func (c *FakeSecrets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("secrets", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.SecretList{}) + return err +} + +func (c *FakeSecrets) Get(name string) (result *v1.Secret, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("secrets", c.ns, name), &v1.Secret{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Secret), err +} + +func (c *FakeSecrets) List(opts api.ListOptions) (result *v1.SecretList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("secrets", c.ns, opts), &v1.SecretList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.SecretList{} + for _, item := range obj.(*v1.SecretList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested secrets. +func (c *FakeSecrets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("secrets", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_service.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_service.go new file mode 100644 index 000000000..303dff9c6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_service.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeServices implements ServiceInterface +type FakeServices struct { + Fake *FakeCore + ns string +} + +func (c *FakeServices) Create(service *v1.Service) (result *v1.Service, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("services", c.ns, service), &v1.Service{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Service), err +} + +func (c *FakeServices) Update(service *v1.Service) (result *v1.Service, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("services", c.ns, service), &v1.Service{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Service), err +} + +func (c *FakeServices) UpdateStatus(service *v1.Service) (*v1.Service, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("services", "status", c.ns, service), &v1.Service{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Service), err +} + +func (c *FakeServices) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("services", c.ns, name), &v1.Service{}) + + return err +} + +func (c *FakeServices) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("services", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.ServiceList{}) + return err +} + +func (c *FakeServices) Get(name string) (result *v1.Service, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("services", c.ns, name), &v1.Service{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.Service), err +} + +func (c *FakeServices) List(opts api.ListOptions) (result *v1.ServiceList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("services", c.ns, opts), &v1.ServiceList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.ServiceList{} + for _, item := range obj.(*v1.ServiceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested services. +func (c *FakeServices) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("services", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_service_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_service_expansion.go new file mode 100644 index 000000000..18f1b7803 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_service_expansion.go @@ -0,0 +1,26 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeServices) ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper { + return c.Fake.InvokesProxy(core.NewProxyGetAction("services", c.ns, scheme, name, port, path, params)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_serviceaccount.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_serviceaccount.go new file mode 100644 index 000000000..b08488f64 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/fake/fake_serviceaccount.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeServiceAccounts implements ServiceAccountInterface +type FakeServiceAccounts struct { + Fake *FakeCore + ns string +} + +func (c *FakeServiceAccounts) Create(serviceAccount *v1.ServiceAccount) (result *v1.ServiceAccount, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("serviceaccounts", c.ns, serviceAccount), &v1.ServiceAccount{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ServiceAccount), err +} + +func (c *FakeServiceAccounts) Update(serviceAccount *v1.ServiceAccount) (result *v1.ServiceAccount, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("serviceaccounts", c.ns, serviceAccount), &v1.ServiceAccount{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ServiceAccount), err +} + +func (c *FakeServiceAccounts) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("serviceaccounts", c.ns, name), &v1.ServiceAccount{}) + + return err +} + +func (c *FakeServiceAccounts) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("serviceaccounts", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1.ServiceAccountList{}) + return err +} + +func (c *FakeServiceAccounts) Get(name string) (result *v1.ServiceAccount, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("serviceaccounts", c.ns, name), &v1.ServiceAccount{}) + + if obj == nil { + return nil, err + } + return obj.(*v1.ServiceAccount), err +} + +func (c *FakeServiceAccounts) List(opts api.ListOptions) (result *v1.ServiceAccountList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("serviceaccounts", c.ns, opts), &v1.ServiceAccountList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1.ServiceAccountList{} + for _, item := range obj.(*v1.ServiceAccountList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested serviceAccounts. +func (c *FakeServiceAccounts) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("serviceaccounts", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/generated_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/generated_expansion.go new file mode 100644 index 000000000..9974ef5c6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/generated_expansion.go @@ -0,0 +1,41 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +type ComponentStatusExpansion interface{} + +type EndpointsExpansion interface{} + +type LimitRangeExpansion interface{} + +type NodeExpansion interface{} + +type PersistentVolumeExpansion interface{} + +type PersistentVolumeClaimExpansion interface{} + +type PodTemplateExpansion interface{} + +type ReplicationControllerExpansion interface{} + +type ResourceQuotaExpansion interface{} + +type SecretExpansion interface{} + +type ServiceAccountExpansion interface{} + +type ConfigMapExpansion interface{} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/limitrange.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/limitrange.go new file mode 100644 index 000000000..a44c61fa2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/limitrange.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// LimitRangesGetter has a method to return a LimitRangeInterface. +// A group's client should implement this interface. +type LimitRangesGetter interface { + LimitRanges(namespace string) LimitRangeInterface +} + +// LimitRangeInterface has methods to work with LimitRange resources. +type LimitRangeInterface interface { + Create(*v1.LimitRange) (*v1.LimitRange, error) + Update(*v1.LimitRange) (*v1.LimitRange, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.LimitRange, error) + List(opts api.ListOptions) (*v1.LimitRangeList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + LimitRangeExpansion +} + +// limitRanges implements LimitRangeInterface +type limitRanges struct { + client *CoreClient + ns string +} + +// newLimitRanges returns a LimitRanges +func newLimitRanges(c *CoreClient, namespace string) *limitRanges { + return &limitRanges{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a limitRange and creates it. Returns the server's representation of the limitRange, and an error, if there is any. +func (c *limitRanges) Create(limitRange *v1.LimitRange) (result *v1.LimitRange, err error) { + result = &v1.LimitRange{} + err = c.client.Post(). + Namespace(c.ns). + Resource("limitranges"). + Body(limitRange). + Do(). + Into(result) + return +} + +// Update takes the representation of a limitRange and updates it. Returns the server's representation of the limitRange, and an error, if there is any. +func (c *limitRanges) Update(limitRange *v1.LimitRange) (result *v1.LimitRange, err error) { + result = &v1.LimitRange{} + err = c.client.Put(). + Namespace(c.ns). + Resource("limitranges"). + Name(limitRange.Name). + Body(limitRange). + Do(). + Into(result) + return +} + +// Delete takes name of the limitRange and deletes it. Returns an error if one occurs. +func (c *limitRanges) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("limitranges"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *limitRanges) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("limitranges"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the limitRange, and returns the corresponding limitRange object, and an error if there is any. +func (c *limitRanges) Get(name string) (result *v1.LimitRange, err error) { + result = &v1.LimitRange{} + err = c.client.Get(). + Namespace(c.ns). + Resource("limitranges"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of LimitRanges that match those selectors. +func (c *limitRanges) List(opts api.ListOptions) (result *v1.LimitRangeList, err error) { + result = &v1.LimitRangeList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("limitranges"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested limitRanges. +func (c *limitRanges) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("limitranges"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/namespace.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/namespace.go new file mode 100644 index 000000000..3d2cff144 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/namespace.go @@ -0,0 +1,140 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// NamespacesGetter has a method to return a NamespaceInterface. +// A group's client should implement this interface. +type NamespacesGetter interface { + Namespaces() NamespaceInterface +} + +// NamespaceInterface has methods to work with Namespace resources. +type NamespaceInterface interface { + Create(*v1.Namespace) (*v1.Namespace, error) + Update(*v1.Namespace) (*v1.Namespace, error) + UpdateStatus(*v1.Namespace) (*v1.Namespace, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.Namespace, error) + List(opts api.ListOptions) (*v1.NamespaceList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + NamespaceExpansion +} + +// namespaces implements NamespaceInterface +type namespaces struct { + client *CoreClient +} + +// newNamespaces returns a Namespaces +func newNamespaces(c *CoreClient) *namespaces { + return &namespaces{ + client: c, + } +} + +// Create takes the representation of a namespace and creates it. Returns the server's representation of the namespace, and an error, if there is any. +func (c *namespaces) Create(namespace *v1.Namespace) (result *v1.Namespace, err error) { + result = &v1.Namespace{} + err = c.client.Post(). + Resource("namespaces"). + Body(namespace). + Do(). + Into(result) + return +} + +// Update takes the representation of a namespace and updates it. Returns the server's representation of the namespace, and an error, if there is any. +func (c *namespaces) Update(namespace *v1.Namespace) (result *v1.Namespace, err error) { + result = &v1.Namespace{} + err = c.client.Put(). + Resource("namespaces"). + Name(namespace.Name). + Body(namespace). + Do(). + Into(result) + return +} + +func (c *namespaces) UpdateStatus(namespace *v1.Namespace) (result *v1.Namespace, err error) { + result = &v1.Namespace{} + err = c.client.Put(). + Resource("namespaces"). + Name(namespace.Name). + SubResource("status"). + Body(namespace). + Do(). + Into(result) + return +} + +// Delete takes name of the namespace and deletes it. Returns an error if one occurs. +func (c *namespaces) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("namespaces"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *namespaces) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("namespaces"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the namespace, and returns the corresponding namespace object, and an error if there is any. +func (c *namespaces) Get(name string) (result *v1.Namespace, err error) { + result = &v1.Namespace{} + err = c.client.Get(). + Resource("namespaces"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Namespaces that match those selectors. +func (c *namespaces) List(opts api.ListOptions) (result *v1.NamespaceList, err error) { + result = &v1.NamespaceList{} + err = c.client.Get(). + Resource("namespaces"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested namespaces. +func (c *namespaces) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("namespaces"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/namespace_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/namespace_expansion.go new file mode 100644 index 000000000..7b5cf683d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/namespace_expansion.go @@ -0,0 +1,31 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import "k8s.io/kubernetes/pkg/api/v1" + +// The NamespaceExpansion interface allows manually adding extra methods to the NamespaceInterface. +type NamespaceExpansion interface { + Finalize(item *v1.Namespace) (*v1.Namespace, error) +} + +// Finalize takes the representation of a namespace to update. Returns the server's representation of the namespace, and an error, if it occurs. +func (c *namespaces) Finalize(namespace *v1.Namespace) (result *v1.Namespace, err error) { + result = &v1.Namespace{} + err = c.client.Put().Resource("namespaces").Name(namespace.Name).SubResource("finalize").Body(namespace).Do().Into(result) + return +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/node.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/node.go new file mode 100644 index 000000000..464eb8d6d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/node.go @@ -0,0 +1,140 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// NodesGetter has a method to return a NodeInterface. +// A group's client should implement this interface. +type NodesGetter interface { + Nodes() NodeInterface +} + +// NodeInterface has methods to work with Node resources. +type NodeInterface interface { + Create(*v1.Node) (*v1.Node, error) + Update(*v1.Node) (*v1.Node, error) + UpdateStatus(*v1.Node) (*v1.Node, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.Node, error) + List(opts api.ListOptions) (*v1.NodeList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + NodeExpansion +} + +// nodes implements NodeInterface +type nodes struct { + client *CoreClient +} + +// newNodes returns a Nodes +func newNodes(c *CoreClient) *nodes { + return &nodes{ + client: c, + } +} + +// Create takes the representation of a node and creates it. Returns the server's representation of the node, and an error, if there is any. +func (c *nodes) Create(node *v1.Node) (result *v1.Node, err error) { + result = &v1.Node{} + err = c.client.Post(). + Resource("nodes"). + Body(node). + Do(). + Into(result) + return +} + +// Update takes the representation of a node and updates it. Returns the server's representation of the node, and an error, if there is any. +func (c *nodes) Update(node *v1.Node) (result *v1.Node, err error) { + result = &v1.Node{} + err = c.client.Put(). + Resource("nodes"). + Name(node.Name). + Body(node). + Do(). + Into(result) + return +} + +func (c *nodes) UpdateStatus(node *v1.Node) (result *v1.Node, err error) { + result = &v1.Node{} + err = c.client.Put(). + Resource("nodes"). + Name(node.Name). + SubResource("status"). + Body(node). + Do(). + Into(result) + return +} + +// Delete takes name of the node and deletes it. Returns an error if one occurs. +func (c *nodes) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("nodes"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *nodes) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("nodes"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the node, and returns the corresponding node object, and an error if there is any. +func (c *nodes) Get(name string) (result *v1.Node, err error) { + result = &v1.Node{} + err = c.client.Get(). + Resource("nodes"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Nodes that match those selectors. +func (c *nodes) List(opts api.ListOptions) (result *v1.NodeList, err error) { + result = &v1.NodeList{} + err = c.client.Get(). + Resource("nodes"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested nodes. +func (c *nodes) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("nodes"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/persistentvolume.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/persistentvolume.go new file mode 100644 index 000000000..85ddf060e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/persistentvolume.go @@ -0,0 +1,140 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// PersistentVolumesGetter has a method to return a PersistentVolumeInterface. +// A group's client should implement this interface. +type PersistentVolumesGetter interface { + PersistentVolumes() PersistentVolumeInterface +} + +// PersistentVolumeInterface has methods to work with PersistentVolume resources. +type PersistentVolumeInterface interface { + Create(*v1.PersistentVolume) (*v1.PersistentVolume, error) + Update(*v1.PersistentVolume) (*v1.PersistentVolume, error) + UpdateStatus(*v1.PersistentVolume) (*v1.PersistentVolume, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.PersistentVolume, error) + List(opts api.ListOptions) (*v1.PersistentVolumeList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + PersistentVolumeExpansion +} + +// persistentVolumes implements PersistentVolumeInterface +type persistentVolumes struct { + client *CoreClient +} + +// newPersistentVolumes returns a PersistentVolumes +func newPersistentVolumes(c *CoreClient) *persistentVolumes { + return &persistentVolumes{ + client: c, + } +} + +// Create takes the representation of a persistentVolume and creates it. Returns the server's representation of the persistentVolume, and an error, if there is any. +func (c *persistentVolumes) Create(persistentVolume *v1.PersistentVolume) (result *v1.PersistentVolume, err error) { + result = &v1.PersistentVolume{} + err = c.client.Post(). + Resource("persistentvolumes"). + Body(persistentVolume). + Do(). + Into(result) + return +} + +// Update takes the representation of a persistentVolume and updates it. Returns the server's representation of the persistentVolume, and an error, if there is any. +func (c *persistentVolumes) Update(persistentVolume *v1.PersistentVolume) (result *v1.PersistentVolume, err error) { + result = &v1.PersistentVolume{} + err = c.client.Put(). + Resource("persistentvolumes"). + Name(persistentVolume.Name). + Body(persistentVolume). + Do(). + Into(result) + return +} + +func (c *persistentVolumes) UpdateStatus(persistentVolume *v1.PersistentVolume) (result *v1.PersistentVolume, err error) { + result = &v1.PersistentVolume{} + err = c.client.Put(). + Resource("persistentvolumes"). + Name(persistentVolume.Name). + SubResource("status"). + Body(persistentVolume). + Do(). + Into(result) + return +} + +// Delete takes name of the persistentVolume and deletes it. Returns an error if one occurs. +func (c *persistentVolumes) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Resource("persistentvolumes"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *persistentVolumes) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Resource("persistentvolumes"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the persistentVolume, and returns the corresponding persistentVolume object, and an error if there is any. +func (c *persistentVolumes) Get(name string) (result *v1.PersistentVolume, err error) { + result = &v1.PersistentVolume{} + err = c.client.Get(). + Resource("persistentvolumes"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of PersistentVolumes that match those selectors. +func (c *persistentVolumes) List(opts api.ListOptions) (result *v1.PersistentVolumeList, err error) { + result = &v1.PersistentVolumeList{} + err = c.client.Get(). + Resource("persistentvolumes"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested persistentVolumes. +func (c *persistentVolumes) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Resource("persistentvolumes"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/pod.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/pod.go new file mode 100644 index 000000000..d2ed5faaa --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/pod.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// PodsGetter has a method to return a PodInterface. +// A group's client should implement this interface. +type PodsGetter interface { + Pods(namespace string) PodInterface +} + +// PodInterface has methods to work with Pod resources. +type PodInterface interface { + Create(*v1.Pod) (*v1.Pod, error) + Update(*v1.Pod) (*v1.Pod, error) + UpdateStatus(*v1.Pod) (*v1.Pod, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.Pod, error) + List(opts api.ListOptions) (*v1.PodList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + PodExpansion +} + +// pods implements PodInterface +type pods struct { + client *CoreClient + ns string +} + +// newPods returns a Pods +func newPods(c *CoreClient, namespace string) *pods { + return &pods{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a pod and creates it. Returns the server's representation of the pod, and an error, if there is any. +func (c *pods) Create(pod *v1.Pod) (result *v1.Pod, err error) { + result = &v1.Pod{} + err = c.client.Post(). + Namespace(c.ns). + Resource("pods"). + Body(pod). + Do(). + Into(result) + return +} + +// Update takes the representation of a pod and updates it. Returns the server's representation of the pod, and an error, if there is any. +func (c *pods) Update(pod *v1.Pod) (result *v1.Pod, err error) { + result = &v1.Pod{} + err = c.client.Put(). + Namespace(c.ns). + Resource("pods"). + Name(pod.Name). + Body(pod). + Do(). + Into(result) + return +} + +func (c *pods) UpdateStatus(pod *v1.Pod) (result *v1.Pod, err error) { + result = &v1.Pod{} + err = c.client.Put(). + Namespace(c.ns). + Resource("pods"). + Name(pod.Name). + SubResource("status"). + Body(pod). + Do(). + Into(result) + return +} + +// Delete takes name of the pod and deletes it. Returns an error if one occurs. +func (c *pods) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("pods"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *pods) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("pods"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the pod, and returns the corresponding pod object, and an error if there is any. +func (c *pods) Get(name string) (result *v1.Pod, err error) { + result = &v1.Pod{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pods"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Pods that match those selectors. +func (c *pods) List(opts api.ListOptions) (result *v1.PodList, err error) { + result = &v1.PodList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("pods"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested pods. +func (c *pods) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("pods"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/pod_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/pod_expansion.go new file mode 100644 index 000000000..f061b5d92 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/pod_expansion.go @@ -0,0 +1,39 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/client/restclient" +) + +// The PodExpansion interface allows manually adding extra methods to the PodInterface. +type PodExpansion interface { + Bind(binding *v1.Binding) error + GetLogs(name string, opts *v1.PodLogOptions) *restclient.Request +} + +// Bind applies the provided binding to the named pod in the current namespace (binding.Namespace is ignored). +func (c *pods) Bind(binding *v1.Binding) error { + return c.client.Post().Namespace(c.ns).Resource("pods").Name(binding.Name).SubResource("binding").Body(binding).Do().Error() +} + +// Get constructs a request for getting the logs for a pod +func (c *pods) GetLogs(name string, opts *v1.PodLogOptions) *restclient.Request { + return c.client.Get().Namespace(c.ns).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, api.ParameterCodec) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/podtemplate.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/podtemplate.go new file mode 100644 index 000000000..1b95106d1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/podtemplate.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// PodTemplatesGetter has a method to return a PodTemplateInterface. +// A group's client should implement this interface. +type PodTemplatesGetter interface { + PodTemplates(namespace string) PodTemplateInterface +} + +// PodTemplateInterface has methods to work with PodTemplate resources. +type PodTemplateInterface interface { + Create(*v1.PodTemplate) (*v1.PodTemplate, error) + Update(*v1.PodTemplate) (*v1.PodTemplate, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.PodTemplate, error) + List(opts api.ListOptions) (*v1.PodTemplateList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + PodTemplateExpansion +} + +// podTemplates implements PodTemplateInterface +type podTemplates struct { + client *CoreClient + ns string +} + +// newPodTemplates returns a PodTemplates +func newPodTemplates(c *CoreClient, namespace string) *podTemplates { + return &podTemplates{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a podTemplate and creates it. Returns the server's representation of the podTemplate, and an error, if there is any. +func (c *podTemplates) Create(podTemplate *v1.PodTemplate) (result *v1.PodTemplate, err error) { + result = &v1.PodTemplate{} + err = c.client.Post(). + Namespace(c.ns). + Resource("podtemplates"). + Body(podTemplate). + Do(). + Into(result) + return +} + +// Update takes the representation of a podTemplate and updates it. Returns the server's representation of the podTemplate, and an error, if there is any. +func (c *podTemplates) Update(podTemplate *v1.PodTemplate) (result *v1.PodTemplate, err error) { + result = &v1.PodTemplate{} + err = c.client.Put(). + Namespace(c.ns). + Resource("podtemplates"). + Name(podTemplate.Name). + Body(podTemplate). + Do(). + Into(result) + return +} + +// Delete takes name of the podTemplate and deletes it. Returns an error if one occurs. +func (c *podTemplates) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("podtemplates"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *podTemplates) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("podtemplates"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the podTemplate, and returns the corresponding podTemplate object, and an error if there is any. +func (c *podTemplates) Get(name string) (result *v1.PodTemplate, err error) { + result = &v1.PodTemplate{} + err = c.client.Get(). + Namespace(c.ns). + Resource("podtemplates"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of PodTemplates that match those selectors. +func (c *podTemplates) List(opts api.ListOptions) (result *v1.PodTemplateList, err error) { + result = &v1.PodTemplateList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("podtemplates"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested podTemplates. +func (c *podTemplates) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("podtemplates"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/replicationcontroller.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/replicationcontroller.go new file mode 100644 index 000000000..20bcc90c3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/replicationcontroller.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ReplicationControllersGetter has a method to return a ReplicationControllerInterface. +// A group's client should implement this interface. +type ReplicationControllersGetter interface { + ReplicationControllers(namespace string) ReplicationControllerInterface +} + +// ReplicationControllerInterface has methods to work with ReplicationController resources. +type ReplicationControllerInterface interface { + Create(*v1.ReplicationController) (*v1.ReplicationController, error) + Update(*v1.ReplicationController) (*v1.ReplicationController, error) + UpdateStatus(*v1.ReplicationController) (*v1.ReplicationController, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.ReplicationController, error) + List(opts api.ListOptions) (*v1.ReplicationControllerList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ReplicationControllerExpansion +} + +// replicationControllers implements ReplicationControllerInterface +type replicationControllers struct { + client *CoreClient + ns string +} + +// newReplicationControllers returns a ReplicationControllers +func newReplicationControllers(c *CoreClient, namespace string) *replicationControllers { + return &replicationControllers{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a replicationController and creates it. Returns the server's representation of the replicationController, and an error, if there is any. +func (c *replicationControllers) Create(replicationController *v1.ReplicationController) (result *v1.ReplicationController, err error) { + result = &v1.ReplicationController{} + err = c.client.Post(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Body(replicationController). + Do(). + Into(result) + return +} + +// Update takes the representation of a replicationController and updates it. Returns the server's representation of the replicationController, and an error, if there is any. +func (c *replicationControllers) Update(replicationController *v1.ReplicationController) (result *v1.ReplicationController, err error) { + result = &v1.ReplicationController{} + err = c.client.Put(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Name(replicationController.Name). + Body(replicationController). + Do(). + Into(result) + return +} + +func (c *replicationControllers) UpdateStatus(replicationController *v1.ReplicationController) (result *v1.ReplicationController, err error) { + result = &v1.ReplicationController{} + err = c.client.Put(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Name(replicationController.Name). + SubResource("status"). + Body(replicationController). + Do(). + Into(result) + return +} + +// Delete takes name of the replicationController and deletes it. Returns an error if one occurs. +func (c *replicationControllers) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *replicationControllers) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("replicationcontrollers"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the replicationController, and returns the corresponding replicationController object, and an error if there is any. +func (c *replicationControllers) Get(name string) (result *v1.ReplicationController, err error) { + result = &v1.ReplicationController{} + err = c.client.Get(). + Namespace(c.ns). + Resource("replicationcontrollers"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ReplicationControllers that match those selectors. +func (c *replicationControllers) List(opts api.ListOptions) (result *v1.ReplicationControllerList, err error) { + result = &v1.ReplicationControllerList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("replicationcontrollers"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested replicationControllers. +func (c *replicationControllers) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("replicationcontrollers"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/resourcequota.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/resourcequota.go new file mode 100644 index 000000000..466e963d6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/resourcequota.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ResourceQuotasGetter has a method to return a ResourceQuotaInterface. +// A group's client should implement this interface. +type ResourceQuotasGetter interface { + ResourceQuotas(namespace string) ResourceQuotaInterface +} + +// ResourceQuotaInterface has methods to work with ResourceQuota resources. +type ResourceQuotaInterface interface { + Create(*v1.ResourceQuota) (*v1.ResourceQuota, error) + Update(*v1.ResourceQuota) (*v1.ResourceQuota, error) + UpdateStatus(*v1.ResourceQuota) (*v1.ResourceQuota, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.ResourceQuota, error) + List(opts api.ListOptions) (*v1.ResourceQuotaList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ResourceQuotaExpansion +} + +// resourceQuotas implements ResourceQuotaInterface +type resourceQuotas struct { + client *CoreClient + ns string +} + +// newResourceQuotas returns a ResourceQuotas +func newResourceQuotas(c *CoreClient, namespace string) *resourceQuotas { + return &resourceQuotas{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a resourceQuota and creates it. Returns the server's representation of the resourceQuota, and an error, if there is any. +func (c *resourceQuotas) Create(resourceQuota *v1.ResourceQuota) (result *v1.ResourceQuota, err error) { + result = &v1.ResourceQuota{} + err = c.client.Post(). + Namespace(c.ns). + Resource("resourcequotas"). + Body(resourceQuota). + Do(). + Into(result) + return +} + +// Update takes the representation of a resourceQuota and updates it. Returns the server's representation of the resourceQuota, and an error, if there is any. +func (c *resourceQuotas) Update(resourceQuota *v1.ResourceQuota) (result *v1.ResourceQuota, err error) { + result = &v1.ResourceQuota{} + err = c.client.Put(). + Namespace(c.ns). + Resource("resourcequotas"). + Name(resourceQuota.Name). + Body(resourceQuota). + Do(). + Into(result) + return +} + +func (c *resourceQuotas) UpdateStatus(resourceQuota *v1.ResourceQuota) (result *v1.ResourceQuota, err error) { + result = &v1.ResourceQuota{} + err = c.client.Put(). + Namespace(c.ns). + Resource("resourcequotas"). + Name(resourceQuota.Name). + SubResource("status"). + Body(resourceQuota). + Do(). + Into(result) + return +} + +// Delete takes name of the resourceQuota and deletes it. Returns an error if one occurs. +func (c *resourceQuotas) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("resourcequotas"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *resourceQuotas) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("resourcequotas"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the resourceQuota, and returns the corresponding resourceQuota object, and an error if there is any. +func (c *resourceQuotas) Get(name string) (result *v1.ResourceQuota, err error) { + result = &v1.ResourceQuota{} + err = c.client.Get(). + Namespace(c.ns). + Resource("resourcequotas"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ResourceQuotas that match those selectors. +func (c *resourceQuotas) List(opts api.ListOptions) (result *v1.ResourceQuotaList, err error) { + result = &v1.ResourceQuotaList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("resourcequotas"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested resourceQuotas. +func (c *resourceQuotas) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("resourcequotas"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/secret.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/secret.go new file mode 100644 index 000000000..a95aa84f4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/secret.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// SecretsGetter has a method to return a SecretInterface. +// A group's client should implement this interface. +type SecretsGetter interface { + Secrets(namespace string) SecretInterface +} + +// SecretInterface has methods to work with Secret resources. +type SecretInterface interface { + Create(*v1.Secret) (*v1.Secret, error) + Update(*v1.Secret) (*v1.Secret, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.Secret, error) + List(opts api.ListOptions) (*v1.SecretList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + SecretExpansion +} + +// secrets implements SecretInterface +type secrets struct { + client *CoreClient + ns string +} + +// newSecrets returns a Secrets +func newSecrets(c *CoreClient, namespace string) *secrets { + return &secrets{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a secret and creates it. Returns the server's representation of the secret, and an error, if there is any. +func (c *secrets) Create(secret *v1.Secret) (result *v1.Secret, err error) { + result = &v1.Secret{} + err = c.client.Post(). + Namespace(c.ns). + Resource("secrets"). + Body(secret). + Do(). + Into(result) + return +} + +// Update takes the representation of a secret and updates it. Returns the server's representation of the secret, and an error, if there is any. +func (c *secrets) Update(secret *v1.Secret) (result *v1.Secret, err error) { + result = &v1.Secret{} + err = c.client.Put(). + Namespace(c.ns). + Resource("secrets"). + Name(secret.Name). + Body(secret). + Do(). + Into(result) + return +} + +// Delete takes name of the secret and deletes it. Returns an error if one occurs. +func (c *secrets) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("secrets"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *secrets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("secrets"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the secret, and returns the corresponding secret object, and an error if there is any. +func (c *secrets) Get(name string) (result *v1.Secret, err error) { + result = &v1.Secret{} + err = c.client.Get(). + Namespace(c.ns). + Resource("secrets"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Secrets that match those selectors. +func (c *secrets) List(opts api.ListOptions) (result *v1.SecretList, err error) { + result = &v1.SecretList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("secrets"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested secrets. +func (c *secrets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("secrets"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/service.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/service.go new file mode 100644 index 000000000..cd62b5d94 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/service.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ServicesGetter has a method to return a ServiceInterface. +// A group's client should implement this interface. +type ServicesGetter interface { + Services(namespace string) ServiceInterface +} + +// ServiceInterface has methods to work with Service resources. +type ServiceInterface interface { + Create(*v1.Service) (*v1.Service, error) + Update(*v1.Service) (*v1.Service, error) + UpdateStatus(*v1.Service) (*v1.Service, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.Service, error) + List(opts api.ListOptions) (*v1.ServiceList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ServiceExpansion +} + +// services implements ServiceInterface +type services struct { + client *CoreClient + ns string +} + +// newServices returns a Services +func newServices(c *CoreClient, namespace string) *services { + return &services{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a service and creates it. Returns the server's representation of the service, and an error, if there is any. +func (c *services) Create(service *v1.Service) (result *v1.Service, err error) { + result = &v1.Service{} + err = c.client.Post(). + Namespace(c.ns). + Resource("services"). + Body(service). + Do(). + Into(result) + return +} + +// Update takes the representation of a service and updates it. Returns the server's representation of the service, and an error, if there is any. +func (c *services) Update(service *v1.Service) (result *v1.Service, err error) { + result = &v1.Service{} + err = c.client.Put(). + Namespace(c.ns). + Resource("services"). + Name(service.Name). + Body(service). + Do(). + Into(result) + return +} + +func (c *services) UpdateStatus(service *v1.Service) (result *v1.Service, err error) { + result = &v1.Service{} + err = c.client.Put(). + Namespace(c.ns). + Resource("services"). + Name(service.Name). + SubResource("status"). + Body(service). + Do(). + Into(result) + return +} + +// Delete takes name of the service and deletes it. Returns an error if one occurs. +func (c *services) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("services"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *services) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("services"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the service, and returns the corresponding service object, and an error if there is any. +func (c *services) Get(name string) (result *v1.Service, err error) { + result = &v1.Service{} + err = c.client.Get(). + Namespace(c.ns). + Resource("services"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Services that match those selectors. +func (c *services) List(opts api.ListOptions) (result *v1.ServiceList, err error) { + result = &v1.ServiceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("services"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested services. +func (c *services) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("services"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/service_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/service_expansion.go new file mode 100644 index 000000000..b4300483b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/service_expansion.go @@ -0,0 +1,41 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/util/net" +) + +// The ServiceExpansion interface allows manually adding extra methods to the ServiceInterface. +type ServiceExpansion interface { + ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper +} + +// ProxyGet returns a response of the service by calling it through the proxy. +func (c *services) ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper { + request := c.client.Get(). + Prefix("proxy"). + Namespace(c.ns). + Resource("services"). + Name(net.JoinSchemeNamePort(scheme, name, port)). + Suffix(path) + for k, v := range params { + request = request.Param(k, v) + } + return request +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/serviceaccount.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/serviceaccount.go new file mode 100644 index 000000000..eb0b258fa --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/core/v1/serviceaccount.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1 "k8s.io/kubernetes/pkg/api/v1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ServiceAccountsGetter has a method to return a ServiceAccountInterface. +// A group's client should implement this interface. +type ServiceAccountsGetter interface { + ServiceAccounts(namespace string) ServiceAccountInterface +} + +// ServiceAccountInterface has methods to work with ServiceAccount resources. +type ServiceAccountInterface interface { + Create(*v1.ServiceAccount) (*v1.ServiceAccount, error) + Update(*v1.ServiceAccount) (*v1.ServiceAccount, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1.ServiceAccount, error) + List(opts api.ListOptions) (*v1.ServiceAccountList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ServiceAccountExpansion +} + +// serviceAccounts implements ServiceAccountInterface +type serviceAccounts struct { + client *CoreClient + ns string +} + +// newServiceAccounts returns a ServiceAccounts +func newServiceAccounts(c *CoreClient, namespace string) *serviceAccounts { + return &serviceAccounts{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a serviceAccount and creates it. Returns the server's representation of the serviceAccount, and an error, if there is any. +func (c *serviceAccounts) Create(serviceAccount *v1.ServiceAccount) (result *v1.ServiceAccount, err error) { + result = &v1.ServiceAccount{} + err = c.client.Post(). + Namespace(c.ns). + Resource("serviceaccounts"). + Body(serviceAccount). + Do(). + Into(result) + return +} + +// Update takes the representation of a serviceAccount and updates it. Returns the server's representation of the serviceAccount, and an error, if there is any. +func (c *serviceAccounts) Update(serviceAccount *v1.ServiceAccount) (result *v1.ServiceAccount, err error) { + result = &v1.ServiceAccount{} + err = c.client.Put(). + Namespace(c.ns). + Resource("serviceaccounts"). + Name(serviceAccount.Name). + Body(serviceAccount). + Do(). + Into(result) + return +} + +// Delete takes name of the serviceAccount and deletes it. Returns an error if one occurs. +func (c *serviceAccounts) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("serviceaccounts"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *serviceAccounts) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("serviceaccounts"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the serviceAccount, and returns the corresponding serviceAccount object, and an error if there is any. +func (c *serviceAccounts) Get(name string) (result *v1.ServiceAccount, err error) { + result = &v1.ServiceAccount{} + err = c.client.Get(). + Namespace(c.ns). + Resource("serviceaccounts"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ServiceAccounts that match those selectors. +func (c *serviceAccounts) List(opts api.ListOptions) (result *v1.ServiceAccountList, err error) { + result = &v1.ServiceAccountList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("serviceaccounts"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested serviceAccounts. +func (c *serviceAccounts) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("serviceaccounts"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/daemonset.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/daemonset.go index 96dae5835..cc9ac7922 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/daemonset.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/daemonset.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/deployment.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/deployment.go index 3b995c021..7606a9e79 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/deployment.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/deployment.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/doc.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/doc.go index 930017040..3c8dbaac6 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/doc.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/doc.go @@ -14,5 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + // Package unversioned has the automatically generated clients for unversioned resources. package unversioned diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/extensions_client.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/extensions_client.go index 6780a5c03..27ceffad2 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/extensions_client.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/extensions_client.go @@ -14,12 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( api "k8s.io/kubernetes/pkg/api" registered "k8s.io/kubernetes/pkg/apimachinery/registered" - unversioned "k8s.io/kubernetes/pkg/client/unversioned" + restclient "k8s.io/kubernetes/pkg/client/restclient" ) type ExtensionsInterface interface { @@ -35,7 +37,7 @@ type ExtensionsInterface interface { // ExtensionsClient is used to interact with features provided by the Extensions group. type ExtensionsClient struct { - *unversioned.RESTClient + *restclient.RESTClient } func (c *ExtensionsClient) DaemonSets(namespace string) DaemonSetInterface { @@ -71,12 +73,12 @@ func (c *ExtensionsClient) ThirdPartyResources(namespace string) ThirdPartyResou } // NewForConfig creates a new ExtensionsClient for the given config. -func NewForConfig(c *unversioned.Config) (*ExtensionsClient, error) { +func NewForConfig(c *restclient.Config) (*ExtensionsClient, error) { config := *c if err := setConfigDefaults(&config); err != nil { return nil, err } - client, err := unversioned.RESTClientFor(&config) + client, err := restclient.RESTClientFor(&config) if err != nil { return nil, err } @@ -85,7 +87,7 @@ func NewForConfig(c *unversioned.Config) (*ExtensionsClient, error) { // NewForConfigOrDie creates a new ExtensionsClient for the given config and // panics if there is an error in the config. -func NewForConfigOrDie(c *unversioned.Config) *ExtensionsClient { +func NewForConfigOrDie(c *restclient.Config) *ExtensionsClient { client, err := NewForConfig(c) if err != nil { panic(err) @@ -94,11 +96,11 @@ func NewForConfigOrDie(c *unversioned.Config) *ExtensionsClient { } // New creates a new ExtensionsClient for the given RESTClient. -func New(c *unversioned.RESTClient) *ExtensionsClient { +func New(c *restclient.RESTClient) *ExtensionsClient { return &ExtensionsClient{c} } -func setConfigDefaults(config *unversioned.Config) error { +func setConfigDefaults(config *restclient.Config) error { // if extensions group is not registered, return an error g, err := registered.Group("extensions") if err != nil { @@ -106,7 +108,7 @@ func setConfigDefaults(config *unversioned.Config) error { } config.APIPath = "/apis" if config.UserAgent == "" { - config.UserAgent = unversioned.DefaultKubernetesUserAgent() + config.UserAgent = restclient.DefaultKubernetesUserAgent() } // TODO: Unconditionally set the config.Version, until we fix the config. //if config.Version == "" { diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/doc.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/doc.go index dd6d4da71..ea86647e2 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/doc.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/doc.go @@ -14,5 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + // Package fake has the automatically generated clients. package fake diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_daemonset.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_daemonset.go index 7de9f927f..b7af8804f 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_daemonset.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_daemonset.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_deployment.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_deployment.go index 748968a9d..6ce396e8d 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_deployment.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_deployment.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_extensions_client.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_extensions_client.go index 2f7fb0423..22a720694 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_extensions_client.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_extensions_client.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_horizontalpodautoscaler.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_horizontalpodautoscaler.go index 71b5cf322..5384b69a8 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_horizontalpodautoscaler.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_horizontalpodautoscaler.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_ingress.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_ingress.go index a331644e4..2c2123aa6 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_ingress.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_ingress.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_job.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_job.go index c1875c006..2cd903d9c 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_job.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_job.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_replicaset.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_replicaset.go index d861326b7..61280d18a 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_replicaset.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_replicaset.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_scale.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_scale.go index d2cfc5f7b..f22cfb656 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_scale.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_scale.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake // FakeScales implements ScaleInterface diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_thirdpartyresource.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_thirdpartyresource.go index 9a005d570..a18970fde 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_thirdpartyresource.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/fake/fake_thirdpartyresource.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package fake import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/horizontalpodautoscaler.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/horizontalpodautoscaler.go index 2cffcee46..171ffde3c 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/horizontalpodautoscaler.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/horizontalpodautoscaler.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/ingress.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/ingress.go index a9d950eae..eb8896b17 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/ingress.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/ingress.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/job.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/job.go index 04d0d1282..df88328dc 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/job.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/job.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/replicaset.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/replicaset.go index 6257fd898..a9ef43149 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/replicaset.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/replicaset.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/scale.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/scale.go index 7e54bc347..e568ad483 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/scale.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/scale.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned // ScalesGetter has a method to return a ScaleInterface. diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/thirdpartyresource.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/thirdpartyresource.go index 0f1026fab..9eaad859a 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/thirdpartyresource.go +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/unversioned/thirdpartyresource.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// This file is generated by client-gen with the default arguments. + package unversioned import ( diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/daemonset.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/daemonset.go new file mode 100644 index 000000000..ecbece591 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/daemonset.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// DaemonSetsGetter has a method to return a DaemonSetInterface. +// A group's client should implement this interface. +type DaemonSetsGetter interface { + DaemonSets(namespace string) DaemonSetInterface +} + +// DaemonSetInterface has methods to work with DaemonSet resources. +type DaemonSetInterface interface { + Create(*v1beta1.DaemonSet) (*v1beta1.DaemonSet, error) + Update(*v1beta1.DaemonSet) (*v1beta1.DaemonSet, error) + UpdateStatus(*v1beta1.DaemonSet) (*v1beta1.DaemonSet, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.DaemonSet, error) + List(opts api.ListOptions) (*v1beta1.DaemonSetList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + DaemonSetExpansion +} + +// daemonSets implements DaemonSetInterface +type daemonSets struct { + client *ExtensionsClient + ns string +} + +// newDaemonSets returns a DaemonSets +func newDaemonSets(c *ExtensionsClient, namespace string) *daemonSets { + return &daemonSets{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a daemonSet and creates it. Returns the server's representation of the daemonSet, and an error, if there is any. +func (c *daemonSets) Create(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { + result = &v1beta1.DaemonSet{} + err = c.client.Post(). + Namespace(c.ns). + Resource("daemonsets"). + Body(daemonSet). + Do(). + Into(result) + return +} + +// Update takes the representation of a daemonSet and updates it. Returns the server's representation of the daemonSet, and an error, if there is any. +func (c *daemonSets) Update(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { + result = &v1beta1.DaemonSet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("daemonsets"). + Name(daemonSet.Name). + Body(daemonSet). + Do(). + Into(result) + return +} + +func (c *daemonSets) UpdateStatus(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { + result = &v1beta1.DaemonSet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("daemonsets"). + Name(daemonSet.Name). + SubResource("status"). + Body(daemonSet). + Do(). + Into(result) + return +} + +// Delete takes name of the daemonSet and deletes it. Returns an error if one occurs. +func (c *daemonSets) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("daemonsets"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *daemonSets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("daemonsets"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the daemonSet, and returns the corresponding daemonSet object, and an error if there is any. +func (c *daemonSets) Get(name string) (result *v1beta1.DaemonSet, err error) { + result = &v1beta1.DaemonSet{} + err = c.client.Get(). + Namespace(c.ns). + Resource("daemonsets"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of DaemonSets that match those selectors. +func (c *daemonSets) List(opts api.ListOptions) (result *v1beta1.DaemonSetList, err error) { + result = &v1beta1.DaemonSetList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("daemonsets"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested daemonSets. +func (c *daemonSets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("daemonsets"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/deployment.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/deployment.go new file mode 100644 index 000000000..7cc3ff9d3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/deployment.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// DeploymentsGetter has a method to return a DeploymentInterface. +// A group's client should implement this interface. +type DeploymentsGetter interface { + Deployments(namespace string) DeploymentInterface +} + +// DeploymentInterface has methods to work with Deployment resources. +type DeploymentInterface interface { + Create(*v1beta1.Deployment) (*v1beta1.Deployment, error) + Update(*v1beta1.Deployment) (*v1beta1.Deployment, error) + UpdateStatus(*v1beta1.Deployment) (*v1beta1.Deployment, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.Deployment, error) + List(opts api.ListOptions) (*v1beta1.DeploymentList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + DeploymentExpansion +} + +// deployments implements DeploymentInterface +type deployments struct { + client *ExtensionsClient + ns string +} + +// newDeployments returns a Deployments +func newDeployments(c *ExtensionsClient, namespace string) *deployments { + return &deployments{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a deployment and creates it. Returns the server's representation of the deployment, and an error, if there is any. +func (c *deployments) Create(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { + result = &v1beta1.Deployment{} + err = c.client.Post(). + Namespace(c.ns). + Resource("deployments"). + Body(deployment). + Do(). + Into(result) + return +} + +// Update takes the representation of a deployment and updates it. Returns the server's representation of the deployment, and an error, if there is any. +func (c *deployments) Update(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { + result = &v1beta1.Deployment{} + err = c.client.Put(). + Namespace(c.ns). + Resource("deployments"). + Name(deployment.Name). + Body(deployment). + Do(). + Into(result) + return +} + +func (c *deployments) UpdateStatus(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { + result = &v1beta1.Deployment{} + err = c.client.Put(). + Namespace(c.ns). + Resource("deployments"). + Name(deployment.Name). + SubResource("status"). + Body(deployment). + Do(). + Into(result) + return +} + +// Delete takes name of the deployment and deletes it. Returns an error if one occurs. +func (c *deployments) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("deployments"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *deployments) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("deployments"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the deployment, and returns the corresponding deployment object, and an error if there is any. +func (c *deployments) Get(name string) (result *v1beta1.Deployment, err error) { + result = &v1beta1.Deployment{} + err = c.client.Get(). + Namespace(c.ns). + Resource("deployments"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Deployments that match those selectors. +func (c *deployments) List(opts api.ListOptions) (result *v1beta1.DeploymentList, err error) { + result = &v1beta1.DeploymentList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("deployments"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested deployments. +func (c *deployments) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("deployments"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/deployment_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/deployment_expansion.go new file mode 100644 index 000000000..0c3ff6367 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/deployment_expansion.go @@ -0,0 +1,29 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + +// The DeploymentExpansion interface allows manually adding extra methods to the DeploymentInterface. +type DeploymentExpansion interface { + Rollback(*v1beta1.DeploymentRollback) error +} + +// Rollback applied the provided DeploymentRollback to the named deployment in the current namespace. +func (c *deployments) Rollback(deploymentRollback *v1beta1.DeploymentRollback) error { + return c.client.Post().Namespace(c.ns).Resource("deployments").Name(deploymentRollback.Name).SubResource("rollback").Body(deploymentRollback).Do().Error() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/doc.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/doc.go new file mode 100644 index 000000000..0df36e4fa --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned has the automatically generated clients for unversioned resources. +package v1beta1 diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/extensions_client.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/extensions_client.go new file mode 100644 index 000000000..af3348a33 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/extensions_client.go @@ -0,0 +1,125 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + registered "k8s.io/kubernetes/pkg/apimachinery/registered" + restclient "k8s.io/kubernetes/pkg/client/restclient" +) + +type ExtensionsInterface interface { + DaemonSetsGetter + DeploymentsGetter + HorizontalPodAutoscalersGetter + IngressesGetter + JobsGetter + ReplicaSetsGetter + ScalesGetter + ThirdPartyResourcesGetter +} + +// ExtensionsClient is used to interact with features provided by the Extensions group. +type ExtensionsClient struct { + *restclient.RESTClient +} + +func (c *ExtensionsClient) DaemonSets(namespace string) DaemonSetInterface { + return newDaemonSets(c, namespace) +} + +func (c *ExtensionsClient) Deployments(namespace string) DeploymentInterface { + return newDeployments(c, namespace) +} + +func (c *ExtensionsClient) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface { + return newHorizontalPodAutoscalers(c, namespace) +} + +func (c *ExtensionsClient) Ingresses(namespace string) IngressInterface { + return newIngresses(c, namespace) +} + +func (c *ExtensionsClient) Jobs(namespace string) JobInterface { + return newJobs(c, namespace) +} + +func (c *ExtensionsClient) ReplicaSets(namespace string) ReplicaSetInterface { + return newReplicaSets(c, namespace) +} + +func (c *ExtensionsClient) Scales(namespace string) ScaleInterface { + return newScales(c, namespace) +} + +func (c *ExtensionsClient) ThirdPartyResources(namespace string) ThirdPartyResourceInterface { + return newThirdPartyResources(c, namespace) +} + +// NewForConfig creates a new ExtensionsClient for the given config. +func NewForConfig(c *restclient.Config) (*ExtensionsClient, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := restclient.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &ExtensionsClient{client}, nil +} + +// NewForConfigOrDie creates a new ExtensionsClient for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *restclient.Config) *ExtensionsClient { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new ExtensionsClient for the given RESTClient. +func New(c *restclient.RESTClient) *ExtensionsClient { + return &ExtensionsClient{c} +} + +func setConfigDefaults(config *restclient.Config) error { + // if extensions group is not registered, return an error + g, err := registered.Group("extensions") + if err != nil { + return err + } + config.APIPath = "/apis" + if config.UserAgent == "" { + config.UserAgent = restclient.DefaultKubernetesUserAgent() + } + // TODO: Unconditionally set the config.Version, until we fix the config. + //if config.Version == "" { + copyGroupVersion := g.GroupVersion + config.GroupVersion = ©GroupVersion + //} + + config.Codec = api.Codecs.LegacyCodec(*config.GroupVersion) + if config.QPS == 0 { + config.QPS = 5 + } + if config.Burst == 0 { + config.Burst = 10 + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/doc.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/doc.go new file mode 100644 index 000000000..dd6d4da71 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake has the automatically generated clients. +package fake diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_daemonset.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_daemonset.go new file mode 100644 index 000000000..8ae468dc5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_daemonset.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeDaemonSets implements DaemonSetInterface +type FakeDaemonSets struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeDaemonSets) Create(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("daemonsets", c.ns, daemonSet), &v1beta1.DaemonSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.DaemonSet), err +} + +func (c *FakeDaemonSets) Update(daemonSet *v1beta1.DaemonSet) (result *v1beta1.DaemonSet, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("daemonsets", c.ns, daemonSet), &v1beta1.DaemonSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.DaemonSet), err +} + +func (c *FakeDaemonSets) UpdateStatus(daemonSet *v1beta1.DaemonSet) (*v1beta1.DaemonSet, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("daemonsets", "status", c.ns, daemonSet), &v1beta1.DaemonSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.DaemonSet), err +} + +func (c *FakeDaemonSets) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("daemonsets", c.ns, name), &v1beta1.DaemonSet{}) + + return err +} + +func (c *FakeDaemonSets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("daemonsets", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.DaemonSetList{}) + return err +} + +func (c *FakeDaemonSets) Get(name string) (result *v1beta1.DaemonSet, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("daemonsets", c.ns, name), &v1beta1.DaemonSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.DaemonSet), err +} + +func (c *FakeDaemonSets) List(opts api.ListOptions) (result *v1beta1.DaemonSetList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("daemonsets", c.ns, opts), &v1beta1.DaemonSetList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.DaemonSetList{} + for _, item := range obj.(*v1beta1.DaemonSetList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested daemonSets. +func (c *FakeDaemonSets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("daemonsets", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_deployment.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_deployment.go new file mode 100644 index 000000000..739a4f31d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_deployment.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeDeployments implements DeploymentInterface +type FakeDeployments struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeDeployments) Create(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("deployments", c.ns, deployment), &v1beta1.Deployment{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Deployment), err +} + +func (c *FakeDeployments) Update(deployment *v1beta1.Deployment) (result *v1beta1.Deployment, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("deployments", c.ns, deployment), &v1beta1.Deployment{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Deployment), err +} + +func (c *FakeDeployments) UpdateStatus(deployment *v1beta1.Deployment) (*v1beta1.Deployment, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("deployments", "status", c.ns, deployment), &v1beta1.Deployment{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Deployment), err +} + +func (c *FakeDeployments) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("deployments", c.ns, name), &v1beta1.Deployment{}) + + return err +} + +func (c *FakeDeployments) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("deployments", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.DeploymentList{}) + return err +} + +func (c *FakeDeployments) Get(name string) (result *v1beta1.Deployment, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("deployments", c.ns, name), &v1beta1.Deployment{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Deployment), err +} + +func (c *FakeDeployments) List(opts api.ListOptions) (result *v1beta1.DeploymentList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("deployments", c.ns, opts), &v1beta1.DeploymentList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.DeploymentList{} + for _, item := range obj.(*v1beta1.DeploymentList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested deployments. +func (c *FakeDeployments) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("deployments", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_deployment_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_deployment_expansion.go new file mode 100644 index 000000000..5d0aff06d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_deployment_expansion.go @@ -0,0 +1,33 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeDeployments) Rollback(deploymentRollback *v1beta1.DeploymentRollback) error { + action := core.CreateActionImpl{} + action.Verb = "create" + action.Resource = "deployments" + action.Subresource = "rollback" + action.Object = deploymentRollback + + _, err := c.Fake.Invokes(action, deploymentRollback) + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_extensions_client.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_extensions_client.go new file mode 100644 index 000000000..82487e685 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_extensions_client.go @@ -0,0 +1,58 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + core "k8s.io/kubernetes/pkg/client/testing/core" + v1beta1 "k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1" +) + +type FakeExtensions struct { + *core.Fake +} + +func (c *FakeExtensions) DaemonSets(namespace string) v1beta1.DaemonSetInterface { + return &FakeDaemonSets{c, namespace} +} + +func (c *FakeExtensions) Deployments(namespace string) v1beta1.DeploymentInterface { + return &FakeDeployments{c, namespace} +} + +func (c *FakeExtensions) HorizontalPodAutoscalers(namespace string) v1beta1.HorizontalPodAutoscalerInterface { + return &FakeHorizontalPodAutoscalers{c, namespace} +} + +func (c *FakeExtensions) Ingresses(namespace string) v1beta1.IngressInterface { + return &FakeIngresses{c, namespace} +} + +func (c *FakeExtensions) Jobs(namespace string) v1beta1.JobInterface { + return &FakeJobs{c, namespace} +} + +func (c *FakeExtensions) ReplicaSets(namespace string) v1beta1.ReplicaSetInterface { + return &FakeReplicaSets{c, namespace} +} + +func (c *FakeExtensions) Scales(namespace string) v1beta1.ScaleInterface { + return &FakeScales{c, namespace} +} + +func (c *FakeExtensions) ThirdPartyResources(namespace string) v1beta1.ThirdPartyResourceInterface { + return &FakeThirdPartyResources{c, namespace} +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_horizontalpodautoscaler.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_horizontalpodautoscaler.go new file mode 100644 index 000000000..1fef97ea5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_horizontalpodautoscaler.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeHorizontalPodAutoscalers implements HorizontalPodAutoscalerInterface +type FakeHorizontalPodAutoscalers struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeHorizontalPodAutoscalers) Create(horizontalPodAutoscaler *v1beta1.HorizontalPodAutoscaler) (result *v1beta1.HorizontalPodAutoscaler, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("horizontalpodautoscalers", c.ns, horizontalPodAutoscaler), &v1beta1.HorizontalPodAutoscaler{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalers) Update(horizontalPodAutoscaler *v1beta1.HorizontalPodAutoscaler) (result *v1beta1.HorizontalPodAutoscaler, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("horizontalpodautoscalers", c.ns, horizontalPodAutoscaler), &v1beta1.HorizontalPodAutoscaler{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalers) UpdateStatus(horizontalPodAutoscaler *v1beta1.HorizontalPodAutoscaler) (*v1beta1.HorizontalPodAutoscaler, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("horizontalpodautoscalers", "status", c.ns, horizontalPodAutoscaler), &v1beta1.HorizontalPodAutoscaler{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalers) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("horizontalpodautoscalers", c.ns, name), &v1beta1.HorizontalPodAutoscaler{}) + + return err +} + +func (c *FakeHorizontalPodAutoscalers) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("horizontalpodautoscalers", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.HorizontalPodAutoscalerList{}) + return err +} + +func (c *FakeHorizontalPodAutoscalers) Get(name string) (result *v1beta1.HorizontalPodAutoscaler, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("horizontalpodautoscalers", c.ns, name), &v1beta1.HorizontalPodAutoscaler{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalers) List(opts api.ListOptions) (result *v1beta1.HorizontalPodAutoscalerList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("horizontalpodautoscalers", c.ns, opts), &v1beta1.HorizontalPodAutoscalerList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.HorizontalPodAutoscalerList{} + for _, item := range obj.(*v1beta1.HorizontalPodAutoscalerList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers. +func (c *FakeHorizontalPodAutoscalers) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("horizontalpodautoscalers", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_ingress.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_ingress.go new file mode 100644 index 000000000..b9c1ab096 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_ingress.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeIngresses implements IngressInterface +type FakeIngresses struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeIngresses) Create(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("ingresses", c.ns, ingress), &v1beta1.Ingress{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Ingress), err +} + +func (c *FakeIngresses) Update(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("ingresses", c.ns, ingress), &v1beta1.Ingress{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Ingress), err +} + +func (c *FakeIngresses) UpdateStatus(ingress *v1beta1.Ingress) (*v1beta1.Ingress, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("ingresses", "status", c.ns, ingress), &v1beta1.Ingress{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Ingress), err +} + +func (c *FakeIngresses) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("ingresses", c.ns, name), &v1beta1.Ingress{}) + + return err +} + +func (c *FakeIngresses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("ingresses", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.IngressList{}) + return err +} + +func (c *FakeIngresses) Get(name string) (result *v1beta1.Ingress, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("ingresses", c.ns, name), &v1beta1.Ingress{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Ingress), err +} + +func (c *FakeIngresses) List(opts api.ListOptions) (result *v1beta1.IngressList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("ingresses", c.ns, opts), &v1beta1.IngressList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.IngressList{} + for _, item := range obj.(*v1beta1.IngressList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested ingresses. +func (c *FakeIngresses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("ingresses", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_job.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_job.go new file mode 100644 index 000000000..21610e2bc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_job.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeJobs implements JobInterface +type FakeJobs struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeJobs) Create(job *v1beta1.Job) (result *v1beta1.Job, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("jobs", c.ns, job), &v1beta1.Job{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Job), err +} + +func (c *FakeJobs) Update(job *v1beta1.Job) (result *v1beta1.Job, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("jobs", c.ns, job), &v1beta1.Job{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Job), err +} + +func (c *FakeJobs) UpdateStatus(job *v1beta1.Job) (*v1beta1.Job, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("jobs", "status", c.ns, job), &v1beta1.Job{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Job), err +} + +func (c *FakeJobs) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("jobs", c.ns, name), &v1beta1.Job{}) + + return err +} + +func (c *FakeJobs) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("jobs", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.JobList{}) + return err +} + +func (c *FakeJobs) Get(name string) (result *v1beta1.Job, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("jobs", c.ns, name), &v1beta1.Job{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.Job), err +} + +func (c *FakeJobs) List(opts api.ListOptions) (result *v1beta1.JobList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("jobs", c.ns, opts), &v1beta1.JobList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.JobList{} + for _, item := range obj.(*v1beta1.JobList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested jobs. +func (c *FakeJobs) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("jobs", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_replicaset.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_replicaset.go new file mode 100644 index 000000000..f785deced --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_replicaset.go @@ -0,0 +1,113 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeReplicaSets implements ReplicaSetInterface +type FakeReplicaSets struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeReplicaSets) Create(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("replicasets", c.ns, replicaSet), &v1beta1.ReplicaSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ReplicaSet), err +} + +func (c *FakeReplicaSets) Update(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("replicasets", c.ns, replicaSet), &v1beta1.ReplicaSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ReplicaSet), err +} + +func (c *FakeReplicaSets) UpdateStatus(replicaSet *v1beta1.ReplicaSet) (*v1beta1.ReplicaSet, error) { + obj, err := c.Fake. + Invokes(core.NewUpdateSubresourceAction("replicasets", "status", c.ns, replicaSet), &v1beta1.ReplicaSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ReplicaSet), err +} + +func (c *FakeReplicaSets) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("replicasets", c.ns, name), &v1beta1.ReplicaSet{}) + + return err +} + +func (c *FakeReplicaSets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("replicasets", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.ReplicaSetList{}) + return err +} + +func (c *FakeReplicaSets) Get(name string) (result *v1beta1.ReplicaSet, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("replicasets", c.ns, name), &v1beta1.ReplicaSet{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ReplicaSet), err +} + +func (c *FakeReplicaSets) List(opts api.ListOptions) (result *v1beta1.ReplicaSetList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("replicasets", c.ns, opts), &v1beta1.ReplicaSetList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.ReplicaSetList{} + for _, item := range obj.(*v1beta1.ReplicaSetList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested replicaSets. +func (c *FakeReplicaSets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("replicasets", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_scale.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_scale.go new file mode 100644 index 000000000..d2cfc5f7b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_scale.go @@ -0,0 +1,23 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +// FakeScales implements ScaleInterface +type FakeScales struct { + Fake *FakeExtensions + ns string +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_scale_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_scale_expansion.go new file mode 100644 index 000000000..ea6f5ab31 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_scale_expansion.go @@ -0,0 +1,46 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/client/testing/core" +) + +func (c *FakeScales) Get(kind string, name string) (result *v1beta1.Scale, err error) { + action := core.GetActionImpl{} + action.Verb = "get" + action.Namespace = c.ns + action.Resource = kind + action.Subresource = "scale" + action.Name = name + obj, err := c.Fake.Invokes(action, &v1beta1.Scale{}) + result = obj.(*v1beta1.Scale) + return +} + +func (c *FakeScales) Update(kind string, scale *v1beta1.Scale) (result *v1beta1.Scale, err error) { + action := core.UpdateActionImpl{} + action.Verb = "update" + action.Namespace = c.ns + action.Resource = kind + action.Subresource = "scale" + action.Object = scale + obj, err := c.Fake.Invokes(action, scale) + result = obj.(*v1beta1.Scale) + return +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_thirdpartyresource.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_thirdpartyresource.go new file mode 100644 index 000000000..364d1efb9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/fake/fake_thirdpartyresource.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + core "k8s.io/kubernetes/pkg/client/testing/core" + labels "k8s.io/kubernetes/pkg/labels" + watch "k8s.io/kubernetes/pkg/watch" +) + +// FakeThirdPartyResources implements ThirdPartyResourceInterface +type FakeThirdPartyResources struct { + Fake *FakeExtensions + ns string +} + +func (c *FakeThirdPartyResources) Create(thirdPartyResource *v1beta1.ThirdPartyResource) (result *v1beta1.ThirdPartyResource, err error) { + obj, err := c.Fake. + Invokes(core.NewCreateAction("thirdpartyresources", c.ns, thirdPartyResource), &v1beta1.ThirdPartyResource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ThirdPartyResource), err +} + +func (c *FakeThirdPartyResources) Update(thirdPartyResource *v1beta1.ThirdPartyResource) (result *v1beta1.ThirdPartyResource, err error) { + obj, err := c.Fake. + Invokes(core.NewUpdateAction("thirdpartyresources", c.ns, thirdPartyResource), &v1beta1.ThirdPartyResource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ThirdPartyResource), err +} + +func (c *FakeThirdPartyResources) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake. + Invokes(core.NewDeleteAction("thirdpartyresources", c.ns, name), &v1beta1.ThirdPartyResource{}) + + return err +} + +func (c *FakeThirdPartyResources) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + action := core.NewDeleteCollectionAction("thirdpartyresources", c.ns, listOptions) + + _, err := c.Fake.Invokes(action, &v1beta1.ThirdPartyResourceList{}) + return err +} + +func (c *FakeThirdPartyResources) Get(name string) (result *v1beta1.ThirdPartyResource, err error) { + obj, err := c.Fake. + Invokes(core.NewGetAction("thirdpartyresources", c.ns, name), &v1beta1.ThirdPartyResource{}) + + if obj == nil { + return nil, err + } + return obj.(*v1beta1.ThirdPartyResource), err +} + +func (c *FakeThirdPartyResources) List(opts api.ListOptions) (result *v1beta1.ThirdPartyResourceList, err error) { + obj, err := c.Fake. + Invokes(core.NewListAction("thirdpartyresources", c.ns, opts), &v1beta1.ThirdPartyResourceList{}) + + if obj == nil { + return nil, err + } + + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &v1beta1.ThirdPartyResourceList{} + for _, item := range obj.(*v1beta1.ThirdPartyResourceList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested thirdPartyResources. +func (c *FakeThirdPartyResources) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(core.NewWatchAction("thirdpartyresources", c.ns, opts)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/generated_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/generated_expansion.go new file mode 100644 index 000000000..97c6a1c06 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/generated_expansion.go @@ -0,0 +1,29 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +type DaemonSetExpansion interface{} + +type HorizontalPodAutoscalerExpansion interface{} + +type IngressExpansion interface{} + +type JobExpansion interface{} + +type ThirdPartyResourceExpansion interface{} + +type ReplicaSetExpansion interface{} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/horizontalpodautoscaler.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/horizontalpodautoscaler.go new file mode 100644 index 000000000..93b486b89 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/horizontalpodautoscaler.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// HorizontalPodAutoscalersGetter has a method to return a HorizontalPodAutoscalerInterface. +// A group's client should implement this interface. +type HorizontalPodAutoscalersGetter interface { + HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface +} + +// HorizontalPodAutoscalerInterface has methods to work with HorizontalPodAutoscaler resources. +type HorizontalPodAutoscalerInterface interface { + Create(*v1beta1.HorizontalPodAutoscaler) (*v1beta1.HorizontalPodAutoscaler, error) + Update(*v1beta1.HorizontalPodAutoscaler) (*v1beta1.HorizontalPodAutoscaler, error) + UpdateStatus(*v1beta1.HorizontalPodAutoscaler) (*v1beta1.HorizontalPodAutoscaler, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.HorizontalPodAutoscaler, error) + List(opts api.ListOptions) (*v1beta1.HorizontalPodAutoscalerList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + HorizontalPodAutoscalerExpansion +} + +// horizontalPodAutoscalers implements HorizontalPodAutoscalerInterface +type horizontalPodAutoscalers struct { + client *ExtensionsClient + ns string +} + +// newHorizontalPodAutoscalers returns a HorizontalPodAutoscalers +func newHorizontalPodAutoscalers(c *ExtensionsClient, namespace string) *horizontalPodAutoscalers { + return &horizontalPodAutoscalers{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a horizontalPodAutoscaler and creates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any. +func (c *horizontalPodAutoscalers) Create(horizontalPodAutoscaler *v1beta1.HorizontalPodAutoscaler) (result *v1beta1.HorizontalPodAutoscaler, err error) { + result = &v1beta1.HorizontalPodAutoscaler{} + err = c.client.Post(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Body(horizontalPodAutoscaler). + Do(). + Into(result) + return +} + +// Update takes the representation of a horizontalPodAutoscaler and updates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if there is any. +func (c *horizontalPodAutoscalers) Update(horizontalPodAutoscaler *v1beta1.HorizontalPodAutoscaler) (result *v1beta1.HorizontalPodAutoscaler, err error) { + result = &v1beta1.HorizontalPodAutoscaler{} + err = c.client.Put(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Name(horizontalPodAutoscaler.Name). + Body(horizontalPodAutoscaler). + Do(). + Into(result) + return +} + +func (c *horizontalPodAutoscalers) UpdateStatus(horizontalPodAutoscaler *v1beta1.HorizontalPodAutoscaler) (result *v1beta1.HorizontalPodAutoscaler, err error) { + result = &v1beta1.HorizontalPodAutoscaler{} + err = c.client.Put(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Name(horizontalPodAutoscaler.Name). + SubResource("status"). + Body(horizontalPodAutoscaler). + Do(). + Into(result) + return +} + +// Delete takes name of the horizontalPodAutoscaler and deletes it. Returns an error if one occurs. +func (c *horizontalPodAutoscalers) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *horizontalPodAutoscalers) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the horizontalPodAutoscaler, and returns the corresponding horizontalPodAutoscaler object, and an error if there is any. +func (c *horizontalPodAutoscalers) Get(name string) (result *v1beta1.HorizontalPodAutoscaler, err error) { + result = &v1beta1.HorizontalPodAutoscaler{} + err = c.client.Get(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of HorizontalPodAutoscalers that match those selectors. +func (c *horizontalPodAutoscalers) List(opts api.ListOptions) (result *v1beta1.HorizontalPodAutoscalerList, err error) { + result = &v1beta1.HorizontalPodAutoscalerList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers. +func (c *horizontalPodAutoscalers) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("horizontalpodautoscalers"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/ingress.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/ingress.go new file mode 100644 index 000000000..96b4d0439 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/ingress.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// IngressesGetter has a method to return a IngressInterface. +// A group's client should implement this interface. +type IngressesGetter interface { + Ingresses(namespace string) IngressInterface +} + +// IngressInterface has methods to work with Ingress resources. +type IngressInterface interface { + Create(*v1beta1.Ingress) (*v1beta1.Ingress, error) + Update(*v1beta1.Ingress) (*v1beta1.Ingress, error) + UpdateStatus(*v1beta1.Ingress) (*v1beta1.Ingress, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.Ingress, error) + List(opts api.ListOptions) (*v1beta1.IngressList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + IngressExpansion +} + +// ingresses implements IngressInterface +type ingresses struct { + client *ExtensionsClient + ns string +} + +// newIngresses returns a Ingresses +func newIngresses(c *ExtensionsClient, namespace string) *ingresses { + return &ingresses{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a ingress and creates it. Returns the server's representation of the ingress, and an error, if there is any. +func (c *ingresses) Create(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { + result = &v1beta1.Ingress{} + err = c.client.Post(). + Namespace(c.ns). + Resource("ingresses"). + Body(ingress). + Do(). + Into(result) + return +} + +// Update takes the representation of a ingress and updates it. Returns the server's representation of the ingress, and an error, if there is any. +func (c *ingresses) Update(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { + result = &v1beta1.Ingress{} + err = c.client.Put(). + Namespace(c.ns). + Resource("ingresses"). + Name(ingress.Name). + Body(ingress). + Do(). + Into(result) + return +} + +func (c *ingresses) UpdateStatus(ingress *v1beta1.Ingress) (result *v1beta1.Ingress, err error) { + result = &v1beta1.Ingress{} + err = c.client.Put(). + Namespace(c.ns). + Resource("ingresses"). + Name(ingress.Name). + SubResource("status"). + Body(ingress). + Do(). + Into(result) + return +} + +// Delete takes name of the ingress and deletes it. Returns an error if one occurs. +func (c *ingresses) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("ingresses"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *ingresses) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("ingresses"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the ingress, and returns the corresponding ingress object, and an error if there is any. +func (c *ingresses) Get(name string) (result *v1beta1.Ingress, err error) { + result = &v1beta1.Ingress{} + err = c.client.Get(). + Namespace(c.ns). + Resource("ingresses"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Ingresses that match those selectors. +func (c *ingresses) List(opts api.ListOptions) (result *v1beta1.IngressList, err error) { + result = &v1beta1.IngressList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("ingresses"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested ingresses. +func (c *ingresses) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("ingresses"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/job.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/job.go new file mode 100644 index 000000000..c518c5abd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/job.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// JobsGetter has a method to return a JobInterface. +// A group's client should implement this interface. +type JobsGetter interface { + Jobs(namespace string) JobInterface +} + +// JobInterface has methods to work with Job resources. +type JobInterface interface { + Create(*v1beta1.Job) (*v1beta1.Job, error) + Update(*v1beta1.Job) (*v1beta1.Job, error) + UpdateStatus(*v1beta1.Job) (*v1beta1.Job, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.Job, error) + List(opts api.ListOptions) (*v1beta1.JobList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + JobExpansion +} + +// jobs implements JobInterface +type jobs struct { + client *ExtensionsClient + ns string +} + +// newJobs returns a Jobs +func newJobs(c *ExtensionsClient, namespace string) *jobs { + return &jobs{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a job and creates it. Returns the server's representation of the job, and an error, if there is any. +func (c *jobs) Create(job *v1beta1.Job) (result *v1beta1.Job, err error) { + result = &v1beta1.Job{} + err = c.client.Post(). + Namespace(c.ns). + Resource("jobs"). + Body(job). + Do(). + Into(result) + return +} + +// Update takes the representation of a job and updates it. Returns the server's representation of the job, and an error, if there is any. +func (c *jobs) Update(job *v1beta1.Job) (result *v1beta1.Job, err error) { + result = &v1beta1.Job{} + err = c.client.Put(). + Namespace(c.ns). + Resource("jobs"). + Name(job.Name). + Body(job). + Do(). + Into(result) + return +} + +func (c *jobs) UpdateStatus(job *v1beta1.Job) (result *v1beta1.Job, err error) { + result = &v1beta1.Job{} + err = c.client.Put(). + Namespace(c.ns). + Resource("jobs"). + Name(job.Name). + SubResource("status"). + Body(job). + Do(). + Into(result) + return +} + +// Delete takes name of the job and deletes it. Returns an error if one occurs. +func (c *jobs) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("jobs"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *jobs) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("jobs"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the job, and returns the corresponding job object, and an error if there is any. +func (c *jobs) Get(name string) (result *v1beta1.Job, err error) { + result = &v1beta1.Job{} + err = c.client.Get(). + Namespace(c.ns). + Resource("jobs"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Jobs that match those selectors. +func (c *jobs) List(opts api.ListOptions) (result *v1beta1.JobList, err error) { + result = &v1beta1.JobList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("jobs"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested jobs. +func (c *jobs) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("jobs"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/replicaset.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/replicaset.go new file mode 100644 index 000000000..1822f052c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/replicaset.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ReplicaSetsGetter has a method to return a ReplicaSetInterface. +// A group's client should implement this interface. +type ReplicaSetsGetter interface { + ReplicaSets(namespace string) ReplicaSetInterface +} + +// ReplicaSetInterface has methods to work with ReplicaSet resources. +type ReplicaSetInterface interface { + Create(*v1beta1.ReplicaSet) (*v1beta1.ReplicaSet, error) + Update(*v1beta1.ReplicaSet) (*v1beta1.ReplicaSet, error) + UpdateStatus(*v1beta1.ReplicaSet) (*v1beta1.ReplicaSet, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.ReplicaSet, error) + List(opts api.ListOptions) (*v1beta1.ReplicaSetList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ReplicaSetExpansion +} + +// replicaSets implements ReplicaSetInterface +type replicaSets struct { + client *ExtensionsClient + ns string +} + +// newReplicaSets returns a ReplicaSets +func newReplicaSets(c *ExtensionsClient, namespace string) *replicaSets { + return &replicaSets{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a replicaSet and creates it. Returns the server's representation of the replicaSet, and an error, if there is any. +func (c *replicaSets) Create(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { + result = &v1beta1.ReplicaSet{} + err = c.client.Post(). + Namespace(c.ns). + Resource("replicasets"). + Body(replicaSet). + Do(). + Into(result) + return +} + +// Update takes the representation of a replicaSet and updates it. Returns the server's representation of the replicaSet, and an error, if there is any. +func (c *replicaSets) Update(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { + result = &v1beta1.ReplicaSet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("replicasets"). + Name(replicaSet.Name). + Body(replicaSet). + Do(). + Into(result) + return +} + +func (c *replicaSets) UpdateStatus(replicaSet *v1beta1.ReplicaSet) (result *v1beta1.ReplicaSet, err error) { + result = &v1beta1.ReplicaSet{} + err = c.client.Put(). + Namespace(c.ns). + Resource("replicasets"). + Name(replicaSet.Name). + SubResource("status"). + Body(replicaSet). + Do(). + Into(result) + return +} + +// Delete takes name of the replicaSet and deletes it. Returns an error if one occurs. +func (c *replicaSets) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("replicasets"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *replicaSets) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("replicasets"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the replicaSet, and returns the corresponding replicaSet object, and an error if there is any. +func (c *replicaSets) Get(name string) (result *v1beta1.ReplicaSet, err error) { + result = &v1beta1.ReplicaSet{} + err = c.client.Get(). + Namespace(c.ns). + Resource("replicasets"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ReplicaSets that match those selectors. +func (c *replicaSets) List(opts api.ListOptions) (result *v1beta1.ReplicaSetList, err error) { + result = &v1beta1.ReplicaSetList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("replicasets"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested replicaSets. +func (c *replicaSets) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("replicasets"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/scale.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/scale.go new file mode 100644 index 000000000..231fe5ccf --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/scale.go @@ -0,0 +1,42 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +// ScalesGetter has a method to return a ScaleInterface. +// A group's client should implement this interface. +type ScalesGetter interface { + Scales(namespace string) ScaleInterface +} + +// ScaleInterface has methods to work with Scale resources. +type ScaleInterface interface { + ScaleExpansion +} + +// scales implements ScaleInterface +type scales struct { + client *ExtensionsClient + ns string +} + +// newScales returns a Scales +func newScales(c *ExtensionsClient, namespace string) *scales { + return &scales{ + client: c, + ns: namespace, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/scale_expansion.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/scale_expansion.go new file mode 100644 index 000000000..488863d9f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/scale_expansion.go @@ -0,0 +1,65 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" +) + +// The ScaleExpansion interface allows manually adding extra methods to the ScaleInterface. +type ScaleExpansion interface { + Get(kind string, name string) (*v1beta1.Scale, error) + Update(kind string, scale *v1beta1.Scale) (*v1beta1.Scale, error) +} + +// Get takes the reference to scale subresource and returns the subresource or error, if one occurs. +func (c *scales) Get(kind string, name string) (result *v1beta1.Scale, err error) { + result = &v1beta1.Scale{} + + // TODO this method needs to take a proper unambiguous kind + fullyQualifiedKind := unversioned.GroupVersionKind{Kind: kind} + resource, _ := meta.KindToResource(fullyQualifiedKind) + + err = c.client.Get(). + Namespace(c.ns). + Resource(resource.Resource). + Name(name). + SubResource("scale"). + Do(). + Into(result) + return +} + +func (c *scales) Update(kind string, scale *v1beta1.Scale) (result *v1beta1.Scale, err error) { + result = &v1beta1.Scale{} + + // TODO this method needs to take a proper unambiguous kind + fullyQualifiedKind := unversioned.GroupVersionKind{Kind: kind} + resource, _ := meta.KindToResource(fullyQualifiedKind) + + err = c.client.Put(). + Namespace(scale.Namespace). + Resource(resource.Resource). + Name(scale.Name). + SubResource("scale"). + Body(scale). + Do(). + Into(result) + return +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/thirdpartyresource.go b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/thirdpartyresource.go new file mode 100644 index 000000000..cfd128dc3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/typed/generated/extensions/v1beta1/thirdpartyresource.go @@ -0,0 +1,136 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 v1beta1 + +import ( + api "k8s.io/kubernetes/pkg/api" + v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + watch "k8s.io/kubernetes/pkg/watch" +) + +// ThirdPartyResourcesGetter has a method to return a ThirdPartyResourceInterface. +// A group's client should implement this interface. +type ThirdPartyResourcesGetter interface { + ThirdPartyResources(namespace string) ThirdPartyResourceInterface +} + +// ThirdPartyResourceInterface has methods to work with ThirdPartyResource resources. +type ThirdPartyResourceInterface interface { + Create(*v1beta1.ThirdPartyResource) (*v1beta1.ThirdPartyResource, error) + Update(*v1beta1.ThirdPartyResource) (*v1beta1.ThirdPartyResource, error) + Delete(name string, options *api.DeleteOptions) error + DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error + Get(name string) (*v1beta1.ThirdPartyResource, error) + List(opts api.ListOptions) (*v1beta1.ThirdPartyResourceList, error) + Watch(opts api.ListOptions) (watch.Interface, error) + ThirdPartyResourceExpansion +} + +// thirdPartyResources implements ThirdPartyResourceInterface +type thirdPartyResources struct { + client *ExtensionsClient + ns string +} + +// newThirdPartyResources returns a ThirdPartyResources +func newThirdPartyResources(c *ExtensionsClient, namespace string) *thirdPartyResources { + return &thirdPartyResources{ + client: c, + ns: namespace, + } +} + +// Create takes the representation of a thirdPartyResource and creates it. Returns the server's representation of the thirdPartyResource, and an error, if there is any. +func (c *thirdPartyResources) Create(thirdPartyResource *v1beta1.ThirdPartyResource) (result *v1beta1.ThirdPartyResource, err error) { + result = &v1beta1.ThirdPartyResource{} + err = c.client.Post(). + Namespace(c.ns). + Resource("thirdpartyresources"). + Body(thirdPartyResource). + Do(). + Into(result) + return +} + +// Update takes the representation of a thirdPartyResource and updates it. Returns the server's representation of the thirdPartyResource, and an error, if there is any. +func (c *thirdPartyResources) Update(thirdPartyResource *v1beta1.ThirdPartyResource) (result *v1beta1.ThirdPartyResource, err error) { + result = &v1beta1.ThirdPartyResource{} + err = c.client.Put(). + Namespace(c.ns). + Resource("thirdpartyresources"). + Name(thirdPartyResource.Name). + Body(thirdPartyResource). + Do(). + Into(result) + return +} + +// Delete takes name of the thirdPartyResource and deletes it. Returns an error if one occurs. +func (c *thirdPartyResources) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("thirdpartyresources"). + Name(name). + Body(options). + Do(). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *thirdPartyResources) DeleteCollection(options *api.DeleteOptions, listOptions api.ListOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("thirdpartyresources"). + VersionedParams(&listOptions, api.ParameterCodec). + Body(options). + Do(). + Error() +} + +// Get takes name of the thirdPartyResource, and returns the corresponding thirdPartyResource object, and an error if there is any. +func (c *thirdPartyResources) Get(name string) (result *v1beta1.ThirdPartyResource, err error) { + result = &v1beta1.ThirdPartyResource{} + err = c.client.Get(). + Namespace(c.ns). + Resource("thirdpartyresources"). + Name(name). + Do(). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of ThirdPartyResources that match those selectors. +func (c *thirdPartyResources) List(opts api.ListOptions) (result *v1beta1.ThirdPartyResourceList, err error) { + result = &v1beta1.ThirdPartyResourceList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("thirdpartyresources"). + VersionedParams(&opts, api.ParameterCodec). + Do(). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested thirdPartyResources. +func (c *thirdPartyResources) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("thirdpartyresources"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/adapters/internalclientset/clientset_adaption.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/adapters/internalclientset/clientset_adaption.go new file mode 100644 index 000000000..ad98d06f6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/adapters/internalclientset/clientset_adaption.go @@ -0,0 +1,50 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 internalclientset + +import ( + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + unversionedextensions "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned" + "k8s.io/kubernetes/pkg/client/typed/discovery" + "k8s.io/kubernetes/pkg/client/unversioned" +) + +// FromUnversionedClient adapts a unversioned.Client to a internalclientset.Clientset. +// This function is temporary. We will remove it when everyone has moved to using +// Clientset. New code should NOT use this function. +func FromUnversionedClient(c *unversioned.Client) *internalclientset.Clientset { + var clientset internalclientset.Clientset + if c != nil { + clientset.CoreClient = unversionedcore.New(c.RESTClient) + } else { + clientset.CoreClient = unversionedcore.New(nil) + } + if c != nil && c.ExtensionsClient != nil { + clientset.ExtensionsClient = unversionedextensions.New(c.ExtensionsClient.RESTClient) + } else { + clientset.ExtensionsClient = unversionedextensions.New(nil) + } + + if c != nil && c.DiscoveryClient != nil { + clientset.DiscoveryClient = discovery.NewDiscoveryClient(c.DiscoveryClient.RESTClient) + } else { + clientset.DiscoveryClient = discovery.NewDiscoveryClient(nil) + } + + return &clientset +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/adapters/release_1_2/clientset_adaption.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/adapters/release_1_2/clientset_adaption.go new file mode 100644 index 000000000..9e33ccd19 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/adapters/release_1_2/clientset_adaption.go @@ -0,0 +1,50 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 release_1_2 + +import ( + "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2" + v1core "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/core/v1" + v1beta1extensions "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_2/typed/extensions/v1beta1" + "k8s.io/kubernetes/pkg/client/typed/discovery" + "k8s.io/kubernetes/pkg/client/unversioned" +) + +// FromUnversionedClient adapts a unversioned.Client to a release_1_2.Clientset. +// This function is temporary. We will remove it when everyone has moved to using +// Clientset. New code should NOT use this function. +func FromUnversionedClient(c *unversioned.Client) *release_1_2.Clientset { + var clientset release_1_2.Clientset + if c != nil { + clientset.CoreClient = v1core.New(c.RESTClient) + } else { + clientset.CoreClient = v1core.New(nil) + } + if c != nil && c.ExtensionsClient != nil { + clientset.ExtensionsClient = v1beta1extensions.New(c.ExtensionsClient.RESTClient) + } else { + clientset.ExtensionsClient = v1beta1extensions.New(nil) + } + + if c != nil && c.DiscoveryClient != nil { + clientset.DiscoveryClient = discovery.NewDiscoveryClient(c.DiscoveryClient.RESTClient) + } else { + clientset.DiscoveryClient = discovery.NewDiscoveryClient(nil) + } + + return &clientset +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/auth/clientauth.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/auth/clientauth.go index 16acc00ce..64b3ef6be 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/auth/clientauth.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/auth/clientauth.go @@ -68,7 +68,7 @@ import ( "io/ioutil" "os" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" ) // Info holds Kubernetes API authorization config. It is intended @@ -104,8 +104,8 @@ func LoadFromFile(path string) (*Info, error) { // MergeWithConfig returns a copy of a client.Config with values from the Info. // The fields of client.Config with a corresponding field in the Info are set // with the value from the Info. -func (info Info) MergeWithConfig(c client.Config) (client.Config, error) { - var config client.Config = c +func (info Info) MergeWithConfig(c restclient.Config) (restclient.Config, error) { + var config restclient.Config = c config.Username = info.User config.Password = info.Password config.CAFile = info.CAFile diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/autoscaling.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/autoscaling.go new file mode 100644 index 000000000..c3ec19810 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/autoscaling.go @@ -0,0 +1,83 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/apis/autoscaling" + "k8s.io/kubernetes/pkg/client/restclient" +) + +type AutoscalingInterface interface { + HorizontalPodAutoscalersNamespacer +} + +// AutoscalingClient is used to interact with Kubernetes autoscaling features. +type AutoscalingClient struct { + *restclient.RESTClient +} + +func (c *AutoscalingClient) HorizontalPodAutoscalers(namespace string) HorizontalPodAutoscalerInterface { + return newHorizontalPodAutoscalersV1(c, namespace) +} + +func NewAutoscaling(c *restclient.Config) (*AutoscalingClient, error) { + config := *c + if err := setAutoscalingDefaults(&config); err != nil { + return nil, err + } + client, err := restclient.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &AutoscalingClient{client}, nil +} + +func NewAutoscalingOrDie(c *restclient.Config) *AutoscalingClient { + client, err := NewAutoscaling(c) + if err != nil { + panic(err) + } + return client +} + +func setAutoscalingDefaults(config *restclient.Config) error { + // if autoscaling group is not registered, return an error + g, err := registered.Group(autoscaling.GroupName) + if err != nil { + return err + } + config.APIPath = defaultAPIPath + if config.UserAgent == "" { + config.UserAgent = restclient.DefaultKubernetesUserAgent() + } + // TODO: Unconditionally set the config.Version, until we fix the config. + //if config.Version == "" { + copyGroupVersion := g.GroupVersion + config.GroupVersion = ©GroupVersion + //} + + config.Codec = api.Codecs.LegacyCodec(*config.GroupVersion) + if config.QPS == 0 { + config.QPS = 5 + } + if config.Burst == 0 { + config.Burst = 10 + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/batch.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/batch.go new file mode 100644 index 000000000..a432e4c78 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/batch.go @@ -0,0 +1,83 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 unversioned + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/apis/batch" + "k8s.io/kubernetes/pkg/client/restclient" +) + +type BatchInterface interface { + JobsNamespacer +} + +// BatchClient is used to interact with Kubernetes batch features. +type BatchClient struct { + *restclient.RESTClient +} + +func (c *BatchClient) Jobs(namespace string) JobInterface { + return newJobsV1(c, namespace) +} + +func NewBatch(c *restclient.Config) (*BatchClient, error) { + config := *c + if err := setBatchDefaults(&config); err != nil { + return nil, err + } + client, err := restclient.RESTClientFor(&config) + if err != nil { + return nil, err + } + return &BatchClient{client}, nil +} + +func NewBatchOrDie(c *restclient.Config) *BatchClient { + client, err := NewBatch(c) + if err != nil { + panic(err) + } + return client +} + +func setBatchDefaults(config *restclient.Config) error { + // if batch group is not registered, return an error + g, err := registered.Group(batch.GroupName) + if err != nil { + return err + } + config.APIPath = defaultAPIPath + if config.UserAgent == "" { + config.UserAgent = restclient.DefaultKubernetesUserAgent() + } + // TODO: Unconditionally set the config.Version, until we fix the config. + //if config.Version == "" { + copyGroupVersion := g.GroupVersion + config.GroupVersion = ©GroupVersion + //} + + config.Codec = api.Codecs.LegacyCodec(*config.GroupVersion) + if config.QPS == 0 { + config.QPS = 5 + } + if config.Burst == 0 { + config.Burst = 10 + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/client.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/client.go index 24bc54c93..b897bc230 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/client.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/client.go @@ -20,6 +20,9 @@ import ( "net" "net/url" "strings" + + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/typed/discovery" ) // Interface holds the methods for clients of Kubernetes, @@ -41,8 +44,10 @@ type Interface interface { PersistentVolumeClaimsNamespacer ComponentStatusesInterface ConfigMapsNamespacer + Autoscaling() AutoscalingInterface + Batch() BatchInterface Extensions() ExtensionsInterface - Discovery() DiscoveryInterface + Discovery() discovery.DiscoveryInterface } func (c *Client) ReplicationControllers(namespace string) ReplicationControllerInterface { @@ -110,18 +115,11 @@ func (c *Client) ConfigMaps(namespace string) ConfigMapsInterface { // Client is the implementation of a Kubernetes client. type Client struct { - *RESTClient + *restclient.RESTClient + *AutoscalingClient + *BatchClient *ExtensionsClient - *DiscoveryClient -} - -func stringDoesntExistIn(str string, slice []string) bool { - for _, s := range slice { - if s == str { - return false - } - } - return true + *discovery.DiscoveryClient } // IsTimeout tests if this is a timeout error in the underlying transport. @@ -146,10 +144,18 @@ func IsTimeout(err error) bool { return false } +func (c *Client) Autoscaling() AutoscalingInterface { + return c.AutoscalingClient +} + +func (c *Client) Batch() BatchInterface { + return c.BatchClient +} + func (c *Client) Extensions() ExtensionsInterface { return c.ExtensionsClient } -func (c *Client) Discovery() DiscoveryInterface { +func (c *Client) Discovery() discovery.DiscoveryInterface { return c.DiscoveryClient } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/helpers_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/helpers_test.go index ca970f3b4..6952524c6 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/helpers_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/helpers_test.go @@ -206,7 +206,7 @@ func TestFlattenSuccess(t *testing.T) { } -func ExampleMinifyAndShorten() { +func Example_minifyAndShorten() { certFile, _ := ioutil.TempFile("", "") defer os.Remove(certFile.Name()) keyFile, _ := ioutil.TempFile("", "") diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest/latest.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest/latest.go index 90d5c5380..d974aa9a9 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest/latest.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest/latest.go @@ -40,9 +40,15 @@ const OldestVersion = "v1" // with a set of versions to choose. var Versions = []string{"v1"} -var Codec = versioning.NewCodecForScheme( - api.Scheme, - json.NewYAMLSerializer(json.DefaultMetaFactory, api.Scheme, runtime.ObjectTyperToTyper(api.Scheme)), - []unversioned.GroupVersion{{Version: Version}}, - []unversioned.GroupVersion{{Version: runtime.APIVersionInternal}}, -) +var Codec runtime.Codec + +func init() { + yamlSerializer := json.NewYAMLSerializer(json.DefaultMetaFactory, api.Scheme, runtime.ObjectTyperToTyper(api.Scheme)) + Codec = versioning.NewCodecForScheme( + api.Scheme, + yamlSerializer, + yamlSerializer, + []unversioned.GroupVersion{{Version: Version}}, + []unversioned.GroupVersion{{Version: runtime.APIVersionInternal}}, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/types.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/types.go index 4a1dc364f..7e2bfcfa8 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/types.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/types.go @@ -29,7 +29,11 @@ type Config struct { // Legacy field from pkg/api/types.go TypeMeta. // TODO(jlowdermilk): remove this after eliminating downstream dependencies. Kind string `json:"kind,omitempty"` - // Version of the schema for this config object. + // DEPRECATED: APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc). + // Because a cluster can run multiple API groups and potentially multiple versions of each, it no longer makes sense to specify + // a single value for the cluster version. + // This field isnt really needed anyway, so we are deprecating it without replacement. + // It will be ignored if it is present. APIVersion string `json:"apiVersion,omitempty"` // Preferences holds general information to be use for cli interactions Preferences Preferences `json:"preferences"` diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/types_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/types_test.go index 8059fe21f..398e139bd 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/types_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/types_test.go @@ -22,7 +22,7 @@ import ( "github.com/ghodss/yaml" ) -func ExampleEmptyConfig() { +func Example_emptyConfig() { defaultConfig := NewConfig() output, err := yaml.Marshal(defaultConfig) @@ -39,18 +39,16 @@ func ExampleEmptyConfig() { // users: {} } -func ExampleOfOptionsConfig() { +func Example_ofOptionsConfig() { defaultConfig := NewConfig() defaultConfig.Preferences.Colors = true defaultConfig.Clusters["alfa"] = &Cluster{ Server: "https://alfa.org:8080", - APIVersion: "v1", InsecureSkipTLSVerify: true, CertificateAuthority: "path/to/my/cert-ca-filename", } defaultConfig.Clusters["bravo"] = &Cluster{ Server: "https://bravo.org:8080", - APIVersion: "v1", InsecureSkipTLSVerify: false, } defaultConfig.AuthInfos["white-mage-via-cert"] = &AuthInfo{ @@ -86,13 +84,11 @@ func ExampleOfOptionsConfig() { // clusters: // alfa: // LocationOfOrigin: "" - // api-version: v1 // certificate-authority: path/to/my/cert-ca-filename // insecure-skip-tls-verify: true // server: https://alfa.org:8080 // bravo: // LocationOfOrigin: "" - // api-version: v1 // server: https://bravo.org:8080 // contexts: // alfa-as-black-mage: diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/types.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/types.go index e04f1311e..c9b4ab56b 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/types.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/types.go @@ -28,7 +28,11 @@ type Config struct { // Legacy field from pkg/api/types.go TypeMeta. // TODO(jlowdermilk): remove this after eliminating downstream dependencies. Kind string `json:"kind,omitempty"` - // Version of the schema for this config object. + // DEPRECATED: APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc). + // Because a cluster can run multiple API groups and potentially multiple versions of each, it no longer makes sense to specify + // a single value for the cluster version. + // This field isnt really needed anyway, so we are deprecating it without replacement. + // It will be ignored if it is present. APIVersion string `json:"apiVersion,omitempty"` // Preferences holds general information to be use for cli interactions Preferences Preferences `json:"preferences"` diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/client_config.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/client_config.go index ce94a128b..533feb54a 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/client_config.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/client_config.go @@ -28,8 +28,7 @@ import ( "github.com/imdario/mergo" "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/unversioned" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" clientauth "k8s.io/kubernetes/pkg/client/unversioned/auth" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" ) @@ -50,7 +49,7 @@ type ClientConfig interface { // RawConfig returns the merged result of all overrides RawConfig() (clientcmdapi.Config, error) // ClientConfig returns a complete client config - ClientConfig() (*client.Config, error) + ClientConfig() (*restclient.Config, error) // Namespace returns the namespace resulting from the merged // result of all overrides and a boolean indicating if it was // overridden @@ -67,25 +66,25 @@ type DirectClientConfig struct { // NewDefaultClientConfig creates a DirectClientConfig using the config.CurrentContext as the context name func NewDefaultClientConfig(config clientcmdapi.Config, overrides *ConfigOverrides) ClientConfig { - return DirectClientConfig{config, config.CurrentContext, overrides, nil} + return &DirectClientConfig{config, config.CurrentContext, overrides, nil} } // NewNonInteractiveClientConfig creates a DirectClientConfig using the passed context name and does not have a fallback reader for auth information func NewNonInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides) ClientConfig { - return DirectClientConfig{config, contextName, overrides, nil} + return &DirectClientConfig{config, contextName, overrides, nil} } // NewInteractiveClientConfig creates a DirectClientConfig using the passed context name and a reader in case auth information is not provided via files or flags func NewInteractiveClientConfig(config clientcmdapi.Config, contextName string, overrides *ConfigOverrides, fallbackReader io.Reader) ClientConfig { - return DirectClientConfig{config, contextName, overrides, fallbackReader} + return &DirectClientConfig{config, contextName, overrides, fallbackReader} } -func (config DirectClientConfig) RawConfig() (clientcmdapi.Config, error) { +func (config *DirectClientConfig) RawConfig() (clientcmdapi.Config, error) { return config.config, nil } // ClientConfig implements ClientConfig -func (config DirectClientConfig) ClientConfig() (*client.Config, error) { +func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) { if err := config.ConfirmUsable(); err != nil { return nil, err } @@ -93,26 +92,21 @@ func (config DirectClientConfig) ClientConfig() (*client.Config, error) { configAuthInfo := config.getAuthInfo() configClusterInfo := config.getCluster() - clientConfig := &client.Config{} + clientConfig := &restclient.Config{} clientConfig.Host = configClusterInfo.Server if u, err := url.ParseRequestURI(clientConfig.Host); err == nil && u.Opaque == "" && len(u.Path) > 1 { u.RawQuery = "" u.Fragment = "" clientConfig.Host = u.String() } - if len(configClusterInfo.APIVersion) != 0 { - gv, err := unversioned.ParseGroupVersion(configClusterInfo.APIVersion) - if err != nil { - return nil, err - } - clientConfig.GroupVersion = &gv - } // only try to read the auth information if we are secure - if client.IsConfigTransportTLS(*clientConfig) { + if restclient.IsConfigTransportTLS(*clientConfig) { var err error // mergo is a first write wins for map value and a last writing wins for interface values + // NOTE: This behavior changed with https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a. + // Our mergo.Merge version is older than this change. userAuthPartialConfig, err := getUserIdentificationPartialConfig(configAuthInfo, config.fallbackReader) if err != nil { return nil, err @@ -135,11 +129,11 @@ func (config DirectClientConfig) ClientConfig() (*client.Config, error) { // 1. configClusterInfo (the final result of command line flags and merged .kubeconfig files) // 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority) // 3. load the ~/.kubernetes_auth file as a default -func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, configClusterInfo clientcmdapi.Cluster) (*client.Config, error) { - mergedConfig := &client.Config{} +func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, configClusterInfo clientcmdapi.Cluster) (*restclient.Config, error) { + mergedConfig := &restclient.Config{} // configClusterInfo holds the information identify the server provided by .kubeconfig - configClientConfig := &client.Config{} + configClientConfig := &restclient.Config{} configClientConfig.CAFile = configClusterInfo.CertificateAuthority configClientConfig.CAData = configClusterInfo.CertificateAuthorityData configClientConfig.Insecure = configClusterInfo.InsecureSkipTLSVerify @@ -155,8 +149,8 @@ func getServerIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, // 2. configAuthInfo.auth-path (this file can contain information that conflicts with #1, and we want #1 to win the priority) // 3. if there is not enough information to idenfity the user, load try the ~/.kubernetes_auth file // 4. if there is not enough information to identify the user, prompt if possible -func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader) (*client.Config, error) { - mergedConfig := &client.Config{} +func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fallbackReader io.Reader) (*restclient.Config, error) { + mergedConfig := &restclient.Config{} // blindly overwrite existing values based on precedence if len(configAuthInfo.Token) > 0 { @@ -180,7 +174,7 @@ func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fa promptedConfig := makeUserIdentificationConfig(*promptedAuthInfo) previouslyMergedConfig := mergedConfig - mergedConfig = &client.Config{} + mergedConfig = &restclient.Config{} mergo.Merge(mergedConfig, promptedConfig) mergo.Merge(mergedConfig, previouslyMergedConfig) } @@ -189,8 +183,8 @@ func getUserIdentificationPartialConfig(configAuthInfo clientcmdapi.AuthInfo, fa } // makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only user identification information -func makeUserIdentificationConfig(info clientauth.Info) *client.Config { - config := &client.Config{} +func makeUserIdentificationConfig(info clientauth.Info) *restclient.Config { + config := &restclient.Config{} config.Username = info.User config.Password = info.Password config.CertFile = info.CertFile @@ -200,8 +194,8 @@ func makeUserIdentificationConfig(info clientauth.Info) *client.Config { } // makeUserIdentificationFieldsConfig returns a client.Config capable of being merged using mergo for only server identification information -func makeServerIdentificationConfig(info clientauth.Info) client.Config { - config := client.Config{} +func makeServerIdentificationConfig(info clientauth.Info) restclient.Config { + config := restclient.Config{} config.CAFile = info.CAFile if info.Insecure != nil { config.Insecure = *info.Insecure @@ -209,7 +203,7 @@ func makeServerIdentificationConfig(info clientauth.Info) client.Config { return config } -func canIdentifyUser(config client.Config) bool { +func canIdentifyUser(config restclient.Config) bool { return len(config.Username) > 0 || (len(config.CertFile) > 0 || len(config.CertData) > 0) || len(config.BearerToken) > 0 @@ -217,7 +211,7 @@ func canIdentifyUser(config client.Config) bool { } // Namespace implements KubeConfig -func (config DirectClientConfig) Namespace() (string, bool, error) { +func (config *DirectClientConfig) Namespace() (string, bool, error) { if err := config.ConfirmUsable(); err != nil { return "", false, err } @@ -237,7 +231,7 @@ func (config DirectClientConfig) Namespace() (string, bool, error) { // ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config, // but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible. -func (config DirectClientConfig) ConfirmUsable() error { +func (config *DirectClientConfig) ConfirmUsable() error { validationErrors := make([]error, 0) validationErrors = append(validationErrors, validateAuthInfo(config.getAuthInfoName(), config.getAuthInfo())...) validationErrors = append(validationErrors, validateClusterInfo(config.getClusterName(), config.getCluster())...) @@ -249,7 +243,7 @@ func (config DirectClientConfig) ConfirmUsable() error { return newErrConfigurationInvalid(validationErrors) } -func (config DirectClientConfig) getContextName() string { +func (config *DirectClientConfig) getContextName() string { if len(config.overrides.CurrentContext) != 0 { return config.overrides.CurrentContext } @@ -260,21 +254,21 @@ func (config DirectClientConfig) getContextName() string { return config.config.CurrentContext } -func (config DirectClientConfig) getAuthInfoName() string { +func (config *DirectClientConfig) getAuthInfoName() string { if len(config.overrides.Context.AuthInfo) != 0 { return config.overrides.Context.AuthInfo } return config.getContext().AuthInfo } -func (config DirectClientConfig) getClusterName() string { +func (config *DirectClientConfig) getClusterName() string { if len(config.overrides.Context.Cluster) != 0 { return config.overrides.Context.Cluster } return config.getContext().Cluster } -func (config DirectClientConfig) getContext() clientcmdapi.Context { +func (config *DirectClientConfig) getContext() clientcmdapi.Context { contexts := config.config.Contexts contextName := config.getContextName() @@ -287,7 +281,7 @@ func (config DirectClientConfig) getContext() clientcmdapi.Context { return mergedContext } -func (config DirectClientConfig) getAuthInfo() clientcmdapi.AuthInfo { +func (config *DirectClientConfig) getAuthInfo() clientcmdapi.AuthInfo { authInfos := config.config.AuthInfos authInfoName := config.getAuthInfoName() @@ -300,7 +294,7 @@ func (config DirectClientConfig) getAuthInfo() clientcmdapi.AuthInfo { return mergedAuthInfo } -func (config DirectClientConfig) getCluster() clientcmdapi.Cluster { +func (config *DirectClientConfig) getCluster() clientcmdapi.Cluster { clusterInfos := config.config.Clusters clusterInfoName := config.getClusterName() @@ -311,6 +305,14 @@ func (config DirectClientConfig) getCluster() clientcmdapi.Cluster { mergo.Merge(&mergedClusterInfo, configClusterInfo) } mergo.Merge(&mergedClusterInfo, config.overrides.ClusterInfo) + // An override of --insecure-skip-tls-verify=true and no accompanying CA/CA data should clear already-set CA/CA data + // otherwise, a kubeconfig containing a CA reference would return an error that "CA and insecure-skip-tls-verify couldn't both be set" + caLen := len(config.overrides.ClusterInfo.CertificateAuthority) + caDataLen := len(config.overrides.ClusterInfo.CertificateAuthorityData) + if config.overrides.ClusterInfo.InsecureSkipTLSVerify && caLen == 0 && caDataLen == 0 { + mergedClusterInfo.CertificateAuthority = "" + mergedClusterInfo.CertificateAuthorityData = nil + } return mergedClusterInfo } @@ -322,8 +324,8 @@ func (inClusterClientConfig) RawConfig() (clientcmdapi.Config, error) { return clientcmdapi.Config{}, fmt.Errorf("inCluster environment config doesn't support multiple clusters") } -func (inClusterClientConfig) ClientConfig() (*client.Config, error) { - return client.InClusterConfig() +func (inClusterClientConfig) ClientConfig() (*restclient.Config, error) { + return restclient.InClusterConfig() } func (inClusterClientConfig) Namespace() (string, error) { @@ -356,10 +358,10 @@ func (inClusterClientConfig) Possible() bool { // components. Warnings should reflect this usage. If neither masterUrl or kubeconfigPath // are passed in we fallback to inClusterConfig. If inClusterConfig fails, we fallback // to the default config. -func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*client.Config, error) { +func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) { if kubeconfigPath == "" && masterUrl == "" { glog.Warningf("Neither --kubeconfig nor --master was specified. Using the inClusterConfig. This might not work.") - kubeconfig, err := client.InClusterConfig() + kubeconfig, err := restclient.InClusterConfig() if err == nil { return kubeconfig, nil } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/client_config_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/client_config_test.go index 834869677..8e68ff7a1 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/client_config_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/client_config_test.go @@ -20,11 +20,29 @@ import ( "reflect" "testing" - "k8s.io/kubernetes/pkg/api/testapi" - client "k8s.io/kubernetes/pkg/client/unversioned" + "github.com/imdario/mergo" + "k8s.io/kubernetes/pkg/client/restclient" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" ) +func TestOldMergoLib(t *testing.T) { + type T struct { + X string + } + dst := T{X: "one"} + src := T{X: "two"} + mergo.Merge(&dst, &src) + if dst.X != "two" { + // mergo.Merge changed in an incompatible way with + // + // https://github.com/imdario/mergo/commit/d304790b2ed594794496464fadd89d2bb266600a + // + // We have to stay with the old version which still does eager + // copying from src to dst in structs. + t.Errorf("mergo.Merge library found with incompatible, new behavior") + } +} + func createValidTestConfig() *clientcmdapi.Config { const ( server = "https://anything.com:8080" @@ -33,8 +51,7 @@ func createValidTestConfig() *clientcmdapi.Config { config := clientcmdapi.NewConfig() config.Clusters["clean"] = &clientcmdapi.Cluster{ - Server: server, - APIVersion: testapi.Default.GroupVersion().String(), + Server: server, } config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ Token: token, @@ -48,6 +65,31 @@ func createValidTestConfig() *clientcmdapi.Config { return config } +func createCAValidTestConfig() *clientcmdapi.Config { + + config := createValidTestConfig() + config.Clusters["clean"].CertificateAuthorityData = []byte{0, 0} + return config +} + +func TestInsecureOverridesCA(t *testing.T) { + config := createCAValidTestConfig() + clientBuilder := NewNonInteractiveClientConfig(*config, "clean", &ConfigOverrides{ + ClusterInfo: clientcmdapi.Cluster{ + InsecureSkipTLSVerify: true, + }, + }) + + actualCfg, err := clientBuilder.ClientConfig() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + matchBoolArg(true, actualCfg.Insecure, t) + matchStringArg("", actualCfg.TLSClientConfig.CAFile, t) + matchByteArg(nil, actualCfg.TLSClientConfig.CAData, t) +} + func TestMergeContext(t *testing.T) { const namespace = "overriden-namespace" @@ -88,8 +130,7 @@ func TestCertificateData(t *testing.T) { config := clientcmdapi.NewConfig() config.Clusters["clean"] = &clientcmdapi.Cluster{ - Server: "https://localhost:8443", - APIVersion: testapi.Default.GroupVersion().String(), + Server: "https://localhost:8443", CertificateAuthorityData: caData, } config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ @@ -121,8 +162,7 @@ func TestBasicAuthData(t *testing.T) { config := clientcmdapi.NewConfig() config.Clusters["clean"] = &clientcmdapi.Cluster{ - Server: "https://localhost:8443", - APIVersion: testapi.Default.GroupVersion().String(), + Server: "https://localhost:8443", } config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ Username: username, @@ -157,7 +197,6 @@ func TestCreateClean(t *testing.T) { matchStringArg(config.Clusters["clean"].Server, clientConfig.Host, t) matchStringArg("", clientConfig.APIPath, t) - matchStringArg(config.Clusters["clean"].APIVersion, clientConfig.GroupVersion.String(), t) matchBoolArg(config.Clusters["clean"].InsecureSkipTLSVerify, clientConfig.Insecure, t) matchStringArg(config.AuthInfos["clean"].Token, clientConfig.BearerToken, t) } @@ -210,7 +249,6 @@ func TestCreateCleanDefault(t *testing.T) { } matchStringArg(config.Clusters["clean"].Server, clientConfig.Host, t) - matchStringArg(config.Clusters["clean"].APIVersion, clientConfig.GroupVersion.String(), t) matchBoolArg(config.Clusters["clean"].InsecureSkipTLSVerify, clientConfig.Insecure, t) matchStringArg(config.AuthInfos["clean"].Token, clientConfig.BearerToken, t) } @@ -225,7 +263,7 @@ func TestCreateMissingContext(t *testing.T) { t.Errorf("Unexpected error: %v", err) } - expectedConfig := &client.Config{Host: clientConfig.Host} + expectedConfig := &restclient.Config{Host: clientConfig.Host} if !reflect.DeepEqual(expectedConfig, clientConfig) { t.Errorf("Expected %#v, got %#v", expectedConfig, clientConfig) diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/loader.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/loader.go index 215431583..7650dd24f 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/loader.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/loader.go @@ -23,6 +23,7 @@ import ( "os" "path" "path/filepath" + goruntime "runtime" "strings" "github.com/golang/glog" @@ -33,6 +34,7 @@ import ( clientcmdlatest "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest" "k8s.io/kubernetes/pkg/runtime" utilerrors "k8s.io/kubernetes/pkg/util/errors" + "k8s.io/kubernetes/pkg/util/homedir" ) const ( @@ -43,9 +45,23 @@ const ( RecommendedSchemaName = "schema" ) -var OldRecommendedHomeFile = path.Join(os.Getenv("HOME"), "/.kube/.kubeconfig") -var RecommendedHomeFile = path.Join(os.Getenv("HOME"), RecommendedHomeDir, RecommendedFileName) -var RecommendedSchemaFile = path.Join(os.Getenv("HOME"), RecommendedHomeDir, RecommendedSchemaName) +var RecommendedHomeFile = path.Join(homedir.HomeDir(), RecommendedHomeDir, RecommendedFileName) +var RecommendedSchemaFile = path.Join(homedir.HomeDir(), RecommendedHomeDir, RecommendedSchemaName) + +// currentMigrationRules returns a map that holds the history of recommended home directories used in previous versions. +// Any future changes to RecommendedHomeFile and related are expected to add a migration rule here, in order to make +// sure existing config files are migrated to their new locations properly. +func currentMigrationRules() map[string]string { + oldRecommendedHomeFile := path.Join(os.Getenv("HOME"), "/.kube/.kubeconfig") + oldRecommendedWindowsHomeFile := path.Join(os.Getenv("HOME"), RecommendedHomeDir, RecommendedFileName) + + migrationRules := map[string]string{} + migrationRules[RecommendedHomeFile] = oldRecommendedHomeFile + if goruntime.GOOS == "windows" { + migrationRules[RecommendedHomeFile] = oldRecommendedWindowsHomeFile + } + return migrationRules +} // ClientConfigLoadingRules is an ExplicitPath and string slice of specific locations that are used for merging together a Config // Callers can put the chain together however they want, but we'd recommend: @@ -68,7 +84,6 @@ type ClientConfigLoadingRules struct { // use this constructor func NewDefaultClientConfigLoadingRules() *ClientConfigLoadingRules { chain := []string{} - migrationRules := map[string]string{} envVarFiles := os.Getenv(RecommendedConfigPathEnvVar) if len(envVarFiles) != 0 { @@ -76,13 +91,11 @@ func NewDefaultClientConfigLoadingRules() *ClientConfigLoadingRules { } else { chain = append(chain, RecommendedHomeFile) - migrationRules[RecommendedHomeFile] = OldRecommendedHomeFile - } return &ClientConfigLoadingRules{ Precedence: chain, - MigrationRules: migrationRules, + MigrationRules: currentMigrationRules(), } } @@ -117,23 +130,41 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) { } else { kubeConfigFiles = append(kubeConfigFiles, rules.Precedence...) + } + kubeconfigs := []*clientcmdapi.Config{} + // read and cache the config files so that we only look at them once + for _, filename := range kubeConfigFiles { + if len(filename) == 0 { + // no work to do + continue + } + + config, err := LoadFromFile(filename) + if os.IsNotExist(err) { + // skip missing files + continue + } + if err != nil { + errlist = append(errlist, fmt.Errorf("Error loading config file \"%s\": %v", filename, err)) + continue + } + + kubeconfigs = append(kubeconfigs, config) } // first merge all of our maps mapConfig := clientcmdapi.NewConfig() - for _, file := range kubeConfigFiles { - if err := mergeConfigWithFile(mapConfig, file); err != nil { - errlist = append(errlist, err) - } + for _, kubeconfig := range kubeconfigs { + mergo.Merge(mapConfig, kubeconfig) } // merge all of the struct values in the reverse order so that priority is given correctly // errors are not added to the list the second time nonMapConfig := clientcmdapi.NewConfig() - for i := len(kubeConfigFiles) - 1; i >= 0; i-- { - file := kubeConfigFiles[i] - mergeConfigWithFile(nonMapConfig, file) + for i := len(kubeconfigs) - 1; i >= 0; i-- { + kubeconfig := kubeconfigs[i] + mergo.Merge(nonMapConfig, kubeconfig) } // since values are overwritten, but maps values are not, we can merge the non-map config on top of the map config and @@ -198,25 +229,6 @@ func (rules *ClientConfigLoadingRules) Migrate() error { return nil } -func mergeConfigWithFile(startingConfig *clientcmdapi.Config, filename string) error { - if len(filename) == 0 { - // no work to do - return nil - } - - config, err := LoadFromFile(filename) - if os.IsNotExist(err) { - return nil - } - if err != nil { - return fmt.Errorf("Error loading config file \"%s\": %v", filename, err) - } - - mergo.Merge(startingConfig, config) - - return nil -} - // LoadFromFile takes a filename and deserializes the contents into Config object func LoadFromFile(filename string) (*clientcmdapi.Config, error) { kubeconfigBytes, err := ioutil.ReadFile(filename) diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/loader_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/loader_test.go index 0921f7043..ad79c7b81 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/loader_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/loader_test.go @@ -376,7 +376,7 @@ func TestMigratingFileSourceMissingSkip(t *testing.T) { } } -func ExampleNoMergingOnExplicitPaths() { +func Example_noMergingOnExplicitPaths() { commandLineFile, _ := ioutil.TempFile("", "") defer os.Remove(commandLineFile.Name()) envVarFile, _ := ioutil.TempFile("", "") @@ -423,7 +423,7 @@ func ExampleNoMergingOnExplicitPaths() { // token: red-token } -func ExampleMergingSomeWithConflict() { +func Example_mergingSomeWithConflict() { commandLineFile, _ := ioutil.TempFile("", "") defer os.Remove(commandLineFile.Name()) envVarFile, _ := ioutil.TempFile("", "") @@ -476,7 +476,7 @@ func ExampleMergingSomeWithConflict() { // token: yellow-token } -func ExampleMergingEverythingNoConflicts() { +func Example_mergingEverythingNoConflicts() { commandLineFile, _ := ioutil.TempFile("", "") defer os.Remove(commandLineFile.Name()) envVarFile, _ := ioutil.TempFile("", "") diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/merged_client_builder.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/merged_client_builder.go index 2888981f9..321eae9e8 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/merged_client_builder.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/merged_client_builder.go @@ -19,10 +19,11 @@ package clientcmd import ( "io" "reflect" + "sync" "github.com/golang/glog" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" ) @@ -35,35 +36,47 @@ type DeferredLoadingClientConfig struct { loadingRules *ClientConfigLoadingRules overrides *ConfigOverrides fallbackReader io.Reader + + clientConfig ClientConfig + loadingLock sync.Mutex } // NewNonInteractiveDeferredLoadingClientConfig creates a ConfigClientClientConfig using the passed context name func NewNonInteractiveDeferredLoadingClientConfig(loadingRules *ClientConfigLoadingRules, overrides *ConfigOverrides) ClientConfig { - return DeferredLoadingClientConfig{loadingRules, overrides, nil} + return &DeferredLoadingClientConfig{loadingRules: loadingRules, overrides: overrides} } // NewInteractiveDeferredLoadingClientConfig creates a ConfigClientClientConfig using the passed context name and the fallback auth reader func NewInteractiveDeferredLoadingClientConfig(loadingRules *ClientConfigLoadingRules, overrides *ConfigOverrides, fallbackReader io.Reader) ClientConfig { - return DeferredLoadingClientConfig{loadingRules, overrides, fallbackReader} + return &DeferredLoadingClientConfig{loadingRules: loadingRules, overrides: overrides, fallbackReader: fallbackReader} } -func (config DeferredLoadingClientConfig) createClientConfig() (ClientConfig, error) { - mergedConfig, err := config.loadingRules.Load() - if err != nil { - return nil, err +func (config *DeferredLoadingClientConfig) createClientConfig() (ClientConfig, error) { + if config.clientConfig == nil { + config.loadingLock.Lock() + defer config.loadingLock.Unlock() + + if config.clientConfig == nil { + mergedConfig, err := config.loadingRules.Load() + if err != nil { + return nil, err + } + + var mergedClientConfig ClientConfig + if config.fallbackReader != nil { + mergedClientConfig = NewInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides, config.fallbackReader) + } else { + mergedClientConfig = NewNonInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides) + } + + config.clientConfig = mergedClientConfig + } } - var mergedClientConfig ClientConfig - if config.fallbackReader != nil { - mergedClientConfig = NewInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides, config.fallbackReader) - } else { - mergedClientConfig = NewNonInteractiveClientConfig(*mergedConfig, config.overrides.CurrentContext, config.overrides) - } - - return mergedClientConfig, nil + return config.clientConfig, nil } -func (config DeferredLoadingClientConfig) RawConfig() (clientcmdapi.Config, error) { +func (config *DeferredLoadingClientConfig) RawConfig() (clientcmdapi.Config, error) { mergedConfig, err := config.createClientConfig() if err != nil { return clientcmdapi.Config{}, err @@ -73,7 +86,7 @@ func (config DeferredLoadingClientConfig) RawConfig() (clientcmdapi.Config, erro } // ClientConfig implements ClientConfig -func (config DeferredLoadingClientConfig) ClientConfig() (*client.Config, error) { +func (config *DeferredLoadingClientConfig) ClientConfig() (*restclient.Config, error) { mergedClientConfig, err := config.createClientConfig() if err != nil { return nil, err @@ -94,7 +107,7 @@ func (config DeferredLoadingClientConfig) ClientConfig() (*client.Config, error) } // Namespace implements KubeConfig -func (config DeferredLoadingClientConfig) Namespace() (string, bool, error) { +func (config *DeferredLoadingClientConfig) Namespace() (string, bool, error) { mergedKubeConfig, err := config.createClientConfig() if err != nil { return "", false, err diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/overrides.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/overrides.go index d6bc496a2..9996d2f44 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/overrides.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/overrides.go @@ -134,7 +134,7 @@ func RecommendedAuthOverrideFlags(prefix string) AuthOverrideFlags { func RecommendedClusterOverrideFlags(prefix string) ClusterOverrideFlags { return ClusterOverrideFlags{ APIServer: FlagInfo{prefix + FlagAPIServer, "", "", "The address and port of the Kubernetes API server"}, - APIVersion: FlagInfo{prefix + FlagAPIVersion, "", "", "The API version to use when talking to the server"}, + APIVersion: FlagInfo{prefix + FlagAPIVersion, "", "", "DEPRECATED: The API version to use when talking to the server"}, CertificateAuthority: FlagInfo{prefix + FlagCAFile, "", "", "Path to a cert. file for the certificate authority."}, InsecureSkipTLSVerify: FlagInfo{prefix + FlagInsecure, "", "false", "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure."}, } @@ -171,7 +171,9 @@ func BindAuthInfoFlags(authInfo *clientcmdapi.AuthInfo, flags *pflag.FlagSet, fl // BindClusterFlags is a convenience method to bind the specified flags to their associated variables func BindClusterFlags(clusterInfo *clientcmdapi.Cluster, flags *pflag.FlagSet, flagNames ClusterOverrideFlags) { flagNames.APIServer.BindStringFlag(flags, &clusterInfo.Server) + // TODO: remove --api-version flag in 1.3. flagNames.APIVersion.BindStringFlag(flags, &clusterInfo.APIVersion) + flags.MarkDeprecated(FlagAPIVersion, "flag is no longer respected and will be deleted in the next release") flagNames.CertificateAuthority.BindStringFlag(flags, &clusterInfo.CertificateAuthority) flagNames.InsecureSkipTLSVerify.BindBoolFlag(flags, &clusterInfo.InsecureSkipTLSVerify) } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/conditions.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/conditions.go index 7102c8116..5087baa80 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/conditions.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/conditions.go @@ -153,12 +153,18 @@ func JobHasDesiredParallelism(c ExtensionsInterface, job *extensions.Job) wait.C // the desired replica count for a deployment equals its updated replicas count. // (non-terminated pods that have the desired template spec). func DeploymentHasDesiredReplicas(c ExtensionsInterface, deployment *extensions.Deployment) wait.ConditionFunc { + // If we're given a deployment where the status lags the spec, it either + // means that the deployment is stale, or that the deployment manager hasn't + // noticed the update yet. Polling status.Replicas is not safe in the latter + // case. + desiredGeneration := deployment.Generation return func() (bool, error) { deployment, err := c.Deployments(deployment.Namespace).Get(deployment.Name) if err != nil { return false, err } - return deployment.Status.UpdatedReplicas == deployment.Spec.Replicas, nil + return deployment.Status.ObservedGeneration >= desiredGeneration && + deployment.Status.UpdatedReplicas == deployment.Spec.Replicas, nil } } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/daemon_sets_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/daemon_sets_test.go index 2bfbb44b0..f453a9138 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/daemon_sets_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/daemon_sets_test.go @@ -17,7 +17,6 @@ limitations under the License. package unversioned_test import ( - . "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/deployment_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/deployment_test.go index 2480c3a25..c530411a7 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/deployment_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/deployment_test.go @@ -16,11 +16,6 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "net/http" "net/url" @@ -30,6 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" "k8s.io/kubernetes/pkg/labels" ) diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/endpoints_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/endpoints_test.go index ef1701585..59bc869b8 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/endpoints_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/endpoints_test.go @@ -16,16 +16,12 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "testing" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) func TestListEndpoints(t *testing.T) { diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/extensions.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/extensions.go index a95bd1b89..5db86dbb8 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/extensions.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/extensions.go @@ -20,6 +20,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/restclient" ) // Interface holds the experimental methods for clients of Kubernetes @@ -42,7 +43,7 @@ type ExtensionsInterface interface { // Features of Extensions group are not supported and may be changed or removed in // incompatible ways at any time. type ExtensionsClient struct { - *RESTClient + *restclient.RESTClient } func (c *ExtensionsClient) PodSecurityPolicies() PodSecurityPolicyInterface { @@ -85,12 +86,12 @@ func (c *ExtensionsClient) ReplicaSets(namespace string) ReplicaSetInterface { // provides access to experimental Kubernetes features. // Features of Extensions group are not supported and may be changed or removed in // incompatible ways at any time. -func NewExtensions(c *Config) (*ExtensionsClient, error) { +func NewExtensions(c *restclient.Config) (*ExtensionsClient, error) { config := *c if err := setExtensionsDefaults(&config); err != nil { return nil, err } - client, err := RESTClientFor(&config) + client, err := restclient.RESTClientFor(&config) if err != nil { return nil, err } @@ -101,7 +102,7 @@ func NewExtensions(c *Config) (*ExtensionsClient, error) { // panics if there is an error in the config. // Features of Extensions group are not supported and may be changed or removed in // incompatible ways at any time. -func NewExtensionsOrDie(c *Config) *ExtensionsClient { +func NewExtensionsOrDie(c *restclient.Config) *ExtensionsClient { client, err := NewExtensions(c) if err != nil { panic(err) @@ -109,7 +110,7 @@ func NewExtensionsOrDie(c *Config) *ExtensionsClient { return client } -func setExtensionsDefaults(config *Config) error { +func setExtensionsDefaults(config *restclient.Config) error { // if experimental group is not registered, return an error g, err := registered.Group(extensions.GroupName) if err != nil { @@ -117,7 +118,7 @@ func setExtensionsDefaults(config *Config) error { } config.APIPath = defaultAPIPath if config.UserAgent == "" { - config.UserAgent = DefaultKubernetesUserAgent() + config.UserAgent = restclient.DefaultKubernetesUserAgent() } // TODO: Unconditionally set the config.Version, until we fix the config. //if config.Version == "" { diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/fake/fake.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/fake/fake.go index 3a314708f..09f1f0274 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/fake/fake.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/fake/fake.go @@ -24,7 +24,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" - "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/runtime" ) @@ -49,28 +49,28 @@ type RESTClient struct { Err error } -func (c *RESTClient) Get() *unversioned.Request { +func (c *RESTClient) Get() *restclient.Request { return c.request("GET") } -func (c *RESTClient) Put() *unversioned.Request { +func (c *RESTClient) Put() *restclient.Request { return c.request("PUT") } -func (c *RESTClient) Patch(_ api.PatchType) *unversioned.Request { +func (c *RESTClient) Patch(_ api.PatchType) *restclient.Request { return c.request("PATCH") } -func (c *RESTClient) Post() *unversioned.Request { +func (c *RESTClient) Post() *restclient.Request { return c.request("POST") } -func (c *RESTClient) Delete() *unversioned.Request { +func (c *RESTClient) Delete() *restclient.Request { return c.request("DELETE") } -func (c *RESTClient) request(verb string) *unversioned.Request { - return unversioned.NewRequest(c, verb, &url.URL{Host: "localhost"}, "", unversioned.ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: c.Codec}, nil, nil) +func (c *RESTClient) request(verb string) *restclient.Request { + return restclient.NewRequest(c, verb, &url.URL{Host: "localhost"}, "", restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: c.Codec}, nil, nil) } func (c *RESTClient) Do(req *http.Request) (*http.Response, error) { diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper.go index 507f9a30a..953b57463 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper.go @@ -17,25 +17,16 @@ limitations under the License. package unversioned import ( - "encoding/json" "fmt" - "io/ioutil" - "net" - "net/http" - "net/url" - "os" - "path" - "reflect" - gruntime "runtime" - "strings" - "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/apis/autoscaling" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" - "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/typed/discovery" "k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/version" ) @@ -45,132 +36,60 @@ const ( defaultAPIPath = "/apis" ) -// Config holds the common attributes that can be passed to a Kubernetes client on -// initialization. -type Config struct { - // Host must be a host string, a host:port pair, or a URL to the base of the apiserver. - // If a URL is given then the (optional) Path of that URL represents a prefix that must - // be appended to all request URIs used to access the apiserver. This allows a frontend - // proxy to easily relocate all of the apiserver endpoints. - Host string - // APIPath is a sub-path that points to an API root. - APIPath string - // Prefix is the sub path of the server. If not specified, the client will set - // a default value. Use "/" to indicate the server root should be used - Prefix string - - // ContentConfig contains settings that affect how objects are transformed when - // sent to the server. - ContentConfig - - // Server requires Basic authentication - Username string - Password string - - // Server requires Bearer authentication. This client will not attempt to use - // refresh tokens for an OAuth2 flow. - // TODO: demonstrate an OAuth2 compatible client. - BearerToken string - - // TLSClientConfig contains settings to enable transport layer security - TLSClientConfig - - // Server should be accessed without verifying the TLS - // certificate. For testing only. - Insecure bool - - // UserAgent is an optional field that specifies the caller of this request. - UserAgent string - - // Transport may be used for custom HTTP behavior. This attribute may not - // be specified with the TLS client certificate options. Use WrapTransport - // for most client level operations. - Transport http.RoundTripper - // WrapTransport will be invoked for custom HTTP behavior after the underlying - // transport is initialized (either the transport created from TLSClientConfig, - // Transport, or http.DefaultTransport). The config may layer other RoundTrippers - // on top of the returned RoundTripper. - WrapTransport func(rt http.RoundTripper) http.RoundTripper - - // QPS indicates the maximum QPS to the master from this client. If zero, QPS is unlimited. - QPS float32 - - // Maximum burst for throttle - Burst int -} - -// TLSClientConfig contains settings to enable transport layer security -type TLSClientConfig struct { - // Server requires TLS client certificate authentication - CertFile string - // Server requires TLS client certificate authentication - KeyFile string - // Trusted root certificates for server - CAFile string - - // CertData holds PEM-encoded bytes (typically read from a client certificate file). - // CertData takes precedence over CertFile - CertData []byte - // KeyData holds PEM-encoded bytes (typically read from a client certificate key file). - // KeyData takes precedence over KeyFile - KeyData []byte - // CAData holds PEM-encoded bytes (typically read from a root certificates bundle). - // CAData takes precedence over CAFile - CAData []byte -} - -type ContentConfig struct { - // ContentType specifies the wire format used to communicate with the server. - // This value will be set as the Accept header on requests made to the server, and - // as the default content type on any object sent to the server. If not set, - // "application/json" is used. - ContentType string - // GroupVersion is the API version to talk to. Must be provided when initializing - // a RESTClient directly. When initializing a Client, will be set with the default - // code version. - GroupVersion *unversioned.GroupVersion - // Codec specifies the encoding and decoding behavior for runtime.Objects passed - // to a RESTClient or Client. Required when initializing a RESTClient, optional - // when initializing a Client. - Codec runtime.Codec -} - // New creates a Kubernetes client for the given config. This client works with pods, // replication controllers, daemons, and services. It allows operations such as list, get, update // and delete on these objects. An error is returned if the provided configuration // is not valid. -func New(c *Config) (*Client, error) { +func New(c *restclient.Config) (*Client, error) { config := *c if err := SetKubernetesDefaults(&config); err != nil { return nil, err } - client, err := RESTClientFor(&config) + client, err := restclient.RESTClientFor(&config) if err != nil { return nil, err } discoveryConfig := *c - discoveryClient, err := NewDiscoveryClientForConfig(&discoveryConfig) + discoveryClient, err := discovery.NewDiscoveryClientForConfig(&discoveryConfig) if err != nil { return nil, err } - if _, err := registered.Group(extensions.GroupName); err != nil { - return &Client{RESTClient: client, ExtensionsClient: nil, DiscoveryClient: discoveryClient}, nil - } - experimentalConfig := *c - experimentalClient, err := NewExtensions(&experimentalConfig) - if err != nil { - return nil, err + var autoscalingClient *AutoscalingClient + if registered.IsRegistered(autoscaling.GroupName) { + autoscalingConfig := *c + autoscalingClient, err = NewAutoscaling(&autoscalingConfig) + if err != nil { + return nil, err + } } - return &Client{RESTClient: client, ExtensionsClient: experimentalClient, DiscoveryClient: discoveryClient}, nil + var batchClient *BatchClient + if registered.IsRegistered(batch.GroupName) { + batchConfig := *c + batchClient, err = NewBatch(&batchConfig) + if err != nil { + return nil, err + } + } + + var extensionsClient *ExtensionsClient + if registered.IsRegistered(extensions.GroupName) { + extensionsConfig := *c + extensionsClient, err = NewExtensions(&extensionsConfig) + if err != nil { + return nil, err + } + } + + return &Client{RESTClient: client, AutoscalingClient: autoscalingClient, BatchClient: batchClient, ExtensionsClient: extensionsClient, DiscoveryClient: discoveryClient}, nil } // MatchesServerVersion queries the server to compares the build version // (git hash) of the client with the server's build version. It returns an error // if it failed to contact the server or if the versions are not an exact match. -func MatchesServerVersion(client *Client, c *Config) error { +func MatchesServerVersion(client *Client, c *restclient.Config) error { var err error if client == nil { client, err = New(c) @@ -178,78 +97,19 @@ func MatchesServerVersion(client *Client, c *Config) error { return err } } - clientVersion := version.Get() - serverVersion, err := client.Discovery().ServerVersion() + cVer := version.Get() + sVer, err := client.Discovery().ServerVersion() if err != nil { return fmt.Errorf("couldn't read version from server: %v\n", err) } - if s := *serverVersion; !reflect.DeepEqual(clientVersion, s) { - return fmt.Errorf("server version (%#v) differs from client version (%#v)!\n", s, clientVersion) + // GitVersion includes GitCommit and GitTreeState, but best to be safe? + if cVer.GitVersion != sVer.GitVersion || cVer.GitCommit != sVer.GitCommit || cVer.GitTreeState != cVer.GitTreeState { + return fmt.Errorf("server version (%#v) differs from client version (%#v)!\n", sVer, cVer) } return nil } -func ExtractGroupVersions(l *unversioned.APIGroupList) []string { - var groupVersions []string - for _, g := range l.Groups { - for _, gv := range g.Versions { - groupVersions = append(groupVersions, gv.GroupVersion) - } - } - return groupVersions -} - -// ServerAPIVersions returns the GroupVersions supported by the API server. -// It creates a RESTClient based on the passed in config, but it doesn't rely -// on the Version and Codec of the config, because it uses AbsPath and -// takes the raw response. -func ServerAPIVersions(c *Config) (groupVersions []string, err error) { - transport, err := TransportFor(c) - if err != nil { - return nil, err - } - client := http.Client{Transport: transport} - - configCopy := *c - configCopy.GroupVersion = nil - configCopy.APIPath = "" - baseURL, _, err := defaultServerUrlFor(&configCopy) - if err != nil { - return nil, err - } - // Get the groupVersions exposed at /api - originalPath := baseURL.Path - baseURL.Path = path.Join(originalPath, legacyAPIPath) - resp, err := client.Get(baseURL.String()) - if err != nil { - return nil, err - } - var v unversioned.APIVersions - defer resp.Body.Close() - err = json.NewDecoder(resp.Body).Decode(&v) - if err != nil { - return nil, fmt.Errorf("unexpected error: %v", err) - } - - groupVersions = append(groupVersions, v.Versions...) - // Get the groupVersions exposed at /apis - baseURL.Path = path.Join(originalPath, defaultAPIPath) - resp2, err := client.Get(baseURL.String()) - if err != nil { - return nil, err - } - var apiGroupList unversioned.APIGroupList - defer resp2.Body.Close() - err = json.NewDecoder(resp2.Body).Decode(&apiGroupList) - if err != nil { - return nil, fmt.Errorf("unexpected error: %v", err) - } - groupVersions = append(groupVersions, ExtractGroupVersions(&apiGroupList)...) - - return groupVersions, nil -} - // NegotiateVersion queries the server's supported api versions to find // a version that both client and server support. // - If no version is provided, try registered client versions in order of @@ -259,7 +119,7 @@ func ServerAPIVersions(c *Config) (groupVersions []string, err error) { // stderr and try client's registered versions in order of preference. // - If version is config default, and the server does not support it, // return an error. -func NegotiateVersion(client *Client, c *Config, requestedGV *unversioned.GroupVersion, clientRegisteredGVs []unversioned.GroupVersion) (*unversioned.GroupVersion, error) { +func NegotiateVersion(client *Client, c *restclient.Config, requestedGV *unversioned.GroupVersion, clientRegisteredGVs []unversioned.GroupVersion) (*unversioned.GroupVersion, error) { var err error if client == nil { client, err = New(c) @@ -277,7 +137,7 @@ func NegotiateVersion(client *Client, c *Config, requestedGV *unversioned.GroupV // not a negotiation specific error. return nil, err } - versions := ExtractGroupVersions(groups) + versions := unversioned.ExtractGroupVersions(groups) serverVersions := sets.String{} for _, v := range versions { serverVersions.Insert(v) @@ -329,7 +189,7 @@ func NegotiateVersion(client *Client, c *Config, requestedGV *unversioned.GroupV } // NewOrDie creates a Kubernetes client and panics if the provided API version is not recognized. -func NewOrDie(c *Config) *Client { +func NewOrDie(c *restclient.Config) *Client { client, err := New(c) if err != nil { panic(err) @@ -337,39 +197,9 @@ func NewOrDie(c *Config) *Client { return client } -// InClusterConfig returns a config object which uses the service account -// kubernetes gives to pods. It's intended for clients that expect to be -// running inside a pod running on kuberenetes. It will return an error if -// called from a process not running in a kubernetes environment. -func InClusterConfig() (*Config, error) { - host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT") - if len(host) == 0 || len(port) == 0 { - return nil, fmt.Errorf("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined") - } - - token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountTokenKey) - if err != nil { - return nil, err - } - tlsClientConfig := TLSClientConfig{} - rootCAFile := "/var/run/secrets/kubernetes.io/serviceaccount/" + api.ServiceAccountRootCAKey - if _, err := util.CertPoolFromFile(rootCAFile); err != nil { - glog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err) - } else { - tlsClientConfig.CAFile = rootCAFile - } - - return &Config{ - // TODO: switch to using cluster DNS. - Host: "https://" + net.JoinHostPort(host, port), - BearerToken: string(token), - TLSClientConfig: tlsClientConfig, - }, nil -} - // NewInCluster is a shortcut for calling InClusterConfig() and then New(). func NewInCluster() (*Client, error) { - cc, err := InClusterConfig() + cc, err := restclient.InClusterConfig() if err != nil { return nil, err } @@ -379,13 +209,10 @@ func NewInCluster() (*Client, error) { // SetKubernetesDefaults sets default values on the provided client config for accessing the // Kubernetes API or returns an error if any of the defaults are impossible or invalid. // TODO: this method needs to be split into one that sets defaults per group, expected to be fix in PR "Refactoring clientcache.go and helper.go #14592" -func SetKubernetesDefaults(config *Config) error { +func SetKubernetesDefaults(config *restclient.Config) error { if config.APIPath == "" { config.APIPath = legacyAPIPath } - if len(config.UserAgent) == 0 { - config.UserAgent = DefaultKubernetesUserAgent() - } g, err := registered.Group(api.GroupName) if err != nil { return err @@ -396,233 +223,6 @@ func SetKubernetesDefaults(config *Config) error { if config.Codec == nil { config.Codec = api.Codecs.LegacyCodec(*config.GroupVersion) } - if config.QPS == 0.0 { - config.QPS = 5.0 - } - if config.Burst == 0 { - config.Burst = 10 - } - return nil -} -// RESTClientFor returns a RESTClient that satisfies the requested attributes on a client Config -// object. Note that a RESTClient may require fields that are optional when initializing a Client. -// A RESTClient created by this method is generic - it expects to operate on an API that follows -// the Kubernetes conventions, but may not be the Kubernetes API. -func RESTClientFor(config *Config) (*RESTClient, error) { - if config.GroupVersion == nil { - return nil, fmt.Errorf("GroupVersion is required when initializing a RESTClient") - } - if config.Codec == nil { - return nil, fmt.Errorf("Codec is required when initializing a RESTClient") - } - - baseURL, versionedAPIPath, err := defaultServerUrlFor(config) - if err != nil { - return nil, err - } - - transport, err := TransportFor(config) - if err != nil { - return nil, err - } - - var httpClient *http.Client - if transport != http.DefaultTransport { - httpClient = &http.Client{Transport: transport} - } - - client := NewRESTClient(baseURL, versionedAPIPath, config.ContentConfig, config.QPS, config.Burst, httpClient) - - return client, nil -} - -// UnversionedRESTClientFor is the same as RESTClientFor, except that it allows -// the config.Version to be empty. -func UnversionedRESTClientFor(config *Config) (*RESTClient, error) { - if config.Codec == nil { - return nil, fmt.Errorf("Codec is required when initializing a RESTClient") - } - - baseURL, versionedAPIPath, err := defaultServerUrlFor(config) - if err != nil { - return nil, err - } - - transport, err := TransportFor(config) - if err != nil { - return nil, err - } - - var httpClient *http.Client - if transport != http.DefaultTransport { - httpClient = &http.Client{Transport: transport} - } - - versionConfig := config.ContentConfig - if versionConfig.GroupVersion == nil { - v := unversioned.SchemeGroupVersion - versionConfig.GroupVersion = &v - } - - client := NewRESTClient(baseURL, versionedAPIPath, versionConfig, config.QPS, config.Burst, httpClient) - return client, nil -} - -// DefaultServerURL converts a host, host:port, or URL string to the default base server API path -// to use with a Client at a given API version following the standard conventions for a -// Kubernetes API. -func DefaultServerURL(host, apiPath string, groupVersion unversioned.GroupVersion, defaultTLS bool) (*url.URL, string, error) { - if host == "" { - return nil, "", fmt.Errorf("host must be a URL or a host:port pair") - } - base := host - hostURL, err := url.Parse(base) - if err != nil { - return nil, "", err - } - if hostURL.Scheme == "" { - scheme := "http://" - if defaultTLS { - scheme = "https://" - } - hostURL, err = url.Parse(scheme + base) - if err != nil { - return nil, "", err - } - if hostURL.Path != "" && hostURL.Path != "/" { - return nil, "", fmt.Errorf("host must be a URL or a host:port pair: %q", base) - } - } - - // hostURL.Path is optional; a non-empty Path is treated as a prefix that is to be applied to - // all URIs used to access the host. this is useful when there's a proxy in front of the - // apiserver that has relocated the apiserver endpoints, forwarding all requests from, for - // example, /a/b/c to the apiserver. in this case the Path should be /a/b/c. - // - // if running without a frontend proxy (that changes the location of the apiserver), then - // hostURL.Path should be blank. - // - // versionedAPIPath, a path relative to baseURL.Path, points to a versioned API base - versionedAPIPath := path.Join("/", apiPath) - - // Add the version to the end of the path - if len(groupVersion.Group) > 0 { - versionedAPIPath = path.Join(versionedAPIPath, groupVersion.Group, groupVersion.Version) - - } else { - versionedAPIPath = path.Join(versionedAPIPath, groupVersion.Version) - - } - - return hostURL, versionedAPIPath, nil -} - -// IsConfigTransportTLS returns true if and only if the provided config will result in a protected -// connection to the server when it is passed to client.New() or client.RESTClientFor(). -// Use to determine when to send credentials over the wire. -// -// Note: the Insecure flag is ignored when testing for this value, so MITM attacks are -// still possible. -func IsConfigTransportTLS(config Config) bool { - // determination of TLS transport does not logically require a version to be specified - // modify the copy of the config we got to satisfy preconditions for defaultServerUrlFor - config.GroupVersion = defaultVersionFor(&config) - - baseURL, _, err := defaultServerUrlFor(&config) - if err != nil { - return false - } - return baseURL.Scheme == "https" -} - -// defaultServerUrlFor is shared between IsConfigTransportTLS and RESTClientFor. It -// requires Host and Version to be set prior to being called. -func defaultServerUrlFor(config *Config) (*url.URL, string, error) { - // TODO: move the default to secure when the apiserver supports TLS by default - // config.Insecure is taken to mean "I want HTTPS but don't bother checking the certs against a CA." - hasCA := len(config.CAFile) != 0 || len(config.CAData) != 0 - hasCert := len(config.CertFile) != 0 || len(config.CertData) != 0 - defaultTLS := hasCA || hasCert || config.Insecure - host := config.Host - if host == "" { - host = "localhost" - } - - if config.GroupVersion != nil { - return DefaultServerURL(host, config.APIPath, *config.GroupVersion, defaultTLS) - } - return DefaultServerURL(host, config.APIPath, unversioned.GroupVersion{}, defaultTLS) -} - -// defaultVersionFor is shared between IsConfigTransportTLS and RESTClientFor -func defaultVersionFor(config *Config) *unversioned.GroupVersion { - if config.GroupVersion == nil { - // Clients default to the preferred code API version - // TODO: implement version negotiation (highest version supported by server) - // TODO this drops out when groupmeta is refactored - copyGroupVersion := registered.GroupOrDie(api.GroupName).GroupVersion - return ©GroupVersion - } - - return config.GroupVersion -} - -// DefaultKubernetesUserAgent returns the default user agent that clients can use. -func DefaultKubernetesUserAgent() string { - commit := version.Get().GitCommit - if len(commit) > 7 { - commit = commit[:7] - } - if len(commit) == 0 { - commit = "unknown" - } - version := version.Get().GitVersion - seg := strings.SplitN(version, "-", 2) - version = seg[0] - return fmt.Sprintf("%s/%s (%s/%s) kubernetes/%s", path.Base(os.Args[0]), version, gruntime.GOOS, gruntime.GOARCH, commit) -} - -// LoadTLSFiles copies the data from the CertFile, KeyFile, and CAFile fields into the CertData, -// KeyData, and CAFile fields, or returns an error. If no error is returned, all three fields are -// either populated or were empty to start. -func LoadTLSFiles(c *Config) error { - var err error - c.CAData, err = dataFromSliceOrFile(c.CAData, c.CAFile) - if err != nil { - return err - } - - c.CertData, err = dataFromSliceOrFile(c.CertData, c.CertFile) - if err != nil { - return err - } - - c.KeyData, err = dataFromSliceOrFile(c.KeyData, c.KeyFile) - if err != nil { - return err - } - return nil -} - -// dataFromSliceOrFile returns data from the slice (if non-empty), or from the file, -// or an error if an error occurred reading the file -func dataFromSliceOrFile(data []byte, file string) ([]byte, error) { - if len(data) > 0 { - return data, nil - } - if len(file) > 0 { - fileData, err := ioutil.ReadFile(file) - if err != nil { - return []byte{}, err - } - return fileData, nil - } - return nil, nil -} - -func AddUserAgent(config *Config, userAgent string) *Config { - fullUserAgent := DefaultKubernetesUserAgent() + "/" + userAgent - config.UserAgent = fullUserAgent - return config + return restclient.SetKubernetesDefaults(config) } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper_blackbox_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper_blackbox_test.go index 5d497d503..c4860a12f 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper_blackbox_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper_blackbox_test.go @@ -28,7 +28,7 @@ import ( "k8s.io/kubernetes/pkg/api/testapi" uapi "k8s.io/kubernetes/pkg/api/unversioned" - unversionedapi "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/fake" ) @@ -48,14 +48,14 @@ func TestNegotiateVersion(t *testing.T) { expectedVersion *uapi.GroupVersion serverVersions []string clientVersions []uapi.GroupVersion - config *unversioned.Config + config *restclient.Config expectErr func(err error) bool sendErr error }{ { name: "server supports client default", version: &uapi.GroupVersion{Version: "version1"}, - config: &unversioned.Config{}, + config: &restclient.Config{}, serverVersions: []string{"version1", testapi.Default.GroupVersion().String()}, clientVersions: []uapi.GroupVersion{{Version: "version1"}, *testapi.Default.GroupVersion()}, expectedVersion: &uapi.GroupVersion{Version: "version1"}, @@ -63,28 +63,28 @@ func TestNegotiateVersion(t *testing.T) { { name: "server falls back to client supported", version: testapi.Default.GroupVersion(), - config: &unversioned.Config{}, + config: &restclient.Config{}, serverVersions: []string{"version1"}, clientVersions: []uapi.GroupVersion{{Version: "version1"}, *testapi.Default.GroupVersion()}, expectedVersion: &uapi.GroupVersion{Version: "version1"}, }, { name: "explicit version supported", - config: &unversioned.Config{ContentConfig: unversioned.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}, + config: &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}, serverVersions: []string{"/version1", testapi.Default.GroupVersion().String()}, clientVersions: []uapi.GroupVersion{{Version: "version1"}, *testapi.Default.GroupVersion()}, expectedVersion: testapi.Default.GroupVersion(), }, { name: "explicit version not supported", - config: &unversioned.Config{ContentConfig: unversioned.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}, + config: &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}, serverVersions: []string{"version1"}, clientVersions: []uapi.GroupVersion{{Version: "version1"}, *testapi.Default.GroupVersion()}, expectErr: func(err error) bool { return strings.Contains(err.Error(), `server does not support API version "v1"`) }, }, { name: "connection refused error", - config: &unversioned.Config{ContentConfig: unversioned.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}, + config: &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}, serverVersions: []string{"version1"}, clientVersions: []uapi.GroupVersion{{Version: "version1"}, *testapi.Default.GroupVersion()}, sendErr: errors.New("connection refused"), @@ -98,13 +98,13 @@ func TestNegotiateVersion(t *testing.T) { Codec: codec, Resp: &http.Response{ StatusCode: 200, - Body: objBody(&unversionedapi.APIVersions{Versions: test.serverVersions}), + Body: objBody(&uapi.APIVersions{Versions: test.serverVersions}), }, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { if test.sendErr != nil { return nil, test.sendErr } - return &http.Response{StatusCode: 200, Body: objBody(&unversionedapi.APIVersions{Versions: test.serverVersions})}, nil + return &http.Response{StatusCode: 200, Body: objBody(&uapi.APIVersions{Versions: test.serverVersions})}, nil }), } c := unversioned.NewOrDie(test.config) diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper_test.go index 2d565afc2..b4d9a7644 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/helper_test.go @@ -20,80 +20,26 @@ import ( "encoding/json" "net/http" "net/http/httptest" - "path" "reflect" - "strings" "testing" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/runtime" ) -func TestIsConfigTransportTLS(t *testing.T) { - testCases := []struct { - Config *Config - TransportTLS bool - }{ - { - Config: &Config{}, - TransportTLS: false, - }, - { - Config: &Config{ - Host: "https://localhost", - }, - TransportTLS: true, - }, - { - Config: &Config{ - Host: "localhost", - TLSClientConfig: TLSClientConfig{ - CertFile: "foo", - }, - }, - TransportTLS: true, - }, - { - Config: &Config{ - Host: "///:://localhost", - TLSClientConfig: TLSClientConfig{ - CertFile: "foo", - }, - }, - TransportTLS: false, - }, - { - Config: &Config{ - Host: "1.2.3.4:567", - Insecure: true, - }, - TransportTLS: true, - }, - } - for _, testCase := range testCases { - if err := SetKubernetesDefaults(testCase.Config); err != nil { - t.Errorf("setting defaults failed for %#v: %v", testCase.Config, err) - continue - } - useTLS := IsConfigTransportTLS(*testCase.Config) - if testCase.TransportTLS != useTLS { - t.Errorf("expected %v for %#v", testCase.TransportTLS, testCase.Config) - } - } -} - func TestSetKubernetesDefaults(t *testing.T) { testCases := []struct { - Config Config - After Config + Config restclient.Config + After restclient.Config Err bool }{ { - Config{}, - Config{ + restclient.Config{}, + restclient.Config{ APIPath: "/api", - ContentConfig: ContentConfig{ + ContentConfig: restclient.ContentConfig{ GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.Codec(), }, @@ -104,10 +50,10 @@ func TestSetKubernetesDefaults(t *testing.T) { }, // Add this test back when we fixed config and SetKubernetesDefaults // { - // Config{ + // restclient.Config{ // GroupVersion: &unversioned.GroupVersion{Group: "not.a.group", Version: "not_an_api"}, // }, - // Config{}, + // restclient.Config{}, // true, // }, } @@ -131,16 +77,6 @@ func TestSetKubernetesDefaults(t *testing.T) { } } -func TestSetKubernetesDefaultsUserAgent(t *testing.T) { - config := &Config{} - if err := SetKubernetesDefaults(config); err != nil { - t.Errorf("unexpected error: %v", err) - } - if !strings.Contains(config.UserAgent, "kubernetes/") { - t.Errorf("no user agent set: %#v", config) - } -} - func TestHelperGetServerAPIVersions(t *testing.T) { expect := []string{"v1", "v2", "v3"} APIVersions := unversioned.APIVersions{Versions: expect} @@ -190,7 +126,7 @@ func TestHelperGetServerAPIVersions(t *testing.T) { })) // TODO: Uncomment when fix #19254 // defer server.Close() - got, err := ServerAPIVersions(&Config{Host: server.URL, ContentConfig: ContentConfig{GroupVersion: &unversioned.GroupVersion{Group: "invalid version", Version: "one"}, Codec: testapi.Default.Codec()}}) + got, err := restclient.ServerAPIVersions(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Group: "invalid version", Version: "one"}, Codec: testapi.Default.Codec()}}) if err != nil { t.Fatalf("unexpected encoding error: %v", err) } @@ -210,7 +146,19 @@ func TestSetsCodec(t *testing.T) { // "invalidVersion": {true, "", nil}, } for version, expected := range testCases { - client, err := New(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: version}}}) + conf := &restclient.Config{ + Host: "127.0.0.1", + ContentConfig: restclient.ContentConfig{ + GroupVersion: &unversioned.GroupVersion{Version: version}, + }, + } + + var versionedPath string + err := SetKubernetesDefaults(conf) + if err == nil { + _, versionedPath, err = restclient.DefaultServerURL(conf.Host, conf.APIPath, *conf.GroupVersion, false) + } + switch { case err == nil && expected.Err: t.Errorf("expected error but was nil") @@ -221,60 +169,11 @@ func TestSetsCodec(t *testing.T) { case err != nil: continue } - if e, a := expected.Prefix, client.RESTClient.versionedAPIPath; e != a { + if e, a := expected.Prefix, versionedPath; e != a { t.Errorf("expected %#v, got %#v", e, a) } - if e, a := expected.Codec, client.RESTClient.contentConfig.Codec; !reflect.DeepEqual(e, a) { + if e, a := expected.Codec, conf.Codec; !reflect.DeepEqual(e, a) { t.Errorf("expected %#v, got %#v", e, a) } } } - -func TestRESTClientRequires(t *testing.T) { - if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{Codec: testapi.Default.Codec()}}); err == nil { - t.Errorf("unexpected non-error") - } - if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}); err == nil { - t.Errorf("unexpected non-error") - } - if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: testapi.Default.GroupVersion(), Codec: testapi.Default.Codec()}}); err != nil { - t.Errorf("unexpected error: %v", err) - } -} - -func TestValidatesHostParameter(t *testing.T) { - testCases := []struct { - Host string - APIPath string - - URL string - Err bool - }{ - {"127.0.0.1", "", "http://127.0.0.1/" + testapi.Default.GroupVersion().Version, false}, - {"127.0.0.1:8080", "", "http://127.0.0.1:8080/" + testapi.Default.GroupVersion().Version, false}, - {"foo.bar.com", "", "http://foo.bar.com/" + testapi.Default.GroupVersion().Version, false}, - {"http://host/prefix", "", "http://host/prefix/" + testapi.Default.GroupVersion().Version, false}, - {"http://host", "", "http://host/" + testapi.Default.GroupVersion().Version, false}, - {"http://host", "/", "http://host/" + testapi.Default.GroupVersion().Version, false}, - {"http://host", "/other", "http://host/other/" + testapi.Default.GroupVersion().Version, false}, - {"host/server", "", "", true}, - } - for i, testCase := range testCases { - u, versionedAPIPath, err := DefaultServerURL(testCase.Host, testCase.APIPath, *testapi.Default.GroupVersion(), false) - switch { - case err == nil && testCase.Err: - t.Errorf("expected error but was nil") - continue - case err != nil && !testCase.Err: - t.Errorf("unexpected error %v", err) - continue - case err != nil: - continue - } - u.Path = path.Join(u.Path, versionedAPIPath) - if e, a := testCase.URL, u.String(); e != a { - t.Errorf("%d: expected host %s, got %s", i, e, a) - continue - } - } -} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/horizontalpodautoscaler.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/horizontalpodautoscaler.go index efdf82b70..a4efc232a 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/horizontalpodautoscaler.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/horizontalpodautoscaler.go @@ -101,3 +101,68 @@ func (c *horizontalPodAutoscalers) Watch(opts api.ListOptions) (watch.Interface, VersionedParams(&opts, api.ParameterCodec). Watch() } + +// horizontalPodAutoscalersV1 implements HorizontalPodAutoscalersNamespacer interface using AutoscalingClient internally +// TODO(piosz): get back to one client implementation once HPA will be graduated to GA completely +type horizontalPodAutoscalersV1 struct { + client *AutoscalingClient + ns string +} + +// newHorizontalPodAutoscalers returns a horizontalPodAutoscalers +func newHorizontalPodAutoscalersV1(c *AutoscalingClient, namespace string) *horizontalPodAutoscalersV1 { + return &horizontalPodAutoscalersV1{ + client: c, + ns: namespace, + } +} + +// List takes label and field selectors, and returns the list of horizontalPodAutoscalers that match those selectors. +func (c *horizontalPodAutoscalersV1) List(opts api.ListOptions) (result *extensions.HorizontalPodAutoscalerList, err error) { + result = &extensions.HorizontalPodAutoscalerList{} + err = c.client.Get().Namespace(c.ns).Resource("horizontalPodAutoscalers").VersionedParams(&opts, api.ParameterCodec).Do().Into(result) + return +} + +// Get takes the name of the horizontalPodAutoscaler, and returns the corresponding HorizontalPodAutoscaler object, and an error if it occurs +func (c *horizontalPodAutoscalersV1) Get(name string) (result *extensions.HorizontalPodAutoscaler, err error) { + result = &extensions.HorizontalPodAutoscaler{} + err = c.client.Get().Namespace(c.ns).Resource("horizontalPodAutoscalers").Name(name).Do().Into(result) + return +} + +// Delete takes the name of the horizontalPodAutoscaler and deletes it. Returns an error if one occurs. +func (c *horizontalPodAutoscalersV1) Delete(name string, options *api.DeleteOptions) error { + return c.client.Delete().Namespace(c.ns).Resource("horizontalPodAutoscalers").Name(name).Body(options).Do().Error() +} + +// Create takes the representation of a horizontalPodAutoscaler and creates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if it occurs. +func (c *horizontalPodAutoscalersV1) Create(horizontalPodAutoscaler *extensions.HorizontalPodAutoscaler) (result *extensions.HorizontalPodAutoscaler, err error) { + result = &extensions.HorizontalPodAutoscaler{} + err = c.client.Post().Namespace(c.ns).Resource("horizontalPodAutoscalers").Body(horizontalPodAutoscaler).Do().Into(result) + return +} + +// Update takes the representation of a horizontalPodAutoscaler and updates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if it occurs. +func (c *horizontalPodAutoscalersV1) Update(horizontalPodAutoscaler *extensions.HorizontalPodAutoscaler) (result *extensions.HorizontalPodAutoscaler, err error) { + result = &extensions.HorizontalPodAutoscaler{} + err = c.client.Put().Namespace(c.ns).Resource("horizontalPodAutoscalers").Name(horizontalPodAutoscaler.Name).Body(horizontalPodAutoscaler).Do().Into(result) + return +} + +// UpdateStatus takes the representation of a horizontalPodAutoscaler and updates it. Returns the server's representation of the horizontalPodAutoscaler, and an error, if it occurs. +func (c *horizontalPodAutoscalersV1) UpdateStatus(horizontalPodAutoscaler *extensions.HorizontalPodAutoscaler) (result *extensions.HorizontalPodAutoscaler, err error) { + result = &extensions.HorizontalPodAutoscaler{} + err = c.client.Put().Namespace(c.ns).Resource("horizontalPodAutoscalers").Name(horizontalPodAutoscaler.Name).SubResource("status").Body(horizontalPodAutoscaler).Do().Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested horizontalPodAutoscalers. +func (c *horizontalPodAutoscalersV1) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.client.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("horizontalPodAutoscalers"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/horizontalpodautoscaler_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/horizontalpodautoscaler_test.go index bd4c06c82..550dae6ec 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/horizontalpodautoscaler_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/horizontalpodautoscaler_test.go @@ -16,25 +16,35 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "net/url" "testing" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) func getHorizontalPodAutoscalersResoureName() string { return "horizontalpodautoscalers" } -func TestHorizontalPodAutoscalerCreate(t *testing.T) { +func getHPAClient(t *testing.T, c *simple.Client, ns, resourceGroup string) unversioned.HorizontalPodAutoscalerInterface { + switch resourceGroup { + case autoscaling.GroupName: + return c.Setup(t).Autoscaling().HorizontalPodAutoscalers(ns) + case extensions.GroupName: + return c.Setup(t).Extensions().HorizontalPodAutoscalers(ns) + default: + t.Fatalf("Unknown group %v", resourceGroup) + } + return nil +} + +func testHorizontalPodAutoscalerCreate(t *testing.T, group testapi.TestGroup, resourceGroup string) { ns := api.NamespaceDefault horizontalPodAutoscaler := extensions.HorizontalPodAutoscaler{ ObjectMeta: api.ObjectMeta{ @@ -45,14 +55,15 @@ func TestHorizontalPodAutoscalerCreate(t *testing.T) { c := &simple.Client{ Request: simple.Request{ Method: "POST", - Path: testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, ""), + Path: group.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, ""), Query: simple.BuildQueryValues(nil), Body: &horizontalPodAutoscaler, }, - Response: simple.Response{StatusCode: 200, Body: &horizontalPodAutoscaler}, + Response: simple.Response{StatusCode: 200, Body: &horizontalPodAutoscaler}, + ResourceGroup: resourceGroup, } - response, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).Create(&horizontalPodAutoscaler) + response, err := getHPAClient(t, c, ns, resourceGroup).Create(&horizontalPodAutoscaler) defer c.Close() if err != nil { t.Fatalf("unexpected error: %v", err) @@ -60,7 +71,12 @@ func TestHorizontalPodAutoscalerCreate(t *testing.T) { c.Validate(t, response, err) } -func TestHorizontalPodAutoscalerGet(t *testing.T) { +func TestHorizontalPodAutoscalerCreate(t *testing.T) { + testHorizontalPodAutoscalerCreate(t, testapi.Extensions, extensions.GroupName) + testHorizontalPodAutoscalerCreate(t, testapi.Autoscaling, autoscaling.GroupName) +} + +func testHorizontalPodAutoscalerGet(t *testing.T, group testapi.TestGroup, resourceGroup string) { ns := api.NamespaceDefault horizontalPodAutoscaler := &extensions.HorizontalPodAutoscaler{ ObjectMeta: api.ObjectMeta{ @@ -71,19 +87,25 @@ func TestHorizontalPodAutoscalerGet(t *testing.T) { c := &simple.Client{ Request: simple.Request{ Method: "GET", - Path: testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc"), + Path: group.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc"), Query: simple.BuildQueryValues(nil), Body: nil, }, - Response: simple.Response{StatusCode: 200, Body: horizontalPodAutoscaler}, + Response: simple.Response{StatusCode: 200, Body: horizontalPodAutoscaler}, + ResourceGroup: resourceGroup, } - response, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).Get("abc") + response, err := getHPAClient(t, c, ns, resourceGroup).Get("abc") defer c.Close() c.Validate(t, response, err) } -func TestHorizontalPodAutoscalerList(t *testing.T) { +func TestHorizontalPodAutoscalerGet(t *testing.T) { + testHorizontalPodAutoscalerGet(t, testapi.Extensions, extensions.GroupName) + testHorizontalPodAutoscalerGet(t, testapi.Autoscaling, autoscaling.GroupName) +} + +func testHorizontalPodAutoscalerList(t *testing.T, group testapi.TestGroup, resourceGroup string) { ns := api.NamespaceDefault horizontalPodAutoscalerList := &extensions.HorizontalPodAutoscalerList{ Items: []extensions.HorizontalPodAutoscaler{ @@ -98,18 +120,48 @@ func TestHorizontalPodAutoscalerList(t *testing.T) { c := &simple.Client{ Request: simple.Request{ Method: "GET", - Path: testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, ""), + Path: group.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, ""), Query: simple.BuildQueryValues(nil), Body: nil, }, - Response: simple.Response{StatusCode: 200, Body: horizontalPodAutoscalerList}, + Response: simple.Response{StatusCode: 200, Body: horizontalPodAutoscalerList}, + ResourceGroup: resourceGroup, } - response, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).List(api.ListOptions{}) + response, err := getHPAClient(t, c, ns, resourceGroup).List(api.ListOptions{}) + defer c.Close() + c.Validate(t, response, err) +} + +func TestHorizontalPodAutoscalerList(t *testing.T) { + testHorizontalPodAutoscalerList(t, testapi.Extensions, extensions.GroupName) + testHorizontalPodAutoscalerList(t, testapi.Autoscaling, autoscaling.GroupName) +} + +func testHorizontalPodAutoscalerUpdate(t *testing.T, group testapi.TestGroup, resourceGroup string) { + ns := api.NamespaceDefault + horizontalPodAutoscaler := &extensions.HorizontalPodAutoscaler{ + ObjectMeta: api.ObjectMeta{ + Name: "abc", + Namespace: ns, + ResourceVersion: "1", + }, + } + c := &simple.Client{ + Request: simple.Request{Method: "PUT", Path: group.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc"), Query: simple.BuildQueryValues(nil)}, + Response: simple.Response{StatusCode: 200, Body: horizontalPodAutoscaler}, + ResourceGroup: resourceGroup, + } + response, err := getHPAClient(t, c, ns, resourceGroup).Update(horizontalPodAutoscaler) defer c.Close() c.Validate(t, response, err) } func TestHorizontalPodAutoscalerUpdate(t *testing.T) { + testHorizontalPodAutoscalerUpdate(t, testapi.Extensions, extensions.GroupName) + testHorizontalPodAutoscalerUpdate(t, testapi.Autoscaling, autoscaling.GroupName) +} + +func testHorizontalPodAutoscalerUpdateStatus(t *testing.T, group testapi.TestGroup, resourceGroup string) { ns := api.NamespaceDefault horizontalPodAutoscaler := &extensions.HorizontalPodAutoscaler{ ObjectMeta: api.ObjectMeta{ @@ -119,52 +171,52 @@ func TestHorizontalPodAutoscalerUpdate(t *testing.T) { }, } c := &simple.Client{ - Request: simple.Request{Method: "PUT", Path: testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc"), Query: simple.BuildQueryValues(nil)}, - Response: simple.Response{StatusCode: 200, Body: horizontalPodAutoscaler}, + Request: simple.Request{Method: "PUT", Path: group.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc") + "/status", Query: simple.BuildQueryValues(nil)}, + Response: simple.Response{StatusCode: 200, Body: horizontalPodAutoscaler}, + ResourceGroup: resourceGroup, } - response, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).Update(horizontalPodAutoscaler) + response, err := getHPAClient(t, c, ns, resourceGroup).UpdateStatus(horizontalPodAutoscaler) defer c.Close() c.Validate(t, response, err) } func TestHorizontalPodAutoscalerUpdateStatus(t *testing.T) { + testHorizontalPodAutoscalerUpdateStatus(t, testapi.Extensions, extensions.GroupName) + testHorizontalPodAutoscalerUpdateStatus(t, testapi.Autoscaling, autoscaling.GroupName) +} + +func testHorizontalPodAutoscalerDelete(t *testing.T, group testapi.TestGroup, resourceGroup string) { ns := api.NamespaceDefault - horizontalPodAutoscaler := &extensions.HorizontalPodAutoscaler{ - ObjectMeta: api.ObjectMeta{ - Name: "abc", - Namespace: ns, - ResourceVersion: "1", - }, - } c := &simple.Client{ - Request: simple.Request{Method: "PUT", Path: testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "abc") + "/status", Query: simple.BuildQueryValues(nil)}, - Response: simple.Response{StatusCode: 200, Body: horizontalPodAutoscaler}, + Request: simple.Request{Method: "DELETE", Path: group.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "foo"), Query: simple.BuildQueryValues(nil)}, + Response: simple.Response{StatusCode: 200}, + ResourceGroup: resourceGroup, } - response, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).UpdateStatus(horizontalPodAutoscaler) + err := getHPAClient(t, c, ns, resourceGroup).Delete("foo", nil) defer c.Close() - c.Validate(t, response, err) + c.Validate(t, nil, err) } func TestHorizontalPodAutoscalerDelete(t *testing.T) { - ns := api.NamespaceDefault + testHorizontalPodAutoscalerDelete(t, testapi.Extensions, extensions.GroupName) + testHorizontalPodAutoscalerDelete(t, testapi.Autoscaling, autoscaling.GroupName) +} + +func testHorizontalPodAutoscalerWatch(t *testing.T, group testapi.TestGroup, resourceGroup string) { c := &simple.Client{ - Request: simple.Request{Method: "DELETE", Path: testapi.Extensions.ResourcePath(getHorizontalPodAutoscalersResoureName(), ns, "foo"), Query: simple.BuildQueryValues(nil)}, - Response: simple.Response{StatusCode: 200}, + Request: simple.Request{ + Method: "GET", + Path: group.ResourcePathWithPrefix("watch", getHorizontalPodAutoscalersResoureName(), "", ""), + Query: url.Values{"resourceVersion": []string{}}}, + Response: simple.Response{StatusCode: 200}, + ResourceGroup: resourceGroup, } - err := c.Setup(t).Extensions().HorizontalPodAutoscalers(ns).Delete("foo", nil) + _, err := getHPAClient(t, c, api.NamespaceAll, resourceGroup).Watch(api.ListOptions{}) defer c.Close() c.Validate(t, nil, err) } func TestHorizontalPodAutoscalerWatch(t *testing.T) { - c := &simple.Client{ - Request: simple.Request{ - Method: "GET", - Path: testapi.Extensions.ResourcePathWithPrefix("watch", getHorizontalPodAutoscalersResoureName(), "", ""), - Query: url.Values{"resourceVersion": []string{}}}, - Response: simple.Response{StatusCode: 200}, - } - _, err := c.Setup(t).Extensions().HorizontalPodAutoscalers(api.NamespaceAll).Watch(api.ListOptions{}) - defer c.Close() - c.Validate(t, nil, err) + testHorizontalPodAutoscalerWatch(t, testapi.Extensions, extensions.GroupName) + testHorizontalPodAutoscalerWatch(t, testapi.Autoscaling, autoscaling.GroupName) } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/import_known_versions.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/import_known_versions.go index 95f664430..c2d7fde2f 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/import_known_versions.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/import_known_versions.go @@ -23,6 +23,8 @@ import ( _ "k8s.io/kubernetes/pkg/api/install" "k8s.io/kubernetes/pkg/apimachinery/registered" _ "k8s.io/kubernetes/pkg/apis/authorization/install" + _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" + _ "k8s.io/kubernetes/pkg/apis/batch/install" _ "k8s.io/kubernetes/pkg/apis/componentconfig/install" _ "k8s.io/kubernetes/pkg/apis/extensions/install" _ "k8s.io/kubernetes/pkg/apis/metrics/install" diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/ingress_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/ingress_test.go index 6bab5f5ed..dfec482ea 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/ingress_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/ingress_test.go @@ -16,17 +16,13 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "testing" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) func getIngressResourceName() string { diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/jobs.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/jobs.go index 0d10997c7..f965a0874 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/jobs.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/jobs.go @@ -101,3 +101,67 @@ func (c *jobs) UpdateStatus(job *extensions.Job) (result *extensions.Job, err er err = c.r.Put().Namespace(c.ns).Resource("jobs").Name(job.Name).SubResource("status").Body(job).Do().Into(result) return } + +// jobsV1 implements JobsNamespacer interface using BatchClient internally +type jobsV1 struct { + r *BatchClient + ns string +} + +// newJobsV1 returns a jobsV1 +func newJobsV1(c *BatchClient, namespace string) *jobsV1 { + return &jobsV1{c, namespace} +} + +// Ensure statically that jobsV1 implements JobInterface. +var _ JobInterface = &jobsV1{} + +// List returns a list of jobs that match the label and field selectors. +func (c *jobsV1) List(opts api.ListOptions) (result *extensions.JobList, err error) { + result = &extensions.JobList{} + err = c.r.Get().Namespace(c.ns).Resource("jobs").VersionedParams(&opts, api.ParameterCodec).Do().Into(result) + return +} + +// Get returns information about a particular job. +func (c *jobsV1) Get(name string) (result *extensions.Job, err error) { + result = &extensions.Job{} + err = c.r.Get().Namespace(c.ns).Resource("jobs").Name(name).Do().Into(result) + return +} + +// Create creates a new job. +func (c *jobsV1) Create(job *extensions.Job) (result *extensions.Job, err error) { + result = &extensions.Job{} + err = c.r.Post().Namespace(c.ns).Resource("jobs").Body(job).Do().Into(result) + return +} + +// Update updates an existing job. +func (c *jobsV1) Update(job *extensions.Job) (result *extensions.Job, err error) { + result = &extensions.Job{} + err = c.r.Put().Namespace(c.ns).Resource("jobs").Name(job.Name).Body(job).Do().Into(result) + return +} + +// Delete deletes a job, returns error if one occurs. +func (c *jobsV1) Delete(name string, options *api.DeleteOptions) (err error) { + return c.r.Delete().Namespace(c.ns).Resource("jobs").Name(name).Body(options).Do().Error() +} + +// Watch returns a watch.Interface that watches the requested jobs. +func (c *jobsV1) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.r.Get(). + Prefix("watch"). + Namespace(c.ns). + Resource("jobs"). + VersionedParams(&opts, api.ParameterCodec). + Watch() +} + +// UpdateStatus takes the name of the job and the new status. Returns the server's representation of the job, and an error, if it occurs. +func (c *jobsV1) UpdateStatus(job *extensions.Job) (result *extensions.Job, err error) { + result = &extensions.Job{} + err = c.r.Put().Namespace(c.ns).Resource("jobs").Name(job.Name).SubResource("status").Body(job).Do().Into(result) + return +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/jobs_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/jobs_test.go index 76a87eab0..a3f8c2270 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/jobs_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/jobs_test.go @@ -16,29 +16,39 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "testing" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) -func getJobResourceName() string { +func getJobsResourceName() string { return "jobs" } -func TestListJobs(t *testing.T) { +func getJobClient(t *testing.T, c *simple.Client, ns, resourceGroup string) unversioned.JobInterface { + switch resourceGroup { + case batch.GroupName: + return c.Setup(t).Batch().Jobs(ns) + case extensions.GroupName: + return c.Setup(t).Extensions().Jobs(ns) + default: + t.Fatalf("Unknown group %v", resourceGroup) + } + return nil +} + +func testListJob(t *testing.T, group testapi.TestGroup, resourceGroup string) { ns := api.NamespaceAll c := &simple.Client{ Request: simple.Request{ Method: "GET", - Path: testapi.Extensions.ResourcePath(getJobResourceName(), ns, ""), + Path: group.ResourcePath(getJobsResourceName(), ns, ""), }, Response: simple.Response{StatusCode: 200, Body: &extensions.JobList{ @@ -58,18 +68,24 @@ func TestListJobs(t *testing.T) { }, }, }, + ResourceGroup: resourceGroup, } - receivedJobList, err := c.Setup(t).Extensions().Jobs(ns).List(api.ListOptions{}) + receivedJobList, err := getJobClient(t, c, ns, resourceGroup).List(api.ListOptions{}) defer c.Close() c.Validate(t, receivedJobList, err) } -func TestGetJob(t *testing.T) { +func TestListJob(t *testing.T) { + testListJob(t, testapi.Extensions, extensions.GroupName) + testListJob(t, testapi.Batch, batch.GroupName) +} + +func testGetJob(t *testing.T, group testapi.TestGroup, resourceGroup string) { ns := api.NamespaceDefault c := &simple.Client{ Request: simple.Request{ Method: "GET", - Path: testapi.Extensions.ResourcePath(getJobResourceName(), ns, "foo"), + Path: group.ResourcePath(getJobsResourceName(), ns, "foo"), Query: simple.BuildQueryValues(nil), }, Response: simple.Response{ @@ -87,61 +103,61 @@ func TestGetJob(t *testing.T) { }, }, }, + ResourceGroup: resourceGroup, } - receivedJob, err := c.Setup(t).Extensions().Jobs(ns).Get("foo") + receivedJob, err := getJobClient(t, c, ns, resourceGroup).Get("foo") defer c.Close() c.Validate(t, receivedJob, err) } -func TestGetJobWithNoName(t *testing.T) { - ns := api.NamespaceDefault - c := &simple.Client{Error: true} - receivedJob, err := c.Setup(t).Extensions().Jobs(ns).Get("") - defer c.Close() - if (err != nil) && (err.Error() != simple.NameRequiredError) { - t.Errorf("Expected error: %v, but got %v", simple.NameRequiredError, err) - } +func TestGetJob(t *testing.T) { + testGetJob(t, testapi.Extensions, extensions.GroupName) + testGetJob(t, testapi.Batch, batch.GroupName) +} +func testUpdateJob(t *testing.T, group testapi.TestGroup, resourceGroup string) { + ns := api.NamespaceDefault + requestJob := &extensions.Job{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + } + c := &simple.Client{ + Request: simple.Request{ + Method: "PUT", + Path: group.ResourcePath(getJobsResourceName(), ns, "foo"), + Query: simple.BuildQueryValues(nil), + }, + Response: simple.Response{ + StatusCode: 200, + Body: &extensions.Job{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + "foo": "bar", + "name": "baz", + }, + }, + Spec: extensions.JobSpec{ + Template: api.PodTemplateSpec{}, + }, + }, + }, + ResourceGroup: resourceGroup, + } + receivedJob, err := getJobClient(t, c, ns, resourceGroup).Update(requestJob) + defer c.Close() c.Validate(t, receivedJob, err) } func TestUpdateJob(t *testing.T) { - ns := api.NamespaceDefault - requestJob := &extensions.Job{ - ObjectMeta: api.ObjectMeta{ - Name: "foo", - Namespace: ns, - ResourceVersion: "1", - }, - } - c := &simple.Client{ - Request: simple.Request{ - Method: "PUT", - Path: testapi.Extensions.ResourcePath(getJobResourceName(), ns, "foo"), - Query: simple.BuildQueryValues(nil), - }, - Response: simple.Response{ - StatusCode: 200, - Body: &extensions.Job{ - ObjectMeta: api.ObjectMeta{ - Name: "foo", - Labels: map[string]string{ - "foo": "bar", - "name": "baz", - }, - }, - Spec: extensions.JobSpec{ - Template: api.PodTemplateSpec{}, - }, - }, - }, - } - receivedJob, err := c.Setup(t).Extensions().Jobs(ns).Update(requestJob) - defer c.Close() - c.Validate(t, receivedJob, err) + testUpdateJob(t, testapi.Extensions, extensions.GroupName) + testUpdateJob(t, testapi.Batch, batch.GroupName) } -func TestUpdateJobStatus(t *testing.T) { +func testUpdateJobStatus(t *testing.T, group testapi.TestGroup, resourceGroup string) { ns := api.NamespaceDefault requestJob := &extensions.Job{ ObjectMeta: api.ObjectMeta{ @@ -153,7 +169,7 @@ func TestUpdateJobStatus(t *testing.T) { c := &simple.Client{ Request: simple.Request{ Method: "PUT", - Path: testapi.Extensions.ResourcePath(getJobResourceName(), ns, "foo") + "/status", + Path: group.ResourcePath(getJobsResourceName(), ns, "foo") + "/status", Query: simple.BuildQueryValues(nil), }, Response: simple.Response{ @@ -174,28 +190,40 @@ func TestUpdateJobStatus(t *testing.T) { }, }, }, + ResourceGroup: resourceGroup, } - receivedJob, err := c.Setup(t).Extensions().Jobs(ns).UpdateStatus(requestJob) + receivedJob, err := getJobClient(t, c, ns, resourceGroup).UpdateStatus(requestJob) defer c.Close() c.Validate(t, receivedJob, err) } -func TestDeleteJob(t *testing.T) { +func TestUpdateJobStatus(t *testing.T) { + testUpdateJobStatus(t, testapi.Extensions, extensions.GroupName) + testUpdateJobStatus(t, testapi.Batch, batch.GroupName) +} + +func testDeleteJob(t *testing.T, group testapi.TestGroup, resourceGroup string) { ns := api.NamespaceDefault c := &simple.Client{ Request: simple.Request{ Method: "DELETE", - Path: testapi.Extensions.ResourcePath(getJobResourceName(), ns, "foo"), + Path: group.ResourcePath(getJobsResourceName(), ns, "foo"), Query: simple.BuildQueryValues(nil), }, - Response: simple.Response{StatusCode: 200}, + Response: simple.Response{StatusCode: 200}, + ResourceGroup: resourceGroup, } - err := c.Setup(t).Extensions().Jobs(ns).Delete("foo", nil) + err := getJobClient(t, c, ns, resourceGroup).Delete("foo", nil) defer c.Close() c.Validate(t, nil, err) } -func TestCreateJob(t *testing.T) { +func TestDeleteJob(t *testing.T) { + testDeleteJob(t, testapi.Extensions, extensions.GroupName) + testDeleteJob(t, testapi.Batch, batch.GroupName) +} + +func testCreateJob(t *testing.T, group testapi.TestGroup, resourceGroup string) { ns := api.NamespaceDefault requestJob := &extensions.Job{ ObjectMeta: api.ObjectMeta{ @@ -206,7 +234,7 @@ func TestCreateJob(t *testing.T) { c := &simple.Client{ Request: simple.Request{ Method: "POST", - Path: testapi.Extensions.ResourcePath(getJobResourceName(), ns, ""), + Path: group.ResourcePath(getJobsResourceName(), ns, ""), Body: requestJob, Query: simple.BuildQueryValues(nil), }, @@ -225,8 +253,17 @@ func TestCreateJob(t *testing.T) { }, }, }, + ResourceGroup: resourceGroup, } - receivedJob, err := c.Setup(t).Extensions().Jobs(ns).Create(requestJob) + receivedJob, err := getJobClient(t, c, ns, resourceGroup).Create(requestJob) defer c.Close() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } c.Validate(t, receivedJob, err) } + +func TestCreateJob(t *testing.T) { + testCreateJob(t, testapi.Extensions, extensions.GroupName) + testCreateJob(t, testapi.Batch, batch.GroupName) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/limit_ranges_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/limit_ranges_test.go index f6936570c..445310291 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/limit_ranges_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/limit_ranges_test.go @@ -16,11 +16,6 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "net/url" "testing" @@ -28,6 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) func getLimitRangesResourceName() string { diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/namespaces_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/namespaces_test.go index 509980e7e..8e38c935b 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/namespaces_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/namespaces_test.go @@ -16,17 +16,13 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "net/url" "testing" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) func TestNamespaceCreate(t *testing.T) { diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/nodes_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/nodes_test.go index 37735ec7d..d20656d49 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/nodes_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/nodes_test.go @@ -16,11 +16,6 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "net/url" "testing" @@ -29,6 +24,7 @@ import ( "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" "k8s.io/kubernetes/pkg/labels" ) diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/persistentvolume_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/persistentvolume_test.go index 329539a83..03ebed7e7 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/persistentvolume_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/persistentvolume_test.go @@ -16,11 +16,6 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "net/url" "testing" @@ -28,6 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) func getPersistentVolumesResoureName() string { diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/persistentvolumeclaim_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/persistentvolumeclaim_test.go index 837b72a9c..901f510df 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/persistentvolumeclaim_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/persistentvolumeclaim_test.go @@ -16,11 +16,6 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "net/url" "testing" @@ -28,6 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) func getPersistentVolumeClaimsResoureName() string { diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/pod_templates_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/pod_templates_test.go index bd9bcb549..c72f0a21c 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/pod_templates_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/pod_templates_test.go @@ -16,17 +16,13 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "net/url" "testing" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) func getPodTemplatesResoureName() string { diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/pods.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/pods.go index 04bb989c2..426d3ee8e 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/pods.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/pods.go @@ -18,6 +18,7 @@ package unversioned import ( "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/watch" ) @@ -36,7 +37,7 @@ type PodInterface interface { Watch(opts api.ListOptions) (watch.Interface, error) Bind(binding *api.Binding) error UpdateStatus(pod *api.Pod) (*api.Pod, error) - GetLogs(name string, opts *api.PodLogOptions) *Request + GetLogs(name string, opts *api.PodLogOptions) *restclient.Request } // pods implements PodsNamespacer interface @@ -109,6 +110,6 @@ func (c *pods) UpdateStatus(pod *api.Pod) (result *api.Pod, err error) { } // Get constructs a request for getting the logs for a pod -func (c *pods) GetLogs(name string, opts *api.PodLogOptions) *Request { +func (c *pods) GetLogs(name string, opts *api.PodLogOptions) *restclient.Request { return c.r.Get().Namespace(c.ns).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, api.ParameterCodec) } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/pods_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/pods_test.go index 49175facb..42a806502 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/pods_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/pods_test.go @@ -16,8 +16,6 @@ limitations under the License. package unversioned_test -import . "k8s.io/kubernetes/pkg/client/unversioned" - import ( "net/http" "net/url" diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/podsecuritypolicy_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/podsecuritypolicy_test.go index dee0f5935..d51e2c5a0 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/podsecuritypolicy_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/podsecuritypolicy_test.go @@ -24,7 +24,6 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/apis/extensions" - . "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/portforward/portforward_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/portforward/portforward_test.go index 2d5060b29..6a2a26144 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/portforward/portforward_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/portforward/portforward_test.go @@ -30,7 +30,7 @@ import ( "testing" "time" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/remotecommand" kubeletserver "k8s.io/kubernetes/pkg/kubelet/server" "k8s.io/kubernetes/pkg/types" @@ -298,7 +298,7 @@ func TestForwardPorts(t *testing.T) { server := httptest.NewServer(fakePortForwardServer(t, testName, test.serverSends, test.clientSends)) url, _ := url.Parse(server.URL) - exec, err := remotecommand.NewExecutor(&client.Config{}, "POST", url) + exec, err := remotecommand.NewExecutor(&restclient.Config{}, "POST", url) if err != nil { t.Fatal(err) } @@ -374,7 +374,7 @@ func TestForwardPortsReturnsErrorWhenAllBindsFailed(t *testing.T) { // defer server.Close() url, _ := url.Parse(server.URL) - exec, err := remotecommand.NewExecutor(&client.Config{}, "POST", url) + exec, err := remotecommand.NewExecutor(&restclient.Config{}, "POST", url) if err != nil { t.Fatal(err) } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/remotecommand.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/remotecommand.go index d827f1010..7144f3093 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/remotecommand.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/remotecommand.go @@ -24,8 +24,9 @@ import ( "github.com/golang/glog" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/transport" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/kubelet/server/remotecommand" "k8s.io/kubernetes/pkg/util/httpstream" "k8s.io/kubernetes/pkg/util/httpstream/spdy" ) @@ -36,7 +37,7 @@ type Executor interface { // non-nil stream to a remote system, and return an error if a problem occurs. If tty // is set, the stderr stream is not used (raw TTY manages stdout and stderr over the // stdout stream). - Stream(stdin io.Reader, stdout, stderr io.Writer, tty bool) error + Stream(supportedProtocols []string, stdin io.Reader, stdout, stderr io.Writer, tty bool) error } // StreamExecutor supports the ability to dial an httpstream connection and the ability to @@ -59,14 +60,14 @@ type streamExecutor struct { // multiplexed bidirectional streams. The current implementation uses SPDY, // but this could be replaced with HTTP/2 once it's available, or something else. // TODO: the common code between this and portforward could be abstracted. -func NewExecutor(config *client.Config, method string, url *url.URL) (StreamExecutor, error) { - tlsConfig, err := client.TLSConfigFor(config) +func NewExecutor(config *restclient.Config, method string, url *url.URL) (StreamExecutor, error) { + tlsConfig, err := restclient.TLSConfigFor(config) if err != nil { return nil, err } upgradeRoundTripper := spdy.NewRoundTripper(tlsConfig) - wrapper, err := client.HTTPWrappersForConfig(config, upgradeRoundTripper) + wrapper, err := restclient.HTTPWrappersForConfig(config, upgradeRoundTripper) if err != nil { return nil, err } @@ -128,26 +129,13 @@ func (e *streamExecutor) Dial(protocols ...string) (httpstream.Connection, strin return conn, resp.Header.Get(httpstream.HeaderProtocolVersion), nil } -const ( - // The SPDY subprotocol "channel.k8s.io" is used for remote command - // attachment/execution. This represents the initial unversioned subprotocol, - // which has the known bugs http://issues.k8s.io/13394 and - // http://issues.k8s.io/13395. - StreamProtocolV1Name = "channel.k8s.io" - // The SPDY subprotocol "v2.channel.k8s.io" is used for remote command - // attachment/execution. It is the second version of the subprotocol and - // resolves the issues present in the first version. - StreamProtocolV2Name = "v2.channel.k8s.io" -) - type streamProtocolHandler interface { stream(httpstream.Connection) error } // Stream opens a protocol streamer to the server and streams until a client closes // the connection or the server disconnects. -func (e *streamExecutor) Stream(stdin io.Reader, stdout, stderr io.Writer, tty bool) error { - supportedProtocols := []string{StreamProtocolV2Name, StreamProtocolV1Name} +func (e *streamExecutor) Stream(supportedProtocols []string, stdin io.Reader, stdout, stderr io.Writer, tty bool) error { conn, protocol, err := e.Dial(supportedProtocols...) if err != nil { return err @@ -157,7 +145,7 @@ func (e *streamExecutor) Stream(stdin io.Reader, stdout, stderr io.Writer, tty b var streamer streamProtocolHandler switch protocol { - case StreamProtocolV2Name: + case remotecommand.StreamProtocolV2Name: streamer = &streamProtocolV2{ stdin: stdin, stdout: stdout, @@ -165,9 +153,9 @@ func (e *streamExecutor) Stream(stdin io.Reader, stdout, stderr io.Writer, tty b tty: tty, } case "": - glog.V(4).Infof("The server did not negotiate a streaming protocol version. Falling back to %s", StreamProtocolV1Name) + glog.V(4).Infof("The server did not negotiate a streaming protocol version. Falling back to %s", remotecommand.StreamProtocolV1Name) fallthrough - case StreamProtocolV1Name: + case remotecommand.StreamProtocolV1Name: streamer = &streamProtocolV1{ stdin: stdin, stdout: stdout, diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/remotecommand_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/remotecommand_test.go index 71acd239d..f8010035c 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/remotecommand_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/remotecommand/remotecommand_test.go @@ -18,6 +18,7 @@ package remotecommand import ( "bytes" + "errors" "fmt" "io" "io/ioutil" @@ -26,325 +27,263 @@ import ( "net/url" "strings" "testing" + "time" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/kubelet/server/remotecommand" + "k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/util/httpstream" - "k8s.io/kubernetes/pkg/util/httpstream/spdy" ) -type streamAndReply struct { - httpstream.Stream - replySent <-chan struct{} +type fakeExecutor struct { + t *testing.T + testName string + errorData string + stdoutData string + stderrData string + expectStdin bool + stdinReceived bytes.Buffer + tty bool + messageCount int + command []string + exec bool } -func waitStreamReply(replySent <-chan struct{}, notify chan<- struct{}, stop <-chan struct{}) { - select { - case <-replySent: - notify <- struct{}{} - case <-stop: - } +func (ex *fakeExecutor) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error { + return ex.run(name, uid, container, cmd, in, out, err, tty) } -func fakeExecServer(t *testing.T, i int, stdinData, stdoutData, stderrData, errorData string, tty bool, messageCount int) http.HandlerFunc { - // error + stdin + stdout - expectedStreams := 3 - if !tty { - // stderr - expectedStreams++ +func (ex *fakeExecutor) AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool) error { + return ex.run(name, uid, container, nil, in, out, err, tty) +} + +func (ex *fakeExecutor) run(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error { + ex.command = cmd + ex.tty = tty + + if e, a := "pod", name; e != a { + ex.t.Errorf("%s: pod: expected %q, got %q", ex.testName, e, a) + } + if e, a := "uid", uid; e != string(a) { + ex.t.Errorf("%s: uid: expected %q, got %q", ex.testName, e, a) + } + if ex.exec { + if e, a := "ls /", strings.Join(ex.command, " "); e != a { + ex.t.Errorf("%s: command: expected %q, got %q", ex.testName, e, a) + } + } else { + if len(ex.command) > 0 { + ex.t.Errorf("%s: command: expected nothing, got %v", ex.testName, ex.command) + } } + if len(ex.errorData) > 0 { + return errors.New(ex.errorData) + } + + if len(ex.stdoutData) > 0 { + for i := 0; i < ex.messageCount; i++ { + fmt.Fprint(out, ex.stdoutData) + } + } + + if len(ex.stderrData) > 0 { + for i := 0; i < ex.messageCount; i++ { + fmt.Fprint(err, ex.stderrData) + } + } + + if ex.expectStdin { + io.Copy(&ex.stdinReceived, in) + } + + return nil +} + +func fakeServer(t *testing.T, testName string, exec bool, stdinData, stdoutData, stderrData, errorData string, tty bool, messageCount int, serverProtocols []string) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - protocol, err := httpstream.Handshake(req, w, []string{StreamProtocolV2Name}, StreamProtocolV1Name) - if err != nil { - t.Fatal(err) - } - if protocol != StreamProtocolV2Name { - t.Fatalf("unexpected protocol: %s", protocol) - } - streamCh := make(chan streamAndReply) - - upgrader := spdy.NewResponseUpgrader() - conn := upgrader.UpgradeResponse(w, req, func(stream httpstream.Stream, replySent <-chan struct{}) error { - streamCh <- streamAndReply{Stream: stream, replySent: replySent} - return nil - }) - // from this point on, we can no longer call methods on w - if conn == nil { - // The upgrader is responsible for notifying the client of any errors that - // occurred during upgrading. All we can do is return here at this point - // if we weren't successful in upgrading. - return - } - defer conn.Close() - - var errorStream, stdinStream, stdoutStream, stderrStream httpstream.Stream - receivedStreams := 0 - replyChan := make(chan struct{}) - stop := make(chan struct{}) - defer close(stop) - WaitForStreams: - for { - select { - case stream := <-streamCh: - streamType := stream.Headers().Get(api.StreamType) - switch streamType { - case api.StreamTypeError: - errorStream = stream - go waitStreamReply(stream.replySent, replyChan, stop) - case api.StreamTypeStdin: - stdinStream = stream - go waitStreamReply(stream.replySent, replyChan, stop) - case api.StreamTypeStdout: - stdoutStream = stream - go waitStreamReply(stream.replySent, replyChan, stop) - case api.StreamTypeStderr: - stderrStream = stream - go waitStreamReply(stream.replySent, replyChan, stop) - default: - t.Errorf("%d: unexpected stream type: %q", i, streamType) - } - - if receivedStreams == expectedStreams { - break WaitForStreams - } - case <-replyChan: - receivedStreams++ - if receivedStreams == expectedStreams { - break WaitForStreams - } - } + executor := &fakeExecutor{ + t: t, + testName: testName, + errorData: errorData, + stdoutData: stdoutData, + stderrData: stderrData, + expectStdin: len(stdinData) > 0, + tty: tty, + messageCount: messageCount, + exec: exec, } - if len(errorData) > 0 { - n, err := fmt.Fprint(errorStream, errorData) - if err != nil { - t.Errorf("%d: error writing to errorStream: %v", i, err) - } - if e, a := len(errorData), n; e != a { - t.Errorf("%d: expected to write %d bytes to errorStream, but only wrote %d", i, e, a) - } - errorStream.Close() + if exec { + remotecommand.ServeExec(w, req, executor, "pod", "uid", "container", 0, 10*time.Second, serverProtocols) + } else { + remotecommand.ServeAttach(w, req, executor, "pod", "uid", "container", 0, 10*time.Second, serverProtocols) } - if len(stdoutData) > 0 { - for j := 0; j < messageCount; j++ { - n, err := fmt.Fprint(stdoutStream, stdoutData) - if err != nil { - t.Errorf("%d: error writing to stdoutStream: %v", i, err) - } - if e, a := len(stdoutData), n; e != a { - t.Errorf("%d: expected to write %d bytes to stdoutStream, but only wrote %d", i, e, a) - } - } - stdoutStream.Close() - } - if len(stderrData) > 0 { - for j := 0; j < messageCount; j++ { - n, err := fmt.Fprint(stderrStream, stderrData) - if err != nil { - t.Errorf("%d: error writing to stderrStream: %v", i, err) - } - if e, a := len(stderrData), n; e != a { - t.Errorf("%d: expected to write %d bytes to stderrStream, but only wrote %d", i, e, a) - } - } - stderrStream.Close() - } - if len(stdinData) > 0 { - data := make([]byte, len(stdinData)) - for j := 0; j < messageCount; j++ { - n, err := io.ReadFull(stdinStream, data) - if err != nil { - t.Errorf("%d: error reading stdin stream: %v", i, err) - } - if e, a := len(stdinData), n; e != a { - t.Errorf("%d: expected to read %d bytes from stdinStream, but only read %d", i, e, a) - } - if e, a := stdinData, string(data); e != a { - t.Errorf("%d: stdin: expected %q, got %q", i, e, a) - } - } - stdinStream.Close() + if e, a := strings.Repeat(stdinData, messageCount), executor.stdinReceived.String(); e != a { + t.Errorf("%s: stdin: expected %q, got %q", testName, e, a) } }) } -func TestRequestExecuteRemoteCommand(t *testing.T) { +func TestStream(t *testing.T) { testCases := []struct { - Stdin string - Stdout string - Stderr string - Error string - Tty bool - MessageCount int + TestName string + Stdin string + Stdout string + Stderr string + Error string + Tty bool + MessageCount int + ClientProtocols []string + ServerProtocols []string }{ { - Error: "bail", + TestName: "error", + Error: "bail", + Stdout: "a", + ClientProtocols: []string{remotecommand.StreamProtocolV2Name}, + ServerProtocols: []string{remotecommand.StreamProtocolV2Name}, }, { - Stdin: "a", - Stdout: "b", - Stderr: "c", - // TODO bump this to a larger number such as 100 once - // https://github.com/docker/spdystream/issues/55 is fixed and the Godep - // is bumped. Sending multiple messages over stdin/stdout/stderr results - // in more frames being spread across multiple spdystream frame workers. - // This makes it more likely that the spdystream bug will be encountered, - // where streams are closed as soon as a goaway frame is received, and - // any pending frames that haven't been processed yet may not be - // delivered (it's a race). - MessageCount: 1, + TestName: "in/out/err", + Stdin: "a", + Stdout: "b", + Stderr: "c", + MessageCount: 100, + ClientProtocols: []string{remotecommand.StreamProtocolV2Name}, + ServerProtocols: []string{remotecommand.StreamProtocolV2Name}, }, { - Stdin: "a", - Stdout: "b", - Tty: true, + TestName: "in/out/tty", + Stdin: "a", + Stdout: "b", + Tty: true, + MessageCount: 100, + ClientProtocols: []string{remotecommand.StreamProtocolV2Name}, + ServerProtocols: []string{remotecommand.StreamProtocolV2Name}, + }, + { + // 1.0 kubectl, 1.0 kubelet + TestName: "unversioned client, unversioned server", + Stdout: "b", + Stderr: "c", + MessageCount: 1, + ClientProtocols: []string{}, + ServerProtocols: []string{}, + }, + { + // 1.0 kubectl, 1.1+ kubelet + TestName: "unversioned client, versioned server", + Stdout: "b", + Stderr: "c", + MessageCount: 1, + ClientProtocols: []string{}, + ServerProtocols: []string{remotecommand.StreamProtocolV2Name, remotecommand.StreamProtocolV1Name}, + }, + { + // 1.1+ kubectl, 1.0 kubelet + TestName: "versioned client, unversioned server", + Stdout: "b", + Stderr: "c", + MessageCount: 1, + ClientProtocols: []string{remotecommand.StreamProtocolV2Name, remotecommand.StreamProtocolV1Name}, + ServerProtocols: []string{}, }, } - for i, testCase := range testCases { - localOut := &bytes.Buffer{} - localErr := &bytes.Buffer{} - - server := httptest.NewServer(fakeExecServer(t, i, testCase.Stdin, testCase.Stdout, testCase.Stderr, testCase.Error, testCase.Tty, testCase.MessageCount)) - - url, _ := url.ParseRequestURI(server.URL) - c := client.NewRESTClient(url, "", client.ContentConfig{GroupVersion: &unversioned.GroupVersion{Group: "x"}}, -1, -1, nil) - req := c.Post().Resource("testing") - req.SetHeader(httpstream.HeaderProtocolVersion, StreamProtocolV2Name) - req.Param("command", "ls") - req.Param("command", "/") - conf := &client.Config{ - Host: server.URL, - } - e, err := NewExecutor(conf, "POST", req.URL()) - if err != nil { - t.Errorf("%d: unexpected error: %v", i, err) - continue - } - err = e.Stream(strings.NewReader(strings.Repeat(testCase.Stdin, testCase.MessageCount)), localOut, localErr, testCase.Tty) - hasErr := err != nil - - if len(testCase.Error) > 0 { - if !hasErr { - t.Errorf("%d: expected an error", i) + for _, testCase := range testCases { + for _, exec := range []bool{true, false} { + var name string + if exec { + name = testCase.TestName + " (exec)" } else { - if e, a := testCase.Error, err.Error(); !strings.Contains(a, e) { - t.Errorf("%d: expected error stream read '%v', got '%v'", i, e, a) + name = testCase.TestName + " (attach)" + } + var ( + streamIn io.Reader + streamOut, streamErr io.Writer + ) + localOut := &bytes.Buffer{} + localErr := &bytes.Buffer{} + + server := httptest.NewServer(fakeServer(t, name, exec, testCase.Stdin, testCase.Stdout, testCase.Stderr, testCase.Error, testCase.Tty, testCase.MessageCount, testCase.ServerProtocols)) + + url, _ := url.ParseRequestURI(server.URL) + c := restclient.NewRESTClient(url, "", restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Group: "x"}}, -1, -1, nil) + req := c.Post().Resource("testing") + + if exec { + req.Param("command", "ls") + req.Param("command", "/") + } + + if len(testCase.Stdin) > 0 { + req.Param(api.ExecStdinParam, "1") + streamIn = strings.NewReader(strings.Repeat(testCase.Stdin, testCase.MessageCount)) + } + + if len(testCase.Stdout) > 0 { + req.Param(api.ExecStdoutParam, "1") + streamOut = localOut + } + + if testCase.Tty { + req.Param(api.ExecTTYParam, "1") + } else if len(testCase.Stderr) > 0 { + req.Param(api.ExecStderrParam, "1") + streamErr = localErr + } + + conf := &restclient.Config{ + Host: server.URL, + } + e, err := NewExecutor(conf, "POST", req.URL()) + if err != nil { + t.Errorf("%s: unexpected error: %v", name, err) + continue + } + err = e.Stream(testCase.ClientProtocols, streamIn, streamOut, streamErr, testCase.Tty) + hasErr := err != nil + + if len(testCase.Error) > 0 { + if !hasErr { + t.Errorf("%s: expected an error", name) + } else { + if e, a := testCase.Error, err.Error(); !strings.Contains(a, e) { + t.Errorf("%s: expected error stream read %q, got %q", name, e, a) + } + } + + // TODO: Uncomment when fix #19254 + // server.Close() + continue + } + + if hasErr { + t.Errorf("%s: unexpected error: %v", name, err) + // TODO: Uncomment when fix #19254 + // server.Close() + continue + } + + if len(testCase.Stdout) > 0 { + if e, a := strings.Repeat(testCase.Stdout, testCase.MessageCount), localOut; e != a.String() { + t.Errorf("%s: expected stdout data '%s', got '%s'", name, e, a) + } + } + + if testCase.Stderr != "" { + if e, a := strings.Repeat(testCase.Stderr, testCase.MessageCount), localErr; e != a.String() { + t.Errorf("%s: expected stderr data '%s', got '%s'", name, e, a) } } // TODO: Uncomment when fix #19254 // server.Close() - continue } - - if hasErr { - t.Errorf("%d: unexpected error: %v", i, err) - // TODO: Uncomment when fix #19254 - // server.Close() - continue - } - - if len(testCase.Stdout) > 0 { - if e, a := strings.Repeat(testCase.Stdout, testCase.MessageCount), localOut; e != a.String() { - t.Errorf("%d: expected stdout data '%s', got '%s'", i, e, a) - } - } - - if testCase.Stderr != "" { - if e, a := strings.Repeat(testCase.Stderr, testCase.MessageCount), localErr; e != a.String() { - t.Errorf("%d: expected stderr data '%s', got '%s'", i, e, a) - } - } - - // TODO: Uncomment when fix #19254 - // server.Close() - } -} - -// TODO: this test is largely cut and paste, refactor to share code -func TestRequestAttachRemoteCommand(t *testing.T) { - testCases := []struct { - Stdin string - Stdout string - Stderr string - Error string - Tty bool - }{ - { - Error: "bail", - }, - { - Stdin: "a", - Stdout: "b", - Stderr: "c", - }, - { - Stdin: "a", - Stdout: "b", - Tty: true, - }, - } - - for i, testCase := range testCases { - localOut := &bytes.Buffer{} - localErr := &bytes.Buffer{} - - server := httptest.NewServer(fakeExecServer(t, i, testCase.Stdin, testCase.Stdout, testCase.Stderr, testCase.Error, testCase.Tty, 1)) - - url, _ := url.ParseRequestURI(server.URL) - c := client.NewRESTClient(url, "", client.ContentConfig{GroupVersion: &unversioned.GroupVersion{Group: "x"}}, -1, -1, nil) - req := c.Post().Resource("testing") - - conf := &client.Config{ - Host: server.URL, - } - e, err := NewExecutor(conf, "POST", req.URL()) - if err != nil { - t.Errorf("%d: unexpected error: %v", i, err) - continue - } - err = e.Stream(strings.NewReader(testCase.Stdin), localOut, localErr, testCase.Tty) - hasErr := err != nil - - if len(testCase.Error) > 0 { - if !hasErr { - t.Errorf("%d: expected an error", i) - } else { - if e, a := testCase.Error, err.Error(); !strings.Contains(a, e) { - t.Errorf("%d: expected error stream read '%v', got '%v'", i, e, a) - } - } - - // TODO: Uncomment when fix #19254 - // server.Close() - continue - } - - if hasErr { - t.Errorf("%d: unexpected error: %v", i, err) - // TODO: Uncomment when fix #19254 - // server.Close() - continue - } - - if len(testCase.Stdout) > 0 { - if e, a := testCase.Stdout, localOut; e != a.String() { - t.Errorf("%d: expected stdout data '%s', got '%s'", i, e, a) - } - } - - if testCase.Stderr != "" { - if e, a := testCase.Stderr, localErr; e != a.String() { - t.Errorf("%d: expected stderr data '%s', got '%s'", i, e, a) - } - } - - // TODO: Uncomment when fix #19254 - // server.Close() } } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/replica_sets_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/replica_sets_test.go index 960baae04..2a0e8142c 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/replica_sets_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/replica_sets_test.go @@ -16,17 +16,13 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "testing" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) func getReplicaSetResourceName() string { @@ -53,7 +49,7 @@ func TestListReplicaSets(t *testing.T) { }, Spec: extensions.ReplicaSetSpec{ Replicas: 2, - Template: &api.PodTemplateSpec{}, + Template: api.PodTemplateSpec{}, }, }, }, @@ -80,7 +76,7 @@ func TestGetReplicaSet(t *testing.T) { }, Spec: extensions.ReplicaSetSpec{ Replicas: 2, - Template: &api.PodTemplateSpec{}, + Template: api.PodTemplateSpec{}, }, }, }, @@ -119,7 +115,7 @@ func TestUpdateReplicaSet(t *testing.T) { }, Spec: extensions.ReplicaSetSpec{ Replicas: 2, - Template: &api.PodTemplateSpec{}, + Template: api.PodTemplateSpec{}, }, }, }, @@ -147,7 +143,7 @@ func TestUpdateStatusReplicaSet(t *testing.T) { }, Spec: extensions.ReplicaSetSpec{ Replicas: 2, - Template: &api.PodTemplateSpec{}, + Template: api.PodTemplateSpec{}, }, Status: extensions.ReplicaSetStatus{ Replicas: 2, @@ -187,7 +183,7 @@ func TestCreateReplicaSet(t *testing.T) { }, Spec: extensions.ReplicaSetSpec{ Replicas: 2, - Template: &api.PodTemplateSpec{}, + Template: api.PodTemplateSpec{}, }, }, }, diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/replication_controllers_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/replication_controllers_test.go index 151423b92..de0458ce4 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/replication_controllers_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/replication_controllers_test.go @@ -16,16 +16,12 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "testing" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) func getRCResourceName() string { diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/resource_quotas_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/resource_quotas_test.go index ff8abddce..73dba8dfb 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/resource_quotas_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/resource_quotas_test.go @@ -16,11 +16,6 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "net/url" "testing" @@ -28,6 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) func getResourceQuotasResoureName() string { diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/services.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/services.go index 510ed99b7..8b40a5d04 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/services.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/services.go @@ -18,6 +18,7 @@ package unversioned import ( "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/util/net" "k8s.io/kubernetes/pkg/watch" ) @@ -36,7 +37,7 @@ type ServiceInterface interface { UpdateStatus(srv *api.Service) (*api.Service, error) Delete(name string) error Watch(opts api.ListOptions) (watch.Interface, error) - ProxyGet(scheme, name, port, path string, params map[string]string) ResponseWrapper + ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper } // services implements ServicesNamespacer interface @@ -106,11 +107,11 @@ func (c *services) Watch(opts api.ListOptions) (watch.Interface, error) { } // ProxyGet returns a response of the service by calling it through the proxy. -func (c *services) ProxyGet(scheme, name, port, path string, params map[string]string) ResponseWrapper { +func (c *services) ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper { request := c.r.Get(). - Prefix("proxy"). Namespace(c.ns). Resource("services"). + SubResource("proxy"). Name(net.JoinSchemeNamePort(scheme, name, port)). Suffix(path) for k, v := range params { diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/services_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/services_test.go index e984a42aa..fadfe2be4 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/services_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/services_test.go @@ -16,11 +16,6 @@ limitations under the License. package unversioned_test -import ( - . "k8s.io/kubernetes/pkg/client/unversioned" - "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" -) - import ( "net/url" "testing" @@ -28,6 +23,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" "k8s.io/kubernetes/pkg/labels" ) @@ -218,7 +214,7 @@ func TestServiceProxyGet(t *testing.T) { c := &simple.Client{ Request: simple.Request{ Method: "GET", - Path: testapi.Default.ResourcePathWithPrefix("proxy", "services", ns, "service-1") + "/foo", + Path: testapi.Default.ResourcePath("services", ns, "service-1") + "/proxy/foo", Query: simple.BuildQueryValues(url.Values{"param-name": []string{"param-value"}}), }, Response: simple.Response{StatusCode: 200, RawBody: &body}, @@ -231,7 +227,7 @@ func TestServiceProxyGet(t *testing.T) { c = &simple.Client{ Request: simple.Request{ Method: "GET", - Path: testapi.Default.ResourcePathWithPrefix("proxy", "services", ns, "https:service-1:my-port") + "/foo", + Path: testapi.Default.ResourcePath("services", ns, "https:service-1:my-port") + "/proxy/foo", Query: simple.BuildQueryValues(url.Values{"param-name": []string{"param-value"}}), }, Response: simple.Response{StatusCode: 200, RawBody: &body}, diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_horizontal_pod_autoscalers.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_horizontal_pod_autoscalers.go index a70a1af99..e50b326d9 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_horizontal_pod_autoscalers.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_horizontal_pod_autoscalers.go @@ -91,3 +91,74 @@ func (c *FakeHorizontalPodAutoscalers) Delete(name string, options *api.DeleteOp func (c *FakeHorizontalPodAutoscalers) Watch(opts api.ListOptions) (watch.Interface, error) { return c.Fake.InvokesWatch(NewWatchAction("horizontalpodautoscalers", c.Namespace, opts)) } + +// FakeHorizontalPodAutoscalers implements HorizontalPodAutoscalerInterface. Meant to be embedded into a struct to get a default +// implementation. This makes faking out just the methods you want to test easier. +// This is a test implementation of HorizontalPodAutoscalersV1 +// TODO(piosz): get back to one client implementation once HPA will be graduated to GA completely +type FakeHorizontalPodAutoscalersV1 struct { + Fake *FakeAutoscaling + Namespace string +} + +func (c *FakeHorizontalPodAutoscalersV1) Get(name string) (*extensions.HorizontalPodAutoscaler, error) { + obj, err := c.Fake.Invokes(NewGetAction("horizontalpodautoscalers", c.Namespace, name), &extensions.HorizontalPodAutoscaler{}) + if obj == nil { + return nil, err + } + + return obj.(*extensions.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalersV1) List(opts api.ListOptions) (*extensions.HorizontalPodAutoscalerList, error) { + obj, err := c.Fake.Invokes(NewListAction("horizontalpodautoscalers", c.Namespace, opts), &extensions.HorizontalPodAutoscalerList{}) + if obj == nil { + return nil, err + } + label := opts.LabelSelector + if label == nil { + label = labels.Everything() + } + list := &extensions.HorizontalPodAutoscalerList{} + for _, a := range obj.(*extensions.HorizontalPodAutoscalerList).Items { + if label.Matches(labels.Set(a.Labels)) { + list.Items = append(list.Items, a) + } + } + return list, err +} + +func (c *FakeHorizontalPodAutoscalersV1) Create(a *extensions.HorizontalPodAutoscaler) (*extensions.HorizontalPodAutoscaler, error) { + obj, err := c.Fake.Invokes(NewCreateAction("horizontalpodautoscalers", c.Namespace, a), a) + if obj == nil { + return nil, err + } + + return obj.(*extensions.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalersV1) Update(a *extensions.HorizontalPodAutoscaler) (*extensions.HorizontalPodAutoscaler, error) { + obj, err := c.Fake.Invokes(NewUpdateAction("horizontalpodautoscalers", c.Namespace, a), a) + if obj == nil { + return nil, err + } + + return obj.(*extensions.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalersV1) UpdateStatus(a *extensions.HorizontalPodAutoscaler) (*extensions.HorizontalPodAutoscaler, error) { + obj, err := c.Fake.Invokes(NewUpdateSubresourceAction("horizontalpodautoscalers", "status", c.Namespace, a), &extensions.HorizontalPodAutoscaler{}) + if obj == nil { + return nil, err + } + return obj.(*extensions.HorizontalPodAutoscaler), err +} + +func (c *FakeHorizontalPodAutoscalersV1) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake.Invokes(NewDeleteAction("horizontalpodautoscalers", c.Namespace, name), &extensions.HorizontalPodAutoscaler{}) + return err +} + +func (c *FakeHorizontalPodAutoscalersV1) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake.InvokesWatch(NewWatchAction("horizontalpodautoscalers", c.Namespace, opts)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_jobs.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_jobs.go index d6fb79fa1..71ac8dfd6 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_jobs.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_jobs.go @@ -82,3 +82,66 @@ func (c *FakeJobs) UpdateStatus(job *extensions.Job) (result *extensions.Job, er return obj.(*extensions.Job), err } + +// FakeJobs implements JobInterface. Meant to be embedded into a struct to get a default +// implementation. This makes faking out just the methods you want to test easier. +// This is a test implementation of JobsV1 +// TODO(piosz): get back to one client implementation once HPA will be graduated to GA completely +type FakeJobsV1 struct { + Fake *FakeBatch + Namespace string +} + +func (c *FakeJobsV1) Get(name string) (*extensions.Job, error) { + obj, err := c.Fake.Invokes(NewGetAction("jobs", c.Namespace, name), &extensions.Job{}) + if obj == nil { + return nil, err + } + + return obj.(*extensions.Job), err +} + +func (c *FakeJobsV1) List(opts api.ListOptions) (*extensions.JobList, error) { + obj, err := c.Fake.Invokes(NewListAction("jobs", c.Namespace, opts), &extensions.JobList{}) + if obj == nil { + return nil, err + } + + return obj.(*extensions.JobList), err +} + +func (c *FakeJobsV1) Create(job *extensions.Job) (*extensions.Job, error) { + obj, err := c.Fake.Invokes(NewCreateAction("jobs", c.Namespace, job), job) + if obj == nil { + return nil, err + } + + return obj.(*extensions.Job), err +} + +func (c *FakeJobsV1) Update(job *extensions.Job) (*extensions.Job, error) { + obj, err := c.Fake.Invokes(NewUpdateAction("jobs", c.Namespace, job), job) + if obj == nil { + return nil, err + } + + return obj.(*extensions.Job), err +} + +func (c *FakeJobsV1) Delete(name string, options *api.DeleteOptions) error { + _, err := c.Fake.Invokes(NewDeleteAction("jobs", c.Namespace, name), &extensions.Job{}) + return err +} + +func (c *FakeJobsV1) Watch(opts api.ListOptions) (watch.Interface, error) { + return c.Fake.InvokesWatch(NewWatchAction("jobs", c.Namespace, opts)) +} + +func (c *FakeJobsV1) UpdateStatus(job *extensions.Job) (result *extensions.Job, err error) { + obj, err := c.Fake.Invokes(NewUpdateSubresourceAction("jobs", "status", c.Namespace, job), job) + if obj == nil { + return nil, err + } + + return obj.(*extensions.Job), err +} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_pods.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_pods.go index 824356aac..e634030c8 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_pods.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_pods.go @@ -18,7 +18,7 @@ package testclient import ( "k8s.io/kubernetes/pkg/api" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/watch" ) @@ -104,7 +104,7 @@ func (c *FakePods) UpdateStatus(pod *api.Pod) (*api.Pod, error) { return obj.(*api.Pod), err } -func (c *FakePods) GetLogs(name string, opts *api.PodLogOptions) *client.Request { +func (c *FakePods) GetLogs(name string, opts *api.PodLogOptions) *restclient.Request { action := GenericActionImpl{} action.Verb = "get" action.Namespace = c.Namespace @@ -113,5 +113,5 @@ func (c *FakePods) GetLogs(name string, opts *api.PodLogOptions) *client.Request action.Value = opts _, _ = c.Fake.Invokes(action, &api.Pod{}) - return &client.Request{} + return &restclient.Request{} } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_services.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_services.go index 2f17c7578..fd4861ba1 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_services.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fake_services.go @@ -18,7 +18,7 @@ package testclient import ( "k8s.io/kubernetes/pkg/api" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/watch" ) @@ -83,6 +83,6 @@ func (c *FakeServices) Watch(opts api.ListOptions) (watch.Interface, error) { return c.Fake.InvokesWatch(NewWatchAction("services", c.Namespace, opts)) } -func (c *FakeServices) ProxyGet(scheme, name, port, path string, params map[string]string) client.ResponseWrapper { +func (c *FakeServices) ProxyGet(scheme, name, port, path string, params map[string]string) restclient.ResponseWrapper { return c.Fake.InvokesProxy(NewProxyGetAction("services", c.Namespace, scheme, name, port, path, params)) } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fixture.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fixture.go index 64388fb63..bb02e96d1 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fixture.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/fixture.go @@ -22,11 +22,10 @@ import ( "reflect" "strings" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/yaml" "k8s.io/kubernetes/pkg/watch" @@ -80,26 +79,24 @@ func ObjectReaction(o ObjectRetriever, mapper meta.RESTMapper) ReactionFunc { return true, resource, err case CreateAction: - meta, err := api.ObjectMetaFor(castAction.GetObject()) + accessor, err := meta.Accessor(castAction.GetObject()) if err != nil { return true, nil, err } - resource, err := o.Kind(kind, meta.Name) + resource, err := o.Kind(kind, accessor.GetName()) return true, resource, err case UpdateAction: - meta, err := api.ObjectMetaFor(castAction.GetObject()) + accessor, err := meta.Accessor(castAction.GetObject()) if err != nil { return true, nil, err } - resource, err := o.Kind(kind, meta.Name) + resource, err := o.Kind(kind, accessor.GetName()) return true, resource, err default: return false, nil, fmt.Errorf("no reaction implemented for %s", action) } - - return true, nil, nil } } @@ -311,6 +308,6 @@ func (r *SimpleProxyReactor) Handles(action Action) bool { return true } -func (r *SimpleProxyReactor) React(action Action) (bool, client.ResponseWrapper, error) { +func (r *SimpleProxyReactor) React(action Action) (bool, restclient.ResponseWrapper, error) { return r.Reaction(action) } diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/simple/simple_testclient.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/simple/simple_testclient.go index 7325f16ae..8697da29e 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/simple/simple_testclient.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/simple/simple_testclient.go @@ -28,6 +28,7 @@ import ( "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/restclient" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" @@ -66,30 +67,42 @@ type Client struct { // Maps from query arg key to validator. // If no validator is present, string equality is used. QueryValidator map[string]func(string, string) bool + + // If your object could exist in multiple groups, set this to + // correspond to the URL you're testing it with. + ResourceGroup string } func (c *Client) Setup(t *testing.T) *Client { c.handler = &utiltesting.FakeHandler{ StatusCode: c.Response.StatusCode, } - if responseBody := body(t, c.Response.Body, c.Response.RawBody); responseBody != nil { + if responseBody := c.body(t, c.Response.Body, c.Response.RawBody); responseBody != nil { c.handler.ResponseBody = *responseBody } c.server = httptest.NewServer(c.handler) if c.Client == nil { - c.Client = client.NewOrDie(&client.Config{ + c.Client = client.NewOrDie(&restclient.Config{ Host: c.server.URL, - ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}, + ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}, }) // TODO: caesarxuchao: hacky way to specify version of Experimental client. // We will fix this by supporting multiple group versions in Config - c.ExtensionsClient = client.NewExtensionsOrDie(&client.Config{ + c.AutoscalingClient = client.NewAutoscalingOrDie(&restclient.Config{ Host: c.server.URL, - ContentConfig: client.ContentConfig{GroupVersion: testapi.Extensions.GroupVersion()}, + ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Autoscaling.GroupVersion()}, + }) + c.BatchClient = client.NewBatchOrDie(&restclient.Config{ + Host: c.server.URL, + ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Batch.GroupVersion()}, + }) + c.ExtensionsClient = client.NewExtensionsOrDie(&restclient.Config{ + Host: c.server.URL, + ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Extensions.GroupVersion()}, }) - c.Clientset = clientset.NewForConfigOrDie(&client.Config{Host: c.server.URL}) + c.Clientset = clientset.NewForConfigOrDie(&restclient.Config{Host: c.server.URL}) } c.QueryValidator = map[string]func(string, string) bool{} return c @@ -138,7 +151,7 @@ func (c *Client) ValidateCommon(t *testing.T, err error) { return } - requestBody := body(t, c.Request.Body, c.Request.RawBody) + requestBody := c.body(t, c.Request.Body, c.Request.RawBody) actualQuery := c.handler.RequestReceived.URL.Query() t.Logf("got query: %v", actualQuery) t.Logf("path: %v", c.Request.Path) @@ -206,16 +219,20 @@ func validateFields(a, b string) bool { return sA.String() == sB.String() } -func body(t *testing.T, obj runtime.Object, raw *string) *string { +func (c *Client) body(t *testing.T, obj runtime.Object, raw *string) *string { if obj != nil { fqKind, err := api.Scheme.ObjectKind(obj) if err != nil { t.Errorf("unexpected encoding error: %v", err) } + groupName := fqKind.GroupVersion().Group + if c.ResourceGroup != "" { + groupName = c.ResourceGroup + } var bs []byte - g, found := testapi.Groups[fqKind.GroupVersion().Group] + g, found := testapi.Groups[groupName] if !found { - t.Errorf("Group %s is not registered in testapi", fqKind.GroupVersion().Group) + t.Errorf("Group %s is not registered in testapi", groupName) } bs, err = runtime.Encode(g.Codec(), obj) if err != nil { diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/testclient.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/testclient.go index 0da887984..cd82377b6 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/testclient.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/testclient/testclient.go @@ -25,6 +25,9 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/typed/discovery" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/version" @@ -41,7 +44,7 @@ func NewSimpleFake(objects ...runtime.Object) *Fake { } fakeClient := &Fake{} - fakeClient.AddReactor("*", "*", ObjectReaction(o, api.RESTMapper)) + fakeClient.AddReactor("*", "*", ObjectReaction(o, registered.RESTMapper())) fakeClient.AddWatchReactor("*", DefaultWatchReactor(watch.NewFake(), nil)) @@ -85,7 +88,7 @@ type ProxyReactor interface { // Handles indicates whether or not this Reactor deals with a given action Handles(action Action) bool // React handles a watch action and returns results. It may choose to delegate by indicated handled=false - React(action Action) (handled bool, ret client.ResponseWrapper, err error) + React(action Action) (handled bool, ret restclient.ResponseWrapper, err error) } // ReactionFunc is a function that returns an object or error for a given Action. If "handled" is false, @@ -98,7 +101,7 @@ type WatchReactionFunc func(action Action) (handled bool, ret watch.Interface, e // ProxyReactionFunc is a function that returns a ResponseWrapper interface for a given Action. If "handled" is false, // then the test client will continue ignore the results and continue to the next ProxyReactionFunc -type ProxyReactionFunc func(action Action) (handled bool, ret client.ResponseWrapper, err error) +type ProxyReactionFunc func(action Action) (handled bool, ret restclient.ResponseWrapper, err error) // AddReactor appends a reactor to the end of the chain func (c *Fake) AddReactor(verb, resource string, reaction ReactionFunc) { @@ -176,7 +179,7 @@ func (c *Fake) InvokesWatch(action Action) (watch.Interface, error) { } // InvokesProxy records the provided Action and then invokes the ReactFn (if provided). -func (c *Fake) InvokesProxy(action Action) client.ResponseWrapper { +func (c *Fake) InvokesProxy(action Action) restclient.ResponseWrapper { c.Lock() defer c.Unlock() @@ -274,11 +277,19 @@ func (c *Fake) Namespaces() client.NamespaceInterface { return &FakeNamespaces{Fake: c} } +func (c *Fake) Autoscaling() client.AutoscalingInterface { + return &FakeAutoscaling{c} +} + +func (c *Fake) Batch() client.BatchInterface { + return &FakeBatch{c} +} + func (c *Fake) Extensions() client.ExtensionsInterface { return &FakeExperimental{c} } -func (c *Fake) Discovery() client.DiscoveryInterface { +func (c *Fake) Discovery() discovery.DiscoveryInterface { return &FakeDiscovery{c} } @@ -304,6 +315,32 @@ func (c *Fake) SwaggerSchema(version unversioned.GroupVersion) (*swagger.ApiDecl return &swagger.ApiDeclaration{}, nil } +// NewSimpleFakeAutoscaling returns a client that will respond with the provided objects +func NewSimpleFakeAutoscaling(objects ...runtime.Object) *FakeAutoscaling { + return &FakeAutoscaling{Fake: NewSimpleFake(objects...)} +} + +type FakeAutoscaling struct { + *Fake +} + +func (c *FakeAutoscaling) HorizontalPodAutoscalers(namespace string) client.HorizontalPodAutoscalerInterface { + return &FakeHorizontalPodAutoscalersV1{Fake: c, Namespace: namespace} +} + +// NewSimpleFakeBatch returns a client that will respond with the provided objects +func NewSimpleFakeBatch(objects ...runtime.Object) *FakeBatch { + return &FakeBatch{Fake: NewSimpleFake(objects...)} +} + +type FakeBatch struct { + *Fake +} + +func (c *FakeBatch) Jobs(namespace string) client.JobInterface { + return &FakeJobsV1{Fake: c, Namespace: namespace} +} + // NewSimpleFakeExp returns a client that will respond with the provided objects func NewSimpleFakeExp(objects ...runtime.Object) *FakeExperimental { return &FakeExperimental{Fake: NewSimpleFake(objects...)} diff --git a/vendor/k8s.io/kubernetes/pkg/client/unversioned/thirdpartyresources_test.go b/vendor/k8s.io/kubernetes/pkg/client/unversioned/thirdpartyresources_test.go index bf2e9e15c..753692861 100644 --- a/vendor/k8s.io/kubernetes/pkg/client/unversioned/thirdpartyresources_test.go +++ b/vendor/k8s.io/kubernetes/pkg/client/unversioned/thirdpartyresources_test.go @@ -22,7 +22,6 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/apis/extensions" - . "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/testclient/simple" ) diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/OWNERS b/vendor/k8s.io/kubernetes/pkg/cloudprovider/OWNERS new file mode 100644 index 000000000..31a963ed8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/OWNERS @@ -0,0 +1,3 @@ +assignees: + - davidopp + - mikedanese diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/cloud.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/cloud.go new file mode 100644 index 000000000..cda6db22d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/cloud.go @@ -0,0 +1,164 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 cloudprovider + +import ( + "errors" + "fmt" + "strings" + + "k8s.io/kubernetes/pkg/api" +) + +// Interface is an abstract, pluggable interface for cloud providers. +type Interface interface { + // 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. + Instances() (Instances, bool) + // Zones returns a zones interface. Also returns true if the interface is supported, false otherwise. + Zones() (Zones, bool) + // Clusters returns a clusters interface. Also returns true if the interface is supported, false otherwise. + Clusters() (Clusters, bool) + // Routes returns a routes interface along with whether the interface is supported. + Routes() (Routes, bool) + // ProviderName returns the cloud provider ID. + ProviderName() string + // ScrubDNS provides an opportunity for cloud-provider-specific code to process DNS settings for pods. + ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) +} + +// Clusters is an abstract, pluggable interface for clusters of containers. +type Clusters interface { + // ListClusters lists the names of the available clusters. + ListClusters() ([]string, error) + // Master gets back the address (either DNS name or IP address) of the master node for the cluster. + Master(clusterName string) (string, error) +} + +// TODO(#6812): Use a shorter name that's less likely to be longer than cloud +// providers' name length limits. +func GetLoadBalancerName(service *api.Service) string { + //GCE requires that the name of a load balancer starts with a lower case letter. + ret := "a" + string(service.UID) + ret = strings.Replace(ret, "-", "", -1) + //AWS requires that the name of a load balancer is shorter than 32 bytes. + if len(ret) > 32 { + ret = ret[:32] + } + return ret +} + +func GetInstanceProviderID(cloud Interface, nodeName string) (string, error) { + instances, ok := cloud.Instances() + if !ok { + return "", fmt.Errorf("failed to get instances from cloud provider") + } + instanceID, err := instances.InstanceID(nodeName) + if err != nil { + return "", fmt.Errorf("failed to get instance ID from cloud provider: %v", err) + } + return cloud.ProviderName() + "://" + instanceID, nil +} + +// LoadBalancer is an abstract, pluggable interface for load balancers. +type LoadBalancer interface { + // TODO: Break this up into different interfaces (LB, etc) when we have more than one type of service + // GetLoadBalancer returns whether the specified load balancer exists, and + // if so, what its status is. + // Implementations must treat the *api.Service parameter as read-only and not modify it. + GetLoadBalancer(service *api.Service) (status *api.LoadBalancerStatus, exists bool, err error) + // EnsureLoadBalancer creates a new load balancer 'name', or updates the existing one. Returns the status of the balancer + // Implementations must treat the *api.Service parameter as read-only and not modify it. + EnsureLoadBalancer(service *api.Service, hosts []string, annotations map[string]string) (*api.LoadBalancerStatus, error) + // UpdateLoadBalancer updates hosts under the specified load balancer. + // Implementations must treat the *api.Service parameter as read-only and not modify it. + UpdateLoadBalancer(service *api.Service, hosts []string) error + // EnsureLoadBalancerDeleted deletes the specified load balancer if it + // exists, returning nil if the load balancer specified either didn't exist or + // was successfully deleted. + // This construction is useful because many cloud providers' load balancers + // have multiple underlying components, meaning a Get could say that the LB + // doesn't exist even if some part of it is still laying around. + // Implementations must treat the *api.Service parameter as read-only and not modify it. + EnsureLoadBalancerDeleted(service *api.Service) error +} + +// Instances is an abstract, pluggable interface for sets of instances. +type Instances interface { + // NodeAddresses returns the addresses of the specified instance. + // TODO(roberthbailey): This currently is only used in such a way that it + // returns the address of the calling instance. We should do a rename to + // make this clearer. + NodeAddresses(name string) ([]api.NodeAddress, error) + // ExternalID returns the cloud provider ID of the specified instance (deprecated). + ExternalID(name string) (string, error) + // InstanceID returns the cloud provider ID of the specified instance. + // Note that if the instance does not exist or is no longer running, we must return ("", cloudprovider.InstanceNotFound) + InstanceID(name string) (string, error) + // InstanceType returns the type of the specified instance. + // Note that if the instance does not exist or is no longer running, we must return ("", cloudprovider.InstanceNotFound) + InstanceType(name string) (string, error) + // List lists instances that match 'filter' which is a regular expression which must match the entire instance name (fqdn) + List(filter string) ([]string, error) + // AddSSHKeyToAllInstances adds an SSH public key as a legal identity for all instances + // expected format for the key is standard ssh-keygen format: + AddSSHKeyToAllInstances(user string, keyData []byte) error + // CurrentNodeName returns the name of the node we are currently running on + // On most clouds (e.g. GCE) this is the hostname, so we provide the hostname + CurrentNodeName(hostname string) (string, error) +} + +// Route is a representation of an advanced routing rule. +type Route struct { + // Name is the name of the routing rule in the cloud-provider. + // It will be ignored in a Create (although nameHint may influence it) + Name string + // TargetInstance is the name of the instance as specified in routing rules + // for the cloud-provider (in gce: the Instance Name). + TargetInstance string + // DestinationCIDR is the CIDR format IP range that this routing rule + // applies to. + DestinationCIDR string +} + +// Routes is an abstract, pluggable interface for advanced routing rules. +type Routes interface { + // ListRoutes lists all managed routes that belong to the specified clusterName + ListRoutes(clusterName string) ([]*Route, error) + // CreateRoute creates the described managed route + // route.Name will be ignored, although the cloud-provider may use nameHint + // to create a more user-meaningful name. + CreateRoute(clusterName string, nameHint string, route *Route) error + // DeleteRoute deletes the specified managed route + // Route should be as returned by ListRoutes + DeleteRoute(clusterName string, route *Route) error +} + +var InstanceNotFound = errors.New("instance not found") + +// Zone represents the location of a particular machine. +type Zone struct { + FailureDomain string + Region string +} + +// Zones is an abstract, pluggable interface for zone enumeration. +type Zones interface { + // GetZone returns the Zone containing the current failure zone and locality region that the program is running in + GetZone() (Zone, error) +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/doc.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/doc.go new file mode 100644 index 000000000..eaa91e6cf --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 cloudprovider supplies interfaces and implementations for cloud service providers. +package cloudprovider diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/plugins.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/plugins.go new file mode 100644 index 000000000..ad39e3405 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/plugins.go @@ -0,0 +1,99 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 cloudprovider + +import ( + "fmt" + "io" + "os" + "sync" + + "github.com/golang/glog" +) + +// Factory is a function that returns a cloudprovider.Interface. +// The config parameter provides an io.Reader handler to the factory in +// order to load specific configurations. If no configuration is provided +// the parameter is nil. +type Factory func(config io.Reader) (Interface, error) + +// All registered cloud providers. +var providersMutex sync.Mutex +var providers = make(map[string]Factory) + +// RegisterCloudProvider registers a cloudprovider.Factory by name. This +// is expected to happen during app startup. +func RegisterCloudProvider(name string, cloud Factory) { + providersMutex.Lock() + defer providersMutex.Unlock() + if _, found := providers[name]; found { + glog.Fatalf("Cloud provider %q was registered twice", name) + } + glog.V(1).Infof("Registered cloud provider %q", name) + providers[name] = cloud +} + +// GetCloudProvider creates an instance of the named cloud provider, or nil if +// the name is not known. The error return is only used if the named provider +// was known but failed to initialize. The config parameter specifies the +// io.Reader handler of the configuration file for the cloud provider, or nil +// for no configuation. +func GetCloudProvider(name string, config io.Reader) (Interface, error) { + providersMutex.Lock() + defer providersMutex.Unlock() + f, found := providers[name] + if !found { + return nil, nil + } + return f(config) +} + +// InitCloudProvider creates an instance of the named cloud provider. +func InitCloudProvider(name string, configFilePath string) (Interface, error) { + var cloud Interface + var err error + + if name == "" { + glog.Info("No cloud provider specified.") + return nil, nil + } + + if configFilePath != "" { + var config *os.File + config, err = os.Open(configFilePath) + if err != nil { + glog.Fatalf("Couldn't open cloud provider configuration %s: %#v", + configFilePath, err) + } + + defer config.Close() + cloud, err = GetCloudProvider(name, config) + } else { + // Pass explicit nil so plugins can actually check for nil. See + // "Why is my nil error value not equal to nil?" in golang.org/doc/faq. + cloud, err = GetCloudProvider(name, nil) + } + + if err != nil { + return nil, fmt.Errorf("could not init cloud provider %q: %v", name, err) + } + if cloud == nil { + return nil, fmt.Errorf("unknown cloud provider %q", name) + } + + return cloud, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/OWNERS b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/OWNERS new file mode 100644 index 000000000..3c625f946 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/OWNERS @@ -0,0 +1,2 @@ +assignees: + - justinsb diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws.go new file mode 100644 index 000000000..42e4adde7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws.go @@ -0,0 +1,2724 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 aws + +import ( + "errors" + "fmt" + "io" + "net" + "net/url" + "os" + "regexp" + "strings" + "sync" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" + "github.com/aws/aws-sdk-go/aws/ec2metadata" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/elb" + "gopkg.in/gcfg.v1" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/credentialprovider/aws" + "k8s.io/kubernetes/pkg/types" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api/service" + "k8s.io/kubernetes/pkg/api/unversioned" +) + +const ProviderName = "aws" + +// The tag name we use to differentiate multiple logically independent clusters running in the same AZ +const TagNameKubernetesCluster = "KubernetesCluster" + +// The tag name we use to differentiate multiple services. Used currently for ELBs only. +const TagNameKubernetesService = "kubernetes.io/service-name" + +// The tag name used on a subnet to designate that it should be used for internal ELBs +const TagNameSubnetInternalELB = "kubernetes.io/role/internal-elb" + +// The tag name used on a subnet to designate that it should be used for internet ELBs +const TagNameSubnetPublicELB = "kubernetes.io/role/elb" + +// Annotation used on the service to indicate that we want an internal ELB. +// Currently we accept only the value "0.0.0.0/0" - other values are an error. +// This lets us define more advanced semantics in future. +const ServiceAnnotationLoadBalancerInternal = "service.beta.kubernetes.io/aws-load-balancer-internal" + +// We sometimes read to see if something exists; then try to create it if we didn't find it +// This can fail once in a consistent system if done in parallel +// In an eventually consistent system, it could fail unboundedly +// MaxReadThenCreateRetries sets the maximum number of attempts we will make +const MaxReadThenCreateRetries = 30 + +// Default volume type for newly created Volumes +// TODO: Remove when user/admin can configure volume types and thus we don't +// need hardcoded defaults. +const DefaultVolumeType = "gp2" + +// Amazon recommends having no more that 40 volumes attached to an instance, +// and at least one of those is for the system root volume. +// See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/volume_limits.html#linux-specific-volume-limits +const DefaultMaxEBSVolumes = 39 + +// Used to call aws_credentials.Init() just once +var once sync.Once + +// Abstraction over AWS, to allow mocking/other implementations +type AWSServices interface { + Compute(region string) (EC2, error) + LoadBalancing(region string) (ELB, error) + Autoscaling(region string) (ASG, error) + Metadata() (EC2Metadata, error) +} + +// TODO: Should we rename this to AWS (EBS & ELB are not technically part of EC2) +// Abstraction over EC2, to allow mocking/other implementations +// Note that the DescribeX functions return a list, so callers don't need to deal with paging +type EC2 interface { + // Query EC2 for instances matching the filter + DescribeInstances(request *ec2.DescribeInstancesInput) ([]*ec2.Instance, error) + + // Attach a volume to an instance + AttachVolume(*ec2.AttachVolumeInput) (*ec2.VolumeAttachment, error) + // Detach a volume from an instance it is attached to + DetachVolume(request *ec2.DetachVolumeInput) (resp *ec2.VolumeAttachment, err error) + // Lists volumes + DescribeVolumes(request *ec2.DescribeVolumesInput) ([]*ec2.Volume, error) + // Create an EBS volume + CreateVolume(request *ec2.CreateVolumeInput) (resp *ec2.Volume, err error) + // Delete an EBS volume + DeleteVolume(*ec2.DeleteVolumeInput) (*ec2.DeleteVolumeOutput, error) + + DescribeSecurityGroups(request *ec2.DescribeSecurityGroupsInput) ([]*ec2.SecurityGroup, error) + + CreateSecurityGroup(*ec2.CreateSecurityGroupInput) (*ec2.CreateSecurityGroupOutput, error) + DeleteSecurityGroup(request *ec2.DeleteSecurityGroupInput) (*ec2.DeleteSecurityGroupOutput, error) + + AuthorizeSecurityGroupIngress(*ec2.AuthorizeSecurityGroupIngressInput) (*ec2.AuthorizeSecurityGroupIngressOutput, error) + RevokeSecurityGroupIngress(*ec2.RevokeSecurityGroupIngressInput) (*ec2.RevokeSecurityGroupIngressOutput, error) + + DescribeSubnets(*ec2.DescribeSubnetsInput) ([]*ec2.Subnet, error) + + CreateTags(*ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error) + + DescribeRouteTables(request *ec2.DescribeRouteTablesInput) ([]*ec2.RouteTable, error) + CreateRoute(request *ec2.CreateRouteInput) (*ec2.CreateRouteOutput, error) + DeleteRoute(request *ec2.DeleteRouteInput) (*ec2.DeleteRouteOutput, error) + + ModifyInstanceAttribute(request *ec2.ModifyInstanceAttributeInput) (*ec2.ModifyInstanceAttributeOutput, error) +} + +// This is a simple pass-through of the ELB client interface, which allows for testing +type ELB interface { + CreateLoadBalancer(*elb.CreateLoadBalancerInput) (*elb.CreateLoadBalancerOutput, error) + DeleteLoadBalancer(*elb.DeleteLoadBalancerInput) (*elb.DeleteLoadBalancerOutput, error) + DescribeLoadBalancers(*elb.DescribeLoadBalancersInput) (*elb.DescribeLoadBalancersOutput, error) + RegisterInstancesWithLoadBalancer(*elb.RegisterInstancesWithLoadBalancerInput) (*elb.RegisterInstancesWithLoadBalancerOutput, error) + DeregisterInstancesFromLoadBalancer(*elb.DeregisterInstancesFromLoadBalancerInput) (*elb.DeregisterInstancesFromLoadBalancerOutput, error) + + DetachLoadBalancerFromSubnets(*elb.DetachLoadBalancerFromSubnetsInput) (*elb.DetachLoadBalancerFromSubnetsOutput, error) + AttachLoadBalancerToSubnets(*elb.AttachLoadBalancerToSubnetsInput) (*elb.AttachLoadBalancerToSubnetsOutput, error) + + CreateLoadBalancerListeners(*elb.CreateLoadBalancerListenersInput) (*elb.CreateLoadBalancerListenersOutput, error) + DeleteLoadBalancerListeners(*elb.DeleteLoadBalancerListenersInput) (*elb.DeleteLoadBalancerListenersOutput, error) + + ApplySecurityGroupsToLoadBalancer(*elb.ApplySecurityGroupsToLoadBalancerInput) (*elb.ApplySecurityGroupsToLoadBalancerOutput, error) + + ConfigureHealthCheck(*elb.ConfigureHealthCheckInput) (*elb.ConfigureHealthCheckOutput, error) +} + +// This is a simple pass-through of the Autoscaling client interface, which allows for testing +type ASG interface { + UpdateAutoScalingGroup(*autoscaling.UpdateAutoScalingGroupInput) (*autoscaling.UpdateAutoScalingGroupOutput, error) + DescribeAutoScalingGroups(*autoscaling.DescribeAutoScalingGroupsInput) (*autoscaling.DescribeAutoScalingGroupsOutput, error) +} + +// Abstraction over the AWS metadata service +type EC2Metadata interface { + // Query the EC2 metadata service (used to discover instance-id etc) + GetMetadata(path string) (string, error) +} + +type VolumeOptions struct { + CapacityGB int + Tags map[string]string +} + +// Volumes is an interface for managing cloud-provisioned volumes +// TODO: Allow other clouds to implement this +type Volumes interface { + // Attach the disk to the specified instance + // instanceName can be empty to mean "the instance on which we are running" + // Returns the device (e.g. /dev/xvdf) where we attached the volume + AttachDisk(diskName string, instanceName string, readOnly bool) (string, error) + // Detach the disk from the specified instance + // instanceName can be empty to mean "the instance on which we are running" + // Returns the device where the volume was attached + DetachDisk(diskName string, instanceName string) (string, error) + + // Create a volume with the specified options + CreateDisk(volumeOptions *VolumeOptions) (volumeName string, err error) + // Delete the specified volume + // Returns true iff the volume was deleted + // If the was not found, returns (false, nil) + DeleteDisk(volumeName string) (bool, error) + + // Get labels to apply to volume on creation + GetVolumeLabels(volumeName string) (map[string]string, error) +} + +// InstanceGroups is an interface for managing cloud-managed instance groups / autoscaling instance groups +// TODO: Allow other clouds to implement this +type InstanceGroups interface { + // Set the size to the fixed size + ResizeInstanceGroup(instanceGroupName string, size int) error + // Queries the cloud provider for information about the specified instance group + DescribeInstanceGroup(instanceGroupName string) (InstanceGroupInfo, error) +} + +// InstanceGroupInfo is returned by InstanceGroups.Describe, and exposes information about the group. +type InstanceGroupInfo interface { + // The number of instances currently running under control of this group + CurrentSize() (int, error) +} + +// AWSCloud is an implementation of Interface, LoadBalancer and Instances for Amazon Web Services. +type AWSCloud struct { + ec2 EC2 + elb ELB + asg ASG + metadata EC2Metadata + cfg *AWSCloudConfig + region string + vpcID string + + filterTags map[string]string + + // The AWS instance that we are running on + // Note that we cache some state in awsInstance (mountpoints), so we must preserve the instance + selfAWSInstance *awsInstance + + mutex sync.Mutex +} + +var _ Volumes = &AWSCloud{} + +type AWSCloudConfig struct { + Global struct { + // TODO: Is there any use for this? We can get it from the instance metadata service + // Maybe if we're not running on AWS, e.g. bootstrap; for now it is not very useful + Zone string + + KubernetesClusterTag string + + //The aws provider creates an inbound rule per load balancer on the node security + //group. However, this can run into the AWS security group rule limit of 50 if + //many LoadBalancers are created. + // + //This flag disables the automatic ingress creation. It requires that the user + //has setup a rule that allows inbound traffic on kubelet ports from the + //local VPC subnet (so load balancers can access it). E.g. 10.82.0.0/16 30000-32000. + DisableSecurityGroupIngress bool + } +} + +// awsSdkEC2 is an implementation of the EC2 interface, backed by aws-sdk-go +type awsSdkEC2 struct { + ec2 *ec2.EC2 +} + +type awsSDKProvider struct { + creds *credentials.Credentials + + mutex sync.Mutex + regionDelayers map[string]*CrossRequestRetryDelay +} + +func newAWSSDKProvider(creds *credentials.Credentials) *awsSDKProvider { + return &awsSDKProvider{ + creds: creds, + regionDelayers: make(map[string]*CrossRequestRetryDelay), + } +} + +func (p *awsSDKProvider) addHandlers(regionName string, h *request.Handlers) { + h.Sign.PushFrontNamed(request.NamedHandler{ + Name: "k8s/logger", + Fn: awsHandlerLogger, + }) + + delayer := p.getCrossRequestRetryDelay(regionName) + if delayer != nil { + h.Sign.PushFrontNamed(request.NamedHandler{ + Name: "k8s/delay-presign", + Fn: delayer.BeforeSign, + }) + + h.AfterRetry.PushFrontNamed(request.NamedHandler{ + Name: "k8s/delay-afterretry", + Fn: delayer.AfterRetry, + }) + } +} + +// Get a CrossRequestRetryDelay, scoped to the region, not to the request. +// This means that when we hit a limit on a call, we will delay _all_ calls to the API. +// We do this to protect the AWS account from becoming overloaded and effectively locked. +// We also log when we hit request limits. +// Note that this delays the current goroutine; this is bad behaviour and will +// likely cause k8s to become slow or unresponsive for cloud operations. +// However, this throttle is intended only as a last resort. When we observe +// this throttling, we need to address the root cause (e.g. add a delay to a +// controller retry loop) +func (p *awsSDKProvider) getCrossRequestRetryDelay(regionName string) *CrossRequestRetryDelay { + p.mutex.Lock() + defer p.mutex.Unlock() + + delayer, found := p.regionDelayers[regionName] + if !found { + delayer = NewCrossRequestRetryDelay() + p.regionDelayers[regionName] = delayer + } + return delayer +} + +func (p *awsSDKProvider) Compute(regionName string) (EC2, error) { + service := ec2.New(session.New(&aws.Config{ + Region: ®ionName, + Credentials: p.creds, + })) + + p.addHandlers(regionName, &service.Handlers) + + ec2 := &awsSdkEC2{ + ec2: service, + } + return ec2, nil +} + +func (p *awsSDKProvider) LoadBalancing(regionName string) (ELB, error) { + elbClient := elb.New(session.New(&aws.Config{ + Region: ®ionName, + Credentials: p.creds, + })) + + p.addHandlers(regionName, &elbClient.Handlers) + + return elbClient, nil +} + +func (p *awsSDKProvider) Autoscaling(regionName string) (ASG, error) { + client := autoscaling.New(session.New(&aws.Config{ + Region: ®ionName, + Credentials: p.creds, + })) + + p.addHandlers(regionName, &client.Handlers) + + return client, nil +} + +func (p *awsSDKProvider) Metadata() (EC2Metadata, error) { + client := ec2metadata.New(session.New(&aws.Config{})) + return client, nil +} + +func stringPointerArray(orig []string) []*string { + if orig == nil { + return nil + } + n := make([]*string, len(orig)) + for i := range orig { + n[i] = &orig[i] + } + return n +} + +func isNilOrEmpty(s *string) bool { + return s == nil || *s == "" +} + +func orEmpty(s *string) string { + if s == nil { + return "" + } + return *s +} + +func newEc2Filter(name string, value string) *ec2.Filter { + filter := &ec2.Filter{ + Name: aws.String(name), + Values: []*string{ + aws.String(value), + }, + } + return filter +} + +func (self *AWSCloud) AddSSHKeyToAllInstances(user string, keyData []byte) error { + return errors.New("unimplemented") +} + +func (c *AWSCloud) CurrentNodeName(hostname string) (string, error) { + return c.selfAWSInstance.nodeName, nil +} + +// Implementation of EC2.Instances +func (self *awsSdkEC2) DescribeInstances(request *ec2.DescribeInstancesInput) ([]*ec2.Instance, error) { + // Instances are paged + results := []*ec2.Instance{} + var nextToken *string + + for { + response, err := self.ec2.DescribeInstances(request) + if err != nil { + return nil, fmt.Errorf("error listing AWS instances: %v", err) + } + + for _, reservation := range response.Reservations { + results = append(results, reservation.Instances...) + } + + nextToken = response.NextToken + if isNilOrEmpty(nextToken) { + break + } + request.NextToken = nextToken + } + + return results, nil +} + +// Implements EC2.DescribeSecurityGroups +func (s *awsSdkEC2) DescribeSecurityGroups(request *ec2.DescribeSecurityGroupsInput) ([]*ec2.SecurityGroup, error) { + // Security groups are not paged + response, err := s.ec2.DescribeSecurityGroups(request) + if err != nil { + return nil, fmt.Errorf("error listing AWS security groups: %v", err) + } + return response.SecurityGroups, nil +} + +func (s *awsSdkEC2) AttachVolume(request *ec2.AttachVolumeInput) (*ec2.VolumeAttachment, error) { + return s.ec2.AttachVolume(request) +} + +func (s *awsSdkEC2) DetachVolume(request *ec2.DetachVolumeInput) (*ec2.VolumeAttachment, error) { + return s.ec2.DetachVolume(request) +} + +func (s *awsSdkEC2) DescribeVolumes(request *ec2.DescribeVolumesInput) ([]*ec2.Volume, error) { + // Volumes are paged + results := []*ec2.Volume{} + var nextToken *string + + for { + response, err := s.ec2.DescribeVolumes(request) + + if err != nil { + return nil, fmt.Errorf("error listing AWS volumes: %v", err) + } + + results = append(results, response.Volumes...) + + nextToken = response.NextToken + if isNilOrEmpty(nextToken) { + break + } + request.NextToken = nextToken + } + + return results, nil +} + +func (s *awsSdkEC2) CreateVolume(request *ec2.CreateVolumeInput) (resp *ec2.Volume, err error) { + return s.ec2.CreateVolume(request) +} + +func (s *awsSdkEC2) DeleteVolume(request *ec2.DeleteVolumeInput) (*ec2.DeleteVolumeOutput, error) { + return s.ec2.DeleteVolume(request) +} + +func (s *awsSdkEC2) DescribeSubnets(request *ec2.DescribeSubnetsInput) ([]*ec2.Subnet, error) { + // Subnets are not paged + response, err := s.ec2.DescribeSubnets(request) + if err != nil { + return nil, fmt.Errorf("error listing AWS subnets: %v", err) + } + return response.Subnets, nil +} + +func (s *awsSdkEC2) CreateSecurityGroup(request *ec2.CreateSecurityGroupInput) (*ec2.CreateSecurityGroupOutput, error) { + return s.ec2.CreateSecurityGroup(request) +} + +func (s *awsSdkEC2) DeleteSecurityGroup(request *ec2.DeleteSecurityGroupInput) (*ec2.DeleteSecurityGroupOutput, error) { + return s.ec2.DeleteSecurityGroup(request) +} + +func (s *awsSdkEC2) AuthorizeSecurityGroupIngress(request *ec2.AuthorizeSecurityGroupIngressInput) (*ec2.AuthorizeSecurityGroupIngressOutput, error) { + return s.ec2.AuthorizeSecurityGroupIngress(request) +} + +func (s *awsSdkEC2) RevokeSecurityGroupIngress(request *ec2.RevokeSecurityGroupIngressInput) (*ec2.RevokeSecurityGroupIngressOutput, error) { + return s.ec2.RevokeSecurityGroupIngress(request) +} + +func (s *awsSdkEC2) CreateTags(request *ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error) { + return s.ec2.CreateTags(request) +} + +func (s *awsSdkEC2) DescribeRouteTables(request *ec2.DescribeRouteTablesInput) ([]*ec2.RouteTable, error) { + // Not paged + response, err := s.ec2.DescribeRouteTables(request) + if err != nil { + return nil, fmt.Errorf("error listing AWS route tables: %v", err) + } + return response.RouteTables, nil +} + +func (s *awsSdkEC2) CreateRoute(request *ec2.CreateRouteInput) (*ec2.CreateRouteOutput, error) { + return s.ec2.CreateRoute(request) +} + +func (s *awsSdkEC2) DeleteRoute(request *ec2.DeleteRouteInput) (*ec2.DeleteRouteOutput, error) { + return s.ec2.DeleteRoute(request) +} + +func (s *awsSdkEC2) ModifyInstanceAttribute(request *ec2.ModifyInstanceAttributeInput) (*ec2.ModifyInstanceAttributeOutput, error) { + return s.ec2.ModifyInstanceAttribute(request) +} + +func init() { + cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) { + creds := credentials.NewChainCredentials( + []credentials.Provider{ + &credentials.EnvProvider{}, + &ec2rolecreds.EC2RoleProvider{ + Client: ec2metadata.New(session.New(&aws.Config{})), + }, + &credentials.SharedCredentialsProvider{}, + }) + aws := newAWSSDKProvider(creds) + return newAWSCloud(config, aws) + }) +} + +// readAWSCloudConfig reads an instance of AWSCloudConfig from config reader. +func readAWSCloudConfig(config io.Reader, metadata EC2Metadata) (*AWSCloudConfig, error) { + var cfg AWSCloudConfig + var err error + + if config != nil { + err = gcfg.ReadInto(&cfg, config) + if err != nil { + return nil, err + } + } + + if cfg.Global.Zone == "" { + if metadata != nil { + glog.Info("Zone not specified in configuration file; querying AWS metadata service") + cfg.Global.Zone, err = getAvailabilityZone(metadata) + if err != nil { + return nil, err + } + } + if cfg.Global.Zone == "" { + return nil, fmt.Errorf("no zone specified in configuration file") + } + } + + return &cfg, nil +} + +func getInstanceType(metadata EC2Metadata) (string, error) { + return metadata.GetMetadata("instance-type") +} + +func getAvailabilityZone(metadata EC2Metadata) (string, error) { + return metadata.GetMetadata("placement/availability-zone") +} + +func isRegionValid(region string) bool { + regions := [...]string{ + "us-east-1", + "us-west-1", + "us-west-2", + "eu-west-1", + "eu-central-1", + "ap-southeast-1", + "ap-southeast-2", + "ap-northeast-1", + "cn-north-1", + "us-gov-west-1", + "sa-east-1", + } + for _, r := range regions { + if r == region { + return true + } + } + return false +} + +// Derives the region from a valid az name. +// Returns an error if the az is known invalid (empty) +func azToRegion(az string) (string, error) { + if len(az) < 1 { + return "", fmt.Errorf("invalid (empty) AZ") + } + region := az[:len(az)-1] + return region, nil +} + +// newAWSCloud creates a new instance of AWSCloud. +// AWSProvider and instanceId are primarily for tests +func newAWSCloud(config io.Reader, awsServices AWSServices) (*AWSCloud, error) { + metadata, err := awsServices.Metadata() + if err != nil { + return nil, fmt.Errorf("error creating AWS metadata client: %v", err) + } + + cfg, err := readAWSCloudConfig(config, metadata) + if err != nil { + return nil, fmt.Errorf("unable to read AWS cloud provider config file: %v", err) + } + + zone := cfg.Global.Zone + if len(zone) <= 1 { + return nil, fmt.Errorf("invalid AWS zone in config file: %s", zone) + } + regionName, err := azToRegion(zone) + if err != nil { + return nil, err + } + + valid := isRegionValid(regionName) + if !valid { + return nil, fmt.Errorf("not a valid AWS zone (unknown region): %s", zone) + } + + ec2, err := awsServices.Compute(regionName) + if err != nil { + return nil, fmt.Errorf("error creating AWS EC2 client: %v", err) + } + + elb, err := awsServices.LoadBalancing(regionName) + if err != nil { + return nil, fmt.Errorf("error creating AWS ELB client: %v", err) + } + + asg, err := awsServices.Autoscaling(regionName) + if err != nil { + return nil, fmt.Errorf("error creating AWS autoscaling client: %v", err) + } + + awsCloud := &AWSCloud{ + ec2: ec2, + elb: elb, + asg: asg, + metadata: metadata, + cfg: cfg, + region: regionName, + } + + selfAWSInstance, err := awsCloud.buildSelfAWSInstance() + if err != nil { + return nil, err + } + + awsCloud.selfAWSInstance = selfAWSInstance + awsCloud.vpcID = selfAWSInstance.vpcID + + filterTags := map[string]string{} + if cfg.Global.KubernetesClusterTag != "" { + filterTags[TagNameKubernetesCluster] = cfg.Global.KubernetesClusterTag + } else { + // TODO: Clean up double-API query + info, err := selfAWSInstance.describeInstance() + if err != nil { + return nil, err + } + for _, tag := range info.Tags { + if orEmpty(tag.Key) == TagNameKubernetesCluster { + filterTags[TagNameKubernetesCluster] = orEmpty(tag.Value) + } + } + } + + if filterTags[TagNameKubernetesCluster] == "" { + glog.Errorf("Tag %q not found; Kuberentes may behave unexpectedly.", TagNameKubernetesCluster) + } + + awsCloud.filterTags = filterTags + if len(filterTags) > 0 { + glog.Infof("AWS cloud filtering on tags: %v", filterTags) + } else { + glog.Infof("AWS cloud - no tag filtering") + } + + // Register handler for ECR credentials + once.Do(func() { + aws_credentials.Init() + }) + + return awsCloud, nil +} + +func (aws *AWSCloud) Clusters() (cloudprovider.Clusters, bool) { + return nil, false +} + +// ProviderName returns the cloud provider ID. +func (aws *AWSCloud) ProviderName() string { + return ProviderName +} + +// ScrubDNS filters DNS settings for pods. +func (aws *AWSCloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { + return nameservers, searches +} + +// LoadBalancer returns an implementation of LoadBalancer for Amazon Web Services. +func (s *AWSCloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) { + return s, true +} + +// Instances returns an implementation of Instances for Amazon Web Services. +func (aws *AWSCloud) Instances() (cloudprovider.Instances, bool) { + return aws, true +} + +// Zones returns an implementation of Zones for Amazon Web Services. +func (aws *AWSCloud) Zones() (cloudprovider.Zones, bool) { + return aws, true +} + +// Routes returns an implementation of Routes for Amazon Web Services. +func (aws *AWSCloud) Routes() (cloudprovider.Routes, bool) { + return aws, true +} + +// NodeAddresses is an implementation of Instances.NodeAddresses. +func (c *AWSCloud) NodeAddresses(name string) ([]api.NodeAddress, error) { + if c.selfAWSInstance.nodeName == name || len(name) == 0 { + addresses := []api.NodeAddress{} + + internalIP, err := c.metadata.GetMetadata("local-ipv4") + if err != nil { + return nil, err + } + addresses = append(addresses, api.NodeAddress{Type: api.NodeInternalIP, Address: internalIP}) + // Legacy compatibility: the private ip was the legacy host ip + addresses = append(addresses, api.NodeAddress{Type: api.NodeLegacyHostIP, Address: internalIP}) + + externalIP, err := c.metadata.GetMetadata("public-ipv4") + if err != nil { + //TODO: It would be nice to be able to determine the reason for the failure, + // but the AWS client masks all failures with the same error description. + glog.V(2).Info("Could not determine public IP from AWS metadata.") + } else { + addresses = append(addresses, api.NodeAddress{Type: api.NodeExternalIP, Address: externalIP}) + } + + return addresses, nil + } + instance, err := c.getInstanceByNodeName(name) + if err != nil { + return nil, err + } + + addresses := []api.NodeAddress{} + + if !isNilOrEmpty(instance.PrivateIpAddress) { + ipAddress := *instance.PrivateIpAddress + ip := net.ParseIP(ipAddress) + if ip == nil { + return nil, fmt.Errorf("EC2 instance had invalid private address: %s (%s)", orEmpty(instance.InstanceId), ipAddress) + } + addresses = append(addresses, api.NodeAddress{Type: api.NodeInternalIP, Address: ip.String()}) + + // Legacy compatibility: the private ip was the legacy host ip + addresses = append(addresses, api.NodeAddress{Type: api.NodeLegacyHostIP, Address: ip.String()}) + } + + // TODO: Other IP addresses (multiple ips)? + if !isNilOrEmpty(instance.PublicIpAddress) { + ipAddress := *instance.PublicIpAddress + ip := net.ParseIP(ipAddress) + if ip == nil { + return nil, fmt.Errorf("EC2 instance had invalid public address: %s (%s)", orEmpty(instance.InstanceId), ipAddress) + } + addresses = append(addresses, api.NodeAddress{Type: api.NodeExternalIP, Address: ip.String()}) + } + + return addresses, nil +} + +// ExternalID returns the cloud provider ID of the specified instance (deprecated). +func (c *AWSCloud) ExternalID(name string) (string, error) { + if c.selfAWSInstance.nodeName == name { + // We assume that if this is run on the instance itself, the instance exists and is alive + return c.selfAWSInstance.awsID, nil + } else { + // We must verify that the instance still exists + // Note that if the instance does not exist or is no longer running, we must return ("", cloudprovider.InstanceNotFound) + instance, err := c.findInstanceByNodeName(name) + if err != nil { + return "", err + } + if instance == nil { + return "", cloudprovider.InstanceNotFound + } + return orEmpty(instance.InstanceId), nil + } +} + +// InstanceID returns the cloud provider ID of the specified instance. +func (c *AWSCloud) InstanceID(name string) (string, error) { + // In the future it is possible to also return an endpoint as: + // // + if c.selfAWSInstance.nodeName == name { + return "/" + c.selfAWSInstance.availabilityZone + "/" + c.selfAWSInstance.awsID, nil + } else { + inst, err := c.getInstanceByNodeName(name) + if err != nil { + return "", err + } + return "/" + orEmpty(inst.Placement.AvailabilityZone) + "/" + orEmpty(inst.InstanceId), nil + } +} + +// InstanceType returns the type of the specified instance. +func (c *AWSCloud) InstanceType(name string) (string, error) { + if c.selfAWSInstance.nodeName == name { + return c.selfAWSInstance.instanceType, nil + } else { + inst, err := c.getInstanceByNodeName(name) + if err != nil { + return "", err + } + return orEmpty(inst.InstanceType), nil + } +} + +// Return a list of instances matching regex string. +func (s *AWSCloud) getInstancesByRegex(regex string) ([]string, error) { + filters := []*ec2.Filter{newEc2Filter("instance-state-name", "running")} + filters = s.addFilters(filters) + request := &ec2.DescribeInstancesInput{ + Filters: filters, + } + + instances, err := s.ec2.DescribeInstances(request) + if err != nil { + return []string{}, err + } + if len(instances) == 0 { + return []string{}, fmt.Errorf("no instances returned") + } + + if strings.HasPrefix(regex, "'") && strings.HasSuffix(regex, "'") { + glog.Infof("Stripping quotes around regex (%s)", regex) + regex = regex[1 : len(regex)-1] + } + + re, err := regexp.Compile(regex) + if err != nil { + return []string{}, err + } + + matchingInstances := []string{} + for _, instance := range instances { + // Only return fully-ready instances when listing instances + // (vs a query by name, where we will return it if we find it) + if orEmpty(instance.State.Name) == "pending" { + glog.V(2).Infof("Skipping EC2 instance (pending): %s", *instance.InstanceId) + continue + } + + privateDNSName := orEmpty(instance.PrivateDnsName) + if privateDNSName == "" { + glog.V(2).Infof("Skipping EC2 instance (no PrivateDNSName): %s", + orEmpty(instance.InstanceId)) + continue + } + + for _, tag := range instance.Tags { + if orEmpty(tag.Key) == "Name" && re.MatchString(orEmpty(tag.Value)) { + matchingInstances = append(matchingInstances, privateDNSName) + break + } + } + } + glog.V(2).Infof("Matched EC2 instances: %s", matchingInstances) + return matchingInstances, nil +} + +// List is an implementation of Instances.List. +func (aws *AWSCloud) List(filter string) ([]string, error) { + // TODO: Should really use tag query. No need to go regexp. + return aws.getInstancesByRegex(filter) +} + +// GetZone implements Zones.GetZone +func (c *AWSCloud) GetZone() (cloudprovider.Zone, error) { + return cloudprovider.Zone{ + FailureDomain: c.selfAWSInstance.availabilityZone, + Region: c.region, + }, nil +} + +// Abstraction around AWS Instance Types +// There isn't an API to get information for a particular instance type (that I know of) +type awsInstanceType struct { +} + +// Used to represent a mount device for attaching an EBS volume +// This should be stored as a single letter (i.e. c, not sdc or /dev/sdc) +type mountDevice string + +// TODO: Also return number of mounts allowed? +func (self *awsInstanceType) getEBSMountDevices() []mountDevice { + // See: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html + // We will generate "ba", "bb", "bc"..."bz", "ca", ..., up to DefaultMaxEBSVolumes + devices := []mountDevice{} + count := 0 + for first := 'b'; count < DefaultMaxEBSVolumes; first++ { + for second := 'a'; count < DefaultMaxEBSVolumes && second <= 'z'; second++ { + device := mountDevice(fmt.Sprintf("%c%c", first, second)) + devices = append(devices, device) + count++ + } + } + + return devices +} + +type awsInstance struct { + ec2 EC2 + + // id in AWS + awsID string + + // node name in k8s + nodeName string + + // availability zone the instance resides in + availabilityZone string + + // ID of VPC the instance resides in + vpcID string + + // ID of subnet the instance resides in + subnetID string + + // instance type + instanceType string + + mutex sync.Mutex + + // We keep an active list of devices we have assigned but not yet + // attached, to avoid a race condition where we assign a device mapping + // and then get a second request before we attach the volume + attaching map[mountDevice]string +} + +// newAWSInstance creates a new awsInstance object +func newAWSInstance(ec2Service EC2, instance *ec2.Instance) *awsInstance { + az := "" + if instance.Placement != nil { + az = aws.StringValue(instance.Placement.AvailabilityZone) + } + self := &awsInstance{ + ec2: ec2Service, + awsID: aws.StringValue(instance.InstanceId), + nodeName: aws.StringValue(instance.PrivateDnsName), + availabilityZone: az, + instanceType: aws.StringValue(instance.InstanceType), + vpcID: aws.StringValue(instance.VpcId), + subnetID: aws.StringValue(instance.SubnetId), + } + + self.attaching = make(map[mountDevice]string) + + return self +} + +// Gets the awsInstanceType that models the instance type of this instance +func (self *awsInstance) getInstanceType() *awsInstanceType { + // TODO: Make this real + awsInstanceType := &awsInstanceType{} + return awsInstanceType +} + +// Gets the full information about this instance from the EC2 API +func (self *awsInstance) describeInstance() (*ec2.Instance, error) { + instanceID := self.awsID + request := &ec2.DescribeInstancesInput{ + InstanceIds: []*string{&instanceID}, + } + + instances, err := self.ec2.DescribeInstances(request) + if err != nil { + return nil, err + } + if len(instances) == 0 { + return nil, fmt.Errorf("no instances found for instance: %s", self.awsID) + } + if len(instances) > 1 { + return nil, fmt.Errorf("multiple instances found for instance: %s", self.awsID) + } + return instances[0], nil +} + +// Gets the mountDevice already assigned to the volume, or assigns an unused mountDevice. +// If the volume is already assigned, this will return the existing mountDevice with alreadyAttached=true. +// Otherwise the mountDevice is assigned by finding the first available mountDevice, and it is returned with alreadyAttached=false. +func (self *awsInstance) getMountDevice(volumeID string, assign bool) (assigned mountDevice, alreadyAttached bool, err error) { + instanceType := self.getInstanceType() + if instanceType == nil { + return "", false, fmt.Errorf("could not get instance type for instance: %s", self.awsID) + } + + // We lock to prevent concurrent mounts from conflicting + // We may still conflict if someone calls the API concurrently, + // but the AWS API will then fail one of the two attach operations + self.mutex.Lock() + defer self.mutex.Unlock() + + info, err := self.describeInstance() + if err != nil { + return "", false, err + } + deviceMappings := map[mountDevice]string{} + for _, blockDevice := range info.BlockDeviceMappings { + name := aws.StringValue(blockDevice.DeviceName) + if strings.HasPrefix(name, "/dev/sd") { + name = name[7:] + } + if strings.HasPrefix(name, "/dev/xvd") { + name = name[8:] + } + if len(name) < 1 || len(name) > 2 { + glog.Warningf("Unexpected EBS DeviceName: %q", aws.StringValue(blockDevice.DeviceName)) + } + deviceMappings[mountDevice(name)] = aws.StringValue(blockDevice.Ebs.VolumeId) + } + + for mountDevice, volume := range self.attaching { + deviceMappings[mountDevice] = volume + } + + // Check to see if this volume is already assigned a device on this machine + for mountDevice, mappingVolumeID := range deviceMappings { + if volumeID == mappingVolumeID { + if assign { + glog.Warningf("Got assignment call for already-assigned volume: %s@%s", mountDevice, mappingVolumeID) + } + return mountDevice, true, nil + } + } + + if !assign { + return mountDevice(""), false, nil + } + + // Check all the valid mountpoints to see if any of them are free + valid := instanceType.getEBSMountDevices() + chosen := mountDevice("") + for _, mountDevice := range valid { + _, found := deviceMappings[mountDevice] + if !found { + chosen = mountDevice + break + } + } + + if chosen == "" { + glog.Warningf("Could not assign a mount device (all in use?). mappings=%v, valid=%v", deviceMappings, valid) + return "", false, fmt.Errorf("Too many EBS volumes attached to node %s.", self.nodeName) + } + + self.attaching[chosen] = volumeID + glog.V(2).Infof("Assigned mount device %s -> volume %s", chosen, volumeID) + + return chosen, false, nil +} + +func (self *awsInstance) endAttaching(volumeID string, mountDevice mountDevice) { + self.mutex.Lock() + defer self.mutex.Unlock() + + existingVolumeID, found := self.attaching[mountDevice] + if !found { + glog.Errorf("endAttaching on non-allocated device") + return + } + if volumeID != existingVolumeID { + glog.Errorf("endAttaching on device assigned to different volume") + return + } + glog.V(2).Infof("Releasing mount device mapping: %s -> volume %s", mountDevice, volumeID) + delete(self.attaching, mountDevice) +} + +type awsDisk struct { + ec2 EC2 + + // Name in k8s + name string + // id in AWS + awsID string +} + +func newAWSDisk(aws *AWSCloud, name string) (*awsDisk, error) { + // name looks like aws://availability-zone/id + + // The original idea of the URL-style name was to put the AZ into the + // host, so we could find the AZ immediately from the name without + // querying the API. But it turns out we don't actually need it for + // Ubernetes-Lite, as we put the AZ into the labels on the PV instead. + // However, if in future we want to support Ubernetes-Lite + // volume-awareness without using PersistentVolumes, we likely will + // want the AZ in the host. + + if !strings.HasPrefix(name, "aws://") { + name = "aws://" + "" + "/" + name + } + url, err := url.Parse(name) + if err != nil { + // TODO: Maybe we should pass a URL into the Volume functions + return nil, fmt.Errorf("Invalid disk name (%s): %v", name, err) + } + if url.Scheme != "aws" { + return nil, fmt.Errorf("Invalid scheme for AWS volume (%s)", name) + } + + awsID := url.Path + if len(awsID) > 1 && awsID[0] == '/' { + awsID = awsID[1:] + } + + // TODO: Regex match? + if strings.Contains(awsID, "/") || !strings.HasPrefix(awsID, "vol-") { + return nil, fmt.Errorf("Invalid format for AWS volume (%s)", name) + } + + disk := &awsDisk{ec2: aws.ec2, name: name, awsID: awsID} + return disk, nil +} + +// Gets the full information about this volume from the EC2 API +func (self *awsDisk) describeVolume() (*ec2.Volume, error) { + volumeID := self.awsID + + request := &ec2.DescribeVolumesInput{ + VolumeIds: []*string{&volumeID}, + } + + volumes, err := self.ec2.DescribeVolumes(request) + if err != nil { + return nil, fmt.Errorf("error querying ec2 for volume info: %v", err) + } + if len(volumes) == 0 { + return nil, fmt.Errorf("no volumes found for volume: %s", self.awsID) + } + if len(volumes) > 1 { + return nil, fmt.Errorf("multiple volumes found for volume: %s", self.awsID) + } + return volumes[0], nil +} + +// waitForAttachmentStatus polls until the attachment status is the expected value +// TODO(justinsb): return (bool, error) +func (self *awsDisk) waitForAttachmentStatus(status string) error { + // TODO: There may be a faster way to get this when we're attaching locally + attempt := 0 + maxAttempts := 60 + + for { + info, err := self.describeVolume() + if err != nil { + return err + } + if len(info.Attachments) > 1 { + glog.Warningf("Found multiple attachments for volume: %v", info) + } + attachmentStatus := "" + for _, attachment := range info.Attachments { + if attachmentStatus != "" { + glog.Warning("Found multiple attachments: ", info) + } + if attachment.State != nil { + attachmentStatus = *attachment.State + } else { + // Shouldn't happen, but don't panic... + glog.Warning("Ignoring nil attachment state: ", attachment) + } + } + if attachmentStatus == "" { + attachmentStatus = "detached" + } + if attachmentStatus == status { + return nil + } + + glog.V(2).Infof("Waiting for volume state: actual=%s, desired=%s", attachmentStatus, status) + + attempt++ + if attempt > maxAttempts { + glog.Warningf("Timeout waiting for volume state: actual=%s, desired=%s", attachmentStatus, status) + return errors.New("Timeout waiting for volume state") + } + + time.Sleep(1 * time.Second) + } +} + +// Deletes the EBS disk +func (self *awsDisk) deleteVolume() (bool, error) { + request := &ec2.DeleteVolumeInput{VolumeId: aws.String(self.awsID)} + _, err := self.ec2.DeleteVolume(request) + if err != nil { + if awsError, ok := err.(awserr.Error); ok { + if awsError.Code() == "InvalidVolume.NotFound" { + return false, nil + } + } + return false, fmt.Errorf("error deleting EBS volumes: %v", err) + } + return true, nil +} + +// Builds the awsInstance for the EC2 instance on which we are running. +// This is called when the AWSCloud is initialized, and should not be called otherwise (because the awsInstance for the local instance is a singleton with drive mapping state) +func (c *AWSCloud) buildSelfAWSInstance() (*awsInstance, error) { + if c.selfAWSInstance != nil { + panic("do not call buildSelfAWSInstance directly") + } + instanceId, err := c.metadata.GetMetadata("instance-id") + if err != nil { + return nil, fmt.Errorf("error fetching instance-id from ec2 metadata service: %v", err) + } + + // We want to fetch the hostname via the EC2 metadata service + // (`GetMetadata("local-hostname")`): But see #11543 - we need to use + // the EC2 API to get the privateDnsName in case of a private DNS zone + // e.g. mydomain.io, because the metadata service returns the wrong + // hostname. Once we're doing that, we might as well get all our + // information from the instance returned by the EC2 API - it is a + // single API call to get all the information, and it means we don't + // have two code paths. + instance, err := c.getInstanceByID(instanceId) + if err != nil { + return nil, fmt.Errorf("error finding instance %s: %v", instanceId, err) + } + return newAWSInstance(c.ec2, instance), nil +} + +// Gets the awsInstance with node-name nodeName, or the 'self' instance if nodeName == "" +func (c *AWSCloud) getAwsInstance(nodeName string) (*awsInstance, error) { + var awsInstance *awsInstance + if nodeName == "" { + awsInstance = c.selfAWSInstance + } else { + instance, err := c.getInstanceByNodeName(nodeName) + if err != nil { + return nil, fmt.Errorf("error finding instance %s: %v", nodeName, err) + } + + awsInstance = newAWSInstance(c.ec2, instance) + } + + return awsInstance, nil +} + +// Implements Volumes.AttachDisk +func (c *AWSCloud) AttachDisk(diskName string, instanceName string, readOnly bool) (string, error) { + disk, err := newAWSDisk(c, diskName) + if err != nil { + return "", err + } + + awsInstance, err := c.getAwsInstance(instanceName) + if err != nil { + return "", err + } + + if readOnly { + // TODO: We could enforce this when we mount the volume (?) + // TODO: We could also snapshot the volume and attach copies of it + return "", errors.New("AWS volumes cannot be mounted read-only") + } + + mountDevice, alreadyAttached, err := awsInstance.getMountDevice(disk.awsID, true) + if err != nil { + return "", err + } + + // Inside the instance, the mountpoint always looks like /dev/xvdX (?) + hostDevice := "/dev/xvd" + string(mountDevice) + // In the EC2 API, it is sometimes is /dev/sdX and sometimes /dev/xvdX + // We are running on the node here, so we check if /dev/xvda exists to determine this + ec2Device := "/dev/xvd" + string(mountDevice) + if _, err := os.Stat("/dev/xvda"); os.IsNotExist(err) { + ec2Device = "/dev/sd" + string(mountDevice) + } + + // attachEnded is set to true if the attach operation completed + // (successfully or not) + attachEnded := false + defer func() { + if attachEnded { + awsInstance.endAttaching(disk.awsID, mountDevice) + } + }() + + if !alreadyAttached { + request := &ec2.AttachVolumeInput{ + Device: aws.String(ec2Device), + InstanceId: aws.String(awsInstance.awsID), + VolumeId: aws.String(disk.awsID), + } + + attachResponse, err := c.ec2.AttachVolume(request) + if err != nil { + attachEnded = true + // TODO: Check if the volume was concurrently attached? + return "", fmt.Errorf("Error attaching EBS volume: %v", err) + } + + glog.V(2).Infof("AttachVolume request returned %v", attachResponse) + } + + err = disk.waitForAttachmentStatus("attached") + if err != nil { + return "", err + } + + attachEnded = true + + return hostDevice, nil +} + +// Implements Volumes.DetachDisk +func (aws *AWSCloud) DetachDisk(diskName string, instanceName string) (string, error) { + disk, err := newAWSDisk(aws, diskName) + if err != nil { + return "", err + } + + awsInstance, err := aws.getAwsInstance(instanceName) + if err != nil { + return "", err + } + + mountDevice, alreadyAttached, err := awsInstance.getMountDevice(disk.awsID, false) + if err != nil { + return "", err + } + + if !alreadyAttached { + glog.Warning("DetachDisk called on non-attached disk: ", diskName) + // TODO: Continue? Tolerate non-attached error in DetachVolume? + } + + request := ec2.DetachVolumeInput{ + InstanceId: &awsInstance.awsID, + VolumeId: &disk.awsID, + } + + response, err := aws.ec2.DetachVolume(&request) + if err != nil { + return "", fmt.Errorf("error detaching EBS volume: %v", err) + } + if response == nil { + return "", errors.New("no response from DetachVolume") + } + + err = disk.waitForAttachmentStatus("detached") + if err != nil { + return "", err + } + + if mountDevice != "" { + awsInstance.endAttaching(disk.awsID, mountDevice) + } + + hostDevicePath := "/dev/xvd" + string(mountDevice) + return hostDevicePath, err +} + +// Implements Volumes.CreateVolume +func (s *AWSCloud) CreateDisk(volumeOptions *VolumeOptions) (string, error) { + // Default to creating in the current zone + // TODO: Spread across zones? + createAZ := s.selfAWSInstance.availabilityZone + + // TODO: Should we tag this with the cluster id (so it gets deleted when the cluster does?) + request := &ec2.CreateVolumeInput{} + request.AvailabilityZone = &createAZ + volSize := int64(volumeOptions.CapacityGB) + request.Size = &volSize + request.VolumeType = aws.String(DefaultVolumeType) + response, err := s.ec2.CreateVolume(request) + if err != nil { + return "", err + } + + az := orEmpty(response.AvailabilityZone) + awsID := orEmpty(response.VolumeId) + + volumeName := "aws://" + az + "/" + awsID + + // apply tags + tags := make(map[string]string) + for k, v := range volumeOptions.Tags { + tags[k] = v + } + + if s.getClusterName() != "" { + tags[TagNameKubernetesCluster] = s.getClusterName() + } + + if len(tags) != 0 { + if err := s.createTags(awsID, tags); err != nil { + // delete the volume and hope it succeeds + _, delerr := s.DeleteDisk(volumeName) + if delerr != nil { + // delete did not succeed, we have a stray volume! + return "", fmt.Errorf("error tagging volume %s, could not delete the volume: %v", volumeName, delerr) + } + return "", fmt.Errorf("error tagging volume %s: %v", volumeName, err) + } + } + return volumeName, nil +} + +// Implements Volumes.DeleteDisk +func (c *AWSCloud) DeleteDisk(volumeName string) (bool, error) { + awsDisk, err := newAWSDisk(c, volumeName) + if err != nil { + return false, err + } + return awsDisk.deleteVolume() +} + +// Implements Volumes.GetVolumeLabels +func (c *AWSCloud) GetVolumeLabels(volumeName string) (map[string]string, error) { + awsDisk, err := newAWSDisk(c, volumeName) + if err != nil { + return nil, err + } + info, err := awsDisk.describeVolume() + if err != nil { + return nil, err + } + labels := make(map[string]string) + az := aws.StringValue(info.AvailabilityZone) + if az == "" { + return nil, fmt.Errorf("volume did not have AZ information: %q", info.VolumeId) + } + + labels[unversioned.LabelZoneFailureDomain] = az + region, err := azToRegion(az) + if err != nil { + return nil, err + } + labels[unversioned.LabelZoneRegion] = region + + return labels, nil +} + +// Gets the current load balancer state +func (s *AWSCloud) describeLoadBalancer(name string) (*elb.LoadBalancerDescription, error) { + request := &elb.DescribeLoadBalancersInput{} + request.LoadBalancerNames = []*string{&name} + + response, err := s.elb.DescribeLoadBalancers(request) + if err != nil { + if awsError, ok := err.(awserr.Error); ok { + if awsError.Code() == "LoadBalancerNotFound" { + return nil, nil + } + } + return nil, err + } + + var ret *elb.LoadBalancerDescription + for _, loadBalancer := range response.LoadBalancerDescriptions { + if ret != nil { + glog.Errorf("Found multiple load balancers with name: %s", name) + } + ret = loadBalancer + } + return ret, nil +} + +// Retrieves instance's vpc id from metadata +func (self *AWSCloud) findVPCID() (string, error) { + macs, err := self.metadata.GetMetadata("network/interfaces/macs/") + if err != nil { + return "", fmt.Errorf("Could not list interfaces of the instance: %v", err) + } + + // loop over interfaces, first vpc id returned wins + for _, macPath := range strings.Split(macs, "\n") { + if len(macPath) == 0 { + continue + } + url := fmt.Sprintf("network/interfaces/macs/%svpc-id", macPath) + vpcID, err := self.metadata.GetMetadata(url) + if err != nil { + continue + } + return vpcID, nil + } + return "", fmt.Errorf("Could not find VPC ID in instance metadata") +} + +// Retrieves the specified security group from the AWS API, or returns nil if not found +func (s *AWSCloud) findSecurityGroup(securityGroupId string) (*ec2.SecurityGroup, error) { + describeSecurityGroupsRequest := &ec2.DescribeSecurityGroupsInput{ + GroupIds: []*string{&securityGroupId}, + } + // We don't apply our tag filters because we are retrieving by ID + + groups, err := s.ec2.DescribeSecurityGroups(describeSecurityGroupsRequest) + if err != nil { + glog.Warningf("Error retrieving security group: %q", err) + return nil, err + } + + if len(groups) == 0 { + return nil, nil + } + if len(groups) != 1 { + // This should not be possible - ids should be unique + return nil, fmt.Errorf("multiple security groups found with same id %q", securityGroupId) + } + group := groups[0] + return group, nil +} + +func isEqualIntPointer(l, r *int64) bool { + if l == nil { + return r == nil + } + if r == nil { + return l == nil + } + return *l == *r +} + +func isEqualStringPointer(l, r *string) bool { + if l == nil { + return r == nil + } + if r == nil { + return l == nil + } + return *l == *r +} + +func ipPermissionExists(newPermission, existing *ec2.IpPermission, compareGroupUserIDs bool) bool { + if !isEqualIntPointer(newPermission.FromPort, existing.FromPort) { + return false + } + if !isEqualIntPointer(newPermission.ToPort, existing.ToPort) { + return false + } + if !isEqualStringPointer(newPermission.IpProtocol, existing.IpProtocol) { + return false + } + // Check only if newPermission is a subset of existing. Usually it has zero or one elements. + // Not doing actual CIDR math yet; not clear it's needed, either. + glog.V(4).Infof("Comparing %v to %v", newPermission, existing) + if len(newPermission.IpRanges) > len(existing.IpRanges) { + return false + } + + for j := range newPermission.IpRanges { + found := false + for k := range existing.IpRanges { + if isEqualStringPointer(newPermission.IpRanges[j].CidrIp, existing.IpRanges[k].CidrIp) { + found = true + break + } + } + if found == false { + return false + } + } + for _, leftPair := range newPermission.UserIdGroupPairs { + for _, rightPair := range existing.UserIdGroupPairs { + if isEqualUserGroupPair(leftPair, rightPair, compareGroupUserIDs) { + return true + } + } + return false + } + + return true +} + +func isEqualUserGroupPair(l, r *ec2.UserIdGroupPair, compareGroupUserIDs bool) bool { + glog.V(2).Infof("Comparing %v to %v", *l.GroupId, *r.GroupId) + if isEqualStringPointer(l.GroupId, r.GroupId) { + if compareGroupUserIDs { + if isEqualStringPointer(l.UserId, r.UserId) { + return true + } + } else { + return true + } + } + + return false +} + +// Makes sure the security group ingress is exactly the specified permissions +// Returns true if and only if changes were made +// The security group must already exist +func (s *AWSCloud) setSecurityGroupIngress(securityGroupId string, permissions IPPermissionSet) (bool, error) { + group, err := s.findSecurityGroup(securityGroupId) + if err != nil { + glog.Warning("Error retrieving security group", err) + return false, err + } + + if group == nil { + return false, fmt.Errorf("security group not found: %s", securityGroupId) + } + + glog.V(2).Infof("Existing security group ingress: %s %v", securityGroupId, group.IpPermissions) + + actual := NewIPPermissionSet(group.IpPermissions...) + + // EC2 groups rules together, for example combining: + // + // { Port=80, Range=[A] } and { Port=80, Range=[B] } + // + // into { Port=80, Range=[A,B] } + // + // We have to ungroup them, because otherwise the logic becomes really + // complicated, and also because if we have Range=[A,B] and we try to + // add Range=[A] then EC2 complains about a duplicate rule. + permissions = permissions.Ungroup() + actual = actual.Ungroup() + + remove := actual.Difference(permissions) + add := permissions.Difference(actual) + + if add.Len() == 0 && remove.Len() == 0 { + return false, nil + } + + // TODO: There is a limit in VPC of 100 rules per security group, so we + // probably should try grouping or combining to fit under this limit. + // But this is only used on the ELB security group currently, so it + // would require (ports * CIDRS) > 100. Also, it isn't obvious exactly + // how removing single permissions from compound rules works, and we + // don't want to accidentally open more than intended while we're + // applying changes. + if add.Len() != 0 { + glog.V(2).Infof("Adding security group ingress: %s %v", securityGroupId, add.List()) + + request := &ec2.AuthorizeSecurityGroupIngressInput{} + request.GroupId = &securityGroupId + request.IpPermissions = add.List() + _, err = s.ec2.AuthorizeSecurityGroupIngress(request) + if err != nil { + return false, fmt.Errorf("error authorizing security group ingress: %v", err) + } + } + if remove.Len() != 0 { + glog.V(2).Infof("Remove security group ingress: %s %v", securityGroupId, remove.List()) + + request := &ec2.RevokeSecurityGroupIngressInput{} + request.GroupId = &securityGroupId + request.IpPermissions = remove.List() + _, err = s.ec2.RevokeSecurityGroupIngress(request) + if err != nil { + return false, fmt.Errorf("error revoking security group ingress: %v", err) + } + } + + return true, nil +} + +// Makes sure the security group includes the specified permissions +// Returns true if and only if changes were made +// The security group must already exist +func (s *AWSCloud) addSecurityGroupIngress(securityGroupId string, addPermissions []*ec2.IpPermission) (bool, error) { + group, err := s.findSecurityGroup(securityGroupId) + if err != nil { + glog.Warningf("Error retrieving security group: %v", err) + return false, err + } + + if group == nil { + return false, fmt.Errorf("security group not found: %s", securityGroupId) + } + + glog.V(2).Infof("Existing security group ingress: %s %v", securityGroupId, group.IpPermissions) + + changes := []*ec2.IpPermission{} + for _, addPermission := range addPermissions { + hasUserID := false + for i := range addPermission.UserIdGroupPairs { + if addPermission.UserIdGroupPairs[i].UserId != nil { + hasUserID = true + } + } + + found := false + for _, groupPermission := range group.IpPermissions { + if ipPermissionExists(addPermission, groupPermission, hasUserID) { + found = true + break + } + } + + if !found { + changes = append(changes, addPermission) + } + } + + if len(changes) == 0 { + return false, nil + } + + glog.V(2).Infof("Adding security group ingress: %s %v", securityGroupId, changes) + + request := &ec2.AuthorizeSecurityGroupIngressInput{} + request.GroupId = &securityGroupId + request.IpPermissions = changes + _, err = s.ec2.AuthorizeSecurityGroupIngress(request) + if err != nil { + glog.Warning("Error authorizing security group ingress", err) + return false, fmt.Errorf("error authorizing security group ingress: %v", err) + } + + return true, nil +} + +// Makes sure the security group no longer includes the specified permissions +// Returns true if and only if changes were made +// If the security group no longer exists, will return (false, nil) +func (s *AWSCloud) removeSecurityGroupIngress(securityGroupId string, removePermissions []*ec2.IpPermission) (bool, error) { + group, err := s.findSecurityGroup(securityGroupId) + if err != nil { + glog.Warningf("Error retrieving security group: %v", err) + return false, err + } + + if group == nil { + glog.Warning("Security group not found: ", securityGroupId) + return false, nil + } + + changes := []*ec2.IpPermission{} + for _, removePermission := range removePermissions { + hasUserID := false + for i := range removePermission.UserIdGroupPairs { + if removePermission.UserIdGroupPairs[i].UserId != nil { + hasUserID = true + } + } + + var found *ec2.IpPermission + for _, groupPermission := range group.IpPermissions { + if ipPermissionExists(removePermission, groupPermission, hasUserID) { + found = removePermission + break + } + } + + if found != nil { + changes = append(changes, found) + } + } + + if len(changes) == 0 { + return false, nil + } + + glog.V(2).Infof("Removing security group ingress: %s %v", securityGroupId, changes) + + request := &ec2.RevokeSecurityGroupIngressInput{} + request.GroupId = &securityGroupId + request.IpPermissions = changes + _, err = s.ec2.RevokeSecurityGroupIngress(request) + if err != nil { + glog.Warningf("Error revoking security group ingress: %v", err) + return false, err + } + + return true, nil +} + +// Ensure that a resource has the correct tags +// If it has no tags, we assume that this was a problem caused by an error in between creation and tagging, +// and we add the tags. If it has a different cluster's tags, that is an error. +func (s *AWSCloud) ensureClusterTags(resourceID string, tags []*ec2.Tag) error { + actualTags := make(map[string]string) + for _, tag := range tags { + actualTags[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value) + } + + addTags := make(map[string]string) + for k, expected := range s.filterTags { + actual := actualTags[k] + if actual == expected { + continue + } + if actual == "" { + glog.Warningf("Resource %q was missing expected cluster tag %q. Will add (with value %q)", resourceID, k, expected) + addTags[k] = expected + } else { + return fmt.Errorf("resource %q has tag belonging to another cluster: %q=%q (expected %q)", resourceID, k, actual, expected) + } + } + + if err := s.createTags(resourceID, addTags); err != nil { + return fmt.Errorf("error adding missing tags to resource %q: %v", resourceID, err) + } + + return nil +} + +// Makes sure the security group exists. +// For multi-cluster isolation, name must be globally unique, for example derived from the service UUID. +// Returns the security group id or error +func (s *AWSCloud) ensureSecurityGroup(name string, description string) (string, error) { + groupID := "" + attempt := 0 + for { + attempt++ + + request := &ec2.DescribeSecurityGroupsInput{} + filters := []*ec2.Filter{ + newEc2Filter("group-name", name), + newEc2Filter("vpc-id", s.vpcID), + } + // Note that we do _not_ add our tag filters; group-name + vpc-id is the EC2 primary key. + // However, we do check that it matches our tags. + // If it doesn't have any tags, we tag it; this is how we recover if we failed to tag before. + // If it has a different cluster's tags, that is an error. + // This shouldn't happen because name is expected to be globally unique (UUID derived) + request.Filters = filters + + securityGroups, err := s.ec2.DescribeSecurityGroups(request) + if err != nil { + return "", err + } + + if len(securityGroups) >= 1 { + if len(securityGroups) > 1 { + glog.Warningf("Found multiple security groups with name: %q", name) + } + err := s.ensureClusterTags(aws.StringValue(securityGroups[0].GroupId), securityGroups[0].Tags) + if err != nil { + return "", err + } + + return aws.StringValue(securityGroups[0].GroupId), nil + } + + createRequest := &ec2.CreateSecurityGroupInput{} + createRequest.VpcId = &s.vpcID + createRequest.GroupName = &name + createRequest.Description = &description + + createResponse, err := s.ec2.CreateSecurityGroup(createRequest) + if err != nil { + ignore := false + switch err := err.(type) { + case awserr.Error: + if err.Code() == "InvalidGroup.Duplicate" && attempt < MaxReadThenCreateRetries { + glog.V(2).Infof("Got InvalidGroup.Duplicate while creating security group (race?); will retry") + ignore = true + } + } + if !ignore { + glog.Error("Error creating security group: ", err) + return "", err + } + time.Sleep(1 * time.Second) + } else { + groupID = orEmpty(createResponse.GroupId) + break + } + } + if groupID == "" { + return "", fmt.Errorf("created security group, but id was not returned: %s", name) + } + + err := s.createTags(groupID, s.filterTags) + if err != nil { + // If we retry, ensureClusterTags will recover from this - it + // will add the missing tags. We could delete the security + // group here, but that doesn't feel like the right thing, as + // the caller is likely to retry the create + return "", fmt.Errorf("error tagging security group: %v", err) + } + return groupID, nil +} + +// createTags calls EC2 CreateTags, but adds retry-on-failure logic +// We retry mainly because if we create an object, we cannot tag it until it is "fully created" (eventual consistency) +// The error code varies though (depending on what we are tagging), so we simply retry on all errors +func (s *AWSCloud) createTags(resourceID string, tags map[string]string) error { + if tags == nil || len(tags) == 0 { + return nil + } + + var awsTags []*ec2.Tag + for k, v := range tags { + tag := &ec2.Tag{ + Key: aws.String(k), + Value: aws.String(v), + } + awsTags = append(awsTags, tag) + } + + request := &ec2.CreateTagsInput{} + request.Resources = []*string{&resourceID} + request.Tags = awsTags + + // TODO: We really should do exponential backoff here + attempt := 0 + maxAttempts := 60 + + for { + _, err := s.ec2.CreateTags(request) + if err == nil { + return nil + } + + // We could check that the error is retryable, but the error code changes based on what we are tagging + // SecurityGroup: InvalidGroup.NotFound + attempt++ + if attempt > maxAttempts { + glog.Warningf("Failed to create tags (too many attempts): %v", err) + return err + } + glog.V(2).Infof("Failed to create tags; will retry. Error was %v", err) + time.Sleep(1 * time.Second) + } +} + +// Finds the value for a given tag. +func findTag(tags []*ec2.Tag, key string) (string, bool) { + for _, tag := range tags { + if aws.StringValue(tag.Key) == key { + return aws.StringValue(tag.Value), true + } + } + return "", false +} + +// Finds the subnets associated with the cluster, by matching tags. +// For maximal backwards compatability, if no subnets are tagged, it will fall-back to the current subnet. +// However, in future this will likely be treated as an error. +func (c *AWSCloud) findSubnets() ([]*ec2.Subnet, error) { + request := &ec2.DescribeSubnetsInput{} + vpcIDFilter := newEc2Filter("vpc-id", c.vpcID) + filters := []*ec2.Filter{vpcIDFilter} + filters = c.addFilters(filters) + request.Filters = filters + + subnets, err := c.ec2.DescribeSubnets(request) + if err != nil { + return nil, fmt.Errorf("error describing subnets: %v", err) + } + + if len(subnets) != 0 { + return subnets, nil + } + + // Fall back to the current instance subnets, if nothing is tagged + glog.Warningf("No tagged subnets found; will fall-back to the current subnet only. This is likely to be an error in a future version of k8s.") + + request = &ec2.DescribeSubnetsInput{} + filters = []*ec2.Filter{newEc2Filter("subnet-id", c.selfAWSInstance.subnetID)} + request.Filters = filters + + subnets, err = c.ec2.DescribeSubnets(request) + if err != nil { + return nil, fmt.Errorf("error describing subnets: %v", err) + } + + return subnets, nil +} + +// Finds the subnets to use for an ELB we are creating. +// Normal (Internet-facing) ELBs must use public subnets, so we skip private subnets. +// Internal ELBs can use public or private subnets, but if we have a private subnet we should prefer that. +func (s *AWSCloud) findELBSubnets(internalELB bool) ([]string, error) { + vpcIDFilter := newEc2Filter("vpc-id", s.vpcID) + + subnets, err := s.findSubnets() + if err != nil { + return nil, err + } + + rRequest := &ec2.DescribeRouteTablesInput{} + rRequest.Filters = []*ec2.Filter{vpcIDFilter} + rt, err := s.ec2.DescribeRouteTables(rRequest) + if err != nil { + return nil, fmt.Errorf("error describe route table: %v", err) + } + + subnetsByAZ := make(map[string]*ec2.Subnet) + for _, subnet := range subnets { + az := aws.StringValue(subnet.AvailabilityZone) + id := aws.StringValue(subnet.SubnetId) + if az == "" || id == "" { + glog.Warningf("Ignoring subnet with empty az/id: %v", subnet) + continue + } + + isPublic, err := isSubnetPublic(rt, id) + if err != nil { + return nil, err + } + if !internalELB && !isPublic { + glog.V(2).Infof("Ignoring private subnet for public ELB %q", id) + continue + } + + existing := subnetsByAZ[az] + if existing == nil { + subnetsByAZ[az] = subnet + continue + } + + // Try to break the tie using a tag + var tagName string + if internalELB { + tagName = TagNameSubnetInternalELB + } else { + tagName = TagNameSubnetPublicELB + } + + _, existingHasTag := findTag(existing.Tags, tagName) + _, subnetHasTag := findTag(subnet.Tags, tagName) + + if existingHasTag != subnetHasTag { + if subnetHasTag { + subnetsByAZ[az] = subnet + } + continue + } + + // TODO: Should this be an error? + glog.Warningf("Found multiple subnets in AZ %q; making arbitrary choice between subnets %q and %q", az, *existing.SubnetId, *subnet.SubnetId) + continue + } + + var subnetIDs []string + for _, subnet := range subnetsByAZ { + subnetIDs = append(subnetIDs, aws.StringValue(subnet.SubnetId)) + } + + return subnetIDs, nil +} + +func isSubnetPublic(rt []*ec2.RouteTable, subnetID string) (bool, error) { + var subnetTable *ec2.RouteTable + for _, table := range rt { + for _, assoc := range table.Associations { + if aws.StringValue(assoc.SubnetId) == subnetID { + subnetTable = table + break + } + } + } + + if subnetTable == nil { + // If there is no explicit association, the subnet will be implicitly + // associated with the VPC's main routing table. + for _, table := range rt { + for _, assoc := range table.Associations { + if aws.BoolValue(assoc.Main) == true { + glog.V(4).Infof("Assuming implicit use of main routing table %s for %s", + aws.StringValue(table.RouteTableId), subnetID) + subnetTable = table + break + } + } + } + } + + if subnetTable == nil { + return false, fmt.Errorf("Could not locate routing table for subnet %s", subnetID) + } + + for _, route := range subnetTable.Routes { + // There is no direct way in the AWS API to determine if a subnet is public or private. + // A public subnet is one which has an internet gateway route + // we look for the gatewayId and make sure it has the prefix of igw to differentiate + // from the default in-subnet route which is called "local" + // or other virtual gateway (starting with vgv) + // or vpc peering connections (starting with pcx). + if strings.HasPrefix(aws.StringValue(route.GatewayId), "igw") { + return true, nil + } + } + + return false, nil +} + +// EnsureLoadBalancer implements LoadBalancer.EnsureLoadBalancer +func (s *AWSCloud) EnsureLoadBalancer(apiService *api.Service, hosts []string, annotations map[string]string) (*api.LoadBalancerStatus, error) { + glog.V(2).Infof("EnsureLoadBalancer(%v, %v, %v, %v, %v, %v, %v)", + apiService.Namespace, apiService.Name, s.region, apiService.Spec.LoadBalancerIP, apiService.Spec.Ports, hosts, annotations) + + if apiService.Spec.SessionAffinity != api.ServiceAffinityNone { + // ELB supports sticky sessions, but only when configured for HTTP/HTTPS + return nil, fmt.Errorf("unsupported load balancer affinity: %v", apiService.Spec.SessionAffinity) + } + + if len(apiService.Spec.Ports) == 0 { + return nil, fmt.Errorf("requested load balancer with no ports") + } + + for _, port := range apiService.Spec.Ports { + if port.Protocol != api.ProtocolTCP { + return nil, fmt.Errorf("Only TCP LoadBalancer is supported for AWS ELB") + } + } + + if apiService.Spec.LoadBalancerIP != "" { + return nil, fmt.Errorf("LoadBalancerIP cannot be specified for AWS ELB") + } + + instances, err := s.getInstancesByNodeNames(hosts) + if err != nil { + return nil, err + } + + sourceRanges, err := service.GetLoadBalancerSourceRanges(annotations) + if err != nil { + return nil, err + } + + // Determine if this is tagged as an Internal ELB + internalELB := false + internalAnnotation := annotations[ServiceAnnotationLoadBalancerInternal] + if internalAnnotation != "" { + if internalAnnotation != "0.0.0.0/0" { + return nil, fmt.Errorf("annotation %q=%q detected, but the only value supported currently is 0.0.0.0/0", ServiceAnnotationLoadBalancerInternal, internalAnnotation) + } + if !service.IsAllowAll(sourceRanges) { + // TODO: Unify the two annotations + return nil, fmt.Errorf("source-range annotation cannot be combined with the internal-elb annotation") + } + internalELB = true + } + + // Find the subnets that the ELB will live in + subnetIDs, err := s.findELBSubnets(internalELB) + if err != nil { + glog.Error("Error listing subnets in VPC: ", err) + return nil, err + } + + // Bail out early if there are no subnets + if len(subnetIDs) == 0 { + return nil, fmt.Errorf("could not find any suitable subnets for creating the ELB") + } + + loadBalancerName := cloudprovider.GetLoadBalancerName(apiService) + serviceName := types.NamespacedName{Namespace: apiService.Namespace, Name: apiService.Name} + + // Create a security group for the load balancer + var securityGroupID string + { + sgName := "k8s-elb-" + loadBalancerName + sgDescription := fmt.Sprintf("Security group for Kubernetes ELB %s (%v)", loadBalancerName, serviceName) + securityGroupID, err = s.ensureSecurityGroup(sgName, sgDescription) + if err != nil { + glog.Error("Error creating load balancer security group: ", err) + return nil, err + } + + ec2SourceRanges := []*ec2.IpRange{} + for _, sourceRange := range sourceRanges.StringSlice() { + ec2SourceRanges = append(ec2SourceRanges, &ec2.IpRange{CidrIp: aws.String(sourceRange)}) + } + + permissions := NewIPPermissionSet() + for _, port := range apiService.Spec.Ports { + portInt64 := int64(port.Port) + protocol := strings.ToLower(string(port.Protocol)) + + permission := &ec2.IpPermission{} + permission.FromPort = &portInt64 + permission.ToPort = &portInt64 + permission.IpRanges = ec2SourceRanges + permission.IpProtocol = &protocol + + permissions.Insert(permission) + } + _, err = s.setSecurityGroupIngress(securityGroupID, permissions) + if err != nil { + return nil, err + } + } + securityGroupIDs := []string{securityGroupID} + + // Figure out what mappings we want on the load balancer + listeners := []*elb.Listener{} + for _, port := range apiService.Spec.Ports { + if port.NodePort == 0 { + glog.Errorf("Ignoring port without NodePort defined: %v", port) + continue + } + instancePort := int64(port.NodePort) + loadBalancerPort := int64(port.Port) + protocol := strings.ToLower(string(port.Protocol)) + + listener := &elb.Listener{} + listener.InstancePort = &instancePort + listener.LoadBalancerPort = &loadBalancerPort + listener.Protocol = &protocol + listener.InstanceProtocol = &protocol + + listeners = append(listeners, listener) + } + + // Build the load balancer itself + loadBalancer, err := s.ensureLoadBalancer(serviceName, loadBalancerName, listeners, subnetIDs, securityGroupIDs, internalELB) + if err != nil { + return nil, err + } + + err = s.ensureLoadBalancerHealthCheck(loadBalancer, listeners) + if err != nil { + return nil, err + } + + err = s.updateInstanceSecurityGroupsForLoadBalancer(loadBalancer, instances) + if err != nil { + glog.Warningf("Error opening ingress rules for the load balancer to the instances: %v", err) + return nil, err + } + + err = s.ensureLoadBalancerInstances(orEmpty(loadBalancer.LoadBalancerName), loadBalancer.Instances, instances) + if err != nil { + glog.Warningf("Error registering instances with the load balancer: %v", err) + return nil, err + } + + glog.V(1).Infof("Loadbalancer %s (%v) has DNS name %s", loadBalancerName, serviceName, orEmpty(loadBalancer.DNSName)) + + // TODO: Wait for creation? + + status := toStatus(loadBalancer) + return status, nil +} + +// GetLoadBalancer is an implementation of LoadBalancer.GetLoadBalancer +func (s *AWSCloud) GetLoadBalancer(service *api.Service) (*api.LoadBalancerStatus, bool, error) { + loadBalancerName := cloudprovider.GetLoadBalancerName(service) + lb, err := s.describeLoadBalancer(loadBalancerName) + if err != nil { + return nil, false, err + } + + if lb == nil { + return nil, false, nil + } + + status := toStatus(lb) + return status, true, nil +} + +func toStatus(lb *elb.LoadBalancerDescription) *api.LoadBalancerStatus { + status := &api.LoadBalancerStatus{} + + if !isNilOrEmpty(lb.DNSName) { + var ingress api.LoadBalancerIngress + ingress.Hostname = orEmpty(lb.DNSName) + status.Ingress = []api.LoadBalancerIngress{ingress} + } + + return status +} + +// Returns the first security group for an instance, or nil +// We only create instances with one security group, so we don't expect multiple security groups. +// However, if there are multiple security groups, we will choose the one tagged with our cluster filter. +// Otherwise we will return an error. +func findSecurityGroupForInstance(instance *ec2.Instance, taggedSecurityGroups map[string]*ec2.SecurityGroup) (*ec2.GroupIdentifier, error) { + instanceID := aws.StringValue(instance.InstanceId) + + var tagged []*ec2.GroupIdentifier + var untagged []*ec2.GroupIdentifier + for _, group := range instance.SecurityGroups { + groupID := aws.StringValue(group.GroupId) + if groupID == "" { + glog.Warningf("Ignoring security group without id for instance %q: %v", instanceID, group) + continue + } + _, isTagged := taggedSecurityGroups[groupID] + if isTagged { + tagged = append(tagged, group) + } else { + untagged = append(untagged, group) + } + } + + if len(tagged) > 0 { + // We create instances with one SG + // If users create multiple SGs, they must tag one of them as being k8s owned + if len(tagged) != 1 { + return nil, fmt.Errorf("Multiple tagged security groups found for instance %s; ensure only the k8s security group is tagged", instanceID) + } + return tagged[0], nil + } + + if len(untagged) > 0 { + // For back-compat, we will allow a single untagged SG + if len(untagged) != 1 { + return nil, fmt.Errorf("Multiple untagged security groups found for instance %s; ensure the k8s security group is tagged", instanceID) + } + return untagged[0], nil + } + + glog.Warningf("No security group found for instance %q", instanceID) + return nil, nil +} + +// Return all the security groups that are tagged as being part of our cluster +func (s *AWSCloud) getTaggedSecurityGroups() (map[string]*ec2.SecurityGroup, error) { + request := &ec2.DescribeSecurityGroupsInput{} + request.Filters = s.addFilters(nil) + groups, err := s.ec2.DescribeSecurityGroups(request) + if err != nil { + return nil, fmt.Errorf("error querying security groups: %v", err) + } + + m := make(map[string]*ec2.SecurityGroup) + for _, group := range groups { + id := aws.StringValue(group.GroupId) + if id == "" { + glog.Warningf("Ignoring group without id: %v", group) + continue + } + m[id] = group + } + return m, nil +} + +// Open security group ingress rules on the instances so that the load balancer can talk to them +// Will also remove any security groups ingress rules for the load balancer that are _not_ needed for allInstances +func (s *AWSCloud) updateInstanceSecurityGroupsForLoadBalancer(lb *elb.LoadBalancerDescription, allInstances []*ec2.Instance) error { + if s.cfg.Global.DisableSecurityGroupIngress { + return nil + } + + // Determine the load balancer security group id + loadBalancerSecurityGroupId := "" + for _, securityGroup := range lb.SecurityGroups { + if isNilOrEmpty(securityGroup) { + continue + } + if loadBalancerSecurityGroupId != "" { + // We create LBs with one SG + glog.Warningf("Multiple security groups for load balancer: %q", orEmpty(lb.LoadBalancerName)) + } + loadBalancerSecurityGroupId = *securityGroup + } + if loadBalancerSecurityGroupId == "" { + return fmt.Errorf("Could not determine security group for load balancer: %s", orEmpty(lb.LoadBalancerName)) + } + + // Get the actual list of groups that allow ingress from the load-balancer + describeRequest := &ec2.DescribeSecurityGroupsInput{} + filters := []*ec2.Filter{} + filters = append(filters, newEc2Filter("ip-permission.group-id", loadBalancerSecurityGroupId)) + describeRequest.Filters = s.addFilters(filters) + actualGroups, err := s.ec2.DescribeSecurityGroups(describeRequest) + if err != nil { + return fmt.Errorf("error querying security groups for ELB: %v", err) + } + + taggedSecurityGroups, err := s.getTaggedSecurityGroups() + if err != nil { + return fmt.Errorf("error querying for tagged security groups: %v", err) + } + + // Open the firewall from the load balancer to the instance + // We don't actually have a trivial way to know in advance which security group the instance is in + // (it is probably the minion security group, but we don't easily have that). + // However, we _do_ have the list of security groups on the instance records. + + // Map containing the changes we want to make; true to add, false to remove + instanceSecurityGroupIds := map[string]bool{} + + // Scan instances for groups we want open + for _, instance := range allInstances { + securityGroup, err := findSecurityGroupForInstance(instance, taggedSecurityGroups) + if err != nil { + return err + } + + if securityGroup == nil { + glog.Warning("Ignoring instance without security group: ", orEmpty(instance.InstanceId)) + continue + } + id := aws.StringValue(securityGroup.GroupId) + if id == "" { + glog.Warningf("found security group without id: %v", securityGroup) + continue + } + + instanceSecurityGroupIds[id] = true + } + + // Compare to actual groups + for _, actualGroup := range actualGroups { + actualGroupID := aws.StringValue(actualGroup.GroupId) + if actualGroupID == "" { + glog.Warning("Ignoring group without ID: ", actualGroup) + continue + } + + adding, found := instanceSecurityGroupIds[actualGroupID] + if found && adding { + // We don't need to make a change; the permission is already in place + delete(instanceSecurityGroupIds, actualGroupID) + } else { + // This group is not needed by allInstances; delete it + instanceSecurityGroupIds[actualGroupID] = false + } + } + + for instanceSecurityGroupId, add := range instanceSecurityGroupIds { + if add { + glog.V(2).Infof("Adding rule for traffic from the load balancer (%s) to instances (%s)", loadBalancerSecurityGroupId, instanceSecurityGroupId) + } else { + glog.V(2).Infof("Removing rule for traffic from the load balancer (%s) to instance (%s)", loadBalancerSecurityGroupId, instanceSecurityGroupId) + } + sourceGroupId := &ec2.UserIdGroupPair{} + sourceGroupId.GroupId = &loadBalancerSecurityGroupId + + allProtocols := "-1" + + permission := &ec2.IpPermission{} + permission.IpProtocol = &allProtocols + permission.UserIdGroupPairs = []*ec2.UserIdGroupPair{sourceGroupId} + + permissions := []*ec2.IpPermission{permission} + + if add { + changed, err := s.addSecurityGroupIngress(instanceSecurityGroupId, permissions) + if err != nil { + return err + } + if !changed { + glog.Warning("Allowing ingress was not needed; concurrent change? groupId=", instanceSecurityGroupId) + } + } else { + changed, err := s.removeSecurityGroupIngress(instanceSecurityGroupId, permissions) + if err != nil { + return err + } + if !changed { + glog.Warning("Revoking ingress was not needed; concurrent change? groupId=", instanceSecurityGroupId) + } + } + } + + return nil +} + +// EnsureLoadBalancerDeleted implements LoadBalancer.EnsureLoadBalancerDeleted. +func (s *AWSCloud) EnsureLoadBalancerDeleted(service *api.Service) error { + loadBalancerName := cloudprovider.GetLoadBalancerName(service) + lb, err := s.describeLoadBalancer(loadBalancerName) + if err != nil { + return err + } + + if lb == nil { + glog.Info("Load balancer already deleted: ", loadBalancerName) + return nil + } + + { + // De-authorize the load balancer security group from the instances security group + err = s.updateInstanceSecurityGroupsForLoadBalancer(lb, nil) + if err != nil { + glog.Error("Error deregistering load balancer from instance security groups: ", err) + return err + } + } + + { + // Delete the load balancer itself + request := &elb.DeleteLoadBalancerInput{} + request.LoadBalancerName = lb.LoadBalancerName + + _, err = s.elb.DeleteLoadBalancer(request) + if err != nil { + // TODO: Check if error was because load balancer was concurrently deleted + glog.Error("Error deleting load balancer: ", err) + return err + } + } + + { + // Delete the security group(s) for the load balancer + // Note that this is annoying: the load balancer disappears from the API immediately, but it is still + // deleting in the background. We get a DependencyViolation until the load balancer has deleted itself + + // Collect the security groups to delete + securityGroupIDs := map[string]struct{}{} + for _, securityGroupID := range lb.SecurityGroups { + if isNilOrEmpty(securityGroupID) { + glog.Warning("Ignoring empty security group in ", service.Name) + continue + } + securityGroupIDs[*securityGroupID] = struct{}{} + } + + // Loop through and try to delete them + timeoutAt := time.Now().Add(time.Second * 600) + for { + for securityGroupID := range securityGroupIDs { + request := &ec2.DeleteSecurityGroupInput{} + request.GroupId = &securityGroupID + _, err := s.ec2.DeleteSecurityGroup(request) + if err == nil { + delete(securityGroupIDs, securityGroupID) + } else { + ignore := false + if awsError, ok := err.(awserr.Error); ok { + if awsError.Code() == "DependencyViolation" { + glog.V(2).Infof("Ignoring DependencyViolation while deleting load-balancer security group (%s), assuming because LB is in process of deleting", securityGroupID) + ignore = true + } + } + if !ignore { + return fmt.Errorf("error while deleting load balancer security group (%s): %v", securityGroupID, err) + } + } + } + + if len(securityGroupIDs) == 0 { + glog.V(2).Info("Deleted all security groups for load balancer: ", service.Name) + break + } + + if time.Now().After(timeoutAt) { + ids := []string{} + for id := range securityGroupIDs { + ids = append(ids, id) + } + + return fmt.Errorf("timed out deleting ELB: %s. Could not delete security groups %v", service.Name, strings.Join(ids, ",")) + } + + glog.V(2).Info("Waiting for load-balancer to delete so we can delete security groups: ", service.Name) + + time.Sleep(10 * time.Second) + } + } + + return nil +} + +// UpdateLoadBalancer implements LoadBalancer.UpdateLoadBalancer +func (s *AWSCloud) UpdateLoadBalancer(service *api.Service, hosts []string) error { + instances, err := s.getInstancesByNodeNames(hosts) + if err != nil { + return err + } + + loadBalancerName := cloudprovider.GetLoadBalancerName(service) + lb, err := s.describeLoadBalancer(loadBalancerName) + if err != nil { + return err + } + + if lb == nil { + return fmt.Errorf("Load balancer not found") + } + + err = s.ensureLoadBalancerInstances(orEmpty(lb.LoadBalancerName), lb.Instances, instances) + if err != nil { + return nil + } + + err = s.updateInstanceSecurityGroupsForLoadBalancer(lb, instances) + if err != nil { + return err + } + + return nil +} + +// Returns the instance with the specified ID +func (a *AWSCloud) getInstanceByID(instanceID string) (*ec2.Instance, error) { + instances, err := a.getInstancesByIDs([]*string{&instanceID}) + if err != nil { + return nil, err + } + + if len(instances) == 0 { + return nil, fmt.Errorf("no instances found for instance: %s", instanceID) + } + if len(instances) > 1 { + return nil, fmt.Errorf("multiple instances found for instance: %s", instanceID) + } + + return instances[instanceID], nil +} + +func (a *AWSCloud) getInstancesByIDs(instanceIDs []*string) (map[string]*ec2.Instance, error) { + instancesByID := make(map[string]*ec2.Instance) + if len(instanceIDs) == 0 { + return instancesByID, nil + } + + request := &ec2.DescribeInstancesInput{ + InstanceIds: instanceIDs, + } + + instances, err := a.ec2.DescribeInstances(request) + if err != nil { + return nil, err + } + + for _, instance := range instances { + instanceID := orEmpty(instance.InstanceId) + if instanceID == "" { + continue + } + + instancesByID[instanceID] = instance + } + + return instancesByID, nil +} + +// Fetches instances by node names; returns an error if any cannot be found. +// This is implemented with a multi value filter on the node names, fetching the desired instances with a single query. +func (a *AWSCloud) getInstancesByNodeNames(nodeNames []string) ([]*ec2.Instance, error) { + names := aws.StringSlice(nodeNames) + + nodeNameFilter := &ec2.Filter{ + Name: aws.String("private-dns-name"), + Values: names, + } + + filters := []*ec2.Filter{ + nodeNameFilter, + newEc2Filter("instance-state-name", "running"), + } + + filters = a.addFilters(filters) + request := &ec2.DescribeInstancesInput{ + Filters: filters, + } + + instances, err := a.ec2.DescribeInstances(request) + if err != nil { + glog.V(2).Infof("Failed to describe instances %v", nodeNames) + return nil, err + } + + if len(instances) == 0 { + glog.V(3).Infof("Failed to find any instances %v", nodeNames) + return nil, nil + } + + return instances, nil +} + +// Returns the instance with the specified node name +// Returns nil if it does not exist +func (a *AWSCloud) findInstanceByNodeName(nodeName string) (*ec2.Instance, error) { + filters := []*ec2.Filter{ + newEc2Filter("private-dns-name", nodeName), + newEc2Filter("instance-state-name", "running"), + } + filters = a.addFilters(filters) + request := &ec2.DescribeInstancesInput{ + Filters: filters, + } + + instances, err := a.ec2.DescribeInstances(request) + if err != nil { + return nil, err + } + if len(instances) == 0 { + return nil, nil + } + if len(instances) > 1 { + return nil, fmt.Errorf("multiple instances found for name: %s", nodeName) + } + return instances[0], nil +} + +// Returns the instance with the specified node name +// Like findInstanceByNodeName, but returns error if node not found +func (a *AWSCloud) getInstanceByNodeName(nodeName string) (*ec2.Instance, error) { + instance, err := a.findInstanceByNodeName(nodeName) + if err == nil && instance == nil { + return nil, fmt.Errorf("no instances found for name: %s", nodeName) + } + return instance, err +} + +// Add additional filters, to match on our tags +// This lets us run multiple k8s clusters in a single EC2 AZ +func (s *AWSCloud) addFilters(filters []*ec2.Filter) []*ec2.Filter { + for k, v := range s.filterTags { + filters = append(filters, newEc2Filter("tag:"+k, v)) + } + if len(filters) == 0 { + // We can't pass a zero-length Filters to AWS (it's an error) + // So if we end up with no filters; just return nil + return nil + } + + return filters +} + +// Returns the cluster name or an empty string +func (s *AWSCloud) getClusterName() string { + return s.filterTags[TagNameKubernetesCluster] +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_instancegroups.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_instancegroups.go new file mode 100644 index 000000000..563c90de1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_instancegroups.go @@ -0,0 +1,90 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 aws + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/golang/glog" +) + +// AWSCloud implements InstanceGroups +var _ InstanceGroups = &AWSCloud{} + +// ResizeInstanceGroup sets the size of the specificed instancegroup Exported +// so it can be used by the e2e tests, which don't want to instantiate a full +// cloudprovider. +func ResizeInstanceGroup(asg ASG, instanceGroupName string, size int) error { + request := &autoscaling.UpdateAutoScalingGroupInput{ + AutoScalingGroupName: aws.String(instanceGroupName), + MinSize: aws.Int64(int64(size)), + MaxSize: aws.Int64(int64(size)), + } + if _, err := asg.UpdateAutoScalingGroup(request); err != nil { + return fmt.Errorf("error resizing AWS autoscaling group: %v", err) + } + return nil +} + +// Implement InstanceGroups.ResizeInstanceGroup +// Set the size to the fixed size +func (a *AWSCloud) ResizeInstanceGroup(instanceGroupName string, size int) error { + return ResizeInstanceGroup(a.asg, instanceGroupName, size) +} + +// DescribeInstanceGroup gets info about the specified instancegroup +// Exported so it can be used by the e2e tests, +// which don't want to instantiate a full cloudprovider. +func DescribeInstanceGroup(asg ASG, instanceGroupName string) (InstanceGroupInfo, error) { + request := &autoscaling.DescribeAutoScalingGroupsInput{ + AutoScalingGroupNames: []*string{aws.String(instanceGroupName)}, + } + response, err := asg.DescribeAutoScalingGroups(request) + if err != nil { + return nil, fmt.Errorf("error listing AWS autoscaling group (%s): %v", instanceGroupName, err) + } + + if len(response.AutoScalingGroups) == 0 { + return nil, nil + } + if len(response.AutoScalingGroups) > 1 { + glog.Warning("AWS returned multiple autoscaling groups with name ", instanceGroupName) + } + group := response.AutoScalingGroups[0] + return &awsInstanceGroup{group: group}, nil +} + +// Implement InstanceGroups.DescribeInstanceGroup +// Queries the cloud provider for information about the specified instance group +func (a *AWSCloud) DescribeInstanceGroup(instanceGroupName string) (InstanceGroupInfo, error) { + return DescribeInstanceGroup(a.asg, instanceGroupName) +} + +// awsInstanceGroup implements InstanceGroupInfo +var _ InstanceGroupInfo = &awsInstanceGroup{} + +type awsInstanceGroup struct { + group *autoscaling.Group +} + +// Implement InstanceGroupInfo.CurrentSize +// The number of instances currently running under control of this group +func (g *awsInstanceGroup) CurrentSize() (int, error) { + return len(g.group.Instances), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_loadbalancer.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_loadbalancer.go new file mode 100644 index 000000000..fe48045cf --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_loadbalancer.go @@ -0,0 +1,310 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 aws + +import ( + "fmt" + "strconv" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/elb" + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/sets" +) + +func (s *AWSCloud) ensureLoadBalancer(namespacedName types.NamespacedName, loadBalancerName string, listeners []*elb.Listener, subnetIDs []string, securityGroupIDs []string, internalELB bool) (*elb.LoadBalancerDescription, error) { + loadBalancer, err := s.describeLoadBalancer(loadBalancerName) + if err != nil { + return nil, err + } + + dirty := false + + if loadBalancer == nil { + createRequest := &elb.CreateLoadBalancerInput{} + createRequest.LoadBalancerName = aws.String(loadBalancerName) + + createRequest.Listeners = listeners + + if internalELB { + createRequest.Scheme = aws.String("internal") + } + + // We are supposed to specify one subnet per AZ. + // TODO: What happens if we have more than one subnet per AZ? + createRequest.Subnets = stringPointerArray(subnetIDs) + + createRequest.SecurityGroups = stringPointerArray(securityGroupIDs) + + createRequest.Tags = []*elb.Tag{ + {Key: aws.String(TagNameKubernetesCluster), Value: aws.String(s.getClusterName())}, + {Key: aws.String(TagNameKubernetesService), Value: aws.String(namespacedName.String())}, + } + + glog.Infof("Creating load balancer for %v with name: ", namespacedName, loadBalancerName) + _, err := s.elb.CreateLoadBalancer(createRequest) + if err != nil { + return nil, err + } + dirty = true + } else { + // TODO: Sync internal vs non-internal + + { + // Sync subnets + expected := sets.NewString(subnetIDs...) + actual := stringSetFromPointers(loadBalancer.Subnets) + + additions := expected.Difference(actual) + removals := actual.Difference(expected) + + if removals.Len() != 0 { + request := &elb.DetachLoadBalancerFromSubnetsInput{} + request.LoadBalancerName = aws.String(loadBalancerName) + request.Subnets = stringSetToPointers(removals) + glog.V(2).Info("Detaching load balancer from removed subnets") + _, err := s.elb.DetachLoadBalancerFromSubnets(request) + if err != nil { + return nil, fmt.Errorf("error detaching AWS loadbalancer from subnets: %v", err) + } + dirty = true + } + + if additions.Len() != 0 { + request := &elb.AttachLoadBalancerToSubnetsInput{} + request.LoadBalancerName = aws.String(loadBalancerName) + request.Subnets = stringSetToPointers(additions) + glog.V(2).Info("Attaching load balancer to added subnets") + _, err := s.elb.AttachLoadBalancerToSubnets(request) + if err != nil { + return nil, fmt.Errorf("error attaching AWS loadbalancer to subnets: %v", err) + } + dirty = true + } + } + + { + // Sync security groups + expected := sets.NewString(securityGroupIDs...) + actual := stringSetFromPointers(loadBalancer.SecurityGroups) + + if !expected.Equal(actual) { + // This call just replaces the security groups, unlike e.g. subnets (!) + request := &elb.ApplySecurityGroupsToLoadBalancerInput{} + request.LoadBalancerName = aws.String(loadBalancerName) + request.SecurityGroups = stringPointerArray(securityGroupIDs) + glog.V(2).Info("Applying updated security groups to load balancer") + _, err := s.elb.ApplySecurityGroupsToLoadBalancer(request) + if err != nil { + return nil, fmt.Errorf("error applying AWS loadbalancer security groups: %v", err) + } + dirty = true + } + } + + { + // Sync listeners + listenerDescriptions := loadBalancer.ListenerDescriptions + + foundSet := make(map[int]bool) + removals := []*int64{} + for _, listenerDescription := range listenerDescriptions { + actual := listenerDescription.Listener + if actual == nil { + glog.Warning("Ignoring empty listener in AWS loadbalancer: ", loadBalancerName) + continue + } + + found := -1 + for i, expected := range listeners { + if orEmpty(actual.Protocol) != orEmpty(expected.Protocol) { + continue + } + if orEmpty(actual.InstanceProtocol) != orEmpty(expected.InstanceProtocol) { + continue + } + if orZero(actual.InstancePort) != orZero(expected.InstancePort) { + continue + } + if orZero(actual.LoadBalancerPort) != orZero(expected.LoadBalancerPort) { + continue + } + if orEmpty(actual.SSLCertificateId) != orEmpty(expected.SSLCertificateId) { + continue + } + found = i + } + if found != -1 { + foundSet[found] = true + } else { + removals = append(removals, actual.LoadBalancerPort) + } + } + + additions := []*elb.Listener{} + for i := range listeners { + if foundSet[i] { + continue + } + additions = append(additions, listeners[i]) + } + + if len(removals) != 0 { + request := &elb.DeleteLoadBalancerListenersInput{} + request.LoadBalancerName = aws.String(loadBalancerName) + request.LoadBalancerPorts = removals + glog.V(2).Info("Deleting removed load balancer listeners") + _, err := s.elb.DeleteLoadBalancerListeners(request) + if err != nil { + return nil, fmt.Errorf("error deleting AWS loadbalancer listeners: %v", err) + } + dirty = true + } + + if len(additions) != 0 { + request := &elb.CreateLoadBalancerListenersInput{} + request.LoadBalancerName = aws.String(loadBalancerName) + request.Listeners = additions + glog.V(2).Info("Creating added load balancer listeners") + _, err := s.elb.CreateLoadBalancerListeners(request) + if err != nil { + return nil, fmt.Errorf("error creating AWS loadbalancer listeners: %v", err) + } + dirty = true + } + } + } + + if dirty { + loadBalancer, err = s.describeLoadBalancer(loadBalancerName) + if err != nil { + glog.Warning("Unable to retrieve load balancer after creation/update") + return nil, err + } + } + + return loadBalancer, nil +} + +// Makes sure that the health check for an ELB matches the configured listeners +func (s *AWSCloud) ensureLoadBalancerHealthCheck(loadBalancer *elb.LoadBalancerDescription, listeners []*elb.Listener) error { + actual := loadBalancer.HealthCheck + + // Default AWS settings + expectedHealthyThreshold := int64(2) + expectedUnhealthyThreshold := int64(6) + expectedTimeout := int64(5) + expectedInterval := int64(10) + + // We only configure a TCP health-check on the first port + expectedTarget := "" + for _, listener := range listeners { + if listener.InstancePort == nil { + continue + } + expectedTarget = "TCP:" + strconv.FormatInt(*listener.InstancePort, 10) + break + } + + if expectedTarget == "" { + return fmt.Errorf("unable to determine health check port (no valid listeners)") + } + + if expectedTarget == orEmpty(actual.Target) && + expectedHealthyThreshold == orZero(actual.HealthyThreshold) && + expectedUnhealthyThreshold == orZero(actual.UnhealthyThreshold) && + expectedTimeout == orZero(actual.Timeout) && + expectedInterval == orZero(actual.Interval) { + return nil + } + + glog.V(2).Info("Updating load-balancer health-check") + + healthCheck := &elb.HealthCheck{} + healthCheck.HealthyThreshold = &expectedHealthyThreshold + healthCheck.UnhealthyThreshold = &expectedUnhealthyThreshold + healthCheck.Timeout = &expectedTimeout + healthCheck.Interval = &expectedInterval + healthCheck.Target = &expectedTarget + + request := &elb.ConfigureHealthCheckInput{} + request.HealthCheck = healthCheck + request.LoadBalancerName = loadBalancer.LoadBalancerName + + _, err := s.elb.ConfigureHealthCheck(request) + if err != nil { + return fmt.Errorf("error configuring load-balancer health-check: %v", err) + } + + return nil +} + +// Makes sure that exactly the specified hosts are registered as instances with the load balancer +func (s *AWSCloud) ensureLoadBalancerInstances(loadBalancerName string, lbInstances []*elb.Instance, instances []*ec2.Instance) error { + expected := sets.NewString() + for _, instance := range instances { + expected.Insert(orEmpty(instance.InstanceId)) + } + + actual := sets.NewString() + for _, lbInstance := range lbInstances { + actual.Insert(orEmpty(lbInstance.InstanceId)) + } + + additions := expected.Difference(actual) + removals := actual.Difference(expected) + + addInstances := []*elb.Instance{} + for _, instanceId := range additions.List() { + addInstance := &elb.Instance{} + addInstance.InstanceId = aws.String(instanceId) + addInstances = append(addInstances, addInstance) + } + + removeInstances := []*elb.Instance{} + for _, instanceId := range removals.List() { + removeInstance := &elb.Instance{} + removeInstance.InstanceId = aws.String(instanceId) + removeInstances = append(removeInstances, removeInstance) + } + + if len(addInstances) > 0 { + registerRequest := &elb.RegisterInstancesWithLoadBalancerInput{} + registerRequest.Instances = addInstances + registerRequest.LoadBalancerName = aws.String(loadBalancerName) + _, err := s.elb.RegisterInstancesWithLoadBalancer(registerRequest) + if err != nil { + return err + } + glog.V(1).Infof("Instances added to load-balancer %s", loadBalancerName) + } + + if len(removeInstances) > 0 { + deregisterRequest := &elb.DeregisterInstancesFromLoadBalancerInput{} + deregisterRequest.Instances = removeInstances + deregisterRequest.LoadBalancerName = aws.String(loadBalancerName) + _, err := s.elb.DeregisterInstancesFromLoadBalancer(deregisterRequest) + if err != nil { + return err + } + glog.V(1).Infof("Instances removed from load-balancer %s", loadBalancerName) + } + + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_routes.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_routes.go new file mode 100644 index 000000000..a469e5f70 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_routes.go @@ -0,0 +1,188 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 aws + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/cloudprovider" +) + +func (s *AWSCloud) findRouteTable(clusterName string) (*ec2.RouteTable, error) { + // This should be unnecessary (we already filter on TagNameKubernetesCluster, + // and something is broken if cluster name doesn't match, but anyway... + // TODO: All clouds should be cluster-aware by default + filters := []*ec2.Filter{newEc2Filter("tag:"+TagNameKubernetesCluster, clusterName)} + request := &ec2.DescribeRouteTablesInput{Filters: s.addFilters(filters)} + + tables, err := s.ec2.DescribeRouteTables(request) + if err != nil { + return nil, err + } + + if len(tables) == 0 { + return nil, fmt.Errorf("unable to find route table for AWS cluster: %s", clusterName) + } + + if len(tables) != 1 { + return nil, fmt.Errorf("found multiple matching AWS route tables for AWS cluster: %s", clusterName) + } + return tables[0], nil +} + +// ListRoutes implements Routes.ListRoutes +// List all routes that match the filter +func (s *AWSCloud) ListRoutes(clusterName string) ([]*cloudprovider.Route, error) { + table, err := s.findRouteTable(clusterName) + if err != nil { + return nil, err + } + + var routes []*cloudprovider.Route + var instanceIDs []*string + + for _, r := range table.Routes { + instanceID := orEmpty(r.InstanceId) + + if instanceID == "" { + continue + } + + instanceIDs = append(instanceIDs, &instanceID) + } + + instances, err := s.getInstancesByIDs(instanceIDs) + if err != nil { + return nil, err + } + + for _, r := range table.Routes { + instanceID := orEmpty(r.InstanceId) + destinationCIDR := orEmpty(r.DestinationCidrBlock) + + if instanceID == "" || destinationCIDR == "" { + continue + } + + instance, found := instances[instanceID] + if !found { + glog.Warningf("unable to find instance ID %s in the list of instances being routed to", instanceID) + continue + } + instanceName := orEmpty(instance.PrivateDnsName) + routeName := clusterName + "-" + destinationCIDR + routes = append(routes, &cloudprovider.Route{Name: routeName, TargetInstance: instanceName, DestinationCIDR: destinationCIDR}) + } + + return routes, nil +} + +// Sets the instance attribute "source-dest-check" to the specified value +func (s *AWSCloud) configureInstanceSourceDestCheck(instanceID string, sourceDestCheck bool) error { + request := &ec2.ModifyInstanceAttributeInput{} + request.InstanceId = aws.String(instanceID) + request.SourceDestCheck = &ec2.AttributeBooleanValue{Value: aws.Bool(sourceDestCheck)} + + _, err := s.ec2.ModifyInstanceAttribute(request) + if err != nil { + return fmt.Errorf("error configuring source-dest-check on instance %s: %v", instanceID, err) + } + return nil +} + +// CreateRoute implements Routes.CreateRoute +// Create the described route +func (s *AWSCloud) CreateRoute(clusterName string, nameHint string, route *cloudprovider.Route) error { + instance, err := s.getInstanceByNodeName(route.TargetInstance) + if err != nil { + return err + } + + // In addition to configuring the route itself, we also need to configure the instance to accept that traffic + // On AWS, this requires turning source-dest checks off + err = s.configureInstanceSourceDestCheck(orEmpty(instance.InstanceId), false) + if err != nil { + return err + } + + table, err := s.findRouteTable(clusterName) + if err != nil { + return err + } + + var deleteRoute *ec2.Route + for _, r := range table.Routes { + destinationCIDR := aws.StringValue(r.DestinationCidrBlock) + + if destinationCIDR != route.DestinationCIDR { + continue + } + + if aws.StringValue(r.State) == ec2.RouteStateBlackhole { + deleteRoute = r + } + } + + if deleteRoute != nil { + glog.Infof("deleting blackholed route: %s", aws.StringValue(deleteRoute.DestinationCidrBlock)) + + request := &ec2.DeleteRouteInput{} + request.DestinationCidrBlock = deleteRoute.DestinationCidrBlock + request.RouteTableId = table.RouteTableId + + _, err = s.ec2.DeleteRoute(request) + if err != nil { + return fmt.Errorf("error deleting blackholed AWS route (%s): %v", aws.StringValue(deleteRoute.DestinationCidrBlock), err) + } + } + + request := &ec2.CreateRouteInput{} + // TODO: use ClientToken for idempotency? + request.DestinationCidrBlock = aws.String(route.DestinationCIDR) + request.InstanceId = instance.InstanceId + request.RouteTableId = table.RouteTableId + + _, err = s.ec2.CreateRoute(request) + if err != nil { + return fmt.Errorf("error creating AWS route (%s): %v", route.DestinationCIDR, err) + } + + return nil +} + +// DeleteRoute implements Routes.DeleteRoute +// Delete the specified route +func (s *AWSCloud) DeleteRoute(clusterName string, route *cloudprovider.Route) error { + table, err := s.findRouteTable(clusterName) + if err != nil { + return err + } + + request := &ec2.DeleteRouteInput{} + request.DestinationCidrBlock = aws.String(route.DestinationCIDR) + request.RouteTableId = table.RouteTableId + + _, err = s.ec2.DeleteRoute(request) + if err != nil { + return fmt.Errorf("error deleting AWS route (%s): %v", route.DestinationCIDR, err) + } + + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_test.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_test.go new file mode 100644 index 000000000..338886530 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_test.go @@ -0,0 +1,1201 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 aws + +import ( + "io" + "reflect" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/elb" + + "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +const TestClusterId = "clusterid.test" + +func TestReadAWSCloudConfig(t *testing.T) { + tests := []struct { + name string + + reader io.Reader + aws AWSServices + + expectError bool + zone string + }{ + { + "No config reader", + nil, nil, + true, "", + }, + { + "Empty config, no metadata", + strings.NewReader(""), nil, + true, "", + }, + { + "No zone in config, no metadata", + strings.NewReader("[global]\n"), nil, + true, "", + }, + { + "Zone in config, no metadata", + strings.NewReader("[global]\nzone = eu-west-1a"), nil, + false, "eu-west-1a", + }, + { + "No zone in config, metadata does not have zone", + strings.NewReader("[global]\n"), NewFakeAWSServices().withAz(""), + true, "", + }, + { + "No zone in config, metadata has zone", + strings.NewReader("[global]\n"), NewFakeAWSServices(), + false, "us-east-1a", + }, + { + "Zone in config should take precedence over metadata", + strings.NewReader("[global]\nzone = eu-west-1a"), NewFakeAWSServices(), + false, "eu-west-1a", + }, + } + + for _, test := range tests { + t.Logf("Running test case %s", test.name) + var metadata EC2Metadata + if test.aws != nil { + metadata, _ = test.aws.Metadata() + } + cfg, err := readAWSCloudConfig(test.reader, metadata) + if test.expectError { + if err == nil { + t.Errorf("Should error for case %s (cfg=%v)", test.name, cfg) + } + } else { + if err != nil { + t.Errorf("Should succeed for case: %s", test.name) + } + if cfg.Global.Zone != test.zone { + t.Errorf("Incorrect zone value (%s vs %s) for case: %s", + cfg.Global.Zone, test.zone, test.name) + } + } + } +} + +type FakeAWSServices struct { + region string + instances []*ec2.Instance + selfInstance *ec2.Instance + networkInterfacesMacs []string + networkInterfacesVpcIDs []string + + ec2 *FakeEC2 + elb *FakeELB + asg *FakeASG + metadata *FakeMetadata +} + +func NewFakeAWSServices() *FakeAWSServices { + s := &FakeAWSServices{} + s.region = "us-east-1" + s.ec2 = &FakeEC2{aws: s} + s.elb = &FakeELB{aws: s} + s.asg = &FakeASG{aws: s} + s.metadata = &FakeMetadata{aws: s} + + s.networkInterfacesMacs = []string{"aa:bb:cc:dd:ee:00", "aa:bb:cc:dd:ee:01"} + s.networkInterfacesVpcIDs = []string{"vpc-mac0", "vpc-mac1"} + + selfInstance := &ec2.Instance{} + selfInstance.InstanceId = aws.String("i-self") + selfInstance.Placement = &ec2.Placement{ + AvailabilityZone: aws.String("us-east-1a"), + } + selfInstance.PrivateDnsName = aws.String("ip-172-20-0-100.ec2.internal") + selfInstance.PrivateIpAddress = aws.String("192.168.0.1") + selfInstance.PublicIpAddress = aws.String("1.2.3.4") + s.selfInstance = selfInstance + s.instances = []*ec2.Instance{selfInstance} + + var tag ec2.Tag + tag.Key = aws.String(TagNameKubernetesCluster) + tag.Value = aws.String(TestClusterId) + selfInstance.Tags = []*ec2.Tag{&tag} + + return s +} + +func (s *FakeAWSServices) withAz(az string) *FakeAWSServices { + if s.selfInstance.Placement == nil { + s.selfInstance.Placement = &ec2.Placement{} + } + s.selfInstance.Placement.AvailabilityZone = aws.String(az) + return s +} + +func (s *FakeAWSServices) Compute(region string) (EC2, error) { + return s.ec2, nil +} + +func (s *FakeAWSServices) LoadBalancing(region string) (ELB, error) { + return s.elb, nil +} + +func (s *FakeAWSServices) Autoscaling(region string) (ASG, error) { + return s.asg, nil +} + +func (s *FakeAWSServices) Metadata() (EC2Metadata, error) { + return s.metadata, nil +} + +func TestFilterTags(t *testing.T) { + awsServices := NewFakeAWSServices() + c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) + if err != nil { + t.Errorf("Error building aws cloud: %v", err) + return + } + + if len(c.filterTags) != 1 { + t.Errorf("unexpected filter tags: %v", c.filterTags) + return + } + + if c.filterTags[TagNameKubernetesCluster] != TestClusterId { + t.Errorf("unexpected filter tags: %v", c.filterTags) + } +} + +func TestNewAWSCloud(t *testing.T) { + tests := []struct { + name string + + reader io.Reader + awsServices AWSServices + + expectError bool + region string + }{ + { + "No config reader", + nil, NewFakeAWSServices().withAz(""), + true, "", + }, + { + "Config specified invalid zone", + strings.NewReader("[global]\nzone = blahonga"), NewFakeAWSServices(), + true, "", + }, + { + "Config specifies valid zone", + strings.NewReader("[global]\nzone = eu-west-1a"), NewFakeAWSServices(), + false, "eu-west-1", + }, + { + "Gets zone from metadata when not in config", + strings.NewReader("[global]\n"), + NewFakeAWSServices(), + false, "us-east-1", + }, + { + "No zone in config or metadata", + strings.NewReader("[global]\n"), + NewFakeAWSServices().withAz(""), + true, "", + }, + } + + for _, test := range tests { + t.Logf("Running test case %s", test.name) + c, err := newAWSCloud(test.reader, test.awsServices) + if test.expectError { + if err == nil { + t.Errorf("Should error for case %s", test.name) + } + } else { + if err != nil { + t.Errorf("Should succeed for case: %s, got %v", test.name, err) + } else if c.region != test.region { + t.Errorf("Incorrect region value (%s vs %s) for case: %s", + c.region, test.region, test.name) + } + } + } +} + +type FakeEC2 struct { + aws *FakeAWSServices + Subnets []*ec2.Subnet + DescribeSubnetsInput *ec2.DescribeSubnetsInput + RouteTables []*ec2.RouteTable + DescribeRouteTablesInput *ec2.DescribeRouteTablesInput + mock.Mock +} + +func contains(haystack []*string, needle string) bool { + for _, s := range haystack { + // (deliberately panic if s == nil) + if needle == *s { + return true + } + } + return false +} + +func instanceMatchesFilter(instance *ec2.Instance, filter *ec2.Filter) bool { + name := *filter.Name + if name == "private-dns-name" { + if instance.PrivateDnsName == nil { + return false + } + return contains(filter.Values, *instance.PrivateDnsName) + } + + if name == "instance-state-name" { + return contains(filter.Values, *instance.State.Name) + } + + if strings.HasPrefix(name, "tag:") { + tagName := name[4:] + for _, instanceTag := range instance.Tags { + if aws.StringValue(instanceTag.Key) == tagName && contains(filter.Values, aws.StringValue(instanceTag.Value)) { + return true + } + } + } + panic("Unknown filter name: " + name) +} + +func (self *FakeEC2) DescribeInstances(request *ec2.DescribeInstancesInput) ([]*ec2.Instance, error) { + matches := []*ec2.Instance{} + for _, instance := range self.aws.instances { + if request.InstanceIds != nil { + if instance.InstanceId == nil { + glog.Warning("Instance with no instance id: ", instance) + continue + } + + found := false + for _, instanceID := range request.InstanceIds { + if *instanceID == *instance.InstanceId { + found = true + break + } + } + if !found { + continue + } + } + if request.Filters != nil { + allMatch := true + for _, filter := range request.Filters { + if !instanceMatchesFilter(instance, filter) { + allMatch = false + break + } + } + if !allMatch { + continue + } + } + matches = append(matches, instance) + } + + return matches, nil +} + +type FakeMetadata struct { + aws *FakeAWSServices +} + +func (self *FakeMetadata) GetMetadata(key string) (string, error) { + networkInterfacesPrefix := "network/interfaces/macs/" + i := self.aws.selfInstance + if key == "placement/availability-zone" { + az := "" + if i.Placement != nil { + az = aws.StringValue(i.Placement.AvailabilityZone) + } + return az, nil + } else if key == "instance-id" { + return aws.StringValue(i.InstanceId), nil + } else if key == "local-hostname" { + return aws.StringValue(i.PrivateDnsName), nil + } else if key == "local-ipv4" { + return aws.StringValue(i.PrivateIpAddress), nil + } else if key == "public-ipv4" { + return aws.StringValue(i.PublicIpAddress), nil + } else if strings.HasPrefix(key, networkInterfacesPrefix) { + if key == networkInterfacesPrefix { + return strings.Join(self.aws.networkInterfacesMacs, "/\n") + "/\n", nil + } else { + keySplit := strings.Split(key, "/") + macParam := keySplit[3] + if len(keySplit) == 5 && keySplit[4] == "vpc-id" { + for i, macElem := range self.aws.networkInterfacesMacs { + if macParam == macElem { + return self.aws.networkInterfacesVpcIDs[i], nil + } + } + } + return "", nil + } + } else { + return "", nil + } +} + +func (ec2 *FakeEC2) AttachVolume(request *ec2.AttachVolumeInput) (resp *ec2.VolumeAttachment, err error) { + panic("Not implemented") +} + +func (ec2 *FakeEC2) DetachVolume(request *ec2.DetachVolumeInput) (resp *ec2.VolumeAttachment, err error) { + panic("Not implemented") +} + +func (e *FakeEC2) DescribeVolumes(request *ec2.DescribeVolumesInput) ([]*ec2.Volume, error) { + args := e.Called(request) + return args.Get(0).([]*ec2.Volume), nil +} + +func (ec2 *FakeEC2) CreateVolume(request *ec2.CreateVolumeInput) (resp *ec2.Volume, err error) { + panic("Not implemented") +} + +func (ec2 *FakeEC2) DeleteVolume(request *ec2.DeleteVolumeInput) (resp *ec2.DeleteVolumeOutput, err error) { + panic("Not implemented") +} + +func (ec2 *FakeEC2) DescribeSecurityGroups(request *ec2.DescribeSecurityGroupsInput) ([]*ec2.SecurityGroup, error) { + panic("Not implemented") +} + +func (ec2 *FakeEC2) CreateSecurityGroup(*ec2.CreateSecurityGroupInput) (*ec2.CreateSecurityGroupOutput, error) { + panic("Not implemented") +} + +func (ec2 *FakeEC2) DeleteSecurityGroup(*ec2.DeleteSecurityGroupInput) (*ec2.DeleteSecurityGroupOutput, error) { + panic("Not implemented") +} + +func (ec2 *FakeEC2) AuthorizeSecurityGroupIngress(*ec2.AuthorizeSecurityGroupIngressInput) (*ec2.AuthorizeSecurityGroupIngressOutput, error) { + panic("Not implemented") +} + +func (ec2 *FakeEC2) RevokeSecurityGroupIngress(*ec2.RevokeSecurityGroupIngressInput) (*ec2.RevokeSecurityGroupIngressOutput, error) { + panic("Not implemented") +} + +func (ec2 *FakeEC2) DescribeSubnets(request *ec2.DescribeSubnetsInput) ([]*ec2.Subnet, error) { + ec2.DescribeSubnetsInput = request + return ec2.Subnets, nil +} + +func (ec2 *FakeEC2) CreateTags(*ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error) { + panic("Not implemented") +} + +func (ec2 *FakeEC2) DescribeRouteTables(request *ec2.DescribeRouteTablesInput) ([]*ec2.RouteTable, error) { + ec2.DescribeRouteTablesInput = request + return ec2.RouteTables, nil +} + +func (s *FakeEC2) CreateRoute(request *ec2.CreateRouteInput) (*ec2.CreateRouteOutput, error) { + panic("Not implemented") +} + +func (s *FakeEC2) DeleteRoute(request *ec2.DeleteRouteInput) (*ec2.DeleteRouteOutput, error) { + panic("Not implemented") +} + +func (s *FakeEC2) ModifyInstanceAttribute(request *ec2.ModifyInstanceAttributeInput) (*ec2.ModifyInstanceAttributeOutput, error) { + panic("Not implemented") +} + +type FakeELB struct { + aws *FakeAWSServices + mock.Mock +} + +func (ec2 *FakeELB) CreateLoadBalancer(*elb.CreateLoadBalancerInput) (*elb.CreateLoadBalancerOutput, error) { + panic("Not implemented") +} + +func (ec2 *FakeELB) DeleteLoadBalancer(input *elb.DeleteLoadBalancerInput) (*elb.DeleteLoadBalancerOutput, error) { + panic("Not implemented") +} + +func (ec2 *FakeELB) DescribeLoadBalancers(input *elb.DescribeLoadBalancersInput) (*elb.DescribeLoadBalancersOutput, error) { + args := ec2.Called(input) + return args.Get(0).(*elb.DescribeLoadBalancersOutput), nil +} +func (ec2 *FakeELB) RegisterInstancesWithLoadBalancer(*elb.RegisterInstancesWithLoadBalancerInput) (*elb.RegisterInstancesWithLoadBalancerOutput, error) { + panic("Not implemented") +} + +func (ec2 *FakeELB) DeregisterInstancesFromLoadBalancer(*elb.DeregisterInstancesFromLoadBalancerInput) (*elb.DeregisterInstancesFromLoadBalancerOutput, error) { + panic("Not implemented") +} + +func (ec2 *FakeELB) DetachLoadBalancerFromSubnets(*elb.DetachLoadBalancerFromSubnetsInput) (*elb.DetachLoadBalancerFromSubnetsOutput, error) { + panic("Not implemented") +} + +func (ec2 *FakeELB) AttachLoadBalancerToSubnets(*elb.AttachLoadBalancerToSubnetsInput) (*elb.AttachLoadBalancerToSubnetsOutput, error) { + panic("Not implemented") +} + +func (ec2 *FakeELB) CreateLoadBalancerListeners(*elb.CreateLoadBalancerListenersInput) (*elb.CreateLoadBalancerListenersOutput, error) { + panic("Not implemented") +} + +func (ec2 *FakeELB) DeleteLoadBalancerListeners(*elb.DeleteLoadBalancerListenersInput) (*elb.DeleteLoadBalancerListenersOutput, error) { + panic("Not implemented") +} + +func (ec2 *FakeELB) ApplySecurityGroupsToLoadBalancer(*elb.ApplySecurityGroupsToLoadBalancerInput) (*elb.ApplySecurityGroupsToLoadBalancerOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) ConfigureHealthCheck(*elb.ConfigureHealthCheckInput) (*elb.ConfigureHealthCheckOutput, error) { + panic("Not implemented") +} + +type FakeASG struct { + aws *FakeAWSServices +} + +func (a *FakeASG) UpdateAutoScalingGroup(*autoscaling.UpdateAutoScalingGroupInput) (*autoscaling.UpdateAutoScalingGroupOutput, error) { + panic("Not implemented") +} + +func (a *FakeASG) DescribeAutoScalingGroups(*autoscaling.DescribeAutoScalingGroupsInput) (*autoscaling.DescribeAutoScalingGroupsOutput, error) { + panic("Not implemented") +} + +func mockInstancesResp(selfInstance *ec2.Instance, instances []*ec2.Instance) (*AWSCloud, *FakeAWSServices) { + awsServices := NewFakeAWSServices() + awsServices.instances = instances + awsServices.selfInstance = selfInstance + awsCloud, err := newAWSCloud(nil, awsServices) + if err != nil { + panic(err) + } + return awsCloud, awsServices +} + +func mockAvailabilityZone(availabilityZone string) *AWSCloud { + awsServices := NewFakeAWSServices().withAz(availabilityZone) + awsCloud, err := newAWSCloud(nil, awsServices) + if err != nil { + panic(err) + } + return awsCloud +} + +func TestList(t *testing.T) { + // TODO this setup is not very clean and could probably be improved + var instance0 ec2.Instance + var instance1 ec2.Instance + var instance2 ec2.Instance + var instance3 ec2.Instance + + //0 + tag0 := ec2.Tag{ + Key: aws.String("Name"), + Value: aws.String("foo"), + } + instance0.Tags = []*ec2.Tag{&tag0} + instance0.InstanceId = aws.String("instance0") + instance0.PrivateDnsName = aws.String("instance0.ec2.internal") + instance0.Placement = &ec2.Placement{AvailabilityZone: aws.String("us-east-1a")} + state0 := ec2.InstanceState{ + Name: aws.String("running"), + } + instance0.State = &state0 + + //1 + tag1 := ec2.Tag{ + Key: aws.String("Name"), + Value: aws.String("bar"), + } + instance1.Tags = []*ec2.Tag{&tag1} + instance1.InstanceId = aws.String("instance1") + instance1.PrivateDnsName = aws.String("instance1.ec2.internal") + instance1.Placement = &ec2.Placement{AvailabilityZone: aws.String("us-east-1a")} + state1 := ec2.InstanceState{ + Name: aws.String("running"), + } + instance1.State = &state1 + + //2 + tag2 := ec2.Tag{ + Key: aws.String("Name"), + Value: aws.String("baz"), + } + instance2.Tags = []*ec2.Tag{&tag2} + instance2.InstanceId = aws.String("instance2") + instance2.PrivateDnsName = aws.String("instance2.ec2.internal") + instance2.Placement = &ec2.Placement{AvailabilityZone: aws.String("us-east-1a")} + state2 := ec2.InstanceState{ + Name: aws.String("running"), + } + instance2.State = &state2 + + //3 + tag3 := ec2.Tag{ + Key: aws.String("Name"), + Value: aws.String("quux"), + } + instance3.Tags = []*ec2.Tag{&tag3} + instance3.InstanceId = aws.String("instance3") + instance3.PrivateDnsName = aws.String("instance3.ec2.internal") + instance3.Placement = &ec2.Placement{AvailabilityZone: aws.String("us-east-1a")} + state3 := ec2.InstanceState{ + Name: aws.String("running"), + } + instance3.State = &state3 + + instances := []*ec2.Instance{&instance0, &instance1, &instance2, &instance3} + aws, _ := mockInstancesResp(&instance0, instances) + + table := []struct { + input string + expect []string + }{ + {"blahonga", []string{}}, + {"quux", []string{"instance3.ec2.internal"}}, + {"a", []string{"instance1.ec2.internal", "instance2.ec2.internal"}}, + } + + for _, item := range table { + result, err := aws.List(item.input) + if err != nil { + t.Errorf("Expected call with %v to succeed, failed with %s", item.input, err) + } + if e, a := item.expect, result; !reflect.DeepEqual(e, a) { + t.Errorf("Expected %v, got %v", e, a) + } + } +} + +func testHasNodeAddress(t *testing.T, addrs []api.NodeAddress, addressType api.NodeAddressType, address string) { + for _, addr := range addrs { + if addr.Type == addressType && addr.Address == address { + return + } + } + t.Errorf("Did not find expected address: %s:%s in %v", addressType, address, addrs) +} + +func TestNodeAddresses(t *testing.T) { + // Note these instances have the same name + // (we test that this produces an error) + var instance0 ec2.Instance + var instance1 ec2.Instance + var instance2 ec2.Instance + + //0 + instance0.InstanceId = aws.String("i-0") + instance0.PrivateDnsName = aws.String("instance-same.ec2.internal") + instance0.PrivateIpAddress = aws.String("192.168.0.1") + instance0.PublicIpAddress = aws.String("1.2.3.4") + instance0.InstanceType = aws.String("c3.large") + instance0.Placement = &ec2.Placement{AvailabilityZone: aws.String("us-east-1a")} + state0 := ec2.InstanceState{ + Name: aws.String("running"), + } + instance0.State = &state0 + + //1 + instance1.InstanceId = aws.String("i-1") + instance1.PrivateDnsName = aws.String("instance-same.ec2.internal") + instance1.PrivateIpAddress = aws.String("192.168.0.2") + instance1.InstanceType = aws.String("c3.large") + instance1.Placement = &ec2.Placement{AvailabilityZone: aws.String("us-east-1a")} + state1 := ec2.InstanceState{ + Name: aws.String("running"), + } + instance1.State = &state1 + + //2 + instance2.InstanceId = aws.String("i-2") + instance2.PrivateDnsName = aws.String("instance-other.ec2.internal") + instance2.PrivateIpAddress = aws.String("192.168.0.1") + instance2.PublicIpAddress = aws.String("1.2.3.4") + instance2.InstanceType = aws.String("c3.large") + instance2.Placement = &ec2.Placement{AvailabilityZone: aws.String("us-east-1a")} + state2 := ec2.InstanceState{ + Name: aws.String("running"), + } + instance2.State = &state2 + + instances := []*ec2.Instance{&instance0, &instance1, &instance2} + + aws1, _ := mockInstancesResp(&instance0, []*ec2.Instance{&instance0}) + _, err1 := aws1.NodeAddresses("instance-mismatch.ec2.internal") + if err1 == nil { + t.Errorf("Should error when no instance found") + } + + aws2, _ := mockInstancesResp(&instance2, instances) + _, err2 := aws2.NodeAddresses("instance-same.ec2.internal") + if err2 == nil { + t.Errorf("Should error when multiple instances found") + } + + aws3, _ := mockInstancesResp(&instance0, instances[0:1]) + addrs3, err3 := aws3.NodeAddresses("instance-same.ec2.internal") + if err3 != nil { + t.Errorf("Should not error when instance found") + } + if len(addrs3) != 3 { + t.Errorf("Should return exactly 3 NodeAddresses") + } + testHasNodeAddress(t, addrs3, api.NodeInternalIP, "192.168.0.1") + testHasNodeAddress(t, addrs3, api.NodeLegacyHostIP, "192.168.0.1") + testHasNodeAddress(t, addrs3, api.NodeExternalIP, "1.2.3.4") + + // Fetch from metadata + aws4, fakeServices := mockInstancesResp(&instance0, []*ec2.Instance{&instance0}) + fakeServices.selfInstance.PublicIpAddress = aws.String("2.3.4.5") + fakeServices.selfInstance.PrivateIpAddress = aws.String("192.168.0.2") + + addrs4, err4 := aws4.NodeAddresses(*instance0.PrivateDnsName) + if err4 != nil { + t.Errorf("unexpected error: %v", err4) + } + testHasNodeAddress(t, addrs4, api.NodeInternalIP, "192.168.0.2") + testHasNodeAddress(t, addrs4, api.NodeExternalIP, "2.3.4.5") +} + +func TestGetRegion(t *testing.T) { + aws := mockAvailabilityZone("us-west-2e") + zones, ok := aws.Zones() + if !ok { + t.Fatalf("Unexpected missing zones impl") + } + zone, err := zones.GetZone() + if err != nil { + t.Fatalf("unexpected error %v", err) + } + if zone.Region != "us-west-2" { + t.Errorf("Unexpected region: %s", zone.Region) + } + if zone.FailureDomain != "us-west-2e" { + t.Errorf("Unexpected FailureDomain: %s", zone.FailureDomain) + } +} + +func TestFindVPCID(t *testing.T) { + awsServices := NewFakeAWSServices() + c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) + if err != nil { + t.Errorf("Error building aws cloud: %v", err) + return + } + vpcID, err := c.findVPCID() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if vpcID != "vpc-mac0" { + t.Errorf("Unexpected vpcID: %s", vpcID) + } +} + +func constructSubnets(subnetsIn map[int]map[string]string) (subnetsOut []*ec2.Subnet) { + for i := range subnetsIn { + subnetsOut = append( + subnetsOut, + constructSubnet( + subnetsIn[i]["id"], + subnetsIn[i]["az"], + ), + ) + } + return +} + +func constructSubnet(id string, az string) *ec2.Subnet { + return &ec2.Subnet{ + SubnetId: &id, + AvailabilityZone: &az, + } +} + +func constructRouteTables(routeTablesIn map[string]bool) (routeTablesOut []*ec2.RouteTable) { + routeTablesOut = append(routeTablesOut, + &ec2.RouteTable{ + Associations: []*ec2.RouteTableAssociation{{Main: aws.Bool(true)}}, + Routes: []*ec2.Route{{ + DestinationCidrBlock: aws.String("0.0.0.0/0"), + GatewayId: aws.String("igw-main"), + }}, + }) + + for subnetID := range routeTablesIn { + routeTablesOut = append( + routeTablesOut, + constructRouteTable( + subnetID, + routeTablesIn[subnetID], + ), + ) + } + return +} + +func constructRouteTable(subnetID string, public bool) *ec2.RouteTable { + var gatewayID string + if public { + gatewayID = "igw-" + subnetID[len(subnetID)-8:8] + } else { + gatewayID = "vgw-" + subnetID[len(subnetID)-8:8] + } + return &ec2.RouteTable{ + Associations: []*ec2.RouteTableAssociation{{SubnetId: aws.String(subnetID)}}, + Routes: []*ec2.Route{{ + DestinationCidrBlock: aws.String("0.0.0.0/0"), + GatewayId: aws.String(gatewayID), + }}, + } +} + +func TestSubnetIDsinVPC(t *testing.T) { + awsServices := NewFakeAWSServices() + c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) + if err != nil { + t.Errorf("Error building aws cloud: %v", err) + return + } + + // test with 3 subnets from 3 different AZs + subnets := make(map[int]map[string]string) + subnets[0] = make(map[string]string) + subnets[0]["id"] = "subnet-a0000001" + subnets[0]["az"] = "af-south-1a" + subnets[1] = make(map[string]string) + subnets[1]["id"] = "subnet-b0000001" + subnets[1]["az"] = "af-south-1b" + subnets[2] = make(map[string]string) + subnets[2]["id"] = "subnet-c0000001" + subnets[2]["az"] = "af-south-1c" + awsServices.ec2.Subnets = constructSubnets(subnets) + + routeTables := map[string]bool{ + "subnet-a0000001": true, + "subnet-b0000001": true, + "subnet-c0000001": true, + } + awsServices.ec2.RouteTables = constructRouteTables(routeTables) + + result, err := c.findELBSubnets(false) + if err != nil { + t.Errorf("Error listing subnets: %v", err) + return + } + + if len(result) != 3 { + t.Errorf("Expected 3 subnets but got %d", len(result)) + return + } + + result_set := make(map[string]bool) + for _, v := range result { + result_set[v] = true + } + + for i := range subnets { + if !result_set[subnets[i]["id"]] { + t.Errorf("Expected subnet%d '%s' in result: %v", i, subnets[i]["id"], result) + return + } + } + + // test implicit routing table - when subnets are not explicitly linked to a table they should use main + awsServices.ec2.RouteTables = constructRouteTables(map[string]bool{}) + + result, err = c.findELBSubnets(false) + if err != nil { + t.Errorf("Error listing subnets: %v", err) + return + } + + if len(result) != 3 { + t.Errorf("Expected 3 subnets but got %d", len(result)) + return + } + + result_set = make(map[string]bool) + for _, v := range result { + result_set[v] = true + } + + for i := range subnets { + if !result_set[subnets[i]["id"]] { + t.Errorf("Expected subnet%d '%s' in result: %v", i, subnets[i]["id"], result) + return + } + } + + // test with 4 subnets from 3 different AZs + // add duplicate az subnet + subnets[3] = make(map[string]string) + subnets[3]["id"] = "subnet-c0000002" + subnets[3]["az"] = "af-south-1c" + awsServices.ec2.Subnets = constructSubnets(subnets) + routeTables["subnet-c0000002"] = true + awsServices.ec2.RouteTables = constructRouteTables(routeTables) + + result, err = c.findELBSubnets(false) + if err != nil { + t.Errorf("Error listing subnets: %v", err) + return + } + + if len(result) != 3 { + t.Errorf("Expected 3 subnets but got %d", len(result)) + return + } + + // test with 6 subnets from 3 different AZs + // with 3 private subnets + subnets[4] = make(map[string]string) + subnets[4]["id"] = "subnet-d0000001" + subnets[4]["az"] = "af-south-1a" + subnets[5] = make(map[string]string) + subnets[5]["id"] = "subnet-d0000002" + subnets[5]["az"] = "af-south-1b" + + awsServices.ec2.Subnets = constructSubnets(subnets) + routeTables["subnet-a0000001"] = false + routeTables["subnet-b0000001"] = false + routeTables["subnet-c0000001"] = false + routeTables["subnet-c0000002"] = true + routeTables["subnet-d0000001"] = true + routeTables["subnet-d0000002"] = true + awsServices.ec2.RouteTables = constructRouteTables(routeTables) + result, err = c.findELBSubnets(false) + if err != nil { + t.Errorf("Error listing subnets: %v", err) + return + } + + if len(result) != 3 { + t.Errorf("Expected 3 subnets but got %d", len(result)) + return + } + + expected := []*string{aws.String("subnet-c0000002"), aws.String("subnet-d0000001"), aws.String("subnet-d0000002")} + for _, s := range result { + if !contains(expected, s) { + t.Errorf("Unexpected subnet '%s' found", s) + return + } + } +} + +func TestIpPermissionExistsHandlesMultipleGroupIds(t *testing.T) { + oldIpPermission := ec2.IpPermission{ + UserIdGroupPairs: []*ec2.UserIdGroupPair{ + {GroupId: aws.String("firstGroupId")}, + {GroupId: aws.String("secondGroupId")}, + {GroupId: aws.String("thirdGroupId")}, + }, + } + + existingIpPermission := ec2.IpPermission{ + UserIdGroupPairs: []*ec2.UserIdGroupPair{ + {GroupId: aws.String("secondGroupId")}, + }, + } + + newIpPermission := ec2.IpPermission{ + UserIdGroupPairs: []*ec2.UserIdGroupPair{ + {GroupId: aws.String("fourthGroupId")}, + }, + } + + equals := ipPermissionExists(&existingIpPermission, &oldIpPermission, false) + if !equals { + t.Errorf("Should have been considered equal since first is in the second array of groups") + } + + equals = ipPermissionExists(&newIpPermission, &oldIpPermission, false) + if equals { + t.Errorf("Should have not been considered equal since first is not in the second array of groups") + } +} + +func TestIpPermissionExistsHandlesRangeSubsets(t *testing.T) { + // Two existing scenarios we'll test against + emptyIpPermission := ec2.IpPermission{} + + oldIpPermission := ec2.IpPermission{ + IpRanges: []*ec2.IpRange{ + {CidrIp: aws.String("10.0.0.0/8")}, + {CidrIp: aws.String("192.168.1.0/24")}, + }, + } + + // Two already existing ranges and a new one + existingIpPermission := ec2.IpPermission{ + IpRanges: []*ec2.IpRange{ + {CidrIp: aws.String("10.0.0.0/8")}, + }, + } + existingIpPermission2 := ec2.IpPermission{ + IpRanges: []*ec2.IpRange{ + {CidrIp: aws.String("192.168.1.0/24")}, + }, + } + + newIpPermission := ec2.IpPermission{ + IpRanges: []*ec2.IpRange{ + {CidrIp: aws.String("172.16.0.0/16")}, + }, + } + + exists := ipPermissionExists(&emptyIpPermission, &emptyIpPermission, false) + if !exists { + t.Errorf("Should have been considered existing since we're comparing a range array against itself") + } + exists = ipPermissionExists(&oldIpPermission, &oldIpPermission, false) + if !exists { + t.Errorf("Should have been considered existing since we're comparing a range array against itself") + } + + exists = ipPermissionExists(&existingIpPermission, &oldIpPermission, false) + if !exists { + t.Errorf("Should have been considered existing since 10.* is in oldIpPermission's array of ranges") + } + exists = ipPermissionExists(&existingIpPermission2, &oldIpPermission, false) + if !exists { + t.Errorf("Should have been considered existing since 192.* is in oldIpPermission2's array of ranges") + } + + exists = ipPermissionExists(&newIpPermission, &emptyIpPermission, false) + if exists { + t.Errorf("Should have not been considered existing since we compared against a missing array of ranges") + } + exists = ipPermissionExists(&newIpPermission, &oldIpPermission, false) + if exists { + t.Errorf("Should have not been considered existing since 172.* is not in oldIpPermission's array of ranges") + } +} + +func TestIpPermissionExistsHandlesMultipleGroupIdsWithUserIds(t *testing.T) { + oldIpPermission := ec2.IpPermission{ + UserIdGroupPairs: []*ec2.UserIdGroupPair{ + {GroupId: aws.String("firstGroupId"), UserId: aws.String("firstUserId")}, + {GroupId: aws.String("secondGroupId"), UserId: aws.String("secondUserId")}, + {GroupId: aws.String("thirdGroupId"), UserId: aws.String("thirdUserId")}, + }, + } + + existingIpPermission := ec2.IpPermission{ + UserIdGroupPairs: []*ec2.UserIdGroupPair{ + {GroupId: aws.String("secondGroupId"), UserId: aws.String("secondUserId")}, + }, + } + + newIpPermission := ec2.IpPermission{ + UserIdGroupPairs: []*ec2.UserIdGroupPair{ + {GroupId: aws.String("secondGroupId"), UserId: aws.String("anotherUserId")}, + }, + } + + equals := ipPermissionExists(&existingIpPermission, &oldIpPermission, true) + if !equals { + t.Errorf("Should have been considered equal since first is in the second array of groups") + } + + equals = ipPermissionExists(&newIpPermission, &oldIpPermission, true) + if equals { + t.Errorf("Should have not been considered equal since first is not in the second array of groups") + } +} +func TestFindInstanceByNodeNameExcludesTerminatedInstances(t *testing.T) { + awsServices := NewFakeAWSServices() + + nodeName := "my-dns.internal" + + var tag ec2.Tag + tag.Key = aws.String(TagNameKubernetesCluster) + tag.Value = aws.String(TestClusterId) + tags := []*ec2.Tag{&tag} + + var runningInstance ec2.Instance + runningInstance.InstanceId = aws.String("i-running") + runningInstance.PrivateDnsName = aws.String(nodeName) + runningInstance.State = &ec2.InstanceState{Code: aws.Int64(16), Name: aws.String("running")} + runningInstance.Tags = tags + + var terminatedInstance ec2.Instance + terminatedInstance.InstanceId = aws.String("i-terminated") + terminatedInstance.PrivateDnsName = aws.String(nodeName) + terminatedInstance.State = &ec2.InstanceState{Code: aws.Int64(48), Name: aws.String("terminated")} + terminatedInstance.Tags = tags + + instances := []*ec2.Instance{&terminatedInstance, &runningInstance} + awsServices.instances = append(awsServices.instances, instances...) + + c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) + if err != nil { + t.Errorf("Error building aws cloud: %v", err) + return + } + + instance, err := c.findInstanceByNodeName(nodeName) + + if err != nil { + t.Errorf("Failed to find instance: %v", err) + return + } + + if *instance.InstanceId != "i-running" { + t.Errorf("Expected running instance but got %v", *instance.InstanceId) + } +} + +func TestFindInstancesByNodeName(t *testing.T) { + awsServices := NewFakeAWSServices() + + nodeNameOne := "my-dns.internal" + nodeNameTwo := "my-dns-two.internal" + + var tag ec2.Tag + tag.Key = aws.String(TagNameKubernetesCluster) + tag.Value = aws.String(TestClusterId) + tags := []*ec2.Tag{&tag} + + var runningInstance ec2.Instance + runningInstance.InstanceId = aws.String("i-running") + runningInstance.PrivateDnsName = aws.String(nodeNameOne) + runningInstance.State = &ec2.InstanceState{Code: aws.Int64(16), Name: aws.String("running")} + runningInstance.Tags = tags + + var secondInstance ec2.Instance + + secondInstance.InstanceId = aws.String("i-running") + secondInstance.PrivateDnsName = aws.String(nodeNameTwo) + secondInstance.State = &ec2.InstanceState{Code: aws.Int64(48), Name: aws.String("running")} + secondInstance.Tags = tags + + var terminatedInstance ec2.Instance + terminatedInstance.InstanceId = aws.String("i-terminated") + terminatedInstance.PrivateDnsName = aws.String(nodeNameOne) + terminatedInstance.State = &ec2.InstanceState{Code: aws.Int64(48), Name: aws.String("terminated")} + terminatedInstance.Tags = tags + + instances := []*ec2.Instance{&secondInstance, &runningInstance, &terminatedInstance} + awsServices.instances = append(awsServices.instances, instances...) + + c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) + if err != nil { + t.Errorf("Error building aws cloud: %v", err) + return + } + + nodeNames := []string{nodeNameOne} + returnedInstances, errr := c.getInstancesByNodeNames(nodeNames) + + if errr != nil { + t.Errorf("Failed to find instance: %v", err) + return + } + + if len(returnedInstances) != 1 { + t.Errorf("Expected a single isntance but found: %v", returnedInstances) + } + + if *returnedInstances[0].PrivateDnsName != nodeNameOne { + t.Errorf("Expected node name %v but got %v", nodeNameOne, returnedInstances[0].PrivateDnsName) + } +} + +func TestGetVolumeLabels(t *testing.T) { + awsServices := NewFakeAWSServices() + c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) + assert.Nil(t, err, "Error building aws cloud: %v", err) + volumeId := aws.String("vol-VolumeId") + expectedVolumeRequest := &ec2.DescribeVolumesInput{VolumeIds: []*string{volumeId}} + awsServices.ec2.On("DescribeVolumes", expectedVolumeRequest).Return([]*ec2.Volume{ + { + VolumeId: volumeId, + AvailabilityZone: aws.String("us-east-1a"), + }, + }) + + labels, err := c.GetVolumeLabels(*volumeId) + + assert.Nil(t, err, "Error creating Volume %v", err) + assert.Equal(t, map[string]string{ + unversioned.LabelZoneFailureDomain: "us-east-1a", + unversioned.LabelZoneRegion: "us-east-1"}, labels) + awsServices.ec2.AssertExpectations(t) +} + +func (self *FakeELB) expectDescribeLoadBalancers(loadBalancerName string) { + self.On("DescribeLoadBalancers", &elb.DescribeLoadBalancersInput{LoadBalancerNames: []*string{aws.String(loadBalancerName)}}).Return(&elb.DescribeLoadBalancersOutput{ + LoadBalancerDescriptions: []*elb.LoadBalancerDescription{{}}, + }) +} + +func TestDescribeLoadBalancerOnDelete(t *testing.T) { + awsServices := NewFakeAWSServices() + c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices) + awsServices.elb.expectDescribeLoadBalancers("aid") + + c.EnsureLoadBalancerDeleted(&api.Service{ObjectMeta: api.ObjectMeta{Name: "myservice", UID: "id"}}) +} + +func TestDescribeLoadBalancerOnUpdate(t *testing.T) { + awsServices := NewFakeAWSServices() + c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices) + awsServices.elb.expectDescribeLoadBalancers("aid") + + c.UpdateLoadBalancer(&api.Service{ObjectMeta: api.ObjectMeta{Name: "myservice", UID: "id"}}, []string{}) +} + +func TestDescribeLoadBalancerOnGet(t *testing.T) { + awsServices := NewFakeAWSServices() + c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices) + awsServices.elb.expectDescribeLoadBalancers("aid") + + c.GetLoadBalancer(&api.Service{ObjectMeta: api.ObjectMeta{Name: "myservice", UID: "id"}}) +} + +func TestDescribeLoadBalancerOnEnsure(t *testing.T) { + awsServices := NewFakeAWSServices() + c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices) + awsServices.elb.expectDescribeLoadBalancers("aid") + + c.EnsureLoadBalancer(&api.Service{ObjectMeta: api.ObjectMeta{Name: "myservice", UID: "id"}}, []string{}, map[string]string{}) +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_utils.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_utils.go new file mode 100644 index 000000000..310b4898e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/aws_utils.go @@ -0,0 +1,51 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 aws + +import ( + "github.com/aws/aws-sdk-go/aws" + "k8s.io/kubernetes/pkg/util/sets" +) + +func stringSetToPointers(in sets.String) []*string { + if in == nil { + return nil + } + out := make([]*string, len(in)) + for k := range in { + out = append(out, aws.String(k)) + } + return out +} + +func stringSetFromPointers(in []*string) sets.String { + if in == nil { + return nil + } + out := sets.NewString() + for i := range in { + out.Insert(orEmpty(in[i])) + } + return out +} + +func orZero(v *int64) int64 { + if v == nil { + return 0 + } + return *v +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/log_handler.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/log_handler.go new file mode 100644 index 000000000..177c7074a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/log_handler.go @@ -0,0 +1,34 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 aws + +import ( + "github.com/aws/aws-sdk-go/aws/request" + "github.com/golang/glog" +) + +// Handler for aws-sdk-go that logs all requests +func awsHandlerLogger(req *request.Request) { + service := req.ClientInfo.ServiceName + + name := "?" + if req.Operation != nil { + name = req.Operation.Name + } + + glog.V(4).Infof("AWS request: %s %s", service, name) +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/retry_handler.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/retry_handler.go new file mode 100644 index 000000000..6e6657bf0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/retry_handler.go @@ -0,0 +1,161 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 aws + +import ( + "math" + "sync" + "time" + + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/golang/glog" +) + +const ( + decayIntervalSeconds = 20 + decayFraction = 0.8 + maxDelay = 60 * time.Second +) + +// CrossRequestRetryDelay inserts delays before AWS calls, when we are observing RequestLimitExceeded errors +// Note that we share a CrossRequestRetryDelay across multiple AWS requests; this is a process-wide back-off, +// whereas the aws-sdk-go implements a per-request exponential backoff/retry +type CrossRequestRetryDelay struct { + backoff Backoff +} + +// Create a new CrossRequestRetryDelay +func NewCrossRequestRetryDelay() *CrossRequestRetryDelay { + c := &CrossRequestRetryDelay{} + c.backoff.init(decayIntervalSeconds, decayFraction, maxDelay) + return c +} + +// Added to the Sign chain; called before each request +func (c *CrossRequestRetryDelay) BeforeSign(r *request.Request) { + now := time.Now() + delay := c.backoff.ComputeDelayForRequest(now) + if delay > 0 { + glog.Warningf("Inserting delay before AWS request (%s) to avoid RequestLimitExceeded: %s", + describeRequest(r), delay.String()) + r.Config.SleepDelay(delay) + + // Avoid clock skew problems + r.Time = now + } +} + +// Return a user-friendly string describing the request, for use in log messages +func describeRequest(r *request.Request) string { + service := r.ClientInfo.ServiceName + + name := "?" + if r.Operation != nil { + name = r.Operation.Name + } + + return service + "::" + name +} + +// Added to the AfterRetry chain; called after any error +func (c *CrossRequestRetryDelay) AfterRetry(r *request.Request) { + if r.Error == nil { + return + } + awsError, ok := r.Error.(awserr.Error) + if !ok { + return + } + if awsError.Code() == "RequestLimitExceeded" { + c.backoff.ReportError() + glog.Warningf("Got RequestLimitExceeded error on AWS request (%s)", + describeRequest(r)) + } +} + +// Backoff manages a backoff that varies based on the recently observed failures +type Backoff struct { + decayIntervalSeconds int64 + decayFraction float64 + maxDelay time.Duration + + mutex sync.Mutex + + // We count all requests & the number of requests which hit a + // RequestLimit. We only really care about 'recent' requests, so we + // decay the counts exponentially to bias towards recent values. + countErrorsRequestLimit float32 + countRequests float32 + lastDecay int64 +} + +func (b *Backoff) init(decayIntervalSeconds int, decayFraction float64, maxDelay time.Duration) { + b.lastDecay = time.Now().Unix() + // Bias so that if the first request hits the limit we don't immediately apply the full delay + b.countRequests = 4 + b.decayIntervalSeconds = int64(decayIntervalSeconds) + b.decayFraction = decayFraction + b.maxDelay = maxDelay +} + +// Computes the delay required for a request, also updating internal state to count this request +func (b *Backoff) ComputeDelayForRequest(now time.Time) time.Duration { + b.mutex.Lock() + defer b.mutex.Unlock() + + // Apply exponential decay to the counters + timeDeltaSeconds := now.Unix() - b.lastDecay + if timeDeltaSeconds > b.decayIntervalSeconds { + intervals := float64(timeDeltaSeconds) / float64(b.decayIntervalSeconds) + decay := float32(math.Pow(b.decayFraction, intervals)) + b.countErrorsRequestLimit *= decay + b.countRequests *= decay + b.lastDecay = now.Unix() + } + + // Count this request + b.countRequests += 1.0 + + // Compute the failure rate + errorFraction := float32(0.0) + if b.countRequests > 0.5 { + // Avoid tiny residuals & rounding errors + errorFraction = b.countErrorsRequestLimit / b.countRequests + } + + // Ignore a low fraction of errors + // This also allows them to time-out + if errorFraction < 0.1 { + return time.Duration(0) + } + + // Delay by the max delay multiplied by the recent error rate + // (i.e. we apply a linear delay function) + // TODO: This is pretty arbitrary + delay := time.Nanosecond * time.Duration(float32(b.maxDelay.Nanoseconds())*errorFraction) + // Round down to the nearest second for sanity + return time.Second * time.Duration(int(delay.Seconds())) +} + +// Called when we observe a throttling error +func (b *Backoff) ReportError() { + b.mutex.Lock() + defer b.mutex.Unlock() + + b.countErrorsRequestLimit += 1.0 +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/retry_handler_test.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/retry_handler_test.go new file mode 100644 index 000000000..e02b52d6d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/retry_handler_test.go @@ -0,0 +1,135 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 aws + +import ( + "testing" + "time" +) + +// There follows a group of tests for the backoff logic. There's nothing +// particularly special about the values chosen: if we tweak the values in the +// backoff logic then we might well have to update the tests. However the key +// behavioural elements should remain (e.g. no errors => no backoff), and these +// are each tested by one of the tests below. + +// Test that we don't apply any delays when there are no errors +func TestBackoffNoErrors(t *testing.T) { + b := &Backoff{} + b.init(decayIntervalSeconds, decayFraction, maxDelay) + + now := time.Now() + for i := 0; i < 100; i++ { + d := b.ComputeDelayForRequest(now) + if d.Nanoseconds() != 0 { + t.Fatalf("unexpected delay during no-error case") + } + now = now.Add(time.Second) + } +} + +// Test that we always apply a delay when there are errors, and also that we +// don't "flap" - that our own delay doesn't cause us to oscillate between +// delay and no-delay. +func TestBackoffAllErrors(t *testing.T) { + b := &Backoff{} + b.init(decayIntervalSeconds, decayFraction, maxDelay) + + now := time.Now() + // Warm up + for i := 0; i < 10; i++ { + _ = b.ComputeDelayForRequest(now) + b.ReportError() + now = now.Add(time.Second) + } + + for i := 0; i < 100; i++ { + d := b.ComputeDelayForRequest(now) + b.ReportError() + if d.Seconds() < 5 { + t.Fatalf("unexpected short-delay during all-error case: %v", d) + } + t.Logf("delay @%d %v", i, d) + now = now.Add(d) + } +} + +// Test that we do come close to our max delay, when we see all errors at 1 +// second intervals (this simulates multiple concurrent requests, because we +// don't wait for delay in between requests) +func TestBackoffHitsMax(t *testing.T) { + b := &Backoff{} + b.init(decayIntervalSeconds, decayFraction, maxDelay) + + now := time.Now() + for i := 0; i < 100; i++ { + _ = b.ComputeDelayForRequest(now) + b.ReportError() + now = now.Add(time.Second) + } + + for i := 0; i < 10; i++ { + d := b.ComputeDelayForRequest(now) + b.ReportError() + if float32(d.Nanoseconds()) < (float32(maxDelay.Nanoseconds()) * 0.95) { + t.Fatalf("expected delay to be >= 95 percent of max delay, was %v", d) + } + t.Logf("delay @%d %v", i, d) + now = now.Add(time.Second) + } +} + +// Test that after a phase of errors, we eventually stop applying a delay once there are +// no more errors. +func TestBackoffRecovers(t *testing.T) { + b := &Backoff{} + b.init(decayIntervalSeconds, decayFraction, maxDelay) + + now := time.Now() + + // Phase of all-errors + for i := 0; i < 100; i++ { + _ = b.ComputeDelayForRequest(now) + b.ReportError() + now = now.Add(time.Second) + } + + for i := 0; i < 10; i++ { + d := b.ComputeDelayForRequest(now) + b.ReportError() + if d.Seconds() < 5 { + t.Fatalf("unexpected short-delay during all-error phase: %v", d) + } + t.Logf("error phase delay @%d %v", i, d) + now = now.Add(time.Second) + } + + // Phase of no errors + for i := 0; i < 100; i++ { + _ = b.ComputeDelayForRequest(now) + now = now.Add(3 * time.Second) + } + + for i := 0; i < 10; i++ { + d := b.ComputeDelayForRequest(now) + if d.Seconds() != 0 { + t.Fatalf("unexpected delay during error recovery phase: %v", d) + } + t.Logf("no-error phase delay @%d %v", i, d) + now = now.Add(time.Second) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/sets_ippermissions.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/sets_ippermissions.go new file mode 100644 index 000000000..2e1343ff8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/aws/sets_ippermissions.go @@ -0,0 +1,146 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 aws + +import ( + "encoding/json" + "fmt" + + "github.com/aws/aws-sdk-go/service/ec2" +) + +type IPPermissionSet map[string]*ec2.IpPermission + +func NewIPPermissionSet(items ...*ec2.IpPermission) IPPermissionSet { + s := make(IPPermissionSet) + s.Insert(items...) + return s +} + +// Ungroup splits permissions out into individual permissions +// EC2 will combine permissions with the same port but different SourceRanges together, for example +// We ungroup them so we can process them +func (s IPPermissionSet) Ungroup() IPPermissionSet { + l := []*ec2.IpPermission{} + for _, p := range s.List() { + if len(p.IpRanges) <= 1 { + l = append(l, p) + continue + } + for _, ipRange := range p.IpRanges { + c := &ec2.IpPermission{} + *c = *p + c.IpRanges = []*ec2.IpRange{ipRange} + l = append(l, c) + } + } + + l2 := []*ec2.IpPermission{} + for _, p := range l { + if len(p.UserIdGroupPairs) <= 1 { + l2 = append(l2, p) + continue + } + for _, u := range p.UserIdGroupPairs { + c := &ec2.IpPermission{} + *c = *p + c.UserIdGroupPairs = []*ec2.UserIdGroupPair{u} + l2 = append(l, c) + } + } + + l3 := []*ec2.IpPermission{} + for _, p := range l2 { + if len(p.PrefixListIds) <= 1 { + l3 = append(l3, p) + continue + } + for _, v := range p.PrefixListIds { + c := &ec2.IpPermission{} + *c = *p + c.PrefixListIds = []*ec2.PrefixListId{v} + l3 = append(l3, c) + } + } + + return NewIPPermissionSet(l3...) +} + +// Insert adds items to the set. +func (s IPPermissionSet) Insert(items ...*ec2.IpPermission) { + for _, p := range items { + k := keyForIPPermission(p) + s[k] = p + } +} + +// List returns the contents as a slice. Order is not defined. +func (s IPPermissionSet) List() []*ec2.IpPermission { + res := make([]*ec2.IpPermission, 0, len(s)) + for _, v := range s { + res = append(res, v) + } + return res +} + +// IsSuperset returns true if and only if s1 is a superset of s2. +func (s1 IPPermissionSet) IsSuperset(s2 IPPermissionSet) bool { + for k := range s2 { + _, found := s1[k] + if !found { + return false + } + } + return true +} + +// Equal returns true if and only if s1 is equal (as a set) to s2. +// Two sets are equal if their membership is identical. +// (In practice, this means same elements, order doesn't matter) +func (s1 IPPermissionSet) Equal(s2 IPPermissionSet) bool { + return len(s1) == len(s2) && s1.IsSuperset(s2) +} + +// Difference returns a set of objects that are not in s2 +// For example: +// s1 = {a1, a2, a3} +// s2 = {a1, a2, a4, a5} +// s1.Difference(s2) = {a3} +// s2.Difference(s1) = {a4, a5} +func (s IPPermissionSet) Difference(s2 IPPermissionSet) IPPermissionSet { + result := NewIPPermissionSet() + for k, v := range s { + _, found := s2[k] + if !found { + result[k] = v + } + } + return result +} + +// Len returns the size of the set. +func (s IPPermissionSet) Len() int { + return len(s) +} + +func keyForIPPermission(p *ec2.IpPermission) string { + v, err := json.Marshal(p) + if err != nil { + panic(fmt.Sprintf("error building JSON representation of ec2.IpPermission: %v", err)) + } + return string(v) +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/fake/doc.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/fake/doc.go new file mode 100644 index 000000000..ff22d568f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/fake/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake is a test-double implementation of cloudprovider +// Interface, LoadBalancer and Instances. It is useful for testing. +package fake diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/fake/fake.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/fake/fake.go new file mode 100644 index 000000000..6bc0a0e76 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/fake/fake.go @@ -0,0 +1,267 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 fake + +import ( + "errors" + "fmt" + "net" + "regexp" + "sync" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/cloudprovider" +) + +const ProviderName = "fake" + +// FakeBalancer is a fake storage of balancer information +type FakeBalancer struct { + Name string + Region string + LoadBalancerIP string + Ports []api.ServicePort + Hosts []string +} + +type FakeUpdateBalancerCall struct { + Service *api.Service + Hosts []string +} + +// FakeCloud is a test-double implementation of Interface, LoadBalancer, Instances, and Routes. It is useful for testing. +type FakeCloud struct { + Exists bool + Err error + Calls []string + Addresses []api.NodeAddress + ExtID map[string]string + InstanceTypes map[string]string + Machines []string + NodeResources *api.NodeResources + ClusterList []string + MasterName string + ExternalIP net.IP + Balancers map[string]FakeBalancer + UpdateCalls []FakeUpdateBalancerCall + RouteMap map[string]*FakeRoute + Lock sync.Mutex + cloudprovider.Zone +} + +type FakeRoute struct { + ClusterName string + Route cloudprovider.Route +} + +func (f *FakeCloud) addCall(desc string) { + f.Calls = append(f.Calls, desc) +} + +// ClearCalls clears internal record of method calls to this FakeCloud. +func (f *FakeCloud) ClearCalls() { + f.Calls = []string{} +} + +func (f *FakeCloud) ListClusters() ([]string, error) { + return f.ClusterList, f.Err +} + +func (f *FakeCloud) Master(name string) (string, error) { + return f.MasterName, f.Err +} + +func (f *FakeCloud) Clusters() (cloudprovider.Clusters, bool) { + return f, true +} + +// ProviderName returns the cloud provider ID. +func (f *FakeCloud) ProviderName() string { + return ProviderName +} + +// ScrubDNS filters DNS settings for pods. +func (f *FakeCloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { + return nameservers, searches +} + +// LoadBalancer returns a fake implementation of LoadBalancer. +// Actually it just returns f itself. +func (f *FakeCloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) { + return f, true +} + +// Instances returns a fake implementation of Instances. +// +// Actually it just returns f itself. +func (f *FakeCloud) Instances() (cloudprovider.Instances, bool) { + return f, true +} + +func (f *FakeCloud) Zones() (cloudprovider.Zones, bool) { + return f, true +} + +func (f *FakeCloud) Routes() (cloudprovider.Routes, bool) { + return f, true +} + +// GetLoadBalancer is a stub implementation of LoadBalancer.GetLoadBalancer. +func (f *FakeCloud) GetLoadBalancer(service *api.Service) (*api.LoadBalancerStatus, bool, error) { + status := &api.LoadBalancerStatus{} + status.Ingress = []api.LoadBalancerIngress{{IP: f.ExternalIP.String()}} + + return status, f.Exists, f.Err +} + +// EnsureLoadBalancer is a test-spy implementation of LoadBalancer.EnsureLoadBalancer. +// It adds an entry "create" into the internal method call record. +func (f *FakeCloud) EnsureLoadBalancer(service *api.Service, hosts []string, annotations map[string]string) (*api.LoadBalancerStatus, error) { + f.addCall("create") + if f.Balancers == nil { + f.Balancers = make(map[string]FakeBalancer) + } + + name := cloudprovider.GetLoadBalancerName(service) + spec := service.Spec + + zone, err := f.GetZone() + if err != nil { + return nil, err + } + region := zone.Region + + f.Balancers[name] = FakeBalancer{name, region, spec.LoadBalancerIP, spec.Ports, hosts} + + status := &api.LoadBalancerStatus{} + status.Ingress = []api.LoadBalancerIngress{{IP: f.ExternalIP.String()}} + + return status, f.Err +} + +// UpdateLoadBalancer is a test-spy implementation of LoadBalancer.UpdateLoadBalancer. +// It adds an entry "update" into the internal method call record. +func (f *FakeCloud) UpdateLoadBalancer(service *api.Service, hosts []string) error { + f.addCall("update") + f.UpdateCalls = append(f.UpdateCalls, FakeUpdateBalancerCall{service, hosts}) + return f.Err +} + +// EnsureLoadBalancerDeleted is a test-spy implementation of LoadBalancer.EnsureLoadBalancerDeleted. +// It adds an entry "delete" into the internal method call record. +func (f *FakeCloud) EnsureLoadBalancerDeleted(service *api.Service) error { + f.addCall("delete") + return f.Err +} + +func (f *FakeCloud) AddSSHKeyToAllInstances(user string, keyData []byte) error { + return errors.New("unimplemented") +} + +// Implementation of Instances.CurrentNodeName +func (f *FakeCloud) CurrentNodeName(hostname string) (string, error) { + return hostname, nil +} + +// NodeAddresses is a test-spy implementation of Instances.NodeAddresses. +// It adds an entry "node-addresses" into the internal method call record. +func (f *FakeCloud) NodeAddresses(instance string) ([]api.NodeAddress, error) { + f.addCall("node-addresses") + return f.Addresses, f.Err +} + +// ExternalID is a test-spy implementation of Instances.ExternalID. +// It adds an entry "external-id" into the internal method call record. +// It returns an external id to the mapped instance name, if not found, it will return "ext-{instance}" +func (f *FakeCloud) ExternalID(instance string) (string, error) { + f.addCall("external-id") + return f.ExtID[instance], f.Err +} + +// InstanceID returns the cloud provider ID of the specified instance. +func (f *FakeCloud) InstanceID(instance string) (string, error) { + f.addCall("instance-id") + return f.ExtID[instance], nil +} + +// InstanceType returns the type of the specified instance. +func (f *FakeCloud) InstanceType(instance string) (string, error) { + f.addCall("instance-type") + return f.InstanceTypes[instance], nil +} + +// List is a test-spy implementation of Instances.List. +// It adds an entry "list" into the internal method call record. +func (f *FakeCloud) List(filter string) ([]string, error) { + f.addCall("list") + result := []string{} + for _, machine := range f.Machines { + if match, _ := regexp.MatchString(filter, machine); match { + result = append(result, machine) + } + } + return result, f.Err +} + +func (f *FakeCloud) GetZone() (cloudprovider.Zone, error) { + f.addCall("get-zone") + return f.Zone, f.Err +} + +func (f *FakeCloud) ListRoutes(clusterName string) ([]*cloudprovider.Route, error) { + f.Lock.Lock() + defer f.Lock.Unlock() + f.addCall("list-routes") + var routes []*cloudprovider.Route + for _, fakeRoute := range f.RouteMap { + if clusterName == fakeRoute.ClusterName { + routeCopy := fakeRoute.Route + routes = append(routes, &routeCopy) + } + } + return routes, f.Err +} + +func (f *FakeCloud) CreateRoute(clusterName string, nameHint string, route *cloudprovider.Route) error { + f.Lock.Lock() + defer f.Lock.Unlock() + f.addCall("create-route") + name := clusterName + "-" + nameHint + if _, exists := f.RouteMap[name]; exists { + f.Err = fmt.Errorf("route %q already exists", name) + return f.Err + } + fakeRoute := FakeRoute{} + fakeRoute.Route = *route + fakeRoute.Route.Name = name + fakeRoute.ClusterName = clusterName + f.RouteMap[name] = &fakeRoute + return nil +} + +func (f *FakeCloud) DeleteRoute(clusterName string, route *cloudprovider.Route) error { + f.Lock.Lock() + defer f.Lock.Unlock() + f.addCall("delete-route") + name := route.Name + if _, exists := f.RouteMap[name]; !exists { + f.Err = fmt.Errorf("no route found with name %q", name) + return f.Err + } + delete(f.RouteMap, name) + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/doc.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/doc.go new file mode 100644 index 000000000..93acc5a31 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package gce is an implementation of Interface, LoadBalancer +// and Instances for Google Compute Engine. +package gce diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/gce.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/gce.go new file mode 100644 index 000000000..c23da8ee3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/gce.go @@ -0,0 +1,2440 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gce + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "path" + "regexp" + "sort" + "strconv" + "strings" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/service" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/types" + utilerrors "k8s.io/kubernetes/pkg/util/errors" + "k8s.io/kubernetes/pkg/util/flowcontrol" + netsets "k8s.io/kubernetes/pkg/util/net/sets" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/wait" + + "github.com/golang/glog" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + compute "google.golang.org/api/compute/v1" + container "google.golang.org/api/container/v1" + "google.golang.org/api/googleapi" + "google.golang.org/cloud/compute/metadata" + "gopkg.in/gcfg.v1" +) + +const ( + ProviderName = "gce" + + k8sNodeRouteTag = "k8s-node-route" + + // AffinityTypeNone - no session affinity. + gceAffinityTypeNone = "NONE" + // AffinityTypeClientIP - affinity based on Client IP. + gceAffinityTypeClientIP = "CLIENT_IP" + // AffinityTypeClientIPProto - affinity based on Client IP and port. + gceAffinityTypeClientIPProto = "CLIENT_IP_PROTO" + + operationPollInterval = 3 * time.Second + operationPollTimeoutDuration = 30 * time.Minute + + // Each page can have 500 results, but we cap how many pages + // are iterated through to prevent infinite loops if the API + // were to continuously return a nextPageToken. + maxPages = 25 +) + +// GCECloud is an implementation of Interface, LoadBalancer and Instances for Google Compute Engine. +type GCECloud struct { + service *compute.Service + containerService *container.Service + projectID string + region string + localZone string // The zone in which we are running + managedZones []string // List of zones we are spanning (for Ubernetes-Lite, primarily when running on master) + networkURL string + useMetadataServer bool + operationPollRateLimiter flowcontrol.RateLimiter +} + +type Config struct { + Global struct { + TokenURL string `gcfg:"token-url"` + TokenBody string `gcfg:"token-body"` + ProjectID string `gcfg:"project-id"` + NetworkName string `gcfg:"network-name"` + Multizone bool `gcfg:"multizone"` + } +} + +func init() { + cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) { return newGCECloud(config) }) +} + +// Raw access to the underlying GCE service, probably should only be used for e2e tests +func (g *GCECloud) GetComputeService() *compute.Service { + return g.service +} + +func getProjectAndZone() (string, string, error) { + result, err := metadata.Get("instance/zone") + if err != nil { + return "", "", err + } + parts := strings.Split(result, "/") + if len(parts) != 4 { + return "", "", fmt.Errorf("unexpected response: %s", result) + } + zone := parts[3] + projectID, err := metadata.ProjectID() + if err != nil { + return "", "", err + } + return projectID, zone, nil +} + +func getInstanceIDViaMetadata() (string, error) { + result, err := metadata.Get("instance/hostname") + if err != nil { + return "", err + } + parts := strings.Split(result, ".") + if len(parts) == 0 { + return "", fmt.Errorf("unexpected response: %s", result) + } + return parts[0], nil +} + +func getCurrentExternalIDViaMetadata() (string, error) { + externalID, err := metadata.Get("instance/id") + if err != nil { + return "", fmt.Errorf("couldn't get external ID: %v", err) + } + return externalID, nil +} + +func getCurrentMachineTypeViaMetadata() (string, error) { + mType, err := metadata.Get("instance/machine-type") + if err != nil { + return "", fmt.Errorf("couldn't get machine type: %v", err) + } + parts := strings.Split(mType, "/") + if len(parts) != 4 { + return "", fmt.Errorf("unexpected response for machine type: %s", mType) + } + + return parts[3], nil +} + +func getNetworkNameViaMetadata() (string, error) { + result, err := metadata.Get("instance/network-interfaces/0/network") + if err != nil { + return "", err + } + parts := strings.Split(result, "/") + if len(parts) != 4 { + return "", fmt.Errorf("unexpected response: %s", result) + } + return parts[3], nil +} + +func getNetworkNameViaAPICall(svc *compute.Service, projectID string) (string, error) { + // TODO: use PageToken to list all not just the first 500 + networkList, err := svc.Networks.List(projectID).Do() + if err != nil { + return "", err + } + + if networkList == nil || len(networkList.Items) <= 0 { + return "", fmt.Errorf("GCE Network List call returned no networks for project %q.", projectID) + } + + return networkList.Items[0].Name, nil +} + +func getZonesForRegion(svc *compute.Service, projectID, region string) ([]string, error) { + // TODO: use PageToken to list all not just the first 500 + listCall := svc.Zones.List(projectID) + + // Filtering by region doesn't seem to work + // (tested in https://cloud.google.com/compute/docs/reference/latest/zones/list) + // listCall = listCall.Filter("region eq " + region) + + res, err := listCall.Do() + if err != nil { + return nil, fmt.Errorf("unexpected response listing zones: %v", err) + } + zones := []string{} + for _, zone := range res.Items { + regionName := lastComponent(zone.Region) + if regionName == region { + zones = append(zones, zone.Name) + } + } + return zones, nil +} + +// newGCECloud creates a new instance of GCECloud. +func newGCECloud(config io.Reader) (*GCECloud, error) { + projectID, zone, err := getProjectAndZone() + if err != nil { + return nil, err + } + + region, err := GetGCERegion(zone) + if err != nil { + return nil, err + } + + networkName, err := getNetworkNameViaMetadata() + if err != nil { + return nil, err + } + networkURL := gceNetworkURL(projectID, networkName) + + // By default, Kubernetes clusters only run against one zone + managedZones := []string{zone} + + tokenSource := google.ComputeTokenSource("") + if config != nil { + var cfg Config + if err := gcfg.ReadInto(&cfg, config); err != nil { + glog.Errorf("Couldn't read config: %v", err) + return nil, err + } + if cfg.Global.ProjectID != "" { + projectID = cfg.Global.ProjectID + } + if cfg.Global.NetworkName != "" { + if strings.Contains(cfg.Global.NetworkName, "/") { + networkURL = cfg.Global.NetworkName + } else { + networkURL = gceNetworkURL(cfg.Global.ProjectID, cfg.Global.NetworkName) + } + } + if cfg.Global.TokenURL != "" { + tokenSource = newAltTokenSource(cfg.Global.TokenURL, cfg.Global.TokenBody) + } + if cfg.Global.Multizone { + managedZones = nil // Use all zones in region + } + } + + return CreateGCECloud(projectID, region, zone, managedZones, networkURL, tokenSource, true /* useMetadataServer */) +} + +// Creates a GCECloud object using the specified parameters. +// If no networkUrl is specified, loads networkName via rest call. +// If no tokenSource is specified, uses oauth2.DefaultTokenSource. +// If managedZones is nil / empty all zones in the region will be managed. +func CreateGCECloud(projectID, region, zone string, managedZones []string, networkURL string, tokenSource oauth2.TokenSource, useMetadataServer bool) (*GCECloud, error) { + if tokenSource == nil { + var err error + tokenSource, err = google.DefaultTokenSource( + oauth2.NoContext, + compute.CloudPlatformScope, + compute.ComputeScope) + glog.Infof("Using DefaultTokenSource %#v", tokenSource) + if err != nil { + return nil, err + } + } else { + glog.Infof("Using existing Token Source %#v", tokenSource) + } + + client := oauth2.NewClient(oauth2.NoContext, tokenSource) + svc, err := compute.New(client) + if err != nil { + return nil, err + } + + containerSvc, err := container.New(client) + if err != nil { + return nil, err + } + + if networkURL == "" { + networkName, err := getNetworkNameViaAPICall(svc, projectID) + if err != nil { + return nil, err + } + networkURL = gceNetworkURL(projectID, networkName) + } + + if len(managedZones) == 0 { + managedZones, err = getZonesForRegion(svc, projectID, region) + if err != nil { + return nil, err + } + } + if len(managedZones) != 1 { + glog.Infof("managing multiple zones: %v", managedZones) + } + + operationPollRateLimiter := flowcontrol.NewTokenBucketRateLimiter(10, 100) // 10 qps, 100 bucket size. + + return &GCECloud{ + service: svc, + containerService: containerSvc, + projectID: projectID, + region: region, + localZone: zone, + managedZones: managedZones, + networkURL: networkURL, + useMetadataServer: useMetadataServer, + operationPollRateLimiter: operationPollRateLimiter, + }, nil +} + +func (gce *GCECloud) Clusters() (cloudprovider.Clusters, bool) { + return gce, true +} + +// ProviderName returns the cloud provider ID. +func (gce *GCECloud) ProviderName() string { + return ProviderName +} + +// Known-useless DNS search path. +var uselessDNSSearchRE = regexp.MustCompile(`^[0-9]+.google.internal.$`) + +// ScrubDNS filters DNS settings for pods. +func (gce *GCECloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { + // GCE has too many search paths by default. Filter the ones we know are useless. + for _, s := range searches { + if !uselessDNSSearchRE.MatchString(s) { + srchOut = append(srchOut, s) + } + } + return nameservers, srchOut +} + +// LoadBalancer returns an implementation of LoadBalancer for Google Compute Engine. +func (gce *GCECloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) { + return gce, true +} + +// Instances returns an implementation of Instances for Google Compute Engine. +func (gce *GCECloud) Instances() (cloudprovider.Instances, bool) { + return gce, true +} + +// Zones returns an implementation of Zones for Google Compute Engine. +func (gce *GCECloud) Zones() (cloudprovider.Zones, bool) { + return gce, true +} + +// Routes returns an implementation of Routes for Google Compute Engine. +func (gce *GCECloud) Routes() (cloudprovider.Routes, bool) { + return gce, true +} + +func makeHostURL(projectID, zone, host string) string { + host = canonicalizeInstanceName(host) + return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/zones/%s/instances/%s", + projectID, zone, host) +} + +func (h *gceInstance) makeComparableHostPath() string { + return fmt.Sprintf("/zones/%s/instances/%s", h.Zone, h.Name) +} + +func hostURLToComparablePath(hostURL string) string { + idx := strings.Index(hostURL, "/zones/") + if idx < 0 { + return "" + } + return hostURL[idx:] +} + +func (gce *GCECloud) targetPoolURL(name, region string) string { + return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/regions/%s/targetPools/%s", gce.projectID, region, name) +} + +func (gce *GCECloud) waitForOp(op *compute.Operation, getOperation func(operationName string) (*compute.Operation, error)) error { + if op == nil { + return fmt.Errorf("operation must not be nil") + } + + if opIsDone(op) { + return getErrorFromOp(op) + } + + opName := op.Name + return wait.Poll(operationPollInterval, operationPollTimeoutDuration, func() (bool, error) { + start := time.Now() + gce.operationPollRateLimiter.Accept() + duration := time.Now().Sub(start) + if duration > 5*time.Second { + glog.Infof("pollOperation: waited %v for %v", duration, opName) + } + pollOp, err := getOperation(opName) + if err != nil { + glog.Warningf("GCE poll operation %s failed: pollOp: [%v] err: [%v] getErrorFromOp: [%v]", opName, pollOp, err, getErrorFromOp(pollOp)) + } + return opIsDone(pollOp), getErrorFromOp(pollOp) + }) +} + +func opIsDone(op *compute.Operation) bool { + return op != nil && op.Status == "DONE" +} + +func getErrorFromOp(op *compute.Operation) error { + if op != nil && op.Error != nil && len(op.Error.Errors) > 0 { + err := &googleapi.Error{ + Code: int(op.HttpErrorStatusCode), + Message: op.Error.Errors[0].Message, + } + glog.Errorf("GCE operation failed: %v", err) + return err + } + + return nil +} + +func (gce *GCECloud) waitForGlobalOp(op *compute.Operation) error { + return gce.waitForOp(op, func(operationName string) (*compute.Operation, error) { + return gce.service.GlobalOperations.Get(gce.projectID, operationName).Do() + }) +} + +func (gce *GCECloud) waitForRegionOp(op *compute.Operation, region string) error { + return gce.waitForOp(op, func(operationName string) (*compute.Operation, error) { + return gce.service.RegionOperations.Get(gce.projectID, region, operationName).Do() + }) +} + +func (gce *GCECloud) waitForZoneOp(op *compute.Operation, zone string) error { + return gce.waitForOp(op, func(operationName string) (*compute.Operation, error) { + return gce.service.ZoneOperations.Get(gce.projectID, zone, operationName).Do() + }) +} + +// GetLoadBalancer is an implementation of LoadBalancer.GetLoadBalancer +func (gce *GCECloud) GetLoadBalancer(service *api.Service) (*api.LoadBalancerStatus, bool, error) { + loadBalancerName := cloudprovider.GetLoadBalancerName(service) + fwd, err := gce.service.ForwardingRules.Get(gce.projectID, gce.region, loadBalancerName).Do() + if err == nil { + status := &api.LoadBalancerStatus{} + status.Ingress = []api.LoadBalancerIngress{{IP: fwd.IPAddress}} + + return status, true, nil + } + if isHTTPErrorCode(err, http.StatusNotFound) { + return nil, false, nil + } + return nil, false, err +} + +func isHTTPErrorCode(err error, code int) bool { + apiErr, ok := err.(*googleapi.Error) + return ok && apiErr.Code == code +} + +// EnsureLoadBalancer is an implementation of LoadBalancer.EnsureLoadBalancer. +// Our load balancers in GCE consist of four separate GCE resources - a static +// IP address, a firewall rule, a target pool, and a forwarding rule. This +// function has to manage all of them. +// Due to an interesting series of design decisions, this handles both creating +// new load balancers and updating existing load balancers, recognizing when +// each is needed. +func (gce *GCECloud) EnsureLoadBalancer(apiService *api.Service, hostNames []string, annotations map[string]string) (*api.LoadBalancerStatus, error) { + if len(hostNames) == 0 { + return nil, fmt.Errorf("Cannot EnsureLoadBalancer() with no hosts") + } + + hosts, err := gce.getInstancesByNames(hostNames) + if err != nil { + return nil, err + } + + loadBalancerName := cloudprovider.GetLoadBalancerName(apiService) + loadBalancerIP := apiService.Spec.LoadBalancerIP + ports := apiService.Spec.Ports + portStr := []string{} + for _, p := range apiService.Spec.Ports { + portStr = append(portStr, fmt.Sprintf("%s/%d", p.Protocol, p.Port)) + } + + affinityType := apiService.Spec.SessionAffinity + + serviceName := types.NamespacedName{Namespace: apiService.Namespace, Name: apiService.Name} + glog.V(2).Infof("EnsureLoadBalancer(%v, %v, %v, %v, %v, %v, %v)", loadBalancerName, gce.region, loadBalancerIP, portStr, hosts, serviceName, annotations) + + // Check if the forwarding rule exists, and if so, what its IP is. + fwdRuleExists, fwdRuleNeedsUpdate, fwdRuleIP, err := gce.forwardingRuleNeedsUpdate(loadBalancerName, gce.region, loadBalancerIP, ports) + if err != nil { + return nil, err + } + + // Make sure we know which IP address will be used and have properly reserved + // it as static before moving forward with the rest of our operations. + // + // We use static IP addresses when updating a load balancer to ensure that we + // can replace the load balancer's other components without changing the + // address its service is reachable on. We do it this way rather than always + // keeping the static IP around even though this is more complicated because + // it makes it less likely that we'll run into quota issues. Only 7 static + // IP addresses are allowed per region by default. + // + // We could let an IP be allocated for us when the forwarding rule is created, + // but we need the IP to set up the firewall rule, and we want to keep the + // forwarding rule creation as the last thing that needs to be done in this + // function in order to maintain the invariant that "if the forwarding rule + // exists, the LB has been fully created". + ipAddress := "" + + // Through this process we try to keep track of whether it is safe to + // release the IP that was allocated. If the user specifically asked for + // an IP, we assume they are managing it themselves. Otherwise, we will + // release the IP in case of early-terminating failure or upon successful + // creating of the LB. + isUserOwnedIP := false // if this is set, we never release the IP + isSafeToReleaseIP := false + defer func() { + if isUserOwnedIP { + return + } + if isSafeToReleaseIP { + if err := gce.deleteStaticIP(loadBalancerName, gce.region); err != nil { + glog.Errorf("failed to release static IP %s for load balancer (%v(%v), %v): %v", ipAddress, loadBalancerName, serviceName, gce.region, err) + } + glog.V(2).Infof("EnsureLoadBalancer(%v(%v)): released static IP %s", loadBalancerName, serviceName, ipAddress) + } else { + glog.Warningf("orphaning static IP %s during update of load balancer (%v(%v), %v): %v", ipAddress, loadBalancerName, serviceName, gce.region, err) + } + }() + + if loadBalancerIP != "" { + // If a specific IP address has been requested, we have to respect the + // user's request and use that IP. If the forwarding rule was already using + // a different IP, it will be harmlessly abandoned because it was only an + // ephemeral IP (or it was a different static IP owned by the user, in which + // case we shouldn't delete it anyway). + if isStatic, err := gce.projectOwnsStaticIP(loadBalancerName, gce.region, loadBalancerIP); err != nil { + return nil, fmt.Errorf("failed to test if this GCE project owns the static IP %s: %v", loadBalancerIP, err) + } else if isStatic { + // The requested IP is a static IP, owned and managed by the user. + isUserOwnedIP = true + isSafeToReleaseIP = false + ipAddress = loadBalancerIP + glog.V(4).Infof("EnsureLoadBalancer(%v(%v)): using user-provided static IP %s", loadBalancerName, serviceName, ipAddress) + } else if loadBalancerIP == fwdRuleIP { + // The requested IP is not a static IP, but is currently assigned + // to this forwarding rule, so we can keep it. + isUserOwnedIP = false + isSafeToReleaseIP = true + ipAddress, _, err = gce.ensureStaticIP(loadBalancerName, serviceName.String(), gce.region, fwdRuleIP) + if err != nil { + return nil, fmt.Errorf("failed to ensure static IP %s: %v", fwdRuleIP, err) + } + glog.V(4).Infof("EnsureLoadBalancer(%v(%v)): using user-provided non-static IP %s", loadBalancerName, serviceName, ipAddress) + } else { + // The requested IP is not static and it is not assigned to the + // current forwarding rule. It might be attached to a different + // rule or it might not be part of this project at all. Either + // way, we can't use it. + return nil, fmt.Errorf("requested ip %s is neither static nor assigned to LB %s(%v): %v", loadBalancerIP, loadBalancerName, serviceName, err) + } + } else { + // The user did not request a specific IP. + isUserOwnedIP = false + + // This will either allocate a new static IP if the forwarding rule didn't + // already have an IP, or it will promote the forwarding rule's current + // IP from ephemeral to static, or it will just get the IP if it is + // already static. + existed := false + ipAddress, existed, err = gce.ensureStaticIP(loadBalancerName, serviceName.String(), gce.region, fwdRuleIP) + if err != nil { + return nil, fmt.Errorf("failed to ensure static IP %s: %v", fwdRuleIP, err) + } + if existed { + // If the IP was not specifically requested by the user, but it + // already existed, it seems to be a failed update cycle. We can + // use this IP and try to run through the process again, but we + // should not release the IP unless it is explicitly flagged as OK. + isSafeToReleaseIP = false + glog.V(4).Infof("EnsureLoadBalancer(%v(%v)): adopting static IP %s", loadBalancerName, serviceName, ipAddress) + } else { + // For total clarity. The IP did not pre-exist and the user did + // not ask for a particular one, so we can release the IP in case + // of failure or success. + isSafeToReleaseIP = true + glog.V(4).Infof("EnsureLoadBalancer(%v(%v)): allocated static IP %s", loadBalancerName, serviceName, ipAddress) + } + } + + // Deal with the firewall next. The reason we do this here rather than last + // is because the forwarding rule is used as the indicator that the load + // balancer is fully created - it's what getLoadBalancer checks for. + // Check if user specified the allow source range + sourceRanges, err := service.GetLoadBalancerSourceRanges(annotations) + if err != nil { + return nil, err + } + + firewallExists, firewallNeedsUpdate, err := gce.firewallNeedsUpdate(loadBalancerName, serviceName.String(), gce.region, ipAddress, ports, sourceRanges) + if err != nil { + return nil, err + } + + if firewallNeedsUpdate { + desc := makeFirewallDescription(serviceName.String(), ipAddress) + // Unlike forwarding rules and target pools, firewalls can be updated + // without needing to be deleted and recreated. + if firewallExists { + if err := gce.updateFirewall(loadBalancerName, gce.region, desc, sourceRanges, ports, hosts); err != nil { + return nil, err + } + glog.V(4).Infof("EnsureLoadBalancer(%v(%v)): updated firewall", loadBalancerName, serviceName) + } else { + if err := gce.createFirewall(loadBalancerName, gce.region, desc, sourceRanges, ports, hosts); err != nil { + return nil, err + } + glog.V(4).Infof("EnsureLoadBalancer(%v(%v)): created firewall", loadBalancerName, serviceName) + } + } + + tpExists, tpNeedsUpdate, err := gce.targetPoolNeedsUpdate(loadBalancerName, gce.region, affinityType) + if err != nil { + return nil, err + } + + // Now we get to some slightly more interesting logic. + // First, neither target pools nor forwarding rules can be updated in place - + // they have to be deleted and recreated. + // Second, forwarding rules are layered on top of target pools in that you + // can't delete a target pool that's currently in use by a forwarding rule. + // Thus, we have to tear down the forwarding rule if either it or the target + // pool needs to be updated. + if fwdRuleExists && (fwdRuleNeedsUpdate || tpNeedsUpdate) { + // Begin critical section. If we have to delete the forwarding rule, + // and something should fail before we recreate it, don't release the + // IP. That way we can come back to it later. + isSafeToReleaseIP = false + if err := gce.deleteForwardingRule(loadBalancerName, gce.region); err != nil { + return nil, fmt.Errorf("failed to delete existing forwarding rule %s for load balancer update: %v", loadBalancerName, err) + } + glog.V(4).Infof("EnsureLoadBalancer(%v(%v)): deleted forwarding rule", loadBalancerName, serviceName) + } + if tpExists && tpNeedsUpdate { + if err := gce.deleteTargetPool(loadBalancerName, gce.region); err != nil { + return nil, fmt.Errorf("failed to delete existing target pool %s for load balancer update: %v", loadBalancerName, err) + } + glog.V(4).Infof("EnsureLoadBalancer(%v(%v)): deleted target pool", loadBalancerName, serviceName) + } + + // Once we've deleted the resources (if necessary), build them back up (or for + // the first time if they're new). + if tpNeedsUpdate { + if err := gce.createTargetPool(loadBalancerName, serviceName.String(), gce.region, hosts, affinityType); err != nil { + return nil, fmt.Errorf("failed to create target pool %s: %v", loadBalancerName, err) + } + glog.V(4).Infof("EnsureLoadBalancer(%v(%v)): created target pool", loadBalancerName, serviceName) + } + if tpNeedsUpdate || fwdRuleNeedsUpdate { + if err := gce.createForwardingRule(loadBalancerName, serviceName.String(), gce.region, ipAddress, ports); err != nil { + return nil, fmt.Errorf("failed to create forwarding rule %s: %v", loadBalancerName, err) + } + // End critical section. It is safe to release the static IP (which + // just demotes it to ephemeral) now that it is attached. In the case + // of a user-requested IP, the "is user-owned" flag will be set, + // preventing it from actually being released. + isSafeToReleaseIP = true + glog.V(4).Infof("EnsureLoadBalancer(%v(%v)): created forwarding rule, IP %s", loadBalancerName, serviceName, ipAddress) + } + + status := &api.LoadBalancerStatus{} + status.Ingress = []api.LoadBalancerIngress{{IP: ipAddress}} + return status, nil +} + +// Passing nil for requested IP is perfectly fine - it just means that no specific +// IP is being requested. +// Returns whether the forwarding rule exists, whether it needs to be updated, +// what its IP address is (if it exists), and any error we encountered. +func (gce *GCECloud) forwardingRuleNeedsUpdate(name, region string, loadBalancerIP string, ports []api.ServicePort) (exists bool, needsUpdate bool, ipAddress string, err error) { + fwd, err := gce.service.ForwardingRules.Get(gce.projectID, region, name).Do() + if err != nil { + if isHTTPErrorCode(err, http.StatusNotFound) { + return false, true, "", nil + } + return false, false, "", fmt.Errorf("error getting load balancer's forwarding rule: %v", err) + } + if loadBalancerIP != fwd.IPAddress { + return true, true, fwd.IPAddress, nil + } + portRange, err := loadBalancerPortRange(ports) + if err != nil { + return false, false, "", err + } + if portRange != fwd.PortRange { + return true, true, fwd.IPAddress, nil + } + // The service controller verified all the protocols match on the ports, just check the first one + if string(ports[0].Protocol) != fwd.IPProtocol { + return true, true, fwd.IPAddress, nil + } + + return true, false, fwd.IPAddress, nil +} + +func loadBalancerPortRange(ports []api.ServicePort) (string, error) { + if len(ports) == 0 { + return "", fmt.Errorf("no ports specified for GCE load balancer") + } + + // The service controller verified all the protocols match on the ports, just check and use the first one + if ports[0].Protocol != api.ProtocolTCP && ports[0].Protocol != api.ProtocolUDP { + return "", fmt.Errorf("Invalid protocol %s, only TCP and UDP are supported", string(ports[0].Protocol)) + } + + minPort := 65536 + maxPort := 0 + for i := range ports { + if ports[i].Port < minPort { + minPort = ports[i].Port + } + if ports[i].Port > maxPort { + maxPort = ports[i].Port + } + } + return fmt.Sprintf("%d-%d", minPort, maxPort), nil +} + +// Doesn't check whether the hosts have changed, since host updating is handled +// separately. +func (gce *GCECloud) targetPoolNeedsUpdate(name, region string, affinityType api.ServiceAffinity) (exists bool, needsUpdate bool, err error) { + tp, err := gce.service.TargetPools.Get(gce.projectID, region, name).Do() + if err != nil { + if isHTTPErrorCode(err, http.StatusNotFound) { + return false, true, nil + } + return false, false, fmt.Errorf("error getting load balancer's target pool: %v", err) + } + if translateAffinityType(affinityType) != tp.SessionAffinity { + return true, true, nil + } + return true, false, nil +} + +// translate from what K8s supports to what the cloud provider supports for session affinity. +func translateAffinityType(affinityType api.ServiceAffinity) string { + switch affinityType { + case api.ServiceAffinityClientIP: + return gceAffinityTypeClientIP + case api.ServiceAffinityNone: + return gceAffinityTypeNone + default: + glog.Errorf("Unexpected affinity type: %v", affinityType) + return gceAffinityTypeNone + } +} + +func (gce *GCECloud) firewallNeedsUpdate(name, serviceName, region, ipAddress string, ports []api.ServicePort, sourceRanges netsets.IPNet) (exists bool, needsUpdate bool, err error) { + fw, err := gce.service.Firewalls.Get(gce.projectID, makeFirewallName(name)).Do() + if err != nil { + if isHTTPErrorCode(err, http.StatusNotFound) { + return false, true, nil + } + return false, false, fmt.Errorf("error getting load balancer's target pool: %v", err) + } + if fw.Description != makeFirewallDescription(serviceName, ipAddress) { + return true, true, nil + } + if len(fw.Allowed) != 1 || (fw.Allowed[0].IPProtocol != "tcp" && fw.Allowed[0].IPProtocol != "udp") { + return true, true, nil + } + // Make sure the allowed ports match. + allowedPorts := make([]string, len(ports)) + for ix := range ports { + allowedPorts[ix] = strconv.Itoa(ports[ix].Port) + } + if !slicesEqual(allowedPorts, fw.Allowed[0].Ports) { + return true, true, nil + } + // The service controller already verified that the protocol matches on all ports, no need to check. + + actualSourceRanges, err := netsets.ParseIPNets(fw.SourceRanges...) + if err != nil { + // This really shouldn't happen... GCE has returned something unexpected + glog.Warningf("Error parsing firewall SourceRanges: %v", fw.SourceRanges) + // We don't return the error, because we can hopefully recover from this by reconfiguring the firewall + return true, true, nil + } + + if !sourceRanges.Equal(actualSourceRanges) { + return true, true, nil + } + return true, false, nil +} + +func makeFirewallName(name string) string { + return fmt.Sprintf("k8s-fw-%s", name) +} + +func makeFirewallDescription(serviceName, ipAddress string) string { + return fmt.Sprintf(`{"kubernetes.io/service-ip":"%s", "kubernetes.io/service-name":"%s"}`, + ipAddress, serviceName) +} + +func slicesEqual(x, y []string) bool { + if len(x) != len(y) { + return false + } + sort.Strings(x) + sort.Strings(y) + for i := range x { + if x[i] != y[i] { + return false + } + } + return true +} + +func (gce *GCECloud) createForwardingRule(name, serviceName, region, ipAddress string, ports []api.ServicePort) error { + portRange, err := loadBalancerPortRange(ports) + if err != nil { + return err + } + req := &compute.ForwardingRule{ + Name: name, + Description: fmt.Sprintf(`{"kubernetes.io/service-name":"%s"}`, serviceName), + IPAddress: ipAddress, + IPProtocol: string(ports[0].Protocol), + PortRange: portRange, + Target: gce.targetPoolURL(name, region), + } + + op, err := gce.service.ForwardingRules.Insert(gce.projectID, region, req).Do() + if err != nil && !isHTTPErrorCode(err, http.StatusConflict) { + return err + } + if op != nil { + err = gce.waitForRegionOp(op, region) + if err != nil && !isHTTPErrorCode(err, http.StatusConflict) { + return err + } + } + return nil +} + +func (gce *GCECloud) createTargetPool(name, serviceName, region string, hosts []*gceInstance, affinityType api.ServiceAffinity) error { + var instances []string + for _, host := range hosts { + instances = append(instances, makeHostURL(gce.projectID, host.Zone, host.Name)) + } + pool := &compute.TargetPool{ + Name: name, + Description: fmt.Sprintf(`{"kubernetes.io/service-name":"%s"}`, serviceName), + Instances: instances, + SessionAffinity: translateAffinityType(affinityType), + } + op, err := gce.service.TargetPools.Insert(gce.projectID, region, pool).Do() + if err != nil && !isHTTPErrorCode(err, http.StatusConflict) { + return err + } + if op != nil { + err = gce.waitForRegionOp(op, region) + if err != nil && !isHTTPErrorCode(err, http.StatusConflict) { + return err + } + } + return nil +} + +func (gce *GCECloud) createFirewall(name, region, desc string, sourceRanges netsets.IPNet, ports []api.ServicePort, hosts []*gceInstance) error { + firewall, err := gce.firewallObject(name, region, desc, sourceRanges, ports, hosts) + if err != nil { + return err + } + op, err := gce.service.Firewalls.Insert(gce.projectID, firewall).Do() + if err != nil && !isHTTPErrorCode(err, http.StatusConflict) { + return err + } + if op != nil { + err = gce.waitForGlobalOp(op) + if err != nil && !isHTTPErrorCode(err, http.StatusConflict) { + return err + } + } + return nil +} + +func (gce *GCECloud) updateFirewall(name, region, desc string, sourceRanges netsets.IPNet, ports []api.ServicePort, hosts []*gceInstance) error { + firewall, err := gce.firewallObject(name, region, desc, sourceRanges, ports, hosts) + if err != nil { + return err + } + op, err := gce.service.Firewalls.Update(gce.projectID, makeFirewallName(name), firewall).Do() + if err != nil && !isHTTPErrorCode(err, http.StatusConflict) { + return err + } + if op != nil { + err = gce.waitForGlobalOp(op) + if err != nil { + return err + } + } + return nil +} + +func (gce *GCECloud) firewallObject(name, region, desc string, sourceRanges netsets.IPNet, ports []api.ServicePort, hosts []*gceInstance) (*compute.Firewall, error) { + allowedPorts := make([]string, len(ports)) + for ix := range ports { + allowedPorts[ix] = strconv.Itoa(ports[ix].Port) + } + hostTags, err := gce.computeHostTags(hosts) + if err != nil { + return nil, err + } + firewall := &compute.Firewall{ + Name: makeFirewallName(name), + Description: desc, + Network: gce.networkURL, + SourceRanges: sourceRanges.StringSlice(), + TargetTags: hostTags, + Allowed: []*compute.FirewallAllowed{ + { + // TODO: Make this more generic. Currently this method is only + // used to create firewall rules for loadbalancers, which have + // exactly one protocol, so we can never end up with a list of + // mixed TCP and UDP ports. It should be possible to use a + // single firewall rule for both a TCP and UDP lb. + IPProtocol: strings.ToLower(string(ports[0].Protocol)), + Ports: allowedPorts, + }, + }, + } + return firewall, nil +} + +// We grab all tags from all instances being added to the pool. +// * The longest tag that is a prefix of the instance name is used +// * If any instance has a prefix tag, all instances must +// * If no instances have a prefix tag, no tags are used +func (gce *GCECloud) computeHostTags(hosts []*gceInstance) ([]string, error) { + // TODO: We could store the tags in gceInstance, so we could have already fetched it + hostNamesByZone := make(map[string][]string) + for _, host := range hosts { + hostNamesByZone[host.Zone] = append(hostNamesByZone[host.Zone], host.Name) + } + + tags := sets.NewString() + + for zone, hostNames := range hostNamesByZone { + pageToken := "" + page := 0 + for ; page == 0 || (pageToken != "" && page < maxPages); page++ { + listCall := gce.service.Instances.List(gce.projectID, zone) + + // Add the filter for hosts + listCall = listCall.Filter("name eq (" + strings.Join(hostNames, "|") + ")") + + // Add the fields we want + listCall = listCall.Fields("items(name,tags)") + + if pageToken != "" { + listCall = listCall.PageToken(pageToken) + } + + res, err := listCall.Do() + if err != nil { + return nil, err + } + pageToken = res.NextPageToken + for _, instance := range res.Items { + longest_tag := "" + for _, tag := range instance.Tags.Items { + if strings.HasPrefix(instance.Name, tag) && len(tag) > len(longest_tag) { + longest_tag = tag + } + } + if len(longest_tag) > 0 { + tags.Insert(longest_tag) + } else if len(tags) > 0 { + return nil, fmt.Errorf("Some, but not all, instances have prefix tags (%s is missing)", instance.Name) + } + } + } + if page >= maxPages { + glog.Errorf("computeHostTags exceeded maxPages=%d for Instances.List: truncating.", maxPages) + } + } + + if len(tags) == 0 { + glog.V(2).Info("No instances had tags, creating rule without target tags") + } + + return tags.List(), nil +} + +func (gce *GCECloud) projectOwnsStaticIP(name, region string, ipAddress string) (bool, error) { + pageToken := "" + page := 0 + for ; page == 0 || (pageToken != "" && page < maxPages); page++ { + listCall := gce.service.Addresses.List(gce.projectID, region) + if pageToken != "" { + listCall = listCall.PageToken(pageToken) + } + addresses, err := listCall.Do() + if err != nil { + return false, fmt.Errorf("failed to list gce IP addresses: %v", err) + } + pageToken = addresses.NextPageToken + for _, addr := range addresses.Items { + if addr.Address == ipAddress { + // This project does own the address, so return success. + return true, nil + } + } + } + if page >= maxPages { + glog.Errorf("projectOwnsStaticIP exceeded maxPages=%d for Addresses.List; truncating.", maxPages) + } + return false, nil +} + +func (gce *GCECloud) ensureStaticIP(name, serviceName, region, existingIP string) (ipAddress string, created bool, err error) { + // If the address doesn't exist, this will create it. + // If the existingIP exists but is ephemeral, this will promote it to static. + // If the address already exists, this will harmlessly return a StatusConflict + // and we'll grab the IP before returning. + existed := false + addressObj := &compute.Address{ + Name: name, + Description: fmt.Sprintf(`{"kubernetes.io/service-name":"%s"}`, serviceName), + } + if existingIP != "" { + addressObj.Address = existingIP + } + op, err := gce.service.Addresses.Insert(gce.projectID, region, addressObj).Do() + if err != nil { + if !isHTTPErrorCode(err, http.StatusConflict) { + return "", false, fmt.Errorf("error creating gce static IP address: %v", err) + } + // StatusConflict == the IP exists already. + existed = true + } + if op != nil { + err := gce.waitForRegionOp(op, region) + if err != nil { + if !isHTTPErrorCode(err, http.StatusConflict) { + return "", false, fmt.Errorf("error waiting for gce static IP address to be created: %v", err) + } + // StatusConflict == the IP exists already. + existed = true + } + } + + // We have to get the address to know which IP was allocated for us. + address, err := gce.service.Addresses.Get(gce.projectID, region, name).Do() + if err != nil { + return "", false, fmt.Errorf("error re-getting gce static IP address: %v", err) + } + return address.Address, existed, nil +} + +// UpdateLoadBalancer is an implementation of LoadBalancer.UpdateLoadBalancer. +func (gce *GCECloud) UpdateLoadBalancer(service *api.Service, hostNames []string) error { + hosts, err := gce.getInstancesByNames(hostNames) + if err != nil { + return err + } + + loadBalancerName := cloudprovider.GetLoadBalancerName(service) + pool, err := gce.service.TargetPools.Get(gce.projectID, gce.region, loadBalancerName).Do() + if err != nil { + return err + } + existing := sets.NewString() + for _, instance := range pool.Instances { + existing.Insert(hostURLToComparablePath(instance)) + } + + var toAdd []*compute.InstanceReference + var toRemove []*compute.InstanceReference + for _, host := range hosts { + link := host.makeComparableHostPath() + if !existing.Has(link) { + toAdd = append(toAdd, &compute.InstanceReference{Instance: link}) + } + existing.Delete(link) + } + for link := range existing { + toRemove = append(toRemove, &compute.InstanceReference{Instance: link}) + } + + if len(toAdd) > 0 { + add := &compute.TargetPoolsAddInstanceRequest{Instances: toAdd} + op, err := gce.service.TargetPools.AddInstance(gce.projectID, gce.region, loadBalancerName, add).Do() + if err != nil { + return err + } + if err := gce.waitForRegionOp(op, gce.region); err != nil { + return err + } + } + + if len(toRemove) > 0 { + rm := &compute.TargetPoolsRemoveInstanceRequest{Instances: toRemove} + op, err := gce.service.TargetPools.RemoveInstance(gce.projectID, gce.region, loadBalancerName, rm).Do() + if err != nil { + return err + } + if err := gce.waitForRegionOp(op, gce.region); err != nil { + return err + } + } + + // Try to verify that the correct number of nodes are now in the target pool. + // We've been bitten by a bug here before (#11327) where all nodes were + // accidentally removed and want to make similar problems easier to notice. + updatedPool, err := gce.service.TargetPools.Get(gce.projectID, gce.region, loadBalancerName).Do() + if err != nil { + return err + } + if len(updatedPool.Instances) != len(hosts) { + glog.Errorf("Unexpected number of instances (%d) in target pool %s after updating (expected %d). Instances in updated pool: %s", + len(updatedPool.Instances), loadBalancerName, len(hosts), strings.Join(updatedPool.Instances, ",")) + return fmt.Errorf("Unexpected number of instances (%d) in target pool %s after update (expected %d)", len(updatedPool.Instances), loadBalancerName, len(hosts)) + } + return nil +} + +// EnsureLoadBalancerDeleted is an implementation of LoadBalancer.EnsureLoadBalancerDeleted. +func (gce *GCECloud) EnsureLoadBalancerDeleted(service *api.Service) error { + loadBalancerName := cloudprovider.GetLoadBalancerName(service) + glog.V(2).Infof("EnsureLoadBalancerDeleted(%v, %v, %v, %v)", service.Namespace, service.Name, loadBalancerName, + gce.region) + + errs := utilerrors.AggregateGoroutines( + func() error { return gce.deleteFirewall(loadBalancerName, gce.region) }, + // Even though we don't hold on to static IPs for load balancers, it's + // possible that EnsureLoadBalancer left one around in a failed + // creation/update attempt, so make sure we clean it up here just in case. + func() error { return gce.deleteStaticIP(loadBalancerName, gce.region) }, + func() error { + // The forwarding rule must be deleted before either the target pool can, + // unfortunately, so we have to do these two serially. + if err := gce.deleteForwardingRule(loadBalancerName, gce.region); err != nil { + return err + } + if err := gce.deleteTargetPool(loadBalancerName, gce.region); err != nil { + return err + } + return nil + }, + ) + if errs != nil { + return utilerrors.Flatten(errs) + } + return nil +} + +func (gce *GCECloud) deleteForwardingRule(name, region string) error { + op, err := gce.service.ForwardingRules.Delete(gce.projectID, region, name).Do() + if err != nil && isHTTPErrorCode(err, http.StatusNotFound) { + glog.Infof("Forwarding rule %s already deleted. Continuing to delete other resources.", name) + } else if err != nil { + glog.Warningf("Failed to delete forwarding rule %s: got error %s.", name, err.Error()) + return err + } else { + if err := gce.waitForRegionOp(op, region); err != nil { + glog.Warningf("Failed waiting for forwarding rule %s to be deleted: got error %s.", name, err.Error()) + return err + } + } + return nil +} + +func (gce *GCECloud) deleteTargetPool(name, region string) error { + op, err := gce.service.TargetPools.Delete(gce.projectID, region, name).Do() + if err != nil && isHTTPErrorCode(err, http.StatusNotFound) { + glog.Infof("Target pool %s already deleted. Continuing to delete other resources.", name) + } else if err != nil { + glog.Warningf("Failed to delete target pool %s, got error %s.", name, err.Error()) + return err + } else { + if err := gce.waitForRegionOp(op, region); err != nil { + glog.Warningf("Failed waiting for target pool %s to be deleted: got error %s.", name, err.Error()) + return err + } + } + return nil +} + +func (gce *GCECloud) deleteFirewall(name, region string) error { + fwName := makeFirewallName(name) + op, err := gce.service.Firewalls.Delete(gce.projectID, fwName).Do() + if err != nil && isHTTPErrorCode(err, http.StatusNotFound) { + glog.Infof("Firewall %s already deleted. Continuing to delete other resources.", name) + } else if err != nil { + glog.Warningf("Failed to delete firewall %s, got error %v", fwName, err) + return err + } else { + if err := gce.waitForGlobalOp(op); err != nil { + glog.Warningf("Failed waiting for Firewall %s to be deleted. Got error: %v", fwName, err) + return err + } + } + return nil +} + +func (gce *GCECloud) deleteStaticIP(name, region string) error { + op, err := gce.service.Addresses.Delete(gce.projectID, region, name).Do() + if err != nil && isHTTPErrorCode(err, http.StatusNotFound) { + glog.Infof("Static IP address %s is not reserved", name) + } else if err != nil { + glog.Warningf("Failed to delete static IP address %s, got error %v", name, err) + return err + } else { + if err := gce.waitForRegionOp(op, region); err != nil { + glog.Warningf("Failed waiting for address %s to be deleted, got error: %v", name, err) + return err + } + } + return nil +} + +// Firewall management: These methods are just passthrough to the existing +// internal firewall creation methods used to manage TCPLoadBalancer. + +// GetFirewall returns the Firewall by name. +func (gce *GCECloud) GetFirewall(name string) (*compute.Firewall, error) { + return gce.service.Firewalls.Get(gce.projectID, name).Do() +} + +// CreateFirewall creates the given firewall rule. +func (gce *GCECloud) CreateFirewall(name, desc string, sourceRanges netsets.IPNet, ports []int64, hostNames []string) error { + region, err := GetGCERegion(gce.localZone) + if err != nil { + return err + } + // TODO: This completely breaks modularity in the cloudprovider but the methods + // shared with the TCPLoadBalancer take api.ServicePorts. + svcPorts := []api.ServicePort{} + // TODO: Currently the only consumer of this method is the GCE L7 + // loadbalancer controller, which never needs a protocol other than TCP. + // We should pipe through a mapping of port:protocol and default to TCP + // if UDP ports are required. This means the method signature will change + // forcing downstream clients to refactor interfaces. + for _, p := range ports { + svcPorts = append(svcPorts, api.ServicePort{Port: int(p), Protocol: api.ProtocolTCP}) + } + hosts, err := gce.getInstancesByNames(hostNames) + if err != nil { + return err + } + return gce.createFirewall(name, region, desc, sourceRanges, svcPorts, hosts) +} + +// DeleteFirewall deletes the given firewall rule. +func (gce *GCECloud) DeleteFirewall(name string) error { + region, err := GetGCERegion(gce.localZone) + if err != nil { + return err + } + return gce.deleteFirewall(name, region) +} + +// UpdateFirewall applies the given firewall rule as an update to an existing +// firewall rule with the same name. +func (gce *GCECloud) UpdateFirewall(name, desc string, sourceRanges netsets.IPNet, ports []int64, hostNames []string) error { + region, err := GetGCERegion(gce.localZone) + if err != nil { + return err + } + // TODO: This completely breaks modularity in the cloudprovider but the methods + // shared with the TCPLoadBalancer take api.ServicePorts. + svcPorts := []api.ServicePort{} + // TODO: Currently the only consumer of this method is the GCE L7 + // loadbalancer controller, which never needs a protocol other than TCP. + // We should pipe through a mapping of port:protocol and default to TCP + // if UDP ports are required. This means the method signature will change, + // forcing downstream clients to refactor interfaces. + for _, p := range ports { + svcPorts = append(svcPorts, api.ServicePort{Port: int(p), Protocol: api.ProtocolTCP}) + } + hosts, err := gce.getInstancesByNames(hostNames) + if err != nil { + return err + } + return gce.updateFirewall(name, region, desc, sourceRanges, svcPorts, hosts) +} + +// Global static IP management + +// ReserveGlobalStaticIP creates a global static IP. +// Caller is allocated a random IP if they do not specify an ipAddress. If an +// ipAddress is specified, it must belong to the current project, eg: an +// ephemeral IP associated with a global forwarding rule. +func (gce *GCECloud) ReserveGlobalStaticIP(name, ipAddress string) (address *compute.Address, err error) { + op, err := gce.service.GlobalAddresses.Insert(gce.projectID, &compute.Address{Name: name, Address: ipAddress}).Do() + if err != nil { + return nil, err + } + if err := gce.waitForGlobalOp(op); err != nil { + return nil, err + } + // We have to get the address to know which IP was allocated for us. + return gce.service.GlobalAddresses.Get(gce.projectID, name).Do() +} + +// DeleteGlobalStaticIP deletes a global static IP by name. +func (gce *GCECloud) DeleteGlobalStaticIP(name string) error { + op, err := gce.service.GlobalAddresses.Delete(gce.projectID, name).Do() + if err != nil { + return err + } + return gce.waitForGlobalOp(op) +} + +// GetGlobalStaticIP returns the global static IP by name. +func (gce *GCECloud) GetGlobalStaticIP(name string) (address *compute.Address, err error) { + return gce.service.GlobalAddresses.Get(gce.projectID, name).Do() +} + +// UrlMap management + +// GetUrlMap returns the UrlMap by name. +func (gce *GCECloud) GetUrlMap(name string) (*compute.UrlMap, error) { + return gce.service.UrlMaps.Get(gce.projectID, name).Do() +} + +// CreateUrlMap creates an url map, using the given backend service as the default service. +func (gce *GCECloud) CreateUrlMap(backend *compute.BackendService, name string) (*compute.UrlMap, error) { + urlMap := &compute.UrlMap{ + Name: name, + DefaultService: backend.SelfLink, + } + op, err := gce.service.UrlMaps.Insert(gce.projectID, urlMap).Do() + if err != nil { + return nil, err + } + if err = gce.waitForGlobalOp(op); err != nil { + return nil, err + } + return gce.GetUrlMap(name) +} + +// UpdateUrlMap applies the given UrlMap as an update, and returns the new UrlMap. +func (gce *GCECloud) UpdateUrlMap(urlMap *compute.UrlMap) (*compute.UrlMap, error) { + op, err := gce.service.UrlMaps.Update(gce.projectID, urlMap.Name, urlMap).Do() + if err != nil { + return nil, err + } + if err = gce.waitForGlobalOp(op); err != nil { + return nil, err + } + return gce.service.UrlMaps.Get(gce.projectID, urlMap.Name).Do() +} + +// DeleteUrlMap deletes a url map by name. +func (gce *GCECloud) DeleteUrlMap(name string) error { + op, err := gce.service.UrlMaps.Delete(gce.projectID, name).Do() + if err != nil { + if isHTTPErrorCode(err, http.StatusNotFound) { + return nil + } + return err + } + return gce.waitForGlobalOp(op) +} + +// ListUrlMaps lists all UrlMaps in the project. +func (gce *GCECloud) ListUrlMaps() (*compute.UrlMapList, error) { + // TODO: use PageToken to list all not just the first 500 + return gce.service.UrlMaps.List(gce.projectID).Do() +} + +// TargetHttpProxy management + +// GetTargetHttpProxy returns the UrlMap by name. +func (gce *GCECloud) GetTargetHttpProxy(name string) (*compute.TargetHttpProxy, error) { + return gce.service.TargetHttpProxies.Get(gce.projectID, name).Do() +} + +// CreateTargetHttpProxy creates and returns a TargetHttpProxy with the given UrlMap. +func (gce *GCECloud) CreateTargetHttpProxy(urlMap *compute.UrlMap, name string) (*compute.TargetHttpProxy, error) { + proxy := &compute.TargetHttpProxy{ + Name: name, + UrlMap: urlMap.SelfLink, + } + op, err := gce.service.TargetHttpProxies.Insert(gce.projectID, proxy).Do() + if err != nil { + return nil, err + } + if err = gce.waitForGlobalOp(op); err != nil { + return nil, err + } + return gce.GetTargetHttpProxy(name) +} + +// SetUrlMapForTargetHttpProxy sets the given UrlMap for the given TargetHttpProxy. +func (gce *GCECloud) SetUrlMapForTargetHttpProxy(proxy *compute.TargetHttpProxy, urlMap *compute.UrlMap) error { + op, err := gce.service.TargetHttpProxies.SetUrlMap(gce.projectID, proxy.Name, &compute.UrlMapReference{UrlMap: urlMap.SelfLink}).Do() + if err != nil { + return err + } + return gce.waitForGlobalOp(op) +} + +// DeleteTargetHttpProxy deletes the TargetHttpProxy by name. +func (gce *GCECloud) DeleteTargetHttpProxy(name string) error { + op, err := gce.service.TargetHttpProxies.Delete(gce.projectID, name).Do() + if err != nil { + if isHTTPErrorCode(err, http.StatusNotFound) { + return nil + } + return err + } + return gce.waitForGlobalOp(op) +} + +// ListTargetHttpProxies lists all TargetHttpProxies in the project. +func (gce *GCECloud) ListTargetHttpProxies() (*compute.TargetHttpProxyList, error) { + // TODO: use PageToken to list all not just the first 500 + return gce.service.TargetHttpProxies.List(gce.projectID).Do() +} + +// TargetHttpsProxy management + +// GetTargetHttpsProxy returns the UrlMap by name. +func (gce *GCECloud) GetTargetHttpsProxy(name string) (*compute.TargetHttpsProxy, error) { + return gce.service.TargetHttpsProxies.Get(gce.projectID, name).Do() +} + +// CreateTargetHttpsProxy creates and returns a TargetHttpsProxy with the given UrlMap and SslCertificate. +func (gce *GCECloud) CreateTargetHttpsProxy(urlMap *compute.UrlMap, sslCert *compute.SslCertificate, name string) (*compute.TargetHttpsProxy, error) { + proxy := &compute.TargetHttpsProxy{ + Name: name, + UrlMap: urlMap.SelfLink, + SslCertificates: []string{sslCert.SelfLink}, + } + op, err := gce.service.TargetHttpsProxies.Insert(gce.projectID, proxy).Do() + if err != nil { + return nil, err + } + if err = gce.waitForGlobalOp(op); err != nil { + return nil, err + } + return gce.GetTargetHttpsProxy(name) +} + +// SetUrlMapForTargetHttpsProxy sets the given UrlMap for the given TargetHttpsProxy. +func (gce *GCECloud) SetUrlMapForTargetHttpsProxy(proxy *compute.TargetHttpsProxy, urlMap *compute.UrlMap) error { + op, err := gce.service.TargetHttpsProxies.SetUrlMap(gce.projectID, proxy.Name, &compute.UrlMapReference{UrlMap: urlMap.SelfLink}).Do() + if err != nil { + return err + } + return gce.waitForGlobalOp(op) +} + +// SetSslCertificateForTargetHttpsProxy sets the given SslCertificate for the given TargetHttpsProxy. +func (gce *GCECloud) SetSslCertificateForTargetHttpsProxy(proxy *compute.TargetHttpsProxy, sslCert *compute.SslCertificate) error { + op, err := gce.service.TargetHttpsProxies.SetSslCertificates(gce.projectID, proxy.Name, &compute.TargetHttpsProxiesSetSslCertificatesRequest{SslCertificates: []string{sslCert.SelfLink}}).Do() + if err != nil { + return err + } + return gce.waitForGlobalOp(op) +} + +// DeleteTargetHttpsProxy deletes the TargetHttpsProxy by name. +func (gce *GCECloud) DeleteTargetHttpsProxy(name string) error { + op, err := gce.service.TargetHttpsProxies.Delete(gce.projectID, name).Do() + if err != nil { + if isHTTPErrorCode(err, http.StatusNotFound) { + return nil + } + return err + } + return gce.waitForGlobalOp(op) +} + +// ListTargetHttpsProxies lists all TargetHttpsProxies in the project. +func (gce *GCECloud) ListTargetHttpsProxies() (*compute.TargetHttpsProxyList, error) { + // TODO: use PageToken to list all not just the first 500 + return gce.service.TargetHttpsProxies.List(gce.projectID).Do() +} + +// SSL Certificate management + +// GetSslCertificate returns the SslCertificate by name. +func (gce *GCECloud) GetSslCertificate(name string) (*compute.SslCertificate, error) { + return gce.service.SslCertificates.Get(gce.projectID, name).Do() +} + +// CreateSslCertificate creates and returns a SslCertificate. +func (gce *GCECloud) CreateSslCertificate(sslCerts *compute.SslCertificate) (*compute.SslCertificate, error) { + op, err := gce.service.SslCertificates.Insert(gce.projectID, sslCerts).Do() + if err != nil { + return nil, err + } + if err = gce.waitForGlobalOp(op); err != nil { + return nil, err + } + return gce.GetSslCertificate(sslCerts.Name) +} + +// DeleteSslCertificate deletes the SslCertificate by name. +func (gce *GCECloud) DeleteSslCertificate(name string) error { + op, err := gce.service.SslCertificates.Delete(gce.projectID, name).Do() + if err != nil { + if isHTTPErrorCode(err, http.StatusNotFound) { + return nil + } + return err + } + return gce.waitForGlobalOp(op) +} + +// ListSslCertificates lists all SslCertificates in the project. +func (gce *GCECloud) ListSslCertificates() (*compute.SslCertificateList, error) { + // TODO: use PageToken to list all not just the first 500 + return gce.service.SslCertificates.List(gce.projectID).Do() +} + +// GlobalForwardingRule management + +// CreateGlobalForwardingRule creates and returns a GlobalForwardingRule that points to the given TargetHttp(s)Proxy. +// targetProxyLink is the SelfLink of a TargetHttp(s)Proxy. +func (gce *GCECloud) CreateGlobalForwardingRule(targetProxyLink, ip, name, portRange string) (*compute.ForwardingRule, error) { + rule := &compute.ForwardingRule{ + Name: name, + IPAddress: ip, + Target: targetProxyLink, + PortRange: portRange, + IPProtocol: "TCP", + } + op, err := gce.service.GlobalForwardingRules.Insert(gce.projectID, rule).Do() + if err != nil { + return nil, err + } + if err = gce.waitForGlobalOp(op); err != nil { + return nil, err + } + return gce.GetGlobalForwardingRule(name) +} + +// SetProxyForGlobalForwardingRule links the given TargetHttp(s)Proxy with the given GlobalForwardingRule. +// targetProxyLink is the SelfLink of a TargetHttp(s)Proxy. +func (gce *GCECloud) SetProxyForGlobalForwardingRule(fw *compute.ForwardingRule, targetProxyLink string) error { + op, err := gce.service.GlobalForwardingRules.SetTarget(gce.projectID, fw.Name, &compute.TargetReference{Target: targetProxyLink}).Do() + if err != nil { + return err + } + return gce.waitForGlobalOp(op) +} + +// DeleteGlobalForwardingRule deletes the GlobalForwardingRule by name. +func (gce *GCECloud) DeleteGlobalForwardingRule(name string) error { + op, err := gce.service.GlobalForwardingRules.Delete(gce.projectID, name).Do() + if err != nil { + if isHTTPErrorCode(err, http.StatusNotFound) { + return nil + } + return err + } + return gce.waitForGlobalOp(op) +} + +// GetGlobalForwardingRule returns the GlobalForwardingRule by name. +func (gce *GCECloud) GetGlobalForwardingRule(name string) (*compute.ForwardingRule, error) { + return gce.service.GlobalForwardingRules.Get(gce.projectID, name).Do() +} + +// ListGlobalForwardingRules lists all GlobalForwardingRules in the project. +func (gce *GCECloud) ListGlobalForwardingRules() (*compute.ForwardingRuleList, error) { + // TODO: use PageToken to list all not just the first 500 + return gce.service.GlobalForwardingRules.List(gce.projectID).Do() +} + +// BackendService Management + +// GetBackendService retrieves a backend by name. +func (gce *GCECloud) GetBackendService(name string) (*compute.BackendService, error) { + return gce.service.BackendServices.Get(gce.projectID, name).Do() +} + +// UpdateBackendService applies the given BackendService as an update to an existing service. +func (gce *GCECloud) UpdateBackendService(bg *compute.BackendService) error { + op, err := gce.service.BackendServices.Update(gce.projectID, bg.Name, bg).Do() + if err != nil { + return err + } + return gce.waitForGlobalOp(op) +} + +// DeleteBackendService deletes the given BackendService by name. +func (gce *GCECloud) DeleteBackendService(name string) error { + op, err := gce.service.BackendServices.Delete(gce.projectID, name).Do() + if err != nil { + if isHTTPErrorCode(err, http.StatusNotFound) { + return nil + } + return err + } + return gce.waitForGlobalOp(op) +} + +// CreateBackendService creates the given BackendService. +func (gce *GCECloud) CreateBackendService(bg *compute.BackendService) error { + op, err := gce.service.BackendServices.Insert(gce.projectID, bg).Do() + if err != nil { + return err + } + return gce.waitForGlobalOp(op) +} + +// ListBackendServices lists all backend services in the project. +func (gce *GCECloud) ListBackendServices() (*compute.BackendServiceList, error) { + // TODO: use PageToken to list all not just the first 500 + return gce.service.BackendServices.List(gce.projectID).Do() +} + +// GetHealth returns the health of the BackendService identified by the given +// name, in the given instanceGroup. The instanceGroupLink is the fully +// qualified self link of an instance group. +func (gce *GCECloud) GetHealth(name string, instanceGroupLink string) (*compute.BackendServiceGroupHealth, error) { + groupRef := &compute.ResourceGroupReference{Group: instanceGroupLink} + return gce.service.BackendServices.GetHealth(gce.projectID, name, groupRef).Do() +} + +// Health Checks + +// GetHttpHealthCheck returns the given HttpHealthCheck by name. +func (gce *GCECloud) GetHttpHealthCheck(name string) (*compute.HttpHealthCheck, error) { + return gce.service.HttpHealthChecks.Get(gce.projectID, name).Do() +} + +// UpdateHttpHealthCheck applies the given HttpHealthCheck as an update. +func (gce *GCECloud) UpdateHttpHealthCheck(hc *compute.HttpHealthCheck) error { + op, err := gce.service.HttpHealthChecks.Update(gce.projectID, hc.Name, hc).Do() + if err != nil { + return err + } + return gce.waitForGlobalOp(op) +} + +// DeleteHttpHealthCheck deletes the given HttpHealthCheck by name. +func (gce *GCECloud) DeleteHttpHealthCheck(name string) error { + op, err := gce.service.HttpHealthChecks.Delete(gce.projectID, name).Do() + if err != nil { + if isHTTPErrorCode(err, http.StatusNotFound) { + return nil + } + return err + } + return gce.waitForGlobalOp(op) +} + +// CreateHttpHealthCheck creates the given HttpHealthCheck. +func (gce *GCECloud) CreateHttpHealthCheck(hc *compute.HttpHealthCheck) error { + op, err := gce.service.HttpHealthChecks.Insert(gce.projectID, hc).Do() + if err != nil { + return err + } + return gce.waitForGlobalOp(op) +} + +// ListHttpHealthCheck lists all HttpHealthChecks in the project. +func (gce *GCECloud) ListHttpHealthChecks() (*compute.HttpHealthCheckList, error) { + // TODO: use PageToken to list all not just the first 500 + return gce.service.HttpHealthChecks.List(gce.projectID).Do() +} + +// InstanceGroup Management + +// CreateInstanceGroup creates an instance group with the given instances. It is the callers responsibility to add named ports. +func (gce *GCECloud) CreateInstanceGroup(name string, zone string) (*compute.InstanceGroup, error) { + op, err := gce.service.InstanceGroups.Insert( + gce.projectID, zone, &compute.InstanceGroup{Name: name}).Do() + if err != nil { + return nil, err + } + if err = gce.waitForZoneOp(op, zone); err != nil { + return nil, err + } + return gce.GetInstanceGroup(name, zone) +} + +// DeleteInstanceGroup deletes an instance group. +func (gce *GCECloud) DeleteInstanceGroup(name string, zone string) error { + op, err := gce.service.InstanceGroups.Delete( + gce.projectID, zone, name).Do() + if err != nil { + return err + } + return gce.waitForZoneOp(op, zone) +} + +// ListInstanceGroups lists all InstanceGroups in the project and zone. +func (gce *GCECloud) ListInstanceGroups(zone string) (*compute.InstanceGroupList, error) { + // TODO: use PageToken to list all not just the first 500 + return gce.service.InstanceGroups.List(gce.projectID, zone).Do() +} + +// ListInstancesInInstanceGroup lists all the instances in a given instance group and state. +func (gce *GCECloud) ListInstancesInInstanceGroup(name string, zone string, state string) (*compute.InstanceGroupsListInstances, error) { + // TODO: use PageToken to list all not just the first 500 + return gce.service.InstanceGroups.ListInstances( + gce.projectID, zone, name, + &compute.InstanceGroupsListInstancesRequest{InstanceState: state}).Do() +} + +// AddInstancesToInstanceGroup adds the given instances to the given instance group. +func (gce *GCECloud) AddInstancesToInstanceGroup(name string, zone string, instanceNames []string) error { + if len(instanceNames) == 0 { + return nil + } + // Adding the same instance twice will result in a 4xx error + instances := []*compute.InstanceReference{} + for _, ins := range instanceNames { + instances = append(instances, &compute.InstanceReference{Instance: makeHostURL(gce.projectID, zone, ins)}) + } + op, err := gce.service.InstanceGroups.AddInstances( + gce.projectID, zone, name, + &compute.InstanceGroupsAddInstancesRequest{ + Instances: instances, + }).Do() + + if err != nil { + return err + } + return gce.waitForZoneOp(op, zone) +} + +// RemoveInstancesFromInstanceGroup removes the given instances from the instance group. +func (gce *GCECloud) RemoveInstancesFromInstanceGroup(name string, zone string, instanceNames []string) error { + if len(instanceNames) == 0 { + return nil + } + instances := []*compute.InstanceReference{} + for _, ins := range instanceNames { + instanceLink := makeHostURL(gce.projectID, zone, ins) + instances = append(instances, &compute.InstanceReference{Instance: instanceLink}) + } + op, err := gce.service.InstanceGroups.RemoveInstances( + gce.projectID, zone, name, + &compute.InstanceGroupsRemoveInstancesRequest{ + Instances: instances, + }).Do() + + if err != nil { + if isHTTPErrorCode(err, http.StatusNotFound) { + return nil + } + return err + } + return gce.waitForZoneOp(op, zone) +} + +// AddPortToInstanceGroup adds a port to the given instance group. +func (gce *GCECloud) AddPortToInstanceGroup(ig *compute.InstanceGroup, port int64) (*compute.NamedPort, error) { + for _, np := range ig.NamedPorts { + if np.Port == port { + glog.V(3).Infof("Instance group %v already has named port %+v", ig.Name, np) + return np, nil + } + } + glog.Infof("Adding port %v to instance group %v with %d ports", port, ig.Name, len(ig.NamedPorts)) + namedPort := compute.NamedPort{Name: fmt.Sprintf("port%v", port), Port: port} + ig.NamedPorts = append(ig.NamedPorts, &namedPort) + + // setNamedPorts is a zonal endpoint, meaning we invoke it by re-creating a URL like: + // {project}/zones/{zone}/instanceGroups/{instanceGroup}/setNamedPorts, so the "zone" + // parameter given to SetNamedPorts must not be the entire zone URL. + zoneURLParts := strings.Split(ig.Zone, "/") + zone := zoneURLParts[len(zoneURLParts)-1] + + op, err := gce.service.InstanceGroups.SetNamedPorts( + gce.projectID, zone, ig.Name, + &compute.InstanceGroupsSetNamedPortsRequest{ + NamedPorts: ig.NamedPorts}).Do() + if err != nil { + return nil, err + } + if err = gce.waitForZoneOp(op, zone); err != nil { + return nil, err + } + return &namedPort, nil +} + +// GetInstanceGroup returns an instance group by name. +func (gce *GCECloud) GetInstanceGroup(name string, zone string) (*compute.InstanceGroup, error) { + return gce.service.InstanceGroups.Get(gce.projectID, zone, name).Do() +} + +// Take a GCE instance 'hostname' and break it down to something that can be fed +// to the GCE API client library. Basically this means reducing 'kubernetes- +// minion-2.c.my-proj.internal' to 'kubernetes-minion-2' if necessary. +func canonicalizeInstanceName(name string) string { + ix := strings.Index(name, ".") + if ix != -1 { + name = name[:ix] + } + return name +} + +// Implementation of Instances.CurrentNodeName +func (gce *GCECloud) CurrentNodeName(hostname string) (string, error) { + return hostname, nil +} + +func (gce *GCECloud) AddSSHKeyToAllInstances(user string, keyData []byte) error { + return wait.Poll(2*time.Second, 30*time.Second, func() (bool, error) { + project, err := gce.service.Projects.Get(gce.projectID).Do() + if err != nil { + glog.Errorf("Could not get project: %v", err) + return false, nil + } + keyString := fmt.Sprintf("%s:%s %s@%s", user, strings.TrimSpace(string(keyData)), user, user) + found := false + for _, item := range project.CommonInstanceMetadata.Items { + if item.Key == "sshKeys" { + if strings.Contains(*item.Value, keyString) { + // We've already added the key + glog.Info("SSHKey already in project metadata") + return true, nil + } + value := *item.Value + "\n" + keyString + item.Value = &value + found = true + break + } + } + if !found { + // This is super unlikely, so log. + glog.Infof("Failed to find sshKeys metadata, creating a new item") + project.CommonInstanceMetadata.Items = append(project.CommonInstanceMetadata.Items, + &compute.MetadataItems{ + Key: "sshKeys", + Value: &keyString, + }) + } + op, err := gce.service.Projects.SetCommonInstanceMetadata(gce.projectID, project.CommonInstanceMetadata).Do() + if err != nil { + glog.Errorf("Could not Set Metadata: %v", err) + return false, nil + } + if err := gce.waitForGlobalOp(op); err != nil { + glog.Errorf("Could not Set Metadata: %v", err) + return false, nil + } + glog.Infof("Successfully added sshKey to project metadata") + return true, nil + }) +} + +// NodeAddresses is an implementation of Instances.NodeAddresses. +func (gce *GCECloud) NodeAddresses(_ string) ([]api.NodeAddress, error) { + internalIP, err := metadata.Get("instance/network-interfaces/0/ip") + if err != nil { + return nil, fmt.Errorf("couldn't get internal IP: %v", err) + } + externalIP, err := metadata.Get("instance/network-interfaces/0/access-configs/0/external-ip") + if err != nil { + return nil, fmt.Errorf("couldn't get external IP: %v", err) + } + return []api.NodeAddress{ + {Type: api.NodeInternalIP, Address: internalIP}, + {Type: api.NodeExternalIP, Address: externalIP}, + }, nil +} + +// isCurrentInstance uses metadata server to check if specified instanceID matches current machine's instanceID +func (gce *GCECloud) isCurrentInstance(instanceID string) bool { + currentInstanceID, err := getInstanceIDViaMetadata() + if err != nil { + // Log and swallow error + glog.Errorf("Failed to fetch instanceID via Metadata: %v", err) + return false + } + + return currentInstanceID == canonicalizeInstanceName(instanceID) +} + +// ExternalID returns the cloud provider ID of the specified instance (deprecated). +func (gce *GCECloud) ExternalID(instance string) (string, error) { + if gce.useMetadataServer { + // Use metadata, if possible, to fetch ID. See issue #12000 + if gce.isCurrentInstance(instance) { + externalInstanceID, err := getCurrentExternalIDViaMetadata() + if err == nil { + return externalInstanceID, nil + } + } + } + + // Fallback to GCE API call if metadata server fails to retrieve ID + inst, err := gce.getInstanceByName(instance) + if err != nil { + return "", err + } + return strconv.FormatUint(inst.ID, 10), nil +} + +// InstanceID returns the cloud provider ID of the specified instance. +func (gce *GCECloud) InstanceID(instanceName string) (string, error) { + if gce.useMetadataServer { + // Use metadata, if possible, to fetch ID. See issue #12000 + if gce.isCurrentInstance(instanceName) { + projectID, zone, err := getProjectAndZone() + if err == nil { + return projectID + "/" + zone + "/" + canonicalizeInstanceName(instanceName), nil + } + } + } + instance, err := gce.getInstanceByName(instanceName) + if err != nil { + return "", err + } + return gce.projectID + "/" + instance.Zone + "/" + instance.Name, nil +} + +// InstanceType returns the type of the specified instance. +func (gce *GCECloud) InstanceType(instanceName string) (string, error) { + if gce.useMetadataServer { + // Use metadata, if possible, to fetch ID. See issue #12000 + if gce.isCurrentInstance(instanceName) { + mType, err := getCurrentMachineTypeViaMetadata() + if err == nil { + return mType, nil + } + } + } + instance, err := gce.getInstanceByName(instanceName) + if err != nil { + return "", err + } + return instance.Type, nil +} + +// List is an implementation of Instances.List. +func (gce *GCECloud) List(filter string) ([]string, error) { + var instances []string + // TODO: Parallelize, although O(zones) so not too bad (N <= 3 typically) + for _, zone := range gce.managedZones { + pageToken := "" + page := 0 + for ; page == 0 || (pageToken != "" && page < maxPages); page++ { + listCall := gce.service.Instances.List(gce.projectID, zone) + if len(filter) > 0 { + listCall = listCall.Filter("name eq " + filter) + } + if pageToken != "" { + listCall = listCall.PageToken(pageToken) + } + res, err := listCall.Do() + if err != nil { + return nil, err + } + pageToken = res.NextPageToken + for _, instance := range res.Items { + instances = append(instances, instance.Name) + } + } + if page >= maxPages { + glog.Errorf("List exceeded maxPages=%d for Instances.List: truncating.", maxPages) + } + } + return instances, nil +} + +func getMetadataValue(metadata *compute.Metadata, key string) (string, bool) { + for _, item := range metadata.Items { + if item.Key == key { + return *item.Value, true + } + } + return "", false +} + +func truncateClusterName(clusterName string) string { + if len(clusterName) > 26 { + return clusterName[:26] + } + return clusterName +} + +func (gce *GCECloud) ListRoutes(clusterName string) ([]*cloudprovider.Route, error) { + var routes []*cloudprovider.Route + pageToken := "" + page := 0 + for ; page == 0 || (pageToken != "" && page < maxPages); page++ { + listCall := gce.service.Routes.List(gce.projectID) + + prefix := truncateClusterName(clusterName) + listCall = listCall.Filter("name eq " + prefix + "-.*") + if pageToken != "" { + listCall = listCall.PageToken(pageToken) + } + res, err := listCall.Do() + if err != nil { + glog.Errorf("Error getting routes from GCE: %v", err) + return nil, err + } + pageToken = res.NextPageToken + for _, r := range res.Items { + if r.Network != gce.networkURL { + continue + } + // Not managed if route description != "k8s-node-route" + if r.Description != k8sNodeRouteTag { + continue + } + // Not managed if route name doesn't start with + if !strings.HasPrefix(r.Name, prefix) { + continue + } + + target := path.Base(r.NextHopInstance) + routes = append(routes, &cloudprovider.Route{Name: r.Name, TargetInstance: target, DestinationCIDR: r.DestRange}) + } + } + if page >= maxPages { + glog.Errorf("ListRoutes exceeded maxPages=%d for Routes.List; truncating.", maxPages) + } + return routes, nil +} + +func gceNetworkURL(project, network string) string { + return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/networks/%s", project, network) +} + +func (gce *GCECloud) CreateRoute(clusterName string, nameHint string, route *cloudprovider.Route) error { + routeName := truncateClusterName(clusterName) + "-" + nameHint + + targetInstance, err := gce.getInstanceByName(route.TargetInstance) + if err != nil { + return err + } + insertOp, err := gce.service.Routes.Insert(gce.projectID, &compute.Route{ + Name: routeName, + DestRange: route.DestinationCIDR, + NextHopInstance: fmt.Sprintf("zones/%s/instances/%s", targetInstance.Zone, targetInstance.Name), + Network: gce.networkURL, + Priority: 1000, + Description: k8sNodeRouteTag, + }).Do() + if err != nil { + return err + } + return gce.waitForGlobalOp(insertOp) +} + +func (gce *GCECloud) DeleteRoute(clusterName string, route *cloudprovider.Route) error { + deleteOp, err := gce.service.Routes.Delete(gce.projectID, route.Name).Do() + if err != nil { + return err + } + return gce.waitForGlobalOp(deleteOp) +} + +func (gce *GCECloud) GetZone() (cloudprovider.Zone, error) { + return cloudprovider.Zone{ + FailureDomain: gce.localZone, + Region: gce.region, + }, nil +} + +// encodeDiskTags encodes requested volume tags into JSON string, as GCE does +// not support tags on GCE PDs and we use Description field as fallback. +func (gce *GCECloud) encodeDiskTags(tags map[string]string) (string, error) { + if len(tags) == 0 { + // No tags -> empty JSON + return "", nil + } + + enc, err := json.Marshal(tags) + if err != nil { + return "", err + } + return string(enc), nil +} + +// CreateDisk creates a new Persistent Disk, with the specified name & size, in +// the specified zone. It stores specified tags endoced in JSON in Description +// field. +func (gce *GCECloud) CreateDisk(name string, zone string, sizeGb int64, tags map[string]string) error { + tagsStr, err := gce.encodeDiskTags(tags) + if err != nil { + return err + } + + diskToCreate := &compute.Disk{ + Name: name, + SizeGb: sizeGb, + Description: tagsStr, + } + + createOp, err := gce.service.Disks.Insert(gce.projectID, zone, diskToCreate).Do() + if err != nil { + return err + } + + return gce.waitForZoneOp(createOp, zone) +} + +func (gce *GCECloud) DeleteDisk(diskToDelete string) error { + disk, err := gce.getDiskByNameUnknownZone(diskToDelete) + if err != nil { + return err + } + + deleteOp, err := gce.service.Disks.Delete(gce.projectID, disk.Zone, disk.Name).Do() + if err != nil { + return err + } + + return gce.waitForZoneOp(deleteOp, disk.Zone) +} + +// Builds the labels that should be automatically added to a PersistentVolume backed by a GCE PD +// Specifically, this builds FailureDomain (zone) and Region labels. +// The PersistentVolumeLabel admission controller calls this and adds the labels when a PV is created. +func (gce *GCECloud) GetAutoLabelsForPD(name string) (map[string]string, error) { + disk, err := gce.getDiskByNameUnknownZone(name) + if err != nil { + return nil, err + } + + zone := disk.Zone + region, err := GetGCERegion(zone) + if err != nil { + return nil, err + } + + if zone == "" || region == "" { + // Unexpected, but sanity-check + return nil, fmt.Errorf("PD did not have zone/region information: %q", disk.Name) + } + + labels := make(map[string]string) + labels[unversioned.LabelZoneFailureDomain] = zone + labels[unversioned.LabelZoneRegion] = region + + return labels, nil +} + +func (gce *GCECloud) AttachDisk(diskName, instanceID string, readOnly bool) error { + instance, err := gce.getInstanceByName(instanceID) + if err != nil { + return fmt.Errorf("error getting instance %q", instanceID) + } + disk, err := gce.getDiskByName(diskName, instance.Zone) + if err != nil { + return err + } + readWrite := "READ_WRITE" + if readOnly { + readWrite = "READ_ONLY" + } + attachedDisk := gce.convertDiskToAttachedDisk(disk, readWrite) + + attachOp, err := gce.service.Instances.AttachDisk(gce.projectID, disk.Zone, instanceID, attachedDisk).Do() + if err != nil { + return err + } + + return gce.waitForZoneOp(attachOp, disk.Zone) +} + +func (gce *GCECloud) DetachDisk(devicePath, instanceID string) error { + inst, err := gce.getInstanceByName(instanceID) + if err != nil { + return fmt.Errorf("error getting instance %q", instanceID) + } + + detachOp, err := gce.service.Instances.DetachDisk(gce.projectID, inst.Zone, inst.Name, devicePath).Do() + if err != nil { + return err + } + + return gce.waitForZoneOp(detachOp, inst.Zone) +} + +func (gce *GCECloud) DiskIsAttached(diskName, instanceID string) (bool, error) { + instance, err := gce.getInstanceByName(instanceID) + if err != nil { + return false, err + } + + for _, disk := range instance.Disks { + if disk.DeviceName == diskName { + // Disk is still attached to node + return true, nil + } + } + + return false, nil +} + +// Returns a gceDisk for the disk, if it is found in the specified zone. +// If not found, returns (nil, nil) +func (gce *GCECloud) findDiskByName(diskName string, zone string) (*gceDisk, error) { + disk, err := gce.service.Disks.Get(gce.projectID, zone, diskName).Do() + if err == nil { + d := &gceDisk{ + Zone: lastComponent(disk.Zone), + Name: disk.Name, + Kind: disk.Kind, + } + return d, nil + } + if !isHTTPErrorCode(err, http.StatusNotFound) { + return nil, err + } + return nil, nil +} + +// Like findDiskByName, but returns an error if the disk is not found +func (gce *GCECloud) getDiskByName(diskName string, zone string) (*gceDisk, error) { + disk, err := gce.findDiskByName(diskName, zone) + if disk == nil && err == nil { + return nil, fmt.Errorf("GCE persistent disk not found: diskName=%q zone=%q", diskName, zone) + } + return disk, err +} + +// Scans all managed zones to return the GCE PD +// Prefer getDiskByName, if the zone can be established +func (gce *GCECloud) getDiskByNameUnknownZone(diskName string) (*gceDisk, error) { + // Note: this is the gotcha right now with GCE PD support: + // disk names are not unique per-region. + // (I can create two volumes with name "myvol" in e.g. us-central1-b & us-central1-f) + // For now, this is simply undefined behvaiour. + // + // In future, we will have to require users to qualify their disk + // "us-central1-a/mydisk". We could do this for them as part of + // admission control, but that might be a little weird (values changing + // on create) + + var found *gceDisk + for _, zone := range gce.managedZones { + disk, err := gce.findDiskByName(diskName, zone) + if err != nil { + return nil, err + } + if found != nil { + return nil, fmt.Errorf("GCE persistent disk name was found in multiple zones: %q", diskName) + } + found = disk + } + if found != nil { + return found, nil + } + return nil, fmt.Errorf("GCE persistent disk not found: %q", diskName) +} + +// GetGCERegion returns region of the gce zone. Zone names +// are of the form: ${region-name}-${ix}. +// For example "us-central1-b" has a region of "us-central1". +// So we look for the last '-' and trim to just before that. +func GetGCERegion(zone string) (string, error) { + ix := strings.LastIndex(zone, "-") + if ix == -1 { + return "", fmt.Errorf("unexpected zone: %s", zone) + } + return zone[:ix], nil +} + +// Converts a Disk resource to an AttachedDisk resource. +func (gce *GCECloud) convertDiskToAttachedDisk(disk *gceDisk, readWrite string) *compute.AttachedDisk { + return &compute.AttachedDisk{ + DeviceName: disk.Name, + Kind: disk.Kind, + Mode: readWrite, + Source: "https://" + path.Join("www.googleapis.com/compute/v1/projects/", gce.projectID, "zones", disk.Zone, "disks", disk.Name), + Type: "PERSISTENT", + } +} + +func (gce *GCECloud) listClustersInZone(zone string) ([]string, error) { + // TODO: use PageToken to list all not just the first 500 + list, err := gce.containerService.Projects.Zones.Clusters.List(gce.projectID, zone).Do() + if err != nil { + return nil, err + } + result := []string{} + for _, cluster := range list.Clusters { + result = append(result, cluster.Name) + } + return result, nil +} + +func (gce *GCECloud) ListClusters() ([]string, error) { + allClusters := []string{} + + for _, zone := range gce.managedZones { + clusters, err := gce.listClustersInZone(zone) + if err != nil { + return nil, err + } + // TODO: Scoping? Do we need to qualify the cluster name? + allClusters = append(allClusters, clusters...) + } + + return allClusters, nil +} + +func (gce *GCECloud) Master(clusterName string) (string, error) { + return "k8s-" + clusterName + "-master.internal", nil +} + +type gceInstance struct { + Zone string + Name string + ID uint64 + Disks []*compute.AttachedDisk + Type string +} + +type gceDisk struct { + Zone string + Name string + Kind string +} + +// Gets the named instances, returning cloudprovider.InstanceNotFound if any instance is not found +func (gce *GCECloud) getInstancesByNames(names []string) ([]*gceInstance, error) { + instances := make(map[string]*gceInstance) + + for _, name := range names { + name = canonicalizeInstanceName(name) + instances[name] = nil + } + + for _, zone := range gce.managedZones { + var remaining []string + for name, instance := range instances { + if instance == nil { + remaining = append(remaining, name) + } + } + + if len(remaining) == 0 { + break + } + + pageToken := "" + page := 0 + for ; page == 0 || (pageToken != "" && page < maxPages); page++ { + listCall := gce.service.Instances.List(gce.projectID, zone) + + // Add the filter for hosts + listCall = listCall.Filter("name eq (" + strings.Join(remaining, "|") + ")") + + listCall = listCall.Fields("items(name,id,disks,machineType)") + if pageToken != "" { + listCall.PageToken(pageToken) + } + + res, err := listCall.Do() + if err != nil { + return nil, err + } + pageToken = res.NextPageToken + for _, i := range res.Items { + name := i.Name + instance := &gceInstance{ + Zone: zone, + Name: name, + ID: i.Id, + Disks: i.Disks, + Type: lastComponent(i.MachineType), + } + instances[name] = instance + } + } + if page >= maxPages { + glog.Errorf("getInstancesByNames exceeded maxPages=%d for Instances.List: truncating.", maxPages) + } + } + + instanceArray := make([]*gceInstance, len(names)) + for i, name := range names { + instance := instances[name] + if instance == nil { + glog.Errorf("Failed to retrieve instance: %q", name) + return nil, cloudprovider.InstanceNotFound + } + instanceArray[i] = instances[name] + } + + return instanceArray, nil +} + +// Gets the named instance, returning cloudprovider.InstanceNotFound if the instance is not found +func (gce *GCECloud) getInstanceByName(name string) (*gceInstance, error) { + // Avoid changing behaviour when not managing multiple zones + if len(gce.managedZones) == 1 { + name = canonicalizeInstanceName(name) + zone := gce.managedZones[0] + res, err := gce.service.Instances.Get(gce.projectID, zone, name).Do() + if err != nil { + glog.Errorf("getInstanceByName/single-zone: failed to get instance %s; err: %v", name, err) + if isHTTPErrorCode(err, http.StatusNotFound) { + return nil, cloudprovider.InstanceNotFound + } + return nil, err + } + return &gceInstance{ + Zone: lastComponent(res.Zone), + Name: res.Name, + ID: res.Id, + Disks: res.Disks, + Type: lastComponent(res.MachineType), + }, nil + } + + instances, err := gce.getInstancesByNames([]string{name}) + if err != nil { + glog.Errorf("getInstanceByName/multiple-zones: failed to get instance %s; err: %v", name, err) + return nil, err + } + if len(instances) != 1 || instances[0] == nil { + // getInstancesByNames not obeying its contract + return nil, fmt.Errorf("unexpected return value from getInstancesByNames: %v", instances) + } + return instances[0], nil +} + +// Returns the last component of a URL, i.e. anything after the last slash +// If there is no slash, returns the whole string +func lastComponent(s string) string { + lastSlash := strings.LastIndex(s, "/") + if lastSlash != -1 { + s = s[lastSlash+1:] + } + return s +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/gce_test.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/gce_test.go new file mode 100644 index 000000000..f1633d889 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/gce_test.go @@ -0,0 +1,150 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gce + +import ( + "reflect" + "testing" +) + +func TestGetRegion(t *testing.T) { + zoneName := "us-central1-b" + regionName, err := GetGCERegion(zoneName) + if err != nil { + t.Fatalf("unexpected error from GetGCERegion: %v", err) + } + if regionName != "us-central1" { + t.Errorf("Unexpected region from GetGCERegion: %s", regionName) + } + gce := &GCECloud{ + localZone: zoneName, + region: regionName, + } + zones, ok := gce.Zones() + if !ok { + t.Fatalf("Unexpected missing zones impl") + } + zone, err := zones.GetZone() + if err != nil { + t.Fatalf("unexpected error %v", err) + } + if zone.Region != "us-central1" { + t.Errorf("Unexpected region: %s", zone.Region) + } +} + +func TestComparingHostURLs(t *testing.T) { + tests := []struct { + host1 string + zone string + name string + expectEqual bool + }{ + { + host1: "https://www.googleapis.com/compute/v1/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx1", + zone: "us-central1-f", + name: "kubernetes-node-fhx1", + expectEqual: true, + }, + { + host1: "https://www.googleapis.com/compute/v1/projects/cool-project/zones/us-central1-f/instances/kubernetes-node-fhx1", + zone: "us-central1-f", + name: "kubernetes-node-fhx1", + expectEqual: true, + }, + { + host1: "https://www.googleapis.com/compute/v23/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx1", + zone: "us-central1-f", + name: "kubernetes-node-fhx1", + expectEqual: true, + }, + { + host1: "https://www.googleapis.com/compute/v24/projects/1234567/regions/us-central1/zones/us-central1-f/instances/kubernetes-node-fhx1", + zone: "us-central1-f", + name: "kubernetes-node-fhx1", + expectEqual: true, + }, + { + host1: "https://www.googleapis.com/compute/v1/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx1", + zone: "us-central1-c", + name: "kubernetes-node-fhx1", + expectEqual: false, + }, + { + host1: "https://www.googleapis.com/compute/v1/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx", + zone: "us-central1-f", + name: "kubernetes-node-fhx1", + expectEqual: false, + }, + { + host1: "https://www.googleapis.com/compute/v1/projects/1234567/zones/us-central1-f/instances/kubernetes-node-fhx1", + zone: "us-central1-f", + name: "kubernetes-node-fhx", + expectEqual: false, + }, + } + + for _, test := range tests { + link1 := hostURLToComparablePath(test.host1) + testInstance := &gceInstance{ + Name: canonicalizeInstanceName(test.name), + Zone: test.zone, + } + link2 := testInstance.makeComparableHostPath() + if test.expectEqual && link1 != link2 { + t.Errorf("expected link1 and link2 to be equal, got %s and %s", link1, link2) + } else if !test.expectEqual && link1 == link2 { + t.Errorf("expected link1 and link2 not to be equal, got %s and %s", link1, link2) + } + } +} + +func TestScrubDNS(t *testing.T) { + tcs := []struct { + nameserversIn []string + searchesIn []string + nameserversOut []string + searchesOut []string + }{ + { + nameserversIn: []string{"1.2.3.4", "5.6.7.8"}, + nameserversOut: []string{"1.2.3.4", "5.6.7.8"}, + }, + { + searchesIn: []string{"c.prj.internal.", "12345678910.google.internal.", "google.internal."}, + searchesOut: []string{"c.prj.internal.", "google.internal."}, + }, + { + searchesIn: []string{"c.prj.internal.", "12345678910.google.internal.", "zone.c.prj.internal.", "google.internal."}, + searchesOut: []string{"c.prj.internal.", "zone.c.prj.internal.", "google.internal."}, + }, + { + searchesIn: []string{"c.prj.internal.", "12345678910.google.internal.", "zone.c.prj.internal.", "google.internal.", "unexpected"}, + searchesOut: []string{"c.prj.internal.", "zone.c.prj.internal.", "google.internal.", "unexpected"}, + }, + } + gce := &GCECloud{} + for i := range tcs { + n, s := gce.ScrubDNS(tcs[i].nameserversIn, tcs[i].searchesIn) + if !reflect.DeepEqual(n, tcs[i].nameserversOut) { + t.Errorf("Expected %v, got %v", tcs[i].nameserversOut, n) + } + if !reflect.DeepEqual(s, tcs[i].searchesOut) { + t.Errorf("Expected %v, got %v", tcs[i].searchesOut, s) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/token_source.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/token_source.go new file mode 100644 index 000000000..70f3c987d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/gce/token_source.go @@ -0,0 +1,112 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gce + +import ( + "encoding/json" + "net/http" + "strings" + "time" + + "k8s.io/kubernetes/pkg/util/flowcontrol" + + "github.com/prometheus/client_golang/prometheus" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + "google.golang.org/api/googleapi" +) + +const ( + // Max QPS to allow through to the token URL. + tokenURLQPS = .05 // back off to once every 20 seconds when failing + // Maximum burst of requests to token URL before limiting. + tokenURLBurst = 3 +) + +var ( + getTokenCounter = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "get_token_count", + Help: "Counter of total Token() requests to the alternate token source", + }, + ) + getTokenFailCounter = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "get_token_fail_count", + Help: "Counter of failed Token() requests to the alternate token source", + }, + ) +) + +func init() { + prometheus.MustRegister(getTokenCounter) + prometheus.MustRegister(getTokenFailCounter) +} + +type altTokenSource struct { + oauthClient *http.Client + tokenURL string + tokenBody string + throttle flowcontrol.RateLimiter +} + +func (a *altTokenSource) Token() (*oauth2.Token, error) { + a.throttle.Accept() + getTokenCounter.Inc() + t, err := a.token() + if err != nil { + getTokenFailCounter.Inc() + } + return t, err +} + +func (a *altTokenSource) token() (*oauth2.Token, error) { + req, err := http.NewRequest("POST", a.tokenURL, strings.NewReader(a.tokenBody)) + if err != nil { + return nil, err + } + res, err := a.oauthClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + if err := googleapi.CheckResponse(res); err != nil { + return nil, err + } + var tok struct { + AccessToken string `json:"accessToken"` + ExpireTime time.Time `json:"expireTime"` + } + if err := json.NewDecoder(res.Body).Decode(&tok); err != nil { + return nil, err + } + return &oauth2.Token{ + AccessToken: tok.AccessToken, + Expiry: tok.ExpireTime, + }, nil +} + +func newAltTokenSource(tokenURL, tokenBody string) oauth2.TokenSource { + client := oauth2.NewClient(oauth2.NoContext, google.ComputeTokenSource("")) + a := &altTokenSource{ + oauthClient: client, + tokenURL: tokenURL, + tokenBody: tokenBody, + throttle: flowcontrol.NewTokenBucketRateLimiter(tokenURLQPS, tokenURLBurst), + } + return oauth2.ReuseTokenSource(nil, a) +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/client.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/client.go new file mode 100644 index 000000000..1b488bc62 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/client.go @@ -0,0 +1,376 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 mesos + +import ( + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "sync" + "time" + + log "github.com/golang/glog" + "github.com/mesos/mesos-go/detector" + mesos "github.com/mesos/mesos-go/mesosproto" + "golang.org/x/net/context" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + utilnet "k8s.io/kubernetes/pkg/util/net" +) + +const defaultClusterName = "mesos" + +var noLeadingMasterError = errors.New("there is no current leading master available to query") + +type mesosClient struct { + masterLock sync.RWMutex + master string // host:port formatted address + httpClient *http.Client + tr *http.Transport + initialMaster <-chan struct{} // signal chan, closes once an initial, non-nil master is found + state *stateCache +} + +type slaveNode struct { + hostname string + kubeletRunning bool + resources *api.NodeResources +} + +type mesosState struct { + clusterName string + nodes map[string]*slaveNode // by hostname +} + +type stateCache struct { + sync.Mutex + expiresAt time.Time + cached *mesosState + err error + ttl time.Duration + refill func(context.Context) (*mesosState, error) +} + +// reloadCache reloads the state cache if it has expired. +func (c *stateCache) reloadCache(ctx context.Context) { + now := time.Now() + c.Lock() + defer c.Unlock() + if c.expiresAt.Before(now) { + log.V(4).Infof("Reloading cached Mesos state") + c.cached, c.err = c.refill(ctx) + c.expiresAt = now.Add(c.ttl) + } else { + log.V(4).Infof("Using cached Mesos state") + } +} + +// cachedState returns the cached Mesos state. +func (c *stateCache) cachedState(ctx context.Context) (*mesosState, error) { + c.reloadCache(ctx) + return c.cached, c.err +} + +// clusterName returns the cached Mesos cluster name. +func (c *stateCache) clusterName(ctx context.Context) (string, error) { + cached, err := c.cachedState(ctx) + if err != nil { + return "", err + } + return cached.clusterName, nil +} + +// nodes returns the cached list of slave nodes. +func (c *stateCache) nodes(ctx context.Context) (map[string]*slaveNode, error) { + cached, err := c.cachedState(ctx) + if err != nil { + return nil, err + } + return cached.nodes, nil +} + +func newMesosClient( + md detector.Master, + mesosHttpClientTimeout, stateCacheTTL time.Duration) (*mesosClient, error) { + + tr := utilnet.SetTransportDefaults(&http.Transport{}) + httpClient := &http.Client{ + Transport: tr, + Timeout: mesosHttpClientTimeout, + } + return createMesosClient(md, httpClient, tr, stateCacheTTL) +} + +func createMesosClient( + md detector.Master, + httpClient *http.Client, + tr *http.Transport, + stateCacheTTL time.Duration) (*mesosClient, error) { + + initialMaster := make(chan struct{}) + client := &mesosClient{ + httpClient: httpClient, + tr: tr, + initialMaster: initialMaster, + state: &stateCache{ + ttl: stateCacheTTL, + }, + } + client.state.refill = client.pollMasterForState + first := true + if err := md.Detect(detector.OnMasterChanged(func(info *mesos.MasterInfo) { + host, port := extractMasterAddress(info) + if len(host) > 0 { + client.masterLock.Lock() + defer client.masterLock.Unlock() + client.master = fmt.Sprintf("%s:%d", host, port) + if first { + first = false + close(initialMaster) + } + } + log.Infof("cloud master changed to '%v'", client.master) + })); err != nil { + log.V(1).Infof("detector initialization failed: %v", err) + return nil, err + } + return client, nil +} + +func extractMasterAddress(info *mesos.MasterInfo) (host string, port int) { + if info != nil { + host = info.GetAddress().GetHostname() + if host == "" { + host = info.GetAddress().GetIp() + } + + if host != "" { + // use port from Address + port = int(info.GetAddress().GetPort()) + } else { + // deprecated: get host and port directly from MasterInfo (and not Address) + host = info.GetHostname() + if host == "" { + host = unpackIPv4(info.GetIp()) + } + port = int(info.GetPort()) + } + } + return +} + +func unpackIPv4(ip uint32) string { + octets := make([]byte, 4, 4) + binary.BigEndian.PutUint32(octets, ip) + ipv4 := net.IP(octets) + return ipv4.String() +} + +// listSlaves returns a (possibly cached) map of slave nodes by hostname. +// Callers must not mutate the contents of the returned slice. +func (c *mesosClient) listSlaves(ctx context.Context) (map[string]*slaveNode, error) { + return c.state.nodes(ctx) +} + +// clusterName returns a (possibly cached) cluster name. +func (c *mesosClient) clusterName(ctx context.Context) (string, error) { + return c.state.clusterName(ctx) +} + +// pollMasterForState returns an array of slave nodes +func (c *mesosClient) pollMasterForState(ctx context.Context) (*mesosState, error) { + // wait for initial master detection + select { + case <-c.initialMaster: // noop + case <-ctx.Done(): + return nil, ctx.Err() + } + + master := func() string { + c.masterLock.RLock() + defer c.masterLock.RUnlock() + return c.master + }() + if master == "" { + return nil, noLeadingMasterError + } + + //TODO(jdef) should not assume master uses http (what about https?) + + var state *mesosState + successHandler := func(res *http.Response) error { + blob, err1 := ioutil.ReadAll(res.Body) + if err1 != nil { + return err1 + } + log.V(3).Infof("Got mesos state, content length %v", len(blob)) + state, err1 = parseMesosState(blob) + return err1 + } + // thinking here is that we may get some other status codes from mesos at some point: + // - authentication + // - redirection (possibly from http to https) + // ... + for _, tt := range []struct { + uri string + handlers map[int]func(*http.Response) error + }{ + { + uri: fmt.Sprintf("http://%s/state", master), + handlers: map[int]func(*http.Response) error{ + 200: successHandler, + }, + }, + { + uri: fmt.Sprintf("http://%s/state.json", master), + handlers: map[int]func(*http.Response) error{ + 200: successHandler, + }, + }, + } { + req, err := http.NewRequest("GET", tt.uri, nil) + if err != nil { + return nil, err + } + err = c.httpDo(ctx, req, func(res *http.Response, err error) error { + if err != nil { + return err + } + defer res.Body.Close() + if handler, ok := tt.handlers[res.StatusCode]; ok { + err1 := handler(res) + if err1 != nil { + return err1 + } + } + // no handler for this error code, proceed to the next connection type + return nil + }) + if state != nil || err != nil { + return state, err + } + } + return nil, errors.New("failed to sync with Mesos master") +} + +func parseMesosState(blob []byte) (*mesosState, error) { + type State struct { + ClusterName string `json:"cluster"` + Slaves []*struct { + Id string `json:"id"` // ex: 20150106-162714-3815890698-5050-2453-S2 + Pid string `json:"pid"` // ex: slave(1)@10.22.211.18:5051 + Hostname string `json:"hostname"` // ex: 10.22.211.18, or slave-123.nowhere.com + Resources map[string]interface{} `json:"resources"` // ex: {"mem": 123, "ports": "[31000-3200]"} + } `json:"slaves"` + Frameworks []*struct { + Id string `json:"id"` // ex: 20151105-093752-3745622208-5050-1-0000 + Pid string `json:"pid"` // ex: scheduler(1)@192.168.65.228:57124 + Executors []*struct { + SlaveId string `json:"slave_id"` // ex: 20151105-093752-3745622208-5050-1-S1 + ExecutorId string `json:"executor_id"` // ex: 6704d375c68fee1e_k8sm-executor + Name string `json:"name"` // ex: Kubelet-Executor + } `json:"executors"` + } `json:"frameworks"` + } + + state := &State{ClusterName: defaultClusterName} + if err := json.Unmarshal(blob, state); err != nil { + return nil, err + } + + executorSlaveIds := map[string]struct{}{} + for _, f := range state.Frameworks { + for _, e := range f.Executors { + // Note that this simple comparison breaks when we support more than one + // k8s instance in a cluster. At the moment this is not possible for + // a number of reasons. + // TODO(sttts): find way to detect executors of this k8s instance + if e.Name == KubernetesExecutorName { + executorSlaveIds[e.SlaveId] = struct{}{} + } + } + } + + nodes := map[string]*slaveNode{} // by hostname + for _, slave := range state.Slaves { + if slave.Hostname == "" { + continue + } + node := &slaveNode{hostname: slave.Hostname} + cap := api.ResourceList{} + if slave.Resources != nil && len(slave.Resources) > 0 { + // attempt to translate CPU (cores) and memory (MB) resources + if cpu, found := slave.Resources["cpus"]; found { + if cpuNum, ok := cpu.(float64); ok { + cap[api.ResourceCPU] = *resource.NewQuantity(int64(cpuNum), resource.DecimalSI) + } else { + log.Warningf("unexpected slave cpu resource type %T: %v", cpu, cpu) + } + } else { + log.Warningf("slave failed to report cpu resource") + } + if mem, found := slave.Resources["mem"]; found { + if memNum, ok := mem.(float64); ok { + cap[api.ResourceMemory] = *resource.NewQuantity(int64(memNum), resource.BinarySI) + } else { + log.Warningf("unexpected slave mem resource type %T: %v", mem, mem) + } + } else { + log.Warningf("slave failed to report mem resource") + } + } + if len(cap) > 0 { + node.resources = &api.NodeResources{ + Capacity: cap, + } + log.V(4).Infof("node %q reporting capacity %v", node.hostname, cap) + } + if _, ok := executorSlaveIds[slave.Id]; ok { + node.kubeletRunning = true + } + nodes[node.hostname] = node + } + + result := &mesosState{ + clusterName: state.ClusterName, + nodes: nodes, + } + + return result, nil +} + +type responseHandler func(*http.Response, error) error + +// httpDo executes an HTTP request in the given context, canceling an ongoing request if the context +// is canceled prior to completion of the request. hacked from https://blog.golang.org/context +func (c *mesosClient) httpDo(ctx context.Context, req *http.Request, f responseHandler) error { + // Run the HTTP request in a goroutine and pass the response to f. + ch := make(chan error, 1) + go func() { ch <- f(c.httpClient.Do(req)) }() + select { + case <-ctx.Done(): + c.tr.CancelRequest(req) + <-ch // Wait for f to return. + return ctx.Err() + case err := <-ch: + return err + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/client_test.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/client_test.go new file mode 100644 index 000000000..b92fbaf9f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/client_test.go @@ -0,0 +1,273 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 mesos + +import ( + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "testing" + "time" + + log "github.com/golang/glog" + "github.com/mesos/mesos-go/detector" + "github.com/mesos/mesos-go/mesosutil" + "golang.org/x/net/context" + + utilnet "k8s.io/kubernetes/pkg/util/net" +) + +// Test data + +const ( + TEST_MASTER_ID = "master-12345" + TEST_MASTER_IP = 177048842 // 10.141.141.10 + TEST_MASTER_PORT = 5050 + + TEST_STATE_JSON = ` + { + "version": "0.22.0", + "unregistered_frameworks": [], + "started_tasks": 0, + "start_time": 1429456501.61141, + "staged_tasks": 0, + "slaves": [ + { + "resources": { + "ports": "[31000-32000]", + "mem": 15360, + "disk": 470842, + "cpus": 8 + }, + "registered_time": 1429456502.46999, + "pid": "slave(1)@mesos1.internal.company.com:5050", + "id": "20150419-081501-16777343-5050-16383-S2", + "hostname": "mesos1.internal.company.com", + "attributes": {}, + "active": true + }, + { + "resources": { + "ports": "[31000-32000]", + "mem": 15360, + "disk": 470842, + "cpus": 8 + }, + "registered_time": 1429456502.4144, + "pid": "slave(1)@mesos2.internal.company.com:5050", + "id": "20150419-081501-16777343-5050-16383-S1", + "hostname": "mesos2.internal.company.com", + "attributes": {}, + "active": true + }, + { + "resources": { + "ports": "[31000-32000]", + "mem": 15360, + "disk": 470842, + "cpus": 8 + }, + "registered_time": 1429456502.02879, + "pid": "slave(1)@mesos3.internal.company.com:5050", + "id": "20150419-081501-16777343-5050-16383-S0", + "hostname": "mesos3.internal.company.com", + "attributes": {}, + "active": true + } + ], + "pid": "master@mesos-master0.internal.company.com:5050", + "orphan_tasks": [], + "lost_tasks": 0, + "leader": "master@mesos-master0.internal.company.com:5050", + "killed_tasks": 0, + "failed_tasks": 0, + "elected_time": 1429456501.61638, + "deactivated_slaves": 0, + "completed_frameworks": [], + "build_user": "buildbot", + "build_time": 1425085311, + "build_date": "2015-02-27 17:01:51", + "activated_slaves": 3, + "finished_tasks": 0, + "flags": { + "zk_session_timeout": "10secs", + "work_dir": "/somepath/mesos/local/Lc9arz", + "webui_dir": "/usr/local/share/mesos/webui", + "version": "false", + "user_sorter": "drf", + "slave_reregister_timeout": "10mins", + "logbufsecs": "0", + "log_auto_initialize": "true", + "initialize_driver_logging": "true", + "framework_sorter": "drf", + "authenticators": "crammd5", + "authenticate_slaves": "false", + "authenticate": "false", + "allocation_interval": "1secs", + "logging_level": "INFO", + "quiet": "false", + "recovery_slave_removal_limit": "100%", + "registry": "replicated_log", + "registry_fetch_timeout": "1mins", + "registry_store_timeout": "5secs", + "registry_strict": "false", + "root_submissions": "true" + }, + "frameworks": [], + "git_branch": "refs/heads/0.22.0-rc1", + "git_sha": "46834faca67f877631e1beb7d61be5c080ec3dc2", + "git_tag": "0.22.0-rc1", + "hostname": "localhost", + "id": "20150419-081501-16777343-5050-16383" + }` +) + +// Mocks + +type FakeMasterDetector struct { + callback detector.MasterChanged + done chan struct{} +} + +func newFakeMasterDetector() *FakeMasterDetector { + return &FakeMasterDetector{ + done: make(chan struct{}), + } +} + +func (md FakeMasterDetector) Cancel() { + close(md.done) +} + +func (md FakeMasterDetector) Detect(cb detector.MasterChanged) error { + md.callback = cb + leadingMaster := mesosutil.NewMasterInfo(TEST_MASTER_ID, TEST_MASTER_IP, TEST_MASTER_PORT) + cb.OnMasterChanged(leadingMaster) + return nil +} + +func (md FakeMasterDetector) Done() <-chan struct{} { + return md.done +} + +// Auxiliary functions + +func makeHttpMocks() (*httptest.Server, *http.Client, *http.Transport) { + httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.V(4).Infof("Mocking response for HTTP request: %#v", r) + if r.URL.Path == "/state.json" { + w.WriteHeader(200) // OK + w.Header().Set("Content-Type", "application/json") + fmt.Fprintln(w, TEST_STATE_JSON) + } else { + w.WriteHeader(400) + fmt.Fprintln(w, "Bad Request") + } + })) + + // Intercept all client requests and feed them to the test server + transport := utilnet.SetTransportDefaults(&http.Transport{ + Proxy: func(req *http.Request) (*url.URL, error) { + return url.Parse(httpServer.URL) + }, + }) + + httpClient := &http.Client{Transport: transport} + + return httpServer, httpClient, transport +} + +// Tests + +// test mesos.parseMesosState +func Test_parseMesosState(t *testing.T) { + state, err := parseMesosState([]byte(TEST_STATE_JSON)) + + if err != nil { + t.Fatalf("parseMesosState does not yield an error") + } + if state == nil { + t.Fatalf("parseMesosState yields a non-nil state") + } + if len(state.nodes) != 3 { + t.Fatalf("parseMesosState yields a state with 3 nodes") + } +} + +// test mesos.listSlaves +func Test_listSlaves(t *testing.T) { + defer log.Flush() + md := FakeMasterDetector{} + // TODO: Uncomment next two lines and remove third line when fix #19254 + // defer httpServer.Close() + // httpServer, httpClient, httpTransport := makeHttpMocks() + _, httpClient, httpTransport := makeHttpMocks() + + cacheTTL := 500 * time.Millisecond + mesosClient, err := createMesosClient(md, httpClient, httpTransport, cacheTTL) + + if err != nil { + t.Fatalf("createMesosClient does not yield an error") + } + + slaveNodes, err := mesosClient.listSlaves(context.TODO()) + + if err != nil { + t.Fatalf("listSlaves does not yield an error") + } + if len(slaveNodes) != 3 { + t.Fatalf("listSlaves yields a collection of size 3") + } + + expectedHostnames := map[string]struct{}{ + "mesos1.internal.company.com": {}, + "mesos2.internal.company.com": {}, + "mesos3.internal.company.com": {}, + } + + actualHostnames := make(map[string]struct{}) + for _, node := range slaveNodes { + actualHostnames[node.hostname] = struct{}{} + } + + if !reflect.DeepEqual(expectedHostnames, actualHostnames) { + t.Fatalf("listSlaves yields a collection with the expected hostnames") + } +} + +// test mesos.clusterName +func Test_clusterName(t *testing.T) { + defer log.Flush() + md := FakeMasterDetector{} + // TODO: Uncomment next two lines and remove third line when fix #19254 + // defer httpServer.Close() + // httpServer, httpClient, httpTransport := makeHttpMocks() + _, httpClient, httpTransport := makeHttpMocks() + cacheTTL := 500 * time.Millisecond + mesosClient, err := createMesosClient(md, httpClient, httpTransport, cacheTTL) + + name, err := mesosClient.clusterName(context.TODO()) + + if err != nil { + t.Fatalf("clusterName does not yield an error") + } + if name != defaultClusterName { + t.Fatalf("clusterName yields the expected (default) value") + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/config.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/config.go new file mode 100644 index 000000000..9edbc8f5f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/config.go @@ -0,0 +1,79 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 mesos + +import ( + "io" + "time" + + "gopkg.in/gcfg.v1" +) + +const ( + DefaultMesosMaster = "localhost:5050" + DefaultHttpClientTimeout = time.Duration(10) * time.Second + DefaultStateCacheTTL = time.Duration(5) * time.Second +) + +// Example Mesos cloud provider configuration file: +// +// [mesos-cloud] +// mesos-master = leader.mesos:5050 +// http-client-timeout = 500ms +// state-cache-ttl = 1h + +type ConfigWrapper struct { + Mesos_Cloud Config +} + +type Config struct { + MesosMaster string `gcfg:"mesos-master"` + MesosHttpClientTimeout Duration `gcfg:"http-client-timeout"` + StateCacheTTL Duration `gcfg:"state-cache-ttl"` +} + +type Duration struct { + Duration time.Duration `gcfg:"duration"` +} + +func (d *Duration) UnmarshalText(data []byte) error { + underlying, err := time.ParseDuration(string(data)) + if err == nil { + d.Duration = underlying + } + return err +} + +func createDefaultConfig() *Config { + return &Config{ + MesosMaster: DefaultMesosMaster, + MesosHttpClientTimeout: Duration{Duration: DefaultHttpClientTimeout}, + StateCacheTTL: Duration{Duration: DefaultStateCacheTTL}, + } +} + +func readConfig(configReader io.Reader) (*Config, error) { + config := createDefaultConfig() + wrapper := &ConfigWrapper{Mesos_Cloud: *config} + if configReader != nil { + if err := gcfg.ReadInto(wrapper, configReader); err != nil { + return nil, err + } + config = &(wrapper.Mesos_Cloud) + } + return config, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/config_test.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/config_test.go new file mode 100644 index 000000000..d1013471c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/config_test.go @@ -0,0 +1,75 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 mesos + +import ( + "bytes" + "testing" + "time" + + log "github.com/golang/glog" +) + +// test mesos.createDefaultConfig +func Test_createDefaultConfig(t *testing.T) { + defer log.Flush() + + config := createDefaultConfig() + + if config.MesosMaster != DefaultMesosMaster { + t.Fatalf("Default config has the expected MesosMaster value") + } + + if config.MesosHttpClientTimeout.Duration != DefaultHttpClientTimeout { + t.Fatalf("Default config has the expected MesosHttpClientTimeout value") + } + + if config.StateCacheTTL.Duration != DefaultStateCacheTTL { + t.Fatalf("Default config has the expected StateCacheTTL value") + } +} + +// test mesos.readConfig +func Test_readConfig(t *testing.T) { + defer log.Flush() + + configString := ` +[mesos-cloud] + mesos-master = leader.mesos:5050 + http-client-timeout = 500ms + state-cache-ttl = 1h` + + reader := bytes.NewBufferString(configString) + + config, err := readConfig(reader) + + if err != nil { + t.Fatalf("Reading configuration does not yield an error: %#v", err) + } + + if config.MesosMaster != "leader.mesos:5050" { + t.Fatalf("Parsed config has the expected MesosMaster value") + } + + if config.MesosHttpClientTimeout.Duration != time.Duration(500)*time.Millisecond { + t.Fatalf("Parsed config has the expected MesosHttpClientTimeout value") + } + + if config.StateCacheTTL.Duration != time.Duration(1)*time.Hour { + t.Fatalf("Parsed config has the expected StateCacheTTL value") + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/mesos.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/mesos.go new file mode 100644 index 000000000..20285843b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/mesos.go @@ -0,0 +1,283 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 mesos + +import ( + "errors" + "fmt" + "io" + "net" + "regexp" + + log "github.com/golang/glog" + "github.com/mesos/mesos-go/detector" + "golang.org/x/net/context" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/cloudprovider" +) + +const ( + ProviderName = "mesos" + + // KubernetesExecutorName is shared between contrib/mesos and Mesos cloud provider. + // Because cloud provider -> contrib dependencies are forbidden, this constant + // is defined here, not in contrib. + KubernetesExecutorName = "Kubelet-Executor" +) + +var ( + CloudProvider *MesosCloud + + noHostNameSpecified = errors.New("No hostname specified") +) + +func init() { + cloudprovider.RegisterCloudProvider( + ProviderName, + func(configReader io.Reader) (cloudprovider.Interface, error) { + provider, err := newMesosCloud(configReader) + if err == nil { + CloudProvider = provider + } + return provider, err + }) +} + +type MesosCloud struct { + client *mesosClient + config *Config +} + +func (c *MesosCloud) MasterURI() string { + return c.config.MesosMaster +} + +func newMesosCloud(configReader io.Reader) (*MesosCloud, error) { + config, err := readConfig(configReader) + if err != nil { + return nil, err + } + + log.V(1).Infof("new mesos cloud, master='%v'", config.MesosMaster) + if d, err := detector.New(config.MesosMaster); err != nil { + log.V(1).Infof("failed to create master detector: %v", err) + return nil, err + } else if cl, err := newMesosClient(d, + config.MesosHttpClientTimeout.Duration, + config.StateCacheTTL.Duration); err != nil { + log.V(1).Infof("failed to create mesos cloud client: %v", err) + return nil, err + } else { + return &MesosCloud{client: cl, config: config}, nil + } +} + +// Implementation of Instances.CurrentNodeName +func (c *MesosCloud) CurrentNodeName(hostname string) (string, error) { + return hostname, nil +} + +func (c *MesosCloud) AddSSHKeyToAllInstances(user string, keyData []byte) error { + return errors.New("unimplemented") +} + +// Instances returns a copy of the Mesos cloud Instances implementation. +// Mesos natively provides minimal cloud-type resources. More robust cloud +// support requires a combination of Mesos and cloud-specific knowledge. +func (c *MesosCloud) Instances() (cloudprovider.Instances, bool) { + return c, true +} + +// LoadBalancer always returns nil, false in this implementation. +// Mesos does not provide any type of native load balancing by default, +// so this implementation always returns (nil, false). +func (c *MesosCloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) { + return nil, false +} + +// Zones always returns nil, false in this implementation. +// Mesos does not provide any type of native region or zone awareness, +// so this implementation always returns (nil, false). +func (c *MesosCloud) Zones() (cloudprovider.Zones, bool) { + return nil, false +} + +// Clusters returns a copy of the Mesos cloud Clusters implementation. +// Mesos does not provide support for multiple clusters. +func (c *MesosCloud) Clusters() (cloudprovider.Clusters, bool) { + return c, true +} + +// Routes always returns nil, false in this implementation. +func (c *MesosCloud) Routes() (cloudprovider.Routes, bool) { + return nil, false +} + +// ProviderName returns the cloud provider ID. +func (c *MesosCloud) ProviderName() string { + return ProviderName +} + +// ScrubDNS filters DNS settings for pods. +func (c *MesosCloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { + return nameservers, searches +} + +// ListClusters lists the names of the available Mesos clusters. +func (c *MesosCloud) ListClusters() ([]string, error) { + // Always returns a single cluster (this one!) + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + name, err := c.client.clusterName(ctx) + return []string{name}, err +} + +// Master gets back the address (either DNS name or IP address) of the leading Mesos master node for the cluster. +func (c *MesosCloud) Master(clusterName string) (string, error) { + clusters, err := c.ListClusters() + if err != nil { + return "", err + } + for _, name := range clusters { + if name == clusterName { + if c.client.master == "" { + return "", errors.New("The currently leading master is unknown.") + } + + host, _, err := net.SplitHostPort(c.client.master) + if err != nil { + return "", err + } + + return host, nil + } + } + return "", errors.New(fmt.Sprintf("The supplied cluster '%v' does not exist", clusterName)) +} + +// ipAddress returns an IP address of the specified instance. +func ipAddress(name string) (net.IP, error) { + if name == "" { + return nil, noHostNameSpecified + } + ipaddr := net.ParseIP(name) + if ipaddr != nil { + return ipaddr, nil + } + iplist, err := net.LookupIP(name) + if err != nil { + log.V(2).Infof("failed to resolve IP from host name '%v': %v", name, err) + return nil, err + } + ipaddr = iplist[0] + log.V(2).Infof("resolved host '%v' to '%v'", name, ipaddr) + return ipaddr, nil +} + +// ExternalID returns the cloud provider ID of the specified instance (deprecated). +func (c *MesosCloud) ExternalID(instance string) (string, error) { + //TODO(jdef) use a timeout here? 15s? + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + + nodes, err := c.client.listSlaves(ctx) + if err != nil { + return "", err + } + + node := nodes[instance] + if node == nil { + return "", cloudprovider.InstanceNotFound + } + + ip, err := ipAddress(node.hostname) + if err != nil { + return "", err + } + return ip.String(), nil +} + +// InstanceID returns the cloud provider ID of the specified instance. +func (c *MesosCloud) InstanceID(name string) (string, error) { + return "", nil +} + +// InstanceType returns the type of the specified instance. +func (c *MesosCloud) InstanceType(name string) (string, error) { + return "", nil +} + +func (c *MesosCloud) listNodes() (map[string]*slaveNode, error) { + //TODO(jdef) use a timeout here? 15s? + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + + nodes, err := c.client.listSlaves(ctx) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + log.V(2).Info("no slaves found, are any running?") + return nil, nil + } + return nodes, nil +} + +// List lists instances that match 'filter' which is a regular expression +// which must match the entire instance name (fqdn). +func (c *MesosCloud) List(filter string) ([]string, error) { + nodes, err := c.listNodes() + if err != nil { + return nil, err + } + filterRegex, err := regexp.Compile(filter) + if err != nil { + return nil, err + } + addr := []string{} + for _, node := range nodes { + if filterRegex.MatchString(node.hostname) { + addr = append(addr, node.hostname) + } + } + return addr, nil +} + +// ListWithKubelet list those instance which have no running kubelet, i.e. the +// Kubernetes executor. +func (c *MesosCloud) ListWithoutKubelet() ([]string, error) { + nodes, err := c.listNodes() + if err != nil { + return nil, err + } + addr := make([]string, 0, len(nodes)) + for _, n := range nodes { + if !n.kubeletRunning { + addr = append(addr, n.hostname) + } + } + return addr, nil +} + +// NodeAddresses returns the addresses of the specified instance. +func (c *MesosCloud) NodeAddresses(name string) ([]api.NodeAddress, error) { + ip, err := ipAddress(name) + if err != nil { + return nil, err + } + return []api.NodeAddress{{Type: api.NodeLegacyHostIP, Address: ip.String()}}, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/mesos_test.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/mesos_test.go new file mode 100644 index 000000000..b504f4ef0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/mesos_test.go @@ -0,0 +1,279 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 mesos + +import ( + "bytes" + "net" + "reflect" + "testing" + "time" + + log "github.com/golang/glog" + "k8s.io/kubernetes/pkg/cloudprovider" +) + +func TestIPAddress(t *testing.T) { + expected4 := net.IPv4(127, 0, 0, 1) + ip, err := ipAddress("127.0.0.1") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !reflect.DeepEqual(ip, expected4) { + t.Fatalf("expected %#v instead of %#v", expected4, ip) + } + + expected6 := net.ParseIP("::1") + if expected6 == nil { + t.Fatalf("failed to parse ipv6 ::1") + } + ip, err = ipAddress("::1") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !reflect.DeepEqual(ip, expected6) { + t.Fatalf("expected %#v instead of %#v", expected6, ip) + } + + ip, err = ipAddress("localhost") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !reflect.DeepEqual(ip, expected4) && !reflect.DeepEqual(ip, expected6) { + t.Fatalf("expected %#v or %#v instead of %#v", expected4, expected6, ip) + } + + _, err = ipAddress("") + if err != noHostNameSpecified { + t.Fatalf("expected error noHostNameSpecified but got none") + } +} + +// test mesos.newMesosCloud with no config +func Test_newMesosCloud_NoConfig(t *testing.T) { + defer log.Flush() + mesosCloud, err := newMesosCloud(nil) + + if err != nil { + t.Fatalf("Creating a new Mesos cloud provider without config does not yield an error: %#v", err) + } + + if mesosCloud.client.httpClient.Timeout != DefaultHttpClientTimeout { + t.Fatalf("Creating a new Mesos cloud provider without config does not yield an error: %#v", err) + } + + if mesosCloud.client.state.ttl != DefaultStateCacheTTL { + t.Fatalf("Mesos client with default config has the expected state cache TTL value") + } +} + +// test mesos.newMesosCloud with custom config +func Test_newMesosCloud_WithConfig(t *testing.T) { + defer log.Flush() + + configString := ` +[mesos-cloud] + http-client-timeout = 500ms + state-cache-ttl = 1h` + + reader := bytes.NewBufferString(configString) + + mesosCloud, err := newMesosCloud(reader) + + if err != nil { + t.Fatalf("Creating a new Mesos cloud provider with a custom config does not yield an error: %#v", err) + } + + if mesosCloud.client.httpClient.Timeout != time.Duration(500)*time.Millisecond { + t.Fatalf("Mesos client with a custom config has the expected HTTP client timeout value") + } + + if mesosCloud.client.state.ttl != time.Duration(1)*time.Hour { + t.Fatalf("Mesos client with a custom config has the expected state cache TTL value") + } +} + +// tests for capability reporting functions + +// test mesos.Instances +func Test_Instances(t *testing.T) { + defer log.Flush() + mesosCloud, _ := newMesosCloud(nil) + + instances, supports_instances := mesosCloud.Instances() + + if !supports_instances || instances == nil { + t.Fatalf("MesosCloud provides an implementation of Instances") + } +} + +// test mesos.LoadBalancer +func Test_TcpLoadBalancer(t *testing.T) { + defer log.Flush() + mesosCloud, _ := newMesosCloud(nil) + + lb, supports_lb := mesosCloud.LoadBalancer() + + if supports_lb || lb != nil { + t.Fatalf("MesosCloud does not provide an implementation of LoadBalancer") + } +} + +// test mesos.Zones +func Test_Zones(t *testing.T) { + defer log.Flush() + mesosCloud, _ := newMesosCloud(nil) + + zones, supports_zones := mesosCloud.Zones() + + if supports_zones || zones != nil { + t.Fatalf("MesosCloud does not provide an implementation of Zones") + } +} + +// test mesos.Clusters +func Test_Clusters(t *testing.T) { + defer log.Flush() + mesosCloud, _ := newMesosCloud(nil) + + clusters, supports_clusters := mesosCloud.Clusters() + + if !supports_clusters || clusters == nil { + t.Fatalf("MesosCloud does not provide an implementation of Clusters") + } +} + +// test mesos.MasterURI +func Test_MasterURI(t *testing.T) { + defer log.Flush() + mesosCloud, _ := newMesosCloud(nil) + + uri := mesosCloud.MasterURI() + + if uri != DefaultMesosMaster { + t.Fatalf("MasterURI returns the expected master URI (expected \"localhost\", actual \"%s\"", uri) + } +} + +// test mesos.ListClusters +func Test_ListClusters(t *testing.T) { + defer log.Flush() + md := FakeMasterDetector{} + httpServer, httpClient, httpTransport := makeHttpMocks() + defer httpServer.Close() + cacheTTL := 500 * time.Millisecond + mesosClient, err := createMesosClient(md, httpClient, httpTransport, cacheTTL) + mesosCloud := &MesosCloud{client: mesosClient, config: createDefaultConfig()} + + clusters, err := mesosCloud.ListClusters() + + if err != nil { + t.Fatalf("ListClusters does not yield an error: %#v", err) + } + + if len(clusters) != 1 { + t.Fatalf("ListClusters should return a list of size 1: (actual: %#v)", clusters) + } + + expectedClusterNames := []string{"mesos"} + + if !reflect.DeepEqual(clusters, expectedClusterNames) { + t.Fatalf("ListClusters should return the expected list of names: (expected: %#v, actual: %#v)", + expectedClusterNames, + clusters) + } +} + +// test mesos.Master +func Test_Master(t *testing.T) { + defer log.Flush() + md := FakeMasterDetector{} + httpServer, httpClient, httpTransport := makeHttpMocks() + defer httpServer.Close() + cacheTTL := 500 * time.Millisecond + mesosClient, err := createMesosClient(md, httpClient, httpTransport, cacheTTL) + mesosCloud := &MesosCloud{client: mesosClient, config: createDefaultConfig()} + + clusters, err := mesosCloud.ListClusters() + clusterName := clusters[0] + master, err := mesosCloud.Master(clusterName) + + if err != nil { + t.Fatalf("Master does not yield an error: %#v", err) + } + + expectedMaster := unpackIPv4(TEST_MASTER_IP) + + if master != expectedMaster { + t.Fatalf("Master returns the expected value: (expected: %#v, actual: %#v", expectedMaster, master) + } +} + +// test mesos.List +func Test_List(t *testing.T) { + defer log.Flush() + md := FakeMasterDetector{} + httpServer, httpClient, httpTransport := makeHttpMocks() + defer httpServer.Close() + cacheTTL := 500 * time.Millisecond + mesosClient, err := createMesosClient(md, httpClient, httpTransport, cacheTTL) + mesosCloud := &MesosCloud{client: mesosClient, config: createDefaultConfig()} + + clusters, err := mesosCloud.List(".*") // recognizes the language of all strings + + if err != nil { + t.Fatalf("List does not yield an error: %#v", err) + } + + if len(clusters) != 3 { + t.Fatalf("List with a catch-all filter should return a list of size 3: (actual: %#v)", clusters) + } + + clusters, err = mesosCloud.List("$^") // end-of-string followed by start-of-string: recognizes the empty language + + if err != nil { + t.Fatalf("List does not yield an error: %#v", err) + } + + if len(clusters) != 0 { + t.Fatalf("List with a reject-all filter should return a list of size 0: (actual: %#v)", clusters) + } +} + +func Test_ExternalID(t *testing.T) { + defer log.Flush() + md := FakeMasterDetector{} + httpServer, httpClient, httpTransport := makeHttpMocks() + defer httpServer.Close() + cacheTTL := 500 * time.Millisecond + mesosClient, err := createMesosClient(md, httpClient, httpTransport, cacheTTL) + mesosCloud := &MesosCloud{client: mesosClient, config: createDefaultConfig()} + + _, err = mesosCloud.ExternalID("unknown") + if err != cloudprovider.InstanceNotFound { + t.Fatalf("ExternalID did not return InstanceNotFound on an unknown instance") + } + + slaveName := "mesos3.internal.company.com" + id, err := mesosCloud.ExternalID(slaveName) + if id != "" { + t.Fatalf("ExternalID should not be able to resolve %q", slaveName) + } + if err == cloudprovider.InstanceNotFound { + t.Fatalf("ExternalID should find %q", slaveName) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/plugins.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/plugins.go new file mode 100644 index 000000000..2baf7b47f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/mesos/plugins.go @@ -0,0 +1,21 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 mesos + +import ( + _ "github.com/mesos/mesos-go/detector/zoo" +) diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/openstack/MAINTAINERS.md b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/openstack/MAINTAINERS.md new file mode 100644 index 000000000..f71afec99 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/openstack/MAINTAINERS.md @@ -0,0 +1,6 @@ +# Maintainers + +* [Angus Lees](https://github.com/anguslees) + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/pkg/cloudprovider/providers/openstack/MAINTAINERS.md?pixel)]() diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/openstack/openstack.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/openstack/openstack.go new file mode 100644 index 000000000..e19071e28 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/openstack/openstack.go @@ -0,0 +1,1103 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 openstack + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "regexp" + "strings" + "time" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" + "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" + "github.com/rackspace/gophercloud/openstack/compute/v2/flavors" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips" + "github.com/rackspace/gophercloud/pagination" + "gopkg.in/gcfg.v1" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/service" + "k8s.io/kubernetes/pkg/cloudprovider" +) + +const ProviderName = "openstack" + +// metadataUrl is URL to OpenStack metadata server. It's hadrcoded IPv4 +// link-local address as documented in "OpenStack Cloud Administrator Guide", +// chapter Compute - Networking with nova-network. +// http://docs.openstack.org/admin-guide-cloud/compute-networking-nova.html#metadata-service +const metadataUrl = "http://169.254.169.254/openstack/2012-08-10/meta_data.json" + +var ErrNotFound = errors.New("Failed to find object") +var ErrMultipleResults = errors.New("Multiple results where only one expected") +var ErrNoAddressFound = errors.New("No address found for host") +var ErrAttrNotFound = errors.New("Expected attribute not found") + +const ( + MiB = 1024 * 1024 + GB = 1000 * 1000 * 1000 +) + +// encoding.TextUnmarshaler interface for time.Duration +type MyDuration struct { + time.Duration +} + +func (d *MyDuration) UnmarshalText(text []byte) error { + res, err := time.ParseDuration(string(text)) + if err != nil { + return err + } + d.Duration = res + return nil +} + +type LoadBalancerOpts struct { + SubnetId string `gcfg:"subnet-id"` // required + FloatingNetworkId string `gcfg:"floating-network-id"` + LBMethod string `gcfg:"lb-method"` + CreateMonitor bool `gcfg:"create-monitor"` + MonitorDelay MyDuration `gcfg:"monitor-delay"` + MonitorTimeout MyDuration `gcfg:"monitor-timeout"` + MonitorMaxRetries uint `gcfg:"monitor-max-retries"` +} + +// OpenStack is an implementation of cloud provider Interface for OpenStack. +type OpenStack struct { + provider *gophercloud.ProviderClient + region string + lbOpts LoadBalancerOpts + // InstanceID of the server where this OpenStack object is instantiated. + localInstanceID string +} + +type Config struct { + Global struct { + AuthUrl string `gcfg:"auth-url"` + Username string + UserId string `gcfg:"user-id"` + Password string + ApiKey string `gcfg:"api-key"` + TenantId string `gcfg:"tenant-id"` + TenantName string `gcfg:"tenant-name"` + DomainId string `gcfg:"domain-id"` + DomainName string `gcfg:"domain-name"` + Region string + } + LoadBalancer LoadBalancerOpts +} + +func init() { + cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) { + cfg, err := readConfig(config) + if err != nil { + return nil, err + } + return newOpenStack(cfg) + }) +} + +func (cfg Config) toAuthOptions() gophercloud.AuthOptions { + return gophercloud.AuthOptions{ + IdentityEndpoint: cfg.Global.AuthUrl, + Username: cfg.Global.Username, + UserID: cfg.Global.UserId, + Password: cfg.Global.Password, + APIKey: cfg.Global.ApiKey, + TenantID: cfg.Global.TenantId, + TenantName: cfg.Global.TenantName, + DomainID: cfg.Global.DomainId, + DomainName: cfg.Global.DomainName, + + // Persistent service, so we need to be able to renew tokens. + AllowReauth: true, + } +} + +func readConfig(config io.Reader) (Config, error) { + if config == nil { + err := fmt.Errorf("no OpenStack cloud provider config file given") + return Config{}, err + } + + var cfg Config + err := gcfg.ReadInto(&cfg, config) + return cfg, err +} + +// parseMetadataUUID reads JSON from OpenStack metadata server and parses +// instance ID out of it. +func parseMetadataUUID(jsonData []byte) (string, error) { + // We should receive an object with { 'uuid': '' } and couple of other + // properties (which we ignore). + + obj := struct{ UUID string }{} + err := json.Unmarshal(jsonData, &obj) + if err != nil { + return "", err + } + + uuid := obj.UUID + if uuid == "" { + err = fmt.Errorf("cannot parse OpenStack metadata, got empty uuid") + return "", err + } + + return uuid, nil +} + +func readInstanceID() (string, error) { + // Try to find instance ID on the local filesystem (created by cloud-init) + const instanceIDFile = "/var/lib/cloud/data/instance-id" + idBytes, err := ioutil.ReadFile(instanceIDFile) + if err == nil { + instanceID := string(idBytes) + instanceID = strings.TrimSpace(instanceID) + glog.V(3).Infof("Got instance id from %s: %s", instanceIDFile, instanceID) + if instanceID != "" { + return instanceID, nil + } + // Fall through with empty instanceID and try metadata server. + } + glog.V(5).Infof("Cannot read %s: '%v', trying metadata server", instanceIDFile, err) + + // Try to get JSON from metdata server. + resp, err := http.Get(metadataUrl) + if err != nil { + glog.V(3).Infof("Cannot read %s: %v", metadataUrl, err) + return "", err + } + + if resp.StatusCode != 200 { + err = fmt.Errorf("got unexpected status code when reading metadata from %s: %s", metadataUrl, resp.Status) + glog.V(3).Infof("%v", err) + return "", err + } + + defer resp.Body.Close() + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + glog.V(3).Infof("Cannot get HTTP response body from %s: %v", metadataUrl, err) + return "", err + } + instanceID, err := parseMetadataUUID(bodyBytes) + if err != nil { + glog.V(3).Infof("Cannot parse instance ID from metadata from %s: %v", metadataUrl, err) + return "", err + } + + glog.V(3).Infof("Got instance id from %s: %s", metadataUrl, instanceID) + return instanceID, nil +} + +func newOpenStack(cfg Config) (*OpenStack, error) { + provider, err := openstack.AuthenticatedClient(cfg.toAuthOptions()) + if err != nil { + return nil, err + } + + id, err := readInstanceID() + if err != nil { + return nil, err + } + + os := OpenStack{ + provider: provider, + region: cfg.Global.Region, + lbOpts: cfg.LoadBalancer, + localInstanceID: id, + } + + return &os, nil +} + +type Instances struct { + compute *gophercloud.ServiceClient + flavor_to_resource map[string]*api.NodeResources // keyed by flavor id +} + +// Instances returns an implementation of Instances for OpenStack. +func (os *OpenStack) Instances() (cloudprovider.Instances, bool) { + glog.V(4).Info("openstack.Instances() called") + + compute, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil { + glog.Warningf("Failed to find compute endpoint: %v", err) + return nil, false + } + + pager := flavors.ListDetail(compute, nil) + + flavor_to_resource := make(map[string]*api.NodeResources) + err = pager.EachPage(func(page pagination.Page) (bool, error) { + flavorList, err := flavors.ExtractFlavors(page) + if err != nil { + return false, err + } + for _, flavor := range flavorList { + rsrc := api.NodeResources{ + Capacity: api.ResourceList{ + api.ResourceCPU: *resource.NewQuantity(int64(flavor.VCPUs), resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(int64(flavor.RAM)*MiB, resource.BinarySI), + "openstack.org/disk": *resource.NewQuantity(int64(flavor.Disk)*GB, resource.DecimalSI), + "openstack.org/rxTxFactor": *resource.NewMilliQuantity(int64(flavor.RxTxFactor)*1000, resource.DecimalSI), + "openstack.org/swap": *resource.NewQuantity(int64(flavor.Swap)*MiB, resource.BinarySI), + }, + } + flavor_to_resource[flavor.ID] = &rsrc + } + return true, nil + }) + if err != nil { + glog.Warningf("Failed to find compute flavors: %v", err) + return nil, false + } + + glog.V(3).Infof("Found %v compute flavors", len(flavor_to_resource)) + glog.V(1).Info("Claiming to support Instances") + + return &Instances{compute, flavor_to_resource}, true +} + +func (i *Instances) List(name_filter string) ([]string, error) { + glog.V(4).Infof("openstack List(%v) called", name_filter) + + opts := servers.ListOpts{ + Name: name_filter, + Status: "ACTIVE", + } + pager := servers.List(i.compute, opts) + + ret := make([]string, 0) + err := pager.EachPage(func(page pagination.Page) (bool, error) { + sList, err := servers.ExtractServers(page) + if err != nil { + return false, err + } + for _, server := range sList { + ret = append(ret, server.Name) + } + return true, nil + }) + if err != nil { + return nil, err + } + + glog.V(3).Infof("Found %v instances matching %v: %v", + len(ret), name_filter, ret) + + return ret, nil +} + +func getServerByName(client *gophercloud.ServiceClient, name string) (*servers.Server, error) { + opts := servers.ListOpts{ + Name: fmt.Sprintf("^%s$", regexp.QuoteMeta(name)), + Status: "ACTIVE", + } + pager := servers.List(client, opts) + + serverList := make([]servers.Server, 0, 1) + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + s, err := servers.ExtractServers(page) + if err != nil { + return false, err + } + serverList = append(serverList, s...) + if len(serverList) > 1 { + return false, ErrMultipleResults + } + return true, nil + }) + if err != nil { + return nil, err + } + + if len(serverList) == 0 { + return nil, ErrNotFound + } else if len(serverList) > 1 { + return nil, ErrMultipleResults + } + + return &serverList[0], nil +} + +func getAddressesByName(client *gophercloud.ServiceClient, name string) ([]api.NodeAddress, error) { + srv, err := getServerByName(client, name) + if err != nil { + return nil, err + } + + addrs := []api.NodeAddress{} + + for network, netblob := range srv.Addresses { + list, ok := netblob.([]interface{}) + if !ok { + continue + } + + for _, item := range list { + var addressType api.NodeAddressType + + props, ok := item.(map[string]interface{}) + if !ok { + continue + } + + extIPType, ok := props["OS-EXT-IPS:type"] + if (ok && extIPType == "floating") || (!ok && network == "public") { + addressType = api.NodeExternalIP + } else { + addressType = api.NodeInternalIP + } + + tmp, ok := props["addr"] + if !ok { + continue + } + addr, ok := tmp.(string) + if !ok { + continue + } + + api.AddToNodeAddresses(&addrs, + api.NodeAddress{ + Type: addressType, + Address: addr, + }, + ) + } + } + + // AccessIPs are usually duplicates of "public" addresses. + if srv.AccessIPv4 != "" { + api.AddToNodeAddresses(&addrs, + api.NodeAddress{ + Type: api.NodeExternalIP, + Address: srv.AccessIPv4, + }, + ) + } + + if srv.AccessIPv6 != "" { + api.AddToNodeAddresses(&addrs, + api.NodeAddress{ + Type: api.NodeExternalIP, + Address: srv.AccessIPv6, + }, + ) + } + + return addrs, nil +} + +func getAddressByName(client *gophercloud.ServiceClient, name string) (string, error) { + addrs, err := getAddressesByName(client, name) + if err != nil { + return "", err + } else if len(addrs) == 0 { + return "", ErrNoAddressFound + } + + for _, addr := range addrs { + if addr.Type == api.NodeInternalIP { + return addr.Address, nil + } + } + + return addrs[0].Address, nil +} + +// Implementation of Instances.CurrentNodeName +func (i *Instances) CurrentNodeName(hostname string) (string, error) { + return hostname, nil +} + +func (i *Instances) AddSSHKeyToAllInstances(user string, keyData []byte) error { + return errors.New("unimplemented") +} + +func (i *Instances) NodeAddresses(name string) ([]api.NodeAddress, error) { + glog.V(4).Infof("NodeAddresses(%v) called", name) + + addrs, err := getAddressesByName(i.compute, name) + if err != nil { + return nil, err + } + + glog.V(4).Infof("NodeAddresses(%v) => %v", name, addrs) + return addrs, nil +} + +// ExternalID returns the cloud provider ID of the specified instance (deprecated). +func (i *Instances) ExternalID(name string) (string, error) { + srv, err := getServerByName(i.compute, name) + if err != nil { + return "", err + } + return srv.ID, nil +} + +// InstanceID returns the cloud provider ID of the specified instance. +func (i *Instances) InstanceID(name string) (string, error) { + srv, err := getServerByName(i.compute, name) + if err != nil { + return "", err + } + // In the future it is possible to also return an endpoint as: + // / + return "/" + srv.ID, nil +} + +// InstanceType returns the type of the specified instance. +func (i *Instances) InstanceType(name string) (string, error) { + return "", nil +} + +func (os *OpenStack) Clusters() (cloudprovider.Clusters, bool) { + return nil, false +} + +// ProviderName returns the cloud provider ID. +func (os *OpenStack) ProviderName() string { + return ProviderName +} + +// ScrubDNS filters DNS settings for pods. +func (os *OpenStack) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { + return nameservers, searches +} + +type LoadBalancer struct { + network *gophercloud.ServiceClient + compute *gophercloud.ServiceClient + opts LoadBalancerOpts +} + +func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) { + glog.V(4).Info("openstack.LoadBalancer() called") + + // TODO: Search for and support Rackspace loadbalancer API, and others. + network, err := openstack.NewNetworkV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil { + glog.Warningf("Failed to find neutron endpoint: %v", err) + return nil, false + } + + compute, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil { + glog.Warningf("Failed to find compute endpoint: %v", err) + return nil, false + } + + glog.V(1).Info("Claiming to support LoadBalancer") + + return &LoadBalancer{network, compute, os.lbOpts}, true +} + +func isNotFound(err error) bool { + e, ok := err.(*gophercloud.UnexpectedResponseCodeError) + return ok && e.Actual == http.StatusNotFound +} + +func getPoolByName(client *gophercloud.ServiceClient, name string) (*pools.Pool, error) { + opts := pools.ListOpts{ + Name: name, + } + pager := pools.List(client, opts) + + poolList := make([]pools.Pool, 0, 1) + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + p, err := pools.ExtractPools(page) + if err != nil { + return false, err + } + poolList = append(poolList, p...) + if len(poolList) > 1 { + return false, ErrMultipleResults + } + return true, nil + }) + if err != nil { + if isNotFound(err) { + return nil, ErrNotFound + } + return nil, err + } + + if len(poolList) == 0 { + return nil, ErrNotFound + } else if len(poolList) > 1 { + return nil, ErrMultipleResults + } + + return &poolList[0], nil +} + +func getVipByName(client *gophercloud.ServiceClient, name string) (*vips.VirtualIP, error) { + opts := vips.ListOpts{ + Name: name, + } + pager := vips.List(client, opts) + + vipList := make([]vips.VirtualIP, 0, 1) + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + v, err := vips.ExtractVIPs(page) + if err != nil { + return false, err + } + vipList = append(vipList, v...) + if len(vipList) > 1 { + return false, ErrMultipleResults + } + return true, nil + }) + if err != nil { + if isNotFound(err) { + return nil, ErrNotFound + } + return nil, err + } + + if len(vipList) == 0 { + return nil, ErrNotFound + } else if len(vipList) > 1 { + return nil, ErrMultipleResults + } + + return &vipList[0], nil +} + +func getFloatingIPByPortID(client *gophercloud.ServiceClient, portID string) (*floatingips.FloatingIP, error) { + opts := floatingips.ListOpts{ + PortID: portID, + } + pager := floatingips.List(client, opts) + + floatingIPList := make([]floatingips.FloatingIP, 0, 1) + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + f, err := floatingips.ExtractFloatingIPs(page) + if err != nil { + return false, err + } + floatingIPList = append(floatingIPList, f...) + if len(floatingIPList) > 1 { + return false, ErrMultipleResults + } + return true, nil + }) + if err != nil { + if isNotFound(err) { + return nil, ErrNotFound + } + return nil, err + } + + if len(floatingIPList) == 0 { + return nil, ErrNotFound + } else if len(floatingIPList) > 1 { + return nil, ErrMultipleResults + } + + return &floatingIPList[0], nil +} + +func (lb *LoadBalancer) GetLoadBalancer(service *api.Service) (*api.LoadBalancerStatus, bool, error) { + loadBalancerName := cloudprovider.GetLoadBalancerName(service) + vip, err := getVipByName(lb.network, loadBalancerName) + if err == ErrNotFound { + return nil, false, nil + } + if vip == nil { + return nil, false, err + } + + status := &api.LoadBalancerStatus{} + status.Ingress = []api.LoadBalancerIngress{{IP: vip.Address}} + + return status, true, err +} + +// TODO: This code currently ignores 'region' and always creates a +// loadbalancer in only the current OpenStack region. We should take +// a list of regions (from config) and query/create loadbalancers in +// each region. + +func (lb *LoadBalancer) EnsureLoadBalancer(apiService *api.Service, hosts []string, annotations map[string]string) (*api.LoadBalancerStatus, error) { + glog.V(4).Infof("EnsureLoadBalancer(%v, %v, %v, %v, %v, %v)", apiService.Namespace, apiService.Name, apiService.Spec.LoadBalancerIP, apiService.Spec.Ports, hosts, annotations) + + ports := apiService.Spec.Ports + if len(ports) > 1 { + return nil, fmt.Errorf("multiple ports are not yet supported in openstack load balancers") + } else if len(ports) == 0 { + return nil, fmt.Errorf("no ports provided to openstack load balancer") + } + + // The service controller verified all the protocols match on the ports, just check and use the first one + // TODO: Convert all error messages to use an event recorder + if ports[0].Protocol != api.ProtocolTCP { + return nil, fmt.Errorf("Only TCP LoadBalancer is supported for openstack load balancers") + } + + affinity := apiService.Spec.SessionAffinity + var persistence *vips.SessionPersistence + switch affinity { + case api.ServiceAffinityNone: + persistence = nil + case api.ServiceAffinityClientIP: + persistence = &vips.SessionPersistence{Type: "SOURCE_IP"} + default: + return nil, fmt.Errorf("unsupported load balancer affinity: %v", affinity) + } + + sourceRanges, err := service.GetLoadBalancerSourceRanges(annotations) + if err != nil { + return nil, err + } + + if !service.IsAllowAll(sourceRanges) { + return nil, fmt.Errorf("Source range restrictions are not supported for openstack load balancers") + } + + glog.V(2).Infof("Checking if openstack load balancer already exists: %s", cloudprovider.GetLoadBalancerName(apiService)) + _, exists, err := lb.GetLoadBalancer(apiService) + if err != nil { + return nil, fmt.Errorf("error checking if openstack load balancer already exists: %v", err) + } + + // TODO: Implement a more efficient update strategy for common changes than delete & create + // In particular, if we implement hosts update, we can get rid of UpdateHosts + if exists { + err := lb.EnsureLoadBalancerDeleted(apiService) + if err != nil { + return nil, fmt.Errorf("error deleting existing openstack load balancer: %v", err) + } + } + + lbmethod := lb.opts.LBMethod + if lbmethod == "" { + lbmethod = pools.LBMethodRoundRobin + } + name := cloudprovider.GetLoadBalancerName(apiService) + pool, err := pools.Create(lb.network, pools.CreateOpts{ + Name: name, + Protocol: pools.ProtocolTCP, + SubnetID: lb.opts.SubnetId, + LBMethod: lbmethod, + }).Extract() + if err != nil { + return nil, err + } + + for _, host := range hosts { + addr, err := getAddressByName(lb.compute, host) + if err != nil { + return nil, err + } + + _, err = members.Create(lb.network, members.CreateOpts{ + PoolID: pool.ID, + ProtocolPort: ports[0].NodePort, //TODO: need to handle multi-port + Address: addr, + }).Extract() + if err != nil { + pools.Delete(lb.network, pool.ID) + return nil, err + } + } + + var mon *monitors.Monitor + if lb.opts.CreateMonitor { + mon, err = monitors.Create(lb.network, monitors.CreateOpts{ + Type: monitors.TypeTCP, + Delay: int(lb.opts.MonitorDelay.Duration.Seconds()), + Timeout: int(lb.opts.MonitorTimeout.Duration.Seconds()), + MaxRetries: int(lb.opts.MonitorMaxRetries), + }).Extract() + if err != nil { + pools.Delete(lb.network, pool.ID) + return nil, err + } + + _, err = pools.AssociateMonitor(lb.network, pool.ID, mon.ID).Extract() + if err != nil { + monitors.Delete(lb.network, mon.ID) + pools.Delete(lb.network, pool.ID) + return nil, err + } + } + + createOpts := vips.CreateOpts{ + Name: name, + Description: fmt.Sprintf("Kubernetes external service %s", name), + Protocol: "TCP", + ProtocolPort: ports[0].Port, //TODO: need to handle multi-port + PoolID: pool.ID, + SubnetID: lb.opts.SubnetId, + Persistence: persistence, + } + + loadBalancerIP := apiService.Spec.LoadBalancerIP + if loadBalancerIP != "" { + createOpts.Address = loadBalancerIP + } + + vip, err := vips.Create(lb.network, createOpts).Extract() + if err != nil { + if mon != nil { + monitors.Delete(lb.network, mon.ID) + } + pools.Delete(lb.network, pool.ID) + return nil, err + } + + status := &api.LoadBalancerStatus{} + + status.Ingress = []api.LoadBalancerIngress{{IP: vip.Address}} + + if lb.opts.FloatingNetworkId != "" { + floatIPOpts := floatingips.CreateOpts{ + FloatingNetworkID: lb.opts.FloatingNetworkId, + PortID: vip.PortID, + } + floatIP, err := floatingips.Create(lb.network, floatIPOpts).Extract() + if err != nil { + return nil, err + } + + status.Ingress = append(status.Ingress, api.LoadBalancerIngress{IP: floatIP.FloatingIP}) + } + + return status, nil + +} + +func (lb *LoadBalancer) UpdateLoadBalancer(service *api.Service, hosts []string) error { + loadBalancerName := cloudprovider.GetLoadBalancerName(service) + glog.V(4).Infof("UpdateLoadBalancer(%v, %v)", loadBalancerName, hosts) + + vip, err := getVipByName(lb.network, loadBalancerName) + if err != nil { + return err + } + + // Set of member (addresses) that _should_ exist + addrs := map[string]bool{} + for _, host := range hosts { + addr, err := getAddressByName(lb.compute, host) + if err != nil { + return err + } + + addrs[addr] = true + } + + // Iterate over members that _do_ exist + pager := members.List(lb.network, members.ListOpts{PoolID: vip.PoolID}) + err = pager.EachPage(func(page pagination.Page) (bool, error) { + memList, err := members.ExtractMembers(page) + if err != nil { + return false, err + } + + for _, member := range memList { + if _, found := addrs[member.Address]; found { + // Member already exists + delete(addrs, member.Address) + } else { + // Member needs to be deleted + err = members.Delete(lb.network, member.ID).ExtractErr() + if err != nil { + return false, err + } + } + } + + return true, nil + }) + if err != nil { + return err + } + + // Anything left in addrs is a new member that needs to be added + for addr := range addrs { + _, err := members.Create(lb.network, members.CreateOpts{ + PoolID: vip.PoolID, + Address: addr, + ProtocolPort: vip.ProtocolPort, + }).Extract() + if err != nil { + return err + } + } + + return nil +} + +func (lb *LoadBalancer) EnsureLoadBalancerDeleted(service *api.Service) error { + loadBalancerName := cloudprovider.GetLoadBalancerName(service) + glog.V(4).Infof("EnsureLoadBalancerDeleted(%v)", loadBalancerName) + + vip, err := getVipByName(lb.network, loadBalancerName) + if err != nil && err != ErrNotFound { + return err + } + + if lb.opts.FloatingNetworkId != "" && vip != nil { + floatingIP, err := getFloatingIPByPortID(lb.network, vip.PortID) + if err != nil && !isNotFound(err) { + return err + } + if floatingIP != nil { + err = floatingips.Delete(lb.network, floatingIP.ID).ExtractErr() + if err != nil && !isNotFound(err) { + return err + } + } + } + + // We have to delete the VIP before the pool can be deleted, + // so no point continuing if this fails. + if vip != nil { + err := vips.Delete(lb.network, vip.ID).ExtractErr() + if err != nil && !isNotFound(err) { + return err + } + } + + var pool *pools.Pool + if vip != nil { + pool, err = pools.Get(lb.network, vip.PoolID).Extract() + if err != nil && !isNotFound(err) { + return err + } + } else { + // The VIP is gone, but it is conceivable that a Pool + // still exists that we failed to delete on some + // previous occasion. Make a best effort attempt to + // cleanup any pools with the same name as the VIP. + pool, err = getPoolByName(lb.network, service.Name) + if err != nil && err != ErrNotFound { + return err + } + } + + if pool != nil { + for _, monId := range pool.MonitorIDs { + _, err = pools.DisassociateMonitor(lb.network, pool.ID, monId).Extract() + if err != nil { + return err + } + + err = monitors.Delete(lb.network, monId).ExtractErr() + if err != nil && !isNotFound(err) { + return err + } + } + err = pools.Delete(lb.network, pool.ID).ExtractErr() + if err != nil && !isNotFound(err) { + return err + } + } + + return nil +} + +func (os *OpenStack) Zones() (cloudprovider.Zones, bool) { + glog.V(1).Info("Claiming to support Zones") + + return os, true +} +func (os *OpenStack) GetZone() (cloudprovider.Zone, error) { + glog.V(1).Infof("Current zone is %v", os.region) + + return cloudprovider.Zone{Region: os.region}, nil +} + +func (os *OpenStack) Routes() (cloudprovider.Routes, bool) { + return nil, false +} + +// Attaches given cinder volume to the compute running kubelet +func (os *OpenStack) AttachDisk(diskName string) (string, error) { + disk, err := os.getVolume(diskName) + if err != nil { + return "", err + } + cClient, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil || cClient == nil { + glog.Errorf("Unable to initialize nova client for region: %s", os.region) + return "", err + } + + if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil { + if os.localInstanceID == disk.Attachments[0]["server_id"] { + glog.V(4).Infof("Disk: %q is already attached to compute: %q", diskName, os.localInstanceID) + return disk.ID, nil + } else { + errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding", diskName, disk.Attachments[0]["server_id"]) + glog.Errorf(errMsg) + return "", errors.New(errMsg) + } + } + // add read only flag here if possible spothanis + _, err = volumeattach.Create(cClient, os.localInstanceID, &volumeattach.CreateOpts{ + VolumeID: disk.ID, + }).Extract() + if err != nil { + glog.Errorf("Failed to attach %s volume to %s compute", diskName, os.localInstanceID) + return "", err + } + glog.V(2).Infof("Successfully attached %s volume to %s compute", diskName, os.localInstanceID) + return disk.ID, nil +} + +// Detaches given cinder volume from the compute running kubelet +func (os *OpenStack) DetachDisk(partialDiskId string) error { + disk, err := os.getVolume(partialDiskId) + if err != nil { + return err + } + cClient, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil || cClient == nil { + glog.Errorf("Unable to initialize nova client for region: %s", os.region) + return err + } + if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil && os.localInstanceID == disk.Attachments[0]["server_id"] { + // This is a blocking call and effects kubelet's performance directly. + // We should consider kicking it out into a separate routine, if it is bad. + err = volumeattach.Delete(cClient, os.localInstanceID, disk.ID).ExtractErr() + if err != nil { + glog.Errorf("Failed to delete volume %s from compute %s attached %v", disk.ID, os.localInstanceID, err) + return err + } + glog.V(2).Infof("Successfully detached volume: %s from compute: %s", disk.ID, os.localInstanceID) + } else { + errMsg := fmt.Sprintf("Disk: %s has no attachments or is not attached to compute: %s", disk.Name, os.localInstanceID) + glog.Errorf(errMsg) + return errors.New(errMsg) + } + return nil +} + +// Takes a partial/full disk id or diskname +func (os *OpenStack) getVolume(diskName string) (volumes.Volume, error) { + sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + + var volume volumes.Volume + if err != nil || sClient == nil { + glog.Errorf("Unable to initialize cinder client for region: %s", os.region) + return volume, err + } + + err = volumes.List(sClient, nil).EachPage(func(page pagination.Page) (bool, error) { + vols, err := volumes.ExtractVolumes(page) + if err != nil { + glog.Errorf("Failed to extract volumes: %v", err) + return false, err + } else { + for _, v := range vols { + glog.V(4).Infof("%s %s %v", v.ID, v.Name, v.Attachments) + if v.Name == diskName || strings.Contains(v.ID, diskName) { + volume = v + return true, nil + } + } + } + // if it reached here then no disk with the given name was found. + errmsg := fmt.Sprintf("Unable to find disk: %s in region %s", diskName, os.region) + return false, errors.New(errmsg) + }) + if err != nil { + glog.Errorf("Error occured getting volume: %s", diskName) + return volume, err + } + return volume, err +} + +// Create a volume of given size (in GiB) +func (os *OpenStack) CreateVolume(name string, size int, tags *map[string]string) (volumeName string, err error) { + + sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + + if err != nil || sClient == nil { + glog.Errorf("Unable to initialize cinder client for region: %s", os.region) + return "", err + } + + opts := volumes.CreateOpts{ + Name: name, + Size: size, + } + if tags != nil { + opts.Metadata = *tags + } + vol, err := volumes.Create(sClient, opts).Extract() + if err != nil { + glog.Errorf("Failed to create a %d GB volume: %v", size, err) + return "", err + } + glog.Infof("Created volume %v", vol.ID) + return vol.ID, err +} + +func (os *OpenStack) DeleteVolume(volumeName string) error { + sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + + if err != nil || sClient == nil { + glog.Errorf("Unable to initialize cinder client for region: %s", os.region) + return err + } + err = volumes.Delete(sClient, volumeName).ExtractErr() + if err != nil { + glog.Errorf("Cannot delete volume %s: %v", volumeName, err) + } + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/openstack/openstack_test.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/openstack/openstack_test.go new file mode 100644 index 000000000..84ff57dd8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/openstack/openstack_test.go @@ -0,0 +1,229 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 openstack + +import ( + "os" + "strings" + "testing" + "time" + + "k8s.io/kubernetes/pkg/util/rand" + + "github.com/rackspace/gophercloud" + "k8s.io/kubernetes/pkg/api" +) + +func TestReadConfig(t *testing.T) { + _, err := readConfig(nil) + if err == nil { + t.Errorf("Should fail when no config is provided: %s", err) + } + + cfg, err := readConfig(strings.NewReader(` +[Global] +auth-url = http://auth.url +username = user +[LoadBalancer] +create-monitor = yes +monitor-delay = 1m +monitor-timeout = 30s +monitor-max-retries = 3 +`)) + if err != nil { + t.Fatalf("Should succeed when a valid config is provided: %s", err) + } + if cfg.Global.AuthUrl != "http://auth.url" { + t.Errorf("incorrect authurl: %s", cfg.Global.AuthUrl) + } + + if !cfg.LoadBalancer.CreateMonitor { + t.Errorf("incorrect lb.createmonitor: %t", cfg.LoadBalancer.CreateMonitor) + } + if cfg.LoadBalancer.MonitorDelay.Duration != 1*time.Minute { + t.Errorf("incorrect lb.monitordelay: %s", cfg.LoadBalancer.MonitorDelay) + } + if cfg.LoadBalancer.MonitorTimeout.Duration != 30*time.Second { + t.Errorf("incorrect lb.monitortimeout: %s", cfg.LoadBalancer.MonitorTimeout) + } + if cfg.LoadBalancer.MonitorMaxRetries != 3 { + t.Errorf("incorrect lb.monitormaxretries: %d", cfg.LoadBalancer.MonitorMaxRetries) + } +} + +func TestToAuthOptions(t *testing.T) { + cfg := Config{} + cfg.Global.Username = "user" + // etc. + + ao := cfg.toAuthOptions() + + if !ao.AllowReauth { + t.Errorf("Will need to be able to reauthenticate") + } + if ao.Username != cfg.Global.Username { + t.Errorf("Username %s != %s", ao.Username, cfg.Global.Username) + } +} + +// This allows acceptance testing against an existing OpenStack +// install, using the standard OS_* OpenStack client environment +// variables. +// FIXME: it would be better to hermetically test against canned JSON +// requests/responses. +func configFromEnv() (cfg Config, ok bool) { + cfg.Global.AuthUrl = os.Getenv("OS_AUTH_URL") + + cfg.Global.TenantId = os.Getenv("OS_TENANT_ID") + // Rax/nova _insists_ that we don't specify both tenant ID and name + if cfg.Global.TenantId == "" { + cfg.Global.TenantName = os.Getenv("OS_TENANT_NAME") + } + + cfg.Global.Username = os.Getenv("OS_USERNAME") + cfg.Global.Password = os.Getenv("OS_PASSWORD") + cfg.Global.ApiKey = os.Getenv("OS_API_KEY") + cfg.Global.Region = os.Getenv("OS_REGION_NAME") + cfg.Global.DomainId = os.Getenv("OS_DOMAIN_ID") + cfg.Global.DomainName = os.Getenv("OS_DOMAIN_NAME") + + ok = (cfg.Global.AuthUrl != "" && + cfg.Global.Username != "" && + (cfg.Global.Password != "" || cfg.Global.ApiKey != "") && + (cfg.Global.TenantId != "" || cfg.Global.TenantName != "" || + cfg.Global.DomainId != "" || cfg.Global.DomainName != "")) + + return +} + +func TestNewOpenStack(t *testing.T) { + cfg, ok := configFromEnv() + if !ok { + t.Skipf("No config found in environment") + } + + _, err := newOpenStack(cfg) + if err != nil { + t.Fatalf("Failed to construct/authenticate OpenStack: %s", err) + } +} + +func TestInstances(t *testing.T) { + cfg, ok := configFromEnv() + if !ok { + t.Skipf("No config found in environment") + } + + os, err := newOpenStack(cfg) + if err != nil { + t.Fatalf("Failed to construct/authenticate OpenStack: %s", err) + } + + i, ok := os.Instances() + if !ok { + t.Fatalf("Instances() returned false") + } + + srvs, err := i.List(".") + if err != nil { + t.Fatalf("Instances.List() failed: %s", err) + } + if len(srvs) == 0 { + t.Fatalf("Instances.List() returned zero servers") + } + t.Logf("Found servers (%d): %s\n", len(srvs), srvs) + + addrs, err := i.NodeAddresses(srvs[0]) + if err != nil { + t.Fatalf("Instances.NodeAddresses(%s) failed: %s", srvs[0], err) + } + t.Logf("Found NodeAddresses(%s) = %s\n", srvs[0], addrs) +} + +func TestLoadBalancer(t *testing.T) { + cfg, ok := configFromEnv() + if !ok { + t.Skipf("No config found in environment") + } + + os, err := newOpenStack(cfg) + if err != nil { + t.Fatalf("Failed to construct/authenticate OpenStack: %s", err) + } + + lb, ok := os.LoadBalancer() + if !ok { + t.Fatalf("LoadBalancer() returned false - perhaps your stack doesn't support Neutron?") + } + + _, exists, err := lb.GetLoadBalancer(&api.Service{ObjectMeta: api.ObjectMeta{Name: "noexist"}}) + if err != nil { + t.Fatalf("GetLoadBalancer(\"noexist\") returned error: %s", err) + } + if exists { + t.Fatalf("GetLoadBalancer(\"noexist\") returned exists") + } +} + +func TestZones(t *testing.T) { + os := OpenStack{ + provider: &gophercloud.ProviderClient{ + IdentityBase: "http://auth.url/", + }, + region: "myRegion", + } + + z, ok := os.Zones() + if !ok { + t.Fatalf("Zones() returned false") + } + + zone, err := z.GetZone() + if err != nil { + t.Fatalf("GetZone() returned error: %s", err) + } + + if zone.Region != "myRegion" { + t.Fatalf("GetZone() returned wrong region (%s)", zone.Region) + } +} + +func TestVolumes(t *testing.T) { + cfg, ok := configFromEnv() + if !ok { + t.Skipf("No config found in environment") + } + + os, err := newOpenStack(cfg) + if err != nil { + t.Fatalf("Failed to construct/authenticate OpenStack: %s", err) + } + + tags := map[string]string{ + "test": "value", + } + vol, err := os.CreateVolume("kubernetes-test-volume-"+rand.String(10), 1, &tags) + if err != nil { + t.Fatalf("Cannot create a new Cinder volume: %v", err) + } + + err = os.DeleteVolume(vol) + if err != nil { + t.Fatalf("Cannot delete Cinder volume %s: %v", vol, err) + } + +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt/ovirt.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt/ovirt.go new file mode 100644 index 000000000..d2ba03c10 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt/ovirt.go @@ -0,0 +1,291 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 ovirt + +import ( + "encoding/xml" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "path" + "sort" + "strings" + + "gopkg.in/gcfg.v1" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/cloudprovider" +) + +const ProviderName = "ovirt" + +type OVirtInstance struct { + UUID string + Name string + IPAddress string +} + +type OVirtInstanceMap map[string]OVirtInstance + +type OVirtCloud struct { + VmsRequest *url.URL + HostsRequest *url.URL +} + +type OVirtApiConfig struct { + Connection struct { + ApiEntry string `gcfg:"uri"` + Username string `gcfg:"username"` + Password string `gcfg:"password"` + } + Filters struct { + VmsQuery string `gcfg:"vms"` + } +} + +type XmlVmAddress struct { + Address string `xml:"address,attr"` +} + +type XmlVmInfo struct { + UUID string `xml:"id,attr"` + Name string `xml:"name"` + Hostname string `xml:"guest_info>fqdn"` + Addresses []XmlVmAddress `xml:"guest_info>ips>ip"` + State string `xml:"status>state"` +} + +type XmlVmsList struct { + XMLName xml.Name `xml:"vms"` + Vm []XmlVmInfo `xml:"vm"` +} + +func init() { + cloudprovider.RegisterCloudProvider(ProviderName, + func(config io.Reader) (cloudprovider.Interface, error) { + return newOVirtCloud(config) + }) +} + +func newOVirtCloud(config io.Reader) (*OVirtCloud, error) { + if config == nil { + return nil, fmt.Errorf("missing configuration file for ovirt cloud provider") + } + + oVirtConfig := OVirtApiConfig{} + + /* defaults */ + oVirtConfig.Connection.Username = "admin@internal" + + if err := gcfg.ReadInto(&oVirtConfig, config); err != nil { + return nil, err + } + + if oVirtConfig.Connection.ApiEntry == "" { + return nil, fmt.Errorf("missing ovirt uri in cloud provider configuration") + } + + request, err := url.Parse(oVirtConfig.Connection.ApiEntry) + if err != nil { + return nil, err + } + + request.Path = path.Join(request.Path, "vms") + request.User = url.UserPassword(oVirtConfig.Connection.Username, oVirtConfig.Connection.Password) + request.RawQuery = url.Values{"search": {oVirtConfig.Filters.VmsQuery}}.Encode() + + return &OVirtCloud{VmsRequest: request}, nil +} + +func (aws *OVirtCloud) Clusters() (cloudprovider.Clusters, bool) { + return nil, false +} + +// ProviderName returns the cloud provider ID. +func (v *OVirtCloud) ProviderName() string { + return ProviderName +} + +// ScrubDNS filters DNS settings for pods. +func (v *OVirtCloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { + return nameservers, searches +} + +// LoadBalancer returns an implementation of LoadBalancer for oVirt cloud +func (v *OVirtCloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) { + return nil, false +} + +// Instances returns an implementation of Instances for oVirt cloud +func (v *OVirtCloud) Instances() (cloudprovider.Instances, bool) { + return v, true +} + +// Zones returns an implementation of Zones for oVirt cloud +func (v *OVirtCloud) Zones() (cloudprovider.Zones, bool) { + return nil, false +} + +// Routes returns an implementation of Routes for oVirt cloud +func (v *OVirtCloud) Routes() (cloudprovider.Routes, bool) { + return nil, false +} + +// NodeAddresses returns the NodeAddresses of a particular machine instance +func (v *OVirtCloud) NodeAddresses(name string) ([]api.NodeAddress, error) { + instance, err := v.fetchInstance(name) + if err != nil { + return nil, err + } + + var address net.IP + + if instance.IPAddress != "" { + address = net.ParseIP(instance.IPAddress) + if address == nil { + return nil, fmt.Errorf("couldn't parse address: %s", instance.IPAddress) + } + } else { + resolved, err := net.LookupIP(name) + if err != nil || len(resolved) < 1 { + return nil, fmt.Errorf("couldn't lookup address: %s", name) + } + address = resolved[0] + } + + return []api.NodeAddress{{Type: api.NodeLegacyHostIP, Address: address.String()}}, nil +} + +// ExternalID returns the cloud provider ID of the specified instance (deprecated). +func (v *OVirtCloud) ExternalID(name string) (string, error) { + instance, err := v.fetchInstance(name) + if err != nil { + return "", err + } + return instance.UUID, nil +} + +// InstanceID returns the cloud provider ID of the specified instance. +func (v *OVirtCloud) InstanceID(name string) (string, error) { + instance, err := v.fetchInstance(name) + if err != nil { + return "", err + } + // TODO: define a way to identify the provider instance to complete + // the format /. + return "/" + instance.UUID, err +} + +// InstanceType returns the type of the specified instance. +func (v *OVirtCloud) InstanceType(name string) (string, error) { + return "", nil +} + +func getInstancesFromXml(body io.Reader) (OVirtInstanceMap, error) { + if body == nil { + return nil, fmt.Errorf("ovirt rest-api response body is missing") + } + + content, err := ioutil.ReadAll(body) + if err != nil { + return nil, err + } + + vmlist := XmlVmsList{} + + if err := xml.Unmarshal(content, &vmlist); err != nil { + return nil, err + } + + instances := make(OVirtInstanceMap) + + for _, vm := range vmlist.Vm { + // Always return only vms that are up and running + if vm.Hostname != "" && strings.ToLower(vm.State) == "up" { + address := "" + if len(vm.Addresses) > 0 { + address = vm.Addresses[0].Address + } + + instances[vm.Hostname] = OVirtInstance{ + UUID: vm.UUID, + Name: vm.Name, + IPAddress: address, + } + } + } + + return instances, nil +} + +func (v *OVirtCloud) fetchAllInstances() (OVirtInstanceMap, error) { + response, err := http.Get(v.VmsRequest.String()) + if err != nil { + return nil, err + } + + defer response.Body.Close() + + return getInstancesFromXml(response.Body) +} + +func (v *OVirtCloud) fetchInstance(name string) (*OVirtInstance, error) { + allInstances, err := v.fetchAllInstances() + if err != nil { + return nil, err + } + + instance, found := allInstances[name] + if !found { + return nil, fmt.Errorf("cannot find instance: %s", name) + } + + return &instance, nil +} + +func (m *OVirtInstanceMap) ListSortedNames() []string { + var names []string + + for k := range *m { + names = append(names, k) + } + + sort.Strings(names) + + return names +} + +// List enumerates the set of minions instances known by the cloud provider +func (v *OVirtCloud) List(filter string) ([]string, error) { + instances, err := v.fetchAllInstances() + if err != nil { + return nil, err + } + return instances.ListSortedNames(), nil +} + +// Implementation of Instances.CurrentNodeName +func (v *OVirtCloud) CurrentNodeName(hostname string) (string, error) { + return hostname, nil +} + +func (v *OVirtCloud) AddSSHKeyToAllInstances(user string, keyData []byte) error { + return errors.New("unimplemented") +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt/ovirt_test.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt/ovirt_test.go new file mode 100644 index 000000000..c76bde726 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt/ovirt_test.go @@ -0,0 +1,126 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 ovirt + +import ( + "io" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/cloudprovider" +) + +func TestOVirtCloudConfiguration(t *testing.T) { + config1 := (io.Reader)(nil) + + _, err1 := cloudprovider.GetCloudProvider("ovirt", config1) + if err1 == nil { + t.Fatalf("An error is expected when the configuration is missing") + } + + config2 := strings.NewReader("") + + _, err2 := cloudprovider.GetCloudProvider("ovirt", config2) + if err2 == nil { + t.Fatalf("An error is expected when the configuration is empty") + } + + config3 := strings.NewReader(` +[connection] + `) + + _, err3 := cloudprovider.GetCloudProvider("ovirt", config3) + if err3 == nil { + t.Fatalf("An error is expected when the uri is missing") + } + + config4 := strings.NewReader(` +[connection] +uri = https://localhost:8443/ovirt-engine/api +`) + + _, err4 := cloudprovider.GetCloudProvider("ovirt", config4) + if err4 != nil { + t.Fatalf("Unexpected error creating the provider: %s", err4) + } +} + +func TestOVirtCloudXmlParsing(t *testing.T) { + body1 := (io.Reader)(nil) + + _, err1 := getInstancesFromXml(body1) + if err1 == nil { + t.Fatalf("An error is expected when body is missing") + } + + body2 := strings.NewReader("") + + _, err2 := getInstancesFromXml(body2) + if err2 == nil { + t.Fatalf("An error is expected when body is empty") + } + + body3 := strings.NewReader(` + + + +`) + + instances3, err3 := getInstancesFromXml(body3) + if err3 != nil { + t.Fatalf("Unexpected error listing instances: %s", err3) + } + if len(instances3) > 0 { + t.Fatalf("Unexpected number of instance(s): %d", len(instances3)) + } + + body4 := strings.NewReader(` + + + Up + host1 + + + + + + Up + + + Down + host2 + + + Up + host3 + + +`) + + instances4, err4 := getInstancesFromXml(body4) + if err4 != nil { + t.Fatalf("Unexpected error listing instances: %s", err4) + } + if len(instances4) != 2 { + t.Fatalf("Unexpected number of instance(s): %d", len(instances4)) + } + + names := instances4.ListSortedNames() + if names[0] != "host1" || names[1] != "host3" { + t.Fatalf("Unexpected instance(s): %s", instances4) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/providers.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/providers.go new file mode 100644 index 000000000..6664e8903 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/providers.go @@ -0,0 +1,27 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 cloudprovider + +import ( + // Cloud providers + _ "k8s.io/kubernetes/pkg/cloudprovider/providers/aws" + _ "k8s.io/kubernetes/pkg/cloudprovider/providers/gce" + _ "k8s.io/kubernetes/pkg/cloudprovider/providers/mesos" + _ "k8s.io/kubernetes/pkg/cloudprovider/providers/openstack" + _ "k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt" + _ "k8s.io/kubernetes/pkg/cloudprovider/providers/rackspace" +) diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/rackspace/MAINTAINERS.md b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/rackspace/MAINTAINERS.md new file mode 100644 index 000000000..4c72c1a0a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/rackspace/MAINTAINERS.md @@ -0,0 +1,6 @@ +# Maintainers + +* [Thom May](https://github.com/thommay) + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/pkg/cloudprovider/providers/rackspace/MAINTAINERS.md?pixel)]() diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/rackspace/rackspace.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/rackspace/rackspace.go new file mode 100644 index 000000000..198843816 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/rackspace/rackspace.go @@ -0,0 +1,393 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 rackspace + +import ( + "errors" + "fmt" + "io" + "net" + "regexp" + "time" + + "github.com/rackspace/gophercloud" + osservers "github.com/rackspace/gophercloud/openstack/compute/v2/servers" + "github.com/rackspace/gophercloud/pagination" + "github.com/rackspace/gophercloud/rackspace" + "github.com/rackspace/gophercloud/rackspace/compute/v2/servers" + "gopkg.in/gcfg.v1" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/cloudprovider" +) + +const ProviderName = "rackspace" + +var ErrNotFound = errors.New("Failed to find object") +var ErrMultipleResults = errors.New("Multiple results where only one expected") +var ErrNoAddressFound = errors.New("No address found for host") +var ErrAttrNotFound = errors.New("Expected attribute not found") + +// encoding.TextUnmarshaler interface for time.Duration +type MyDuration struct { + time.Duration +} + +func (d *MyDuration) UnmarshalText(text []byte) error { + res, err := time.ParseDuration(string(text)) + if err != nil { + return err + } + d.Duration = res + return nil +} + +type LoadBalancerOpts struct { + SubnetId string `gcfg:"subnet-id"` // required + CreateMonitor bool `gcfg:"create-monitor"` + MonitorDelay MyDuration `gcfg:"monitor-delay"` + MonitorTimeout MyDuration `gcfg:"monitor-timeout"` + MonitorMaxRetries uint `gcfg:"monitor-max-retries"` +} + +// Rackspace is an implementation of cloud provider Interface for Rackspace. +type Rackspace struct { + provider *gophercloud.ProviderClient + region string + lbOpts LoadBalancerOpts +} + +type Config struct { + Global struct { + AuthUrl string `gcfg:"auth-url"` + Username string + UserId string `gcfg:"user-id"` + Password string + ApiKey string `gcfg:"api-key"` + TenantId string `gcfg:"tenant-id"` + TenantName string `gcfg:"tenant-name"` + DomainId string `gcfg:"domain-id"` + DomainName string `gcfg:"domain-name"` + Region string + } + LoadBalancer LoadBalancerOpts +} + +func init() { + cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) { + cfg, err := readConfig(config) + if err != nil { + return nil, err + } + return newRackspace(cfg) + }) +} + +func (cfg Config) toAuthOptions() gophercloud.AuthOptions { + return gophercloud.AuthOptions{ + IdentityEndpoint: cfg.Global.AuthUrl, + Username: cfg.Global.Username, + UserID: cfg.Global.UserId, + Password: cfg.Global.Password, + APIKey: cfg.Global.ApiKey, + TenantID: cfg.Global.TenantId, + TenantName: cfg.Global.TenantName, + + // Persistent service, so we need to be able to renew tokens + AllowReauth: true, + } +} + +func readConfig(config io.Reader) (Config, error) { + if config == nil { + err := fmt.Errorf("no Rackspace cloud provider config file given") + return Config{}, err + } + + var cfg Config + err := gcfg.ReadInto(&cfg, config) + return cfg, err +} + +func newRackspace(cfg Config) (*Rackspace, error) { + provider, err := rackspace.AuthenticatedClient(cfg.toAuthOptions()) + if err != nil { + return nil, err + } + + os := Rackspace{ + provider: provider, + region: cfg.Global.Region, + lbOpts: cfg.LoadBalancer, + } + return &os, nil +} + +type Instances struct { + compute *gophercloud.ServiceClient +} + +// Instances returns an implementation of Instances for Rackspace. +func (os *Rackspace) Instances() (cloudprovider.Instances, bool) { + glog.V(2).Info("rackspace.Instances() called") + + compute, err := rackspace.NewComputeV2(os.provider, gophercloud.EndpointOpts{ + Region: os.region, + }) + if err != nil { + glog.Warningf("Failed to find compute endpoint: %v", err) + return nil, false + } + glog.V(1).Info("Claiming to support Instances") + + return &Instances{compute}, true +} + +func (i *Instances) List(name_filter string) ([]string, error) { + glog.V(2).Infof("rackspace List(%v) called", name_filter) + + opts := osservers.ListOpts{ + Name: name_filter, + Status: "ACTIVE", + } + pager := servers.List(i.compute, opts) + + ret := make([]string, 0) + err := pager.EachPage(func(page pagination.Page) (bool, error) { + sList, err := servers.ExtractServers(page) + if err != nil { + return false, err + } + for _, server := range sList { + ret = append(ret, server.Name) + } + return true, nil + }) + if err != nil { + return nil, err + } + + glog.V(2).Infof("Found %v entries: %v", len(ret), ret) + + return ret, nil +} + +func serverHasAddress(srv osservers.Server, ip string) bool { + if ip == firstAddr(srv.Addresses["private"]) { + return true + } + if ip == firstAddr(srv.Addresses["public"]) { + return true + } + if ip == srv.AccessIPv4 { + return true + } + if ip == srv.AccessIPv6 { + return true + } + return false +} + +func getServerByAddress(client *gophercloud.ServiceClient, name string) (*osservers.Server, error) { + pager := servers.List(client, nil) + + serverList := make([]osservers.Server, 0, 1) + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + s, err := servers.ExtractServers(page) + if err != nil { + return false, err + } + for _, v := range s { + if serverHasAddress(v, name) { + serverList = append(serverList, v) + } + } + if len(serverList) > 1 { + return false, ErrMultipleResults + } + return true, nil + }) + if err != nil { + return nil, err + } + + if len(serverList) == 0 { + return nil, ErrNotFound + } else if len(serverList) > 1 { + return nil, ErrMultipleResults + } + + return &serverList[0], nil +} + +func getServerByName(client *gophercloud.ServiceClient, name string) (*osservers.Server, error) { + if net.ParseIP(name) != nil { + // we're an IP, so we'll have to walk the full list of servers to + // figure out which one we are. + return getServerByAddress(client, name) + } + opts := osservers.ListOpts{ + Name: fmt.Sprintf("^%s$", regexp.QuoteMeta(name)), + Status: "ACTIVE", + } + pager := servers.List(client, opts) + + serverList := make([]osservers.Server, 0, 1) + + err := pager.EachPage(func(page pagination.Page) (bool, error) { + s, err := servers.ExtractServers(page) + if err != nil { + return false, err + } + serverList = append(serverList, s...) + if len(serverList) > 1 { + return false, ErrMultipleResults + } + return true, nil + }) + if err != nil { + return nil, err + } + + if len(serverList) == 0 { + return nil, ErrNotFound + } else if len(serverList) > 1 { + return nil, ErrMultipleResults + } + + return &serverList[0], nil +} + +func firstAddr(netblob interface{}) string { + // Run-time types for the win :( + list, ok := netblob.([]interface{}) + if !ok || len(list) < 1 { + return "" + } + props, ok := list[0].(map[string]interface{}) + if !ok { + return "" + } + tmp, ok := props["addr"] + if !ok { + return "" + } + addr, ok := tmp.(string) + if !ok { + return "" + } + return addr +} + +func getAddressByName(api *gophercloud.ServiceClient, name string) (string, error) { + srv, err := getServerByName(api, name) + if err != nil { + return "", err + } + + var s string + if s == "" { + s = firstAddr(srv.Addresses["private"]) + } + if s == "" { + s = firstAddr(srv.Addresses["public"]) + } + if s == "" { + s = srv.AccessIPv4 + } + if s == "" { + s = srv.AccessIPv6 + } + if s == "" { + return "", ErrNoAddressFound + } + return s, nil +} + +func (i *Instances) NodeAddresses(name string) ([]api.NodeAddress, error) { + glog.V(2).Infof("NodeAddresses(%v) called", name) + + ip, err := getAddressByName(i.compute, name) + if err != nil { + return nil, err + } + + glog.V(2).Infof("NodeAddresses(%v) => %v", name, ip) + + // net.ParseIP().String() is to maintain compatibility with the old code + return []api.NodeAddress{{Type: api.NodeLegacyHostIP, Address: net.ParseIP(ip).String()}}, nil +} + +// ExternalID returns the cloud provider ID of the specified instance (deprecated). +func (i *Instances) ExternalID(name string) (string, error) { + return "", fmt.Errorf("unimplemented") +} + +// InstanceID returns the cloud provider ID of the specified instance. +func (i *Instances) InstanceID(name string) (string, error) { + return "", nil +} + +// InstanceType returns the type of the specified instance. +func (i *Instances) InstanceType(name string) (string, error) { + return "", nil +} + +func (i *Instances) AddSSHKeyToAllInstances(user string, keyData []byte) error { + return errors.New("unimplemented") +} + +// Implementation of Instances.CurrentNodeName +func (i *Instances) CurrentNodeName(hostname string) (string, error) { + return hostname, nil +} + +func (os *Rackspace) Clusters() (cloudprovider.Clusters, bool) { + return nil, false +} + +// ProviderName returns the cloud provider ID. +func (os *Rackspace) ProviderName() string { + return ProviderName +} + +// ScrubDNS filters DNS settings for pods. +func (os *Rackspace) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { + return nameservers, searches +} + +func (os *Rackspace) LoadBalancer() (cloudprovider.LoadBalancer, bool) { + return nil, false +} + +func (os *Rackspace) Zones() (cloudprovider.Zones, bool) { + glog.V(1).Info("Claiming to support Zones") + + return os, true +} + +func (os *Rackspace) Routes() (cloudprovider.Routes, bool) { + return nil, false +} + +func (os *Rackspace) GetZone() (cloudprovider.Zone, error) { + glog.V(1).Infof("Current zone is %v", os.region) + + return cloudprovider.Zone{Region: os.region}, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/rackspace/rackspace_test.go b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/rackspace/rackspace_test.go new file mode 100644 index 000000000..bc3738310 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/rackspace/rackspace_test.go @@ -0,0 +1,175 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 rackspace + +import ( + "os" + "strings" + "testing" + "time" + + "github.com/rackspace/gophercloud" +) + +func TestReadConfig(t *testing.T) { + _, err := readConfig(nil) + if err == nil { + t.Errorf("Should fail when no config is provided: %s", err) + } + + cfg, err := readConfig(strings.NewReader(` +[Global] +auth-url = http://auth.url +username = user +[LoadBalancer] +create-monitor = yes +monitor-delay = 1m +monitor-timeout = 30s +monitor-max-retries = 3 +`)) + if err != nil { + t.Fatalf("Should succeed when a valid config is provided: %s", err) + } + if cfg.Global.AuthUrl != "http://auth.url" { + t.Errorf("incorrect authurl: %s", cfg.Global.AuthUrl) + } + + if !cfg.LoadBalancer.CreateMonitor { + t.Errorf("incorrect lb.createmonitor: %t", cfg.LoadBalancer.CreateMonitor) + } + if cfg.LoadBalancer.MonitorDelay.Duration != 1*time.Minute { + t.Errorf("incorrect lb.monitordelay: %s", cfg.LoadBalancer.MonitorDelay) + } + if cfg.LoadBalancer.MonitorTimeout.Duration != 30*time.Second { + t.Errorf("incorrect lb.monitortimeout: %s", cfg.LoadBalancer.MonitorTimeout) + } + if cfg.LoadBalancer.MonitorMaxRetries != 3 { + t.Errorf("incorrect lb.monitormaxretries: %d", cfg.LoadBalancer.MonitorMaxRetries) + } +} + +func TestToAuthOptions(t *testing.T) { + cfg := Config{} + cfg.Global.Username = "user" + // etc. + + ao := cfg.toAuthOptions() + + if !ao.AllowReauth { + t.Errorf("Will need to be able to reauthenticate") + } + if ao.Username != cfg.Global.Username { + t.Errorf("Username %s != %s", ao.Username, cfg.Global.Username) + } +} + +// This allows acceptance testing against an existing Rackspace +// install, using the standard OS_* Rackspace client environment +// variables. +// FIXME: it would be better to hermetically test against canned JSON +// requests/responses. +func configFromEnv() (cfg Config, ok bool) { + cfg.Global.AuthUrl = os.Getenv("OS_AUTH_URL") + + cfg.Global.TenantId = os.Getenv("OS_TENANT_ID") + // Rax/nova _insists_ that we don't specify both tenant ID and name + if cfg.Global.TenantId == "" { + cfg.Global.TenantName = os.Getenv("OS_TENANT_NAME") + } + + cfg.Global.Username = os.Getenv("OS_USERNAME") + cfg.Global.Password = os.Getenv("OS_PASSWORD") + cfg.Global.ApiKey = os.Getenv("OS_API_KEY") + cfg.Global.Region = os.Getenv("OS_REGION_NAME") + cfg.Global.DomainId = os.Getenv("OS_DOMAIN_ID") + cfg.Global.DomainName = os.Getenv("OS_DOMAIN_NAME") + + ok = (cfg.Global.AuthUrl != "" && + cfg.Global.Username != "" && + (cfg.Global.Password != "" || cfg.Global.ApiKey != "") && + (cfg.Global.TenantId != "" || cfg.Global.TenantName != "" || + cfg.Global.DomainId != "" || cfg.Global.DomainName != "")) + + return +} + +func TestNewRackspace(t *testing.T) { + cfg, ok := configFromEnv() + if !ok { + t.Skipf("No config found in environment") + } + + _, err := newRackspace(cfg) + if err != nil { + t.Fatalf("Failed to construct/authenticate Rackspace: %s", err) + } +} + +func TestInstances(t *testing.T) { + cfg, ok := configFromEnv() + if !ok { + t.Skipf("No config found in environment") + } + + os, err := newRackspace(cfg) + if err != nil { + t.Fatalf("Failed to construct/authenticate Rackspace: %s", err) + } + + i, ok := os.Instances() + if !ok { + t.Fatalf("Instances() returned false") + } + + srvs, err := i.List(".") + if err != nil { + t.Fatalf("Instances.List() failed: %s", err) + } + if len(srvs) == 0 { + t.Fatalf("Instances.List() returned zero servers") + } + t.Logf("Found servers (%d): %s\n", len(srvs), srvs) + + addrs, err := i.NodeAddresses(srvs[0]) + if err != nil { + t.Fatalf("Instances.NodeAddresses(%s) failed: %s", srvs[0], err) + } + t.Logf("Found NodeAddresses(%s) = %s\n", srvs[0], addrs) +} + +func TestZones(t *testing.T) { + os := Rackspace{ + provider: &gophercloud.ProviderClient{ + IdentityBase: "http://auth.url/", + }, + region: "myRegion", + } + + z, ok := os.Zones() + if !ok { + t.Fatalf("Zones() returned false") + } + + zone, err := z.GetZone() + if err != nil { + t.Fatalf("GetZone() returned error: %s", err) + } + + if zone.Region != "myRegion" { + t.Fatalf("GetZone() returned wrong region (%s)", zone.Region) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/OWNERS b/vendor/k8s.io/kubernetes/pkg/controller/OWNERS new file mode 100644 index 000000000..35859cd8c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/OWNERS @@ -0,0 +1,5 @@ +assignees: + - bprashanth + - davidopp + - derekwaynecarr + - mikedanese diff --git a/vendor/k8s.io/kubernetes/pkg/controller/controller_utils.go b/vendor/k8s.io/kubernetes/pkg/controller/controller_utils.go new file mode 100644 index 000000000..ff043d24e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/controller_utils.go @@ -0,0 +1,648 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 controller + +import ( + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/integer" + "k8s.io/kubernetes/pkg/util/sets" +) + +const ( + CreatedByAnnotation = "kubernetes.io/created-by" + + // If a watch drops a delete event for a pod, it'll take this long + // before a dormant controller waiting for those packets is woken up anyway. It is + // specifically targeted at the case where some problem prevents an update + // of expectations, without it the controller could stay asleep forever. This should + // be set based on the expected latency of watch events. + // + // Currently a controller can service (create *and* observe the watch events for said + // creation) about 10 pods a second, so it takes about 1 min to service + // 500 pods. Just creation is limited to 20qps, and watching happens with ~10-30s + // latency/pod at the scale of 3000 pods over 100 nodes. + ExpectationsTimeout = 5 * time.Minute +) + +var ( + KeyFunc = framework.DeletionHandlingMetaNamespaceKeyFunc +) + +type ResyncPeriodFunc func() time.Duration + +// Returns 0 for resyncPeriod in case resyncing is not needed. +func NoResyncPeriodFunc() time.Duration { + return 0 +} + +// StaticResyncPeriodFunc returns the resync period specified +func StaticResyncPeriodFunc(resyncPeriod time.Duration) ResyncPeriodFunc { + return func() time.Duration { + return resyncPeriod + } +} + +// Expectations are a way for controllers to tell the controller manager what they expect. eg: +// ControllerExpectations: { +// controller1: expects 2 adds in 2 minutes +// controller2: expects 2 dels in 2 minutes +// controller3: expects -1 adds in 2 minutes => controller3's expectations have already been met +// } +// +// Implementation: +// ControlleeExpectation = pair of atomic counters to track controllee's creation/deletion +// ControllerExpectationsStore = TTLStore + a ControlleeExpectation per controller +// +// * Once set expectations can only be lowered +// * A controller isn't synced till its expectations are either fulfilled, or expire +// * Controllers that don't set expectations will get woken up for every matching controllee + +// ExpKeyFunc to parse out the key from a ControlleeExpectation +var ExpKeyFunc = func(obj interface{}) (string, error) { + if e, ok := obj.(*ControlleeExpectations); ok { + return e.key, nil + } + return "", fmt.Errorf("Could not find key for obj %#v", obj) +} + +// ControllerExpectationsInterface is an interface that allows users to set and wait on expectations. +// Only abstracted out for testing. +// Warning: if using KeyFunc it is not safe to use a single ControllerExpectationsInterface with different +// types of controllers, because the keys might conflict across types. +type ControllerExpectationsInterface interface { + GetExpectations(controllerKey string) (*ControlleeExpectations, bool, error) + SatisfiedExpectations(controllerKey string) bool + DeleteExpectations(controllerKey string) + SetExpectations(controllerKey string, add, del int) error + ExpectCreations(controllerKey string, adds int) error + ExpectDeletions(controllerKey string, dels int) error + CreationObserved(controllerKey string) + DeletionObserved(controllerKey string) + RaiseExpectations(controllerKey string, add, del int) + LowerExpectations(controllerKey string, add, del int) +} + +// ControllerExpectations is a cache mapping controllers to what they expect to see before being woken up for a sync. +type ControllerExpectations struct { + cache.Store +} + +// GetExpectations returns the ControlleeExpectations of the given controller. +func (r *ControllerExpectations) GetExpectations(controllerKey string) (*ControlleeExpectations, bool, error) { + if exp, exists, err := r.GetByKey(controllerKey); err == nil && exists { + return exp.(*ControlleeExpectations), true, nil + } else { + return nil, false, err + } +} + +// DeleteExpectations deletes the expectations of the given controller from the TTLStore. +func (r *ControllerExpectations) DeleteExpectations(controllerKey string) { + if exp, exists, err := r.GetByKey(controllerKey); err == nil && exists { + if err := r.Delete(exp); err != nil { + glog.V(2).Infof("Error deleting expectations for controller %v: %v", controllerKey, err) + } + } +} + +// SatisfiedExpectations returns true if the required adds/dels for the given controller have been observed. +// Add/del counts are established by the controller at sync time, and updated as controllees are observed by the controller +// manager. +func (r *ControllerExpectations) SatisfiedExpectations(controllerKey string) bool { + if exp, exists, err := r.GetExpectations(controllerKey); exists { + if exp.Fulfilled() { + return true + } else if exp.isExpired() { + glog.V(4).Infof("Controller expectations expired %#v", exp) + return true + } else { + glog.V(4).Infof("Controller still waiting on expectations %#v", exp) + return false + } + } else if err != nil { + glog.V(2).Infof("Error encountered while checking expectations %#v, forcing sync", err) + } else { + // When a new controller is created, it doesn't have expectations. + // When it doesn't see expected watch events for > TTL, the expectations expire. + // - In this case it wakes up, creates/deletes controllees, and sets expectations again. + // When it has satisfied expectations and no controllees need to be created/destroyed > TTL, the expectations expire. + // - In this case it continues without setting expectations till it needs to create/delete controllees. + glog.V(4).Infof("Controller %v either never recorded expectations, or the ttl expired.", controllerKey) + } + // Trigger a sync if we either encountered and error (which shouldn't happen since we're + // getting from local store) or this controller hasn't established expectations. + return true +} + +// TODO: Extend ExpirationCache to support explicit expiration. +// TODO: Make this possible to disable in tests. +// TODO: Support injection of clock. +func (exp *ControlleeExpectations) isExpired() bool { + return util.RealClock{}.Since(exp.timestamp) > ExpectationsTimeout +} + +// SetExpectations registers new expectations for the given controller. Forgets existing expectations. +func (r *ControllerExpectations) SetExpectations(controllerKey string, add, del int) error { + exp := &ControlleeExpectations{add: int64(add), del: int64(del), key: controllerKey, timestamp: util.RealClock{}.Now()} + glog.V(4).Infof("Setting expectations %+v", exp) + return r.Add(exp) +} + +func (r *ControllerExpectations) ExpectCreations(controllerKey string, adds int) error { + return r.SetExpectations(controllerKey, adds, 0) +} + +func (r *ControllerExpectations) ExpectDeletions(controllerKey string, dels int) error { + return r.SetExpectations(controllerKey, 0, dels) +} + +// Decrements the expectation counts of the given controller. +func (r *ControllerExpectations) LowerExpectations(controllerKey string, add, del int) { + if exp, exists, err := r.GetExpectations(controllerKey); err == nil && exists { + exp.Add(int64(-add), int64(-del)) + // The expectations might've been modified since the update on the previous line. + glog.V(4).Infof("Lowered expectations %+v", exp) + } +} + +// Increments the expectation counts of the given controller. +func (r *ControllerExpectations) RaiseExpectations(controllerKey string, add, del int) { + if exp, exists, err := r.GetExpectations(controllerKey); err == nil && exists { + exp.Add(int64(add), int64(del)) + // The expectations might've been modified since the update on the previous line. + glog.V(4).Infof("Raised expectations %+v", exp) + } +} + +// CreationObserved atomically decrements the `add` expecation count of the given controller. +func (r *ControllerExpectations) CreationObserved(controllerKey string) { + r.LowerExpectations(controllerKey, 1, 0) +} + +// DeletionObserved atomically decrements the `del` expectation count of the given controller. +func (r *ControllerExpectations) DeletionObserved(controllerKey string) { + r.LowerExpectations(controllerKey, 0, 1) +} + +// Expectations are either fulfilled, or expire naturally. +type Expectations interface { + Fulfilled() bool +} + +// ControlleeExpectations track controllee creates/deletes. +type ControlleeExpectations struct { + add int64 + del int64 + key string + timestamp time.Time +} + +// Add increments the add and del counters. +func (e *ControlleeExpectations) Add(add, del int64) { + atomic.AddInt64(&e.add, add) + atomic.AddInt64(&e.del, del) +} + +// Fulfilled returns true if this expectation has been fulfilled. +func (e *ControlleeExpectations) Fulfilled() bool { + // TODO: think about why this line being atomic doesn't matter + return atomic.LoadInt64(&e.add) <= 0 && atomic.LoadInt64(&e.del) <= 0 +} + +// GetExpectations returns the add and del expectations of the controllee. +func (e *ControlleeExpectations) GetExpectations() (int64, int64) { + return atomic.LoadInt64(&e.add), atomic.LoadInt64(&e.del) +} + +// NewControllerExpectations returns a store for ControllerExpectations. +func NewControllerExpectations() *ControllerExpectations { + return &ControllerExpectations{cache.NewStore(ExpKeyFunc)} +} + +// UIDSetKeyFunc to parse out the key from a UIDSet. +var UIDSetKeyFunc = func(obj interface{}) (string, error) { + if u, ok := obj.(*UIDSet); ok { + return u.key, nil + } + return "", fmt.Errorf("Could not find key for obj %#v", obj) +} + +// UIDSet holds a key and a set of UIDs. Used by the +// UIDTrackingControllerExpectations to remember which UID it has seen/still +// waiting for. +type UIDSet struct { + sets.String + key string +} + +// UIDTrackingControllerExpectations tracks the UID of the pods it deletes. +// This cache is needed over plain old expectations to safely handle graceful +// deletion. The desired behavior is to treat an update that sets the +// DeletionTimestamp on an object as a delete. To do so consistenly, one needs +// to remember the expected deletes so they aren't double counted. +// TODO: Track creates as well (#22599) +type UIDTrackingControllerExpectations struct { + ControllerExpectationsInterface + // TODO: There is a much nicer way to do this that involves a single store, + // a lock per entry, and a ControlleeExpectationsInterface type. + uidStoreLock sync.Mutex + // Store used for the UIDs associated with any expectation tracked via the + // ControllerExpectationsInterface. + uidStore cache.Store +} + +// GetUIDs is a convenience method to avoid exposing the set of expected uids. +// The returned set is not thread safe, all modifications must be made holding +// the uidStoreLock. +func (u *UIDTrackingControllerExpectations) GetUIDs(controllerKey string) sets.String { + if uid, exists, err := u.uidStore.GetByKey(controllerKey); err == nil && exists { + return uid.(*UIDSet).String + } + return nil +} + +// ExpectDeletions records expectations for the given deleteKeys, against the given controller. +func (u *UIDTrackingControllerExpectations) ExpectDeletions(rcKey string, deletedKeys []string) error { + u.uidStoreLock.Lock() + defer u.uidStoreLock.Unlock() + + if existing := u.GetUIDs(rcKey); existing != nil && existing.Len() != 0 { + glog.Errorf("Clobbering existing delete keys: %+v", existing) + } + expectedUIDs := sets.NewString() + for _, k := range deletedKeys { + expectedUIDs.Insert(k) + } + glog.V(4).Infof("Controller %v waiting on deletions for: %+v", rcKey, deletedKeys) + if err := u.uidStore.Add(&UIDSet{expectedUIDs, rcKey}); err != nil { + return err + } + return u.ControllerExpectationsInterface.ExpectDeletions(rcKey, expectedUIDs.Len()) +} + +// DeletionObserved records the given deleteKey as a deletion, for the given rc. +func (u *UIDTrackingControllerExpectations) DeletionObserved(rcKey, deleteKey string) { + u.uidStoreLock.Lock() + defer u.uidStoreLock.Unlock() + + uids := u.GetUIDs(rcKey) + if uids != nil && uids.Has(deleteKey) { + glog.V(4).Infof("Controller %v received delete for pod %v", rcKey, deleteKey) + u.ControllerExpectationsInterface.DeletionObserved(rcKey) + uids.Delete(deleteKey) + } +} + +// DeleteExpectations deletes the UID set and invokes DeleteExpectations on the +// underlying ControllerExpectationsInterface. +func (u *UIDTrackingControllerExpectations) DeleteExpectations(rcKey string) { + u.uidStoreLock.Lock() + defer u.uidStoreLock.Unlock() + + u.ControllerExpectationsInterface.DeleteExpectations(rcKey) + if uidExp, exists, err := u.uidStore.GetByKey(rcKey); err == nil && exists { + if err := u.uidStore.Delete(uidExp); err != nil { + glog.V(2).Infof("Error deleting uid expectations for controller %v: %v", rcKey, err) + } + } +} + +// NewUIDTrackingControllerExpectations returns a wrapper around +// ControllerExpectations that is aware of deleteKeys. +func NewUIDTrackingControllerExpectations(ce ControllerExpectationsInterface) *UIDTrackingControllerExpectations { + return &UIDTrackingControllerExpectations{ControllerExpectationsInterface: ce, uidStore: cache.NewStore(UIDSetKeyFunc)} +} + +// PodControlInterface is an interface that knows how to add or delete pods +// created as an interface to allow testing. +type PodControlInterface interface { + // CreatePods creates new pods according to the spec. + CreatePods(namespace string, template *api.PodTemplateSpec, object runtime.Object) error + // CreatePodsOnNode creates a new pod accorting to the spec on the specified node. + CreatePodsOnNode(nodeName, namespace string, template *api.PodTemplateSpec, object runtime.Object) error + // DeletePod deletes the pod identified by podID. + DeletePod(namespace string, podID string, object runtime.Object) error +} + +// RealPodControl is the default implementation of PodControlInterface. +type RealPodControl struct { + KubeClient clientset.Interface + Recorder record.EventRecorder +} + +var _ PodControlInterface = &RealPodControl{} + +func getPodsLabelSet(template *api.PodTemplateSpec) labels.Set { + desiredLabels := make(labels.Set) + for k, v := range template.Labels { + desiredLabels[k] = v + } + return desiredLabels +} + +func getPodsAnnotationSet(template *api.PodTemplateSpec, object runtime.Object) (labels.Set, error) { + desiredAnnotations := make(labels.Set) + for k, v := range template.Annotations { + desiredAnnotations[k] = v + } + createdByRef, err := api.GetReference(object) + if err != nil { + return desiredAnnotations, fmt.Errorf("unable to get controller reference: %v", err) + } + + // TODO: this code was not safe previously - as soon as new code came along that switched to v2, old clients + // would be broken upon reading it. This is explicitly hardcoded to v1 to guarantee predictable deployment. + // We need to consistently handle this case of annotation versioning. + codec := api.Codecs.LegacyCodec(unversioned.GroupVersion{Group: api.GroupName, Version: "v1"}) + + createdByRefJson, err := runtime.Encode(codec, &api.SerializedReference{ + Reference: *createdByRef, + }) + if err != nil { + return desiredAnnotations, fmt.Errorf("unable to serialize controller reference: %v", err) + } + desiredAnnotations[CreatedByAnnotation] = string(createdByRefJson) + return desiredAnnotations, nil +} + +func getPodsPrefix(controllerName string) string { + // use the dash (if the name isn't too long) to make the pod name a bit prettier + prefix := fmt.Sprintf("%s-", controllerName) + if ok, _ := validation.ValidatePodName(prefix, true); !ok { + prefix = controllerName + } + return prefix +} + +func (r RealPodControl) CreatePods(namespace string, template *api.PodTemplateSpec, object runtime.Object) error { + return r.createPods("", namespace, template, object) +} + +func (r RealPodControl) CreatePodsOnNode(nodeName, namespace string, template *api.PodTemplateSpec, object runtime.Object) error { + return r.createPods(nodeName, namespace, template, object) +} + +func (r RealPodControl) createPods(nodeName, namespace string, template *api.PodTemplateSpec, object runtime.Object) error { + desiredLabels := getPodsLabelSet(template) + desiredAnnotations, err := getPodsAnnotationSet(template, object) + if err != nil { + return err + } + accessor, err := meta.Accessor(object) + if err != nil { + return fmt.Errorf("object does not have ObjectMeta, %v", err) + } + prefix := getPodsPrefix(accessor.GetName()) + + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Labels: desiredLabels, + Annotations: desiredAnnotations, + GenerateName: prefix, + }, + } + if err := api.Scheme.Convert(&template.Spec, &pod.Spec); err != nil { + return fmt.Errorf("unable to convert pod template: %v", err) + } + if len(nodeName) != 0 { + pod.Spec.NodeName = nodeName + } + if labels.Set(pod.Labels).AsSelector().Empty() { + return fmt.Errorf("unable to create pods, no labels") + } + if newPod, err := r.KubeClient.Core().Pods(namespace).Create(pod); err != nil { + r.Recorder.Eventf(object, api.EventTypeWarning, "FailedCreate", "Error creating: %v", err) + return fmt.Errorf("unable to create pods: %v", err) + } else { + glog.V(4).Infof("Controller %v created pod %v", accessor.GetName(), newPod.Name) + r.Recorder.Eventf(object, api.EventTypeNormal, "SuccessfulCreate", "Created pod: %v", newPod.Name) + } + return nil +} + +func (r RealPodControl) DeletePod(namespace string, podID string, object runtime.Object) error { + accessor, err := meta.Accessor(object) + if err != nil { + return fmt.Errorf("object does not have ObjectMeta, %v", err) + } + if err := r.KubeClient.Core().Pods(namespace).Delete(podID, nil); err != nil { + r.Recorder.Eventf(object, api.EventTypeWarning, "FailedDelete", "Error deleting: %v", err) + return fmt.Errorf("unable to delete pods: %v", err) + } else { + glog.V(4).Infof("Controller %v deleted pod %v", accessor.GetName(), podID) + r.Recorder.Eventf(object, api.EventTypeNormal, "SuccessfulDelete", "Deleted pod: %v", podID) + } + return nil +} + +type FakePodControl struct { + sync.Mutex + Templates []api.PodTemplateSpec + DeletePodName []string + Err error +} + +var _ PodControlInterface = &FakePodControl{} + +func (f *FakePodControl) CreatePods(namespace string, spec *api.PodTemplateSpec, object runtime.Object) error { + f.Lock() + defer f.Unlock() + if f.Err != nil { + return f.Err + } + f.Templates = append(f.Templates, *spec) + return nil +} + +func (f *FakePodControl) CreatePodsOnNode(nodeName, namespace string, template *api.PodTemplateSpec, object runtime.Object) error { + f.Lock() + defer f.Unlock() + if f.Err != nil { + return f.Err + } + f.Templates = append(f.Templates, *template) + return nil +} + +func (f *FakePodControl) DeletePod(namespace string, podID string, object runtime.Object) error { + f.Lock() + defer f.Unlock() + if f.Err != nil { + return f.Err + } + f.DeletePodName = append(f.DeletePodName, podID) + return nil +} + +func (f *FakePodControl) Clear() { + f.Lock() + defer f.Unlock() + f.DeletePodName = []string{} + f.Templates = []api.PodTemplateSpec{} +} + +// ActivePods type allows custom sorting of pods so a controller can pick the best ones to delete. +type ActivePods []*api.Pod + +func (s ActivePods) Len() int { return len(s) } +func (s ActivePods) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s ActivePods) Less(i, j int) bool { + // 1. Unassigned < assigned + // If only one of the pods is unassigned, the unassigned one is smaller + if s[i].Spec.NodeName != s[j].Spec.NodeName && (len(s[i].Spec.NodeName) == 0 || len(s[j].Spec.NodeName) == 0) { + return len(s[i].Spec.NodeName) == 0 + } + // 2. PodPending < PodUnknown < PodRunning + m := map[api.PodPhase]int{api.PodPending: 0, api.PodUnknown: 1, api.PodRunning: 2} + if m[s[i].Status.Phase] != m[s[j].Status.Phase] { + return m[s[i].Status.Phase] < m[s[j].Status.Phase] + } + // 3. Not ready < ready + // If only one of the pods is not ready, the not ready one is smaller + if api.IsPodReady(s[i]) != api.IsPodReady(s[j]) { + return !api.IsPodReady(s[i]) + } + // TODO: take availability into account when we push minReadySeconds information from deployment into pods, + // see https://github.com/kubernetes/kubernetes/issues/22065 + // 4. Been ready for empty time < less time < more time + // If both pods are ready, the latest ready one is smaller + if api.IsPodReady(s[i]) && api.IsPodReady(s[j]) && !podReadyTime(s[i]).Equal(podReadyTime(s[j])) { + return afterOrZero(podReadyTime(s[i]), podReadyTime(s[j])) + } + // 5. Pods with containers with higher restart counts < lower restart counts + if maxContainerRestarts(s[i]) != maxContainerRestarts(s[j]) { + return maxContainerRestarts(s[i]) > maxContainerRestarts(s[j]) + } + // 6. Empty creation time pods < newer pods < older pods + if !s[i].CreationTimestamp.Equal(s[j].CreationTimestamp) { + return afterOrZero(s[i].CreationTimestamp, s[j].CreationTimestamp) + } + return false +} + +// afterOrZero checks if time t1 is after time t2; if one of them +// is zero, the zero time is seen as after non-zero time. +func afterOrZero(t1, t2 unversioned.Time) bool { + if t1.Time.IsZero() || t2.Time.IsZero() { + return t1.Time.IsZero() + } + return t1.After(t2.Time) +} + +func podReadyTime(pod *api.Pod) unversioned.Time { + if api.IsPodReady(pod) { + for _, c := range pod.Status.Conditions { + // we only care about pod ready conditions + if c.Type == api.PodReady && c.Status == api.ConditionTrue { + return c.LastTransitionTime + } + } + } + return unversioned.Time{} +} + +func maxContainerRestarts(pod *api.Pod) int { + maxRestarts := 0 + for _, c := range pod.Status.ContainerStatuses { + maxRestarts = integer.IntMax(maxRestarts, c.RestartCount) + } + return maxRestarts +} + +// FilterActivePods returns pods that have not terminated. +func FilterActivePods(pods []api.Pod) []*api.Pod { + var result []*api.Pod + for i := range pods { + p := pods[i] + if IsPodActive(p) { + result = append(result, &p) + } else { + glog.V(4).Infof("Ignoring inactive pod %v/%v in state %v, deletion time %v", + p.Namespace, p.Name, p.Status.Phase, p.DeletionTimestamp) + } + } + return result +} + +func IsPodActive(p api.Pod) bool { + return api.PodSucceeded != p.Status.Phase && + api.PodFailed != p.Status.Phase && + p.DeletionTimestamp == nil +} + +// FilterActiveReplicaSets returns replica sets that have (or at least ought to have) pods. +func FilterActiveReplicaSets(replicaSets []*extensions.ReplicaSet) []*extensions.ReplicaSet { + active := []*extensions.ReplicaSet{} + for i := range replicaSets { + if replicaSets[i].Spec.Replicas > 0 { + active = append(active, replicaSets[i]) + } + } + return active +} + +// PodKey returns a key unique to the given pod within a cluster. +// It's used so we consistently use the same key scheme in this module. +// It does exactly what cache.MetaNamespaceKeyFunc would have done +// expcept there's not possibility for error since we know the exact type. +func PodKey(pod *api.Pod) string { + return fmt.Sprintf("%v/%v", pod.Namespace, pod.Name) +} + +// ControllersByCreationTimestamp sorts a list of ReplicationControllers by creation timestamp, using their names as a tie breaker. +type ControllersByCreationTimestamp []*api.ReplicationController + +func (o ControllersByCreationTimestamp) Len() int { return len(o) } +func (o ControllersByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } + +func (o ControllersByCreationTimestamp) Less(i, j int) bool { + if o[i].CreationTimestamp.Equal(o[j].CreationTimestamp) { + return o[i].Name < o[j].Name + } + return o[i].CreationTimestamp.Before(o[j].CreationTimestamp) +} + +// ReplicaSetsByCreationTimestamp sorts a list of ReplicationSets by creation timestamp, using their names as a tie breaker. +type ReplicaSetsByCreationTimestamp []*extensions.ReplicaSet + +func (o ReplicaSetsByCreationTimestamp) Len() int { return len(o) } +func (o ReplicaSetsByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } + +func (o ReplicaSetsByCreationTimestamp) Less(i, j int) bool { + if o[i].CreationTimestamp.Equal(o[j].CreationTimestamp) { + return o[i].Name < o[j].Name + } + return o[i].CreationTimestamp.Before(o[j].CreationTimestamp) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/controller_utils_test.go b/vendor/k8s.io/kubernetes/pkg/controller/controller_utils_test.go new file mode 100644 index 000000000..bf5c8782d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/controller_utils_test.go @@ -0,0 +1,373 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 controller + +import ( + "fmt" + "math/rand" + "net/http/httptest" + "reflect" + "sort" + "sync" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/securitycontext" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/sets" + utiltesting "k8s.io/kubernetes/pkg/util/testing" +) + +// NewFakeControllerExpectationsLookup creates a fake store for PodExpectations. +func NewFakeControllerExpectationsLookup(ttl time.Duration) (*ControllerExpectations, *util.FakeClock) { + fakeTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) + fakeClock := util.NewFakeClock(fakeTime) + ttlPolicy := &cache.TTLPolicy{Ttl: ttl, Clock: fakeClock} + ttlStore := cache.NewFakeExpirationStore( + ExpKeyFunc, nil, ttlPolicy, fakeClock) + return &ControllerExpectations{ttlStore}, fakeClock +} + +func newReplicationController(replicas int) *api.ReplicationController { + rc := &api.ReplicationController{ + TypeMeta: unversioned.TypeMeta{APIVersion: testapi.Default.GroupVersion().String()}, + ObjectMeta: api.ObjectMeta{ + UID: util.NewUUID(), + Name: "foobar", + Namespace: api.NamespaceDefault, + ResourceVersion: "18", + }, + Spec: api.ReplicationControllerSpec{ + Replicas: replicas, + Selector: map[string]string{"foo": "bar"}, + Template: &api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{ + "name": "foo", + "type": "production", + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Image: "foo/bar", + TerminationMessagePath: api.TerminationMessagePathDefault, + ImagePullPolicy: api.PullIfNotPresent, + SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), + }, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSDefault, + NodeSelector: map[string]string{ + "baz": "blah", + }, + }, + }, + }, + } + return rc +} + +// create count pods with the given phase for the given rc (same selectors and namespace), and add them to the store. +func newPodList(store cache.Store, count int, status api.PodPhase, rc *api.ReplicationController) *api.PodList { + pods := []api.Pod{} + for i := 0; i < count; i++ { + newPod := api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: fmt.Sprintf("pod%d", i), + Labels: rc.Spec.Selector, + Namespace: rc.Namespace, + }, + Status: api.PodStatus{Phase: status}, + } + if store != nil { + store.Add(&newPod) + } + pods = append(pods, newPod) + } + return &api.PodList{ + Items: pods, + } +} + +func TestControllerExpectations(t *testing.T) { + ttl := 30 * time.Second + e, fakeClock := NewFakeControllerExpectationsLookup(ttl) + // In practice we can't really have add and delete expectations since we only either create or + // delete replicas in one rc pass, and the rc goes to sleep soon after until the expectations are + // either fulfilled or timeout. + adds, dels := 10, 30 + rc := newReplicationController(1) + + // RC fires off adds and deletes at apiserver, then sets expectations + rcKey, err := KeyFunc(rc) + if err != nil { + t.Errorf("Couldn't get key for object %+v: %v", rc, err) + } + e.SetExpectations(rcKey, adds, dels) + var wg sync.WaitGroup + for i := 0; i < adds+1; i++ { + wg.Add(1) + go func() { + // In prod this can happen either because of a failed create by the rc + // or after having observed a create via informer + e.CreationObserved(rcKey) + wg.Done() + }() + } + wg.Wait() + + // There are still delete expectations + if e.SatisfiedExpectations(rcKey) { + t.Errorf("Rc will sync before expectations are met") + } + for i := 0; i < dels+1; i++ { + wg.Add(1) + go func() { + e.DeletionObserved(rcKey) + wg.Done() + }() + } + wg.Wait() + + // Expectations have been surpassed + if podExp, exists, err := e.GetExpectations(rcKey); err == nil && exists { + add, del := podExp.GetExpectations() + if add != -1 || del != -1 { + t.Errorf("Unexpected pod expectations %#v", podExp) + } + } else { + t.Errorf("Could not get expectations for rc, exists %v and err %v", exists, err) + } + if !e.SatisfiedExpectations(rcKey) { + t.Errorf("Expectations are met but the rc will not sync") + } + + // Next round of rc sync, old expectations are cleared + e.SetExpectations(rcKey, 1, 2) + if podExp, exists, err := e.GetExpectations(rcKey); err == nil && exists { + add, del := podExp.GetExpectations() + if add != 1 || del != 2 { + t.Errorf("Unexpected pod expectations %#v", podExp) + } + } else { + t.Errorf("Could not get expectations for rc, exists %v and err %v", exists, err) + } + + // Expectations have expired because of ttl + fakeClock.Step(ttl + 1) + if !e.SatisfiedExpectations(rcKey) { + t.Errorf("Expectations should have expired but didn't") + } +} + +func TestUIDExpectations(t *testing.T) { + uidExp := NewUIDTrackingControllerExpectations(NewControllerExpectations()) + rcList := []*api.ReplicationController{ + newReplicationController(2), + newReplicationController(1), + newReplicationController(0), + newReplicationController(5), + } + rcToPods := map[string][]string{} + rcKeys := []string{} + for i := range rcList { + rc := rcList[i] + rcName := fmt.Sprintf("rc-%v", i) + rc.Name = rcName + rc.Spec.Selector[rcName] = rcName + podList := newPodList(nil, 5, api.PodRunning, rc) + rcKey, err := KeyFunc(rc) + if err != nil { + t.Fatalf("Couldn't get key for object %+v: %v", rc, err) + } + rcKeys = append(rcKeys, rcKey) + rcPodNames := []string{} + for i := range podList.Items { + p := &podList.Items[i] + p.Name = fmt.Sprintf("%v-%v", p.Name, rc.Name) + rcPodNames = append(rcPodNames, PodKey(p)) + } + rcToPods[rcKey] = rcPodNames + uidExp.ExpectDeletions(rcKey, rcPodNames) + } + for i := range rcKeys { + j := rand.Intn(i + 1) + rcKeys[i], rcKeys[j] = rcKeys[j], rcKeys[i] + } + for _, rcKey := range rcKeys { + if uidExp.SatisfiedExpectations(rcKey) { + t.Errorf("Controller %v satisfied expectations before deletion", rcKey) + } + for _, p := range rcToPods[rcKey] { + uidExp.DeletionObserved(rcKey, p) + } + if !uidExp.SatisfiedExpectations(rcKey) { + t.Errorf("Controller %v didn't satisfy expectations after deletion", rcKey) + } + uidExp.DeleteExpectations(rcKey) + if uidExp.GetUIDs(rcKey) != nil { + t.Errorf("Failed to delete uid expectations for %v", rcKey) + } + } +} + +func TestCreatePods(t *testing.T) { + ns := api.NamespaceDefault + body := runtime.EncodeOrDie(testapi.Default.Codec(), &api.Pod{ObjectMeta: api.ObjectMeta{Name: "empty_pod"}}) + fakeHandler := utiltesting.FakeHandler{ + StatusCode: 200, + ResponseBody: string(body), + } + testServer := httptest.NewServer(&fakeHandler) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + + podControl := RealPodControl{ + KubeClient: clientset, + Recorder: &record.FakeRecorder{}, + } + + controllerSpec := newReplicationController(1) + + // Make sure createReplica sends a POST to the apiserver with a pod from the controllers pod template + podControl.CreatePods(ns, controllerSpec.Spec.Template, controllerSpec) + + expectedPod := api.Pod{ + ObjectMeta: api.ObjectMeta{ + Labels: controllerSpec.Spec.Template.Labels, + GenerateName: fmt.Sprintf("%s-", controllerSpec.Name), + }, + Spec: controllerSpec.Spec.Template.Spec, + } + fakeHandler.ValidateRequest(t, testapi.Default.ResourcePath("pods", api.NamespaceDefault, ""), "POST", nil) + actualPod, err := runtime.Decode(testapi.Default.Codec(), []byte(fakeHandler.RequestBody)) + if err != nil { + t.Errorf("Unexpected error: %#v", err) + } + if !api.Semantic.DeepDerivative(&expectedPod, actualPod) { + t.Logf("Body: %s", fakeHandler.RequestBody) + t.Errorf("Unexpected mismatch. Expected\n %#v,\n Got:\n %#v", &expectedPod, actualPod) + } +} + +func TestActivePodFiltering(t *testing.T) { + // This rc is not needed by the test, only the newPodList to give the pods labels/a namespace. + rc := newReplicationController(0) + podList := newPodList(nil, 5, api.PodRunning, rc) + podList.Items[0].Status.Phase = api.PodSucceeded + podList.Items[1].Status.Phase = api.PodFailed + expectedNames := sets.NewString() + for _, pod := range podList.Items[2:] { + expectedNames.Insert(pod.Name) + } + + got := FilterActivePods(podList.Items) + gotNames := sets.NewString() + for _, pod := range got { + gotNames.Insert(pod.Name) + } + if expectedNames.Difference(gotNames).Len() != 0 || gotNames.Difference(expectedNames).Len() != 0 { + t.Errorf("expected %v, got %v", expectedNames.List(), gotNames.List()) + } +} + +func TestSortingActivePods(t *testing.T) { + numPods := 9 + // This rc is not needed by the test, only the newPodList to give the pods labels/a namespace. + rc := newReplicationController(0) + podList := newPodList(nil, numPods, api.PodRunning, rc) + + pods := make([]*api.Pod, len(podList.Items)) + for i := range podList.Items { + pods[i] = &podList.Items[i] + } + // pods[0] is not scheduled yet. + pods[0].Spec.NodeName = "" + pods[0].Status.Phase = api.PodPending + // pods[1] is scheduled but pending. + pods[1].Spec.NodeName = "bar" + pods[1].Status.Phase = api.PodPending + // pods[2] is unknown. + pods[2].Spec.NodeName = "foo" + pods[2].Status.Phase = api.PodUnknown + // pods[3] is running but not ready. + pods[3].Spec.NodeName = "foo" + pods[3].Status.Phase = api.PodRunning + // pods[4] is running and ready but without LastTransitionTime. + now := unversioned.Now() + pods[4].Spec.NodeName = "foo" + pods[4].Status.Phase = api.PodRunning + pods[4].Status.Conditions = []api.PodCondition{{Type: api.PodReady, Status: api.ConditionTrue}} + pods[4].Status.ContainerStatuses = []api.ContainerStatus{{RestartCount: 3}, {RestartCount: 0}} + // pods[5] is running and ready and with LastTransitionTime. + pods[5].Spec.NodeName = "foo" + pods[5].Status.Phase = api.PodRunning + pods[5].Status.Conditions = []api.PodCondition{{Type: api.PodReady, Status: api.ConditionTrue, LastTransitionTime: now}} + pods[5].Status.ContainerStatuses = []api.ContainerStatus{{RestartCount: 3}, {RestartCount: 0}} + // pods[6] is running ready for a longer time than pods[5]. + then := unversioned.Time{Time: now.AddDate(0, -1, 0)} + pods[6].Spec.NodeName = "foo" + pods[6].Status.Phase = api.PodRunning + pods[6].Status.Conditions = []api.PodCondition{{Type: api.PodReady, Status: api.ConditionTrue, LastTransitionTime: then}} + pods[6].Status.ContainerStatuses = []api.ContainerStatus{{RestartCount: 3}, {RestartCount: 0}} + // pods[7] has lower container restart count than pods[6]. + pods[7].Spec.NodeName = "foo" + pods[7].Status.Phase = api.PodRunning + pods[7].Status.Conditions = []api.PodCondition{{Type: api.PodReady, Status: api.ConditionTrue, LastTransitionTime: then}} + pods[7].Status.ContainerStatuses = []api.ContainerStatus{{RestartCount: 2}, {RestartCount: 1}} + pods[7].CreationTimestamp = now + // pods[8] is older than pods[7]. + pods[8].Spec.NodeName = "foo" + pods[8].Status.Phase = api.PodRunning + pods[8].Status.Conditions = []api.PodCondition{{Type: api.PodReady, Status: api.ConditionTrue, LastTransitionTime: then}} + pods[8].Status.ContainerStatuses = []api.ContainerStatus{{RestartCount: 2}, {RestartCount: 1}} + pods[8].CreationTimestamp = then + + getOrder := func(pods []*api.Pod) []string { + names := make([]string, len(pods)) + for i := range pods { + names[i] = pods[i].Name + } + return names + } + + expected := getOrder(pods) + + for i := 0; i < 20; i++ { + idx := rand.Perm(numPods) + randomizedPods := make([]*api.Pod, numPods) + for j := 0; j < numPods; j++ { + randomizedPods[j] = pods[idx[j]] + } + sort.Sort(ActivePods(randomizedPods)) + actual := getOrder(randomizedPods) + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("expected %v, got %v", expected, actual) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/daemon/controller.go b/vendor/k8s.io/kubernetes/pkg/controller/daemon/controller.go new file mode 100644 index 000000000..5302cdda7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/daemon/controller.go @@ -0,0 +1,725 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 daemon + +import ( + "reflect" + "sort" + "sync" + "time" + + "fmt" + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + unversionedextensions "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/util/workqueue" + "k8s.io/kubernetes/pkg/watch" + "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" +) + +const ( + // Daemon sets will periodically check that their daemon pods are running as expected. + FullDaemonSetResyncPeriod = 30 * time.Second // TODO: Figure out if this time seems reasonable. + + // Realistic value of the burstReplica field for the replication manager based off + // performance requirements for kubernetes 1.0. + BurstReplicas = 500 + + // We must avoid counting pods until the pod store has synced. If it hasn't synced, to + // avoid a hot loop, we'll wait this long between checks. + PodStoreSyncedPollPeriod = 100 * time.Millisecond + + // If sending a status upate to API server fails, we retry a finite number of times. + StatusUpdateRetries = 1 +) + +// DaemonSetsController is responsible for synchronizing DaemonSet objects stored +// in the system with actual running pods. +type DaemonSetsController struct { + kubeClient clientset.Interface + eventRecorder record.EventRecorder + podControl controller.PodControlInterface + + // An dsc is temporarily suspended after creating/deleting these many replicas. + // It resumes normal action after observing the watch events for them. + burstReplicas int + + // To allow injection of syncDaemonSet for testing. + syncHandler func(dsKey string) error + // A TTLCache of pod creates/deletes each ds expects to see + expectations controller.ControllerExpectationsInterface + // A store of daemon sets + dsStore cache.StoreToDaemonSetLister + // A store of pods + podStore cache.StoreToPodLister + // A store of nodes + nodeStore cache.StoreToNodeLister + // Watches changes to all daemon sets. + dsController *framework.Controller + // Watches changes to all pods + podController *framework.Controller + // Watches changes to all nodes. + nodeController *framework.Controller + // podStoreSynced returns true if the pod store has been synced at least once. + // Added as a member to the struct to allow injection for testing. + podStoreSynced func() bool + + lookupCache *controller.MatchingCache + + // Daemon sets that need to be synced. + queue *workqueue.Type +} + +func NewDaemonSetsController(kubeClient clientset.Interface, resyncPeriod controller.ResyncPeriodFunc, lookupCacheSize int) *DaemonSetsController { + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(glog.Infof) + // TODO: remove the wrapper when every clients have moved to use the clientset. + eventBroadcaster.StartRecordingToSink(&unversionedcore.EventSinkImpl{Interface: kubeClient.Core().Events("")}) + + dsc := &DaemonSetsController{ + kubeClient: kubeClient, + eventRecorder: eventBroadcaster.NewRecorder(api.EventSource{Component: "daemonset-controller"}), + podControl: controller.RealPodControl{ + KubeClient: kubeClient, + Recorder: eventBroadcaster.NewRecorder(api.EventSource{Component: "daemon-set"}), + }, + burstReplicas: BurstReplicas, + expectations: controller.NewControllerExpectations(), + queue: workqueue.New(), + } + // Manage addition/update of daemon sets. + dsc.dsStore.Store, dsc.dsController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return dsc.kubeClient.Extensions().DaemonSets(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return dsc.kubeClient.Extensions().DaemonSets(api.NamespaceAll).Watch(options) + }, + }, + &extensions.DaemonSet{}, + // TODO: Can we have much longer period here? + FullDaemonSetResyncPeriod, + framework.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + ds := obj.(*extensions.DaemonSet) + glog.V(4).Infof("Adding daemon set %s", ds.Name) + dsc.enqueueDaemonSet(ds) + }, + UpdateFunc: func(old, cur interface{}) { + oldDS := old.(*extensions.DaemonSet) + curDS := cur.(*extensions.DaemonSet) + // We should invalidate the whole lookup cache if a DS's selector has been updated. + // + // Imagine that you have two RSs: + // * old DS1 + // * new DS2 + // You also have a pod that is attached to DS2 (because it doesn't match DS1 selector). + // Now imagine that you are changing DS1 selector so that it is now matching that pod, + // in such case we must invalidate the whole cache so that pod could be adopted by DS1 + // + // This makes the lookup cache less helpful, but selector update does not happen often, + // so it's not a big problem + if !reflect.DeepEqual(oldDS.Spec.Selector, curDS.Spec.Selector) { + dsc.lookupCache.InvalidateAll() + } + + glog.V(4).Infof("Updating daemon set %s", oldDS.Name) + dsc.enqueueDaemonSet(curDS) + }, + DeleteFunc: func(obj interface{}) { + ds := obj.(*extensions.DaemonSet) + glog.V(4).Infof("Deleting daemon set %s", ds.Name) + dsc.enqueueDaemonSet(ds) + }, + }, + ) + // Watch for creation/deletion of pods. The reason we watch is that we don't want a daemon set to create/delete + // more pods until all the effects (expectations) of a daemon set's create/delete have been observed. + dsc.podStore.Store, dsc.podController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return dsc.kubeClient.Core().Pods(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return dsc.kubeClient.Core().Pods(api.NamespaceAll).Watch(options) + }, + }, + &api.Pod{}, + resyncPeriod(), + framework.ResourceEventHandlerFuncs{ + AddFunc: dsc.addPod, + UpdateFunc: dsc.updatePod, + DeleteFunc: dsc.deletePod, + }, + ) + // Watch for new nodes or updates to nodes - daemon pods are launched on new nodes, and possibly when labels on nodes change, + dsc.nodeStore.Store, dsc.nodeController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return dsc.kubeClient.Core().Nodes().List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return dsc.kubeClient.Core().Nodes().Watch(options) + }, + }, + &api.Node{}, + resyncPeriod(), + framework.ResourceEventHandlerFuncs{ + AddFunc: dsc.addNode, + UpdateFunc: dsc.updateNode, + }, + ) + dsc.syncHandler = dsc.syncDaemonSet + dsc.podStoreSynced = dsc.podController.HasSynced + dsc.lookupCache = controller.NewMatchingCache(lookupCacheSize) + return dsc +} + +// Run begins watching and syncing daemon sets. +func (dsc *DaemonSetsController) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + glog.Infof("Starting Daemon Sets controller manager") + go dsc.dsController.Run(stopCh) + go dsc.podController.Run(stopCh) + go dsc.nodeController.Run(stopCh) + for i := 0; i < workers; i++ { + go wait.Until(dsc.worker, time.Second, stopCh) + } + <-stopCh + glog.Infof("Shutting down Daemon Set Controller") + dsc.queue.ShutDown() +} + +func (dsc *DaemonSetsController) worker() { + for { + func() { + dsKey, quit := dsc.queue.Get() + if quit { + return + } + defer dsc.queue.Done(dsKey) + err := dsc.syncHandler(dsKey.(string)) + if err != nil { + glog.Errorf("Error syncing daemon set with key %s: %v", dsKey.(string), err) + } + }() + } +} + +func (dsc *DaemonSetsController) enqueueAllDaemonSets() { + glog.V(4).Infof("Enqueueing all daemon sets") + ds, err := dsc.dsStore.List() + if err != nil { + glog.Errorf("Error enqueueing daemon sets: %v", err) + return + } + for i := range ds.Items { + dsc.enqueueDaemonSet(&ds.Items[i]) + } +} + +func (dsc *DaemonSetsController) enqueueDaemonSet(ds *extensions.DaemonSet) { + key, err := controller.KeyFunc(ds) + if err != nil { + glog.Errorf("Couldn't get key for object %+v: %v", ds, err) + return + } + + // TODO: Handle overlapping controllers better. See comment in ReplicationManager. + dsc.queue.Add(key) +} + +func (dsc *DaemonSetsController) getPodDaemonSet(pod *api.Pod) *extensions.DaemonSet { + // look up in the cache, if cached and the cache is valid, just return cached value + if obj, cached := dsc.lookupCache.GetMatchingObject(pod); cached { + ds, ok := obj.(*extensions.DaemonSet) + if !ok { + // This should not happen + glog.Errorf("lookup cache does not retuen a ReplicationController object") + return nil + } + if cached && dsc.isCacheValid(pod, ds) { + return ds + } + } + sets, err := dsc.dsStore.GetPodDaemonSets(pod) + if err != nil { + glog.V(4).Infof("No daemon sets found for pod %v, daemon set controller will avoid syncing", pod.Name) + return nil + } + if len(sets) > 1 { + // More than two items in this list indicates user error. If two daemon + // sets overlap, sort by creation timestamp, subsort by name, then pick + // the first. + glog.Errorf("user error! more than one daemon is selecting pods with labels: %+v", pod.Labels) + sort.Sort(byCreationTimestamp(sets)) + } + + // update lookup cache + dsc.lookupCache.Update(pod, &sets[0]) + + return &sets[0] +} + +// isCacheValid check if the cache is valid +func (dsc *DaemonSetsController) isCacheValid(pod *api.Pod, cachedDS *extensions.DaemonSet) bool { + _, exists, err := dsc.dsStore.Get(cachedDS) + // ds has been deleted or updated, cache is invalid + if err != nil || !exists || !isDaemonSetMatch(pod, cachedDS) { + return false + } + return true +} + +// isDaemonSetMatch take a Pod and DaemonSet, return whether the Pod and DaemonSet are matching +// TODO(mqliang): This logic is a copy from GetPodDaemonSets(), remove the duplication +func isDaemonSetMatch(pod *api.Pod, ds *extensions.DaemonSet) bool { + if ds.Namespace != pod.Namespace { + return false + } + selector, err := unversioned.LabelSelectorAsSelector(ds.Spec.Selector) + if err != nil { + err = fmt.Errorf("invalid selector: %v", err) + return false + } + + // If a ReplicaSet with a nil or empty selector creeps in, it should match nothing, not everything. + if selector.Empty() || !selector.Matches(labels.Set(pod.Labels)) { + return false + } + return true +} + +func (dsc *DaemonSetsController) addPod(obj interface{}) { + pod := obj.(*api.Pod) + glog.V(4).Infof("Pod %s added.", pod.Name) + if ds := dsc.getPodDaemonSet(pod); ds != nil { + dsKey, err := controller.KeyFunc(ds) + if err != nil { + glog.Errorf("Couldn't get key for object %+v: %v", ds, err) + return + } + dsc.expectations.CreationObserved(dsKey) + dsc.enqueueDaemonSet(ds) + } +} + +// When a pod is updated, figure out what sets manage it and wake them +// up. If the labels of the pod have changed we need to awaken both the old +// and new set. old and cur must be *api.Pod types. +func (dsc *DaemonSetsController) updatePod(old, cur interface{}) { + if api.Semantic.DeepEqual(old, cur) { + // A periodic relist will send update events for all known pods. + return + } + curPod := cur.(*api.Pod) + glog.V(4).Infof("Pod %s updated.", curPod.Name) + if curDS := dsc.getPodDaemonSet(curPod); curDS != nil { + dsc.enqueueDaemonSet(curDS) + } + oldPod := old.(*api.Pod) + // If the labels have not changed, then the daemon set responsible for + // the pod is the same as it was before. In that case we have enqueued the daemon + // set above, and do not have to enqueue the set again. + if !reflect.DeepEqual(curPod.Labels, oldPod.Labels) { + // It's ok if both oldDS and curDS are the same, because curDS will set + // the expectations on its run so oldDS will have no effect. + if oldDS := dsc.getPodDaemonSet(oldPod); oldDS != nil { + dsc.enqueueDaemonSet(oldDS) + } + } +} + +func (dsc *DaemonSetsController) deletePod(obj interface{}) { + pod, ok := obj.(*api.Pod) + // When a delete is dropped, the relist will notice a pod in the store not + // in the list, leading to the insertion of a tombstone object which contains + // the deleted key/value. Note that this value might be stale. If the pod + // changed labels the new daemonset will not be woken up till the periodic + // resync. + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + glog.Errorf("Couldn't get object from tombstone %+v", obj) + return + } + pod, ok = tombstone.Obj.(*api.Pod) + if !ok { + glog.Errorf("Tombstone contained object that is not a pod %+v", obj) + return + } + } + glog.V(4).Infof("Pod %s deleted.", pod.Name) + if ds := dsc.getPodDaemonSet(pod); ds != nil { + dsKey, err := controller.KeyFunc(ds) + if err != nil { + glog.Errorf("Couldn't get key for object %+v: %v", ds, err) + return + } + dsc.expectations.DeletionObserved(dsKey) + dsc.enqueueDaemonSet(ds) + } +} + +func (dsc *DaemonSetsController) addNode(obj interface{}) { + // TODO: it'd be nice to pass a hint with these enqueues, so that each ds would only examine the added node (unless it has other work to do, too). + dsList, err := dsc.dsStore.List() + if err != nil { + glog.V(4).Infof("Error enqueueing daemon sets: %v", err) + return + } + node := obj.(*api.Node) + for i := range dsList.Items { + ds := &dsList.Items[i] + shouldEnqueue := dsc.nodeShouldRunDaemonPod(node, ds) + if shouldEnqueue { + dsc.enqueueDaemonSet(ds) + } + } +} + +func (dsc *DaemonSetsController) updateNode(old, cur interface{}) { + oldNode := old.(*api.Node) + curNode := cur.(*api.Node) + if api.Semantic.DeepEqual(oldNode.Name, curNode.Name) && api.Semantic.DeepEqual(oldNode.Namespace, curNode.Namespace) && api.Semantic.DeepEqual(oldNode.Labels, curNode.Labels) { + // A periodic relist will send update events for all known pods. + return + } + dsList, err := dsc.dsStore.List() + if err != nil { + glog.V(4).Infof("Error enqueueing daemon sets: %v", err) + return + } + for i := range dsList.Items { + ds := &dsList.Items[i] + shouldEnqueue := (dsc.nodeShouldRunDaemonPod(oldNode, ds) != dsc.nodeShouldRunDaemonPod(curNode, ds)) + if shouldEnqueue { + dsc.enqueueDaemonSet(ds) + } + } + // TODO: it'd be nice to pass a hint with these enqueues, so that each ds would only examine the added node (unless it has other work to do, too). +} + +// getNodesToDaemonSetPods returns a map from nodes to daemon pods (corresponding to ds) running on the nodes. +func (dsc *DaemonSetsController) getNodesToDaemonPods(ds *extensions.DaemonSet) (map[string][]*api.Pod, error) { + nodeToDaemonPods := make(map[string][]*api.Pod) + selector, err := unversioned.LabelSelectorAsSelector(ds.Spec.Selector) + if err != nil { + return nil, err + } + daemonPods, err := dsc.podStore.Pods(ds.Namespace).List(selector) + if err != nil { + return nodeToDaemonPods, err + } + for i := range daemonPods.Items { + nodeName := daemonPods.Items[i].Spec.NodeName + nodeToDaemonPods[nodeName] = append(nodeToDaemonPods[nodeName], &daemonPods.Items[i]) + } + return nodeToDaemonPods, nil +} + +func (dsc *DaemonSetsController) manage(ds *extensions.DaemonSet) { + // Find out which nodes are running the daemon pods selected by ds. + nodeToDaemonPods, err := dsc.getNodesToDaemonPods(ds) + if err != nil { + glog.Errorf("Error getting node to daemon pod mapping for daemon set %+v: %v", ds, err) + } + + // For each node, if the node is running the daemon pod but isn't supposed to, kill the daemon + // pod. If the node is supposed to run the daemon pod, but isn't, create the daemon pod on the node. + nodeList, err := dsc.nodeStore.List() + if err != nil { + glog.Errorf("Couldn't get list of nodes when syncing daemon set %+v: %v", ds, err) + } + var nodesNeedingDaemonPods, podsToDelete []string + for _, node := range nodeList.Items { + shouldRun := dsc.nodeShouldRunDaemonPod(&node, ds) + + daemonPods, isRunning := nodeToDaemonPods[node.Name] + + if shouldRun && !isRunning { + // If daemon pod is supposed to be running on node, but isn't, create daemon pod. + nodesNeedingDaemonPods = append(nodesNeedingDaemonPods, node.Name) + } else if shouldRun && len(daemonPods) > 1 { + // If daemon pod is supposed to be running on node, but more than 1 daemon pod is running, delete the excess daemon pods. + // Sort the daemon pods by creation time, so the the oldest is preserved. + sort.Sort(podByCreationTimestamp(daemonPods)) + for i := 1; i < len(daemonPods); i++ { + podsToDelete = append(podsToDelete, daemonPods[i].Name) + } + } else if !shouldRun && isRunning { + // If daemon pod isn't supposed to run on node, but it is, delete all daemon pods on node. + for i := range daemonPods { + podsToDelete = append(podsToDelete, daemonPods[i].Name) + } + } + } + + // We need to set expectations before creating/deleting pods to avoid race conditions. + dsKey, err := controller.KeyFunc(ds) + if err != nil { + glog.Errorf("Couldn't get key for object %+v: %v", ds, err) + return + } + + createDiff := len(nodesNeedingDaemonPods) + deleteDiff := len(podsToDelete) + + if createDiff > dsc.burstReplicas { + createDiff = dsc.burstReplicas + } + if deleteDiff > dsc.burstReplicas { + deleteDiff = dsc.burstReplicas + } + + dsc.expectations.SetExpectations(dsKey, createDiff, deleteDiff) + + glog.V(4).Infof("Nodes needing daemon pods for daemon set %s: %+v, creating %d", ds.Name, nodesNeedingDaemonPods, createDiff) + createWait := sync.WaitGroup{} + createWait.Add(createDiff) + for i := 0; i < createDiff; i++ { + go func(ix int) { + defer createWait.Done() + if err := dsc.podControl.CreatePodsOnNode(nodesNeedingDaemonPods[ix], ds.Namespace, &ds.Spec.Template, ds); err != nil { + glog.V(2).Infof("Failed creation, decrementing expectations for set %q/%q", ds.Namespace, ds.Name) + dsc.expectations.CreationObserved(dsKey) + utilruntime.HandleError(err) + } + }(i) + } + createWait.Wait() + + glog.V(4).Infof("Pods to delete for daemon set %s: %+v, deleting %d", ds.Name, podsToDelete, deleteDiff) + deleteWait := sync.WaitGroup{} + deleteWait.Add(deleteDiff) + for i := 0; i < deleteDiff; i++ { + go func(ix int) { + defer deleteWait.Done() + if err := dsc.podControl.DeletePod(ds.Namespace, podsToDelete[ix], ds); err != nil { + glog.V(2).Infof("Failed deletion, decrementing expectations for set %q/%q", ds.Namespace, ds.Name) + dsc.expectations.DeletionObserved(dsKey) + utilruntime.HandleError(err) + } + }(i) + } + deleteWait.Wait() +} + +func storeDaemonSetStatus(dsClient unversionedextensions.DaemonSetInterface, ds *extensions.DaemonSet, desiredNumberScheduled, currentNumberScheduled, numberMisscheduled int) error { + if ds.Status.DesiredNumberScheduled == desiredNumberScheduled && ds.Status.CurrentNumberScheduled == currentNumberScheduled && ds.Status.NumberMisscheduled == numberMisscheduled { + return nil + } + + var updateErr, getErr error + for i := 0; i <= StatusUpdateRetries; i++ { + ds.Status.DesiredNumberScheduled = desiredNumberScheduled + ds.Status.CurrentNumberScheduled = currentNumberScheduled + ds.Status.NumberMisscheduled = numberMisscheduled + + _, updateErr = dsClient.UpdateStatus(ds) + if updateErr == nil { + // successful update + return nil + } + // Update the set with the latest resource version for the next poll + if ds, getErr = dsClient.Get(ds.Name); getErr != nil { + // If the GET fails we can't trust status.Replicas anymore. This error + // is bound to be more interesting than the update failure. + return getErr + } + } + return updateErr +} + +func (dsc *DaemonSetsController) updateDaemonSetStatus(ds *extensions.DaemonSet) { + glog.V(4).Infof("Updating daemon set status") + nodeToDaemonPods, err := dsc.getNodesToDaemonPods(ds) + if err != nil { + glog.Errorf("Error getting node to daemon pod mapping for daemon set %+v: %v", ds, err) + } + + nodeList, err := dsc.nodeStore.List() + if err != nil { + glog.Errorf("Couldn't get list of nodes when updating daemon set %+v: %v", ds, err) + } + + var desiredNumberScheduled, currentNumberScheduled, numberMisscheduled int + for _, node := range nodeList.Items { + shouldRun := dsc.nodeShouldRunDaemonPod(&node, ds) + + numDaemonPods := len(nodeToDaemonPods[node.Name]) + + if shouldRun && numDaemonPods > 0 { + currentNumberScheduled++ + } + + if shouldRun { + desiredNumberScheduled++ + } + + if !shouldRun && numDaemonPods > 0 { + numberMisscheduled++ + } + } + + err = storeDaemonSetStatus(dsc.kubeClient.Extensions().DaemonSets(ds.Namespace), ds, desiredNumberScheduled, currentNumberScheduled, numberMisscheduled) + if err != nil { + glog.Errorf("Error storing status for daemon set %+v: %v", ds, err) + } +} + +func (dsc *DaemonSetsController) syncDaemonSet(key string) error { + startTime := time.Now() + defer func() { + glog.V(4).Infof("Finished syncing daemon set %q (%v)", key, time.Now().Sub(startTime)) + }() + + if !dsc.podStoreSynced() { + // Sleep so we give the pod reflector goroutine a chance to run. + time.Sleep(PodStoreSyncedPollPeriod) + glog.Infof("Waiting for pods controller to sync, requeuing ds %v", key) + dsc.queue.Add(key) + return nil + } + + obj, exists, err := dsc.dsStore.Store.GetByKey(key) + if err != nil { + glog.Infof("Unable to retrieve ds %v from store: %v", key, err) + dsc.queue.Add(key) + return err + } + if !exists { + glog.V(3).Infof("daemon set has been deleted %v", key) + dsc.expectations.DeleteExpectations(key) + return nil + } + ds := obj.(*extensions.DaemonSet) + + everything := unversioned.LabelSelector{} + if reflect.DeepEqual(ds.Spec.Selector, &everything) { + dsc.eventRecorder.Eventf(ds, api.EventTypeWarning, "SelectingAll", "This daemon set is selecting all pods. A non-empty selector is required.") + return nil + } + + // Don't process a daemon set until all its creations and deletions have been processed. + // For example if daemon set foo asked for 3 new daemon pods in the previous call to manage, + // then we do not want to call manage on foo until the daemon pods have been created. + dsKey, err := controller.KeyFunc(ds) + if err != nil { + glog.Errorf("Couldn't get key for object %+v: %v", ds, err) + return err + } + dsNeedsSync := dsc.expectations.SatisfiedExpectations(dsKey) + if dsNeedsSync { + dsc.manage(ds) + } + + dsc.updateDaemonSetStatus(ds) + return nil +} + +func (dsc *DaemonSetsController) nodeShouldRunDaemonPod(node *api.Node, ds *extensions.DaemonSet) bool { + // Check if the node satisfies the daemon set's node selector. + nodeSelector := labels.Set(ds.Spec.Template.Spec.NodeSelector).AsSelector() + if !nodeSelector.Matches(labels.Set(node.Labels)) { + return false + } + // If the daemon set specifies a node name, check that it matches with node.Name. + if !(ds.Spec.Template.Spec.NodeName == "" || ds.Spec.Template.Spec.NodeName == node.Name) { + return false + } + + for _, c := range node.Status.Conditions { + if c.Type == api.NodeOutOfDisk && c.Status == api.ConditionTrue { + return false + } + } + + newPod := &api.Pod{Spec: ds.Spec.Template.Spec} + newPod.Spec.NodeName = node.Name + pods := []*api.Pod{newPod} + + for _, m := range dsc.podStore.Store.List() { + pod := m.(*api.Pod) + if pod.Spec.NodeName != node.Name { + continue + } + if pod.Status.Phase == api.PodSucceeded || pod.Status.Phase == api.PodFailed { + continue + } + // ignore pods that belong to the daemonset when taking into account wheter + // a daemonset should bind to a node. + if pds := dsc.getPodDaemonSet(pod); pds != nil && ds.Name == pds.Name { + continue + } + pods = append(pods, pod) + } + _, notFittingCPU, notFittingMemory := predicates.CheckPodsExceedingFreeResources(pods, node.Status.Allocatable) + if len(notFittingCPU)+len(notFittingMemory) != 0 { + dsc.eventRecorder.Eventf(ds, api.EventTypeNormal, "FailedPlacement", "failed to place pod on %q: insufficent free resources", node.ObjectMeta.Name) + return false + } + ports := sets.String{} + for _, pod := range pods { + if errs := validation.AccumulateUniqueHostPorts(pod.Spec.Containers, &ports, field.NewPath("spec", "containers")); len(errs) > 0 { + dsc.eventRecorder.Eventf(ds, api.EventTypeNormal, "FailedPlacement", "failed to place pod on %q: host port conflict", node.ObjectMeta.Name) + return false + } + } + return true +} + +// byCreationTimestamp sorts a list by creation timestamp, using their names as a tie breaker. +type byCreationTimestamp []extensions.DaemonSet + +func (o byCreationTimestamp) Len() int { return len(o) } +func (o byCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } + +func (o byCreationTimestamp) Less(i, j int) bool { + if o[i].CreationTimestamp.Equal(o[j].CreationTimestamp) { + return o[i].Name < o[j].Name + } + return o[i].CreationTimestamp.Before(o[j].CreationTimestamp) +} + +type podByCreationTimestamp []*api.Pod + +func (o podByCreationTimestamp) Len() int { return len(o) } +func (o podByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } + +func (o podByCreationTimestamp) Less(i, j int) bool { + if o[i].CreationTimestamp.Equal(o[j].CreationTimestamp) { + return o[i].Name < o[j].Name + } + return o[i].CreationTimestamp.Before(o[j].CreationTimestamp) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/daemon/controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/daemon/controller_test.go new file mode 100644 index 000000000..61b08c85c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/daemon/controller_test.go @@ -0,0 +1,551 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 daemon + +import ( + "fmt" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/securitycontext" +) + +var ( + simpleDaemonSetLabel = map[string]string{"name": "simple-daemon", "type": "production"} + simpleDaemonSetLabel2 = map[string]string{"name": "simple-daemon", "type": "test"} + simpleNodeLabel = map[string]string{"color": "blue", "speed": "fast"} + simpleNodeLabel2 = map[string]string{"color": "red", "speed": "fast"} + alwaysReady = func() bool { return true } +) + +func getKey(ds *extensions.DaemonSet, t *testing.T) string { + if key, err := controller.KeyFunc(ds); err != nil { + t.Errorf("Unexpected error getting key for ds %v: %v", ds.Name, err) + return "" + } else { + return key + } +} + +func newDaemonSet(name string) *extensions.DaemonSet { + return &extensions.DaemonSet{ + TypeMeta: unversioned.TypeMeta{APIVersion: testapi.Extensions.GroupVersion().String()}, + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: api.NamespaceDefault, + }, + Spec: extensions.DaemonSetSpec{ + Selector: &unversioned.LabelSelector{MatchLabels: simpleDaemonSetLabel}, + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: simpleDaemonSetLabel, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Image: "foo/bar", + TerminationMessagePath: api.TerminationMessagePathDefault, + ImagePullPolicy: api.PullIfNotPresent, + SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), + }, + }, + DNSPolicy: api.DNSDefault, + }, + }, + }, + } +} + +func newNode(name string, label map[string]string) *api.Node { + return &api.Node{ + TypeMeta: unversioned.TypeMeta{APIVersion: testapi.Default.GroupVersion().String()}, + ObjectMeta: api.ObjectMeta{ + Name: name, + Labels: label, + Namespace: api.NamespaceDefault, + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + {Type: api.NodeReady, Status: api.ConditionTrue}, + }, + }, + } +} + +func addNodes(nodeStore cache.Store, startIndex, numNodes int, label map[string]string) { + for i := startIndex; i < startIndex+numNodes; i++ { + nodeStore.Add(newNode(fmt.Sprintf("node-%d", i), label)) + } +} + +func newPod(podName string, nodeName string, label map[string]string) *api.Pod { + pod := &api.Pod{ + TypeMeta: unversioned.TypeMeta{APIVersion: testapi.Default.GroupVersion().String()}, + ObjectMeta: api.ObjectMeta{ + GenerateName: podName, + Labels: label, + Namespace: api.NamespaceDefault, + }, + Spec: api.PodSpec{ + NodeName: nodeName, + Containers: []api.Container{ + { + Image: "foo/bar", + TerminationMessagePath: api.TerminationMessagePathDefault, + ImagePullPolicy: api.PullIfNotPresent, + SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), + }, + }, + DNSPolicy: api.DNSDefault, + }, + } + api.GenerateName(api.SimpleNameGenerator, &pod.ObjectMeta) + return pod +} + +func addPods(podStore cache.Store, nodeName string, label map[string]string, number int) { + for i := 0; i < number; i++ { + podStore.Add(newPod(fmt.Sprintf("%s-", nodeName), nodeName, label)) + } +} + +func newTestController() (*DaemonSetsController, *controller.FakePodControl) { + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewDaemonSetsController(clientset, controller.NoResyncPeriodFunc, 0) + manager.podStoreSynced = alwaysReady + podControl := &controller.FakePodControl{} + manager.podControl = podControl + return manager, podControl +} + +func validateSyncDaemonSets(t *testing.T, fakePodControl *controller.FakePodControl, expectedCreates, expectedDeletes int) { + if len(fakePodControl.Templates) != expectedCreates { + t.Errorf("Unexpected number of creates. Expected %d, saw %d\n", expectedCreates, len(fakePodControl.Templates)) + } + if len(fakePodControl.DeletePodName) != expectedDeletes { + t.Errorf("Unexpected number of deletes. Expected %d, saw %d\n", expectedDeletes, len(fakePodControl.DeletePodName)) + } +} + +func syncAndValidateDaemonSets(t *testing.T, manager *DaemonSetsController, ds *extensions.DaemonSet, podControl *controller.FakePodControl, expectedCreates, expectedDeletes int) { + key, err := controller.KeyFunc(ds) + if err != nil { + t.Errorf("Could not get key for daemon.") + } + manager.syncHandler(key) + validateSyncDaemonSets(t, podControl, expectedCreates, expectedDeletes) +} + +// DaemonSets without node selectors should launch pods on every node. +func TestSimpleDaemonSetLaunchesPods(t *testing.T) { + manager, podControl := newTestController() + addNodes(manager.nodeStore.Store, 0, 5, nil) + ds := newDaemonSet("foo") + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 5, 0) +} + +// DaemonSets should do nothing if there aren't any nodes +func TestNoNodesDoesNothing(t *testing.T) { + manager, podControl := newTestController() + ds := newDaemonSet("foo") + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0) +} + +// DaemonSets without node selectors should launch on a single node in a +// single node cluster. +func TestOneNodeDaemonLaunchesPod(t *testing.T) { + manager, podControl := newTestController() + manager.nodeStore.Add(newNode("only-node", nil)) + ds := newDaemonSet("foo") + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 1, 0) +} + +// DaemonSets should place onto NotReady nodes +func TestNotReadNodeDaemonDoesNotLaunchPod(t *testing.T) { + manager, podControl := newTestController() + node := newNode("not-ready", nil) + node.Status = api.NodeStatus{ + Conditions: []api.NodeCondition{ + {Type: api.NodeReady, Status: api.ConditionFalse}, + }, + } + manager.nodeStore.Add(node) + ds := newDaemonSet("foo") + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 1, 0) +} + +// DaemonSets should not place onto OutOfDisk nodes +func TestOutOfDiskNodeDaemonDoesNotLaunchPod(t *testing.T) { + manager, podControl := newTestController() + node := newNode("not-enough-disk", nil) + node.Status.Conditions = []api.NodeCondition{{Type: api.NodeOutOfDisk, Status: api.ConditionTrue}} + manager.nodeStore.Add(node) + ds := newDaemonSet("foo") + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0) +} + +// DaemonSets should not place onto nodes with insufficient free resource +func TestInsufficentCapacityNodeDaemonDoesNotLaunchPod(t *testing.T) { + podSpec := api.PodSpec{ + NodeName: "too-much-mem", + Containers: []api.Container{{ + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceMemory: resource.MustParse("75M"), + api.ResourceCPU: resource.MustParse("75m"), + }, + }, + }}, + } + manager, podControl := newTestController() + node := newNode("too-much-mem", nil) + node.Status.Allocatable = api.ResourceList{ + api.ResourceMemory: resource.MustParse("100M"), + api.ResourceCPU: resource.MustParse("200m"), + } + manager.nodeStore.Add(node) + manager.podStore.Add(&api.Pod{ + Spec: podSpec, + }) + ds := newDaemonSet("foo") + ds.Spec.Template.Spec = podSpec + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0) +} + +func TestSufficentCapacityWithTerminatedPodsDaemonLaunchesPod(t *testing.T) { + podSpec := api.PodSpec{ + NodeName: "too-much-mem", + Containers: []api.Container{{ + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceMemory: resource.MustParse("75M"), + api.ResourceCPU: resource.MustParse("75m"), + }, + }, + }}, + } + manager, podControl := newTestController() + node := newNode("too-much-mem", nil) + node.Status.Allocatable = api.ResourceList{ + api.ResourceMemory: resource.MustParse("100M"), + api.ResourceCPU: resource.MustParse("200m"), + } + manager.nodeStore.Add(node) + manager.podStore.Add(&api.Pod{ + Spec: podSpec, + Status: api.PodStatus{Phase: api.PodSucceeded}, + }) + ds := newDaemonSet("foo") + ds.Spec.Template.Spec = podSpec + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 1, 0) +} + +// DaemonSets should place onto nodes with sufficient free resource +func TestSufficentCapacityNodeDaemonLaunchesPod(t *testing.T) { + podSpec := api.PodSpec{ + NodeName: "not-too-much-mem", + Containers: []api.Container{{ + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceMemory: resource.MustParse("75M"), + api.ResourceCPU: resource.MustParse("75m"), + }, + }, + }}, + } + manager, podControl := newTestController() + node := newNode("not-too-much-mem", nil) + node.Status.Allocatable = api.ResourceList{ + api.ResourceMemory: resource.MustParse("200M"), + api.ResourceCPU: resource.MustParse("200m"), + } + manager.nodeStore.Add(node) + manager.podStore.Add(&api.Pod{ + Spec: podSpec, + }) + ds := newDaemonSet("foo") + ds.Spec.Template.Spec = podSpec + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 1, 0) +} + +// DaemonSets should not place onto nodes that would cause port conflicts +func TestPortConflictNodeDaemonDoesNotLaunchPod(t *testing.T) { + podSpec := api.PodSpec{ + NodeName: "port-conflict", + Containers: []api.Container{{ + Ports: []api.ContainerPort{{ + HostPort: 666, + }}, + }}, + } + manager, podControl := newTestController() + node := newNode("port-conflict", nil) + manager.nodeStore.Add(node) + manager.podStore.Add(&api.Pod{ + Spec: podSpec, + }) + + ds := newDaemonSet("foo") + ds.Spec.Template.Spec = podSpec + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0) +} + +// Test that if the node is already scheduled with a pod using a host port +// but belonging to the same daemonset, we don't delete that pod +// +// Issue: https://github.com/kubernetes/kubernetes/issues/22309 +func TestPortConflictWithSameDaemonPodDoesNotDeletePod(t *testing.T) { + podSpec := api.PodSpec{ + NodeName: "port-conflict", + Containers: []api.Container{{ + Ports: []api.ContainerPort{{ + HostPort: 666, + }}, + }}, + } + manager, podControl := newTestController() + node := newNode("port-conflict", nil) + manager.nodeStore.Add(node) + manager.podStore.Add(&api.Pod{ + ObjectMeta: api.ObjectMeta{ + Labels: simpleDaemonSetLabel, + Namespace: api.NamespaceDefault, + }, + Spec: podSpec, + }) + ds := newDaemonSet("foo") + ds.Spec.Template.Spec = podSpec + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0) +} + +// DaemonSets should place onto nodes that would not cause port conflicts +func TestNoPortConflictNodeDaemonLaunchesPod(t *testing.T) { + podSpec1 := api.PodSpec{ + NodeName: "no-port-conflict", + Containers: []api.Container{{ + Ports: []api.ContainerPort{{ + HostPort: 6661, + }}, + }}, + } + podSpec2 := api.PodSpec{ + NodeName: "no-port-conflict", + Containers: []api.Container{{ + Ports: []api.ContainerPort{{ + HostPort: 6662, + }}, + }}, + } + manager, podControl := newTestController() + node := newNode("no-port-conflict", nil) + manager.nodeStore.Add(node) + manager.podStore.Add(&api.Pod{ + Spec: podSpec1, + }) + ds := newDaemonSet("foo") + ds.Spec.Template.Spec = podSpec2 + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 1, 0) +} + +// DaemonSetController should not sync DaemonSets with empty pod selectors. +// +// issue https://github.com/kubernetes/kubernetes/pull/23223 +func TestPodIsNotDeletedByDaemonsetWithEmptyLabelSelector(t *testing.T) { + manager, podControl := newTestController() + manager.nodeStore.Store.Add(newNode("node1", nil)) + // Create pod not controlled by a daemonset. + manager.podStore.Add(&api.Pod{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"bang": "boom"}, + Namespace: api.NamespaceDefault, + }, + Spec: api.PodSpec{ + NodeName: "node1", + }, + }) + + // Create a misconfigured DaemonSet. An empty pod selector is invalid but could happen + // if we upgrade and make a backwards incompatible change. + // + // The node selector matches no nodes which mimics the behavior of kubectl delete. + // + // The DaemonSet should not schedule pods and should not delete scheduled pods in + // this case even though it's empty pod selector matches all pods. The DaemonSetController + // should detect this misconfiguration and choose not to sync the DaemonSet. We should + // not observe a deletion of the pod on node1. + ds := newDaemonSet("foo") + ls := unversioned.LabelSelector{} + ds.Spec.Selector = &ls + ds.Spec.Template.Spec.NodeSelector = map[string]string{"foo": "bar"} + manager.dsStore.Add(ds) + + syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0) +} + +// Controller should not create pods on nodes which have daemon pods, and should remove excess pods from nodes that have extra pods. +func TestDealsWithExistingPods(t *testing.T) { + manager, podControl := newTestController() + addNodes(manager.nodeStore.Store, 0, 5, nil) + addPods(manager.podStore.Store, "node-1", simpleDaemonSetLabel, 1) + addPods(manager.podStore.Store, "node-2", simpleDaemonSetLabel, 2) + addPods(manager.podStore.Store, "node-3", simpleDaemonSetLabel, 5) + addPods(manager.podStore.Store, "node-4", simpleDaemonSetLabel2, 2) + ds := newDaemonSet("foo") + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 2, 5) +} + +// Daemon with node selector should launch pods on nodes matching selector. +func TestSelectorDaemonLaunchesPods(t *testing.T) { + manager, podControl := newTestController() + addNodes(manager.nodeStore.Store, 0, 4, nil) + addNodes(manager.nodeStore.Store, 4, 3, simpleNodeLabel) + daemon := newDaemonSet("foo") + daemon.Spec.Template.Spec.NodeSelector = simpleNodeLabel + manager.dsStore.Add(daemon) + syncAndValidateDaemonSets(t, manager, daemon, podControl, 3, 0) +} + +// Daemon with node selector should delete pods from nodes that do not satisfy selector. +func TestSelectorDaemonDeletesUnselectedPods(t *testing.T) { + manager, podControl := newTestController() + addNodes(manager.nodeStore.Store, 0, 5, nil) + addNodes(manager.nodeStore.Store, 5, 5, simpleNodeLabel) + addPods(manager.podStore.Store, "node-0", simpleDaemonSetLabel2, 2) + addPods(manager.podStore.Store, "node-1", simpleDaemonSetLabel, 3) + addPods(manager.podStore.Store, "node-1", simpleDaemonSetLabel2, 1) + addPods(manager.podStore.Store, "node-4", simpleDaemonSetLabel, 1) + daemon := newDaemonSet("foo") + daemon.Spec.Template.Spec.NodeSelector = simpleNodeLabel + manager.dsStore.Add(daemon) + syncAndValidateDaemonSets(t, manager, daemon, podControl, 5, 4) +} + +// DaemonSet with node selector should launch pods on nodes matching selector, but also deal with existing pods on nodes. +func TestSelectorDaemonDealsWithExistingPods(t *testing.T) { + manager, podControl := newTestController() + addNodes(manager.nodeStore.Store, 0, 5, nil) + addNodes(manager.nodeStore.Store, 5, 5, simpleNodeLabel) + addPods(manager.podStore.Store, "node-0", simpleDaemonSetLabel, 1) + addPods(manager.podStore.Store, "node-1", simpleDaemonSetLabel, 3) + addPods(manager.podStore.Store, "node-1", simpleDaemonSetLabel2, 2) + addPods(manager.podStore.Store, "node-2", simpleDaemonSetLabel, 4) + addPods(manager.podStore.Store, "node-6", simpleDaemonSetLabel, 13) + addPods(manager.podStore.Store, "node-7", simpleDaemonSetLabel2, 4) + addPods(manager.podStore.Store, "node-9", simpleDaemonSetLabel, 1) + addPods(manager.podStore.Store, "node-9", simpleDaemonSetLabel2, 1) + ds := newDaemonSet("foo") + ds.Spec.Template.Spec.NodeSelector = simpleNodeLabel + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 3, 20) +} + +// DaemonSet with node selector which does not match any node labels should not launch pods. +func TestBadSelectorDaemonDoesNothing(t *testing.T) { + manager, podControl := newTestController() + addNodes(manager.nodeStore.Store, 0, 4, nil) + addNodes(manager.nodeStore.Store, 4, 3, simpleNodeLabel) + ds := newDaemonSet("foo") + ds.Spec.Template.Spec.NodeSelector = simpleNodeLabel2 + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0) +} + +// DaemonSet with node name should launch pod on node with corresponding name. +func TestNameDaemonSetLaunchesPods(t *testing.T) { + manager, podControl := newTestController() + addNodes(manager.nodeStore.Store, 0, 5, nil) + ds := newDaemonSet("foo") + ds.Spec.Template.Spec.NodeName = "node-0" + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 1, 0) +} + +// DaemonSet with node name that does not exist should not launch pods. +func TestBadNameDaemonSetDoesNothing(t *testing.T) { + manager, podControl := newTestController() + addNodes(manager.nodeStore.Store, 0, 5, nil) + ds := newDaemonSet("foo") + ds.Spec.Template.Spec.NodeName = "node-10" + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0) +} + +// DaemonSet with node selector, and node name, matching a node, should launch a pod on the node. +func TestNameAndSelectorDaemonSetLaunchesPods(t *testing.T) { + manager, podControl := newTestController() + addNodes(manager.nodeStore.Store, 0, 4, nil) + addNodes(manager.nodeStore.Store, 4, 3, simpleNodeLabel) + ds := newDaemonSet("foo") + ds.Spec.Template.Spec.NodeSelector = simpleNodeLabel + ds.Spec.Template.Spec.NodeName = "node-6" + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 1, 0) +} + +// DaemonSet with node selector that matches some nodes, and node name that matches a different node, should do nothing. +func TestInconsistentNameSelectorDaemonSetDoesNothing(t *testing.T) { + manager, podControl := newTestController() + addNodes(manager.nodeStore.Store, 0, 4, nil) + addNodes(manager.nodeStore.Store, 4, 3, simpleNodeLabel) + ds := newDaemonSet("foo") + ds.Spec.Template.Spec.NodeSelector = simpleNodeLabel + ds.Spec.Template.Spec.NodeName = "node-0" + manager.dsStore.Add(ds) + syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0) +} + +func TestDSManagerNotReady(t *testing.T) { + manager, podControl := newTestController() + manager.podStoreSynced = func() bool { return false } + addNodes(manager.nodeStore.Store, 0, 1, nil) + + // Simulates the ds reflector running before the pod reflector. We don't + // want to end up creating daemon pods in this case until the pod reflector + // has synced, so the ds manager should just requeue the ds. + ds := newDaemonSet("foo") + manager.dsStore.Add(ds) + + dsKey := getKey(ds, t) + syncAndValidateDaemonSets(t, manager, ds, podControl, 0, 0) + queueDS, _ := manager.queue.Get() + if queueDS != dsKey { + t.Fatalf("Expected to find key %v in queue, found %v", dsKey, queueDS) + } + + manager.podStoreSynced = alwaysReady + syncAndValidateDaemonSets(t, manager, ds, podControl, 1, 0) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/daemon/doc.go b/vendor/k8s.io/kubernetes/pkg/controller/daemon/doc.go new file mode 100644 index 000000000..db689ac1b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/daemon/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 daemon contains logic for watching and synchronizing +// daemons. +package daemon diff --git a/vendor/k8s.io/kubernetes/pkg/controller/deployment/deployment_controller.go b/vendor/k8s.io/kubernetes/pkg/controller/deployment/deployment_controller.go new file mode 100644 index 000000000..854425715 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/deployment/deployment_controller.go @@ -0,0 +1,1291 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 deployment + +import ( + "fmt" + "reflect" + "sort" + "strconv" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/runtime" + deploymentutil "k8s.io/kubernetes/pkg/util/deployment" + utilerrors "k8s.io/kubernetes/pkg/util/errors" + "k8s.io/kubernetes/pkg/util/integer" + labelsutil "k8s.io/kubernetes/pkg/util/labels" + podutil "k8s.io/kubernetes/pkg/util/pod" + rsutil "k8s.io/kubernetes/pkg/util/replicaset" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/util/workqueue" + "k8s.io/kubernetes/pkg/watch" +) + +const ( + // FullDeploymentResyncPeriod means we'll attempt to recompute the required replicas + // of all deployments. + // This recomputation happens based on contents in the local caches. + FullDeploymentResyncPeriod = 30 * time.Second + // We must avoid creating new replica set / counting pods until the replica set / pods store has synced. + // If it hasn't synced, to avoid a hot loop, we'll wait this long between checks. + StoreSyncedPollPeriod = 100 * time.Millisecond +) + +// DeploymentController is responsible for synchronizing Deployment objects stored +// in the system with actual running replica sets and pods. +type DeploymentController struct { + client clientset.Interface + eventRecorder record.EventRecorder + + // To allow injection of syncDeployment for testing. + syncHandler func(dKey string) error + + // A store of deployments, populated by the dController + dStore cache.StoreToDeploymentLister + // Watches changes to all deployments + dController *framework.Controller + // A store of ReplicaSets, populated by the rsController + rsStore cache.StoreToReplicaSetLister + // Watches changes to all ReplicaSets + rsController *framework.Controller + // rsStoreSynced returns true if the ReplicaSet store has been synced at least once. + // Added as a member to the struct to allow injection for testing. + rsStoreSynced func() bool + // A store of pods, populated by the podController + podStore cache.StoreToPodLister + // Watches changes to all pods + podController *framework.Controller + // podStoreSynced returns true if the pod store has been synced at least once. + // Added as a member to the struct to allow injection for testing. + podStoreSynced func() bool + + // Deployments that need to be synced + queue *workqueue.Type +} + +// NewDeploymentController creates a new DeploymentController. +func NewDeploymentController(client clientset.Interface, resyncPeriod controller.ResyncPeriodFunc) *DeploymentController { + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(glog.Infof) + // TODO: remove the wrapper when every clients have moved to use the clientset. + eventBroadcaster.StartRecordingToSink(&unversionedcore.EventSinkImpl{Interface: client.Core().Events("")}) + + dc := &DeploymentController{ + client: client, + eventRecorder: eventBroadcaster.NewRecorder(api.EventSource{Component: "deployment-controller"}), + queue: workqueue.New(), + } + + dc.dStore.Store, dc.dController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return dc.client.Extensions().Deployments(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return dc.client.Extensions().Deployments(api.NamespaceAll).Watch(options) + }, + }, + &extensions.Deployment{}, + FullDeploymentResyncPeriod, + framework.ResourceEventHandlerFuncs{ + AddFunc: dc.addDeploymentNotification, + UpdateFunc: dc.updateDeploymentNotification, + // This will enter the sync loop and no-op, because the deployment has been deleted from the store. + DeleteFunc: dc.deleteDeploymentNotification, + }, + ) + + dc.rsStore.Store, dc.rsController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return dc.client.Extensions().ReplicaSets(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return dc.client.Extensions().ReplicaSets(api.NamespaceAll).Watch(options) + }, + }, + &extensions.ReplicaSet{}, + resyncPeriod(), + framework.ResourceEventHandlerFuncs{ + AddFunc: dc.addReplicaSet, + UpdateFunc: dc.updateReplicaSet, + DeleteFunc: dc.deleteReplicaSet, + }, + ) + + dc.podStore.Store, dc.podController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return dc.client.Core().Pods(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return dc.client.Core().Pods(api.NamespaceAll).Watch(options) + }, + }, + &api.Pod{}, + resyncPeriod(), + framework.ResourceEventHandlerFuncs{ + AddFunc: dc.addPod, + UpdateFunc: dc.updatePod, + DeleteFunc: dc.deletePod, + }, + ) + + dc.syncHandler = dc.syncDeployment + dc.rsStoreSynced = dc.rsController.HasSynced + dc.podStoreSynced = dc.podController.HasSynced + return dc +} + +// Run begins watching and syncing. +func (dc *DeploymentController) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + go dc.dController.Run(stopCh) + go dc.rsController.Run(stopCh) + go dc.podController.Run(stopCh) + for i := 0; i < workers; i++ { + go wait.Until(dc.worker, time.Second, stopCh) + } + <-stopCh + glog.Infof("Shutting down deployment controller") + dc.queue.ShutDown() +} + +func (dc *DeploymentController) addDeploymentNotification(obj interface{}) { + d := obj.(*extensions.Deployment) + glog.V(4).Infof("Adding deployment %s", d.Name) + dc.enqueueDeployment(d) +} + +func (dc *DeploymentController) updateDeploymentNotification(old, cur interface{}) { + oldD := old.(*extensions.Deployment) + glog.V(4).Infof("Updating deployment %s", oldD.Name) + // Resync on deployment object relist. + dc.enqueueDeployment(cur.(*extensions.Deployment)) +} + +func (dc *DeploymentController) deleteDeploymentNotification(obj interface{}) { + d, ok := obj.(*extensions.Deployment) + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + glog.Errorf("Couldn't get object from tombstone %+v", obj) + return + } + d, ok = tombstone.Obj.(*extensions.Deployment) + if !ok { + glog.Errorf("Tombstone contained object that is not a Deployment %+v", obj) + return + } + } + glog.V(4).Infof("Deleting deployment %s", d.Name) + dc.enqueueDeployment(d) +} + +// addReplicaSet enqueues the deployment that manages a ReplicaSet when the ReplicaSet is created. +func (dc *DeploymentController) addReplicaSet(obj interface{}) { + rs := obj.(*extensions.ReplicaSet) + glog.V(4).Infof("ReplicaSet %s added.", rs.Name) + if d := dc.getDeploymentForReplicaSet(rs); d != nil { + dc.enqueueDeployment(d) + } +} + +// getDeploymentForReplicaSet returns the deployment managing the given ReplicaSet. +// TODO: Surface that we are ignoring multiple deployments for a given ReplicaSet. +func (dc *DeploymentController) getDeploymentForReplicaSet(rs *extensions.ReplicaSet) *extensions.Deployment { + deployments, err := dc.dStore.GetDeploymentsForReplicaSet(rs) + if err != nil || len(deployments) == 0 { + glog.V(4).Infof("Error: %v. No deployment found for ReplicaSet %v, deployment controller will avoid syncing.", err, rs.Name) + return nil + } + // Because all ReplicaSet's belonging to a deployment should have a unique label key, + // there should never be more than one deployment returned by the above method. + // If that happens we should probably dynamically repair the situation by ultimately + // trying to clean up one of the controllers, for now we just return one of the two, + // likely randomly. + return &deployments[0] +} + +// updateReplicaSet figures out what deployment(s) manage a ReplicaSet when the ReplicaSet +// is updated and wake them up. If the anything of the ReplicaSets have changed, we need to +// awaken both the old and new deployments. old and cur must be *extensions.ReplicaSet +// types. +func (dc *DeploymentController) updateReplicaSet(old, cur interface{}) { + if api.Semantic.DeepEqual(old, cur) { + // A periodic relist will send update events for all known controllers. + return + } + // TODO: Write a unittest for this case + curRS := cur.(*extensions.ReplicaSet) + glog.V(4).Infof("ReplicaSet %s updated.", curRS.Name) + if d := dc.getDeploymentForReplicaSet(curRS); d != nil { + dc.enqueueDeployment(d) + } + // A number of things could affect the old deployment: labels changing, + // pod template changing, etc. + oldRS := old.(*extensions.ReplicaSet) + if !api.Semantic.DeepEqual(oldRS, curRS) { + if oldD := dc.getDeploymentForReplicaSet(oldRS); oldD != nil { + dc.enqueueDeployment(oldD) + } + } +} + +// deleteReplicaSet enqueues the deployment that manages a ReplicaSet when +// the ReplicaSet is deleted. obj could be an *extensions.ReplicaSet, or +// a DeletionFinalStateUnknown marker item. +func (dc *DeploymentController) deleteReplicaSet(obj interface{}) { + rs, ok := obj.(*extensions.ReplicaSet) + + // When a delete is dropped, the relist will notice a pod in the store not + // in the list, leading to the insertion of a tombstone object which contains + // the deleted key/value. Note that this value might be stale. If the ReplicaSet + // changed labels the new deployment will not be woken up till the periodic resync. + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + glog.Errorf("Couldn't get object from tombstone %+v, could take up to %v before a deployment recreates/updates replicasets", obj, FullDeploymentResyncPeriod) + return + } + rs, ok = tombstone.Obj.(*extensions.ReplicaSet) + if !ok { + glog.Errorf("Tombstone contained object that is not a ReplicaSet %+v, could take up to %v before a deployment recreates/updates replicasets", obj, FullDeploymentResyncPeriod) + return + } + } + glog.V(4).Infof("ReplicaSet %s deleted.", rs.Name) + if d := dc.getDeploymentForReplicaSet(rs); d != nil { + dc.enqueueDeployment(d) + } +} + +// getDeploymentForPod returns the deployment managing the ReplicaSet that manages the given Pod. +// TODO: Surface that we are ignoring multiple deployments for a given Pod. +func (dc *DeploymentController) getDeploymentForPod(pod *api.Pod) *extensions.Deployment { + rss, err := dc.rsStore.GetPodReplicaSets(pod) + if err != nil { + glog.V(4).Infof("Error: %v. No ReplicaSets found for pod %v, deployment controller will avoid syncing.", err, pod.Name) + return nil + } + for _, rs := range rss { + deployments, err := dc.dStore.GetDeploymentsForReplicaSet(&rs) + if err == nil && len(deployments) > 0 { + return &deployments[0] + } + } + glog.V(4).Infof("No deployments found for pod %v, deployment controller will avoid syncing.", pod.Name) + return nil +} + +// When a pod is created, ensure its controller syncs +func (dc *DeploymentController) addPod(obj interface{}) { + pod, ok := obj.(*api.Pod) + if !ok { + return + } + glog.V(4).Infof("Pod %s created: %+v.", pod.Name, pod) + if d := dc.getDeploymentForPod(pod); d != nil { + dc.enqueueDeployment(d) + } +} + +// updatePod figures out what deployment(s) manage the ReplicaSet that manages the Pod when the Pod +// is updated and wake them up. If anything of the Pods have changed, we need to awaken both +// the old and new deployments. old and cur must be *api.Pod types. +func (dc *DeploymentController) updatePod(old, cur interface{}) { + if api.Semantic.DeepEqual(old, cur) { + return + } + curPod := cur.(*api.Pod) + oldPod := old.(*api.Pod) + glog.V(4).Infof("Pod %s updated %+v -> %+v.", curPod.Name, oldPod, curPod) + if d := dc.getDeploymentForPod(curPod); d != nil { + dc.enqueueDeployment(d) + } + if !api.Semantic.DeepEqual(oldPod, curPod) { + if oldD := dc.getDeploymentForPod(oldPod); oldD != nil { + dc.enqueueDeployment(oldD) + } + } +} + +// When a pod is deleted, ensure its controller syncs. +// obj could be an *api.Pod, or a DeletionFinalStateUnknown marker item. +func (dc *DeploymentController) deletePod(obj interface{}) { + pod, ok := obj.(*api.Pod) + // When a delete is dropped, the relist will notice a pod in the store not + // in the list, leading to the insertion of a tombstone object which contains + // the deleted key/value. Note that this value might be stale. If the pod + // changed labels the new ReplicaSet will not be woken up till the periodic + // resync. + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + glog.Errorf("Couldn't get object from tombstone %+v", obj) + return + } + pod, ok = tombstone.Obj.(*api.Pod) + if !ok { + glog.Errorf("Tombstone contained object that is not a pod %+v", obj) + return + } + } + glog.V(4).Infof("Pod %s deleted: %+v.", pod.Name, pod) + if d := dc.getDeploymentForPod(pod); d != nil { + dc.enqueueDeployment(d) + } +} + +func (dc *DeploymentController) enqueueDeployment(deployment *extensions.Deployment) { + key, err := controller.KeyFunc(deployment) + if err != nil { + glog.Errorf("Couldn't get key for object %+v: %v", deployment, err) + return + } + + // TODO: Handle overlapping deployments better. Either disallow them at admission time or + // deterministically avoid syncing deployments that fight over ReplicaSet's. Currently, we + // only ensure that the same deployment is synced for a given ReplicaSet. When we + // periodically relist all deployments there will still be some ReplicaSet instability. One + // way to handle this is by querying the store for all deployments that this deployment + // overlaps, as well as all deployments that overlap this deployments, and sorting them. + dc.queue.Add(key) +} + +// worker runs a worker thread that just dequeues items, processes them, and marks them done. +// It enforces that the syncHandler is never invoked concurrently with the same key. +func (dc *DeploymentController) worker() { + for { + func() { + key, quit := dc.queue.Get() + if quit { + return + } + defer dc.queue.Done(key) + err := dc.syncHandler(key.(string)) + if err != nil { + glog.Errorf("Error syncing deployment %v: %v", key, err) + } + }() + } +} + +// syncDeployment will sync the deployment with the given key. +// This function is not meant to be invoked concurrently with the same key. +func (dc *DeploymentController) syncDeployment(key string) error { + startTime := time.Now() + defer func() { + glog.V(4).Infof("Finished syncing deployment %q (%v)", key, time.Now().Sub(startTime)) + }() + + if !dc.rsStoreSynced() || !dc.podStoreSynced() { + // Sleep so we give the replica set / pod reflector goroutine a chance to run. + time.Sleep(StoreSyncedPollPeriod) + glog.Infof("Waiting for replica set / pod controller to sync, requeuing deployment %s", key) + dc.queue.Add(key) + return nil + } + + obj, exists, err := dc.dStore.Store.GetByKey(key) + if err != nil { + glog.Infof("Unable to retrieve deployment %v from store: %v", key, err) + dc.queue.Add(key) + return err + } + if !exists { + glog.Infof("Deployment has been deleted %v", key) + return nil + } + + d := obj.(*extensions.Deployment) + everything := unversioned.LabelSelector{} + if reflect.DeepEqual(d.Spec.Selector, &everything) { + dc.eventRecorder.Eventf(d, api.EventTypeWarning, "SelectingAll", "This deployment is selecting all pods. A non-empty selector is required.") + return nil + } + + if d.Spec.Paused { + // TODO: Implement scaling for paused deployments. + // Don't take any action for paused deployment. + // But keep the status up-to-date. + // Ignore paused deployments + glog.V(4).Infof("Updating status only for paused deployment %s/%s", d.Namespace, d.Name) + return dc.syncPausedDeploymentStatus(d) + } + if d.Spec.RollbackTo != nil { + revision := d.Spec.RollbackTo.Revision + if _, err = dc.rollback(d, &revision); err != nil { + return err + } + } + + switch d.Spec.Strategy.Type { + case extensions.RecreateDeploymentStrategyType: + return dc.syncRecreateDeployment(d) + case extensions.RollingUpdateDeploymentStrategyType: + return dc.syncRollingUpdateDeployment(d) + } + return fmt.Errorf("unexpected deployment strategy type: %s", d.Spec.Strategy.Type) +} + +// Updates the status of a paused deployment +func (dc *DeploymentController) syncPausedDeploymentStatus(deployment *extensions.Deployment) error { + newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(deployment, false) + if err != nil { + return err + } + allRSs := append(controller.FilterActiveReplicaSets(oldRSs), newRS) + + // Sync deployment status + return dc.syncDeploymentStatus(allRSs, newRS, deployment) +} + +// Rolling back to a revision; no-op if the toRevision is deployment's current revision +func (dc *DeploymentController) rollback(deployment *extensions.Deployment, toRevision *int64) (*extensions.Deployment, error) { + newRS, allOldRSs, err := dc.getAllReplicaSetsAndSyncRevision(deployment, true) + if err != nil { + return nil, err + } + allRSs := append(allOldRSs, newRS) + // If rollback revision is 0, rollback to the last revision + if *toRevision == 0 { + if *toRevision = lastRevision(allRSs); *toRevision == 0 { + // If we still can't find the last revision, gives up rollback + dc.emitRollbackWarningEvent(deployment, deploymentutil.RollbackRevisionNotFound, "Unable to find last revision.") + // Gives up rollback + return dc.updateDeploymentAndClearRollbackTo(deployment) + } + } + for _, rs := range allRSs { + v, err := deploymentutil.Revision(rs) + if err != nil { + glog.V(4).Infof("Unable to extract revision from deployment's replica set %q: %v", rs.Name, err) + continue + } + if v == *toRevision { + glog.V(4).Infof("Found replica set %q with desired revision %d", rs.Name, v) + // rollback by copying podTemplate.Spec from the replica set, and increment revision number by 1 + // no-op if the the spec matches current deployment's podTemplate.Spec + deployment, performedRollback, err := dc.rollbackToTemplate(deployment, rs) + if performedRollback && err == nil { + dc.emitRollbackNormalEvent(deployment, fmt.Sprintf("Rolled back deployment %q to revision %d", deployment.Name, *toRevision)) + } + return deployment, err + } + } + dc.emitRollbackWarningEvent(deployment, deploymentutil.RollbackRevisionNotFound, "Unable to find the revision to rollback to.") + // Gives up rollback + return dc.updateDeploymentAndClearRollbackTo(deployment) +} + +func (dc *DeploymentController) emitRollbackWarningEvent(deployment *extensions.Deployment, reason, message string) { + dc.eventRecorder.Eventf(deployment, api.EventTypeWarning, reason, message) +} + +func (dc *DeploymentController) emitRollbackNormalEvent(deployment *extensions.Deployment, message string) { + dc.eventRecorder.Eventf(deployment, api.EventTypeNormal, deploymentutil.RollbackDone, message) +} + +// updateDeploymentAndClearRollbackTo sets .spec.rollbackTo to nil and update the input deployment +func (dc *DeploymentController) updateDeploymentAndClearRollbackTo(deployment *extensions.Deployment) (*extensions.Deployment, error) { + glog.V(4).Infof("Cleans up rollbackTo of deployment %s", deployment.Name) + deployment.Spec.RollbackTo = nil + return dc.updateDeployment(deployment) +} + +func (dc *DeploymentController) syncRecreateDeployment(deployment *extensions.Deployment) error { + // Don't create a new RS if not already existed, so that we avoid scaling up before scaling down + newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(deployment, false) + if err != nil { + return err + } + allRSs := append(controller.FilterActiveReplicaSets(oldRSs), newRS) + + // scale down old replica sets + scaledDown, err := dc.scaleDownOldReplicaSetsForRecreate(controller.FilterActiveReplicaSets(oldRSs), deployment) + if err != nil { + return err + } + if scaledDown { + // Update DeploymentStatus + return dc.updateDeploymentStatus(allRSs, newRS, deployment) + } + + // If we need to create a new RS, create it now + // TODO: Create a new RS without re-listing all RSs. + if newRS == nil { + newRS, oldRSs, err = dc.getAllReplicaSetsAndSyncRevision(deployment, true) + if err != nil { + return err + } + allRSs = append(oldRSs, newRS) + } + + // scale up new replica set + scaledUp, err := dc.scaleUpNewReplicaSetForRecreate(newRS, deployment) + if err != nil { + return err + } + if scaledUp { + // Update DeploymentStatus + return dc.updateDeploymentStatus(allRSs, newRS, deployment) + } + + if deployment.Spec.RevisionHistoryLimit != nil { + // Cleanup old replica sets + dc.cleanupOldReplicaSets(oldRSs, deployment) + } + + // Sync deployment status + return dc.syncDeploymentStatus(allRSs, newRS, deployment) +} + +func (dc *DeploymentController) syncRollingUpdateDeployment(deployment *extensions.Deployment) error { + newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(deployment, true) + if err != nil { + return err + } + allRSs := append(controller.FilterActiveReplicaSets(oldRSs), newRS) + + // Scale up, if we can. + scaledUp, err := dc.reconcileNewReplicaSet(allRSs, newRS, deployment) + if err != nil { + return err + } + if scaledUp { + // Update DeploymentStatus + return dc.updateDeploymentStatus(allRSs, newRS, deployment) + } + + // Scale down, if we can. + scaledDown, err := dc.reconcileOldReplicaSets(allRSs, controller.FilterActiveReplicaSets(oldRSs), newRS, deployment) + if err != nil { + return err + } + if scaledDown { + // Update DeploymentStatus + return dc.updateDeploymentStatus(allRSs, newRS, deployment) + } + + if deployment.Spec.RevisionHistoryLimit != nil { + // Cleanup old replicas sets + dc.cleanupOldReplicaSets(oldRSs, deployment) + } + + // Sync deployment status + return dc.syncDeploymentStatus(allRSs, newRS, deployment) +} + +// syncDeploymentStatus checks if the status is up-to-date and sync it if necessary +func (dc *DeploymentController) syncDeploymentStatus(allRSs []*extensions.ReplicaSet, newRS *extensions.ReplicaSet, d *extensions.Deployment) error { + totalActualReplicas, updatedReplicas, availableReplicas, _, err := dc.calculateStatus(allRSs, newRS, d) + if err != nil { + return err + } + if d.Generation > d.Status.ObservedGeneration || d.Status.Replicas != totalActualReplicas || d.Status.UpdatedReplicas != updatedReplicas || d.Status.AvailableReplicas != availableReplicas { + return dc.updateDeploymentStatus(allRSs, newRS, d) + } + return nil +} + +// getAllReplicaSetsAndSyncRevision returns all the replica sets for the provided deployment (new and all old), with new RS's and deployment's revision updated. +// 1. Get all old RSes this deployment targets, and calculate the max revision number among them (maxOldV). +// 2. Get new RS this deployment targets (whose pod template matches deployment's), and update new RS's revision number to (maxOldV + 1), +// only if its revision number is smaller than (maxOldV + 1). If this step failed, we'll update it in the next deployment sync loop. +// 3. Copy new RS's revision number to deployment (update deployment's revision). If this step failed, we'll update it in the next deployment sync loop. +func (dc *DeploymentController) getAllReplicaSetsAndSyncRevision(deployment *extensions.Deployment, createIfNotExisted bool) (*extensions.ReplicaSet, []*extensions.ReplicaSet, error) { + _, allOldRSs, err := dc.getOldReplicaSets(deployment) + if err != nil { + return nil, nil, err + } + + // Calculate the max revision number among all old RSes + maxOldV := maxRevision(allOldRSs) + + // Get new replica set with the updated revision number + newRS, err := dc.getNewReplicaSet(deployment, maxOldV, allOldRSs, createIfNotExisted) + if err != nil { + return nil, nil, err + } + + // Sync deployment's revision number with new replica set + if newRS != nil && newRS.Annotations != nil && len(newRS.Annotations[deploymentutil.RevisionAnnotation]) > 0 && + (deployment.Annotations == nil || deployment.Annotations[deploymentutil.RevisionAnnotation] != newRS.Annotations[deploymentutil.RevisionAnnotation]) { + if err = dc.updateDeploymentRevision(deployment, newRS.Annotations[deploymentutil.RevisionAnnotation]); err != nil { + glog.V(4).Infof("Error: %v. Unable to update deployment revision, will retry later.", err) + } + } + + return newRS, allOldRSs, nil +} + +func maxRevision(allRSs []*extensions.ReplicaSet) int64 { + max := int64(0) + for _, rs := range allRSs { + if v, err := deploymentutil.Revision(rs); err != nil { + // Skip the replica sets when it failed to parse their revision information + glog.V(4).Infof("Error: %v. Couldn't parse revision for replica set %#v, deployment controller will skip it when reconciling revisions.", err, rs) + } else if v > max { + max = v + } + } + return max +} + +// lastRevision finds the second max revision number in all replica sets (the last revision) +func lastRevision(allRSs []*extensions.ReplicaSet) int64 { + max, secMax := int64(0), int64(0) + for _, rs := range allRSs { + if v, err := deploymentutil.Revision(rs); err != nil { + // Skip the replica sets when it failed to parse their revision information + glog.V(4).Infof("Error: %v. Couldn't parse revision for replica set %#v, deployment controller will skip it when reconciling revisions.", err, rs) + } else if v >= max { + secMax = max + max = v + } else if v > secMax { + secMax = v + } + } + return secMax +} + +// getOldReplicaSets returns two sets of old replica sets of the deployment. The first set of old replica sets doesn't include +// the ones with no pods, and the second set of old replica sets include all old replica sets. +// Note that the pod-template-hash will be added to adopted RSes and pods. +func (dc *DeploymentController) getOldReplicaSets(deployment *extensions.Deployment) ([]*extensions.ReplicaSet, []*extensions.ReplicaSet, error) { + // List the deployment's RSes & Pods and apply pod-template-hash info to deployment's adopted RSes/Pods + rsList, podList, err := dc.rsAndPodsWithHashKeySynced(deployment) + if err != nil { + return nil, nil, fmt.Errorf("error labeling replica sets and pods with pod-template-hash: %v", err) + } + return deploymentutil.FindOldReplicaSets(deployment, rsList, podList) +} + +// Returns a replica set that matches the intent of the given deployment. Returns nil if the new replica set doesn't exist yet. +// 1. Get existing new RS (the RS that the given deployment targets, whose pod template is the same as deployment's). +// 2. If there's existing new RS, update its revision number if it's smaller than (maxOldRevision + 1), where maxOldRevision is the max revision number among all old RSes. +// 3. If there's no existing new RS and createIfNotExisted is true, create one with appropriate revision number (maxOldRevision + 1) and replicas. +// Note that the pod-template-hash will be added to adopted RSes and pods. +func (dc *DeploymentController) getNewReplicaSet(deployment *extensions.Deployment, maxOldRevision int64, oldRSs []*extensions.ReplicaSet, createIfNotExisted bool) (*extensions.ReplicaSet, error) { + // Calculate revision number for this new replica set + newRevision := strconv.FormatInt(maxOldRevision+1, 10) + + // List the deployment's RSes and apply pod-template-hash info to deployment's adopted RSes/Pods + rsList, _, err := dc.rsAndPodsWithHashKeySynced(deployment) + if err != nil { + return nil, fmt.Errorf("error labeling replica sets and pods with pod-template-hash: %v", err) + } + existingNewRS, err := deploymentutil.FindNewReplicaSet(deployment, rsList) + if err != nil { + return nil, err + } else if existingNewRS != nil { + // Set existing new replica set's annotation + if setNewReplicaSetAnnotations(deployment, existingNewRS, newRevision) { + return dc.client.Extensions().ReplicaSets(deployment.ObjectMeta.Namespace).Update(existingNewRS) + } + return existingNewRS, nil + } + + if !createIfNotExisted { + return nil, nil + } + + // new ReplicaSet does not exist, create one. + namespace := deployment.ObjectMeta.Namespace + podTemplateSpecHash := podutil.GetPodTemplateSpecHash(deployment.Spec.Template) + newRSTemplate := deploymentutil.GetNewReplicaSetTemplate(deployment) + // Add podTemplateHash label to selector. + newRSSelector := labelsutil.CloneSelectorAndAddLabel(deployment.Spec.Selector, extensions.DefaultDeploymentUniqueLabelKey, podTemplateSpecHash) + + // Create new ReplicaSet + newRS := extensions.ReplicaSet{ + ObjectMeta: api.ObjectMeta{ + // Make the name deterministic, to ensure idempotence + Name: deployment.Name + "-" + fmt.Sprintf("%d", podTemplateSpecHash), + Namespace: namespace, + }, + Spec: extensions.ReplicaSetSpec{ + Replicas: 0, + Selector: newRSSelector, + Template: newRSTemplate, + }, + } + // Set new replica set's annotation + setNewReplicaSetAnnotations(deployment, &newRS, newRevision) + allRSs := append(oldRSs, &newRS) + newReplicasCount, err := deploymentutil.NewRSNewReplicas(deployment, allRSs, &newRS) + if err != nil { + return nil, err + } + + newRS.Spec.Replicas = newReplicasCount + createdRS, err := dc.client.Extensions().ReplicaSets(namespace).Create(&newRS) + if err != nil { + dc.enqueueDeployment(deployment) + return nil, fmt.Errorf("error creating replica set %v: %v", deployment.Name, err) + } + if newReplicasCount > 0 { + dc.eventRecorder.Eventf(deployment, api.EventTypeNormal, "ScalingReplicaSet", "Scaled %s replica set %s to %d", "up", createdRS.Name, newReplicasCount) + } + + return createdRS, dc.updateDeploymentRevision(deployment, newRevision) +} + +// rsAndPodsWithHashKeySynced returns the RSes and pods the given deployment targets, with pod-template-hash information synced. +func (dc *DeploymentController) rsAndPodsWithHashKeySynced(deployment *extensions.Deployment) ([]extensions.ReplicaSet, *api.PodList, error) { + rsList, err := deploymentutil.ListReplicaSets(deployment, + func(namespace string, options api.ListOptions) ([]extensions.ReplicaSet, error) { + return dc.rsStore.ReplicaSets(namespace).List(options.LabelSelector) + }) + if err != nil { + return nil, nil, fmt.Errorf("error listing ReplicaSets: %v", err) + } + syncedRSList := []extensions.ReplicaSet{} + for _, rs := range rsList { + // Add pod-template-hash information if it's not in the RS. + // Otherwise, new RS produced by Deployment will overlap with pre-existing ones + // that aren't constrained by the pod-template-hash. + syncedRS, err := dc.addHashKeyToRSAndPods(rs) + if err != nil { + return nil, nil, err + } + syncedRSList = append(syncedRSList, *syncedRS) + } + syncedPodList, err := deploymentutil.ListPods(deployment, + func(namespace string, options api.ListOptions) (*api.PodList, error) { + podList, err := dc.podStore.Pods(namespace).List(options.LabelSelector) + return &podList, err + }) + + if err != nil { + return nil, nil, err + } + return syncedRSList, syncedPodList, nil +} + +// addHashKeyToRSAndPods adds pod-template-hash information to the given rs, if it's not already there, with the following steps: +// 1. Add hash label to the rs's pod template, and make sure the controller sees this update so that no orphaned pods will be created +// 2. Add hash label to all pods this rs owns, wait until replicaset controller reports rs.Status.FullyLabeledReplicas equal to the desired number of replicas +// 3. Add hash label to the rs's label and selector +func (dc *DeploymentController) addHashKeyToRSAndPods(rs extensions.ReplicaSet) (updatedRS *extensions.ReplicaSet, err error) { + updatedRS = &rs + // If the rs already has the new hash label in its selector, it's done syncing + if labelsutil.SelectorHasLabel(rs.Spec.Selector, extensions.DefaultDeploymentUniqueLabelKey) { + return + } + namespace := rs.Namespace + hash := rsutil.GetPodTemplateSpecHash(rs) + rsUpdated := false + // 1. Add hash template label to the rs. This ensures that any newly created pods will have the new label. + updatedRS, rsUpdated, err = rsutil.UpdateRSWithRetries(dc.client.Extensions().ReplicaSets(namespace), updatedRS, + func(updated *extensions.ReplicaSet) error { + // Precondition: the RS doesn't contain the new hash in its pod template label. + if updated.Spec.Template.Labels[extensions.DefaultDeploymentUniqueLabelKey] == hash { + return utilerrors.ErrPreconditionViolated + } + updated.Spec.Template.Labels = labelsutil.AddLabel(updated.Spec.Template.Labels, extensions.DefaultDeploymentUniqueLabelKey, hash) + return nil + }) + if err != nil { + return nil, fmt.Errorf("error updating %s %s/%s pod template label with template hash: %v", updatedRS.Kind, updatedRS.Namespace, updatedRS.Name, err) + } + if !rsUpdated { + // If RS wasn't updated but didn't return error in step 1, we've hit a RS not found error. + // Return here and retry in the next sync loop. + return &rs, nil + } + // Make sure rs pod template is updated so that it won't create pods without the new label (orphaned pods). + if updatedRS.Generation > updatedRS.Status.ObservedGeneration { + if err = deploymentutil.WaitForReplicaSetUpdated(dc.client, updatedRS.Generation, namespace, updatedRS.Name); err != nil { + return nil, fmt.Errorf("error waiting for %s %s/%s generation %d observed by controller: %v", updatedRS.Kind, updatedRS.Namespace, updatedRS.Name, updatedRS.Generation, err) + } + } + glog.V(4).Infof("Observed the update of %s %s/%s's pod template with hash %s.", rs.Kind, rs.Namespace, rs.Name, hash) + + // 2. Update all pods managed by the rs to have the new hash label, so they will be correctly adopted. + selector, err := unversioned.LabelSelectorAsSelector(updatedRS.Spec.Selector) + if err != nil { + return nil, fmt.Errorf("error in converting selector to label selector for replica set %s: %s", updatedRS.Name, err) + } + options := api.ListOptions{LabelSelector: selector} + podList, err := dc.podStore.Pods(namespace).List(options.LabelSelector) + if err != nil { + return nil, fmt.Errorf("error in getting pod list for namespace %s and list options %+v: %s", namespace, options, err) + } + allPodsLabeled := false + if allPodsLabeled, err = deploymentutil.LabelPodsWithHash(&podList, updatedRS, dc.client, namespace, hash); err != nil { + return nil, fmt.Errorf("error in adding template hash label %s to pods %+v: %s", hash, podList, err) + } + // If not all pods are labeled but didn't return error in step 2, we've hit at least one pod not found error. + // Return here and retry in the next sync loop. + if !allPodsLabeled { + return updatedRS, nil + } + + // We need to wait for the replicaset controller to observe the pods being + // labeled with pod template hash. Because previously we've called + // WaitForReplicaSetUpdated, the replicaset controller should have dropped + // FullyLabeledReplicas to 0 already, we only need to wait it to increase + // back to the number of replicas in the spec. + if err = deploymentutil.WaitForPodsHashPopulated(dc.client, updatedRS.Generation, namespace, updatedRS.Name); err != nil { + return nil, fmt.Errorf("%s %s/%s: error waiting for replicaset controller to observe pods being labeled with template hash: %v", updatedRS.Kind, updatedRS.Namespace, updatedRS.Name, err) + } + + // 3. Update rs label and selector to include the new hash label + // Copy the old selector, so that we can scrub out any orphaned pods + if updatedRS, rsUpdated, err = rsutil.UpdateRSWithRetries(dc.client.Extensions().ReplicaSets(namespace), updatedRS, + func(updated *extensions.ReplicaSet) error { + // Precondition: the RS doesn't contain the new hash in its label or selector. + if updated.Labels[extensions.DefaultDeploymentUniqueLabelKey] == hash && updated.Spec.Selector.MatchLabels[extensions.DefaultDeploymentUniqueLabelKey] == hash { + return utilerrors.ErrPreconditionViolated + } + updated.Labels = labelsutil.AddLabel(updated.Labels, extensions.DefaultDeploymentUniqueLabelKey, hash) + updated.Spec.Selector = labelsutil.AddLabelToSelector(updated.Spec.Selector, extensions.DefaultDeploymentUniqueLabelKey, hash) + return nil + }); err != nil { + return nil, fmt.Errorf("error updating %s %s/%s label and selector with template hash: %v", updatedRS.Kind, updatedRS.Namespace, updatedRS.Name, err) + } + if rsUpdated { + glog.V(4).Infof("Updated %s %s/%s's selector and label with hash %s.", rs.Kind, rs.Namespace, rs.Name, hash) + } + // If the RS isn't actually updated in step 3, that's okay, we'll retry in the next sync loop since its selector isn't updated yet. + + // TODO: look for orphaned pods and label them in the background somewhere else periodically + + return updatedRS, nil +} + +// setNewReplicaSetAnnotations sets new replica set's annotations appropriately by updating its revision and +// copying required deployment annotations to it; it returns true if replica set's annotation is changed. +func setNewReplicaSetAnnotations(deployment *extensions.Deployment, newRS *extensions.ReplicaSet, newRevision string) bool { + // First, copy deployment's annotations (except for apply and revision annotations) + annotationChanged := copyDeploymentAnnotationsToReplicaSet(deployment, newRS) + // Then, update replica set's revision annotation + if newRS.Annotations == nil { + newRS.Annotations = make(map[string]string) + } + // The newRS's revision should be the greatest among all RSes. Usually, its revision number is newRevision (the max revision number + // of all old RSes + 1). However, it's possible that some of the old RSes are deleted after the newRS revision being updated, and + // newRevision becomes smaller than newRS's revision. We should only update newRS revision when it's smaller than newRevision. + if newRS.Annotations[deploymentutil.RevisionAnnotation] < newRevision { + newRS.Annotations[deploymentutil.RevisionAnnotation] = newRevision + annotationChanged = true + glog.V(4).Infof("updating replica set %q's revision to %s - %+v\n", newRS.Name, newRevision, newRS) + } + return annotationChanged +} + +// skipCopyAnnotation returns true if we should skip copying the annotation with the given annotation key +// TODO: How to decide which annotations should / should not be copied? +// See https://github.com/kubernetes/kubernetes/pull/20035#issuecomment-179558615 +func skipCopyAnnotation(key string) bool { + // Skip apply annotations and revision annotations. + return key == kubectl.LastAppliedConfigAnnotation || key == deploymentutil.RevisionAnnotation +} + +func getSkippedAnnotations(annotations map[string]string) map[string]string { + skippedAnnotations := make(map[string]string) + for k, v := range annotations { + if skipCopyAnnotation(k) { + skippedAnnotations[k] = v + } + } + return skippedAnnotations +} + +// copyDeploymentAnnotationsToReplicaSet copies deployment's annotations to replica set's annotations, +// and returns true if replica set's annotation is changed. +// Note that apply and revision annotations are not copied. +func copyDeploymentAnnotationsToReplicaSet(deployment *extensions.Deployment, rs *extensions.ReplicaSet) bool { + rsAnnotationsChanged := false + if rs.Annotations == nil { + rs.Annotations = make(map[string]string) + } + for k, v := range deployment.Annotations { + // newRS revision is updated automatically in getNewReplicaSet, and the deployment's revision number is then updated + // by copying its newRS revision number. We should not copy deployment's revision to its newRS, since the update of + // deployment revision number may fail (revision becomes stale) and the revision number in newRS is more reliable. + if skipCopyAnnotation(k) || rs.Annotations[k] == v { + continue + } + rs.Annotations[k] = v + rsAnnotationsChanged = true + } + return rsAnnotationsChanged +} + +// setDeploymentAnnotationsTo sets deployment's annotations as given RS's annotations. +// This action should be done if and only if the deployment is rolling back to this rs. +// Note that apply and revision annotations are not changed. +func setDeploymentAnnotationsTo(deployment *extensions.Deployment, rollbackToRS *extensions.ReplicaSet) { + deployment.Annotations = getSkippedAnnotations(deployment.Annotations) + for k, v := range rollbackToRS.Annotations { + if !skipCopyAnnotation(k) { + deployment.Annotations[k] = v + } + } +} + +func (dc *DeploymentController) updateDeploymentRevision(deployment *extensions.Deployment, revision string) error { + if deployment.Annotations == nil { + deployment.Annotations = make(map[string]string) + } + if deployment.Annotations[deploymentutil.RevisionAnnotation] != revision { + deployment.Annotations[deploymentutil.RevisionAnnotation] = revision + _, err := dc.updateDeployment(deployment) + return err + } + return nil +} + +func (dc *DeploymentController) reconcileNewReplicaSet(allRSs []*extensions.ReplicaSet, newRS *extensions.ReplicaSet, deployment *extensions.Deployment) (bool, error) { + if newRS.Spec.Replicas == deployment.Spec.Replicas { + // Scaling not required. + return false, nil + } + if newRS.Spec.Replicas > deployment.Spec.Replicas { + // Scale down. + scaled, _, err := dc.scaleReplicaSetAndRecordEvent(newRS, deployment.Spec.Replicas, deployment) + return scaled, err + } + newReplicasCount, err := deploymentutil.NewRSNewReplicas(deployment, allRSs, newRS) + if err != nil { + return false, err + } + scaled, _, err := dc.scaleReplicaSetAndRecordEvent(newRS, newReplicasCount, deployment) + return scaled, err +} + +func (dc *DeploymentController) reconcileOldReplicaSets(allRSs []*extensions.ReplicaSet, oldRSs []*extensions.ReplicaSet, newRS *extensions.ReplicaSet, deployment *extensions.Deployment) (bool, error) { + oldPodsCount := deploymentutil.GetReplicaCountForReplicaSets(oldRSs) + if oldPodsCount == 0 { + // Can't scale down further + return false, nil + } + + minReadySeconds := deployment.Spec.MinReadySeconds + allPodsCount := deploymentutil.GetReplicaCountForReplicaSets(allRSs) + newRSAvailablePodCount, err := deploymentutil.GetAvailablePodsForReplicaSets(dc.client, []*extensions.ReplicaSet{newRS}, minReadySeconds) + if err != nil { + return false, fmt.Errorf("could not find available pods: %v", err) + } + + _, maxUnavailable, err := deploymentutil.ResolveFenceposts(&deployment.Spec.Strategy.RollingUpdate.MaxSurge, &deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, deployment.Spec.Replicas) + if err != nil { + return false, err + } + + // Check if we can scale down. We can scale down in the following 2 cases: + // * Some old replica sets have unhealthy replicas, we could safely scale down those unhealthy replicas since that won't further + // increase unavailability. + // * New replica set has scaled up and it's replicas becomes ready, then we can scale down old replica sets in a further step. + // + // maxScaledDown := allPodsCount - minAvailable - newReplicaSetPodsUnavailable + // take into account not only maxUnavailable and any surge pods that have been created, but also unavailable pods from + // the newRS, so that the unavailable pods from the newRS would not make us scale down old replica sets in a further + // step(that will increase unavailability). + // + // Concrete example: + // + // * 10 replicas + // * 2 maxUnavailable (absolute number, not percent) + // * 3 maxSurge (absolute number, not percent) + // + // case 1: + // * Deployment is updated, newRS is created with 3 replicas, oldRS is scaled down to 8, and newRS is scaled up to 5. + // * The new replica set pods crashloop and never become available. + // * allPodsCount is 13. minAvailable is 8. newRSPodsUnavailable is 5. + // * A node fails and causes one of the oldRS pods to become unavailable. However, 13 - 8 - 5 = 0, so the oldRS won't be scaled down. + // * The user notices the crashloop and does kubectl rollout undo to rollback. + // * newRSPodsUnavailable is 1, since we rolled back to the good replica set, so maxScaledDown = 13 - 8 - 1 = 4. 4 of the crashlooping pods will be scaled down. + // * The total number of pods will then be 9 and the newRS can be scaled up to 10. + // + // case 2: + // Same example, but pushing a new pod template instead of rolling back (aka "roll over"): + // * The new replica set created must start with 0 replicas because allPodsCount is already at 13. + // * However, newRSPodsUnavailable would also be 0, so the 2 old replica sets could be scaled down by 5 (13 - 8 - 0), which would then + // allow the new replica set to be scaled up by 5. + minAvailable := deployment.Spec.Replicas - maxUnavailable + newRSUnavailablePodCount := newRS.Spec.Replicas - newRSAvailablePodCount + maxScaledDown := allPodsCount - minAvailable - newRSUnavailablePodCount + if maxScaledDown <= 0 { + return false, nil + } + + // Clean up unhealthy replicas first, otherwise unhealthy replicas will block deployment + // and cause timeout. See https://github.com/kubernetes/kubernetes/issues/16737 + oldRSs, cleanupCount, err := dc.cleanupUnhealthyReplicas(oldRSs, deployment, maxScaledDown) + if err != nil { + return false, nil + } + glog.V(4).Infof("Cleaned up unhealthy replicas from old RSes by %d", cleanupCount) + + // Scale down old replica sets, need check maxUnavailable to ensure we can scale down + allRSs = append(oldRSs, newRS) + scaledDownCount, err := dc.scaleDownOldReplicaSetsForRollingUpdate(allRSs, oldRSs, deployment) + if err != nil { + return false, nil + } + glog.V(4).Infof("Scaled down old RSes by %d", scaledDownCount) + + totalScaledDown := cleanupCount + scaledDownCount + return totalScaledDown > 0, nil +} + +// cleanupUnhealthyReplicas will scale down old replica sets with unhealthy replicas, so that all unhealthy replicas will be deleted. +func (dc *DeploymentController) cleanupUnhealthyReplicas(oldRSs []*extensions.ReplicaSet, deployment *extensions.Deployment, maxCleanupCount int) ([]*extensions.ReplicaSet, int, error) { + sort.Sort(controller.ReplicaSetsByCreationTimestamp(oldRSs)) + // Safely scale down all old replica sets with unhealthy replicas. Replica set will sort the pods in the order + // such that not-ready < ready, unscheduled < scheduled, and pending < running. This ensures that unhealthy replicas will + // been deleted first and won't increase unavailability. + totalScaledDown := 0 + for i, targetRS := range oldRSs { + if totalScaledDown >= maxCleanupCount { + break + } + if targetRS.Spec.Replicas == 0 { + // cannot scale down this replica set. + continue + } + readyPodCount, err := deploymentutil.GetAvailablePodsForReplicaSets(dc.client, []*extensions.ReplicaSet{targetRS}, 0) + if err != nil { + return nil, totalScaledDown, fmt.Errorf("could not find available pods: %v", err) + } + if targetRS.Spec.Replicas == readyPodCount { + // no unhealthy replicas found, no scaling required. + continue + } + + scaledDownCount := integer.IntMin(maxCleanupCount-totalScaledDown, targetRS.Spec.Replicas-readyPodCount) + newReplicasCount := targetRS.Spec.Replicas - scaledDownCount + if newReplicasCount > targetRS.Spec.Replicas { + return nil, 0, fmt.Errorf("when cleaning up unhealthy replicas, got invalid request to scale down %s/%s %d -> %d", targetRS.Namespace, targetRS.Name, targetRS.Spec.Replicas, newReplicasCount) + } + _, updatedOldRS, err := dc.scaleReplicaSetAndRecordEvent(targetRS, newReplicasCount, deployment) + if err != nil { + return nil, totalScaledDown, err + } + totalScaledDown += scaledDownCount + oldRSs[i] = updatedOldRS + } + return oldRSs, totalScaledDown, nil +} + +// scaleDownOldReplicaSetsForRollingUpdate scales down old replica sets when deployment strategy is "RollingUpdate". +// Need check maxUnavailable to ensure availability +func (dc *DeploymentController) scaleDownOldReplicaSetsForRollingUpdate(allRSs []*extensions.ReplicaSet, oldRSs []*extensions.ReplicaSet, deployment *extensions.Deployment) (int, error) { + _, maxUnavailable, err := deploymentutil.ResolveFenceposts(&deployment.Spec.Strategy.RollingUpdate.MaxSurge, &deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, deployment.Spec.Replicas) + if err != nil { + return 0, err + } + + // Check if we can scale down. + minAvailable := deployment.Spec.Replicas - maxUnavailable + minReadySeconds := deployment.Spec.MinReadySeconds + // Find the number of ready pods. + readyPodCount, err := deploymentutil.GetAvailablePodsForReplicaSets(dc.client, allRSs, minReadySeconds) + if err != nil { + return 0, fmt.Errorf("could not find available pods: %v", err) + } + if readyPodCount <= minAvailable { + // Cannot scale down. + return 0, nil + } + + sort.Sort(controller.ReplicaSetsByCreationTimestamp(oldRSs)) + + totalScaledDown := 0 + totalScaleDownCount := readyPodCount - minAvailable + for _, targetRS := range oldRSs { + if totalScaledDown >= totalScaleDownCount { + // No further scaling required. + break + } + if targetRS.Spec.Replicas == 0 { + // cannot scale down this ReplicaSet. + continue + } + // Scale down. + scaleDownCount := integer.IntMin(targetRS.Spec.Replicas, totalScaleDownCount-totalScaledDown) + newReplicasCount := targetRS.Spec.Replicas - scaleDownCount + if newReplicasCount > targetRS.Spec.Replicas { + return 0, fmt.Errorf("when scaling down old RS, got invalid request to scale down %s/%s %d -> %d", targetRS.Namespace, targetRS.Name, targetRS.Spec.Replicas, newReplicasCount) + } + _, _, err = dc.scaleReplicaSetAndRecordEvent(targetRS, newReplicasCount, deployment) + if err != nil { + return totalScaledDown, err + } + + totalScaledDown += scaleDownCount + } + + return totalScaledDown, nil +} + +// scaleDownOldReplicaSetsForRecreate scales down old replica sets when deployment strategy is "Recreate" +func (dc *DeploymentController) scaleDownOldReplicaSetsForRecreate(oldRSs []*extensions.ReplicaSet, deployment *extensions.Deployment) (bool, error) { + scaled := false + for _, rs := range oldRSs { + // Scaling not required. + if rs.Spec.Replicas == 0 { + continue + } + scaledRS, _, err := dc.scaleReplicaSetAndRecordEvent(rs, 0, deployment) + if err != nil { + return false, err + } + if scaledRS { + scaled = true + } + } + return scaled, nil +} + +// scaleUpNewReplicaSetForRecreate scales up new replica set when deployment strategy is "Recreate" +func (dc *DeploymentController) scaleUpNewReplicaSetForRecreate(newRS *extensions.ReplicaSet, deployment *extensions.Deployment) (bool, error) { + scaled, _, err := dc.scaleReplicaSetAndRecordEvent(newRS, deployment.Spec.Replicas, deployment) + return scaled, err +} + +func (dc *DeploymentController) cleanupOldReplicaSets(oldRSs []*extensions.ReplicaSet, deployment *extensions.Deployment) error { + diff := len(oldRSs) - *deployment.Spec.RevisionHistoryLimit + if diff <= 0 { + return nil + } + + sort.Sort(controller.ReplicaSetsByCreationTimestamp(oldRSs)) + + var errList []error + // TODO: This should be parallelized. + for i := 0; i < diff; i++ { + rs := oldRSs[i] + // Avoid delete replica set with non-zero replica counts + if rs.Status.Replicas != 0 || rs.Spec.Replicas != 0 || rs.Generation > rs.Status.ObservedGeneration { + continue + } + if err := dc.client.Extensions().ReplicaSets(rs.Namespace).Delete(rs.Name, nil); err != nil && !errors.IsNotFound(err) { + glog.V(2).Infof("Failed deleting old replica set %v for deployment %v: %v", rs.Name, deployment.Name, err) + errList = append(errList, err) + } + } + + return utilerrors.NewAggregate(errList) +} + +func (dc *DeploymentController) updateDeploymentStatus(allRSs []*extensions.ReplicaSet, newRS *extensions.ReplicaSet, deployment *extensions.Deployment) error { + totalActualReplicas, updatedReplicas, availableReplicas, unavailableReplicas, err := dc.calculateStatus(allRSs, newRS, deployment) + if err != nil { + return err + } + newDeployment := *deployment + // TODO: Reconcile this with API definition. API definition talks about ready pods, while this just computes created pods. + newDeployment.Status = extensions.DeploymentStatus{ + // TODO: Ensure that if we start retrying status updates, we won't pick up a new Generation value. + ObservedGeneration: deployment.Generation, + Replicas: totalActualReplicas, + UpdatedReplicas: updatedReplicas, + AvailableReplicas: availableReplicas, + UnavailableReplicas: unavailableReplicas, + } + _, err = dc.client.Extensions().Deployments(deployment.ObjectMeta.Namespace).UpdateStatus(&newDeployment) + return err +} + +func (dc *DeploymentController) calculateStatus(allRSs []*extensions.ReplicaSet, newRS *extensions.ReplicaSet, deployment *extensions.Deployment) (totalActualReplicas, updatedReplicas, availableReplicas, unavailableReplicas int, err error) { + totalActualReplicas = deploymentutil.GetActualReplicaCountForReplicaSets(allRSs) + updatedReplicas = deploymentutil.GetActualReplicaCountForReplicaSets([]*extensions.ReplicaSet{newRS}) + minReadySeconds := deployment.Spec.MinReadySeconds + availableReplicas, err = deploymentutil.GetAvailablePodsForReplicaSets(dc.client, allRSs, minReadySeconds) + if err != nil { + err = fmt.Errorf("failed to count available pods: %v", err) + return + } + totalReplicas := deploymentutil.GetReplicaCountForReplicaSets(allRSs) + unavailableReplicas = totalReplicas - availableReplicas + return +} + +func (dc *DeploymentController) scaleReplicaSetAndRecordEvent(rs *extensions.ReplicaSet, newScale int, deployment *extensions.Deployment) (bool, *extensions.ReplicaSet, error) { + // No need to scale + if rs.Spec.Replicas == newScale { + return false, rs, nil + } + var scalingOperation string + if rs.Spec.Replicas < newScale { + scalingOperation = "up" + } else { + scalingOperation = "down" + } + newRS, err := dc.scaleReplicaSet(rs, newScale) + if err == nil { + dc.eventRecorder.Eventf(deployment, api.EventTypeNormal, "ScalingReplicaSet", "Scaled %s replica set %s to %d", scalingOperation, rs.Name, newScale) + } else { + dc.enqueueDeployment(deployment) + } + return true, newRS, err +} + +func (dc *DeploymentController) scaleReplicaSet(rs *extensions.ReplicaSet, newScale int) (*extensions.ReplicaSet, error) { + // TODO: Using client for now, update to use store when it is ready. + // NOTE: This mutates the ReplicaSet passed in. Not sure if that's a good idea. + rs.Spec.Replicas = newScale + return dc.client.Extensions().ReplicaSets(rs.ObjectMeta.Namespace).Update(rs) +} + +func (dc *DeploymentController) updateDeployment(deployment *extensions.Deployment) (*extensions.Deployment, error) { + // TODO: Using client for now, update to use store when it is ready. + return dc.client.Extensions().Deployments(deployment.ObjectMeta.Namespace).Update(deployment) +} + +func (dc *DeploymentController) rollbackToTemplate(deployment *extensions.Deployment, rs *extensions.ReplicaSet) (d *extensions.Deployment, performedRollback bool, err error) { + if !reflect.DeepEqual(deploymentutil.GetNewReplicaSetTemplate(deployment), rs.Spec.Template) { + glog.Infof("Rolling back deployment %s to template spec %+v", deployment.Name, rs.Spec.Template.Spec) + deploymentutil.SetFromReplicaSetTemplate(deployment, rs.Spec.Template) + // set RS (the old RS we'll rolling back to) annotations back to the deployment; + // otherwise, the deployment's current annotations (should be the same as current new RS) will be copied to the RS after the rollback. + // + // For example, + // A Deployment has old RS1 with annotation {change-cause:create}, and new RS2 {change-cause:edit}. + // Note that both annotations are copied from Deployment, and the Deployment should be annotated {change-cause:edit} as well. + // Now, rollback Deployment to RS1, we should update Deployment's pod-template and also copy annotation from RS1. + // Deployment is now annotated {change-cause:create}, and we have new RS1 {change-cause:create}, old RS2 {change-cause:edit}. + // + // If we don't copy the annotations back from RS to deployment on rollback, the Deployment will stay as {change-cause:edit}, + // and new RS1 becomes {change-cause:edit} (copied from deployment after rollback), old RS2 {change-cause:edit}, which is not correct. + setDeploymentAnnotationsTo(deployment, rs) + performedRollback = true + } else { + glog.V(4).Infof("Rolling back to a revision that contains the same template as current deployment %s, skipping rollback...", deployment.Name) + dc.emitRollbackWarningEvent(deployment, deploymentutil.RollbackTemplateUnchanged, fmt.Sprintf("The rollback revision contains the same template as current deployment %q", deployment.Name)) + } + d, err = dc.updateDeploymentAndClearRollbackTo(deployment) + return +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/deployment/deployment_controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/deployment/deployment_controller_test.go new file mode 100644 index 000000000..dcab9a73b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/deployment/deployment_controller_test.go @@ -0,0 +1,816 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 deployment + +import ( + "fmt" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + exp "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/client/unversioned/testclient" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/intstr" +) + +func rs(name string, replicas int, selector map[string]string) *exp.ReplicaSet { + return &exp.ReplicaSet{ + ObjectMeta: api.ObjectMeta{ + Name: name, + }, + Spec: exp.ReplicaSetSpec{ + Replicas: replicas, + Selector: &unversioned.LabelSelector{MatchLabels: selector}, + Template: api.PodTemplateSpec{}, + }, + } +} + +func newRSWithStatus(name string, specReplicas, statusReplicas int, selector map[string]string) *exp.ReplicaSet { + rs := rs(name, specReplicas, selector) + rs.Status = exp.ReplicaSetStatus{ + Replicas: statusReplicas, + } + return rs +} + +func deployment(name string, replicas int, maxSurge, maxUnavailable intstr.IntOrString) exp.Deployment { + return exp.Deployment{ + ObjectMeta: api.ObjectMeta{ + Name: name, + }, + Spec: exp.DeploymentSpec{ + Replicas: replicas, + Strategy: exp.DeploymentStrategy{ + Type: exp.RollingUpdateDeploymentStrategyType, + RollingUpdate: &exp.RollingUpdateDeployment{ + MaxSurge: maxSurge, + MaxUnavailable: maxUnavailable, + }, + }, + }, + } +} + +var alwaysReady = func() bool { return true } + +func newDeployment(replicas int, revisionHistoryLimit *int) *exp.Deployment { + d := exp.Deployment{ + TypeMeta: unversioned.TypeMeta{APIVersion: testapi.Default.GroupVersion().String()}, + ObjectMeta: api.ObjectMeta{ + UID: util.NewUUID(), + Name: "foobar", + Namespace: api.NamespaceDefault, + ResourceVersion: "18", + }, + Spec: exp.DeploymentSpec{ + Strategy: exp.DeploymentStrategy{ + Type: exp.RollingUpdateDeploymentStrategyType, + RollingUpdate: &exp.RollingUpdateDeployment{}, + }, + Replicas: replicas, + Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{ + "name": "foo", + "type": "production", + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Image: "foo/bar", + }, + }, + }, + }, + RevisionHistoryLimit: revisionHistoryLimit, + }, + } + return &d +} + +func newReplicaSet(d *exp.Deployment, name string, replicas int) *exp.ReplicaSet { + return &exp.ReplicaSet{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: api.NamespaceDefault, + }, + Spec: exp.ReplicaSetSpec{ + Replicas: replicas, + Template: d.Spec.Template, + }, + } + +} + +func newListOptions() api.ListOptions { + return api.ListOptions{} +} + +func TestDeploymentController_reconcileNewReplicaSet(t *testing.T) { + tests := []struct { + deploymentReplicas int + maxSurge intstr.IntOrString + oldReplicas int + newReplicas int + scaleExpected bool + expectedNewReplicas int + }{ + { + // Should not scale up. + deploymentReplicas: 10, + maxSurge: intstr.FromInt(0), + oldReplicas: 10, + newReplicas: 0, + scaleExpected: false, + }, + { + deploymentReplicas: 10, + maxSurge: intstr.FromInt(2), + oldReplicas: 10, + newReplicas: 0, + scaleExpected: true, + expectedNewReplicas: 2, + }, + { + deploymentReplicas: 10, + maxSurge: intstr.FromInt(2), + oldReplicas: 5, + newReplicas: 0, + scaleExpected: true, + expectedNewReplicas: 7, + }, + { + deploymentReplicas: 10, + maxSurge: intstr.FromInt(2), + oldReplicas: 10, + newReplicas: 2, + scaleExpected: false, + }, + { + // Should scale down. + deploymentReplicas: 10, + maxSurge: intstr.FromInt(2), + oldReplicas: 2, + newReplicas: 11, + scaleExpected: true, + expectedNewReplicas: 10, + }, + } + + for i, test := range tests { + t.Logf("executing scenario %d", i) + newRS := rs("foo-v2", test.newReplicas, nil) + oldRS := rs("foo-v2", test.oldReplicas, nil) + allRSs := []*exp.ReplicaSet{newRS, oldRS} + deployment := deployment("foo", test.deploymentReplicas, test.maxSurge, intstr.FromInt(0)) + fake := fake.Clientset{} + controller := &DeploymentController{ + client: &fake, + eventRecorder: &record.FakeRecorder{}, + } + scaled, err := controller.reconcileNewReplicaSet(allRSs, newRS, &deployment) + if err != nil { + t.Errorf("unexpected error: %v", err) + continue + } + if !test.scaleExpected { + if scaled || len(fake.Actions()) > 0 { + t.Errorf("unexpected scaling: %v", fake.Actions()) + } + continue + } + if test.scaleExpected && !scaled { + t.Errorf("expected scaling to occur") + continue + } + if len(fake.Actions()) != 1 { + t.Errorf("expected 1 action during scale, got: %v", fake.Actions()) + continue + } + updated := fake.Actions()[0].(testclient.UpdateAction).GetObject().(*exp.ReplicaSet) + if e, a := test.expectedNewReplicas, updated.Spec.Replicas; e != a { + t.Errorf("expected update to %d replicas, got %d", e, a) + } + } +} + +func TestDeploymentController_reconcileOldReplicaSets(t *testing.T) { + tests := []struct { + deploymentReplicas int + maxUnavailable intstr.IntOrString + oldReplicas int + newReplicas int + readyPodsFromOldRS int + readyPodsFromNewRS int + scaleExpected bool + expectedOldReplicas int + }{ + { + deploymentReplicas: 10, + maxUnavailable: intstr.FromInt(0), + oldReplicas: 10, + newReplicas: 0, + readyPodsFromOldRS: 10, + readyPodsFromNewRS: 0, + scaleExpected: true, + expectedOldReplicas: 9, + }, + { + deploymentReplicas: 10, + maxUnavailable: intstr.FromInt(2), + oldReplicas: 10, + newReplicas: 0, + readyPodsFromOldRS: 10, + readyPodsFromNewRS: 0, + scaleExpected: true, + expectedOldReplicas: 8, + }, + { // expect unhealthy replicas from old replica sets been cleaned up + deploymentReplicas: 10, + maxUnavailable: intstr.FromInt(2), + oldReplicas: 10, + newReplicas: 0, + readyPodsFromOldRS: 8, + readyPodsFromNewRS: 0, + scaleExpected: true, + expectedOldReplicas: 8, + }, + { // expect 1 unhealthy replica from old replica sets been cleaned up, and 1 ready pod been scaled down + deploymentReplicas: 10, + maxUnavailable: intstr.FromInt(2), + oldReplicas: 10, + newReplicas: 0, + readyPodsFromOldRS: 9, + readyPodsFromNewRS: 0, + scaleExpected: true, + expectedOldReplicas: 8, + }, + { // the unavailable pods from the newRS would not make us scale down old RSs in a further step + deploymentReplicas: 10, + maxUnavailable: intstr.FromInt(2), + oldReplicas: 8, + newReplicas: 2, + readyPodsFromOldRS: 8, + readyPodsFromNewRS: 0, + scaleExpected: false, + }, + } + for i, test := range tests { + t.Logf("executing scenario %d", i) + + newSelector := map[string]string{"foo": "new"} + oldSelector := map[string]string{"foo": "old"} + newRS := rs("foo-new", test.newReplicas, newSelector) + oldRS := rs("foo-old", test.oldReplicas, oldSelector) + oldRSs := []*exp.ReplicaSet{oldRS} + allRSs := []*exp.ReplicaSet{oldRS, newRS} + + deployment := deployment("foo", test.deploymentReplicas, intstr.FromInt(0), test.maxUnavailable) + fakeClientset := fake.Clientset{} + fakeClientset.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { + switch action.(type) { + case core.ListAction: + podList := &api.PodList{} + for podIndex := 0; podIndex < test.readyPodsFromOldRS; podIndex++ { + podList.Items = append(podList.Items, api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: fmt.Sprintf("%s-oldReadyPod-%d", oldRS.Name, podIndex), + Labels: oldSelector, + }, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Type: api.PodReady, + Status: api.ConditionTrue, + }, + }, + }, + }) + } + for podIndex := 0; podIndex < test.oldReplicas-test.readyPodsFromOldRS; podIndex++ { + podList.Items = append(podList.Items, api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: fmt.Sprintf("%s-oldUnhealthyPod-%d", oldRS.Name, podIndex), + Labels: oldSelector, + }, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Type: api.PodReady, + Status: api.ConditionFalse, + }, + }, + }, + }) + } + for podIndex := 0; podIndex < test.readyPodsFromNewRS; podIndex++ { + podList.Items = append(podList.Items, api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: fmt.Sprintf("%s-newReadyPod-%d", oldRS.Name, podIndex), + Labels: newSelector, + }, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Type: api.PodReady, + Status: api.ConditionTrue, + }, + }, + }, + }) + } + for podIndex := 0; podIndex < test.oldReplicas-test.readyPodsFromOldRS; podIndex++ { + podList.Items = append(podList.Items, api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: fmt.Sprintf("%s-newUnhealthyPod-%d", oldRS.Name, podIndex), + Labels: newSelector, + }, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Type: api.PodReady, + Status: api.ConditionFalse, + }, + }, + }, + }) + } + return true, podList, nil + } + return false, nil, nil + }) + controller := &DeploymentController{ + client: &fakeClientset, + eventRecorder: &record.FakeRecorder{}, + } + + scaled, err := controller.reconcileOldReplicaSets(allRSs, oldRSs, newRS, &deployment) + if err != nil { + t.Errorf("unexpected error: %v", err) + continue + } + if !test.scaleExpected && scaled { + t.Errorf("unexpected scaling: %v", fakeClientset.Actions()) + } + if test.scaleExpected && !scaled { + t.Errorf("expected scaling to occur") + continue + } + continue + } +} + +func TestDeploymentController_cleanupUnhealthyReplicas(t *testing.T) { + tests := []struct { + oldReplicas int + readyPods int + unHealthyPods int + maxCleanupCount int + cleanupCountExpected int + }{ + { + oldReplicas: 10, + readyPods: 8, + unHealthyPods: 2, + maxCleanupCount: 1, + cleanupCountExpected: 1, + }, + { + oldReplicas: 10, + readyPods: 8, + unHealthyPods: 2, + maxCleanupCount: 3, + cleanupCountExpected: 2, + }, + { + oldReplicas: 10, + readyPods: 8, + unHealthyPods: 2, + maxCleanupCount: 0, + cleanupCountExpected: 0, + }, + { + oldReplicas: 10, + readyPods: 10, + unHealthyPods: 0, + maxCleanupCount: 3, + cleanupCountExpected: 0, + }, + } + + for i, test := range tests { + t.Logf("executing scenario %d", i) + oldRS := rs("foo-v2", test.oldReplicas, nil) + oldRSs := []*exp.ReplicaSet{oldRS} + deployment := deployment("foo", 10, intstr.FromInt(2), intstr.FromInt(2)) + fakeClientset := fake.Clientset{} + fakeClientset.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { + switch action.(type) { + case core.ListAction: + podList := &api.PodList{} + for podIndex := 0; podIndex < test.readyPods; podIndex++ { + podList.Items = append(podList.Items, api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: fmt.Sprintf("%s-readyPod-%d", oldRS.Name, podIndex), + }, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Type: api.PodReady, + Status: api.ConditionTrue, + }, + }, + }, + }) + } + for podIndex := 0; podIndex < test.unHealthyPods; podIndex++ { + podList.Items = append(podList.Items, api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: fmt.Sprintf("%s-unHealthyPod-%d", oldRS.Name, podIndex), + }, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Type: api.PodReady, + Status: api.ConditionFalse, + }, + }, + }, + }) + } + return true, podList, nil + } + return false, nil, nil + }) + + controller := &DeploymentController{ + client: &fakeClientset, + eventRecorder: &record.FakeRecorder{}, + } + _, cleanupCount, err := controller.cleanupUnhealthyReplicas(oldRSs, &deployment, test.maxCleanupCount) + if err != nil { + t.Errorf("unexpected error: %v", err) + continue + } + if cleanupCount != test.cleanupCountExpected { + t.Errorf("expected %v unhealthy replicas been cleaned up, got %v", test.cleanupCountExpected, cleanupCount) + continue + } + } +} + +func TestDeploymentController_scaleDownOldReplicaSetsForRollingUpdate(t *testing.T) { + tests := []struct { + deploymentReplicas int + maxUnavailable intstr.IntOrString + readyPods int + oldReplicas int + scaleExpected bool + expectedOldReplicas int + }{ + { + deploymentReplicas: 10, + maxUnavailable: intstr.FromInt(0), + readyPods: 10, + oldReplicas: 10, + scaleExpected: true, + expectedOldReplicas: 9, + }, + { + deploymentReplicas: 10, + maxUnavailable: intstr.FromInt(2), + readyPods: 10, + oldReplicas: 10, + scaleExpected: true, + expectedOldReplicas: 8, + }, + { + deploymentReplicas: 10, + maxUnavailable: intstr.FromInt(2), + readyPods: 8, + oldReplicas: 10, + scaleExpected: false, + }, + { + deploymentReplicas: 10, + maxUnavailable: intstr.FromInt(2), + readyPods: 10, + oldReplicas: 0, + scaleExpected: false, + }, + { + deploymentReplicas: 10, + maxUnavailable: intstr.FromInt(2), + readyPods: 1, + oldReplicas: 10, + scaleExpected: false, + }, + } + + for i, test := range tests { + t.Logf("executing scenario %d", i) + oldRS := rs("foo-v2", test.oldReplicas, nil) + allRSs := []*exp.ReplicaSet{oldRS} + oldRSs := []*exp.ReplicaSet{oldRS} + deployment := deployment("foo", test.deploymentReplicas, intstr.FromInt(0), test.maxUnavailable) + fakeClientset := fake.Clientset{} + fakeClientset.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { + switch action.(type) { + case core.ListAction: + podList := &api.PodList{} + for podIndex := 0; podIndex < test.readyPods; podIndex++ { + podList.Items = append(podList.Items, api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: fmt.Sprintf("%s-pod-%d", oldRS.Name, podIndex), + Labels: map[string]string{"foo": "bar"}, + }, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Type: api.PodReady, + Status: api.ConditionTrue, + }, + }, + }, + }) + } + return true, podList, nil + } + return false, nil, nil + }) + controller := &DeploymentController{ + client: &fakeClientset, + eventRecorder: &record.FakeRecorder{}, + } + scaled, err := controller.scaleDownOldReplicaSetsForRollingUpdate(allRSs, oldRSs, &deployment) + if err != nil { + t.Errorf("unexpected error: %v", err) + continue + } + if !test.scaleExpected { + if scaled != 0 { + t.Errorf("unexpected scaling: %v", fakeClientset.Actions()) + } + continue + } + if test.scaleExpected && scaled == 0 { + t.Errorf("expected scaling to occur; actions: %v", fakeClientset.Actions()) + continue + } + // There are both list and update actions logged, so extract the update + // action for verification. + var updateAction testclient.UpdateAction + for _, action := range fakeClientset.Actions() { + switch a := action.(type) { + case testclient.UpdateAction: + if updateAction != nil { + t.Errorf("expected only 1 update action; had %v and found %v", updateAction, a) + } else { + updateAction = a + } + } + } + if updateAction == nil { + t.Errorf("expected an update action") + continue + } + updated := updateAction.GetObject().(*exp.ReplicaSet) + if e, a := test.expectedOldReplicas, updated.Spec.Replicas; e != a { + t.Errorf("expected update to %d replicas, got %d", e, a) + } + } +} + +func TestDeploymentController_cleanupOldReplicaSets(t *testing.T) { + selector := map[string]string{"foo": "bar"} + + tests := []struct { + oldRSs []*exp.ReplicaSet + revisionHistoryLimit int + expectedDeletions int + }{ + { + oldRSs: []*exp.ReplicaSet{ + newRSWithStatus("foo-1", 0, 0, selector), + newRSWithStatus("foo-2", 0, 0, selector), + newRSWithStatus("foo-3", 0, 0, selector), + }, + revisionHistoryLimit: 1, + expectedDeletions: 2, + }, + { + // Only delete the replica set with Spec.Replicas = Status.Replicas = 0. + oldRSs: []*exp.ReplicaSet{ + newRSWithStatus("foo-1", 0, 0, selector), + newRSWithStatus("foo-2", 0, 1, selector), + newRSWithStatus("foo-3", 1, 0, selector), + newRSWithStatus("foo-4", 1, 1, selector), + }, + revisionHistoryLimit: 0, + expectedDeletions: 1, + }, + + { + oldRSs: []*exp.ReplicaSet{ + newRSWithStatus("foo-1", 0, 0, selector), + newRSWithStatus("foo-2", 0, 0, selector), + }, + revisionHistoryLimit: 0, + expectedDeletions: 2, + }, + { + oldRSs: []*exp.ReplicaSet{ + newRSWithStatus("foo-1", 1, 1, selector), + newRSWithStatus("foo-2", 1, 1, selector), + }, + revisionHistoryLimit: 0, + expectedDeletions: 0, + }, + } + + for i, test := range tests { + fake := &fake.Clientset{} + controller := NewDeploymentController(fake, controller.NoResyncPeriodFunc) + + controller.eventRecorder = &record.FakeRecorder{} + controller.rsStoreSynced = alwaysReady + controller.podStoreSynced = alwaysReady + for _, rs := range test.oldRSs { + controller.rsStore.Add(rs) + } + + d := newDeployment(1, &tests[i].revisionHistoryLimit) + controller.cleanupOldReplicaSets(test.oldRSs, d) + + gotDeletions := 0 + for _, action := range fake.Actions() { + if "delete" == action.GetVerb() { + gotDeletions++ + } + } + if gotDeletions != test.expectedDeletions { + t.Errorf("expect %v old replica sets been deleted, but got %v", test.expectedDeletions, gotDeletions) + continue + } + } +} + +func getKey(d *exp.Deployment, t *testing.T) string { + if key, err := controller.KeyFunc(d); err != nil { + t.Errorf("Unexpected error getting key for deployment %v: %v", d.Name, err) + return "" + } else { + return key + } +} + +type fixture struct { + t *testing.T + + client *fake.Clientset + // Objects to put in the store. + dStore []*exp.Deployment + rsStore []*exp.ReplicaSet + podStore []*api.Pod + + // Actions expected to happen on the client. Objects from here are also + // preloaded into NewSimpleFake. + actions []core.Action + objects *api.List +} + +func (f *fixture) expectUpdateDeploymentAction(d *exp.Deployment) { + f.actions = append(f.actions, core.NewUpdateAction("deployments", d.Namespace, d)) + f.objects.Items = append(f.objects.Items, d) +} + +func (f *fixture) expectCreateRSAction(rs *exp.ReplicaSet) { + f.actions = append(f.actions, core.NewCreateAction("replicasets", rs.Namespace, rs)) + f.objects.Items = append(f.objects.Items, rs) +} + +func (f *fixture) expectUpdateRSAction(rs *exp.ReplicaSet) { + f.actions = append(f.actions, core.NewUpdateAction("replicasets", rs.Namespace, rs)) + f.objects.Items = append(f.objects.Items, rs) +} + +func (f *fixture) expectListPodAction(namespace string, opt api.ListOptions) { + f.actions = append(f.actions, testclient.NewListAction("pods", namespace, opt)) +} + +func newFixture(t *testing.T) *fixture { + f := &fixture{} + f.t = t + f.objects = &api.List{} + return f +} + +func (f *fixture) run(deploymentName string) { + f.client = fake.NewSimpleClientset(f.objects) + c := NewDeploymentController(f.client, controller.NoResyncPeriodFunc) + c.eventRecorder = &record.FakeRecorder{} + c.rsStoreSynced = alwaysReady + c.podStoreSynced = alwaysReady + for _, d := range f.dStore { + c.dStore.Store.Add(d) + } + for _, rs := range f.rsStore { + c.rsStore.Store.Add(rs) + } + for _, pod := range f.podStore { + c.podStore.Store.Add(pod) + } + + err := c.syncDeployment(deploymentName) + if err != nil { + f.t.Errorf("error syncing deployment: %v", err) + } + + actions := f.client.Actions() + for i, action := range actions { + if len(f.actions) < i+1 { + f.t.Errorf("%d unexpected actions: %+v", len(actions)-len(f.actions), actions[i:]) + break + } + + expectedAction := f.actions[i] + if !expectedAction.Matches(action.GetVerb(), action.GetResource()) { + f.t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expectedAction, action) + continue + } + } + + if len(f.actions) > len(actions) { + f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):]) + } +} + +func TestSyncDeploymentCreatesReplicaSet(t *testing.T) { + f := newFixture(t) + + d := newDeployment(1, nil) + f.dStore = append(f.dStore, d) + + // expect that one ReplicaSet with zero replicas is created + // then is updated to 1 replica + rs := newReplicaSet(d, "deploymentrs-4186632231", 0) + updatedRS := newReplicaSet(d, "deploymentrs-4186632231", 1) + opt := newListOptions() + + f.expectCreateRSAction(rs) + f.expectUpdateDeploymentAction(d) + f.expectUpdateRSAction(updatedRS) + f.expectListPodAction(rs.Namespace, opt) + f.expectUpdateDeploymentAction(d) + + f.run(getKey(d, t)) +} + +// issue: https://github.com/kubernetes/kubernetes/issues/23218 +func TestDeploymentController_dontSyncDeploymentsWithEmptyPodSelector(t *testing.T) { + fake := &fake.Clientset{} + controller := NewDeploymentController(fake, controller.NoResyncPeriodFunc) + + controller.eventRecorder = &record.FakeRecorder{} + controller.rsStoreSynced = alwaysReady + controller.podStoreSynced = alwaysReady + + d := newDeployment(1, nil) + empty := unversioned.LabelSelector{} + d.Spec.Selector = &empty + controller.dStore.Store.Add(d) + // We expect the deployment controller to not take action here since it's configuration + // is invalid, even though no replicasets exist that match it's selector. + controller.syncDeployment(fmt.Sprintf("%s/%s", d.ObjectMeta.Namespace, d.ObjectMeta.Name)) + if len(fake.Actions()) == 0 { + return + } + for _, action := range fake.Actions() { + t.Logf("unexpected action: %#v", action) + } + t.Errorf("expected deployment controller to not take action") +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/doc.go b/vendor/k8s.io/kubernetes/pkg/controller/doc.go new file mode 100644 index 000000000..1e310b466 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 controller contains code for controllers (like the replication +// controller). +package controller diff --git a/vendor/k8s.io/kubernetes/pkg/controller/endpoint/doc.go b/vendor/k8s.io/kubernetes/pkg/controller/endpoint/doc.go new file mode 100644 index 000000000..c51ec6518 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/endpoint/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 service provides EndpointController implementation +// to manage and sync service endpoints. +package endpoint diff --git a/vendor/k8s.io/kubernetes/pkg/controller/endpoint/endpoints_controller.go b/vendor/k8s.io/kubernetes/pkg/controller/endpoint/endpoints_controller.go new file mode 100644 index 000000000..e19c0ce91 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/endpoint/endpoints_controller.go @@ -0,0 +1,477 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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. +*/ + +// CAUTION: If you update code in this file, you may need to also update code +// in contrib/mesos/pkg/service/endpoints_controller.go +package endpoint + +import ( + "reflect" + "time" + + "encoding/json" + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/endpoints" + "k8s.io/kubernetes/pkg/api/errors" + podutil "k8s.io/kubernetes/pkg/api/pod" + utilpod "k8s.io/kubernetes/pkg/api/pod" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/util/workqueue" + "k8s.io/kubernetes/pkg/watch" +) + +const ( + // We'll attempt to recompute EVERY service's endpoints at least this + // often. Higher numbers = lower CPU/network load; lower numbers = + // shorter amount of time before a mistaken endpoint is corrected. + FullServiceResyncPeriod = 30 * time.Second + + // We must avoid syncing service until the pod store has synced. If it hasn't synced, to + // avoid a hot loop, we'll wait this long between checks. + PodStoreSyncedPollPeriod = 100 * time.Millisecond +) + +var ( + keyFunc = framework.DeletionHandlingMetaNamespaceKeyFunc +) + +// NewEndpointController returns a new *EndpointController. +func NewEndpointController(client *clientset.Clientset, resyncPeriod controller.ResyncPeriodFunc) *EndpointController { + e := &EndpointController{ + client: client, + queue: workqueue.New(), + } + + e.serviceStore.Store, e.serviceController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return e.client.Core().Services(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return e.client.Core().Services(api.NamespaceAll).Watch(options) + }, + }, + &api.Service{}, + // TODO: Can we have much longer period here? + FullServiceResyncPeriod, + framework.ResourceEventHandlerFuncs{ + AddFunc: e.enqueueService, + UpdateFunc: func(old, cur interface{}) { + e.enqueueService(cur) + }, + DeleteFunc: e.enqueueService, + }, + ) + + e.podStore.Store, e.podController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return e.client.Core().Pods(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return e.client.Core().Pods(api.NamespaceAll).Watch(options) + }, + }, + &api.Pod{}, + resyncPeriod(), + framework.ResourceEventHandlerFuncs{ + AddFunc: e.addPod, + UpdateFunc: e.updatePod, + DeleteFunc: e.deletePod, + }, + ) + e.podStoreSynced = e.podController.HasSynced + + return e +} + +// EndpointController manages selector-based service endpoints. +type EndpointController struct { + client *clientset.Clientset + + serviceStore cache.StoreToServiceLister + podStore cache.StoreToPodLister + + // Services that need to be updated. A channel is inappropriate here, + // because it allows services with lots of pods to be serviced much + // more often than services with few pods; it also would cause a + // service that's inserted multiple times to be processed more than + // necessary. + queue *workqueue.Type + + // Since we join two objects, we'll watch both of them with + // controllers. + serviceController *framework.Controller + podController *framework.Controller + // podStoreSynced returns true if the pod store has been synced at least once. + // Added as a member to the struct to allow injection for testing. + podStoreSynced func() bool +} + +// Runs e; will not return until stopCh is closed. workers determines how many +// endpoints will be handled in parallel. +func (e *EndpointController) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + go e.serviceController.Run(stopCh) + go e.podController.Run(stopCh) + for i := 0; i < workers; i++ { + go wait.Until(e.worker, time.Second, stopCh) + } + go func() { + defer utilruntime.HandleCrash() + time.Sleep(5 * time.Minute) // give time for our cache to fill + e.checkLeftoverEndpoints() + }() + <-stopCh + e.queue.ShutDown() +} + +func (e *EndpointController) getPodServiceMemberships(pod *api.Pod) (sets.String, error) { + set := sets.String{} + services, err := e.serviceStore.GetPodServices(pod) + if err != nil { + // don't log this error because this function makes pointless + // errors when no services match. + return set, nil + } + for i := range services { + key, err := keyFunc(&services[i]) + if err != nil { + return nil, err + } + set.Insert(key) + } + return set, nil +} + +// When a pod is added, figure out what services it will be a member of and +// enqueue them. obj must have *api.Pod type. +func (e *EndpointController) addPod(obj interface{}) { + pod := obj.(*api.Pod) + services, err := e.getPodServiceMemberships(pod) + if err != nil { + glog.Errorf("Unable to get pod %v/%v's service memberships: %v", pod.Namespace, pod.Name, err) + return + } + for key := range services { + e.queue.Add(key) + } +} + +// When a pod is updated, figure out what services it used to be a member of +// and what services it will be a member of, and enqueue the union of these. +// old and cur must be *api.Pod types. +func (e *EndpointController) updatePod(old, cur interface{}) { + if api.Semantic.DeepEqual(old, cur) { + return + } + newPod := old.(*api.Pod) + services, err := e.getPodServiceMemberships(newPod) + if err != nil { + glog.Errorf("Unable to get pod %v/%v's service memberships: %v", newPod.Namespace, newPod.Name, err) + return + } + + oldPod := cur.(*api.Pod) + // Only need to get the old services if the labels changed. + if !reflect.DeepEqual(newPod.Labels, oldPod.Labels) || + !hostNameAndDomainAnnotationsAreEqual(newPod.Annotations, oldPod.Annotations) { + oldServices, err := e.getPodServiceMemberships(oldPod) + if err != nil { + glog.Errorf("Unable to get pod %v/%v's service memberships: %v", oldPod.Namespace, oldPod.Name, err) + return + } + services = services.Union(oldServices) + } + for key := range services { + e.queue.Add(key) + } +} + +func hostNameAndDomainAnnotationsAreEqual(annotation1, annotation2 map[string]string) bool { + if annotation1 == nil { + annotation1 = map[string]string{} + } + if annotation2 == nil { + annotation2 = map[string]string{} + } + return annotation1[utilpod.PodHostnameAnnotation] == annotation2[utilpod.PodHostnameAnnotation] && + annotation1[utilpod.PodSubdomainAnnotation] == annotation2[utilpod.PodSubdomainAnnotation] +} + +// When a pod is deleted, enqueue the services the pod used to be a member of. +// obj could be an *api.Pod, or a DeletionFinalStateUnknown marker item. +func (e *EndpointController) deletePod(obj interface{}) { + if _, ok := obj.(*api.Pod); ok { + // Enqueue all the services that the pod used to be a member + // of. This happens to be exactly the same thing we do when a + // pod is added. + e.addPod(obj) + return + } + podKey, err := keyFunc(obj) + if err != nil { + glog.Errorf("Couldn't get key for object %+v: %v", obj, err) + } + glog.Infof("Pod %q was deleted but we don't have a record of its final state, so it will take up to %v before it will be removed from all endpoint records.", podKey, FullServiceResyncPeriod) + + // TODO: keep a map of pods to services to handle this condition. +} + +// obj could be an *api.Service, or a DeletionFinalStateUnknown marker item. +func (e *EndpointController) enqueueService(obj interface{}) { + key, err := keyFunc(obj) + if err != nil { + glog.Errorf("Couldn't get key for object %+v: %v", obj, err) + } + + e.queue.Add(key) +} + +// worker runs a worker thread that just dequeues items, processes them, and +// marks them done. You may run as many of these in parallel as you wish; the +// workqueue guarantees that they will not end up processing the same service +// at the same time. +func (e *EndpointController) worker() { + for { + func() { + key, quit := e.queue.Get() + if quit { + return + } + // Use defer: in the unlikely event that there's a + // panic, we'd still like this to get marked done-- + // otherwise the controller will not be able to sync + // this service again until it is restarted. + defer e.queue.Done(key) + e.syncService(key.(string)) + }() + } +} + +func (e *EndpointController) syncService(key string) { + startTime := time.Now() + defer func() { + glog.V(4).Infof("Finished syncing service %q endpoints. (%v)", key, time.Now().Sub(startTime)) + }() + + if !e.podStoreSynced() { + // Sleep so we give the pod reflector goroutine a chance to run. + time.Sleep(PodStoreSyncedPollPeriod) + glog.Infof("Waiting for pods controller to sync, requeuing rc %v", key) + e.queue.Add(key) + return + } + + obj, exists, err := e.serviceStore.Store.GetByKey(key) + if err != nil || !exists { + // Delete the corresponding endpoint, as the service has been deleted. + // TODO: Please note that this will delete an endpoint when a + // service is deleted. However, if we're down at the time when + // the service is deleted, we will miss that deletion, so this + // doesn't completely solve the problem. See #6877. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + glog.Errorf("Need to delete endpoint with key %q, but couldn't understand the key: %v", key, err) + // Don't retry, as the key isn't going to magically become understandable. + return + } + err = e.client.Endpoints(namespace).Delete(name, nil) + if err != nil && !errors.IsNotFound(err) { + glog.Errorf("Error deleting endpoint %q: %v", key, err) + e.queue.Add(key) // Retry + } + return + } + + service := obj.(*api.Service) + if service.Spec.Selector == nil { + // services without a selector receive no endpoints from this controller; + // these services will receive the endpoints that are created out-of-band via the REST API. + return + } + + glog.V(5).Infof("About to update endpoints for service %q", key) + pods, err := e.podStore.Pods(service.Namespace).List(labels.Set(service.Spec.Selector).AsSelector()) + if err != nil { + // Since we're getting stuff from a local cache, it is + // basically impossible to get this error. + glog.Errorf("Error syncing service %q: %v", key, err) + e.queue.Add(key) // Retry + return + } + + subsets := []api.EndpointSubset{} + podHostNames := map[string]endpoints.HostRecord{} + + for i := range pods.Items { + pod := &pods.Items[i] + + for i := range service.Spec.Ports { + servicePort := &service.Spec.Ports[i] + + portName := servicePort.Name + portProto := servicePort.Protocol + portNum, err := podutil.FindPort(pod, servicePort) + if err != nil { + glog.V(4).Infof("Failed to find port for service %s/%s: %v", service.Namespace, service.Name, err) + continue + } + if len(pod.Status.PodIP) == 0 { + glog.V(5).Infof("Failed to find an IP for pod %s/%s", pod.Namespace, pod.Name) + continue + } + if pod.DeletionTimestamp != nil { + glog.V(5).Infof("Pod is being deleted %s/%s", pod.Namespace, pod.Name) + continue + } + + hostname := pod.Annotations[utilpod.PodHostnameAnnotation] + if len(hostname) > 0 && + pod.Annotations[utilpod.PodSubdomainAnnotation] == service.Name && + service.Namespace == pod.Namespace { + hostRecord := endpoints.HostRecord{ + HostName: hostname, + } + podHostNames[string(pod.Status.PodIP)] = hostRecord + } + + epp := api.EndpointPort{Name: portName, Port: portNum, Protocol: portProto} + epa := api.EndpointAddress{ + IP: pod.Status.PodIP, + TargetRef: &api.ObjectReference{ + Kind: "Pod", + Namespace: pod.ObjectMeta.Namespace, + Name: pod.ObjectMeta.Name, + UID: pod.ObjectMeta.UID, + ResourceVersion: pod.ObjectMeta.ResourceVersion, + }} + if api.IsPodReady(pod) { + subsets = append(subsets, api.EndpointSubset{ + Addresses: []api.EndpointAddress{epa}, + Ports: []api.EndpointPort{epp}, + }) + } else { + glog.V(5).Infof("Pod is out of service: %v/%v", pod.Namespace, pod.Name) + subsets = append(subsets, api.EndpointSubset{ + NotReadyAddresses: []api.EndpointAddress{epa}, + Ports: []api.EndpointPort{epp}, + }) + } + } + } + subsets = endpoints.RepackSubsets(subsets) + + // See if there's actually an update here. + currentEndpoints, err := e.client.Endpoints(service.Namespace).Get(service.Name) + if err != nil { + if errors.IsNotFound(err) { + currentEndpoints = &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: service.Name, + Labels: service.Labels, + }, + } + } else { + glog.Errorf("Error getting endpoints: %v", err) + e.queue.Add(key) // Retry + return + } + } + + serializedPodHostNames := "" + if len(podHostNames) > 0 { + b, err := json.Marshal(podHostNames) + if err != nil { + glog.Errorf("Error updating endpoints. Marshalling of hostnames failed.: %v", err) + e.queue.Add(key) // Retry + return + } + serializedPodHostNames = string(b) + } + + podHostNamesAreEqual := verifyPodHostNamesAreEqual(serializedPodHostNames, currentEndpoints.Annotations) + + newAnnotations := make(map[string]string) + newAnnotations[endpoints.PodHostnamesAnnotation] = serializedPodHostNames + if reflect.DeepEqual(currentEndpoints.Subsets, subsets) && + reflect.DeepEqual(currentEndpoints.Labels, service.Labels) && podHostNamesAreEqual { + glog.V(5).Infof("endpoints are equal for %s/%s, skipping update", service.Namespace, service.Name) + return + } + newEndpoints := currentEndpoints + newEndpoints.Subsets = subsets + newEndpoints.Labels = service.Labels + if newEndpoints.Annotations == nil { + newEndpoints.Annotations = make(map[string]string) + } + if len(serializedPodHostNames) == 0 { + delete(newEndpoints.Annotations, endpoints.PodHostnamesAnnotation) + } else { + newEndpoints.Annotations[endpoints.PodHostnamesAnnotation] = serializedPodHostNames + } + if len(currentEndpoints.ResourceVersion) == 0 { + // No previous endpoints, create them + _, err = e.client.Endpoints(service.Namespace).Create(newEndpoints) + } else { + // Pre-existing + _, err = e.client.Endpoints(service.Namespace).Update(newEndpoints) + } + if err != nil { + glog.Errorf("Error updating endpoints: %v", err) + e.queue.Add(key) // Retry + } +} + +func verifyPodHostNamesAreEqual(newPodHostNames string, oldAnnotations map[string]string) bool { + oldPodHostNames := "" + if oldAnnotations != nil { + oldPodHostNames = oldAnnotations[endpoints.PodHostnamesAnnotation] + } + return oldPodHostNames == newPodHostNames +} + +// checkLeftoverEndpoints lists all currently existing endpoints and adds their +// service to the queue. This will detect endpoints that exist with no +// corresponding service; these endpoints need to be deleted. We only need to +// do this once on startup, because in steady-state these are detected (but +// some stragglers could have been left behind if the endpoint controller +// reboots). +func (e *EndpointController) checkLeftoverEndpoints() { + list, err := e.client.Endpoints(api.NamespaceAll).List(api.ListOptions{}) + if err != nil { + glog.Errorf("Unable to list endpoints (%v); orphaned endpoints will not be cleaned up. (They're pretty harmless, but you can restart this component if you want another attempt made.)", err) + return + } + for i := range list.Items { + ep := &list.Items[i] + key, err := keyFunc(ep) + if err != nil { + glog.Errorf("Unable to get key for endpoint %#v", ep) + continue + } + e.queue.Add(key) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/endpoint/endpoints_controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/endpoint/endpoints_controller_test.go new file mode 100644 index 000000000..6f5d43fd8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/endpoint/endpoints_controller_test.go @@ -0,0 +1,578 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 endpoint + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "k8s.io/kubernetes/pkg/api" + endptspkg "k8s.io/kubernetes/pkg/api/endpoints" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + _ "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/intstr" + utiltesting "k8s.io/kubernetes/pkg/util/testing" +) + +var alwaysReady = func() bool { return true } + +func addPods(store cache.Store, namespace string, nPods int, nPorts int, nNotReady int) { + for i := 0; i < nPods+nNotReady; i++ { + p := &api.Pod{ + TypeMeta: unversioned.TypeMeta{APIVersion: testapi.Default.GroupVersion().String()}, + ObjectMeta: api.ObjectMeta{ + Namespace: namespace, + Name: fmt.Sprintf("pod%d", i), + Labels: map[string]string{"foo": "bar"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Ports: []api.ContainerPort{}}}, + }, + Status: api.PodStatus{ + PodIP: fmt.Sprintf("1.2.3.%d", 4+i), + Conditions: []api.PodCondition{ + { + Type: api.PodReady, + Status: api.ConditionTrue, + }, + }, + }, + } + if i >= nPods { + p.Status.Conditions[0].Status = api.ConditionFalse + } + for j := 0; j < nPorts; j++ { + p.Spec.Containers[0].Ports = append(p.Spec.Containers[0].Ports, + api.ContainerPort{Name: fmt.Sprintf("port%d", i), ContainerPort: 8080 + j}) + } + store.Add(p) + } +} + +type serverResponse struct { + statusCode int + obj interface{} +} + +func makeTestServer(t *testing.T, namespace string, endpointsResponse serverResponse) (*httptest.Server, *utiltesting.FakeHandler) { + fakeEndpointsHandler := utiltesting.FakeHandler{ + StatusCode: endpointsResponse.statusCode, + ResponseBody: runtime.EncodeOrDie(testapi.Default.Codec(), endpointsResponse.obj.(runtime.Object)), + } + mux := http.NewServeMux() + mux.Handle(testapi.Default.ResourcePath("endpoints", namespace, ""), &fakeEndpointsHandler) + mux.Handle(testapi.Default.ResourcePath("endpoints/", namespace, ""), &fakeEndpointsHandler) + mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { + t.Errorf("unexpected request: %v", req.RequestURI) + res.WriteHeader(http.StatusNotFound) + }) + return httptest.NewServer(mux), &fakeEndpointsHandler +} + +func TestSyncEndpointsItemsPreserveNoSelector(t *testing.T) { + ns := api.NamespaceDefault + testServer, endpointsHandler := makeTestServer(t, ns, + serverResponse{http.StatusOK, &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "6.7.8.9"}}, + Ports: []api.EndpointPort{{Port: 1000}}, + }}, + }}) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + endpoints := NewEndpointController(client, controller.NoResyncPeriodFunc) + endpoints.podStoreSynced = alwaysReady + endpoints.serviceStore.Store.Add(&api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: ns}, + Spec: api.ServiceSpec{Ports: []api.ServicePort{{Port: 80}}}, + }) + endpoints.syncService(ns + "/foo") + endpointsHandler.ValidateRequestCount(t, 0) +} + +func TestCheckLeftoverEndpoints(t *testing.T) { + ns := api.NamespaceDefault + // Note that this requests *all* endpoints, therefore the NamespaceAll + // below. + testServer, _ := makeTestServer(t, api.NamespaceAll, + serverResponse{http.StatusOK, &api.EndpointsList{ + ListMeta: unversioned.ListMeta{ + ResourceVersion: "1", + }, + Items: []api.Endpoints{{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "6.7.8.9"}}, + Ports: []api.EndpointPort{{Port: 1000}}, + }}, + }}, + }}) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + endpoints := NewEndpointController(client, controller.NoResyncPeriodFunc) + endpoints.podStoreSynced = alwaysReady + endpoints.checkLeftoverEndpoints() + + if e, a := 1, endpoints.queue.Len(); e != a { + t.Fatalf("Expected %v, got %v", e, a) + } + got, _ := endpoints.queue.Get() + if e, a := ns+"/foo", got; e != a { + t.Errorf("Expected %v, got %v", e, a) + } +} + +func TestSyncEndpointsProtocolTCP(t *testing.T) { + ns := "other" + testServer, endpointsHandler := makeTestServer(t, ns, + serverResponse{http.StatusOK, &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "6.7.8.9"}}, + Ports: []api.EndpointPort{{Port: 1000, Protocol: "TCP"}}, + }}, + }}) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + endpoints := NewEndpointController(client, controller.NoResyncPeriodFunc) + endpoints.podStoreSynced = alwaysReady + + addPods(endpoints.podStore.Store, ns, 1, 1, 0) + endpoints.serviceStore.Store.Add(&api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: ns}, + Spec: api.ServiceSpec{ + Selector: map[string]string{}, + Ports: []api.ServicePort{{Port: 80, TargetPort: intstr.FromInt(8080), Protocol: "TCP"}}, + }, + }) + endpoints.syncService(ns + "/foo") + endpointsHandler.ValidateRequestCount(t, 2) + data := runtime.EncodeOrDie(testapi.Default.Codec(), &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, + Ports: []api.EndpointPort{{Port: 8080, Protocol: "TCP"}}, + }}, + }) + endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", ns, "foo"), "PUT", &data) +} + +func TestSyncEndpointsProtocolUDP(t *testing.T) { + ns := "other" + testServer, endpointsHandler := makeTestServer(t, ns, + serverResponse{http.StatusOK, &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "6.7.8.9"}}, + Ports: []api.EndpointPort{{Port: 1000, Protocol: "UDP"}}, + }}, + }}) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + endpoints := NewEndpointController(client, controller.NoResyncPeriodFunc) + endpoints.podStoreSynced = alwaysReady + addPods(endpoints.podStore.Store, ns, 1, 1, 0) + endpoints.serviceStore.Store.Add(&api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: ns}, + Spec: api.ServiceSpec{ + Selector: map[string]string{}, + Ports: []api.ServicePort{{Port: 80, TargetPort: intstr.FromInt(8080), Protocol: "UDP"}}, + }, + }) + endpoints.syncService(ns + "/foo") + endpointsHandler.ValidateRequestCount(t, 2) + data := runtime.EncodeOrDie(testapi.Default.Codec(), &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, + Ports: []api.EndpointPort{{Port: 8080, Protocol: "UDP"}}, + }}, + }) + endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", ns, "foo"), "PUT", &data) +} + +func TestSyncEndpointsItemsEmptySelectorSelectsAll(t *testing.T) { + ns := "other" + testServer, endpointsHandler := makeTestServer(t, ns, + serverResponse{http.StatusOK, &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []api.EndpointSubset{}, + }}) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + endpoints := NewEndpointController(client, controller.NoResyncPeriodFunc) + endpoints.podStoreSynced = alwaysReady + addPods(endpoints.podStore.Store, ns, 1, 1, 0) + endpoints.serviceStore.Store.Add(&api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: ns}, + Spec: api.ServiceSpec{ + Selector: map[string]string{}, + Ports: []api.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}}, + }, + }) + endpoints.syncService(ns + "/foo") + data := runtime.EncodeOrDie(testapi.Default.Codec(), &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, + Ports: []api.EndpointPort{{Port: 8080, Protocol: "TCP"}}, + }}, + }) + endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", ns, "foo"), "PUT", &data) +} + +func TestSyncEndpointsItemsEmptySelectorSelectsAllNotReady(t *testing.T) { + ns := "other" + testServer, endpointsHandler := makeTestServer(t, ns, + serverResponse{http.StatusOK, &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []api.EndpointSubset{}, + }}) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + endpoints := NewEndpointController(client, controller.NoResyncPeriodFunc) + endpoints.podStoreSynced = alwaysReady + addPods(endpoints.podStore.Store, ns, 0, 1, 1) + endpoints.serviceStore.Store.Add(&api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: ns}, + Spec: api.ServiceSpec{ + Selector: map[string]string{}, + Ports: []api.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}}, + }, + }) + endpoints.syncService(ns + "/foo") + data := runtime.EncodeOrDie(testapi.Default.Codec(), &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []api.EndpointSubset{{ + NotReadyAddresses: []api.EndpointAddress{{IP: "1.2.3.4", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, + Ports: []api.EndpointPort{{Port: 8080, Protocol: "TCP"}}, + }}, + }) + endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", ns, "foo"), "PUT", &data) +} + +func TestSyncEndpointsItemsEmptySelectorSelectsAllMixed(t *testing.T) { + ns := "other" + testServer, endpointsHandler := makeTestServer(t, ns, + serverResponse{http.StatusOK, &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []api.EndpointSubset{}, + }}) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + endpoints := NewEndpointController(client, controller.NoResyncPeriodFunc) + endpoints.podStoreSynced = alwaysReady + addPods(endpoints.podStore.Store, ns, 1, 1, 1) + endpoints.serviceStore.Store.Add(&api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: ns}, + Spec: api.ServiceSpec{ + Selector: map[string]string{}, + Ports: []api.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}}, + }, + }) + endpoints.syncService(ns + "/foo") + data := runtime.EncodeOrDie(testapi.Default.Codec(), &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, + NotReadyAddresses: []api.EndpointAddress{{IP: "1.2.3.5", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod1", Namespace: ns}}}, + Ports: []api.EndpointPort{{Port: 8080, Protocol: "TCP"}}, + }}, + }) + endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", ns, "foo"), "PUT", &data) +} + +func TestSyncEndpointsItemsPreexisting(t *testing.T) { + ns := "bar" + testServer, endpointsHandler := makeTestServer(t, ns, + serverResponse{http.StatusOK, &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "6.7.8.9"}}, + Ports: []api.EndpointPort{{Port: 1000}}, + }}, + }}) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + endpoints := NewEndpointController(client, controller.NoResyncPeriodFunc) + endpoints.podStoreSynced = alwaysReady + addPods(endpoints.podStore.Store, ns, 1, 1, 0) + endpoints.serviceStore.Store.Add(&api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: ns}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"foo": "bar"}, + Ports: []api.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}}, + }, + }) + endpoints.syncService(ns + "/foo") + data := runtime.EncodeOrDie(testapi.Default.Codec(), &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, + Ports: []api.EndpointPort{{Port: 8080, Protocol: "TCP"}}, + }}, + }) + endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", ns, "foo"), "PUT", &data) +} + +func TestSyncEndpointsItemsPreexistingIdentical(t *testing.T) { + ns := api.NamespaceDefault + testServer, endpointsHandler := makeTestServer(t, api.NamespaceDefault, + serverResponse{http.StatusOK, &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + ResourceVersion: "1", + Name: "foo", + Namespace: ns, + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, + Ports: []api.EndpointPort{{Port: 8080, Protocol: "TCP"}}, + }}, + }}) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + endpoints := NewEndpointController(client, controller.NoResyncPeriodFunc) + endpoints.podStoreSynced = alwaysReady + addPods(endpoints.podStore.Store, api.NamespaceDefault, 1, 1, 0) + endpoints.serviceStore.Store.Add(&api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"foo": "bar"}, + Ports: []api.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}}, + }, + }) + endpoints.syncService(ns + "/foo") + endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", api.NamespaceDefault, "foo"), "GET", nil) +} + +func TestSyncEndpointsItems(t *testing.T) { + ns := "other" + testServer, endpointsHandler := makeTestServer(t, ns, + serverResponse{http.StatusOK, &api.Endpoints{}}) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + endpoints := NewEndpointController(client, controller.NoResyncPeriodFunc) + endpoints.podStoreSynced = alwaysReady + addPods(endpoints.podStore.Store, ns, 3, 2, 0) + addPods(endpoints.podStore.Store, "blah", 5, 2, 0) // make sure these aren't found! + endpoints.serviceStore.Store.Add(&api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: ns}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"foo": "bar"}, + Ports: []api.ServicePort{ + {Name: "port0", Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + {Name: "port1", Port: 88, Protocol: "TCP", TargetPort: intstr.FromInt(8088)}, + }, + }, + }) + endpoints.syncService("other/foo") + expectedSubsets := []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "1.2.3.4", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}, + {IP: "1.2.3.5", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod1", Namespace: ns}}, + {IP: "1.2.3.6", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod2", Namespace: ns}}, + }, + Ports: []api.EndpointPort{ + {Name: "port0", Port: 8080, Protocol: "TCP"}, + {Name: "port1", Port: 8088, Protocol: "TCP"}, + }, + }} + data := runtime.EncodeOrDie(testapi.Default.Codec(), &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + ResourceVersion: "", + }, + Subsets: endptspkg.SortSubsets(expectedSubsets), + }) + // endpointsHandler should get 2 requests - one for "GET" and the next for "POST". + endpointsHandler.ValidateRequestCount(t, 2) + endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", ns, ""), "POST", &data) +} + +func TestSyncEndpointsItemsWithLabels(t *testing.T) { + ns := "other" + testServer, endpointsHandler := makeTestServer(t, ns, + serverResponse{http.StatusOK, &api.Endpoints{}}) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + endpoints := NewEndpointController(client, controller.NoResyncPeriodFunc) + endpoints.podStoreSynced = alwaysReady + addPods(endpoints.podStore.Store, ns, 3, 2, 0) + serviceLabels := map[string]string{"foo": "bar"} + endpoints.serviceStore.Store.Add(&api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + Labels: serviceLabels, + }, + Spec: api.ServiceSpec{ + Selector: map[string]string{"foo": "bar"}, + Ports: []api.ServicePort{ + {Name: "port0", Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + {Name: "port1", Port: 88, Protocol: "TCP", TargetPort: intstr.FromInt(8088)}, + }, + }, + }) + endpoints.syncService(ns + "/foo") + expectedSubsets := []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "1.2.3.4", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}, + {IP: "1.2.3.5", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod1", Namespace: ns}}, + {IP: "1.2.3.6", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod2", Namespace: ns}}, + }, + Ports: []api.EndpointPort{ + {Name: "port0", Port: 8080, Protocol: "TCP"}, + {Name: "port1", Port: 8088, Protocol: "TCP"}, + }, + }} + data := runtime.EncodeOrDie(testapi.Default.Codec(), &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + ResourceVersion: "", + Labels: serviceLabels, + }, + Subsets: endptspkg.SortSubsets(expectedSubsets), + }) + // endpointsHandler should get 2 requests - one for "GET" and the next for "POST". + endpointsHandler.ValidateRequestCount(t, 2) + endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", ns, ""), "POST", &data) +} + +func TestSyncEndpointsItemsPreexistingLabelsChange(t *testing.T) { + ns := "bar" + testServer, endpointsHandler := makeTestServer(t, ns, + serverResponse{http.StatusOK, &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + Labels: map[string]string{ + "foo": "bar", + }, + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "6.7.8.9"}}, + Ports: []api.EndpointPort{{Port: 1000}}, + }}, + }}) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + endpoints := NewEndpointController(client, controller.NoResyncPeriodFunc) + endpoints.podStoreSynced = alwaysReady + addPods(endpoints.podStore.Store, ns, 1, 1, 0) + serviceLabels := map[string]string{"baz": "blah"} + endpoints.serviceStore.Store.Add(&api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + Labels: serviceLabels, + }, + Spec: api.ServiceSpec{ + Selector: map[string]string{"foo": "bar"}, + Ports: []api.ServicePort{{Port: 80, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}}, + }, + }) + endpoints.syncService(ns + "/foo") + data := runtime.EncodeOrDie(testapi.Default.Codec(), &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: ns, + ResourceVersion: "1", + Labels: serviceLabels, + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4", TargetRef: &api.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}}}, + Ports: []api.EndpointPort{{Port: 8080, Protocol: "TCP"}}, + }}, + }) + endpointsHandler.ValidateRequest(t, testapi.Default.ResourcePath("endpoints", ns, "foo"), "PUT", &data) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/framework/controller.go b/vendor/k8s.io/kubernetes/pkg/controller/framework/controller.go new file mode 100644 index 000000000..ed8195252 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/framework/controller.go @@ -0,0 +1,321 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 framework + +import ( + "sync" + "time" + + "k8s.io/kubernetes/pkg/client/cache" + "k8s.io/kubernetes/pkg/runtime" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" +) + +// Config contains all the settings for a Controller. +type Config struct { + // The queue for your objects; either a cache.FIFO or + // a cache.DeltaFIFO. Your Process() function should accept + // the output of this Oueue's Pop() method. + cache.Queue + + // Something that can list and watch your objects. + cache.ListerWatcher + + // Something that can process your objects. + Process ProcessFunc + + // The type of your objects. + ObjectType runtime.Object + + // Reprocess everything at least this often. + // Note that if it takes longer for you to clear the queue than this + // period, you will end up processing items in the order determined + // by cache.FIFO.Replace(). Currently, this is random. If this is a + // problem, we can change that replacement policy to append new + // things to the end of the queue instead of replacing the entire + // queue. + FullResyncPeriod time.Duration + + // If true, when Process() returns an error, re-enqueue the object. + // TODO: add interface to let you inject a delay/backoff or drop + // the object completely if desired. Pass the object in + // question to this interface as a parameter. + RetryOnError bool +} + +// ProcessFunc processes a single object. +type ProcessFunc func(obj interface{}) error + +// Controller is a generic controller framework. +type Controller struct { + config Config + reflector *cache.Reflector + reflectorMutex sync.RWMutex +} + +// New makes a new Controller from the given Config. +func New(c *Config) *Controller { + ctlr := &Controller{ + config: *c, + } + return ctlr +} + +// Run begins processing items, and will continue until a value is sent down stopCh. +// It's an error to call Run more than once. +// Run blocks; call via go. +func (c *Controller) Run(stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + r := cache.NewReflector( + c.config.ListerWatcher, + c.config.ObjectType, + c.config.Queue, + c.config.FullResyncPeriod, + ) + + c.reflectorMutex.Lock() + c.reflector = r + c.reflectorMutex.Unlock() + + r.RunUntil(stopCh) + + wait.Until(c.processLoop, time.Second, stopCh) +} + +// Returns true once this controller has completed an initial resource listing +func (c *Controller) HasSynced() bool { + return c.config.Queue.HasSynced() +} + +// Requeue adds the provided object back into the queue if it does not already exist. +func (c *Controller) Requeue(obj interface{}) error { + return c.config.Queue.AddIfNotPresent(cache.Deltas{ + cache.Delta{ + Type: cache.Sync, + Object: obj, + }, + }) +} + +// processLoop drains the work queue. +// TODO: Consider doing the processing in parallel. This will require a little thought +// to make sure that we don't end up processing the same object multiple times +// concurrently. +func (c *Controller) processLoop() { + for { + obj := c.config.Queue.Pop() + err := c.config.Process(obj) + if err != nil { + if c.config.RetryOnError { + // This is the safe way to re-enqueue. + c.config.Queue.AddIfNotPresent(obj) + } + } + } +} + +// ResourceEventHandler can handle notifications for events that happen to a +// resource. The events are informational only, so you can't return an +// error. +// * OnAdd is called when an object is added. +// * OnUpdate is called when an object is modified. Note that oldObj is the +// last known state of the object-- it is possible that several changes +// were combined together, so you can't use this to see every single +// change. OnUpdate is also called when a re-list happens, and it will +// get called even if nothing changed. This is useful for periodically +// evaluating or syncing something. +// * OnDelete will get the final state of the item if it is known, otherwise +// it will get an object of type cache.DeletedFinalStateUnknown. This can +// happen if the watch is closed and misses the delete event and we don't +// notice the deletion until the subsequent re-list. +type ResourceEventHandler interface { + OnAdd(obj interface{}) + OnUpdate(oldObj, newObj interface{}) + OnDelete(obj interface{}) +} + +// ResourceEventHandlerFuncs is an adaptor to let you easily specify as many or +// as few of the notification functions as you want while still implementing +// ResourceEventHandler. +type ResourceEventHandlerFuncs struct { + AddFunc func(obj interface{}) + UpdateFunc func(oldObj, newObj interface{}) + DeleteFunc func(obj interface{}) +} + +// OnAdd calls AddFunc if it's not nil. +func (r ResourceEventHandlerFuncs) OnAdd(obj interface{}) { + if r.AddFunc != nil { + r.AddFunc(obj) + } +} + +// OnUpdate calls UpdateFunc if it's not nil. +func (r ResourceEventHandlerFuncs) OnUpdate(oldObj, newObj interface{}) { + if r.UpdateFunc != nil { + r.UpdateFunc(oldObj, newObj) + } +} + +// OnDelete calls DeleteFunc if it's not nil. +func (r ResourceEventHandlerFuncs) OnDelete(obj interface{}) { + if r.DeleteFunc != nil { + r.DeleteFunc(obj) + } +} + +// DeletionHandlingMetaNamespaceKeyFunc checks for +// cache.DeletedFinalStateUnknown objects before calling +// cache.MetaNamespaceKeyFunc. +func DeletionHandlingMetaNamespaceKeyFunc(obj interface{}) (string, error) { + if d, ok := obj.(cache.DeletedFinalStateUnknown); ok { + return d.Key, nil + } + return cache.MetaNamespaceKeyFunc(obj) +} + +// NewInformer returns a cache.Store and a controller for populating the store +// while also providing event notifications. You should only used the returned +// cache.Store for Get/List operations; Add/Modify/Deletes will cause the event +// notifications to be faulty. +// +// Parameters: +// * lw is list and watch functions for the source of the resource you want to +// be informed of. +// * objType is an object of the type that you expect to receive. +// * resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate +// calls, even if nothing changed). Otherwise, re-list will be delayed as +// long as possible (until the upstream source closes the watch or times out, +// or you stop the controller). +// * h is the object you want notifications sent to. +// +func NewInformer( + lw cache.ListerWatcher, + objType runtime.Object, + resyncPeriod time.Duration, + h ResourceEventHandler, +) (cache.Store, *Controller) { + // This will hold the client state, as we know it. + clientState := cache.NewStore(DeletionHandlingMetaNamespaceKeyFunc) + + // This will hold incoming changes. Note how we pass clientState in as a + // KeyLister, that way resync operations will result in the correct set + // of update/delete deltas. + fifo := cache.NewDeltaFIFO(cache.MetaNamespaceKeyFunc, nil, clientState) + + cfg := &Config{ + Queue: fifo, + ListerWatcher: lw, + ObjectType: objType, + FullResyncPeriod: resyncPeriod, + RetryOnError: false, + + Process: func(obj interface{}) error { + // from oldest to newest + for _, d := range obj.(cache.Deltas) { + switch d.Type { + case cache.Sync, cache.Added, cache.Updated: + if old, exists, err := clientState.Get(d.Object); err == nil && exists { + if err := clientState.Update(d.Object); err != nil { + return err + } + h.OnUpdate(old, d.Object) + } else { + if err := clientState.Add(d.Object); err != nil { + return err + } + h.OnAdd(d.Object) + } + case cache.Deleted: + if err := clientState.Delete(d.Object); err != nil { + return err + } + h.OnDelete(d.Object) + } + } + return nil + }, + } + return clientState, New(cfg) +} + +// NewIndexerInformer returns a cache.Indexer and a controller for populating the index +// while also providing event notifications. You should only used the returned +// cache.Index for Get/List operations; Add/Modify/Deletes will cause the event +// notifications to be faulty. +// +// Parameters: +// * lw is list and watch functions for the source of the resource you want to +// be informed of. +// * objType is an object of the type that you expect to receive. +// * resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate +// calls, even if nothing changed). Otherwise, re-list will be delayed as +// long as possible (until the upstream source closes the watch or times out, +// or you stop the controller). +// * h is the object you want notifications sent to. +// +func NewIndexerInformer( + lw cache.ListerWatcher, + objType runtime.Object, + resyncPeriod time.Duration, + h ResourceEventHandler, + indexers cache.Indexers, +) (cache.Indexer, *Controller) { + // This will hold the client state, as we know it. + clientState := cache.NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers) + + // This will hold incoming changes. Note how we pass clientState in as a + // KeyLister, that way resync operations will result in the correct set + // of update/delete deltas. + fifo := cache.NewDeltaFIFO(cache.MetaNamespaceKeyFunc, nil, clientState) + + cfg := &Config{ + Queue: fifo, + ListerWatcher: lw, + ObjectType: objType, + FullResyncPeriod: resyncPeriod, + RetryOnError: false, + + Process: func(obj interface{}) error { + // from oldest to newest + for _, d := range obj.(cache.Deltas) { + switch d.Type { + case cache.Sync, cache.Added, cache.Updated: + if old, exists, err := clientState.Get(d.Object); err == nil && exists { + if err := clientState.Update(d.Object); err != nil { + return err + } + h.OnUpdate(old, d.Object) + } else { + if err := clientState.Add(d.Object); err != nil { + return err + } + h.OnAdd(d.Object) + } + case cache.Deleted: + if err := clientState.Delete(d.Object); err != nil { + return err + } + h.OnDelete(d.Object) + } + } + return nil + }, + } + return clientState, New(cfg) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/framework/controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/framework/controller_test.go new file mode 100644 index 000000000..dbede3423 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/framework/controller_test.go @@ -0,0 +1,404 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 framework_test + +import ( + "fmt" + "math/rand" + "sync" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/cache" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/sets" + + "github.com/google/gofuzz" +) + +func Example() { + // source simulates an apiserver object endpoint. + source := framework.NewFakeControllerSource() + + // This will hold the downstream state, as we know it. + downstream := cache.NewStore(framework.DeletionHandlingMetaNamespaceKeyFunc) + + // This will hold incoming changes. Note how we pass downstream in as a + // KeyLister, that way resync operations will result in the correct set + // of update/delete deltas. + fifo := cache.NewDeltaFIFO(cache.MetaNamespaceKeyFunc, nil, downstream) + + // Let's do threadsafe output to get predictable test results. + deletionCounter := make(chan string, 1000) + + cfg := &framework.Config{ + Queue: fifo, + ListerWatcher: source, + ObjectType: &api.Pod{}, + FullResyncPeriod: time.Millisecond * 100, + RetryOnError: false, + + // Let's implement a simple controller that just deletes + // everything that comes in. + Process: func(obj interface{}) error { + // Obj is from the Pop method of the Queue we make above. + newest := obj.(cache.Deltas).Newest() + + if newest.Type != cache.Deleted { + // Update our downstream store. + err := downstream.Add(newest.Object) + if err != nil { + return err + } + + // Delete this object. + source.Delete(newest.Object.(runtime.Object)) + } else { + // Update our downstream store. + err := downstream.Delete(newest.Object) + if err != nil { + return err + } + + // fifo's KeyOf is easiest, because it handles + // DeletedFinalStateUnknown markers. + key, err := fifo.KeyOf(newest.Object) + if err != nil { + return err + } + + // Report this deletion. + deletionCounter <- key + } + return nil + }, + } + + // Create the controller and run it until we close stop. + stop := make(chan struct{}) + defer close(stop) + go framework.New(cfg).Run(stop) + + // Let's add a few objects to the source. + testIDs := []string{"a-hello", "b-controller", "c-framework"} + for _, name := range testIDs { + // Note that these pods are not valid-- the fake source doesn't + // call validation or anything. + source.Add(&api.Pod{ObjectMeta: api.ObjectMeta{Name: name}}) + } + + // Let's wait for the controller to process the things we just added. + outputSet := sets.String{} + for i := 0; i < len(testIDs); i++ { + outputSet.Insert(<-deletionCounter) + } + + for _, key := range outputSet.List() { + fmt.Println(key) + } + // Output: + // a-hello + // b-controller + // c-framework +} + +func ExampleInformer() { + // source simulates an apiserver object endpoint. + source := framework.NewFakeControllerSource() + + // Let's do threadsafe output to get predictable test results. + deletionCounter := make(chan string, 1000) + + // Make a controller that immediately deletes anything added to it, and + // logs anything deleted. + _, controller := framework.NewInformer( + source, + &api.Pod{}, + time.Millisecond*100, + framework.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + source.Delete(obj.(runtime.Object)) + }, + DeleteFunc: func(obj interface{}) { + key, err := framework.DeletionHandlingMetaNamespaceKeyFunc(obj) + if err != nil { + key = "oops something went wrong with the key" + } + + // Report this deletion. + deletionCounter <- key + }, + }, + ) + + // Run the controller and run it until we close stop. + stop := make(chan struct{}) + defer close(stop) + go controller.Run(stop) + + // Let's add a few objects to the source. + testIDs := []string{"a-hello", "b-controller", "c-framework"} + for _, name := range testIDs { + // Note that these pods are not valid-- the fake source doesn't + // call validation or anything. + source.Add(&api.Pod{ObjectMeta: api.ObjectMeta{Name: name}}) + } + + // Let's wait for the controller to process the things we just added. + outputSet := sets.String{} + for i := 0; i < len(testIDs); i++ { + outputSet.Insert(<-deletionCounter) + } + + for _, key := range outputSet.List() { + fmt.Println(key) + } + // Output: + // a-hello + // b-controller + // c-framework +} + +func TestHammerController(t *testing.T) { + // This test executes a bunch of requests through the fake source and + // controller framework to make sure there's no locking/threading + // errors. If an error happens, it should hang forever or trigger the + // race detector. + + // source simulates an apiserver object endpoint. + source := framework.NewFakeControllerSource() + + // Let's do threadsafe output to get predictable test results. + outputSetLock := sync.Mutex{} + // map of key to operations done on the key + outputSet := map[string][]string{} + + recordFunc := func(eventType string, obj interface{}) { + key, err := framework.DeletionHandlingMetaNamespaceKeyFunc(obj) + if err != nil { + t.Errorf("something wrong with key: %v", err) + key = "oops something went wrong with the key" + } + + // Record some output when items are deleted. + outputSetLock.Lock() + defer outputSetLock.Unlock() + outputSet[key] = append(outputSet[key], eventType) + } + + // Make a controller which just logs all the changes it gets. + _, controller := framework.NewInformer( + source, + &api.Pod{}, + time.Millisecond*100, + framework.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { recordFunc("add", obj) }, + UpdateFunc: func(oldObj, newObj interface{}) { recordFunc("update", newObj) }, + DeleteFunc: func(obj interface{}) { recordFunc("delete", obj) }, + }, + ) + + if controller.HasSynced() { + t.Errorf("Expected HasSynced() to return false before we started the controller") + } + + // Run the controller and run it until we close stop. + stop := make(chan struct{}) + go controller.Run(stop) + + // Let's wait for the controller to do its initial sync + time.Sleep(100 * time.Millisecond) + if !controller.HasSynced() { + t.Errorf("Expected HasSynced() to return true after the initial sync") + } + + wg := sync.WaitGroup{} + const threads = 3 + wg.Add(threads) + for i := 0; i < threads; i++ { + go func() { + defer wg.Done() + // Let's add a few objects to the source. + currentNames := sets.String{} + rs := rand.NewSource(rand.Int63()) + f := fuzz.New().NilChance(.5).NumElements(0, 2).RandSource(rs) + r := rand.New(rs) // Mustn't use r and f concurrently! + for i := 0; i < 100; i++ { + var name string + var isNew bool + if currentNames.Len() == 0 || r.Intn(3) == 1 { + f.Fuzz(&name) + isNew = true + } else { + l := currentNames.List() + name = l[r.Intn(len(l))] + } + + pod := &api.Pod{} + f.Fuzz(pod) + pod.ObjectMeta.Name = name + pod.ObjectMeta.Namespace = "default" + // Add, update, or delete randomly. + // Note that these pods are not valid-- the fake source doesn't + // call validation or perform any other checking. + if isNew { + currentNames.Insert(name) + source.Add(pod) + continue + } + switch r.Intn(2) { + case 0: + currentNames.Insert(name) + source.Modify(pod) + case 1: + currentNames.Delete(name) + source.Delete(pod) + } + } + }() + } + wg.Wait() + + // Let's wait for the controller to finish processing the things we just added. + time.Sleep(100 * time.Millisecond) + close(stop) + + outputSetLock.Lock() + t.Logf("got: %#v", outputSet) +} + +func TestUpdate(t *testing.T) { + // This test is going to exercise the various paths that result in a + // call to update. + + // source simulates an apiserver object endpoint. + source := framework.NewFakeControllerSource() + + const ( + FROM = "from" + ADD_MISSED = "missed the add event" + TO = "to" + ) + + // These are the transitions we expect to see; because this is + // asynchronous, there are a lot of valid possibilities. + type pair struct{ from, to string } + allowedTransitions := map[pair]bool{ + pair{FROM, TO}: true, + pair{FROM, ADD_MISSED}: true, + pair{ADD_MISSED, TO}: true, + + // Because a resync can happen when we've already observed one + // of the above but before the item is deleted. + pair{TO, TO}: true, + // Because a resync could happen before we observe an update. + pair{FROM, FROM}: true, + } + + pod := func(name, check string, final bool) *api.Pod { + p := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Labels: map[string]string{"check": check}, + }, + } + if final { + p.Labels["final"] = "true" + } + return p + } + deletePod := func(p *api.Pod) bool { + return p.Labels["final"] == "true" + } + + tests := []func(string){ + func(name string) { + name = "a-" + name + source.Add(pod(name, FROM, false)) + source.Modify(pod(name, TO, true)) + }, + func(name string) { + name = "b-" + name + source.Add(pod(name, FROM, false)) + source.ModifyDropWatch(pod(name, TO, true)) + }, + func(name string) { + name = "c-" + name + source.AddDropWatch(pod(name, FROM, false)) + source.Modify(pod(name, ADD_MISSED, false)) + source.Modify(pod(name, TO, true)) + }, + func(name string) { + name = "d-" + name + source.Add(pod(name, FROM, true)) + }, + } + + const threads = 3 + + var testDoneWG sync.WaitGroup + testDoneWG.Add(threads * len(tests)) + + // Make a controller that deletes things once it observes an update. + // It calls Done() on the wait group on deletions so we can tell when + // everything we've added has been deleted. + _, controller := framework.NewInformer( + source, + &api.Pod{}, + time.Millisecond*1, + framework.ResourceEventHandlerFuncs{ + UpdateFunc: func(oldObj, newObj interface{}) { + o, n := oldObj.(*api.Pod), newObj.(*api.Pod) + from, to := o.Labels["check"], n.Labels["check"] + if !allowedTransitions[pair{from, to}] { + t.Errorf("observed transition %q -> %q for %v", from, to, n.Name) + } + if deletePod(n) { + source.Delete(n) + } + }, + DeleteFunc: func(obj interface{}) { + testDoneWG.Done() + }, + }, + ) + + // Run the controller and run it until we close stop. + // Once Run() is called, calls to testDoneWG.Done() might start, so + // all testDoneWG.Add() calls must happen before this point + stop := make(chan struct{}) + go controller.Run(stop) + + // run every test a few times, in parallel + var wg sync.WaitGroup + wg.Add(threads * len(tests)) + for i := 0; i < threads; i++ { + for j, f := range tests { + go func(name string, f func(string)) { + defer wg.Done() + f(name) + }(fmt.Sprintf("%v-%v", i, j), f) + } + } + wg.Wait() + + // Let's wait for the controller to process the things we just added. + testDoneWG.Wait() + close(stop) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/framework/doc.go b/vendor/k8s.io/kubernetes/pkg/controller/framework/doc.go new file mode 100644 index 000000000..ecd3cf28a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/framework/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 framework implements all the grunt work involved in running a simple controller. +package framework diff --git a/vendor/k8s.io/kubernetes/pkg/controller/framework/fake_controller_source.go b/vendor/k8s.io/kubernetes/pkg/controller/framework/fake_controller_source.go new file mode 100644 index 000000000..bebacb531 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/framework/fake_controller_source.go @@ -0,0 +1,188 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 framework + +import ( + "errors" + "math/rand" + "strconv" + "sync" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/watch" +) + +func NewFakeControllerSource() *FakeControllerSource { + return &FakeControllerSource{ + items: map[nnu]runtime.Object{}, + broadcaster: watch.NewBroadcaster(100, watch.WaitIfChannelFull), + } +} + +// FakeControllerSource implements listing/watching for testing. +type FakeControllerSource struct { + lock sync.RWMutex + items map[nnu]runtime.Object + changes []watch.Event // one change per resourceVersion + broadcaster *watch.Broadcaster +} + +// namespace, name, uid to be used as a key. +type nnu struct { + namespace, name string + uid types.UID +} + +// Add adds an object to the set and sends an add event to watchers. +// obj's ResourceVersion is set. +func (f *FakeControllerSource) Add(obj runtime.Object) { + f.Change(watch.Event{Type: watch.Added, Object: obj}, 1) +} + +// Modify updates an object in the set and sends a modified event to watchers. +// obj's ResourceVersion is set. +func (f *FakeControllerSource) Modify(obj runtime.Object) { + f.Change(watch.Event{Type: watch.Modified, Object: obj}, 1) +} + +// Delete deletes an object from the set and sends a delete event to watchers. +// obj's ResourceVersion is set. +func (f *FakeControllerSource) Delete(lastValue runtime.Object) { + f.Change(watch.Event{Type: watch.Deleted, Object: lastValue}, 1) +} + +// AddDropWatch adds an object to the set but forgets to send an add event to +// watchers. +// obj's ResourceVersion is set. +func (f *FakeControllerSource) AddDropWatch(obj runtime.Object) { + f.Change(watch.Event{Type: watch.Added, Object: obj}, 0) +} + +// ModifyDropWatch updates an object in the set but forgets to send a modify +// event to watchers. +// obj's ResourceVersion is set. +func (f *FakeControllerSource) ModifyDropWatch(obj runtime.Object) { + f.Change(watch.Event{Type: watch.Modified, Object: obj}, 0) +} + +// DeleteDropWatch deletes an object from the set but forgets to send a delete +// event to watchers. +// obj's ResourceVersion is set. +func (f *FakeControllerSource) DeleteDropWatch(lastValue runtime.Object) { + f.Change(watch.Event{Type: watch.Deleted, Object: lastValue}, 0) +} + +func (f *FakeControllerSource) key(accessor meta.Object) nnu { + return nnu{accessor.GetNamespace(), accessor.GetName(), accessor.GetUID()} +} + +// Change records the given event (setting the object's resource version) and +// sends a watch event with the specified probability. +func (f *FakeControllerSource) Change(e watch.Event, watchProbability float64) { + f.lock.Lock() + defer f.lock.Unlock() + + accessor, err := meta.Accessor(e.Object) + if err != nil { + panic(err) // this is test code only + } + + resourceVersion := len(f.changes) + 1 + accessor.SetResourceVersion(strconv.Itoa(resourceVersion)) + f.changes = append(f.changes, e) + key := f.key(accessor) + switch e.Type { + case watch.Added, watch.Modified: + f.items[key] = e.Object + case watch.Deleted: + delete(f.items, key) + } + + if rand.Float64() < watchProbability { + f.broadcaster.Action(e.Type, e.Object) + } +} + +// List returns a list object, with its resource version set. +func (f *FakeControllerSource) List(options api.ListOptions) (runtime.Object, error) { + f.lock.RLock() + defer f.lock.RUnlock() + list := make([]runtime.Object, 0, len(f.items)) + for _, obj := range f.items { + // Must make a copy to allow clients to modify the object. + // Otherwise, if they make a change and write it back, they + // will inadvertently change our canonical copy (in + // addition to racing with other clients). + objCopy, err := api.Scheme.DeepCopy(obj) + if err != nil { + return nil, err + } + list = append(list, objCopy.(runtime.Object)) + } + listObj := &api.List{} + if err := meta.SetList(listObj, list); err != nil { + return nil, err + } + objMeta, err := api.ListMetaFor(listObj) + if err != nil { + return nil, err + } + resourceVersion := len(f.changes) + objMeta.ResourceVersion = strconv.Itoa(resourceVersion) + return listObj, nil +} + +// Watch returns a watch, which will be pre-populated with all changes +// after resourceVersion. +func (f *FakeControllerSource) Watch(options api.ListOptions) (watch.Interface, error) { + f.lock.RLock() + defer f.lock.RUnlock() + rc, err := strconv.Atoi(options.ResourceVersion) + if err != nil { + return nil, err + } + if rc < len(f.changes) { + changes := []watch.Event{} + for _, c := range f.changes[rc:] { + // Must make a copy to allow clients to modify the + // object. Otherwise, if they make a change and write + // it back, they will inadvertently change the our + // canonical copy (in addition to racing with other + // clients). + objCopy, err := api.Scheme.DeepCopy(c.Object) + if err != nil { + return nil, err + } + changes = append(changes, watch.Event{Type: c.Type, Object: objCopy.(runtime.Object)}) + } + return f.broadcaster.WatchWithPrefix(changes), nil + } else if rc > len(f.changes) { + return nil, errors.New("resource version in the future not supported by this fake") + } + return f.broadcaster.Watch(), nil +} + +// Shutdown closes the underlying broadcaster, waiting for events to be +// delivered. It's an error to call any method after calling shutdown. This is +// enforced by Shutdown() leaving f locked. +func (f *FakeControllerSource) Shutdown() { + f.lock.Lock() // Purposely no unlock. + f.broadcaster.Shutdown() +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/framework/fake_controller_source_test.go b/vendor/k8s.io/kubernetes/pkg/controller/framework/fake_controller_source_test.go new file mode 100644 index 000000000..01269ce64 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/framework/fake_controller_source_test.go @@ -0,0 +1,94 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 framework + +import ( + "sync" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/watch" +) + +// ensure the watch delivers the requested and only the requested items. +func consume(t *testing.T, w watch.Interface, rvs []string, done *sync.WaitGroup) { + defer done.Done() + for _, rv := range rvs { + got, ok := <-w.ResultChan() + if !ok { + t.Errorf("%#v: unexpected channel close, wanted %v", rvs, rv) + return + } + gotRV := got.Object.(*api.Pod).ObjectMeta.ResourceVersion + if e, a := rv, gotRV; e != a { + t.Errorf("wanted %v, got %v", e, a) + } else { + t.Logf("Got %v as expected", gotRV) + } + } + // We should not get anything else. + got, open := <-w.ResultChan() + if open { + t.Errorf("%#v: unwanted object %#v", rvs, got) + } +} + +func TestRCNumber(t *testing.T) { + pod := func(name string) *api.Pod { + return &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: name, + }, + } + } + + wg := &sync.WaitGroup{} + wg.Add(3) + + source := NewFakeControllerSource() + source.Add(pod("foo")) + source.Modify(pod("foo")) + source.Modify(pod("foo")) + + w, err := source.Watch(api.ListOptions{ResourceVersion: "1"}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + go consume(t, w, []string{"2", "3"}, wg) + + list, err := source.List(api.ListOptions{}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if e, a := "3", list.(*api.List).ResourceVersion; e != a { + t.Errorf("wanted %v, got %v", e, a) + } + + w2, err := source.Watch(api.ListOptions{ResourceVersion: "2"}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + go consume(t, w2, []string{"3"}, wg) + + w3, err := source.Watch(api.ListOptions{ResourceVersion: "3"}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + go consume(t, w3, []string{}, wg) + source.Shutdown() + wg.Wait() +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/gc/doc.go b/vendor/k8s.io/kubernetes/pkg/controller/gc/doc.go new file mode 100644 index 000000000..db08e7a36 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/gc/doc.go @@ -0,0 +1,24 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 gc contains a very simple pod "garbage collector" implementation, +// GCController, that runs in the controller manager. If the number of pods +// in terminated phases (right now either Failed or Succeeded) surpasses a +// configurable threshold, the controller will delete pods in terminated state +// until the system reaches the allowed threshold again. The GCController +// prioritizes pods to delete by sorting by creation timestamp and deleting the +// oldest objects first. The GCController will not delete non-terminated pods. +package gc diff --git a/vendor/k8s.io/kubernetes/pkg/controller/gc/gc_controller.go b/vendor/k8s.io/kubernetes/pkg/controller/gc/gc_controller.go new file mode 100644 index 000000000..bf09ae928 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/gc/gc_controller.go @@ -0,0 +1,125 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 gc + +import ( + "sort" + "sync" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/watch" + + "github.com/golang/glog" +) + +const ( + gcCheckPeriod = 20 * time.Second +) + +type GCController struct { + kubeClient clientset.Interface + podStore cache.StoreToPodLister + podStoreSyncer *framework.Controller + deletePod func(namespace, name string) error + threshold int +} + +func New(kubeClient clientset.Interface, resyncPeriod controller.ResyncPeriodFunc, threshold int) *GCController { + gcc := &GCController{ + kubeClient: kubeClient, + threshold: threshold, + deletePod: func(namespace, name string) error { + return kubeClient.Core().Pods(namespace).Delete(name, api.NewDeleteOptions(0)) + }, + } + + terminatedSelector := fields.ParseSelectorOrDie("status.phase!=" + string(api.PodPending) + ",status.phase!=" + string(api.PodRunning) + ",status.phase!=" + string(api.PodUnknown)) + + gcc.podStore.Store, gcc.podStoreSyncer = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + options.FieldSelector = terminatedSelector + return gcc.kubeClient.Core().Pods(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + options.FieldSelector = terminatedSelector + return gcc.kubeClient.Core().Pods(api.NamespaceAll).Watch(options) + }, + }, + &api.Pod{}, + resyncPeriod(), + framework.ResourceEventHandlerFuncs{}, + ) + return gcc +} + +func (gcc *GCController) Run(stop <-chan struct{}) { + go gcc.podStoreSyncer.Run(stop) + go wait.Until(gcc.gc, gcCheckPeriod, stop) + <-stop +} + +func (gcc *GCController) gc() { + terminatedPods, _ := gcc.podStore.List(labels.Everything()) + terminatedPodCount := len(terminatedPods) + sort.Sort(byCreationTimestamp(terminatedPods)) + + deleteCount := terminatedPodCount - gcc.threshold + + if deleteCount > terminatedPodCount { + deleteCount = terminatedPodCount + } + if deleteCount > 0 { + glog.Infof("garbage collecting %v pods", deleteCount) + } + + var wait sync.WaitGroup + for i := 0; i < deleteCount; i++ { + wait.Add(1) + go func(namespace string, name string) { + defer wait.Done() + if err := gcc.deletePod(namespace, name); err != nil { + // ignore not founds + defer utilruntime.HandleError(err) + } + }(terminatedPods[i].Namespace, terminatedPods[i].Name) + } + wait.Wait() +} + +// byCreationTimestamp sorts a list by creation timestamp, using their names as a tie breaker. +type byCreationTimestamp []*api.Pod + +func (o byCreationTimestamp) Len() int { return len(o) } +func (o byCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } + +func (o byCreationTimestamp) Less(i, j int) bool { + if o[i].CreationTimestamp.Equal(o[j].CreationTimestamp) { + return o[i].Name < o[j].Name + } + return o[i].CreationTimestamp.Before(o[j].CreationTimestamp) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/gc/gc_controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/gc/gc_controller_test.go new file mode 100644 index 000000000..e7c55e6f8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/gc/gc_controller_test.go @@ -0,0 +1,104 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 gc + +import ( + "sync" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/util/sets" +) + +func TestGC(t *testing.T) { + type nameToPhase struct { + name string + phase api.PodPhase + } + + testCases := []struct { + pods []nameToPhase + threshold int + deletedPodNames sets.String + }{ + { + pods: []nameToPhase{ + {name: "a", phase: api.PodFailed}, + {name: "b", phase: api.PodSucceeded}, + }, + threshold: 0, + deletedPodNames: sets.NewString("a", "b"), + }, + { + pods: []nameToPhase{ + {name: "a", phase: api.PodFailed}, + {name: "b", phase: api.PodSucceeded}, + }, + threshold: 1, + deletedPodNames: sets.NewString("a"), + }, + { + pods: []nameToPhase{ + {name: "a", phase: api.PodFailed}, + {name: "b", phase: api.PodSucceeded}, + }, + threshold: 5, + deletedPodNames: sets.NewString(), + }, + } + + for i, test := range testCases { + client := fake.NewSimpleClientset() + gcc := New(client, controller.NoResyncPeriodFunc, test.threshold) + deletedPodNames := make([]string, 0) + var lock sync.Mutex + gcc.deletePod = func(_, name string) error { + lock.Lock() + defer lock.Unlock() + deletedPodNames = append(deletedPodNames, name) + return nil + } + + creationTime := time.Unix(0, 0) + for _, pod := range test.pods { + creationTime = creationTime.Add(1 * time.Hour) + gcc.podStore.Store.Add(&api.Pod{ + ObjectMeta: api.ObjectMeta{Name: pod.name, CreationTimestamp: unversioned.Time{Time: creationTime}}, + Status: api.PodStatus{Phase: pod.phase}, + }) + } + + gcc.gc() + + pass := true + for _, pod := range deletedPodNames { + if !test.deletedPodNames.Has(pod) { + pass = false + } + } + if len(deletedPodNames) != len(test.deletedPodNames) { + pass = false + } + if !pass { + t.Errorf("[%v]pod's deleted expected and actual did not match.\n\texpected: %v\n\tactual: %v", i, test.deletedPodNames, deletedPodNames) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/job/controller.go b/vendor/k8s.io/kubernetes/pkg/controller/job/controller.go new file mode 100644 index 000000000..2a259303d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/job/controller.go @@ -0,0 +1,570 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 job + +import ( + "reflect" + "sort" + "sync" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/framework" + replicationcontroller "k8s.io/kubernetes/pkg/controller/replication" + "k8s.io/kubernetes/pkg/runtime" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/util/workqueue" + "k8s.io/kubernetes/pkg/watch" +) + +type JobController struct { + kubeClient clientset.Interface + podControl controller.PodControlInterface + + // To allow injection of updateJobStatus for testing. + updateHandler func(job *extensions.Job) error + syncHandler func(jobKey string) error + // podStoreSynced returns true if the pod store has been synced at least once. + // Added as a member to the struct to allow injection for testing. + podStoreSynced func() bool + + // A TTLCache of pod creates/deletes each rc expects to see + expectations controller.ControllerExpectationsInterface + + // A store of job, populated by the jobController + jobStore cache.StoreToJobLister + // Watches changes to all jobs + jobController *framework.Controller + + // A store of pods, populated by the podController + podStore cache.StoreToPodLister + // Watches changes to all pods + podController *framework.Controller + + // Jobs that need to be updated + queue *workqueue.Type + + recorder record.EventRecorder +} + +func NewJobController(kubeClient clientset.Interface, resyncPeriod controller.ResyncPeriodFunc) *JobController { + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(glog.Infof) + // TODO: remove the wrapper when every clients have moved to use the clientset. + eventBroadcaster.StartRecordingToSink(&unversionedcore.EventSinkImpl{Interface: kubeClient.Core().Events("")}) + + jm := &JobController{ + kubeClient: kubeClient, + podControl: controller.RealPodControl{ + KubeClient: kubeClient, + Recorder: eventBroadcaster.NewRecorder(api.EventSource{Component: "job-controller"}), + }, + expectations: controller.NewControllerExpectations(), + queue: workqueue.New(), + recorder: eventBroadcaster.NewRecorder(api.EventSource{Component: "job-controller"}), + } + + jm.jobStore.Store, jm.jobController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return jm.kubeClient.Extensions().Jobs(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return jm.kubeClient.Extensions().Jobs(api.NamespaceAll).Watch(options) + }, + }, + &extensions.Job{}, + // TODO: Can we have much longer period here? + replicationcontroller.FullControllerResyncPeriod, + framework.ResourceEventHandlerFuncs{ + AddFunc: jm.enqueueController, + UpdateFunc: func(old, cur interface{}) { + if job := cur.(*extensions.Job); !isJobFinished(job) { + jm.enqueueController(job) + } + }, + DeleteFunc: jm.enqueueController, + }, + ) + + jm.podStore.Store, jm.podController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return jm.kubeClient.Core().Pods(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return jm.kubeClient.Core().Pods(api.NamespaceAll).Watch(options) + }, + }, + &api.Pod{}, + resyncPeriod(), + framework.ResourceEventHandlerFuncs{ + AddFunc: jm.addPod, + UpdateFunc: jm.updatePod, + DeleteFunc: jm.deletePod, + }, + ) + + jm.updateHandler = jm.updateJobStatus + jm.syncHandler = jm.syncJob + jm.podStoreSynced = jm.podController.HasSynced + return jm +} + +// Run the main goroutine responsible for watching and syncing jobs. +func (jm *JobController) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + go jm.jobController.Run(stopCh) + go jm.podController.Run(stopCh) + for i := 0; i < workers; i++ { + go wait.Until(jm.worker, time.Second, stopCh) + } + <-stopCh + glog.Infof("Shutting down Job Manager") + jm.queue.ShutDown() +} + +// getPodJob returns the job managing the given pod. +func (jm *JobController) getPodJob(pod *api.Pod) *extensions.Job { + jobs, err := jm.jobStore.GetPodJobs(pod) + if err != nil { + glog.V(4).Infof("No jobs found for pod %v, job controller will avoid syncing", pod.Name) + return nil + } + if len(jobs) > 1 { + glog.Errorf("user error! more than one job is selecting pods with labels: %+v", pod.Labels) + sort.Sort(byCreationTimestamp(jobs)) + } + return &jobs[0] +} + +// When a pod is created, enqueue the controller that manages it and update it's expectations. +func (jm *JobController) addPod(obj interface{}) { + pod := obj.(*api.Pod) + if pod.DeletionTimestamp != nil { + // on a restart of the controller controller, it's possible a new pod shows up in a state that + // is already pending deletion. Prevent the pod from being a creation observation. + jm.deletePod(pod) + return + } + if job := jm.getPodJob(pod); job != nil { + jobKey, err := controller.KeyFunc(job) + if err != nil { + glog.Errorf("Couldn't get key for job %#v: %v", job, err) + return + } + jm.expectations.CreationObserved(jobKey) + jm.enqueueController(job) + } +} + +// When a pod is updated, figure out what job/s manage it and wake them up. +// If the labels of the pod have changed we need to awaken both the old +// and new job. old and cur must be *api.Pod types. +func (jm *JobController) updatePod(old, cur interface{}) { + if api.Semantic.DeepEqual(old, cur) { + // A periodic relist will send update events for all known pods. + return + } + curPod := cur.(*api.Pod) + if curPod.DeletionTimestamp != nil { + // when a pod is deleted gracefully it's deletion timestamp is first modified to reflect a grace period, + // and after such time has passed, the kubelet actually deletes it from the store. We receive an update + // for modification of the deletion timestamp and expect an job to create more pods asap, not wait + // until the kubelet actually deletes the pod. + jm.deletePod(curPod) + return + } + if job := jm.getPodJob(curPod); job != nil { + jm.enqueueController(job) + } + oldPod := old.(*api.Pod) + // Only need to get the old job if the labels changed. + if !reflect.DeepEqual(curPod.Labels, oldPod.Labels) { + // If the old and new job are the same, the first one that syncs + // will set expectations preventing any damage from the second. + if oldJob := jm.getPodJob(oldPod); oldJob != nil { + jm.enqueueController(oldJob) + } + } +} + +// When a pod is deleted, enqueue the job that manages the pod and update its expectations. +// obj could be an *api.Pod, or a DeletionFinalStateUnknown marker item. +func (jm *JobController) deletePod(obj interface{}) { + pod, ok := obj.(*api.Pod) + + // When a delete is dropped, the relist will notice a pod in the store not + // in the list, leading to the insertion of a tombstone object which contains + // the deleted key/value. Note that this value might be stale. If the pod + // changed labels the new job will not be woken up till the periodic resync. + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + glog.Errorf("Couldn't get object from tombstone %+v", obj) + return + } + pod, ok = tombstone.Obj.(*api.Pod) + if !ok { + glog.Errorf("Tombstone contained object that is not a pod %+v", obj) + return + } + } + if job := jm.getPodJob(pod); job != nil { + jobKey, err := controller.KeyFunc(job) + if err != nil { + glog.Errorf("Couldn't get key for job %#v: %v", job, err) + return + } + jm.expectations.DeletionObserved(jobKey) + jm.enqueueController(job) + } +} + +// obj could be an *extensions.Job, or a DeletionFinalStateUnknown marker item. +func (jm *JobController) enqueueController(obj interface{}) { + key, err := controller.KeyFunc(obj) + if err != nil { + glog.Errorf("Couldn't get key for object %+v: %v", obj, err) + return + } + + // TODO: Handle overlapping controllers better. Either disallow them at admission time or + // deterministically avoid syncing controllers that fight over pods. Currently, we only + // ensure that the same controller is synced for a given pod. When we periodically relist + // all controllers there will still be some replica instability. One way to handle this is + // by querying the store for all controllers that this rc overlaps, as well as all + // controllers that overlap this rc, and sorting them. + jm.queue.Add(key) +} + +// worker runs a worker thread that just dequeues items, processes them, and marks them done. +// It enforces that the syncHandler is never invoked concurrently with the same key. +func (jm *JobController) worker() { + for { + func() { + key, quit := jm.queue.Get() + if quit { + return + } + defer jm.queue.Done(key) + err := jm.syncHandler(key.(string)) + if err != nil { + glog.Errorf("Error syncing job: %v", err) + } + }() + } +} + +// syncJob will sync the job with the given key if it has had its expectations fulfilled, meaning +// it did not expect to see any more of its pods created or deleted. This function is not meant to be invoked +// concurrently with the same key. +func (jm *JobController) syncJob(key string) error { + startTime := time.Now() + defer func() { + glog.V(4).Infof("Finished syncing job %q (%v)", key, time.Now().Sub(startTime)) + }() + + if !jm.podStoreSynced() { + // Sleep so we give the pod reflector goroutine a chance to run. + time.Sleep(replicationcontroller.PodStoreSyncedPollPeriod) + glog.V(4).Infof("Waiting for pods controller to sync, requeuing job %v", key) + jm.queue.Add(key) + return nil + } + + obj, exists, err := jm.jobStore.Store.GetByKey(key) + if !exists { + glog.V(4).Infof("Job has been deleted: %v", key) + jm.expectations.DeleteExpectations(key) + return nil + } + if err != nil { + glog.Errorf("Unable to retrieve job %v from store: %v", key, err) + jm.queue.Add(key) + return err + } + job := *obj.(*extensions.Job) + + // Check the expectations of the job before counting active pods, otherwise a new pod can sneak in + // and update the expectations after we've retrieved active pods from the store. If a new pod enters + // the store after we've checked the expectation, the job sync is just deferred till the next relist. + jobKey, err := controller.KeyFunc(&job) + if err != nil { + glog.Errorf("Couldn't get key for job %#v: %v", job, err) + return err + } + jobNeedsSync := jm.expectations.SatisfiedExpectations(jobKey) + selector, _ := unversioned.LabelSelectorAsSelector(job.Spec.Selector) + podList, err := jm.podStore.Pods(job.Namespace).List(selector) + if err != nil { + glog.Errorf("Error getting pods for job %q: %v", key, err) + jm.queue.Add(key) + return err + } + + activePods := controller.FilterActivePods(podList.Items) + active := len(activePods) + succeeded, failed := getStatus(podList.Items) + conditions := len(job.Status.Conditions) + if job.Status.StartTime == nil { + now := unversioned.Now() + job.Status.StartTime = &now + } + // if job was finished previously, we don't want to redo the termination + if isJobFinished(&job) { + return nil + } + if pastActiveDeadline(&job) { + // TODO: below code should be replaced with pod termination resulting in + // pod failures, rather than killing pods. Unfortunately none such solution + // exists ATM. There's an open discussion in the topic in + // https://github.com/kubernetes/kubernetes/issues/14602 which might give + // some sort of solution to above problem. + // kill remaining active pods + wait := sync.WaitGroup{} + wait.Add(active) + for i := 0; i < active; i++ { + go func(ix int) { + defer wait.Done() + if err := jm.podControl.DeletePod(job.Namespace, activePods[ix].Name, &job); err != nil { + defer utilruntime.HandleError(err) + } + }(i) + } + wait.Wait() + // update status values accordingly + failed += active + active = 0 + job.Status.Conditions = append(job.Status.Conditions, newCondition(extensions.JobFailed, "DeadlineExceeded", "Job was active longer than specified deadline")) + jm.recorder.Event(&job, api.EventTypeNormal, "DeadlineExceeded", "Job was active longer than specified deadline") + } else { + if jobNeedsSync { + active = jm.manageJob(activePods, succeeded, &job) + } + completions := succeeded + complete := false + if job.Spec.Completions == nil { + // This type of job is complete when any pod exits with success. + // Each pod is capable of + // determining whether or not the entire Job is done. Subsequent pods are + // not expected to fail, but if they do, the failure is ignored. Once any + // pod succeeds, the controller waits for remaining pods to finish, and + // then the job is complete. + if succeeded > 0 && active == 0 { + complete = true + } + } else { + // Job specifies a number of completions. This type of job signals + // success by having that number of successes. Since we do not + // start more pods than there are remaining completions, there should + // not be any remaining active pods once this count is reached. + if completions >= *job.Spec.Completions { + complete = true + if active > 0 { + jm.recorder.Event(&job, api.EventTypeWarning, "TooManyActivePods", "Too many active pods running after completion count reached") + } + if completions > *job.Spec.Completions { + jm.recorder.Event(&job, api.EventTypeWarning, "TooManySucceededPods", "Too many succeeded pods running after completion count reached") + } + } + } + if complete { + job.Status.Conditions = append(job.Status.Conditions, newCondition(extensions.JobComplete, "", "")) + now := unversioned.Now() + job.Status.CompletionTime = &now + } + } + + // no need to update the job if the status hasn't changed since last time + if job.Status.Active != active || job.Status.Succeeded != succeeded || job.Status.Failed != failed || len(job.Status.Conditions) != conditions { + job.Status.Active = active + job.Status.Succeeded = succeeded + job.Status.Failed = failed + + if err := jm.updateHandler(&job); err != nil { + glog.Errorf("Failed to update job %v, requeuing. Error: %v", job.Name, err) + jm.enqueueController(&job) + } + } + return nil +} + +// pastActiveDeadline checks if job has ActiveDeadlineSeconds field set and if it is exceeded. +func pastActiveDeadline(job *extensions.Job) bool { + if job.Spec.ActiveDeadlineSeconds == nil || job.Status.StartTime == nil { + return false + } + now := unversioned.Now() + start := job.Status.StartTime.Time + duration := now.Time.Sub(start) + allowedDuration := time.Duration(*job.Spec.ActiveDeadlineSeconds) * time.Second + return duration >= allowedDuration +} + +func newCondition(conditionType extensions.JobConditionType, reason, message string) extensions.JobCondition { + return extensions.JobCondition{ + Type: conditionType, + Status: api.ConditionTrue, + LastProbeTime: unversioned.Now(), + LastTransitionTime: unversioned.Now(), + Reason: reason, + Message: message, + } +} + +// getStatus returns no of succeeded and failed pods running a job +func getStatus(pods []api.Pod) (succeeded, failed int) { + succeeded = filterPods(pods, api.PodSucceeded) + failed = filterPods(pods, api.PodFailed) + return +} + +// manageJob is the core method responsible for managing the number of running +// pods according to what is specified in the job.Spec. +func (jm *JobController) manageJob(activePods []*api.Pod, succeeded int, job *extensions.Job) int { + var activeLock sync.Mutex + active := len(activePods) + parallelism := *job.Spec.Parallelism + jobKey, err := controller.KeyFunc(job) + if err != nil { + glog.Errorf("Couldn't get key for job %#v: %v", job, err) + return 0 + } + + if active > parallelism { + diff := active - parallelism + jm.expectations.ExpectDeletions(jobKey, diff) + glog.V(4).Infof("Too many pods running job %q, need %d, deleting %d", jobKey, parallelism, diff) + // Sort the pods in the order such that not-ready < ready, unscheduled + // < scheduled, and pending < running. This ensures that we delete pods + // in the earlier stages whenever possible. + sort.Sort(controller.ActivePods(activePods)) + + active -= diff + wait := sync.WaitGroup{} + wait.Add(diff) + for i := 0; i < diff; i++ { + go func(ix int) { + defer wait.Done() + if err := jm.podControl.DeletePod(job.Namespace, activePods[ix].Name, job); err != nil { + defer utilruntime.HandleError(err) + // Decrement the expected number of deletes because the informer won't observe this deletion + jm.expectations.DeletionObserved(jobKey) + activeLock.Lock() + active++ + activeLock.Unlock() + } + }(i) + } + wait.Wait() + + } else if active < parallelism { + wantActive := 0 + if job.Spec.Completions == nil { + // Job does not specify a number of completions. Therefore, number active + // should be equal to parallelism, unless the job has seen at least + // once success, in which leave whatever is running, running. + if succeeded > 0 { + wantActive = active + } else { + wantActive = parallelism + } + } else { + // Job specifies a specific number of completions. Therefore, number + // active should not ever exceed number of remaining completions. + wantActive = *job.Spec.Completions - succeeded + if wantActive > parallelism { + wantActive = parallelism + } + } + diff := wantActive - active + if diff < 0 { + glog.Errorf("More active than wanted: job %q, want %d, have %d", jobKey, wantActive, active) + diff = 0 + } + jm.expectations.ExpectCreations(jobKey, diff) + glog.V(4).Infof("Too few pods running job %q, need %d, creating %d", jobKey, wantActive, diff) + + active += diff + wait := sync.WaitGroup{} + wait.Add(diff) + for i := 0; i < diff; i++ { + go func() { + defer wait.Done() + if err := jm.podControl.CreatePods(job.Namespace, &job.Spec.Template, job); err != nil { + defer utilruntime.HandleError(err) + // Decrement the expected number of creates because the informer won't observe this pod + jm.expectations.CreationObserved(jobKey) + activeLock.Lock() + active-- + activeLock.Unlock() + } + }() + } + wait.Wait() + } + + return active +} + +func (jm *JobController) updateJobStatus(job *extensions.Job) error { + _, err := jm.kubeClient.Extensions().Jobs(job.Namespace).UpdateStatus(job) + return err +} + +// filterPods returns pods based on their phase. +func filterPods(pods []api.Pod, phase api.PodPhase) int { + result := 0 + for i := range pods { + if phase == pods[i].Status.Phase { + result++ + } + } + return result +} + +func isJobFinished(j *extensions.Job) bool { + for _, c := range j.Status.Conditions { + if (c.Type == extensions.JobComplete || c.Type == extensions.JobFailed) && c.Status == api.ConditionTrue { + return true + } + } + return false +} + +// byCreationTimestamp sorts a list by creation timestamp, using their names as a tie breaker. +type byCreationTimestamp []extensions.Job + +func (o byCreationTimestamp) Len() int { return len(o) } +func (o byCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } + +func (o byCreationTimestamp) Less(i, j int) bool { + if o[i].CreationTimestamp.Equal(o[j].CreationTimestamp) { + return o[i].Name < o[j].Name + } + return o[i].CreationTimestamp.Before(o[j].CreationTimestamp) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/job/controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/job/controller_test.go new file mode 100644 index 000000000..63235cb38 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/job/controller_test.go @@ -0,0 +1,703 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 job + +import ( + "fmt" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/client/unversioned/testclient" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/util/rand" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/watch" +) + +var alwaysReady = func() bool { return true } + +func newJob(parallelism, completions int) *extensions.Job { + j := &extensions.Job{ + ObjectMeta: api.ObjectMeta{ + Name: "foobar", + Namespace: api.NamespaceDefault, + }, + Spec: extensions.JobSpec{ + Selector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Image: "foo/bar"}, + }, + }, + }, + }, + } + // Special case: -1 for either completions or parallelism means leave nil (negative is not allowed + // in practice by validation. + if completions >= 0 { + j.Spec.Completions = &completions + } else { + j.Spec.Completions = nil + } + if parallelism >= 0 { + j.Spec.Parallelism = ¶llelism + } else { + j.Spec.Parallelism = nil + } + return j +} + +func getKey(job *extensions.Job, t *testing.T) string { + if key, err := controller.KeyFunc(job); err != nil { + t.Errorf("Unexpected error getting key for job %v: %v", job.Name, err) + return "" + } else { + return key + } +} + +// create count pods with the given phase for the given job +func newPodList(count int, status api.PodPhase, job *extensions.Job) []api.Pod { + pods := []api.Pod{} + for i := 0; i < count; i++ { + newPod := api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: fmt.Sprintf("pod-%v", rand.String(10)), + Labels: job.Spec.Selector.MatchLabels, + Namespace: job.Namespace, + }, + Status: api.PodStatus{Phase: status}, + } + pods = append(pods, newPod) + } + return pods +} + +func TestControllerSyncJob(t *testing.T) { + testCases := map[string]struct { + // job setup + parallelism int + completions int + + // pod setup + podControllerError error + activePods int + succeededPods int + failedPods int + + // expectations + expectedCreations int + expectedDeletions int + expectedActive int + expectedSucceeded int + expectedFailed int + expectedComplete bool + }{ + "job start": { + 2, 5, + nil, 0, 0, 0, + 2, 0, 2, 0, 0, false, + }, + "WQ job start": { + 2, -1, + nil, 0, 0, 0, + 2, 0, 2, 0, 0, false, + }, + "correct # of pods": { + 2, 5, + nil, 2, 0, 0, + 0, 0, 2, 0, 0, false, + }, + "WQ job: correct # of pods": { + 2, -1, + nil, 2, 0, 0, + 0, 0, 2, 0, 0, false, + }, + "too few active pods": { + 2, 5, + nil, 1, 1, 0, + 1, 0, 2, 1, 0, false, + }, + "too few active pods with a dynamic job": { + 2, -1, + nil, 1, 0, 0, + 1, 0, 2, 0, 0, false, + }, + "too few active pods, with controller error": { + 2, 5, + fmt.Errorf("Fake error"), 1, 1, 0, + 0, 0, 1, 1, 0, false, + }, + "too many active pods": { + 2, 5, + nil, 3, 0, 0, + 0, 1, 2, 0, 0, false, + }, + "too many active pods, with controller error": { + 2, 5, + fmt.Errorf("Fake error"), 3, 0, 0, + 0, 0, 3, 0, 0, false, + }, + "failed pod": { + 2, 5, + nil, 1, 1, 1, + 1, 0, 2, 1, 1, false, + }, + "job finish": { + 2, 5, + nil, 0, 5, 0, + 0, 0, 0, 5, 0, true, + }, + "WQ job finishing": { + 2, -1, + nil, 1, 1, 0, + 0, 0, 1, 1, 0, false, + }, + "WQ job all finished": { + 2, -1, + nil, 0, 2, 0, + 0, 0, 0, 2, 0, true, + }, + "WQ job all finished despite one failure": { + 2, -1, + nil, 0, 1, 1, + 0, 0, 0, 1, 1, true, + }, + "more active pods than completions": { + 2, 5, + nil, 10, 0, 0, + 0, 8, 2, 0, 0, false, + }, + "status change": { + 2, 5, + nil, 2, 2, 0, + 0, 0, 2, 2, 0, false, + }, + } + + for name, tc := range testCases { + // job manager setup + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewJobController(clientset, controller.NoResyncPeriodFunc) + fakePodControl := controller.FakePodControl{Err: tc.podControllerError} + manager.podControl = &fakePodControl + manager.podStoreSynced = alwaysReady + var actual *extensions.Job + manager.updateHandler = func(job *extensions.Job) error { + actual = job + return nil + } + + // job & pods setup + job := newJob(tc.parallelism, tc.completions) + manager.jobStore.Store.Add(job) + for _, pod := range newPodList(tc.activePods, api.PodRunning, job) { + manager.podStore.Store.Add(&pod) + } + for _, pod := range newPodList(tc.succeededPods, api.PodSucceeded, job) { + manager.podStore.Store.Add(&pod) + } + for _, pod := range newPodList(tc.failedPods, api.PodFailed, job) { + manager.podStore.Store.Add(&pod) + } + + // run + err := manager.syncJob(getKey(job, t)) + if err != nil { + t.Errorf("%s: unexpected error when syncing jobs %v", name, err) + } + + // validate created/deleted pods + if len(fakePodControl.Templates) != tc.expectedCreations { + t.Errorf("%s: unexpected number of creates. Expected %d, saw %d\n", name, tc.expectedCreations, len(fakePodControl.Templates)) + } + if len(fakePodControl.DeletePodName) != tc.expectedDeletions { + t.Errorf("%s: unexpected number of deletes. Expected %d, saw %d\n", name, tc.expectedDeletions, len(fakePodControl.DeletePodName)) + } + // validate status + if actual.Status.Active != tc.expectedActive { + t.Errorf("%s: unexpected number of active pods. Expected %d, saw %d\n", name, tc.expectedActive, actual.Status.Active) + } + if actual.Status.Succeeded != tc.expectedSucceeded { + t.Errorf("%s: unexpected number of succeeded pods. Expected %d, saw %d\n", name, tc.expectedSucceeded, actual.Status.Succeeded) + } + if actual.Status.Failed != tc.expectedFailed { + t.Errorf("%s: unexpected number of failed pods. Expected %d, saw %d\n", name, tc.expectedFailed, actual.Status.Failed) + } + if actual.Status.StartTime == nil { + t.Errorf("%s: .status.startTime was not set", name) + } + // validate conditions + if tc.expectedComplete && !getCondition(actual, extensions.JobComplete) { + t.Errorf("%s: expected completion condition. Got %#v", name, actual.Status.Conditions) + } + } +} + +func TestSyncJobPastDeadline(t *testing.T) { + testCases := map[string]struct { + // job setup + parallelism int + completions int + activeDeadlineSeconds int64 + startTime int64 + + // pod setup + activePods int + succeededPods int + failedPods int + + // expectations + expectedDeletions int + expectedActive int + expectedSucceeded int + expectedFailed int + }{ + "activeDeadlineSeconds less than single pod execution": { + 1, 1, 10, 15, + 1, 0, 0, + 1, 0, 0, 1, + }, + "activeDeadlineSeconds bigger than single pod execution": { + 1, 2, 10, 15, + 1, 1, 0, + 1, 0, 1, 1, + }, + "activeDeadlineSeconds times-out before any pod starts": { + 1, 1, 10, 10, + 0, 0, 0, + 0, 0, 0, 0, + }, + } + + for name, tc := range testCases { + // job manager setup + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewJobController(clientset, controller.NoResyncPeriodFunc) + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + manager.podStoreSynced = alwaysReady + var actual *extensions.Job + manager.updateHandler = func(job *extensions.Job) error { + actual = job + return nil + } + + // job & pods setup + job := newJob(tc.parallelism, tc.completions) + job.Spec.ActiveDeadlineSeconds = &tc.activeDeadlineSeconds + start := unversioned.Unix(unversioned.Now().Time.Unix()-tc.startTime, 0) + job.Status.StartTime = &start + manager.jobStore.Store.Add(job) + for _, pod := range newPodList(tc.activePods, api.PodRunning, job) { + manager.podStore.Store.Add(&pod) + } + for _, pod := range newPodList(tc.succeededPods, api.PodSucceeded, job) { + manager.podStore.Store.Add(&pod) + } + for _, pod := range newPodList(tc.failedPods, api.PodFailed, job) { + manager.podStore.Store.Add(&pod) + } + + // run + err := manager.syncJob(getKey(job, t)) + if err != nil { + t.Errorf("%s: unexpected error when syncing jobs %v", name, err) + } + + // validate created/deleted pods + if len(fakePodControl.Templates) != 0 { + t.Errorf("%s: unexpected number of creates. Expected 0, saw %d\n", name, len(fakePodControl.Templates)) + } + if len(fakePodControl.DeletePodName) != tc.expectedDeletions { + t.Errorf("%s: unexpected number of deletes. Expected %d, saw %d\n", name, tc.expectedDeletions, len(fakePodControl.DeletePodName)) + } + // validate status + if actual.Status.Active != tc.expectedActive { + t.Errorf("%s: unexpected number of active pods. Expected %d, saw %d\n", name, tc.expectedActive, actual.Status.Active) + } + if actual.Status.Succeeded != tc.expectedSucceeded { + t.Errorf("%s: unexpected number of succeeded pods. Expected %d, saw %d\n", name, tc.expectedSucceeded, actual.Status.Succeeded) + } + if actual.Status.Failed != tc.expectedFailed { + t.Errorf("%s: unexpected number of failed pods. Expected %d, saw %d\n", name, tc.expectedFailed, actual.Status.Failed) + } + if actual.Status.StartTime == nil { + t.Errorf("%s: .status.startTime was not set", name) + } + // validate conditions + if !getCondition(actual, extensions.JobFailed) { + t.Errorf("%s: expected fail condition. Got %#v", name, actual.Status.Conditions) + } + } +} + +func getCondition(job *extensions.Job, condition extensions.JobConditionType) bool { + for _, v := range job.Status.Conditions { + if v.Type == condition && v.Status == api.ConditionTrue { + return true + } + } + return false +} + +func TestSyncPastDeadlineJobFinished(t *testing.T) { + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewJobController(clientset, controller.NoResyncPeriodFunc) + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + manager.podStoreSynced = alwaysReady + var actual *extensions.Job + manager.updateHandler = func(job *extensions.Job) error { + actual = job + return nil + } + + job := newJob(1, 1) + activeDeadlineSeconds := int64(10) + job.Spec.ActiveDeadlineSeconds = &activeDeadlineSeconds + start := unversioned.Unix(unversioned.Now().Time.Unix()-15, 0) + job.Status.StartTime = &start + job.Status.Conditions = append(job.Status.Conditions, newCondition(extensions.JobFailed, "DeadlineExceeded", "Job was active longer than specified deadline")) + manager.jobStore.Store.Add(job) + err := manager.syncJob(getKey(job, t)) + if err != nil { + t.Errorf("Unexpected error when syncing jobs %v", err) + } + if len(fakePodControl.Templates) != 0 { + t.Errorf("Unexpected number of creates. Expected %d, saw %d\n", 0, len(fakePodControl.Templates)) + } + if len(fakePodControl.DeletePodName) != 0 { + t.Errorf("Unexpected number of deletes. Expected %d, saw %d\n", 0, len(fakePodControl.DeletePodName)) + } + if actual != nil { + t.Error("Unexpected job modification") + } +} + +func TestSyncJobComplete(t *testing.T) { + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewJobController(clientset, controller.NoResyncPeriodFunc) + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + manager.podStoreSynced = alwaysReady + + job := newJob(1, 1) + job.Status.Conditions = append(job.Status.Conditions, newCondition(extensions.JobComplete, "", "")) + manager.jobStore.Store.Add(job) + err := manager.syncJob(getKey(job, t)) + if err != nil { + t.Fatalf("Unexpected error when syncing jobs %v", err) + } + uncastJob, _, err := manager.jobStore.Store.Get(job) + if err != nil { + t.Fatalf("Unexpected error when trying to get job from the store: %v", err) + } + actual := uncastJob.(*extensions.Job) + // Verify that after syncing a complete job, the conditions are the same. + if got, expected := len(actual.Status.Conditions), 1; got != expected { + t.Fatalf("Unexpected job status conditions amount; expected %d, got %d", expected, got) + } +} + +func TestSyncJobDeleted(t *testing.T) { + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewJobController(clientset, controller.NoResyncPeriodFunc) + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + manager.podStoreSynced = alwaysReady + manager.updateHandler = func(job *extensions.Job) error { return nil } + job := newJob(2, 2) + err := manager.syncJob(getKey(job, t)) + if err != nil { + t.Errorf("Unexpected error when syncing jobs %v", err) + } + if len(fakePodControl.Templates) != 0 { + t.Errorf("Unexpected number of creates. Expected %d, saw %d\n", 0, len(fakePodControl.Templates)) + } + if len(fakePodControl.DeletePodName) != 0 { + t.Errorf("Unexpected number of deletes. Expected %d, saw %d\n", 0, len(fakePodControl.DeletePodName)) + } +} + +func TestSyncJobUpdateRequeue(t *testing.T) { + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewJobController(clientset, controller.NoResyncPeriodFunc) + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + manager.podStoreSynced = alwaysReady + manager.updateHandler = func(job *extensions.Job) error { return fmt.Errorf("Fake error") } + job := newJob(2, 2) + manager.jobStore.Store.Add(job) + err := manager.syncJob(getKey(job, t)) + if err != nil { + t.Errorf("Unxpected error when syncing jobs, got %v", err) + } + t.Log("Waiting for a job in the queue") + key, _ := manager.queue.Get() + expectedKey := getKey(job, t) + if key != expectedKey { + t.Errorf("Expected requeue of job with key %s got %s", expectedKey, key) + } +} + +func TestJobPodLookup(t *testing.T) { + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewJobController(clientset, controller.NoResyncPeriodFunc) + manager.podStoreSynced = alwaysReady + testCases := []struct { + job *extensions.Job + pod *api.Pod + + expectedName string + }{ + // pods without labels don't match any job + { + job: &extensions.Job{ + ObjectMeta: api.ObjectMeta{Name: "basic"}, + }, + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo1", Namespace: api.NamespaceAll}, + }, + expectedName: "", + }, + // matching labels, different namespace + { + job: &extensions.Job{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: extensions.JobSpec{ + Selector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{"foo": "bar"}, + }, + }, + }, + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo2", + Namespace: "ns", + Labels: map[string]string{"foo": "bar"}, + }, + }, + expectedName: "", + }, + // matching ns and labels returns + { + job: &extensions.Job{ + ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "ns"}, + Spec: extensions.JobSpec{ + Selector: &unversioned.LabelSelector{ + MatchExpressions: []unversioned.LabelSelectorRequirement{ + { + Key: "foo", + Operator: unversioned.LabelSelectorOpIn, + Values: []string{"bar"}, + }, + }, + }, + }, + }, + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo3", + Namespace: "ns", + Labels: map[string]string{"foo": "bar"}, + }, + }, + expectedName: "bar", + }, + } + for _, tc := range testCases { + manager.jobStore.Add(tc.job) + if job := manager.getPodJob(tc.pod); job != nil { + if tc.expectedName != job.Name { + t.Errorf("Got job %+v expected %+v", job.Name, tc.expectedName) + } + } else if tc.expectedName != "" { + t.Errorf("Expected a job %v pod %v, found none", tc.expectedName, tc.pod.Name) + } + } +} + +type FakeJobExpectations struct { + *controller.ControllerExpectations + satisfied bool + expSatisfied func() +} + +func (fe FakeJobExpectations) SatisfiedExpectations(controllerKey string) bool { + fe.expSatisfied() + return fe.satisfied +} + +// TestSyncJobExpectations tests that a pod cannot sneak in between counting active pods +// and checking expectations. +func TestSyncJobExpectations(t *testing.T) { + clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewJobController(clientset, controller.NoResyncPeriodFunc) + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + manager.podStoreSynced = alwaysReady + manager.updateHandler = func(job *extensions.Job) error { return nil } + + job := newJob(2, 2) + manager.jobStore.Store.Add(job) + pods := newPodList(2, api.PodPending, job) + manager.podStore.Store.Add(&pods[0]) + + manager.expectations = FakeJobExpectations{ + controller.NewControllerExpectations(), true, func() { + // If we check active pods before checking expectataions, the job + // will create a new replica because it doesn't see this pod, but + // has fulfilled its expectations. + manager.podStore.Store.Add(&pods[1]) + }, + } + manager.syncJob(getKey(job, t)) + if len(fakePodControl.Templates) != 0 { + t.Errorf("Unexpected number of creates. Expected %d, saw %d\n", 0, len(fakePodControl.Templates)) + } + if len(fakePodControl.DeletePodName) != 0 { + t.Errorf("Unexpected number of deletes. Expected %d, saw %d\n", 0, len(fakePodControl.DeletePodName)) + } +} + +type FakeWatcher struct { + w *watch.FakeWatcher + *testclient.Fake +} + +func TestWatchJobs(t *testing.T) { + clientset := fake.NewSimpleClientset() + fakeWatch := watch.NewFake() + clientset.PrependWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) + manager := NewJobController(clientset, controller.NoResyncPeriodFunc) + manager.podStoreSynced = alwaysReady + + var testJob extensions.Job + received := make(chan struct{}) + + // The update sent through the fakeWatcher should make its way into the workqueue, + // and eventually into the syncHandler. + manager.syncHandler = func(key string) error { + + obj, exists, err := manager.jobStore.Store.GetByKey(key) + if !exists || err != nil { + t.Errorf("Expected to find job under key %v", key) + } + job := *obj.(*extensions.Job) + if !api.Semantic.DeepDerivative(job, testJob) { + t.Errorf("Expected %#v, but got %#v", testJob, job) + } + close(received) + return nil + } + // Start only the job watcher and the workqueue, send a watch event, + // and make sure it hits the sync method. + stopCh := make(chan struct{}) + defer close(stopCh) + go manager.jobController.Run(stopCh) + go wait.Until(manager.worker, 10*time.Millisecond, stopCh) + + // We're sending new job to see if it reaches syncHandler. + testJob.Name = "foo" + fakeWatch.Add(&testJob) + t.Log("Waiting for job to reach syncHandler") + <-received +} + +func TestIsJobFinished(t *testing.T) { + job := &extensions.Job{ + Status: extensions.JobStatus{ + Conditions: []extensions.JobCondition{{ + Type: extensions.JobComplete, + Status: api.ConditionTrue, + }}, + }, + } + + if !isJobFinished(job) { + t.Error("Job was expected to be finished") + } + + job.Status.Conditions[0].Status = api.ConditionFalse + if isJobFinished(job) { + t.Error("Job was not expected to be finished") + } + + job.Status.Conditions[0].Status = api.ConditionUnknown + if isJobFinished(job) { + t.Error("Job was not expected to be finished") + } +} + +func TestWatchPods(t *testing.T) { + clientset := fake.NewSimpleClientset() + fakeWatch := watch.NewFake() + clientset.PrependWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) + manager := NewJobController(clientset, controller.NoResyncPeriodFunc) + manager.podStoreSynced = alwaysReady + + // Put one job and one pod into the store + testJob := newJob(2, 2) + manager.jobStore.Store.Add(testJob) + received := make(chan struct{}) + // The pod update sent through the fakeWatcher should figure out the managing job and + // send it into the syncHandler. + manager.syncHandler = func(key string) error { + + obj, exists, err := manager.jobStore.Store.GetByKey(key) + if !exists || err != nil { + t.Errorf("Expected to find job under key %v", key) + } + job := obj.(*extensions.Job) + if !api.Semantic.DeepDerivative(job, testJob) { + t.Errorf("\nExpected %#v,\nbut got %#v", testJob, job) + } + close(received) + return nil + } + // Start only the pod watcher and the workqueue, send a watch event, + // and make sure it hits the sync method for the right job. + stopCh := make(chan struct{}) + defer close(stopCh) + go manager.podController.Run(stopCh) + go wait.Until(manager.worker, 10*time.Millisecond, stopCh) + + pods := newPodList(1, api.PodRunning, testJob) + testPod := pods[0] + testPod.Status.Phase = api.PodFailed + fakeWatch.Add(&testPod) + + t.Log("Waiting for pod to reach syncHandler") + <-received +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/job/doc.go b/vendor/k8s.io/kubernetes/pkg/controller/job/doc.go new file mode 100644 index 000000000..9c569bfc0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/job/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 job contains logic for watching and synchronizing jobs. +package job diff --git a/vendor/k8s.io/kubernetes/pkg/controller/lookup_cache.go b/vendor/k8s.io/kubernetes/pkg/controller/lookup_cache.go new file mode 100644 index 000000000..5d82908be --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/lookup_cache.go @@ -0,0 +1,90 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 controller + +import ( + "hash/adler32" + "sync" + + "github.com/golang/groupcache/lru" + "k8s.io/kubernetes/pkg/api/meta" + hashutil "k8s.io/kubernetes/pkg/util/hash" +) + +type objectWithMeta interface { + meta.Object +} + +// keyFunc returns the key of an object, which is used to look up in the cache for it's matching object. +// Since we match objects by namespace and Labels/Selector, so if two objects have the same namespace and labels, +// they will have the same key. +func keyFunc(obj objectWithMeta) uint64 { + hash := adler32.New() + hashutil.DeepHashObject(hash, &equivalenceLabelObj{ + namespace: obj.GetNamespace(), + labels: obj.GetLabels(), + }) + return uint64(hash.Sum32()) +} + +type equivalenceLabelObj struct { + namespace string + labels map[string]string +} + +// MatchingCache save label and selector matching relationship +type MatchingCache struct { + mutex sync.RWMutex + cache *lru.Cache +} + +// NewMatchingCache return a NewMatchingCache, which save label and selector matching relationship. +func NewMatchingCache(maxCacheEntries int) *MatchingCache { + return &MatchingCache{ + cache: lru.New(maxCacheEntries), + } +} + +// Add will add matching information to the cache. +func (c *MatchingCache) Add(labelObj objectWithMeta, selectorObj objectWithMeta) { + key := keyFunc(labelObj) + c.mutex.Lock() + defer c.mutex.Unlock() + c.cache.Add(key, selectorObj) +} + +// GetMatchingObject lookup the matching object for a given object. +// Note: the cache information may be invalid since the controller may be deleted or updated, +// we need check in the external request to ensure the cache data is not dirty. +func (c *MatchingCache) GetMatchingObject(labelObj objectWithMeta) (controller interface{}, exists bool) { + key := keyFunc(labelObj) + c.mutex.Lock() + defer c.mutex.Unlock() + return c.cache.Get(key) +} + +// Update update the cached matching information. +func (c *MatchingCache) Update(labelObj objectWithMeta, selectorObj objectWithMeta) { + c.Add(labelObj, selectorObj) +} + +// InvalidateAll invalidate the whole cache. +func (c *MatchingCache) InvalidateAll() { + c.mutex.Lock() + defer c.mutex.Unlock() + c.cache = lru.New(c.cache.MaxEntries) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/namespace/doc.go b/vendor/k8s.io/kubernetes/pkg/controller/namespace/doc.go new file mode 100644 index 000000000..fea657af5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/namespace/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// namespace contains a controller that handles namespace lifecycle +package namespace diff --git a/vendor/k8s.io/kubernetes/pkg/controller/namespace/namespace_controller.go b/vendor/k8s.io/kubernetes/pkg/controller/namespace/namespace_controller.go new file mode 100644 index 000000000..a1c6d4aac --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/namespace/namespace_controller.go @@ -0,0 +1,175 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 namespace + +import ( + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/typed/dynamic" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/runtime" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/util/workqueue" + "k8s.io/kubernetes/pkg/watch" + + "github.com/golang/glog" +) + +// NamespaceController is responsible for performing actions dependent upon a namespace phase +type NamespaceController struct { + // client that purges namespace content, must have list/delete privileges on all content + kubeClient clientset.Interface + // clientPool manages a pool of dynamic clients + clientPool dynamic.ClientPool + // store that holds the namespaces + store cache.Store + // controller that observes the namespaces + controller *framework.Controller + // namespaces that have been queued up for processing by workers + queue *workqueue.Type + // list of preferred group versions and their corresponding resource set for namespace deletion + groupVersionResources []unversioned.GroupVersionResource + // opCache is a cache to remember if a particular operation is not supported to aid dynamic client. + opCache operationNotSupportedCache + // finalizerToken is the finalizer token managed by this controller + finalizerToken api.FinalizerName +} + +// NewNamespaceController creates a new NamespaceController +func NewNamespaceController( + kubeClient clientset.Interface, + clientPool dynamic.ClientPool, + groupVersionResources []unversioned.GroupVersionResource, + resyncPeriod time.Duration, + finalizerToken api.FinalizerName) *NamespaceController { + // create the controller so we can inject the enqueue function + namespaceController := &NamespaceController{ + kubeClient: kubeClient, + clientPool: clientPool, + queue: workqueue.New(), + groupVersionResources: groupVersionResources, + opCache: operationNotSupportedCache{}, + finalizerToken: finalizerToken, + } + + // configure the backing store/controller + store, controller := framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return kubeClient.Core().Namespaces().List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return kubeClient.Core().Namespaces().Watch(options) + }, + }, + &api.Namespace{}, + resyncPeriod, + framework.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + namespace := obj.(*api.Namespace) + namespaceController.enqueueNamespace(namespace) + }, + UpdateFunc: func(oldObj, newObj interface{}) { + namespace := newObj.(*api.Namespace) + namespaceController.enqueueNamespace(namespace) + }, + }, + ) + + namespaceController.store = store + namespaceController.controller = controller + return namespaceController +} + +// enqueueNamespace adds an object to the controller work queue +// obj could be an *api.Namespace, or a DeletionFinalStateUnknown item. +func (nm *NamespaceController) enqueueNamespace(obj interface{}) { + key, err := controller.KeyFunc(obj) + if err != nil { + glog.Errorf("Couldn't get key for object %+v: %v", obj, err) + return + } + nm.queue.Add(key) +} + +// worker processes the queue of namespace objects. +// Each namespace can be in the queue at most once. +// The system ensures that no two workers can process +// the same namespace at the same time. +func (nm *NamespaceController) worker() { + for { + func() { + key, quit := nm.queue.Get() + if quit { + return + } + defer nm.queue.Done(key) + if err := nm.syncNamespaceFromKey(key.(string)); err != nil { + if estimate, ok := err.(*contentRemainingError); ok { + go func() { + defer utilruntime.HandleCrash() + t := estimate.Estimate/2 + 1 + glog.V(4).Infof("Content remaining in namespace %s, waiting %d seconds", key, t) + time.Sleep(time.Duration(t) * time.Second) + nm.queue.Add(key) + }() + } else { + // rather than wait for a full resync, re-add the namespace to the queue to be processed + nm.queue.Add(key) + utilruntime.HandleError(err) + } + } + }() + } +} + +// syncNamespaceFromKey looks for a namespace with the specified key in its store and synchronizes it +func (nm *NamespaceController) syncNamespaceFromKey(key string) (err error) { + startTime := time.Now() + defer glog.V(4).Infof("Finished syncing namespace %q (%v)", key, time.Now().Sub(startTime)) + + obj, exists, err := nm.store.GetByKey(key) + if !exists { + glog.Infof("Namespace has been deleted %v", key) + return nil + } + if err != nil { + glog.Infof("Unable to retrieve namespace %v from store: %v", key, err) + nm.queue.Add(key) + return err + } + namespace := obj.(*api.Namespace) + return syncNamespace(nm.kubeClient, nm.clientPool, nm.opCache, nm.groupVersionResources, namespace, nm.finalizerToken) +} + +// Run starts observing the system with the specified number of workers. +func (nm *NamespaceController) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + go nm.controller.Run(stopCh) + for i := 0; i < workers; i++ { + go wait.Until(nm.worker, time.Second, stopCh) + } + <-stopCh + glog.Infof("Shutting down NamespaceController") + nm.queue.ShutDown() +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/namespace/namespace_controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/namespace/namespace_controller_test.go new file mode 100644 index 000000000..5061f25f3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/namespace/namespace_controller_test.go @@ -0,0 +1,282 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 namespace + +import ( + "fmt" + "net/http" + "net/http/httptest" + "path" + "strings" + "sync" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/unversioned" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/client/typed/dynamic" + "k8s.io/kubernetes/pkg/util/sets" +) + +func TestFinalized(t *testing.T) { + testNamespace := &api.Namespace{ + Spec: api.NamespaceSpec{ + Finalizers: []api.FinalizerName{"a", "b"}, + }, + } + if finalized(testNamespace) { + t.Errorf("Unexpected result, namespace is not finalized") + } + testNamespace.Spec.Finalizers = []api.FinalizerName{} + if !finalized(testNamespace) { + t.Errorf("Expected object to be finalized") + } +} + +func TestFinalizeNamespaceFunc(t *testing.T) { + mockClient := &fake.Clientset{} + testNamespace := &api.Namespace{ + ObjectMeta: api.ObjectMeta{ + Name: "test", + ResourceVersion: "1", + }, + Spec: api.NamespaceSpec{ + Finalizers: []api.FinalizerName{"kubernetes", "other"}, + }, + } + finalizeNamespace(mockClient, testNamespace, api.FinalizerKubernetes) + actions := mockClient.Actions() + if len(actions) != 1 { + t.Errorf("Expected 1 mock client action, but got %v", len(actions)) + } + if !actions[0].Matches("create", "namespaces") || actions[0].GetSubresource() != "finalize" { + t.Errorf("Expected finalize-namespace action %v", actions[0]) + } + finalizers := actions[0].(core.CreateAction).GetObject().(*api.Namespace).Spec.Finalizers + if len(finalizers) != 1 { + t.Errorf("There should be a single finalizer remaining") + } + if "other" != string(finalizers[0]) { + t.Errorf("Unexpected finalizer value, %v", finalizers[0]) + } +} + +func testSyncNamespaceThatIsTerminating(t *testing.T, versions *unversioned.APIVersions) { + now := unversioned.Now() + namespaceName := "test" + testNamespacePendingFinalize := &api.Namespace{ + ObjectMeta: api.ObjectMeta{ + Name: namespaceName, + ResourceVersion: "1", + DeletionTimestamp: &now, + }, + Spec: api.NamespaceSpec{ + Finalizers: []api.FinalizerName{"kubernetes"}, + }, + Status: api.NamespaceStatus{ + Phase: api.NamespaceTerminating, + }, + } + testNamespaceFinalizeComplete := &api.Namespace{ + ObjectMeta: api.ObjectMeta{ + Name: namespaceName, + ResourceVersion: "1", + DeletionTimestamp: &now, + }, + Spec: api.NamespaceSpec{}, + Status: api.NamespaceStatus{ + Phase: api.NamespaceTerminating, + }, + } + + // when doing a delete all of content, we will do a GET of a collection, and DELETE of a collection by default + dynamicClientActionSet := sets.NewString() + groupVersionResources := testGroupVersionResources() + for _, groupVersionResource := range groupVersionResources { + urlPath := path.Join([]string{ + dynamic.LegacyAPIPathResolverFunc(groupVersionResource.GroupVersion()), + groupVersionResource.Group, + groupVersionResource.Version, + "namespaces", + namespaceName, + groupVersionResource.Resource, + }...) + dynamicClientActionSet.Insert((&fakeAction{method: "GET", path: urlPath}).String()) + dynamicClientActionSet.Insert((&fakeAction{method: "DELETE", path: urlPath}).String()) + } + + scenarios := map[string]struct { + testNamespace *api.Namespace + kubeClientActionSet sets.String + dynamicClientActionSet sets.String + }{ + "pending-finalize": { + testNamespace: testNamespacePendingFinalize, + kubeClientActionSet: sets.NewString( + strings.Join([]string{"get", "namespaces", ""}, "-"), + strings.Join([]string{"list", "pods", ""}, "-"), + strings.Join([]string{"create", "namespaces", "finalize"}, "-"), + ), + dynamicClientActionSet: dynamicClientActionSet, + }, + "complete-finalize": { + testNamespace: testNamespaceFinalizeComplete, + kubeClientActionSet: sets.NewString( + strings.Join([]string{"get", "namespaces", ""}, "-"), + strings.Join([]string{"delete", "namespaces", ""}, "-"), + ), + dynamicClientActionSet: sets.NewString(), + }, + } + + for scenario, testInput := range scenarios { + testHandler := &fakeActionHandler{statusCode: 200} + srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP) + defer srv.Close() + + mockClient := fake.NewSimpleClientset(testInput.testNamespace) + clientPool := dynamic.NewClientPool(clientConfig, dynamic.LegacyAPIPathResolverFunc) + + err := syncNamespace(mockClient, clientPool, operationNotSupportedCache{}, groupVersionResources, testInput.testNamespace, api.FinalizerKubernetes) + if err != nil { + t.Errorf("scenario %s - Unexpected error when synching namespace %v", scenario, err) + } + + // validate traffic from kube client + actionSet := sets.NewString() + for _, action := range mockClient.Actions() { + actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource(), action.GetSubresource()}, "-")) + } + if !actionSet.Equal(testInput.kubeClientActionSet) { + t.Errorf("scenario %s - mock client expected actions:\n%v\n but got:\n%v\nDifference:\n%v", scenario, + testInput.kubeClientActionSet, actionSet, testInput.kubeClientActionSet.Difference(actionSet)) + } + + // validate traffic from dynamic client + actionSet = sets.NewString() + for _, action := range testHandler.actions { + actionSet.Insert(action.String()) + } + if !actionSet.Equal(testInput.dynamicClientActionSet) { + t.Errorf("scenario %s - dynamic client expected actions:\n%v\n but got:\n%v\nDifference:\n%v", scenario, + testInput.dynamicClientActionSet, actionSet, testInput.dynamicClientActionSet.Difference(actionSet)) + } + } +} + +func TestRetryOnConflictError(t *testing.T) { + mockClient := &fake.Clientset{} + numTries := 0 + retryOnce := func(kubeClient clientset.Interface, namespace *api.Namespace) (*api.Namespace, error) { + numTries++ + if numTries <= 1 { + return namespace, errors.NewConflict(api.Resource("namespaces"), namespace.Name, fmt.Errorf("ERROR!")) + } + return namespace, nil + } + namespace := &api.Namespace{} + _, err := retryOnConflictError(mockClient, namespace, retryOnce) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + if numTries != 2 { + t.Errorf("Expected %v, but got %v", 2, numTries) + } +} + +func TestSyncNamespaceThatIsTerminatingNonExperimental(t *testing.T) { + testSyncNamespaceThatIsTerminating(t, &unversioned.APIVersions{}) +} + +func TestSyncNamespaceThatIsTerminatingV1Beta1(t *testing.T) { + testSyncNamespaceThatIsTerminating(t, &unversioned.APIVersions{Versions: []string{"extensions/v1beta1"}}) +} + +func TestSyncNamespaceThatIsActive(t *testing.T) { + mockClient := &fake.Clientset{} + testNamespace := &api.Namespace{ + ObjectMeta: api.ObjectMeta{ + Name: "test", + ResourceVersion: "1", + }, + Spec: api.NamespaceSpec{ + Finalizers: []api.FinalizerName{"kubernetes"}, + }, + Status: api.NamespaceStatus{ + Phase: api.NamespaceActive, + }, + } + err := syncNamespace(mockClient, nil, operationNotSupportedCache{}, testGroupVersionResources(), testNamespace, api.FinalizerKubernetes) + if err != nil { + t.Errorf("Unexpected error when synching namespace %v", err) + } + if len(mockClient.Actions()) != 0 { + t.Errorf("Expected no action from controller, but got: %v", mockClient.Actions()) + } +} + +// testServerAndClientConfig returns a server that listens and a config that can reference it +func testServerAndClientConfig(handler func(http.ResponseWriter, *http.Request)) (*httptest.Server, *restclient.Config) { + srv := httptest.NewServer(http.HandlerFunc(handler)) + config := &restclient.Config{ + Host: srv.URL, + } + return srv, config +} + +// fakeAction records information about requests to aid in testing. +type fakeAction struct { + method string + path string +} + +// String returns method=path to aid in testing +func (f *fakeAction) String() string { + return strings.Join([]string{f.method, f.path}, "=") +} + +// fakeActionHandler holds a list of fakeActions received +type fakeActionHandler struct { + // statusCode returned by this handler + statusCode int + + lock sync.Mutex + actions []fakeAction +} + +// ServeHTTP logs the action that occurred and always returns the associated status code +func (f *fakeActionHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) { + f.lock.Lock() + defer f.lock.Unlock() + + f.actions = append(f.actions, fakeAction{method: request.Method, path: request.URL.Path}) + response.WriteHeader(f.statusCode) + response.Write([]byte("{\"kind\": \"List\"}")) +} + +// testGroupVersionResources returns a mocked up set of resources across different api groups for testing namespace controller. +func testGroupVersionResources() []unversioned.GroupVersionResource { + results := []unversioned.GroupVersionResource{} + results = append(results, unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}) + results = append(results, unversioned.GroupVersionResource{Group: "", Version: "v1", Resource: "services"}) + results = append(results, unversioned.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"}) + return results +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/namespace/namespace_controller_utils.go b/vendor/k8s.io/kubernetes/pkg/controller/namespace/namespace_controller_utils.go new file mode 100644 index 000000000..437ce1b6e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/namespace/namespace_controller_utils.go @@ -0,0 +1,480 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 namespace + +import ( + "fmt" + "strings" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/typed/discovery" + "k8s.io/kubernetes/pkg/client/typed/dynamic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/sets" + + "github.com/golang/glog" +) + +// contentRemainingError is used to inform the caller that content is not fully removed from the namespace +type contentRemainingError struct { + Estimate int64 +} + +func (e *contentRemainingError) Error() string { + return fmt.Sprintf("some content remains in the namespace, estimate %d seconds before it is removed", e.Estimate) +} + +// operation is used for caching if an operation is supported on a dynamic client. +type operation string + +const ( + operationDeleteCollection operation = "deleteCollection" + operationList operation = "list" +) + +// operationKey is an entry in a cache. +type operationKey struct { + op operation + gvr unversioned.GroupVersionResource +} + +// operationNotSupportedCache is a simple cache to remember if an operation is not supported for a resource. +// if the operationKey maps to true, it means the operation is not supported. +type operationNotSupportedCache map[operationKey]bool + +// isSupported returns true if the operation is supported +func (o operationNotSupportedCache) isSupported(key operationKey) bool { + return !o[key] +} + +// updateNamespaceFunc is a function that makes an update to a namespace +type updateNamespaceFunc func(kubeClient clientset.Interface, namespace *api.Namespace) (*api.Namespace, error) + +// retryOnConflictError retries the specified fn if there was a conflict error +// TODO RetryOnConflict should be a generic concept in client code +func retryOnConflictError(kubeClient clientset.Interface, namespace *api.Namespace, fn updateNamespaceFunc) (result *api.Namespace, err error) { + latestNamespace := namespace + for { + result, err = fn(kubeClient, latestNamespace) + if err == nil { + return result, nil + } + if !errors.IsConflict(err) { + return nil, err + } + latestNamespace, err = kubeClient.Core().Namespaces().Get(latestNamespace.Name) + if err != nil { + return nil, err + } + } +} + +// updateNamespaceStatusFunc will verify that the status of the namespace is correct +func updateNamespaceStatusFunc(kubeClient clientset.Interface, namespace *api.Namespace) (*api.Namespace, error) { + if namespace.DeletionTimestamp.IsZero() || namespace.Status.Phase == api.NamespaceTerminating { + return namespace, nil + } + newNamespace := api.Namespace{} + newNamespace.ObjectMeta = namespace.ObjectMeta + newNamespace.Status = namespace.Status + newNamespace.Status.Phase = api.NamespaceTerminating + return kubeClient.Core().Namespaces().UpdateStatus(&newNamespace) +} + +// finalized returns true if the namespace.Spec.Finalizers is an empty list +func finalized(namespace *api.Namespace) bool { + return len(namespace.Spec.Finalizers) == 0 +} + +// finalizeNamespaceFunc returns a function that knows how to finalize a namespace for specified token. +func finalizeNamespaceFunc(finalizerToken api.FinalizerName) updateNamespaceFunc { + return func(kubeClient clientset.Interface, namespace *api.Namespace) (*api.Namespace, error) { + return finalizeNamespace(kubeClient, namespace, finalizerToken) + } +} + +// finalizeNamespace removes the specified finalizerToken and finalizes the namespace +func finalizeNamespace(kubeClient clientset.Interface, namespace *api.Namespace, finalizerToken api.FinalizerName) (*api.Namespace, error) { + namespaceFinalize := api.Namespace{} + namespaceFinalize.ObjectMeta = namespace.ObjectMeta + namespaceFinalize.Spec = namespace.Spec + finalizerSet := sets.NewString() + for i := range namespace.Spec.Finalizers { + if namespace.Spec.Finalizers[i] != finalizerToken { + finalizerSet.Insert(string(namespace.Spec.Finalizers[i])) + } + } + namespaceFinalize.Spec.Finalizers = make([]api.FinalizerName, 0, len(finalizerSet)) + for _, value := range finalizerSet.List() { + namespaceFinalize.Spec.Finalizers = append(namespaceFinalize.Spec.Finalizers, api.FinalizerName(value)) + } + namespace, err := kubeClient.Core().Namespaces().Finalize(&namespaceFinalize) + if err != nil { + // it was removed already, so life is good + if errors.IsNotFound(err) { + return namespace, nil + } + } + return namespace, err +} + +// deleteCollection is a helper function that will delete the collection of resources +// it returns true if the operation was supported on the server. +// it returns an error if the operation was supported on the server but was unable to complete. +func deleteCollection( + dynamicClient *dynamic.Client, + opCache operationNotSupportedCache, + gvr unversioned.GroupVersionResource, + namespace string, +) (bool, error) { + glog.V(5).Infof("namespace controller - deleteCollection - namespace: %s, gvr: %v", namespace, gvr) + + key := operationKey{op: operationDeleteCollection, gvr: gvr} + if !opCache.isSupported(key) { + glog.V(5).Infof("namespace controller - deleteCollection ignored since not supported - namespace: %s, gvr: %v", namespace, gvr) + return false, nil + } + + apiResource := unversioned.APIResource{Name: gvr.Resource, Namespaced: true} + err := dynamicClient.Resource(&apiResource, namespace).DeleteCollection(nil, v1.ListOptions{}) + + if err == nil { + return true, nil + } + + // this is strange, but we need to special case for both MethodNotSupported and NotFound errors + // TODO: https://github.com/kubernetes/kubernetes/issues/22413 + // we have a resource returned in the discovery API that supports no top-level verbs: + // /apis/extensions/v1beta1/namespaces/default/replicationcontrollers + // when working with this resource type, we will get a literal not found error rather than expected method not supported + // remember next time that this resource does not support delete collection... + if errors.IsMethodNotSupported(err) || errors.IsNotFound(err) { + glog.V(5).Infof("namespace controller - deleteCollection not supported - namespace: %s, gvr: %v", namespace, gvr) + opCache[key] = true + return false, nil + } + + glog.V(5).Infof("namespace controller - deleteCollection unexpected error - namespace: %s, gvr: %v, error: %v", namespace, gvr, err) + return true, err +} + +// listCollection will list the items in the specified namespace +// it returns the following: +// the list of items in the collection (if found) +// a boolean if the operation is supported +// an error if the operation is supported but could not be completed. +func listCollection( + dynamicClient *dynamic.Client, + opCache operationNotSupportedCache, + gvr unversioned.GroupVersionResource, + namespace string, +) (*runtime.UnstructuredList, bool, error) { + glog.V(5).Infof("namespace controller - listCollection - namespace: %s, gvr: %v", namespace, gvr) + + key := operationKey{op: operationList, gvr: gvr} + if !opCache.isSupported(key) { + glog.V(5).Infof("namespace controller - listCollection ignored since not supported - namespace: %s, gvr: %v", namespace, gvr) + return nil, false, nil + } + + apiResource := unversioned.APIResource{Name: gvr.Resource, Namespaced: true} + unstructuredList, err := dynamicClient.Resource(&apiResource, namespace).List(v1.ListOptions{}) + if err == nil { + return unstructuredList, true, nil + } + + // this is strange, but we need to special case for both MethodNotSupported and NotFound errors + // TODO: https://github.com/kubernetes/kubernetes/issues/22413 + // we have a resource returned in the discovery API that supports no top-level verbs: + // /apis/extensions/v1beta1/namespaces/default/replicationcontrollers + // when working with this resource type, we will get a literal not found error rather than expected method not supported + // remember next time that this resource does not support delete collection... + if errors.IsMethodNotSupported(err) || errors.IsNotFound(err) { + glog.V(5).Infof("namespace controller - listCollection not supported - namespace: %s, gvr: %v", namespace, gvr) + opCache[key] = true + return nil, false, nil + } + + return nil, true, err +} + +// deleteEachItem is a helper function that will list the collection of resources and delete each item 1 by 1. +func deleteEachItem( + dynamicClient *dynamic.Client, + opCache operationNotSupportedCache, + gvr unversioned.GroupVersionResource, + namespace string, +) error { + glog.V(5).Infof("namespace controller - deleteEachItem - namespace: %s, gvr: %v", namespace, gvr) + + unstructuredList, listSupported, err := listCollection(dynamicClient, opCache, gvr, namespace) + if err != nil { + return err + } + if !listSupported { + return nil + } + apiResource := unversioned.APIResource{Name: gvr.Resource, Namespaced: true} + for _, item := range unstructuredList.Items { + if err = dynamicClient.Resource(&apiResource, namespace).Delete(item.Name, nil); err != nil && !errors.IsNotFound(err) && !errors.IsMethodNotSupported(err) { + return err + } + } + return nil +} + +// deleteAllContentForGroupVersionResource will use the dynamic client to delete each resource identified in gvr. +// It returns an estimate of the time remaining before the remaing resources are deleted. +// If estimate > 0, not all resources are guaranteed to be gone. +func deleteAllContentForGroupVersionResource( + kubeClient clientset.Interface, + clientPool dynamic.ClientPool, + opCache operationNotSupportedCache, + gvr unversioned.GroupVersionResource, + namespace string, + namespaceDeletedAt unversioned.Time, +) (int64, error) { + glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - namespace: %s, gvr: %v", namespace, gvr) + + // estimate how long it will take for the resource to be deleted (needed for objects that support graceful delete) + estimate, err := estimateGracefulTermination(kubeClient, gvr, namespace, namespaceDeletedAt) + if err != nil { + glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - unable to estimate - namespace: %s, gvr: %v, err: %v", namespace, gvr, err) + return estimate, err + } + glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - estimate - namespace: %s, gvr: %v, estimate: %v", namespace, gvr, estimate) + + // get a client for this group version... + dynamicClient, err := clientPool.ClientForGroupVersion(gvr.GroupVersion()) + if err != nil { + glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - unable to get client - namespace: %s, gvr: %v, err: %v", namespace, gvr, err) + return estimate, err + } + + // first try to delete the entire collection + deleteCollectionSupported, err := deleteCollection(dynamicClient, opCache, gvr, namespace) + if err != nil { + return estimate, err + } + + // delete collection was not supported, so we list and delete each item... + if !deleteCollectionSupported { + err = deleteEachItem(dynamicClient, opCache, gvr, namespace) + if err != nil { + return estimate, err + } + } + + // verify there are no more remaining items + // it is not an error condition for there to be remaining items if local estimate is non-zero + glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - checking for no more items in namespace: %s, gvr: %v", namespace, gvr) + unstructuredList, listSupported, err := listCollection(dynamicClient, opCache, gvr, namespace) + if err != nil { + glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - error verifying no items in namespace: %s, gvr: %v, err: %v", namespace, gvr, err) + return estimate, err + } + if !listSupported { + return estimate, nil + } + glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - items remaining - namespace: %s, gvr: %v, items: %v", namespace, gvr, len(unstructuredList.Items)) + if len(unstructuredList.Items) != 0 && estimate == int64(0) { + return estimate, fmt.Errorf("unexpected items still remain in namespace: %s for gvr: %v", namespace, gvr) + } + return estimate, nil +} + +// deleteAllContent will use the dynamic client to delete each resource identified in groupVersionResources. +// It returns an estimate of the time remaining before the remaing resources are deleted. +// If estimate > 0, not all resources are guaranteed to be gone. +func deleteAllContent( + kubeClient clientset.Interface, + clientPool dynamic.ClientPool, + opCache operationNotSupportedCache, + groupVersionResources []unversioned.GroupVersionResource, + namespace string, + namespaceDeletedAt unversioned.Time, +) (int64, error) { + estimate := int64(0) + glog.V(4).Infof("namespace controller - deleteAllContent - namespace: %s, gvrs: %v", namespace, groupVersionResources) + // iterate over each group version, and attempt to delete all of its resources + for _, gvr := range groupVersionResources { + gvrEstimate, err := deleteAllContentForGroupVersionResource(kubeClient, clientPool, opCache, gvr, namespace, namespaceDeletedAt) + if err != nil { + return estimate, err + } + if gvrEstimate > estimate { + estimate = gvrEstimate + } + } + glog.V(4).Infof("namespace controller - deleteAllContent - namespace: %s, estimate: %v", namespace, estimate) + return estimate, nil +} + +// syncNamespace orchestrates deletion of a Namespace and its associated content. +func syncNamespace( + kubeClient clientset.Interface, + clientPool dynamic.ClientPool, + opCache operationNotSupportedCache, + groupVersionResources []unversioned.GroupVersionResource, + namespace *api.Namespace, + finalizerToken api.FinalizerName, +) error { + if namespace.DeletionTimestamp == nil { + return nil + } + + // multiple controllers may edit a namespace during termination + // first get the latest state of the namespace before proceeding + // if the namespace was deleted already, don't do anything + namespace, err := kubeClient.Core().Namespaces().Get(namespace.Name) + if err != nil { + if errors.IsNotFound(err) { + return nil + } + return err + } + + glog.V(5).Infof("namespace controller - syncNamespace - namespace: %s, finalizerToken: %s", namespace.Name, finalizerToken) + + // ensure that the status is up to date on the namespace + // if we get a not found error, we assume the namespace is truly gone + namespace, err = retryOnConflictError(kubeClient, namespace, updateNamespaceStatusFunc) + if err != nil { + if errors.IsNotFound(err) { + return nil + } + return err + } + + // if the namespace is already finalized, delete it + if finalized(namespace) { + err = kubeClient.Core().Namespaces().Delete(namespace.Name, nil) + if err != nil && !errors.IsNotFound(err) { + return err + } + return nil + } + + // there may still be content for us to remove + estimate, err := deleteAllContent(kubeClient, clientPool, opCache, groupVersionResources, namespace.Name, *namespace.DeletionTimestamp) + if err != nil { + return err + } + if estimate > 0 { + return &contentRemainingError{estimate} + } + + // we have removed content, so mark it finalized by us + result, err := retryOnConflictError(kubeClient, namespace, finalizeNamespaceFunc(finalizerToken)) + if err != nil { + // in normal practice, this should not be possible, but if a deployment is running + // two controllers to do namespace deletion that share a common finalizer token it's + // possible that a not found could occur since the other controller would have finished the delete. + if errors.IsNotFound(err) { + return nil + } + return err + } + + // now check if all finalizers have reported that we delete now + if finalized(result) { + err = kubeClient.Core().Namespaces().Delete(namespace.Name, nil) + if err != nil && !errors.IsNotFound(err) { + return err + } + } + + return nil +} + +// estimateGrracefulTermination will estimate the graceful termination required for the specific entity in the namespace +func estimateGracefulTermination(kubeClient clientset.Interface, groupVersionResource unversioned.GroupVersionResource, ns string, namespaceDeletedAt unversioned.Time) (int64, error) { + groupResource := groupVersionResource.GroupResource() + glog.V(5).Infof("namespace controller - estimateGracefulTermination - group %s, resource: %s", groupResource.Group, groupResource.Resource) + estimate := int64(0) + var err error + switch groupResource { + case unversioned.GroupResource{Group: "", Resource: "pods"}: + estimate, err = estimateGracefulTerminationForPods(kubeClient, ns) + } + if err != nil { + return estimate, err + } + // determine if the estimate is greater than the deletion timestamp + duration := time.Since(namespaceDeletedAt.Time) + allowedEstimate := time.Duration(estimate) * time.Second + if duration >= allowedEstimate { + estimate = int64(0) + } + return estimate, nil +} + +// estimateGracefulTerminationForPods determines the graceful termination period for pods in the namespace +func estimateGracefulTerminationForPods(kubeClient clientset.Interface, ns string) (int64, error) { + glog.V(5).Infof("namespace controller - estimateGracefulTerminationForPods - namespace %s", ns) + estimate := int64(0) + items, err := kubeClient.Core().Pods(ns).List(api.ListOptions{}) + if err != nil { + return estimate, err + } + for i := range items.Items { + // filter out terminal pods + phase := items.Items[i].Status.Phase + if api.PodSucceeded == phase || api.PodFailed == phase { + continue + } + if items.Items[i].Spec.TerminationGracePeriodSeconds != nil { + grace := *items.Items[i].Spec.TerminationGracePeriodSeconds + if grace > estimate { + estimate = grace + } + } + } + return estimate, nil +} + +// ServerPreferredNamespacedGroupVersionResources uses the specified client to discover the set of preferred groupVersionResources that are namespaced +func ServerPreferredNamespacedGroupVersionResources(discoveryClient discovery.DiscoveryInterface) ([]unversioned.GroupVersionResource, error) { + results := []unversioned.GroupVersionResource{} + serverGroupList, err := discoveryClient.ServerGroups() + if err != nil { + return results, err + } + for _, apiGroup := range serverGroupList.Groups { + preferredVersion := apiGroup.PreferredVersion + apiResourceList, err := discoveryClient.ServerResourcesForGroupVersion(preferredVersion.GroupVersion) + if err != nil { + return results, err + } + groupVersion := unversioned.GroupVersion{Group: apiGroup.Name, Version: preferredVersion.Version} + for _, apiResource := range apiResourceList.APIResources { + if !apiResource.Namespaced { + continue + } + if strings.Contains(apiResource.Name, "/") { + continue + } + results = append(results, groupVersion.WithResource(apiResource.Name)) + } + } + return results, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/node/doc.go b/vendor/k8s.io/kubernetes/pkg/controller/node/doc.go new file mode 100644 index 000000000..084754e69 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/node/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 contains code for syncing cloud instances with +// node registry +package node diff --git a/vendor/k8s.io/kubernetes/pkg/controller/node/nodecontroller.go b/vendor/k8s.io/kubernetes/pkg/controller/node/nodecontroller.go new file mode 100644 index 000000000..78d3fc5c1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/node/nodecontroller.go @@ -0,0 +1,939 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 ( + "errors" + "fmt" + "net" + "strings" + "sync" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/kubelet/util/format" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/flowcontrol" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/version" + "k8s.io/kubernetes/pkg/watch" +) + +var ( + ErrCloudInstance = errors.New("cloud provider doesn't support instances.") +) + +const ( + // nodeStatusUpdateRetry controls the number of retries of writing NodeStatus update. + nodeStatusUpdateRetry = 5 + // controls how often NodeController will try to evict Pods from non-responsive Nodes. + nodeEvictionPeriod = 100 * time.Millisecond +) + +type nodeStatusData struct { + probeTimestamp unversioned.Time + readyTransitionTimestamp unversioned.Time + status api.NodeStatus +} + +type NodeController struct { + allocateNodeCIDRs bool + cloud cloudprovider.Interface + clusterCIDR *net.IPNet + deletingPodsRateLimiter flowcontrol.RateLimiter + knownNodeSet sets.String + kubeClient clientset.Interface + // Method for easy mocking in unittest. + lookupIP func(host string) ([]net.IP, error) + // Value used if sync_nodes_status=False. NodeController will not proactively + // sync node status in this case, but will monitor node status updated from kubelet. If + // it doesn't receive update for this amount of time, it will start posting "NodeReady== + // ConditionUnknown". The amount of time before which NodeController start evicting pods + // is controlled via flag 'pod-eviction-timeout'. + // Note: be cautious when changing the constant, it must work with nodeStatusUpdateFrequency + // in kubelet. There are several constraints: + // 1. nodeMonitorGracePeriod must be N times more than nodeStatusUpdateFrequency, where + // N means number of retries allowed for kubelet to post node status. It is pointless + // to make nodeMonitorGracePeriod be less than nodeStatusUpdateFrequency, since there + // will only be fresh values from Kubelet at an interval of nodeStatusUpdateFrequency. + // The constant must be less than podEvictionTimeout. + // 2. nodeMonitorGracePeriod can't be too large for user experience - larger value takes + // longer for user to see up-to-date node status. + nodeMonitorGracePeriod time.Duration + // Value controlling NodeController monitoring period, i.e. how often does NodeController + // check node status posted from kubelet. This value should be lower than nodeMonitorGracePeriod. + // TODO: Change node status monitor to watch based. + nodeMonitorPeriod time.Duration + // Value used if sync_nodes_status=False, only for node startup. When node + // is just created, e.g. cluster bootstrap or node creation, we give a longer grace period. + nodeStartupGracePeriod time.Duration + // per Node map storing last observed Status together with a local time when it was observed. + // This timestamp is to be used instead of LastProbeTime stored in Condition. We do this + // to aviod the problem with time skew across the cluster. + nodeStatusMap map[string]nodeStatusData + now func() unversioned.Time + // Lock to access evictor workers + evictorLock *sync.Mutex + // workers that evicts pods from unresponsive nodes. + podEvictor *RateLimitedTimedQueue + terminationEvictor *RateLimitedTimedQueue + podEvictionTimeout time.Duration + // The maximum duration before a pod evicted from a node can be forcefully terminated. + maximumGracePeriod time.Duration + recorder record.EventRecorder + // Pod framework and store + podController *framework.Controller + podStore cache.StoreToPodLister + // Node framework and store + nodeController *framework.Controller + nodeStore cache.StoreToNodeLister + // DaemonSet framework and store + daemonSetController *framework.Controller + daemonSetStore cache.StoreToDaemonSetLister + + forcefullyDeletePod func(*api.Pod) error + nodeExistsInCloudProvider func(string) (bool, error) +} + +// NewNodeController returns a new node controller to sync instances from cloudprovider. +func NewNodeController( + cloud cloudprovider.Interface, + kubeClient clientset.Interface, + podEvictionTimeout time.Duration, + deletionEvictionLimiter flowcontrol.RateLimiter, + terminationEvictionLimiter flowcontrol.RateLimiter, + nodeMonitorGracePeriod time.Duration, + nodeStartupGracePeriod time.Duration, + nodeMonitorPeriod time.Duration, + clusterCIDR *net.IPNet, + allocateNodeCIDRs bool) *NodeController { + eventBroadcaster := record.NewBroadcaster() + recorder := eventBroadcaster.NewRecorder(api.EventSource{Component: "controllermanager"}) + eventBroadcaster.StartLogging(glog.Infof) + if kubeClient != nil { + glog.Infof("Sending events to api server.") + eventBroadcaster.StartRecordingToSink(&unversionedcore.EventSinkImpl{Interface: kubeClient.Core().Events("")}) + } else { + glog.Infof("No api server defined - no events will be sent to API server.") + } + if allocateNodeCIDRs && clusterCIDR == nil { + glog.Fatal("NodeController: Must specify clusterCIDR if allocateNodeCIDRs == true.") + } + evictorLock := sync.Mutex{} + + nc := &NodeController{ + cloud: cloud, + knownNodeSet: make(sets.String), + kubeClient: kubeClient, + recorder: recorder, + podEvictionTimeout: podEvictionTimeout, + maximumGracePeriod: 5 * time.Minute, + evictorLock: &evictorLock, + podEvictor: NewRateLimitedTimedQueue(deletionEvictionLimiter), + terminationEvictor: NewRateLimitedTimedQueue(terminationEvictionLimiter), + nodeStatusMap: make(map[string]nodeStatusData), + nodeMonitorGracePeriod: nodeMonitorGracePeriod, + nodeMonitorPeriod: nodeMonitorPeriod, + nodeStartupGracePeriod: nodeStartupGracePeriod, + lookupIP: net.LookupIP, + now: unversioned.Now, + clusterCIDR: clusterCIDR, + allocateNodeCIDRs: allocateNodeCIDRs, + forcefullyDeletePod: func(p *api.Pod) error { return forcefullyDeletePod(kubeClient, p) }, + nodeExistsInCloudProvider: func(nodeName string) (bool, error) { return nodeExistsInCloudProvider(cloud, nodeName) }, + } + + nc.podStore.Store, nc.podController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return nc.kubeClient.Core().Pods(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return nc.kubeClient.Core().Pods(api.NamespaceAll).Watch(options) + }, + }, + &api.Pod{}, + controller.NoResyncPeriodFunc(), + framework.ResourceEventHandlerFuncs{ + AddFunc: nc.maybeDeleteTerminatingPod, + UpdateFunc: func(_, obj interface{}) { nc.maybeDeleteTerminatingPod(obj) }, + }, + ) + nc.nodeStore.Store, nc.nodeController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return nc.kubeClient.Core().Nodes().List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return nc.kubeClient.Core().Nodes().Watch(options) + }, + }, + &api.Node{}, + controller.NoResyncPeriodFunc(), + framework.ResourceEventHandlerFuncs{}, + ) + nc.daemonSetStore.Store, nc.daemonSetController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return nc.kubeClient.Extensions().DaemonSets(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return nc.kubeClient.Extensions().DaemonSets(api.NamespaceAll).Watch(options) + }, + }, + &extensions.DaemonSet{}, + controller.NoResyncPeriodFunc(), + framework.ResourceEventHandlerFuncs{}, + ) + return nc +} + +// Run starts an asynchronous loop that monitors the status of cluster nodes. +func (nc *NodeController) Run(period time.Duration) { + go nc.nodeController.Run(wait.NeverStop) + go nc.podController.Run(wait.NeverStop) + go nc.daemonSetController.Run(wait.NeverStop) + + // Incorporate the results of node status pushed from kubelet to master. + go wait.Until(func() { + if err := nc.monitorNodeStatus(); err != nil { + glog.Errorf("Error monitoring node status: %v", err) + } + }, nc.nodeMonitorPeriod, wait.NeverStop) + + // Managing eviction of nodes: + // 1. when we delete pods off a node, if the node was not empty at the time we then + // queue a termination watcher + // a. If we hit an error, retry deletion + // 2. The terminator loop ensures that pods are eventually cleaned and we never + // terminate a pod in a time period less than nc.maximumGracePeriod. AddedAt + // is the time from which we measure "has this pod been terminating too long", + // after which we will delete the pod with grace period 0 (force delete). + // a. If we hit errors, retry instantly + // b. If there are no pods left terminating, exit + // c. If there are pods still terminating, wait for their estimated completion + // before retrying + go wait.Until(func() { + nc.evictorLock.Lock() + defer nc.evictorLock.Unlock() + nc.podEvictor.Try(func(value TimedValue) (bool, time.Duration) { + remaining, err := nc.deletePods(value.Value) + if err != nil { + utilruntime.HandleError(fmt.Errorf("unable to evict node %q: %v", value.Value, err)) + return false, 0 + } + + if remaining { + nc.terminationEvictor.Add(value.Value) + } + return true, 0 + }) + }, nodeEvictionPeriod, wait.NeverStop) + + // TODO: replace with a controller that ensures pods that are terminating complete + // in a particular time period + go wait.Until(func() { + nc.evictorLock.Lock() + defer nc.evictorLock.Unlock() + nc.terminationEvictor.Try(func(value TimedValue) (bool, time.Duration) { + completed, remaining, err := nc.terminatePods(value.Value, value.AddedAt) + if err != nil { + utilruntime.HandleError(fmt.Errorf("unable to terminate pods on node %q: %v", value.Value, err)) + return false, 0 + } + + if completed { + glog.Infof("All pods terminated on %s", value.Value) + nc.recordNodeEvent(value.Value, api.EventTypeNormal, "TerminatedAllPods", fmt.Sprintf("Terminated all Pods on Node %s.", value.Value)) + return true, 0 + } + + glog.V(2).Infof("Pods terminating since %s on %q, estimated completion %s", value.AddedAt, value.Value, remaining) + // clamp very short intervals + if remaining < nodeEvictionPeriod { + remaining = nodeEvictionPeriod + } + return false, remaining + }) + }, nodeEvictionPeriod, wait.NeverStop) + + go wait.Until(nc.cleanupOrphanedPods, 30*time.Second, wait.NeverStop) +} + +// Generates num pod CIDRs that could be assigned to nodes. +func generateCIDRs(clusterCIDR *net.IPNet, num int) sets.String { + res := sets.NewString() + cidrIP := clusterCIDR.IP.To4() + for i := 0; i < num; i++ { + // TODO: Make the CIDRs configurable. + b1 := byte(i >> 8) + b2 := byte(i % 256) + res.Insert(fmt.Sprintf("%d.%d.%d.0/24", cidrIP[0], cidrIP[1]+b1, cidrIP[2]+b2)) + } + return res +} + +// getCondition returns a condition object for the specific condition +// type, nil if the condition is not set. +func (nc *NodeController) getCondition(status *api.NodeStatus, conditionType api.NodeConditionType) *api.NodeCondition { + if status == nil { + return nil + } + for i := range status.Conditions { + if status.Conditions[i].Type == conditionType { + return &status.Conditions[i] + } + } + return nil +} + +var gracefulDeletionVersion = version.MustParse("v1.1.0") + +// maybeDeleteTerminatingPod non-gracefully deletes pods that are terminating +// that should not be gracefully terminated. +func (nc *NodeController) maybeDeleteTerminatingPod(obj interface{}) { + pod, ok := obj.(*api.Pod) + if !ok { + return + } + + // consider only terminating pods + if pod.DeletionTimestamp == nil { + return + } + + // delete terminating pods that have not yet been scheduled + if len(pod.Spec.NodeName) == 0 { + utilruntime.HandleError(nc.forcefullyDeletePod(pod)) + return + } + + nodeObj, found, err := nc.nodeStore.GetByKey(pod.Spec.NodeName) + if err != nil { + // this can only happen if the Store.KeyFunc has a problem creating + // a key for the pod. If it happens once, it will happen again so + // don't bother requeuing the pod. + utilruntime.HandleError(err) + return + } + + // delete terminating pods that have been scheduled on + // nonexistent nodes + if !found { + utilruntime.HandleError(nc.forcefullyDeletePod(pod)) + return + } + + // delete terminating pods that have been scheduled on + // nodes that do not support graceful termination + // TODO(mikedanese): this can be removed when we no longer + // guarantee backwards compatibility of master API to kubelets with + // versions less than 1.1.0 + node := nodeObj.(*api.Node) + v, err := version.Parse(node.Status.NodeInfo.KubeletVersion) + if err != nil { + glog.Infof("couldn't parse verions %q of minion: %v", node.Status.NodeInfo.KubeletVersion, err) + utilruntime.HandleError(nc.forcefullyDeletePod(pod)) + return + } + if gracefulDeletionVersion.GT(v) { + utilruntime.HandleError(nc.forcefullyDeletePod(pod)) + return + } +} + +// cleanupOrphanedPods deletes pods that are bound to nodes that don't +// exist. +func (nc *NodeController) cleanupOrphanedPods() { + pods, err := nc.podStore.List(labels.Everything()) + if err != nil { + utilruntime.HandleError(err) + return + } + + for _, pod := range pods { + if pod.Spec.NodeName == "" { + continue + } + if _, exists, _ := nc.nodeStore.Store.GetByKey(pod.Spec.NodeName); exists { + continue + } + if err := nc.forcefullyDeletePod(pod); err != nil { + utilruntime.HandleError(err) + } + } +} + +func forcefullyDeletePod(c clientset.Interface, pod *api.Pod) error { + var zero int64 + err := c.Core().Pods(pod.Namespace).Delete(pod.Name, &api.DeleteOptions{GracePeriodSeconds: &zero}) + if err == nil { + glog.Infof("forceful deletion of %s succeeded", pod.Name) + } + return err +} + +// monitorNodeStatus verifies node status are constantly updated by kubelet, and if not, +// post "NodeReady==ConditionUnknown". It also evicts all pods if node is not ready or +// not reachable for a long period of time. +func (nc *NodeController) monitorNodeStatus() error { + nodes, err := nc.kubeClient.Core().Nodes().List(api.ListOptions{}) + if err != nil { + return err + } + for _, node := range nodes.Items { + if !nc.knownNodeSet.Has(node.Name) { + glog.V(1).Infof("NodeController observed a new Node: %#v", node) + nc.recordNodeEvent(node.Name, api.EventTypeNormal, "RegisteredNode", fmt.Sprintf("Registered Node %v in NodeController", node.Name)) + nc.cancelPodEviction(node.Name) + nc.knownNodeSet.Insert(node.Name) + } + } + // If there's a difference between lengths of known Nodes and observed nodes + // we must have removed some Node. + if len(nc.knownNodeSet) != len(nodes.Items) { + observedSet := make(sets.String) + for _, node := range nodes.Items { + observedSet.Insert(node.Name) + } + deleted := nc.knownNodeSet.Difference(observedSet) + for nodeName := range deleted { + glog.V(1).Infof("NodeController observed a Node deletion: %v", nodeName) + nc.recordNodeEvent(nodeName, api.EventTypeNormal, "RemovingNode", fmt.Sprintf("Removing Node %v from NodeController", nodeName)) + nc.evictPods(nodeName) + nc.knownNodeSet.Delete(nodeName) + } + } + + if nc.allocateNodeCIDRs { + // TODO (cjcullen): Use pkg/controller/framework to watch nodes and + // reduce lists/decouple this from monitoring status. + nc.reconcileNodeCIDRs(nodes) + } + for i := range nodes.Items { + var gracePeriod time.Duration + var lastReadyCondition api.NodeCondition + var readyCondition *api.NodeCondition + node := &nodes.Items[i] + for rep := 0; rep < nodeStatusUpdateRetry; rep++ { + gracePeriod, lastReadyCondition, readyCondition, err = nc.tryUpdateNodeStatus(node) + if err == nil { + break + } + name := node.Name + node, err = nc.kubeClient.Core().Nodes().Get(name) + if err != nil { + glog.Errorf("Failed while getting a Node to retry updating NodeStatus. Probably Node %s was deleted.", name) + break + } + } + if err != nil { + glog.Errorf("Update status of Node %v from NodeController exceeds retry count."+ + "Skipping - no pods will be evicted.", node.Name) + continue + } + + decisionTimestamp := nc.now() + + if readyCondition != nil { + // Check eviction timeout against decisionTimestamp + if lastReadyCondition.Status == api.ConditionFalse && + decisionTimestamp.After(nc.nodeStatusMap[node.Name].readyTransitionTimestamp.Add(nc.podEvictionTimeout)) { + if nc.evictPods(node.Name) { + glog.Infof("Evicting pods on node %s: %v is later than %v + %v", node.Name, decisionTimestamp, nc.nodeStatusMap[node.Name].readyTransitionTimestamp, nc.podEvictionTimeout) + } + } + if lastReadyCondition.Status == api.ConditionUnknown && + decisionTimestamp.After(nc.nodeStatusMap[node.Name].probeTimestamp.Add(nc.podEvictionTimeout)) { + if nc.evictPods(node.Name) { + glog.Infof("Evicting pods on node %s: %v is later than %v + %v", node.Name, decisionTimestamp, nc.nodeStatusMap[node.Name].readyTransitionTimestamp, nc.podEvictionTimeout-gracePeriod) + } + } + if lastReadyCondition.Status == api.ConditionTrue { + if nc.cancelPodEviction(node.Name) { + glog.Infof("Node %s is ready again, cancelled pod eviction", node.Name) + } + } + + // Report node event. + if readyCondition.Status != api.ConditionTrue && lastReadyCondition.Status == api.ConditionTrue { + nc.recordNodeStatusChange(node, "NodeNotReady") + if err = nc.markAllPodsNotReady(node.Name); err != nil { + utilruntime.HandleError(fmt.Errorf("Unable to mark all pods NotReady on node %v: %v", node.Name, err)) + } + } + + // Check with the cloud provider to see if the node still exists. If it + // doesn't, delete the node immediately. + if readyCondition.Status != api.ConditionTrue && nc.cloud != nil { + exists, err := nc.nodeExistsInCloudProvider(node.Name) + if err != nil { + glog.Errorf("Error determining if node %v exists in cloud: %v", node.Name, err) + continue + } + if !exists { + glog.Infof("Deleting node (no longer present in cloud provider): %s", node.Name) + nc.recordNodeEvent(node.Name, api.EventTypeNormal, "DeletingNode", fmt.Sprintf("Deleting Node %v because it's not present according to cloud provider", node.Name)) + go func(nodeName string) { + defer utilruntime.HandleCrash() + // Kubelet is not reporting and Cloud Provider says node + // is gone. Delete it without worrying about grace + // periods. + if err := nc.forcefullyDeleteNode(nodeName); err != nil { + glog.Errorf("Unable to forcefully delete node %q: %v", nodeName, err) + } + }(node.Name) + continue + } + } + } + } + return nil +} + +func nodeExistsInCloudProvider(cloud cloudprovider.Interface, nodeName string) (bool, error) { + instances, ok := cloud.Instances() + if !ok { + return false, fmt.Errorf("%v", ErrCloudInstance) + } + if _, err := instances.ExternalID(nodeName); err != nil { + if err == cloudprovider.InstanceNotFound { + return false, nil + } + return false, err + } + return true, nil +} + +// forcefullyDeleteNode immediately deletes all pods on the node, and then +// deletes the node itself. +func (nc *NodeController) forcefullyDeleteNode(nodeName string) error { + selector := fields.OneTermEqualSelector(api.PodHostField, nodeName) + options := api.ListOptions{FieldSelector: selector} + pods, err := nc.kubeClient.Core().Pods(api.NamespaceAll).List(options) + if err != nil { + return fmt.Errorf("unable to list pods on node %q: %v", nodeName, err) + } + for _, pod := range pods.Items { + if pod.Spec.NodeName != nodeName { + continue + } + if err := nc.forcefullyDeletePod(&pod); err != nil { + return fmt.Errorf("unable to delete pod %q on node %q: %v", pod.Name, nodeName, err) + } + } + if err := nc.kubeClient.Core().Nodes().Delete(nodeName, nil); err != nil { + return fmt.Errorf("unable to delete node %q: %v", nodeName, err) + } + return nil +} + +// reconcileNodeCIDRs looks at each node and assigns it a valid CIDR +// if it doesn't currently have one. +func (nc *NodeController) reconcileNodeCIDRs(nodes *api.NodeList) { + glog.V(4).Infof("Reconciling cidrs for %d nodes", len(nodes.Items)) + // TODO(roberthbailey): This seems inefficient. Why re-calculate CIDRs + // on each sync period? + availableCIDRs := generateCIDRs(nc.clusterCIDR, len(nodes.Items)) + for _, node := range nodes.Items { + if node.Spec.PodCIDR != "" { + glog.V(4).Infof("CIDR %s is already being used by node %s", node.Spec.PodCIDR, node.Name) + availableCIDRs.Delete(node.Spec.PodCIDR) + } + } + for _, node := range nodes.Items { + if node.Spec.PodCIDR == "" { + // Re-GET node (because ours might be stale by now). + n, err := nc.kubeClient.Core().Nodes().Get(node.Name) + if err != nil { + glog.Errorf("Failed to get node %q: %v", node.Name, err) + continue + } + podCIDR, found := availableCIDRs.PopAny() + if !found { + nc.recordNodeStatusChange(n, "CIDRNotAvailable") + continue + } + glog.V(1).Infof("Assigning node %s CIDR %s", n.Name, podCIDR) + n.Spec.PodCIDR = podCIDR + if _, err := nc.kubeClient.Core().Nodes().Update(n); err != nil { + nc.recordNodeStatusChange(&node, "CIDRAssignmentFailed") + } + } + + } +} + +func (nc *NodeController) recordNodeEvent(nodeName, eventtype, reason, event string) { + ref := &api.ObjectReference{ + Kind: "Node", + Name: nodeName, + UID: types.UID(nodeName), + Namespace: "", + } + glog.V(2).Infof("Recording %s event message for node %s", event, nodeName) + nc.recorder.Eventf(ref, eventtype, reason, "Node %s event: %s", nodeName, event) +} + +func (nc *NodeController) recordNodeStatusChange(node *api.Node, new_status string) { + ref := &api.ObjectReference{ + Kind: "Node", + Name: node.Name, + UID: types.UID(node.Name), + Namespace: "", + } + glog.V(2).Infof("Recording status change %s event message for node %s", new_status, node.Name) + // TODO: This requires a transaction, either both node status is updated + // and event is recorded or neither should happen, see issue #6055. + nc.recorder.Eventf(ref, api.EventTypeNormal, new_status, "Node %s status is now: %s", node.Name, new_status) +} + +// For a given node checks its conditions and tries to update it. Returns grace period to which given node +// is entitled, state of current and last observed Ready Condition, and an error if it occurred. +func (nc *NodeController) tryUpdateNodeStatus(node *api.Node) (time.Duration, api.NodeCondition, *api.NodeCondition, error) { + var err error + var gracePeriod time.Duration + var lastReadyCondition api.NodeCondition + readyCondition := nc.getCondition(&node.Status, api.NodeReady) + if readyCondition == nil { + // If ready condition is nil, then kubelet (or nodecontroller) never posted node status. + // A fake ready condition is created, where LastProbeTime and LastTransitionTime is set + // to node.CreationTimestamp to avoid handle the corner case. + lastReadyCondition = api.NodeCondition{ + Type: api.NodeReady, + Status: api.ConditionUnknown, + LastHeartbeatTime: node.CreationTimestamp, + LastTransitionTime: node.CreationTimestamp, + } + gracePeriod = nc.nodeStartupGracePeriod + nc.nodeStatusMap[node.Name] = nodeStatusData{ + status: node.Status, + probeTimestamp: node.CreationTimestamp, + readyTransitionTimestamp: node.CreationTimestamp, + } + } else { + // If ready condition is not nil, make a copy of it, since we may modify it in place later. + lastReadyCondition = *readyCondition + gracePeriod = nc.nodeMonitorGracePeriod + } + + savedNodeStatus, found := nc.nodeStatusMap[node.Name] + // There are following cases to check: + // - both saved and new status have no Ready Condition set - we leave everything as it is, + // - saved status have no Ready Condition, but current one does - NodeController was restarted with Node data already present in etcd, + // - saved status have some Ready Condition, but current one does not - it's an error, but we fill it up because that's probably a good thing to do, + // - both saved and current statuses have Ready Conditions and they have the same LastProbeTime - nothing happened on that Node, it may be + // unresponsive, so we leave it as it is, + // - both saved and current statuses have Ready Conditions, they have different LastProbeTimes, but the same Ready Condition State - + // everything's in order, no transition occurred, we update only probeTimestamp, + // - both saved and current statuses have Ready Conditions, different LastProbeTimes and different Ready Condition State - + // Ready Condition changed it state since we last seen it, so we update both probeTimestamp and readyTransitionTimestamp. + // TODO: things to consider: + // - if 'LastProbeTime' have gone back in time its probably an error, currently we ignore it, + // - currently only correct Ready State transition outside of Node Controller is marking it ready by Kubelet, we don't check + // if that's the case, but it does not seem necessary. + var savedCondition *api.NodeCondition + if found { + savedCondition = nc.getCondition(&savedNodeStatus.status, api.NodeReady) + } + observedCondition := nc.getCondition(&node.Status, api.NodeReady) + if !found { + glog.Warningf("Missing timestamp for Node %s. Assuming now as a timestamp.", node.Name) + savedNodeStatus = nodeStatusData{ + status: node.Status, + probeTimestamp: nc.now(), + readyTransitionTimestamp: nc.now(), + } + nc.nodeStatusMap[node.Name] = savedNodeStatus + } else if savedCondition == nil && observedCondition != nil { + glog.V(1).Infof("Creating timestamp entry for newly observed Node %s", node.Name) + savedNodeStatus = nodeStatusData{ + status: node.Status, + probeTimestamp: nc.now(), + readyTransitionTimestamp: nc.now(), + } + nc.nodeStatusMap[node.Name] = savedNodeStatus + } else if savedCondition != nil && observedCondition == nil { + glog.Errorf("ReadyCondition was removed from Status of Node %s", node.Name) + // TODO: figure out what to do in this case. For now we do the same thing as above. + savedNodeStatus = nodeStatusData{ + status: node.Status, + probeTimestamp: nc.now(), + readyTransitionTimestamp: nc.now(), + } + nc.nodeStatusMap[node.Name] = savedNodeStatus + } else if savedCondition != nil && observedCondition != nil && savedCondition.LastHeartbeatTime != observedCondition.LastHeartbeatTime { + var transitionTime unversioned.Time + // If ReadyCondition changed since the last time we checked, we update the transition timestamp to "now", + // otherwise we leave it as it is. + if savedCondition.LastTransitionTime != observedCondition.LastTransitionTime { + glog.V(3).Infof("ReadyCondition for Node %s transitioned from %v to %v", node.Name, savedCondition.Status, observedCondition) + + transitionTime = nc.now() + } else { + transitionTime = savedNodeStatus.readyTransitionTimestamp + } + if glog.V(5) { + glog.Infof("Node %s ReadyCondition updated. Updating timestamp: %+v vs %+v.", node.Name, savedNodeStatus.status, node.Status) + } else { + glog.V(3).Infof("Node %s ReadyCondition updated. Updating timestamp.", node.Name) + } + savedNodeStatus = nodeStatusData{ + status: node.Status, + probeTimestamp: nc.now(), + readyTransitionTimestamp: transitionTime, + } + nc.nodeStatusMap[node.Name] = savedNodeStatus + } + + if nc.now().After(savedNodeStatus.probeTimestamp.Add(gracePeriod)) { + // NodeReady condition was last set longer ago than gracePeriod, so update it to Unknown + // (regardless of its current value) in the master. + if readyCondition == nil { + glog.V(2).Infof("node %v is never updated by kubelet", node.Name) + node.Status.Conditions = append(node.Status.Conditions, api.NodeCondition{ + Type: api.NodeReady, + Status: api.ConditionUnknown, + Reason: "NodeStatusNeverUpdated", + Message: fmt.Sprintf("Kubelet never posted node status."), + LastHeartbeatTime: node.CreationTimestamp, + LastTransitionTime: nc.now(), + }) + } else { + glog.V(2).Infof("node %v hasn't been updated for %+v. Last ready condition is: %+v", + node.Name, nc.now().Time.Sub(savedNodeStatus.probeTimestamp.Time), lastReadyCondition) + if lastReadyCondition.Status != api.ConditionUnknown { + readyCondition.Status = api.ConditionUnknown + readyCondition.Reason = "NodeStatusUnknown" + readyCondition.Message = fmt.Sprintf("Kubelet stopped posting node status.") + // LastProbeTime is the last time we heard from kubelet. + readyCondition.LastHeartbeatTime = lastReadyCondition.LastHeartbeatTime + readyCondition.LastTransitionTime = nc.now() + } + } + + // Like NodeReady condition, NodeOutOfDisk was last set longer ago than gracePeriod, so update + // it to Unknown (regardless of its current value) in the master. + // TODO(madhusudancs): Refactor this with readyCondition to remove duplicated code. + oodCondition := nc.getCondition(&node.Status, api.NodeOutOfDisk) + if oodCondition == nil { + glog.V(2).Infof("Out of disk condition of node %v is never updated by kubelet", node.Name) + node.Status.Conditions = append(node.Status.Conditions, api.NodeCondition{ + Type: api.NodeOutOfDisk, + Status: api.ConditionUnknown, + Reason: "NodeStatusNeverUpdated", + Message: fmt.Sprintf("Kubelet never posted node status."), + LastHeartbeatTime: node.CreationTimestamp, + LastTransitionTime: nc.now(), + }) + } else { + glog.V(2).Infof("node %v hasn't been updated for %+v. Last out of disk condition is: %+v", + node.Name, nc.now().Time.Sub(savedNodeStatus.probeTimestamp.Time), oodCondition) + if oodCondition.Status != api.ConditionUnknown { + oodCondition.Status = api.ConditionUnknown + oodCondition.Reason = "NodeStatusUnknown" + oodCondition.Message = fmt.Sprintf("Kubelet stopped posting node status.") + oodCondition.LastTransitionTime = nc.now() + } + } + + if !api.Semantic.DeepEqual(nc.getCondition(&node.Status, api.NodeReady), &lastReadyCondition) { + if _, err = nc.kubeClient.Core().Nodes().UpdateStatus(node); err != nil { + glog.Errorf("Error updating node %s: %v", node.Name, err) + return gracePeriod, lastReadyCondition, readyCondition, err + } else { + nc.nodeStatusMap[node.Name] = nodeStatusData{ + status: node.Status, + probeTimestamp: nc.nodeStatusMap[node.Name].probeTimestamp, + readyTransitionTimestamp: nc.now(), + } + return gracePeriod, lastReadyCondition, readyCondition, nil + } + } + } + + return gracePeriod, lastReadyCondition, readyCondition, err +} + +// evictPods queues an eviction for the provided node name, and returns false if the node is already +// queued for eviction. +func (nc *NodeController) evictPods(nodeName string) bool { + nc.evictorLock.Lock() + defer nc.evictorLock.Unlock() + return nc.podEvictor.Add(nodeName) +} + +// cancelPodEviction removes any queued evictions, typically because the node is available again. It +// returns true if an eviction was queued. +func (nc *NodeController) cancelPodEviction(nodeName string) bool { + nc.evictorLock.Lock() + defer nc.evictorLock.Unlock() + wasDeleting := nc.podEvictor.Remove(nodeName) + wasTerminating := nc.terminationEvictor.Remove(nodeName) + if wasDeleting || wasTerminating { + glog.V(2).Infof("Cancelling pod Eviction on Node: %v", nodeName) + return true + } + return false +} + +// deletePods will delete all pods from master running on given node, and return true +// if any pods were deleted. +func (nc *NodeController) deletePods(nodeName string) (bool, error) { + remaining := false + selector := fields.OneTermEqualSelector(api.PodHostField, nodeName) + options := api.ListOptions{FieldSelector: selector} + pods, err := nc.kubeClient.Core().Pods(api.NamespaceAll).List(options) + if err != nil { + return remaining, err + } + + if len(pods.Items) > 0 { + nc.recordNodeEvent(nodeName, api.EventTypeNormal, "DeletingAllPods", fmt.Sprintf("Deleting all Pods from Node %v.", nodeName)) + } + + for _, pod := range pods.Items { + // Defensive check, also needed for tests. + if pod.Spec.NodeName != nodeName { + continue + } + // if the pod has already been deleted, ignore it + if pod.DeletionGracePeriodSeconds != nil { + continue + } + // if the pod is managed by a daemonset, ignore it + _, err := nc.daemonSetStore.GetPodDaemonSets(&pod) + if err == nil { // No error means at least one daemonset was found + continue + } + + glog.V(2).Infof("Starting deletion of pod %v", pod.Name) + nc.recorder.Eventf(&pod, api.EventTypeNormal, "NodeControllerEviction", "Marking for deletion Pod %s from Node %s", pod.Name, nodeName) + if err := nc.kubeClient.Core().Pods(pod.Namespace).Delete(pod.Name, nil); err != nil { + return false, err + } + remaining = true + } + return remaining, nil +} + +// update ready status of all pods running on given node from master +// return true if success +func (nc *NodeController) markAllPodsNotReady(nodeName string) error { + glog.V(2).Infof("Update ready status of pods on node [%v]", nodeName) + opts := api.ListOptions{FieldSelector: fields.OneTermEqualSelector(api.PodHostField, nodeName)} + pods, err := nc.kubeClient.Core().Pods(api.NamespaceAll).List(opts) + if err != nil { + return err + } + + errMsg := []string{} + for _, pod := range pods.Items { + // Defensive check, also needed for tests. + if pod.Spec.NodeName != nodeName { + continue + } + + for i, cond := range pod.Status.Conditions { + if cond.Type == api.PodReady { + pod.Status.Conditions[i].Status = api.ConditionFalse + glog.V(2).Infof("Updating ready status of pod %v to false", pod.Name) + pod, err := nc.kubeClient.Core().Pods(pod.Namespace).UpdateStatus(&pod) + if err != nil { + glog.Warningf("Failed to update status for pod %q: %v", format.Pod(pod), err) + errMsg = append(errMsg, fmt.Sprintf("%v", err)) + } + break + } + } + } + if len(errMsg) == 0 { + return nil + } + return fmt.Errorf("%v", strings.Join(errMsg, "; ")) +} + +// terminatePods will ensure all pods on the given node that are in terminating state are eventually +// cleaned up. Returns true if the node has no pods in terminating state, a duration that indicates how +// long before we should check again (the next deadline for a pod to complete), or an error. +func (nc *NodeController) terminatePods(nodeName string, since time.Time) (bool, time.Duration, error) { + // the time before we should try again + nextAttempt := time.Duration(0) + // have we deleted all pods + complete := true + + selector := fields.OneTermEqualSelector(api.PodHostField, nodeName) + options := api.ListOptions{FieldSelector: selector} + pods, err := nc.kubeClient.Core().Pods(api.NamespaceAll).List(options) + if err != nil { + return false, nextAttempt, err + } + + now := time.Now() + elapsed := now.Sub(since) + for _, pod := range pods.Items { + // Defensive check, also needed for tests. + if pod.Spec.NodeName != nodeName { + continue + } + // only clean terminated pods + if pod.DeletionGracePeriodSeconds == nil { + continue + } + + // the user's requested grace period + grace := time.Duration(*pod.DeletionGracePeriodSeconds) * time.Second + if grace > nc.maximumGracePeriod { + grace = nc.maximumGracePeriod + } + + // the time remaining before the pod should have been deleted + remaining := grace - elapsed + if remaining < 0 { + remaining = 0 + glog.V(2).Infof("Removing pod %v after %s grace period", pod.Name, grace) + nc.recordNodeEvent(nodeName, api.EventTypeNormal, "TerminatingEvictedPod", fmt.Sprintf("Pod %s has exceeded the grace period for deletion after being evicted from Node %q and is being force killed", pod.Name, nodeName)) + if err := nc.kubeClient.Core().Pods(pod.Namespace).Delete(pod.Name, api.NewDeleteOptions(0)); err != nil { + glog.Errorf("Error completing deletion of pod %s: %v", pod.Name, err) + complete = false + } + } else { + glog.V(2).Infof("Pod %v still terminating, requested grace period %s, %s remaining", pod.Name, grace, remaining) + complete = false + } + + if nextAttempt < remaining { + nextAttempt = remaining + } + } + return complete, nextAttempt, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/node/nodecontroller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/node/nodecontroller_test.go new file mode 100644 index 000000000..d69c13aec --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/node/nodecontroller_test.go @@ -0,0 +1,1189 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 ( + "errors" + "sync" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + apierrors "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/cache" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + fakecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/fake" + "k8s.io/kubernetes/pkg/util/diff" + "k8s.io/kubernetes/pkg/util/flowcontrol" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/watch" +) + +const ( + testNodeMonitorGracePeriod = 40 * time.Second + testNodeStartupGracePeriod = 60 * time.Second + testNodeMonitorPeriod = 5 * time.Second +) + +// FakeNodeHandler is a fake implementation of NodesInterface and NodeInterface. It +// allows test cases to have fine-grained control over mock behaviors. We also need +// PodsInterface and PodInterface to test list & delet pods, which is implemented in +// the embedded client.Fake field. +type FakeNodeHandler struct { + *fake.Clientset + + // Input: Hooks determine if request is valid or not + CreateHook func(*FakeNodeHandler, *api.Node) bool + Existing []*api.Node + + // Output + CreatedNodes []*api.Node + DeletedNodes []*api.Node + UpdatedNodes []*api.Node + UpdatedNodeStatuses []*api.Node + RequestCount int + + // Synchronization + createLock sync.Mutex + deleteWaitChan chan struct{} +} + +type FakeLegacyHandler struct { + unversionedcore.CoreInterface + n *FakeNodeHandler +} + +func (c *FakeNodeHandler) Core() unversionedcore.CoreInterface { + return &FakeLegacyHandler{c.Clientset.Core(), c} +} + +func (m *FakeLegacyHandler) Nodes() unversionedcore.NodeInterface { + return m.n +} + +func (m *FakeNodeHandler) Create(node *api.Node) (*api.Node, error) { + m.createLock.Lock() + defer func() { + m.RequestCount++ + m.createLock.Unlock() + }() + for _, n := range m.Existing { + if n.Name == node.Name { + return nil, apierrors.NewAlreadyExists(api.Resource("nodes"), node.Name) + } + } + if m.CreateHook == nil || m.CreateHook(m, node) { + nodeCopy := *node + m.CreatedNodes = append(m.CreatedNodes, &nodeCopy) + return node, nil + } else { + return nil, errors.New("Create error.") + } +} + +func (m *FakeNodeHandler) Get(name string) (*api.Node, error) { + return nil, nil +} + +func (m *FakeNodeHandler) List(opts api.ListOptions) (*api.NodeList, error) { + defer func() { m.RequestCount++ }() + var nodes []*api.Node + for i := 0; i < len(m.UpdatedNodes); i++ { + if !contains(m.UpdatedNodes[i], m.DeletedNodes) { + nodes = append(nodes, m.UpdatedNodes[i]) + } + } + for i := 0; i < len(m.Existing); i++ { + if !contains(m.Existing[i], m.DeletedNodes) && !contains(m.Existing[i], nodes) { + nodes = append(nodes, m.Existing[i]) + } + } + for i := 0; i < len(m.CreatedNodes); i++ { + if !contains(m.Existing[i], m.DeletedNodes) && !contains(m.CreatedNodes[i], nodes) { + nodes = append(nodes, m.CreatedNodes[i]) + } + } + nodeList := &api.NodeList{} + for _, node := range nodes { + nodeList.Items = append(nodeList.Items, *node) + } + return nodeList, nil +} + +func (m *FakeNodeHandler) Delete(id string, opt *api.DeleteOptions) error { + defer func() { + if m.deleteWaitChan != nil { + m.deleteWaitChan <- struct{}{} + } + }() + m.DeletedNodes = append(m.DeletedNodes, newNode(id)) + m.RequestCount++ + return nil +} + +func (m *FakeNodeHandler) DeleteCollection(opt *api.DeleteOptions, listOpts api.ListOptions) error { + return nil +} + +func (m *FakeNodeHandler) Update(node *api.Node) (*api.Node, error) { + nodeCopy := *node + m.UpdatedNodes = append(m.UpdatedNodes, &nodeCopy) + m.RequestCount++ + return node, nil +} + +func (m *FakeNodeHandler) UpdateStatus(node *api.Node) (*api.Node, error) { + nodeCopy := *node + m.UpdatedNodeStatuses = append(m.UpdatedNodeStatuses, &nodeCopy) + m.RequestCount++ + return node, nil +} + +func (m *FakeNodeHandler) Watch(opts api.ListOptions) (watch.Interface, error) { + return nil, nil +} + +func TestMonitorNodeStatusEvictPods(t *testing.T) { + fakeNow := unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) + evictionTimeout := 10 * time.Minute + + table := []struct { + fakeNodeHandler *FakeNodeHandler + daemonSets []extensions.DaemonSet + timeToPass time.Duration + newNodeStatus api.NodeStatus + expectedEvictPods bool + description string + }{ + // Node created recently, with no status (happens only at cluster startup). + { + fakeNodeHandler: &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: fakeNow, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), + }, + daemonSets: nil, + timeToPass: 0, + newNodeStatus: api.NodeStatus{}, + expectedEvictPods: false, + description: "Node created recently, with no status.", + }, + // Node created long time ago, and kubelet posted NotReady for a short period of time. + { + fakeNodeHandler: &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionFalse, + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), + }, + daemonSets: nil, + timeToPass: evictionTimeout, + newNodeStatus: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionFalse, + // Node status has just been updated, and is NotReady for 10min. + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 9, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + expectedEvictPods: false, + description: "Node created long time ago, and kubelet posted NotReady for a short period of time.", + }, + // Pod is ds-managed, and kubelet posted NotReady for a long period of time. + { + fakeNodeHandler: &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionFalse, + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset( + &api.PodList{ + Items: []api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + Name: "pod0", + Namespace: "default", + Labels: map[string]string{"daemon": "yes"}, + }, + Spec: api.PodSpec{ + NodeName: "node0", + }, + }, + }, + }, + ), + }, + daemonSets: []extensions.DaemonSet{ + { + ObjectMeta: api.ObjectMeta{ + Name: "ds0", + Namespace: "default", + }, + Spec: extensions.DaemonSetSpec{ + Selector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{"daemon": "yes"}, + }, + }, + }, + }, + timeToPass: time.Hour, + newNodeStatus: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionFalse, + // Node status has just been updated, and is NotReady for 1hr. + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 59, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + expectedEvictPods: false, + description: "Pod is ds-managed, and kubelet posted NotReady for a long period of time.", + }, + // Node created long time ago, and kubelet posted NotReady for a long period of time. + { + fakeNodeHandler: &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionFalse, + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), + }, + daemonSets: nil, + timeToPass: time.Hour, + newNodeStatus: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionFalse, + // Node status has just been updated, and is NotReady for 1hr. + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 59, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + expectedEvictPods: true, + description: "Node created long time ago, and kubelet posted NotReady for a long period of time.", + }, + // Node created long time ago, node controller posted Unknown for a short period of time. + { + fakeNodeHandler: &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionUnknown, + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), + }, + daemonSets: nil, + timeToPass: evictionTimeout - testNodeMonitorGracePeriod, + newNodeStatus: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionUnknown, + // Node status was updated by nodecontroller 10min ago + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + expectedEvictPods: false, + description: "Node created long time ago, node controller posted Unknown for a short period of time.", + }, + // Node created long time ago, node controller posted Unknown for a long period of time. + { + fakeNodeHandler: &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionUnknown, + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), + }, + daemonSets: nil, + timeToPass: 60 * time.Minute, + newNodeStatus: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionUnknown, + // Node status was updated by nodecontroller 1hr ago + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + expectedEvictPods: true, + description: "Node created long time ago, node controller posted Unknown for a long period of time.", + }, + } + + for _, item := range table { + nodeController := NewNodeController(nil, item.fakeNodeHandler, + evictionTimeout, flowcontrol.NewFakeAlwaysRateLimiter(), flowcontrol.NewFakeAlwaysRateLimiter(), testNodeMonitorGracePeriod, + testNodeStartupGracePeriod, testNodeMonitorPeriod, nil, false) + nodeController.now = func() unversioned.Time { return fakeNow } + for _, ds := range item.daemonSets { + nodeController.daemonSetStore.Add(&ds) + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + if item.timeToPass > 0 { + nodeController.now = func() unversioned.Time { return unversioned.Time{Time: fakeNow.Add(item.timeToPass)} } + item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus + } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + + nodeController.podEvictor.Try(func(value TimedValue) (bool, time.Duration) { + remaining, _ := nodeController.deletePods(value.Value) + if remaining { + nodeController.terminationEvictor.Add(value.Value) + } + return true, 0 + }) + nodeController.podEvictor.Try(func(value TimedValue) (bool, time.Duration) { + nodeController.terminatePods(value.Value, value.AddedAt) + return true, 0 + }) + podEvicted := false + for _, action := range item.fakeNodeHandler.Actions() { + if action.GetVerb() == "delete" && action.GetResource() == "pods" { + podEvicted = true + } + } + + if item.expectedEvictPods != podEvicted { + t.Errorf("expected pod eviction: %+v, got %+v for %+v", item.expectedEvictPods, + podEvicted, item.description) + } + } +} + +// TestCloudProviderNoRateLimit tests that monitorNodes() immediately deletes +// pods and the node when kubelet has not reported, and the cloudprovider says +// the node is gone. +func TestCloudProviderNoRateLimit(t *testing.T) { + fnh := &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionUnknown, + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0"), *newPod("pod1", "node0")}}), + deleteWaitChan: make(chan struct{}), + } + nodeController := NewNodeController(nil, fnh, 10*time.Minute, + flowcontrol.NewFakeAlwaysRateLimiter(), flowcontrol.NewFakeAlwaysRateLimiter(), + testNodeMonitorGracePeriod, testNodeStartupGracePeriod, + testNodeMonitorPeriod, nil, false) + nodeController.cloud = &fakecloud.FakeCloud{} + nodeController.now = func() unversioned.Time { return unversioned.Date(2016, 1, 1, 12, 0, 0, 0, time.UTC) } + nodeController.nodeExistsInCloudProvider = func(nodeName string) (bool, error) { + return false, nil + } + // monitorNodeStatus should allow this node to be immediately deleted + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + select { + case <-fnh.deleteWaitChan: + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Timed out waiting %v for node to be deleted", wait.ForeverTestTimeout) + } + if len(fnh.DeletedNodes) != 1 || fnh.DeletedNodes[0].Name != "node0" { + t.Errorf("Node was not deleted") + } + if nodeOnQueue := nodeController.podEvictor.Remove("node0"); nodeOnQueue { + t.Errorf("Node was queued for eviction. Should have been immediately deleted.") + } +} + +func TestMonitorNodeStatusUpdateStatus(t *testing.T) { + fakeNow := unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) + table := []struct { + fakeNodeHandler *FakeNodeHandler + timeToPass time.Duration + newNodeStatus api.NodeStatus + expectedEvictPods bool + expectedRequestCount int + expectedNodes []*api.Node + }{ + // Node created long time ago, without status: + // Expect Unknown status posted from node controller. + { + fakeNodeHandler: &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + }, + Clientset: fake.NewSimpleClientset(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), + }, + expectedRequestCount: 2, // List+Update + expectedNodes: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionUnknown, + Reason: "NodeStatusNeverUpdated", + Message: "Kubelet never posted node status.", + LastHeartbeatTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + LastTransitionTime: fakeNow, + }, + { + Type: api.NodeOutOfDisk, + Status: api.ConditionUnknown, + Reason: "NodeStatusNeverUpdated", + Message: "Kubelet never posted node status.", + LastHeartbeatTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + LastTransitionTime: fakeNow, + }, + }, + }, + }, + }, + }, + // Node created recently, without status. + // Expect no action from node controller (within startup grace period). + { + fakeNodeHandler: &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: fakeNow, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), + }, + expectedRequestCount: 1, // List + expectedNodes: nil, + }, + // Node created long time ago, with status updated by kubelet exceeds grace period. + // Expect Unknown status posted from node controller. + { + fakeNodeHandler: &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionTrue, + // Node status hasn't been updated for 1hr. + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + { + Type: api.NodeOutOfDisk, + Status: api.ConditionFalse, + // Node status hasn't been updated for 1hr. + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + Spec: api.NodeSpec{ + ExternalID: "node0", + }, + }, + }, + Clientset: fake.NewSimpleClientset(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), + }, + expectedRequestCount: 3, // (List+)List+Update + timeToPass: time.Hour, + newNodeStatus: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionTrue, + // Node status hasn't been updated for 1hr. + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + { + Type: api.NodeOutOfDisk, + Status: api.ConditionFalse, + // Node status hasn't been updated for 1hr. + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + expectedNodes: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionUnknown, + Reason: "NodeStatusUnknown", + Message: "Kubelet stopped posting node status.", + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Time{Time: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC).Add(time.Hour)}, + }, + { + Type: api.NodeOutOfDisk, + Status: api.ConditionUnknown, + Reason: "NodeStatusUnknown", + Message: "Kubelet stopped posting node status.", + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Time{Time: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC).Add(time.Hour)}, + }, + }, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + Spec: api.NodeSpec{ + ExternalID: "node0", + }, + }, + }, + }, + // Node created long time ago, with status updated recently. + // Expect no action from node controller (within monitor grace period). + { + fakeNodeHandler: &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionTrue, + // Node status has just been updated. + LastHeartbeatTime: fakeNow, + LastTransitionTime: fakeNow, + }, + }, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + Spec: api.NodeSpec{ + ExternalID: "node0", + }, + }, + }, + Clientset: fake.NewSimpleClientset(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), + }, + expectedRequestCount: 1, // List + expectedNodes: nil, + }, + } + + for i, item := range table { + nodeController := NewNodeController(nil, item.fakeNodeHandler, 5*time.Minute, flowcontrol.NewFakeAlwaysRateLimiter(), + flowcontrol.NewFakeAlwaysRateLimiter(), testNodeMonitorGracePeriod, testNodeStartupGracePeriod, testNodeMonitorPeriod, nil, false) + nodeController.now = func() unversioned.Time { return fakeNow } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + if item.timeToPass > 0 { + nodeController.now = func() unversioned.Time { return unversioned.Time{Time: fakeNow.Add(item.timeToPass)} } + item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + } + if item.expectedRequestCount != item.fakeNodeHandler.RequestCount { + t.Errorf("expected %v call, but got %v.", item.expectedRequestCount, item.fakeNodeHandler.RequestCount) + } + if len(item.fakeNodeHandler.UpdatedNodes) > 0 && !api.Semantic.DeepEqual(item.expectedNodes, item.fakeNodeHandler.UpdatedNodes) { + t.Errorf("Case[%d] unexpected nodes: %s", i, diff.ObjectDiff(item.expectedNodes[0], item.fakeNodeHandler.UpdatedNodes[0])) + } + if len(item.fakeNodeHandler.UpdatedNodeStatuses) > 0 && !api.Semantic.DeepEqual(item.expectedNodes, item.fakeNodeHandler.UpdatedNodeStatuses) { + t.Errorf("Case[%d] unexpected nodes: %s", i, diff.ObjectDiff(item.expectedNodes[0], item.fakeNodeHandler.UpdatedNodeStatuses[0])) + } + } +} + +func TestMonitorNodeStatusMarkPodsNotReady(t *testing.T) { + fakeNow := unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) + table := []struct { + fakeNodeHandler *FakeNodeHandler + timeToPass time.Duration + newNodeStatus api.NodeStatus + expectedPodStatusUpdate bool + }{ + // Node created recently, without status. + // Expect no action from node controller (within startup grace period). + { + fakeNodeHandler: &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: fakeNow, + }, + }, + }, + Clientset: fake.NewSimpleClientset(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), + }, + expectedPodStatusUpdate: false, + }, + // Node created long time ago, with status updated recently. + // Expect no action from node controller (within monitor grace period). + { + fakeNodeHandler: &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionTrue, + // Node status has just been updated. + LastHeartbeatTime: fakeNow, + LastTransitionTime: fakeNow, + }, + }, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + Spec: api.NodeSpec{ + ExternalID: "node0", + }, + }, + }, + Clientset: fake.NewSimpleClientset(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), + }, + expectedPodStatusUpdate: false, + }, + // Node created long time ago, with status updated by kubelet exceeds grace period. + // Expect pods status updated and Unknown node status posted from node controller + { + fakeNodeHandler: &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionTrue, + // Node status hasn't been updated for 1hr. + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + { + Type: api.NodeOutOfDisk, + Status: api.ConditionFalse, + // Node status hasn't been updated for 1hr. + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + Spec: api.NodeSpec{ + ExternalID: "node0", + }, + }, + }, + Clientset: fake.NewSimpleClientset(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0")}}), + }, + timeToPass: 1 * time.Minute, + newNodeStatus: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionTrue, + // Node status hasn't been updated for 1hr. + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + { + Type: api.NodeOutOfDisk, + Status: api.ConditionFalse, + // Node status hasn't been updated for 1hr. + LastHeartbeatTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC), + }, + }, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + expectedPodStatusUpdate: true, + }, + } + + for i, item := range table { + nodeController := NewNodeController(nil, item.fakeNodeHandler, 5*time.Minute, flowcontrol.NewFakeAlwaysRateLimiter(), + flowcontrol.NewFakeAlwaysRateLimiter(), testNodeMonitorGracePeriod, testNodeStartupGracePeriod, testNodeMonitorPeriod, nil, false) + nodeController.now = func() unversioned.Time { return fakeNow } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("Case[%d] unexpected error: %v", i, err) + } + if item.timeToPass > 0 { + nodeController.now = func() unversioned.Time { return unversioned.Time{Time: fakeNow.Add(item.timeToPass)} } + item.fakeNodeHandler.Existing[0].Status = item.newNodeStatus + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("Case[%d] unexpected error: %v", i, err) + } + } + + podStatusUpdated := false + for _, action := range item.fakeNodeHandler.Actions() { + if action.GetVerb() == "update" && action.GetResource() == "pods" && action.GetSubresource() == "status" { + podStatusUpdated = true + } + } + if podStatusUpdated != item.expectedPodStatusUpdate { + t.Errorf("Case[%d] expect pod status updated to be %v, but got %v", i, item.expectedPodStatusUpdate, podStatusUpdated) + } + } +} + +func TestNodeDeletion(t *testing.T) { + fakeNow := unversioned.Date(2015, 1, 1, 12, 0, 0, 0, time.UTC) + fakeNodeHandler := &FakeNodeHandler{ + Existing: []*api.Node{ + { + ObjectMeta: api.ObjectMeta{ + Name: "node0", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionTrue, + // Node status has just been updated. + LastHeartbeatTime: fakeNow, + LastTransitionTime: fakeNow, + }, + }, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + Spec: api.NodeSpec{ + ExternalID: "node0", + }, + }, + { + ObjectMeta: api.ObjectMeta{ + Name: "node1", + CreationTimestamp: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionTrue, + // Node status has just been updated. + LastHeartbeatTime: fakeNow, + LastTransitionTime: fakeNow, + }, + }, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + Spec: api.NodeSpec{ + ExternalID: "node0", + }, + }, + }, + Clientset: fake.NewSimpleClientset(&api.PodList{Items: []api.Pod{*newPod("pod0", "node0"), *newPod("pod1", "node1")}}), + } + + nodeController := NewNodeController(nil, fakeNodeHandler, 5*time.Minute, flowcontrol.NewFakeAlwaysRateLimiter(), flowcontrol.NewFakeAlwaysRateLimiter(), + testNodeMonitorGracePeriod, testNodeStartupGracePeriod, testNodeMonitorPeriod, nil, false) + nodeController.now = func() unversioned.Time { return fakeNow } + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + fakeNodeHandler.Delete("node1", nil) + if err := nodeController.monitorNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + nodeController.podEvictor.Try(func(value TimedValue) (bool, time.Duration) { + nodeController.deletePods(value.Value) + return true, 0 + }) + podEvicted := false + for _, action := range fakeNodeHandler.Actions() { + if action.GetVerb() == "delete" && action.GetResource() == "pods" { + podEvicted = true + } + } + if !podEvicted { + t.Error("expected pods to be evicted from the deleted node") + } +} + +func TestCheckPod(t *testing.T) { + + tcs := []struct { + pod api.Pod + prune bool + }{ + + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{DeletionTimestamp: nil}, + Spec: api.PodSpec{NodeName: "new"}, + }, + prune: false, + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{DeletionTimestamp: nil}, + Spec: api.PodSpec{NodeName: "old"}, + }, + prune: false, + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{DeletionTimestamp: nil}, + Spec: api.PodSpec{NodeName: ""}, + }, + prune: false, + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{DeletionTimestamp: nil}, + Spec: api.PodSpec{NodeName: "nonexistant"}, + }, + prune: false, + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{DeletionTimestamp: &unversioned.Time{}}, + Spec: api.PodSpec{NodeName: "new"}, + }, + prune: false, + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{DeletionTimestamp: &unversioned.Time{}}, + Spec: api.PodSpec{NodeName: "old"}, + }, + prune: true, + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{DeletionTimestamp: &unversioned.Time{}}, + Spec: api.PodSpec{NodeName: "older"}, + }, + prune: true, + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{DeletionTimestamp: &unversioned.Time{}}, + Spec: api.PodSpec{NodeName: "oldest"}, + }, + prune: true, + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{DeletionTimestamp: &unversioned.Time{}}, + Spec: api.PodSpec{NodeName: ""}, + }, + prune: true, + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{DeletionTimestamp: &unversioned.Time{}}, + Spec: api.PodSpec{NodeName: "nonexistant"}, + }, + prune: true, + }, + } + + nc := NewNodeController(nil, nil, 0, nil, nil, 0, 0, 0, nil, false) + nc.nodeStore.Store = cache.NewStore(cache.MetaNamespaceKeyFunc) + nc.nodeStore.Store.Add(&api.Node{ + ObjectMeta: api.ObjectMeta{ + Name: "new", + }, + Status: api.NodeStatus{ + NodeInfo: api.NodeSystemInfo{ + KubeletVersion: "v1.1.0", + }, + }, + }) + nc.nodeStore.Store.Add(&api.Node{ + ObjectMeta: api.ObjectMeta{ + Name: "old", + }, + Status: api.NodeStatus{ + NodeInfo: api.NodeSystemInfo{ + KubeletVersion: "v1.0.0", + }, + }, + }) + nc.nodeStore.Store.Add(&api.Node{ + ObjectMeta: api.ObjectMeta{ + Name: "older", + }, + Status: api.NodeStatus{ + NodeInfo: api.NodeSystemInfo{ + KubeletVersion: "v0.21.4", + }, + }, + }) + nc.nodeStore.Store.Add(&api.Node{ + ObjectMeta: api.ObjectMeta{ + Name: "oldest", + }, + Status: api.NodeStatus{ + NodeInfo: api.NodeSystemInfo{ + KubeletVersion: "v0.19.3", + }, + }, + }) + + for i, tc := range tcs { + var deleteCalls int + nc.forcefullyDeletePod = func(_ *api.Pod) error { + deleteCalls++ + return nil + } + + nc.maybeDeleteTerminatingPod(&tc.pod) + + if tc.prune && deleteCalls != 1 { + t.Errorf("[%v] expected number of delete calls to be 1 but got %v", i, deleteCalls) + } + if !tc.prune && deleteCalls != 0 { + t.Errorf("[%v] expected number of delete calls to be 0 but got %v", i, deleteCalls) + } + } +} + +func TestCleanupOrphanedPods(t *testing.T) { + newPod := func(name, node string) api.Pod { + return api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: name, + }, + Spec: api.PodSpec{ + NodeName: node, + }, + } + } + pods := []api.Pod{ + newPod("a", "foo"), + newPod("b", "bar"), + newPod("c", "gone"), + } + nc := NewNodeController(nil, nil, 0, nil, nil, 0, 0, 0, nil, false) + + nc.nodeStore.Store.Add(newNode("foo")) + nc.nodeStore.Store.Add(newNode("bar")) + for _, pod := range pods { + p := pod + nc.podStore.Store.Add(&p) + } + + var deleteCalls int + var deletedPodName string + nc.forcefullyDeletePod = func(p *api.Pod) error { + deleteCalls++ + deletedPodName = p.ObjectMeta.Name + return nil + } + nc.cleanupOrphanedPods() + + if deleteCalls != 1 { + t.Fatalf("expected one delete, got: %v", deleteCalls) + } + if deletedPodName != "c" { + t.Fatalf("expected deleted pod name to be 'c', but got: %q", deletedPodName) + } +} + +func newNode(name string) *api.Node { + return &api.Node{ + ObjectMeta: api.ObjectMeta{Name: name}, + Spec: api.NodeSpec{ + ExternalID: name, + }, + Status: api.NodeStatus{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), + }, + }, + } +} + +func newPod(name, host string) *api.Pod { + return &api.Pod{ObjectMeta: api.ObjectMeta{Name: name}, Spec: api.PodSpec{NodeName: host}, + Status: api.PodStatus{Conditions: []api.PodCondition{{Type: api.PodReady, Status: api.ConditionTrue}}}} +} + +func contains(node *api.Node, nodes []*api.Node) bool { + for i := 0; i < len(nodes); i++ { + if node.Name == nodes[i].Name { + return true + } + } + return false +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/node/rate_limited_queue.go b/vendor/k8s.io/kubernetes/pkg/controller/node/rate_limited_queue.go new file mode 100644 index 000000000..4b8042ace --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/node/rate_limited_queue.go @@ -0,0 +1,201 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 ( + "container/heap" + "sync" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/util/flowcontrol" + "k8s.io/kubernetes/pkg/util/sets" +) + +// TimedValue is a value that should be processed at a designated time. +type TimedValue struct { + Value string + AddedAt time.Time + ProcessAt time.Time +} + +// now is used to test time +var now func() time.Time = time.Now + +// TimedQueue is a priority heap where the lowest ProcessAt is at the front of the queue +type TimedQueue []*TimedValue + +func (h TimedQueue) Len() int { return len(h) } +func (h TimedQueue) Less(i, j int) bool { return h[i].ProcessAt.Before(h[j].ProcessAt) } +func (h TimedQueue) Swap(i, j int) { h[i], h[j] = h[j], h[i] } + +func (h *TimedQueue) Push(x interface{}) { + *h = append(*h, x.(*TimedValue)) +} + +func (h *TimedQueue) Pop() interface{} { + old := *h + n := len(old) + x := old[n-1] + *h = old[0 : n-1] + return x +} + +// A FIFO queue which additionally guarantees that any element can be added only once until +// it is removed. +type UniqueQueue struct { + lock sync.Mutex + queue TimedQueue + set sets.String +} + +// Adds a new value to the queue if it wasn't added before, or was explicitly removed by the +// Remove call. Returns true if new value was added. +func (q *UniqueQueue) Add(value TimedValue) bool { + q.lock.Lock() + defer q.lock.Unlock() + + if q.set.Has(value.Value) { + return false + } + heap.Push(&q.queue, &value) + q.set.Insert(value.Value) + return true +} + +// Replace replaces an existing value in the queue if it already exists, otherwise it does nothing. +// Returns true if the item was found. +func (q *UniqueQueue) Replace(value TimedValue) bool { + q.lock.Lock() + defer q.lock.Unlock() + + for i := range q.queue { + if q.queue[i].Value != value.Value { + continue + } + heap.Remove(&q.queue, i) + heap.Push(&q.queue, &value) + return true + } + return false +} + +// Removes the value from the queue, so Get() call won't return it, and allow subsequent addition +// of the given value. If the value is not present does nothing and returns false. +func (q *UniqueQueue) Remove(value string) bool { + q.lock.Lock() + defer q.lock.Unlock() + + q.set.Delete(value) + for i, val := range q.queue { + if val.Value == value { + heap.Remove(&q.queue, i) + return true + } + } + return false +} + +// Returns the oldest added value that wasn't returned yet. +func (q *UniqueQueue) Get() (TimedValue, bool) { + q.lock.Lock() + defer q.lock.Unlock() + if len(q.queue) == 0 { + return TimedValue{}, false + } + result := heap.Pop(&q.queue).(*TimedValue) + q.set.Delete(result.Value) + return *result, true +} + +// Head returns the oldest added value that wasn't returned yet without removing it. +func (q *UniqueQueue) Head() (TimedValue, bool) { + q.lock.Lock() + defer q.lock.Unlock() + if len(q.queue) == 0 { + return TimedValue{}, false + } + result := q.queue[0] + return *result, true +} + +// RateLimitedTimedQueue is a unique item priority queue ordered by the expected next time +// of execution. It is also rate limited. +type RateLimitedTimedQueue struct { + queue UniqueQueue + limiter flowcontrol.RateLimiter +} + +// Creates new queue which will use given RateLimiter to oversee execution. +func NewRateLimitedTimedQueue(limiter flowcontrol.RateLimiter) *RateLimitedTimedQueue { + return &RateLimitedTimedQueue{ + queue: UniqueQueue{ + queue: TimedQueue{}, + set: sets.NewString(), + }, + limiter: limiter, + } +} + +// ActionFunc takes a timed value and returns false if the item must be retried, with an optional +// time.Duration if some minimum wait interval should be used. +type ActionFunc func(TimedValue) (bool, time.Duration) + +// Try processes the queue. Ends prematurely if RateLimiter forbids an action and leak is true. +// Otherwise, requeues the item to be processed. Each value is processed once if fn returns true, +// otherwise it is added back to the queue. The returned remaining is used to identify the minimum +// time to execute the next item in the queue. +func (q *RateLimitedTimedQueue) Try(fn ActionFunc) { + val, ok := q.queue.Head() + for ok { + // rate limit the queue checking + if !q.limiter.TryAccept() { + glog.V(10).Info("Try rate limitted...") + // Try again later + break + } + + now := now() + if now.Before(val.ProcessAt) { + break + } + + if ok, wait := fn(val); !ok { + val.ProcessAt = now.Add(wait + 1) + q.queue.Replace(val) + } else { + q.queue.Remove(val.Value) + } + val, ok = q.queue.Head() + } +} + +// Adds value to the queue to be processed. Won't add the same value a second time if it was already +// added and not removed. +func (q *RateLimitedTimedQueue) Add(value string) bool { + now := now() + return q.queue.Add(TimedValue{ + Value: value, + AddedAt: now, + ProcessAt: now, + }) +} + +// Removes Node from the Evictor. The Node won't be processed until added again. +func (q *RateLimitedTimedQueue) Remove(value string) bool { + return q.queue.Remove(value) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/node/rate_limited_queue_test.go b/vendor/k8s.io/kubernetes/pkg/controller/node/rate_limited_queue_test.go new file mode 100644 index 000000000..56b7cff01 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/node/rate_limited_queue_test.go @@ -0,0 +1,230 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 ( + "reflect" + "testing" + "time" + + "k8s.io/kubernetes/pkg/util/flowcontrol" + "k8s.io/kubernetes/pkg/util/sets" +) + +func CheckQueueEq(lhs []string, rhs TimedQueue) bool { + for i := 0; i < len(lhs); i++ { + if rhs[i].Value != lhs[i] { + return false + } + } + return true +} + +func CheckSetEq(lhs, rhs sets.String) bool { + return lhs.HasAll(rhs.List()...) && rhs.HasAll(lhs.List()...) +} + +func TestAddNode(t *testing.T) { + evictor := NewRateLimitedTimedQueue(flowcontrol.NewFakeAlwaysRateLimiter()) + evictor.Add("first") + evictor.Add("second") + evictor.Add("third") + + queuePattern := []string{"first", "second", "third"} + if len(evictor.queue.queue) != len(queuePattern) { + t.Fatalf("Queue %v should have length %d", evictor.queue.queue, len(queuePattern)) + } + if !CheckQueueEq(queuePattern, evictor.queue.queue) { + t.Errorf("Invalid queue. Got %v, expected %v", evictor.queue.queue, queuePattern) + } + + setPattern := sets.NewString("first", "second", "third") + if len(evictor.queue.set) != len(setPattern) { + t.Fatalf("Map %v should have length %d", evictor.queue.set, len(setPattern)) + } + if !CheckSetEq(setPattern, evictor.queue.set) { + t.Errorf("Invalid map. Got %v, expected %v", evictor.queue.set, setPattern) + } +} + +func TestDelNode(t *testing.T) { + evictor := NewRateLimitedTimedQueue(flowcontrol.NewFakeAlwaysRateLimiter()) + evictor.Add("first") + evictor.Add("second") + evictor.Add("third") + evictor.Remove("first") + + queuePattern := []string{"second", "third"} + if len(evictor.queue.queue) != len(queuePattern) { + t.Fatalf("Queue %v should have length %d", evictor.queue.queue, len(queuePattern)) + } + if !CheckQueueEq(queuePattern, evictor.queue.queue) { + t.Errorf("Invalid queue. Got %v, expected %v", evictor.queue.queue, queuePattern) + } + + setPattern := sets.NewString("second", "third") + if len(evictor.queue.set) != len(setPattern) { + t.Fatalf("Map %v should have length %d", evictor.queue.set, len(setPattern)) + } + if !CheckSetEq(setPattern, evictor.queue.set) { + t.Errorf("Invalid map. Got %v, expected %v", evictor.queue.set, setPattern) + } + + evictor = NewRateLimitedTimedQueue(flowcontrol.NewFakeAlwaysRateLimiter()) + evictor.Add("first") + evictor.Add("second") + evictor.Add("third") + evictor.Remove("second") + + queuePattern = []string{"first", "third"} + if len(evictor.queue.queue) != len(queuePattern) { + t.Fatalf("Queue %v should have length %d", evictor.queue.queue, len(queuePattern)) + } + if !CheckQueueEq(queuePattern, evictor.queue.queue) { + t.Errorf("Invalid queue. Got %v, expected %v", evictor.queue.queue, queuePattern) + } + + setPattern = sets.NewString("first", "third") + if len(evictor.queue.set) != len(setPattern) { + t.Fatalf("Map %v should have length %d", evictor.queue.set, len(setPattern)) + } + if !CheckSetEq(setPattern, evictor.queue.set) { + t.Errorf("Invalid map. Got %v, expected %v", evictor.queue.set, setPattern) + } + + evictor = NewRateLimitedTimedQueue(flowcontrol.NewFakeAlwaysRateLimiter()) + evictor.Add("first") + evictor.Add("second") + evictor.Add("third") + evictor.Remove("third") + + queuePattern = []string{"first", "second"} + if len(evictor.queue.queue) != len(queuePattern) { + t.Fatalf("Queue %v should have length %d", evictor.queue.queue, len(queuePattern)) + } + if !CheckQueueEq(queuePattern, evictor.queue.queue) { + t.Errorf("Invalid queue. Got %v, expected %v", evictor.queue.queue, queuePattern) + } + + setPattern = sets.NewString("first", "second") + if len(evictor.queue.set) != len(setPattern) { + t.Fatalf("Map %v should have length %d", evictor.queue.set, len(setPattern)) + } + if !CheckSetEq(setPattern, evictor.queue.set) { + t.Errorf("Invalid map. Got %v, expected %v", evictor.queue.set, setPattern) + } +} + +func TestTry(t *testing.T) { + evictor := NewRateLimitedTimedQueue(flowcontrol.NewFakeAlwaysRateLimiter()) + evictor.Add("first") + evictor.Add("second") + evictor.Add("third") + evictor.Remove("second") + + deletedMap := sets.NewString() + evictor.Try(func(value TimedValue) (bool, time.Duration) { + deletedMap.Insert(value.Value) + return true, 0 + }) + + setPattern := sets.NewString("first", "third") + if len(deletedMap) != len(setPattern) { + t.Fatalf("Map %v should have length %d", evictor.queue.set, len(setPattern)) + } + if !CheckSetEq(setPattern, deletedMap) { + t.Errorf("Invalid map. Got %v, expected %v", deletedMap, setPattern) + } +} + +func TestTryOrdering(t *testing.T) { + evictor := NewRateLimitedTimedQueue(flowcontrol.NewFakeAlwaysRateLimiter()) + evictor.Add("first") + evictor.Add("second") + evictor.Add("third") + + order := []string{} + count := 0 + queued := false + evictor.Try(func(value TimedValue) (bool, time.Duration) { + count++ + if value.AddedAt.IsZero() { + t.Fatalf("added should not be zero") + } + if value.ProcessAt.IsZero() { + t.Fatalf("next should not be zero") + } + if !queued && value.Value == "second" { + queued = true + return false, time.Millisecond + } + order = append(order, value.Value) + return true, 0 + }) + if !reflect.DeepEqual(order, []string{"first", "third"}) { + t.Fatalf("order was wrong: %v", order) + } + if count != 3 { + t.Fatalf("unexpected iterations: %d", count) + } +} + +func TestTryRemovingWhileTry(t *testing.T) { + evictor := NewRateLimitedTimedQueue(flowcontrol.NewFakeAlwaysRateLimiter()) + evictor.Add("first") + evictor.Add("second") + evictor.Add("third") + + processing := make(chan struct{}) + wait := make(chan struct{}) + order := []string{} + count := 0 + queued := false + + // while the Try function is processing "second", remove it from the queue + // we should not see "second" retried. + go func() { + <-processing + evictor.Remove("second") + close(wait) + }() + + evictor.Try(func(value TimedValue) (bool, time.Duration) { + count++ + if value.AddedAt.IsZero() { + t.Fatalf("added should not be zero") + } + if value.ProcessAt.IsZero() { + t.Fatalf("next should not be zero") + } + if !queued && value.Value == "second" { + queued = true + close(processing) + <-wait + return false, time.Millisecond + } + order = append(order, value.Value) + return true, 0 + }) + + if !reflect.DeepEqual(order, []string{"first", "third"}) { + t.Fatalf("order was wrong: %v", order) + } + if count != 3 { + t.Fatalf("unexpected iterations: %d", count) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/OWNERS b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/OWNERS new file mode 100644 index 000000000..b9e1568ab --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/OWNERS @@ -0,0 +1,3 @@ +assignees: + - saad-ali + - thockin diff --git a/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/options/options.go b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/options/options.go new file mode 100644 index 000000000..c3b6c175a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/options/options.go @@ -0,0 +1,87 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 options + +import ( + "time" + + "github.com/spf13/pflag" +) + +// VolumeConfigFlags is used to bind CLI flags to variables. This top-level struct contains *all* enumerated +// CLI flags meant to configure all volume plugins. From this config, the binary will create many instances +// of volume.VolumeConfig which are then passed to the appropriate plugin. The ControllerManager binary is the only +// part of the code which knows what plugins are supported and which CLI flags correspond to each plugin. +type VolumeConfigFlags struct { + PersistentVolumeRecyclerMaximumRetry int + PersistentVolumeRecyclerMinimumTimeoutNFS int + PersistentVolumeRecyclerPodTemplateFilePathNFS string + PersistentVolumeRecyclerIncrementTimeoutNFS int + PersistentVolumeRecyclerPodTemplateFilePathHostPath string + PersistentVolumeRecyclerMinimumTimeoutHostPath int + PersistentVolumeRecyclerIncrementTimeoutHostPath int + EnableHostPathProvisioning bool +} + +type PersistentVolumeControllerOptions struct { + PVClaimBinderSyncPeriod time.Duration + VolumeConfigFlags VolumeConfigFlags +} + +func NewPersistentVolumeControllerOptions() PersistentVolumeControllerOptions { + return PersistentVolumeControllerOptions{ + PVClaimBinderSyncPeriod: 10 * time.Minute, + VolumeConfigFlags: VolumeConfigFlags{ + // default values here + PersistentVolumeRecyclerMaximumRetry: 3, + PersistentVolumeRecyclerMinimumTimeoutNFS: 300, + PersistentVolumeRecyclerIncrementTimeoutNFS: 30, + PersistentVolumeRecyclerMinimumTimeoutHostPath: 60, + PersistentVolumeRecyclerIncrementTimeoutHostPath: 30, + EnableHostPathProvisioning: false, + }, + } +} + +func (o *PersistentVolumeControllerOptions) AddFlags(fs *pflag.FlagSet) { + fs.DurationVar(&o.PVClaimBinderSyncPeriod, "pvclaimbinder-sync-period", o.PVClaimBinderSyncPeriod, + "The period for syncing persistent volumes and persistent volume claims") + fs.StringVar(&o.VolumeConfigFlags.PersistentVolumeRecyclerPodTemplateFilePathNFS, + "pv-recycler-pod-template-filepath-nfs", o.VolumeConfigFlags.PersistentVolumeRecyclerPodTemplateFilePathNFS, + "The file path to a pod definition used as a template for NFS persistent volume recycling") + fs.IntVar(&o.VolumeConfigFlags.PersistentVolumeRecyclerMinimumTimeoutNFS, "pv-recycler-minimum-timeout-nfs", + o.VolumeConfigFlags.PersistentVolumeRecyclerMinimumTimeoutNFS, "The minimum ActiveDeadlineSeconds to use for an NFS Recycler pod") + fs.IntVar(&o.VolumeConfigFlags.PersistentVolumeRecyclerIncrementTimeoutNFS, "pv-recycler-increment-timeout-nfs", + o.VolumeConfigFlags.PersistentVolumeRecyclerIncrementTimeoutNFS, "the increment of time added per Gi to ActiveDeadlineSeconds for an NFS scrubber pod") + fs.StringVar(&o.VolumeConfigFlags.PersistentVolumeRecyclerPodTemplateFilePathHostPath, "pv-recycler-pod-template-filepath-hostpath", + o.VolumeConfigFlags.PersistentVolumeRecyclerPodTemplateFilePathHostPath, + "The file path to a pod definition used as a template for HostPath persistent volume recycling. "+ + "This is for development and testing only and will not work in a multi-node cluster.") + fs.IntVar(&o.VolumeConfigFlags.PersistentVolumeRecyclerMinimumTimeoutHostPath, "pv-recycler-minimum-timeout-hostpath", + o.VolumeConfigFlags.PersistentVolumeRecyclerMinimumTimeoutHostPath, + "The minimum ActiveDeadlineSeconds to use for a HostPath Recycler pod. This is for development and testing only and will not work in a multi-node cluster.") + fs.IntVar(&o.VolumeConfigFlags.PersistentVolumeRecyclerIncrementTimeoutHostPath, "pv-recycler-timeout-increment-hostpath", + o.VolumeConfigFlags.PersistentVolumeRecyclerIncrementTimeoutHostPath, + "the increment of time added per Gi to ActiveDeadlineSeconds for a HostPath scrubber pod. "+ + "This is for development and testing only and will not work in a multi-node cluster.") + fs.IntVar(&o.VolumeConfigFlags.PersistentVolumeRecyclerMaximumRetry, "pv-recycler-maximum-retry", + o.VolumeConfigFlags.PersistentVolumeRecyclerMaximumRetry, + "Maximum number of attempts to recycle or delete a persistent volume") + fs.BoolVar(&o.VolumeConfigFlags.EnableHostPathProvisioning, "enable-hostpath-provisioner", o.VolumeConfigFlags.EnableHostPathProvisioning, + "Enable HostPath PV provisioning when running without a cloud provider. This allows testing and development of provisioning features. "+ + "HostPath provisioning is not supported in any way, won't work in a multi-node cluster, and should not be used for anything other than testing or development.") +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_claim_binder_controller.go b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_claim_binder_controller.go new file mode 100644 index 000000000..12de9f017 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_claim_binder_controller.go @@ -0,0 +1,526 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 persistentvolume + +import ( + "fmt" + "sync" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/conversion" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/watch" + + "github.com/golang/glog" +) + +// PersistentVolumeClaimBinder is a controller that synchronizes PersistentVolumeClaims. +type PersistentVolumeClaimBinder struct { + volumeIndex *persistentVolumeOrderedIndex + volumeController *framework.Controller + claimController *framework.Controller + client binderClient + stopChannels map[string]chan struct{} + lock sync.RWMutex +} + +// NewPersistentVolumeClaimBinder creates a new PersistentVolumeClaimBinder +func NewPersistentVolumeClaimBinder(kubeClient clientset.Interface, syncPeriod time.Duration) *PersistentVolumeClaimBinder { + volumeIndex := NewPersistentVolumeOrderedIndex() + binderClient := NewBinderClient(kubeClient) + binder := &PersistentVolumeClaimBinder{ + volumeIndex: volumeIndex, + client: binderClient, + } + + _, volumeController := framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return kubeClient.Core().PersistentVolumes().List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return kubeClient.Core().PersistentVolumes().Watch(options) + }, + }, + &api.PersistentVolume{}, + // TODO: Can we have much longer period here? + syncPeriod, + framework.ResourceEventHandlerFuncs{ + AddFunc: binder.addVolume, + UpdateFunc: binder.updateVolume, + DeleteFunc: binder.deleteVolume, + }, + ) + _, claimController := framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return kubeClient.Core().PersistentVolumeClaims(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return kubeClient.Core().PersistentVolumeClaims(api.NamespaceAll).Watch(options) + }, + }, + &api.PersistentVolumeClaim{}, + // TODO: Can we have much longer period here? + syncPeriod, + framework.ResourceEventHandlerFuncs{ + AddFunc: binder.addClaim, + UpdateFunc: binder.updateClaim, + DeleteFunc: binder.deleteClaim, + }, + ) + + binder.claimController = claimController + binder.volumeController = volumeController + + return binder +} +func (binder *PersistentVolumeClaimBinder) addVolume(obj interface{}) { + binder.lock.Lock() + defer binder.lock.Unlock() + pv, ok := obj.(*api.PersistentVolume) + if !ok { + glog.Errorf("Expected PersistentVolume but handler received %+v", obj) + return + } + if err := syncVolume(binder.volumeIndex, binder.client, pv); err != nil { + glog.Errorf("PVClaimBinder could not add volume %s: %+v", pv.Name, err) + } +} + +func (binder *PersistentVolumeClaimBinder) updateVolume(oldObj, newObj interface{}) { + binder.lock.Lock() + defer binder.lock.Unlock() + newVolume, ok := newObj.(*api.PersistentVolume) + if !ok { + glog.Errorf("Expected PersistentVolume but handler received %+v", newObj) + return + } + if err := binder.volumeIndex.Update(newVolume); err != nil { + glog.Errorf("Error updating volume %s in index: %v", newVolume.Name, err) + return + } + if err := syncVolume(binder.volumeIndex, binder.client, newVolume); err != nil { + glog.Errorf("PVClaimBinder could not update volume %s: %+v", newVolume.Name, err) + } +} + +func (binder *PersistentVolumeClaimBinder) deleteVolume(obj interface{}) { + binder.lock.Lock() + defer binder.lock.Unlock() + volume, ok := obj.(*api.PersistentVolume) + if !ok { + glog.Errorf("Expected PersistentVolume but handler received %+v", obj) + return + } + if err := binder.volumeIndex.Delete(volume); err != nil { + glog.Errorf("Error deleting volume %s from index: %v", volume.Name, err) + } +} + +func (binder *PersistentVolumeClaimBinder) addClaim(obj interface{}) { + binder.lock.Lock() + defer binder.lock.Unlock() + claim, ok := obj.(*api.PersistentVolumeClaim) + if !ok { + glog.Errorf("Expected PersistentVolumeClaim but handler received %+v", obj) + return + } + if err := syncClaim(binder.volumeIndex, binder.client, claim); err != nil { + glog.Errorf("PVClaimBinder could not add claim %s: %+v", claim.Name, err) + } +} + +func (binder *PersistentVolumeClaimBinder) updateClaim(oldObj, newObj interface{}) { + binder.lock.Lock() + defer binder.lock.Unlock() + newClaim, ok := newObj.(*api.PersistentVolumeClaim) + if !ok { + glog.Errorf("Expected PersistentVolumeClaim but handler received %+v", newObj) + return + } + if err := syncClaim(binder.volumeIndex, binder.client, newClaim); err != nil { + glog.Errorf("PVClaimBinder could not update claim %s: %+v", newClaim.Name, err) + } +} + +func (binder *PersistentVolumeClaimBinder) deleteClaim(obj interface{}) { + binder.lock.Lock() + defer binder.lock.Unlock() + var volume *api.PersistentVolume + if pvc, ok := obj.(*api.PersistentVolumeClaim); ok { + if pvObj, exists, _ := binder.volumeIndex.GetByKey(pvc.Spec.VolumeName); exists { + if pv, ok := pvObj.(*api.PersistentVolume); ok { + volume = pv + } + } + } + if unk, ok := obj.(cache.DeletedFinalStateUnknown); ok && unk.Obj != nil { + if pv, ok := unk.Obj.(*api.PersistentVolume); ok { + volume = pv + } + } + + // sync the volume when its claim is deleted. Explicitly sync'ing the volume here in response to + // claim deletion prevents the volume from waiting until the next sync period for its Release. + if volume != nil { + err := syncVolume(binder.volumeIndex, binder.client, volume) + if err != nil { + glog.Errorf("PVClaimBinder could not update volume %s from deleteClaim handler: %+v", volume.Name, err) + } + } +} + +func syncVolume(volumeIndex *persistentVolumeOrderedIndex, binderClient binderClient, volume *api.PersistentVolume) (err error) { + glog.V(5).Infof("Synchronizing PersistentVolume[%s], current phase: %s\n", volume.Name, volume.Status.Phase) + + // The PV may have been modified by parallel call to syncVolume, load + // the current version. + newPv, err := binderClient.GetPersistentVolume(volume.Name) + if err != nil { + return fmt.Errorf("Cannot reload volume %s: %v", volume.Name, err) + } + volume = newPv + + // volumes can be in one of the following states: + // + // VolumePending -- default value -- not bound to a claim and not yet processed through this controller. + // VolumeAvailable -- not bound to a claim, but processed at least once and found in this controller's volumeIndex. + // VolumeBound -- bound to a claim because volume.Spec.ClaimRef != nil. Claim status may not be correct. + // VolumeReleased -- volume.Spec.ClaimRef != nil but the claim has been deleted by the user. + // VolumeFailed -- volume.Spec.ClaimRef != nil and the volume failed processing in the recycler + currentPhase := volume.Status.Phase + nextPhase := currentPhase + + // Always store the newest volume state in local cache. + _, exists, err := volumeIndex.Get(volume) + if err != nil { + return err + } + if !exists { + volumeIndex.Add(volume) + } else { + volumeIndex.Update(volume) + } + + if isBeingProvisioned(volume) { + glog.V(4).Infof("Skipping PersistentVolume[%s], waiting for provisioning to finish", volume.Name) + return nil + } + + switch currentPhase { + case api.VolumePending: + + // 4 possible states: + // 1. ClaimRef != nil, Claim exists, Claim UID == ClaimRef UID: Prebound to claim. Make volume available for binding (it will match PVC). + // 2. ClaimRef != nil, Claim exists, Claim UID != ClaimRef UID: Recently recycled. Remove bind. Make volume available for new claim. + // 3. ClaimRef != nil, Claim !exists: Recently recycled. Remove bind. Make volume available for new claim. + // 4. ClaimRef == nil: Neither recycled nor prebound. Make volume available for binding. + nextPhase = api.VolumeAvailable + + if volume.Spec.ClaimRef != nil { + claim, err := binderClient.GetPersistentVolumeClaim(volume.Spec.ClaimRef.Namespace, volume.Spec.ClaimRef.Name) + switch { + case err != nil && !errors.IsNotFound(err): + return fmt.Errorf("Error getting PersistentVolumeClaim[%s/%s]: %v", volume.Spec.ClaimRef.Namespace, volume.Spec.ClaimRef.Name, err) + case errors.IsNotFound(err) || (claim != nil && claim.UID != volume.Spec.ClaimRef.UID): + glog.V(5).Infof("PersistentVolume[%s] has a claim ref to a claim which does not exist", volume.Name) + if volume.Spec.PersistentVolumeReclaimPolicy == api.PersistentVolumeReclaimRecycle { + // Pending volumes that have a ClaimRef where the claim is missing were recently recycled. + // The Recycler set the phase to VolumePending to start the volume at the beginning of this lifecycle. + // removing ClaimRef unbinds the volume + clone, err := conversion.NewCloner().DeepCopy(volume) + if err != nil { + return fmt.Errorf("Error cloning pv: %v", err) + } + volumeClone, ok := clone.(*api.PersistentVolume) + if !ok { + return fmt.Errorf("Unexpected pv cast error : %v\n", volumeClone) + } + glog.V(5).Infof("PersistentVolume[%s] is recently recycled; remove claimRef.", volume.Name) + volumeClone.Spec.ClaimRef = nil + + if updatedVolume, err := binderClient.UpdatePersistentVolume(volumeClone); err != nil { + return fmt.Errorf("Unexpected error saving PersistentVolume: %+v", err) + } else { + volume = updatedVolume + volumeIndex.Update(volume) + } + } else { + // Pending volumes that has a ClaimRef and the claim is missing and is was not recycled. + // It must have been freshly provisioned and the claim was deleted during the provisioning. + // Mark the volume as Released, it will be deleted. + nextPhase = api.VolumeReleased + } + } + + // Dynamically provisioned claims remain Pending until its volume is completely provisioned. + // The provisioner updates the PV and triggers this update for the volume. Explicitly sync'ing + // the claim here prevents the need to wait until the next sync period when the claim would normally + // advance to Bound phase. Otherwise, the maximum wait time for the claim to be Bound is the default sync period. + if claim != nil && claim.Status.Phase == api.ClaimPending && keyExists(qosProvisioningKey, claim.Annotations) && isProvisioningComplete(volume) { + syncClaim(volumeIndex, binderClient, claim) + } + } + glog.V(5).Infof("PersistentVolume[%s] is available\n", volume.Name) + + // available volumes await a claim + case api.VolumeAvailable: + if volume.Spec.ClaimRef != nil { + _, err := binderClient.GetPersistentVolumeClaim(volume.Spec.ClaimRef.Namespace, volume.Spec.ClaimRef.Name) + if err == nil { + // change of phase will trigger an update event with the newly bound volume + glog.V(5).Infof("PersistentVolume[%s] is now bound\n", volume.Name) + nextPhase = api.VolumeBound + } else { + if errors.IsNotFound(err) { + nextPhase = api.VolumeReleased + } + } + } + + //bound volumes require verification of their bound claims + case api.VolumeBound: + if volume.Spec.ClaimRef == nil { + return fmt.Errorf("PersistentVolume[%s] expected to be bound but found nil claimRef: %+v", volume.Name, volume) + } else { + claim, err := binderClient.GetPersistentVolumeClaim(volume.Spec.ClaimRef.Namespace, volume.Spec.ClaimRef.Name) + + // A volume is Released when its bound claim cannot be found in the API server. + // A claim by the same name can be found if deleted and recreated before this controller can release + // the volume from the original claim, so a UID check is necessary. + if err != nil { + if errors.IsNotFound(err) { + nextPhase = api.VolumeReleased + } else { + return err + } + } else if claim != nil && claim.UID != volume.Spec.ClaimRef.UID { + nextPhase = api.VolumeReleased + } + } + + // released volumes require recycling + case api.VolumeReleased: + if volume.Spec.ClaimRef == nil { + return fmt.Errorf("PersistentVolume[%s] expected to be bound but found nil claimRef: %+v", volume.Name, volume) + } else { + // another process is watching for released volumes. + // PersistentVolumeReclaimPolicy is set per PersistentVolume + // Recycle - sets the PV to Pending and back under this controller's management + // Delete - delete events are handled by this controller's watch. PVs are removed from the index. + } + + // volumes are removed by processes external to this binder and must be removed from the cluster + case api.VolumeFailed: + if volume.Spec.ClaimRef == nil { + return fmt.Errorf("PersistentVolume[%s] expected to be bound but found nil claimRef: %+v", volume.Name, volume) + } else { + glog.V(5).Infof("PersistentVolume[%s] previously failed recycling. Skipping.\n", volume.Name) + } + } + + if currentPhase != nextPhase { + volume.Status.Phase = nextPhase + + // a change in state will trigger another update through this controller. + // each pass through this controller evaluates current phase and decides whether or not to change to the next phase + glog.V(5).Infof("PersistentVolume[%s] changing phase from %s to %s\n", volume.Name, currentPhase, nextPhase) + volume, err := binderClient.UpdatePersistentVolumeStatus(volume) + if err != nil { + // Rollback to previous phase + volume.Status.Phase = currentPhase + } + volumeIndex.Update(volume) + } + + return nil +} + +func syncClaim(volumeIndex *persistentVolumeOrderedIndex, binderClient binderClient, claim *api.PersistentVolumeClaim) (err error) { + glog.V(5).Infof("Synchronizing PersistentVolumeClaim[%s] for binding", claim.Name) + + // The claim may have been modified by parallel call to syncClaim, load + // the current version. + newClaim, err := binderClient.GetPersistentVolumeClaim(claim.Namespace, claim.Name) + if err != nil { + return fmt.Errorf("Cannot reload claim %s/%s: %v", claim.Namespace, claim.Name, err) + } + claim = newClaim + + switch claim.Status.Phase { + case api.ClaimPending: + // claims w/ a storage-class annotation for provisioning with *only* match volumes with a ClaimRef of the claim. + volume, err := volumeIndex.findBestMatchForClaim(claim) + if err != nil { + return err + } + + if volume == nil { + glog.V(5).Infof("A volume match does not exist for persistent claim: %s", claim.Name) + return nil + } + + if isBeingProvisioned(volume) { + glog.V(5).Infof("PersistentVolume[%s] for PersistentVolumeClaim[%s/%s] is still being provisioned.", volume.Name, claim.Namespace, claim.Name) + return nil + } + + claimRef, err := api.GetReference(claim) + if err != nil { + return fmt.Errorf("Unexpected error getting claim reference: %v\n", err) + } + + // Make a binding reference to the claim by persisting claimRef on the volume. + // The local cache must be updated with the new bind to prevent subsequent + // claims from binding to the volume. + if volume.Spec.ClaimRef == nil { + clone, err := conversion.NewCloner().DeepCopy(volume) + if err != nil { + return fmt.Errorf("Error cloning pv: %v", err) + } + volumeClone, ok := clone.(*api.PersistentVolume) + if !ok { + return fmt.Errorf("Unexpected pv cast error : %v\n", volumeClone) + } + volumeClone.Spec.ClaimRef = claimRef + if updatedVolume, err := binderClient.UpdatePersistentVolume(volumeClone); err != nil { + return fmt.Errorf("Unexpected error saving PersistentVolume.Status: %+v", err) + } else { + volume = updatedVolume + volumeIndex.Update(updatedVolume) + } + } + + // the bind is persisted on the volume above and will always match the claim in a search. + // claim would remain Pending if the update fails, so processing this state is idempotent. + // this only needs to be processed once. + if claim.Spec.VolumeName != volume.Name { + claim.Spec.VolumeName = volume.Name + claim, err = binderClient.UpdatePersistentVolumeClaim(claim) + if err != nil { + return fmt.Errorf("Error updating claim with VolumeName %s: %+v\n", volume.Name, err) + } + } + + claim.Status.Phase = api.ClaimBound + claim.Status.AccessModes = volume.Spec.AccessModes + claim.Status.Capacity = volume.Spec.Capacity + _, err = binderClient.UpdatePersistentVolumeClaimStatus(claim) + if err != nil { + return fmt.Errorf("Unexpected error saving claim status: %+v", err) + } + + case api.ClaimBound: + // no-op. Claim is bound, values from PV are set. PVCs are technically mutable in the API server + // and we don't want to handle those changes at this time. + + default: + return fmt.Errorf("Unknown state for PVC: %#v", claim) + + } + + glog.V(5).Infof("PersistentVolumeClaim[%s] is bound\n", claim.Name) + return nil +} + +func isBeingProvisioned(volume *api.PersistentVolume) bool { + value, found := volume.Annotations[pvProvisioningRequiredAnnotationKey] + if found && value != pvProvisioningCompletedAnnotationValue { + return true + } + return false +} + +// Run starts all of this binder's control loops +func (controller *PersistentVolumeClaimBinder) Run() { + glog.V(5).Infof("Starting PersistentVolumeClaimBinder\n") + if controller.stopChannels == nil { + controller.stopChannels = make(map[string]chan struct{}) + } + + if _, exists := controller.stopChannels["volumes"]; !exists { + controller.stopChannels["volumes"] = make(chan struct{}) + go controller.volumeController.Run(controller.stopChannels["volumes"]) + } + + if _, exists := controller.stopChannels["claims"]; !exists { + controller.stopChannels["claims"] = make(chan struct{}) + go controller.claimController.Run(controller.stopChannels["claims"]) + } +} + +// Stop gracefully shuts down this binder +func (controller *PersistentVolumeClaimBinder) Stop() { + glog.V(5).Infof("Stopping PersistentVolumeClaimBinder\n") + for name, stopChan := range controller.stopChannels { + close(stopChan) + delete(controller.stopChannels, name) + } +} + +// binderClient abstracts access to PVs and PVCs +type binderClient interface { + GetPersistentVolume(name string) (*api.PersistentVolume, error) + UpdatePersistentVolume(volume *api.PersistentVolume) (*api.PersistentVolume, error) + DeletePersistentVolume(volume *api.PersistentVolume) error + UpdatePersistentVolumeStatus(volume *api.PersistentVolume) (*api.PersistentVolume, error) + GetPersistentVolumeClaim(namespace, name string) (*api.PersistentVolumeClaim, error) + UpdatePersistentVolumeClaim(claim *api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) + UpdatePersistentVolumeClaimStatus(claim *api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) +} + +func NewBinderClient(c clientset.Interface) binderClient { + return &realBinderClient{c} +} + +type realBinderClient struct { + client clientset.Interface +} + +func (c *realBinderClient) GetPersistentVolume(name string) (*api.PersistentVolume, error) { + return c.client.Core().PersistentVolumes().Get(name) +} + +func (c *realBinderClient) UpdatePersistentVolume(volume *api.PersistentVolume) (*api.PersistentVolume, error) { + return c.client.Core().PersistentVolumes().Update(volume) +} + +func (c *realBinderClient) DeletePersistentVolume(volume *api.PersistentVolume) error { + return c.client.Core().PersistentVolumes().Delete(volume.Name, nil) +} + +func (c *realBinderClient) UpdatePersistentVolumeStatus(volume *api.PersistentVolume) (*api.PersistentVolume, error) { + return c.client.Core().PersistentVolumes().UpdateStatus(volume) +} + +func (c *realBinderClient) GetPersistentVolumeClaim(namespace, name string) (*api.PersistentVolumeClaim, error) { + return c.client.Core().PersistentVolumeClaims(namespace).Get(name) +} + +func (c *realBinderClient) UpdatePersistentVolumeClaim(claim *api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) { + return c.client.Core().PersistentVolumeClaims(claim.Namespace).Update(claim) +} + +func (c *realBinderClient) UpdatePersistentVolumeClaimStatus(claim *api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) { + return c.client.Core().PersistentVolumeClaims(claim.Namespace).UpdateStatus(claim) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_claim_binder_controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_claim_binder_controller_test.go new file mode 100644 index 000000000..f01908c7f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_claim_binder_controller_test.go @@ -0,0 +1,732 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 persistentvolume + +import ( + "fmt" + "os" + "reflect" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/client/cache" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/types" + utiltesting "k8s.io/kubernetes/pkg/util/testing" + "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/pkg/volume/host_path" + volumetest "k8s.io/kubernetes/pkg/volume/testing" +) + +func TestRunStop(t *testing.T) { + clientset := fake.NewSimpleClientset() + binder := NewPersistentVolumeClaimBinder(clientset, 1*time.Second) + + if len(binder.stopChannels) != 0 { + t.Errorf("Non-running binder should not have any stopChannels. Got %v", len(binder.stopChannels)) + } + + binder.Run() + + if len(binder.stopChannels) != 2 { + t.Errorf("Running binder should have exactly 2 stopChannels. Got %v", len(binder.stopChannels)) + } + + binder.Stop() + + if len(binder.stopChannels) != 0 { + t.Errorf("Non-running binder should not have any stopChannels. Got %v", len(binder.stopChannels)) + } +} + +func TestClaimRace(t *testing.T) { + tmpDir, err := utiltesting.MkTmpdir("claimbinder-test") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + c1 := &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "c1", + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("3Gi"), + }, + }, + }, + Status: api.PersistentVolumeClaimStatus{ + Phase: api.ClaimPending, + }, + } + c1.ObjectMeta.SelfLink = testapi.Default.SelfLink("pvc", "") + + c2 := &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "c2", + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("3Gi"), + }, + }, + }, + Status: api.PersistentVolumeClaimStatus{ + Phase: api.ClaimPending, + }, + } + c2.ObjectMeta.SelfLink = testapi.Default.SelfLink("pvc", "") + + v := &api.PersistentVolume{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + }, + Spec: api.PersistentVolumeSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10Gi"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{ + Path: fmt.Sprintf("%s/data01", tmpDir), + }, + }, + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + }, + } + + volumeIndex := NewPersistentVolumeOrderedIndex() + mockClient := &mockBinderClient{} + mockClient.volume = v + + plugMgr := volume.VolumePluginMgr{} + plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newMockRecycler, volume.VolumeConfig{}), volumetest.NewFakeVolumeHost(tmpDir, nil, nil)) + // adds the volume to the index, making the volume available + syncVolume(volumeIndex, mockClient, v) + if mockClient.volume.Status.Phase != api.VolumeAvailable { + t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase) + } + if _, exists, _ := volumeIndex.Get(v); !exists { + t.Errorf("Expected to find volume in index but it did not exist") + } + + // add the claim to fake API server + mockClient.UpdatePersistentVolumeClaim(c1) + // an initial sync for a claim matches the volume + err = syncClaim(volumeIndex, mockClient, c1) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if c1.Status.Phase != api.ClaimBound { + t.Errorf("Expected phase %s but got %s", api.ClaimBound, c1.Status.Phase) + } + + // before the volume gets updated w/ claimRef, a 2nd claim can attempt to bind and find the same volume + // add the 2nd claim to fake API server + mockClient.UpdatePersistentVolumeClaim(c2) + err = syncClaim(volumeIndex, mockClient, c2) + if err != nil { + t.Errorf("unexpected error for unmatched claim: %v", err) + } + if c2.Status.Phase != api.ClaimPending { + t.Errorf("Expected phase %s but got %s", api.ClaimPending, c2.Status.Phase) + } +} + +func TestNewClaimWithSameNameAsOldClaim(t *testing.T) { + c1 := &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "c1", + Namespace: "foo", + UID: "12345", + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("3Gi"), + }, + }, + }, + Status: api.PersistentVolumeClaimStatus{ + Phase: api.ClaimBound, + }, + } + c1.ObjectMeta.SelfLink = testapi.Default.SelfLink("pvc", "") + + v := &api.PersistentVolume{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + }, + Spec: api.PersistentVolumeSpec{ + ClaimRef: &api.ObjectReference{ + Name: c1.Name, + Namespace: c1.Namespace, + UID: "45678", + }, + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10Gi"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{ + Path: "/tmp/data01", + }, + }, + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + }, + } + + volumeIndex := NewPersistentVolumeOrderedIndex() + mockClient := &mockBinderClient{ + claim: c1, + volume: v, + } + + plugMgr := volume.VolumePluginMgr{} + plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newMockRecycler, volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil)) + + syncVolume(volumeIndex, mockClient, v) + if mockClient.volume.Status.Phase != api.VolumeReleased { + t.Errorf("Expected phase %s but got %s", api.VolumeReleased, mockClient.volume.Status.Phase) + } + +} + +func TestClaimSyncAfterVolumeProvisioning(t *testing.T) { + tmpDir, err := utiltesting.MkTmpdir("claimbinder-test") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Tests that binder.syncVolume will also syncClaim if the PV has completed + // provisioning but the claim is still Pending. We want to advance to Bound + // without having to wait until the binder's next sync period. + claim := &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "bar", + Annotations: map[string]string{ + qosProvisioningKey: "foo", + }, + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("3Gi"), + }, + }, + }, + Status: api.PersistentVolumeClaimStatus{ + Phase: api.ClaimPending, + }, + } + claim.ObjectMeta.SelfLink = testapi.Default.SelfLink("pvc", "") + claimRef, _ := api.GetReference(claim) + + pv := &api.PersistentVolume{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Annotations: map[string]string{ + pvProvisioningRequiredAnnotationKey: pvProvisioningCompletedAnnotationValue, + }, + }, + Spec: api.PersistentVolumeSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10Gi"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{ + Path: fmt.Sprintf("%s/data01", tmpDir), + }, + }, + ClaimRef: claimRef, + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + }, + } + + volumeIndex := NewPersistentVolumeOrderedIndex() + mockClient := &mockBinderClient{ + claim: claim, + volume: pv, + } + + plugMgr := volume.VolumePluginMgr{} + plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newMockRecycler, volume.VolumeConfig{}), volumetest.NewFakeVolumeHost(tmpDir, nil, nil)) + + // adds the volume to the index, making the volume available. + // pv also completed provisioning, so syncClaim should cause claim's phase to advance to Bound + syncVolume(volumeIndex, mockClient, pv) + if mockClient.volume.Status.Phase != api.VolumeAvailable { + t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase) + } + if mockClient.claim.Status.Phase != api.ClaimBound { + t.Errorf("Expected phase %s but got %s", api.ClaimBound, claim.Status.Phase) + } +} + +func TestExampleObjects(t *testing.T) { + scenarios := map[string]struct { + expected interface{} + }{ + "claims/claim-01.yaml": { + expected: &api.PersistentVolumeClaim{ + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("3Gi"), + }, + }, + }, + }, + }, + "claims/claim-02.yaml": { + expected: &api.PersistentVolumeClaim{ + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("8Gi"), + }, + }, + }, + }, + }, + "volumes/local-01.yaml": { + expected: &api.PersistentVolume{ + Spec: api.PersistentVolumeSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10Gi"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{ + Path: "/somepath/data01", + }, + }, + }, + }, + }, + "volumes/local-02.yaml": { + expected: &api.PersistentVolume{ + Spec: api.PersistentVolumeSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("8Gi"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{ + Path: "/somepath/data02", + }, + }, + PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRecycle, + }, + }, + }, + } + + for name, scenario := range scenarios { + codec := api.Codecs.UniversalDecoder() + o := core.NewObjects(api.Scheme, codec) + if err := core.AddObjectsFromPath("../../../docs/user-guide/persistent-volumes/"+name, o, codec); err != nil { + t.Fatal(err) + } + + clientset := &fake.Clientset{} + clientset.AddReactor("*", "*", core.ObjectReaction(o, registered.RESTMapper())) + + if reflect.TypeOf(scenario.expected) == reflect.TypeOf(&api.PersistentVolumeClaim{}) { + pvc, err := clientset.Core().PersistentVolumeClaims("ns").Get("doesntmatter") + if err != nil { + t.Fatalf("Error retrieving object: %v", err) + } + + expected := scenario.expected.(*api.PersistentVolumeClaim) + if pvc.Spec.AccessModes[0] != expected.Spec.AccessModes[0] { + t.Errorf("Unexpected mismatch. Got %v wanted %v", pvc.Spec.AccessModes[0], expected.Spec.AccessModes[0]) + } + + aQty := pvc.Spec.Resources.Requests[api.ResourceStorage] + bQty := expected.Spec.Resources.Requests[api.ResourceStorage] + aSize := aQty.Value() + bSize := bQty.Value() + + if aSize != bSize { + t.Errorf("Unexpected mismatch. Got %v wanted %v", aSize, bSize) + } + } + + if reflect.TypeOf(scenario.expected) == reflect.TypeOf(&api.PersistentVolume{}) { + pv, err := clientset.Core().PersistentVolumes().Get("doesntmatter") + if err != nil { + t.Fatalf("Error retrieving object: %v", err) + } + + expected := scenario.expected.(*api.PersistentVolume) + if pv.Spec.AccessModes[0] != expected.Spec.AccessModes[0] { + t.Errorf("Unexpected mismatch. Got %v wanted %v", pv.Spec.AccessModes[0], expected.Spec.AccessModes[0]) + } + + aQty := pv.Spec.Capacity[api.ResourceStorage] + bQty := expected.Spec.Capacity[api.ResourceStorage] + aSize := aQty.Value() + bSize := bQty.Value() + + if aSize != bSize { + t.Errorf("Unexpected mismatch. Got %v wanted %v", aSize, bSize) + } + + if pv.Spec.HostPath.Path != expected.Spec.HostPath.Path { + t.Errorf("Unexpected mismatch. Got %v wanted %v", pv.Spec.HostPath.Path, expected.Spec.HostPath.Path) + } + } + } +} + +func TestBindingWithExamples(t *testing.T) { + tmpDir, err := utiltesting.MkTmpdir("claimbinder-test") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + codec := api.Codecs.UniversalDecoder() + o := core.NewObjects(api.Scheme, codec) + if err := core.AddObjectsFromPath("../../../docs/user-guide/persistent-volumes/claims/claim-01.yaml", o, codec); err != nil { + t.Fatal(err) + } + if err := core.AddObjectsFromPath("../../../docs/user-guide/persistent-volumes/volumes/local-01.yaml", o, codec); err != nil { + t.Fatal(err) + } + + clientset := &fake.Clientset{} + clientset.AddReactor("*", "*", core.ObjectReaction(o, registered.RESTMapper())) + + pv, err := clientset.Core().PersistentVolumes().Get("any") + if err != nil { + t.Errorf("Unexpected error getting PV from client: %v", err) + } + pv.Spec.PersistentVolumeReclaimPolicy = api.PersistentVolumeReclaimRecycle + if err != nil { + t.Errorf("Unexpected error getting PV from client: %v", err) + } + pv.ObjectMeta.SelfLink = testapi.Default.SelfLink("pv", "") + + // the default value of the PV is Pending. if processed at least once, its status in etcd is Available. + // There was a bug where only Pending volumes were being indexed and made ready for claims. + // Test that !Pending gets correctly added + pv.Status.Phase = api.VolumeAvailable + + claim, error := clientset.Core().PersistentVolumeClaims("ns").Get("any") + if error != nil { + t.Errorf("Unexpected error getting PVC from client: %v", err) + } + claim.ObjectMeta.SelfLink = testapi.Default.SelfLink("pvc", "") + + volumeIndex := NewPersistentVolumeOrderedIndex() + mockClient := &mockBinderClient{ + volume: pv, + claim: claim, + } + + plugMgr := volume.VolumePluginMgr{} + plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newMockRecycler, volume.VolumeConfig{}), volumetest.NewFakeVolumeHost(tmpDir, nil, nil)) + + recycler := &PersistentVolumeRecycler{ + kubeClient: clientset, + client: mockClient, + pluginMgr: plugMgr, + } + + // adds the volume to the index, making the volume available + syncVolume(volumeIndex, mockClient, pv) + if mockClient.volume.Status.Phase != api.VolumeAvailable { + t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase) + } + + // add the claim to fake API server + mockClient.UpdatePersistentVolumeClaim(claim) + // an initial sync for a claim will bind it to an unbound volume + syncClaim(volumeIndex, mockClient, claim) + + // bind expected on pv.Spec but status update hasn't happened yet + if mockClient.volume.Spec.ClaimRef == nil { + t.Errorf("Expected ClaimRef but got nil for pv.Status.ClaimRef\n") + } + if mockClient.volume.Status.Phase != api.VolumeAvailable { + t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase) + } + if mockClient.claim.Spec.VolumeName != pv.Name { + t.Errorf("Expected claim.Spec.VolumeName %s but got %s", mockClient.claim.Spec.VolumeName, pv.Name) + } + if mockClient.claim.Status.Phase != api.ClaimBound { + t.Errorf("Expected phase %s but got %s", api.ClaimBound, claim.Status.Phase) + } + + // state changes in pvc triggers sync that sets pv attributes to pvc.Status + syncClaim(volumeIndex, mockClient, claim) + if len(mockClient.claim.Status.AccessModes) == 0 { + t.Errorf("Expected %d access modes but got 0", len(pv.Spec.AccessModes)) + } + + // persisting the bind to pv.Spec.ClaimRef triggers a sync + syncVolume(volumeIndex, mockClient, mockClient.volume) + if mockClient.volume.Status.Phase != api.VolumeBound { + t.Errorf("Expected phase %s but got %s", api.VolumeBound, mockClient.volume.Status.Phase) + } + + // pretend the user deleted their claim. periodic resync picks it up. + mockClient.claim = nil + syncVolume(volumeIndex, mockClient, mockClient.volume) + + if mockClient.volume.Status.Phase != api.VolumeReleased { + t.Errorf("Expected phase %s but got %s", api.VolumeReleased, mockClient.volume.Status.Phase) + } + + // released volumes with a PersistentVolumeReclaimPolicy (recycle/delete) can have further processing + err = recycler.reclaimVolume(mockClient.volume) + if err != nil { + t.Errorf("Unexpected error reclaiming volume: %+v", err) + } + if mockClient.volume.Status.Phase != api.VolumePending { + t.Errorf("Expected phase %s but got %s", api.VolumePending, mockClient.volume.Status.Phase) + } + + // after the recycling changes the phase to Pending, the binder picks up again + // to remove any vestiges of binding and make the volume Available again + syncVolume(volumeIndex, mockClient, mockClient.volume) + + if mockClient.volume.Status.Phase != api.VolumeAvailable { + t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase) + } + if mockClient.volume.Spec.ClaimRef != nil { + t.Errorf("Expected nil ClaimRef: %+v", mockClient.volume.Spec.ClaimRef) + } +} + +func TestCasting(t *testing.T) { + clientset := fake.NewSimpleClientset() + binder := NewPersistentVolumeClaimBinder(clientset, 1*time.Second) + + pv := &api.PersistentVolume{} + unk := cache.DeletedFinalStateUnknown{} + pvc := &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Status: api.PersistentVolumeClaimStatus{Phase: api.ClaimBound}, + } + + // Inject mockClient into the binder. This prevents weird errors on stderr + // as the binder wants to load PV/PVC from API server. + mockClient := &mockBinderClient{ + volume: pv, + claim: pvc, + } + binder.client = mockClient + + // none of these should fail casting. + // the real test is not failing when passed DeletedFinalStateUnknown in the deleteHandler + binder.addVolume(pv) + binder.updateVolume(pv, pv) + binder.deleteVolume(pv) + binder.deleteVolume(unk) + binder.addClaim(pvc) + binder.updateClaim(pvc, pvc) +} + +func TestRecycledPersistentVolumeUID(t *testing.T) { + tmpDir, err := utiltesting.MkTmpdir("claimbinder-test") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + codec := api.Codecs.UniversalDecoder() + o := core.NewObjects(api.Scheme, codec) + if err := core.AddObjectsFromPath("../../../docs/user-guide/persistent-volumes/claims/claim-01.yaml", o, codec); err != nil { + t.Fatal(err) + } + if err := core.AddObjectsFromPath("../../../docs/user-guide/persistent-volumes/volumes/local-01.yaml", o, codec); err != nil { + t.Fatal(err) + } + + clientset := &fake.Clientset{} + clientset.AddReactor("*", "*", core.ObjectReaction(o, registered.RESTMapper())) + + pv, err := clientset.Core().PersistentVolumes().Get("any") + if err != nil { + t.Errorf("Unexpected error getting PV from client: %v", err) + } + pv.Spec.PersistentVolumeReclaimPolicy = api.PersistentVolumeReclaimRecycle + if err != nil { + t.Errorf("Unexpected error getting PV from client: %v", err) + } + pv.ObjectMeta.SelfLink = testapi.Default.SelfLink("pv", "") + + // the default value of the PV is Pending. if processed at least once, its status in etcd is Available. + // There was a bug where only Pending volumes were being indexed and made ready for claims. + // Test that !Pending gets correctly added + pv.Status.Phase = api.VolumeAvailable + + claim, error := clientset.Core().PersistentVolumeClaims("ns").Get("any") + if error != nil { + t.Errorf("Unexpected error getting PVC from client: %v", err) + } + claim.ObjectMeta.SelfLink = testapi.Default.SelfLink("pvc", "") + claim.ObjectMeta.UID = types.UID("uid1") + + volumeIndex := NewPersistentVolumeOrderedIndex() + mockClient := &mockBinderClient{ + volume: pv, + claim: claim, + } + + plugMgr := volume.VolumePluginMgr{} + plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newMockRecycler, volume.VolumeConfig{}), volumetest.NewFakeVolumeHost(tmpDir, nil, nil)) + + recycler := &PersistentVolumeRecycler{ + kubeClient: clientset, + client: mockClient, + pluginMgr: plugMgr, + } + + // adds the volume to the index, making the volume available + syncVolume(volumeIndex, mockClient, pv) + if mockClient.volume.Status.Phase != api.VolumeAvailable { + t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase) + } + + // add the claim to fake API server + mockClient.UpdatePersistentVolumeClaim(claim) + // an initial sync for a claim will bind it to an unbound volume + syncClaim(volumeIndex, mockClient, claim) + + // pretend the user deleted their claim. periodic resync picks it up. + mockClient.claim = nil + syncVolume(volumeIndex, mockClient, mockClient.volume) + + if mockClient.volume.Status.Phase != api.VolumeReleased { + t.Errorf("Expected phase %s but got %s", api.VolumeReleased, mockClient.volume.Status.Phase) + } + + // released volumes with a PersistentVolumeReclaimPolicy (recycle/delete) can have further processing + err = recycler.reclaimVolume(mockClient.volume) + if err != nil { + t.Errorf("Unexpected error reclaiming volume: %+v", err) + } + if mockClient.volume.Status.Phase != api.VolumePending { + t.Errorf("Expected phase %s but got %s", api.VolumePending, mockClient.volume.Status.Phase) + } + + // after the recycling changes the phase to Pending, the binder picks up again + // to remove any vestiges of binding and make the volume Available again + // + // explicitly set the claim's UID to a different value to ensure that a new claim with the same + // name as what the PV was previously bound still yields an available volume + claim.ObjectMeta.UID = types.UID("uid2") + mockClient.claim = claim + syncVolume(volumeIndex, mockClient, mockClient.volume) + + if mockClient.volume.Status.Phase != api.VolumeAvailable { + t.Errorf("Expected phase %s but got %s", api.VolumeAvailable, mockClient.volume.Status.Phase) + } + if mockClient.volume.Spec.ClaimRef != nil { + t.Errorf("Expected nil ClaimRef: %+v", mockClient.volume.Spec.ClaimRef) + } +} + +type mockBinderClient struct { + volume *api.PersistentVolume + claim *api.PersistentVolumeClaim +} + +func (c *mockBinderClient) GetPersistentVolume(name string) (*api.PersistentVolume, error) { + return c.volume, nil +} + +func (c *mockBinderClient) UpdatePersistentVolume(volume *api.PersistentVolume) (*api.PersistentVolume, error) { + c.volume = volume + return c.volume, nil +} + +func (c *mockBinderClient) DeletePersistentVolume(volume *api.PersistentVolume) error { + c.volume = nil + return nil +} + +func (c *mockBinderClient) UpdatePersistentVolumeStatus(volume *api.PersistentVolume) (*api.PersistentVolume, error) { + c.volume = volume + return c.volume, nil +} + +func (c *mockBinderClient) GetPersistentVolumeClaim(namespace, name string) (*api.PersistentVolumeClaim, error) { + if c.claim != nil { + return c.claim, nil + } else { + return nil, errors.NewNotFound(api.Resource("persistentvolumes"), name) + } +} + +func (c *mockBinderClient) UpdatePersistentVolumeClaim(claim *api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) { + c.claim = claim + return c.claim, nil +} + +func (c *mockBinderClient) UpdatePersistentVolumeClaimStatus(claim *api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) { + c.claim = claim + return c.claim, nil +} + +func newMockRecycler(spec *volume.Spec, host volume.VolumeHost, config volume.VolumeConfig) (volume.Recycler, error) { + return &mockRecycler{ + path: spec.PersistentVolume.Spec.HostPath.Path, + }, nil +} + +type mockRecycler struct { + path string + host volume.VolumeHost + volume.MetricsNil +} + +func (r *mockRecycler) GetPath() string { + return r.path +} + +func (r *mockRecycler) Recycle() error { + // return nil means recycle passed + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_index_test.go b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_index_test.go new file mode 100644 index 000000000..7bb6c5387 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_index_test.go @@ -0,0 +1,549 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 persistentvolume + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/testapi" +) + +func TestMatchVolume(t *testing.T) { + volList := NewPersistentVolumeOrderedIndex() + for _, pv := range createTestVolumes() { + volList.Add(pv) + } + + scenarios := map[string]struct { + expectedMatch string + claim *api.PersistentVolumeClaim + }{ + "successful-match-gce-10": { + expectedMatch: "gce-pd-10", + claim: &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "claim01", + Namespace: "myns", + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany, api.ReadWriteOnce}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("8G"), + }, + }, + }, + }, + }, + "successful-match-nfs-5": { + expectedMatch: "nfs-5", + claim: &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "claim01", + Namespace: "myns", + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany, api.ReadWriteOnce, api.ReadWriteMany}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("5G"), + }, + }, + }, + }, + }, + "successful-skip-1g-bound-volume": { + expectedMatch: "gce-pd-5", + claim: &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "claim01", + Namespace: "myns", + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany, api.ReadWriteOnce}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("1G"), + }, + }, + }, + }, + }, + "successful-no-match": { + expectedMatch: "", + claim: &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "claim01", + Namespace: "myns", + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany, api.ReadWriteOnce}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("999G"), + }, + }, + }, + }, + }, + } + + for name, scenario := range scenarios { + volume, err := volList.findBestMatchForClaim(scenario.claim) + if err != nil { + t.Errorf("Unexpected error matching volume by claim: %v", err) + } + if len(scenario.expectedMatch) != 0 && volume == nil { + t.Errorf("Expected match but received nil volume for scenario: %s", name) + } + if len(scenario.expectedMatch) != 0 && volume != nil && string(volume.UID) != scenario.expectedMatch { + t.Errorf("Expected %s but got volume %s in scenario %s", scenario.expectedMatch, volume.UID, name) + } + if len(scenario.expectedMatch) == 0 && volume != nil { + t.Errorf("Unexpected match for scenario: %s", name) + } + } +} + +func TestMatchingWithBoundVolumes(t *testing.T) { + volumeIndex := NewPersistentVolumeOrderedIndex() + // two similar volumes, one is bound + pv1 := &api.PersistentVolume{ + ObjectMeta: api.ObjectMeta{ + UID: "gce-pd-1", + Name: "gce001", + }, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("1G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, + }, + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce, api.ReadOnlyMany}, + // this one we're pretending is already bound + ClaimRef: &api.ObjectReference{UID: "abc123"}, + }, + } + + pv2 := &api.PersistentVolume{ + ObjectMeta: api.ObjectMeta{ + UID: "gce-pd-2", + Name: "gce002", + }, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("1G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, + }, + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce, api.ReadOnlyMany}, + }, + } + + volumeIndex.Add(pv1) + volumeIndex.Add(pv2) + + claim := &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "claim01", + Namespace: "myns", + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany, api.ReadWriteOnce}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("1G"), + }, + }, + }, + } + + volume, err := volumeIndex.findBestMatchForClaim(claim) + if err != nil { + t.Fatalf("Unexpected error matching volume by claim: %v", err) + } + if volume == nil { + t.Fatalf("Unexpected nil volume. Expected %s", pv2.Name) + } + if pv2.Name != volume.Name { + t.Errorf("Expected %s but got volume %s instead", pv2.Name, volume.Name) + } +} + +func TestSort(t *testing.T) { + volList := NewPersistentVolumeOrderedIndex() + for _, pv := range createTestVolumes() { + volList.Add(pv) + } + + volumes, err := volList.ListByAccessModes([]api.PersistentVolumeAccessMode{api.ReadWriteOnce, api.ReadOnlyMany}) + if err != nil { + t.Error("Unexpected error retrieving volumes by access modes:", err) + } + + for i, expected := range []string{"gce-pd-1", "gce-pd-5", "gce-pd-10"} { + if string(volumes[i].UID) != expected { + t.Errorf("Incorrect ordering of persistent volumes. Expected %s but got %s", expected, volumes[i].UID) + } + } + + volumes, err = volList.ListByAccessModes([]api.PersistentVolumeAccessMode{api.ReadWriteOnce, api.ReadOnlyMany, api.ReadWriteMany}) + if err != nil { + t.Error("Unexpected error retrieving volumes by access modes:", err) + } + + for i, expected := range []string{"nfs-1", "nfs-5", "nfs-10"} { + if string(volumes[i].UID) != expected { + t.Errorf("Incorrect ordering of persistent volumes. Expected %s but got %s", expected, volumes[i].UID) + } + } +} + +func TestAllPossibleAccessModes(t *testing.T) { + index := NewPersistentVolumeOrderedIndex() + for _, pv := range createTestVolumes() { + index.Add(pv) + } + + // the mock PVs creates contain 2 types of accessmodes: RWO+ROX and RWO+ROW+RWX + possibleModes := index.allPossibleMatchingAccessModes([]api.PersistentVolumeAccessMode{api.ReadWriteOnce}) + if len(possibleModes) != 2 { + t.Errorf("Expected 2 arrays of modes that match RWO, but got %v", len(possibleModes)) + } + for _, m := range possibleModes { + if !contains(m, api.ReadWriteOnce) { + t.Errorf("AccessModes does not contain %s", api.ReadWriteOnce) + } + } + + possibleModes = index.allPossibleMatchingAccessModes([]api.PersistentVolumeAccessMode{api.ReadWriteMany}) + if len(possibleModes) != 1 { + t.Errorf("Expected 1 array of modes that match RWX, but got %v", len(possibleModes)) + } + if !contains(possibleModes[0], api.ReadWriteMany) { + t.Errorf("AccessModes does not contain %s", api.ReadWriteOnce) + } + +} + +func TestFindingVolumeWithDifferentAccessModes(t *testing.T) { + gce := &api.PersistentVolume{ + ObjectMeta: api.ObjectMeta{UID: "001", Name: "gce"}, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("10G")}, + PersistentVolumeSource: api.PersistentVolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}}, + AccessModes: []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + api.ReadOnlyMany, + }, + }, + } + + ebs := &api.PersistentVolume{ + ObjectMeta: api.ObjectMeta{UID: "002", Name: "ebs"}, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("10G")}, + PersistentVolumeSource: api.PersistentVolumeSource{AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{}}, + AccessModes: []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + }, + }, + } + + nfs := &api.PersistentVolume{ + ObjectMeta: api.ObjectMeta{UID: "003", Name: "nfs"}, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("10G")}, + PersistentVolumeSource: api.PersistentVolumeSource{NFS: &api.NFSVolumeSource{}}, + AccessModes: []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + api.ReadOnlyMany, + api.ReadWriteMany, + }, + }, + } + + claim := &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "claim01", + Namespace: "myns", + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Resources: api.ResourceRequirements{Requests: api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("1G")}}, + }, + } + + index := NewPersistentVolumeOrderedIndex() + index.Add(gce) + index.Add(ebs) + index.Add(nfs) + + volume, _ := index.findBestMatchForClaim(claim) + if volume.Name != ebs.Name { + t.Errorf("Expected %s but got volume %s instead", ebs.Name, volume.Name) + } + + claim.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteOnce, api.ReadOnlyMany} + volume, _ = index.findBestMatchForClaim(claim) + if volume.Name != gce.Name { + t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name) + } + + // order of the requested modes should not matter + claim.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteMany, api.ReadWriteOnce, api.ReadOnlyMany} + volume, _ = index.findBestMatchForClaim(claim) + if volume.Name != nfs.Name { + t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name) + } + + // fewer modes requested should still match + claim.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteMany} + volume, _ = index.findBestMatchForClaim(claim) + if volume.Name != nfs.Name { + t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name) + } + + // pretend the exact match is bound. should get the next level up of modes. + ebs.Spec.ClaimRef = &api.ObjectReference{} + claim.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteOnce} + volume, _ = index.findBestMatchForClaim(claim) + if volume.Name != gce.Name { + t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name) + } + + // continue up the levels of modes. + gce.Spec.ClaimRef = &api.ObjectReference{} + claim.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteOnce} + volume, _ = index.findBestMatchForClaim(claim) + if volume.Name != nfs.Name { + t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name) + } + + // partial mode request + gce.Spec.ClaimRef = nil + claim.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadOnlyMany} + volume, _ = index.findBestMatchForClaim(claim) + if volume.Name != gce.Name { + t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name) + } +} + +func createTestVolumes() []*api.PersistentVolume { + // these volumes are deliberately out-of-order to test indexing and sorting + return []*api.PersistentVolume{ + { + ObjectMeta: api.ObjectMeta{ + UID: "gce-pd-10", + Name: "gce003", + }, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, + }, + AccessModes: []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + api.ReadOnlyMany, + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "gce-pd-20", + Name: "gce004", + }, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("20G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, + }, + AccessModes: []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + api.ReadOnlyMany, + }, + // this one we're pretending is already bound + ClaimRef: &api.ObjectReference{UID: "def456"}, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "nfs-5", + Name: "nfs002", + }, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("5G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + Glusterfs: &api.GlusterfsVolumeSource{}, + }, + AccessModes: []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + api.ReadOnlyMany, + api.ReadWriteMany, + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "gce-pd-1", + Name: "gce001", + }, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("1G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, + }, + AccessModes: []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + api.ReadOnlyMany, + }, + // this one we're pretending is already bound + ClaimRef: &api.ObjectReference{UID: "abc123"}, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "nfs-10", + Name: "nfs003", + }, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + Glusterfs: &api.GlusterfsVolumeSource{}, + }, + AccessModes: []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + api.ReadOnlyMany, + api.ReadWriteMany, + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "gce-pd-5", + Name: "gce002", + }, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("5G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}, + }, + AccessModes: []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + api.ReadOnlyMany, + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "nfs-1", + Name: "nfs001", + }, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("1G"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + Glusterfs: &api.GlusterfsVolumeSource{}, + }, + AccessModes: []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + api.ReadOnlyMany, + api.ReadWriteMany, + }, + }, + }, + } +} + +func testVolume(name, size string) *api.PersistentVolume { + return &api.PersistentVolume{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Annotations: map[string]string{}, + }, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse(size)}, + PersistentVolumeSource: api.PersistentVolumeSource{HostPath: &api.HostPathVolumeSource{}}, + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + }, + } +} + +func TestFindingPreboundVolumes(t *testing.T) { + claim := &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "claim01", + Namespace: "myns", + SelfLink: testapi.Default.SelfLink("pvc", ""), + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Resources: api.ResourceRequirements{Requests: api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("1Gi")}}, + }, + } + claimRef, err := api.GetReference(claim) + if err != nil { + t.Errorf("error getting claimRef: %v", err) + } + + pv1 := testVolume("pv1", "1Gi") + pv5 := testVolume("pv5", "5Gi") + pv8 := testVolume("pv8", "8Gi") + + index := NewPersistentVolumeOrderedIndex() + index.Add(pv1) + index.Add(pv5) + index.Add(pv8) + + // expected exact match on size + volume, _ := index.findBestMatchForClaim(claim) + if volume.Name != pv1.Name { + t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name) + } + + // pretend the exact match is pre-bound. should get the next size up. + pv1.Spec.ClaimRef = &api.ObjectReference{Name: "foo", Namespace: "bar"} + volume, _ = index.findBestMatchForClaim(claim) + if volume.Name != pv5.Name { + t.Errorf("Expected %s but got volume %s instead", pv5.Name, volume.Name) + } + + // pretend the exact match is available but the largest volume is pre-bound to the claim. + pv1.Spec.ClaimRef = nil + pv8.Spec.ClaimRef = claimRef + volume, _ = index.findBestMatchForClaim(claim) + if volume.Name != pv8.Name { + t.Errorf("Expected %s but got volume %s instead", pv8.Name, volume.Name) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_provisioner_controller.go b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_provisioner_controller.go new file mode 100644 index 000000000..fdb7804a3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_provisioner_controller.go @@ -0,0 +1,536 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 persistentvolume + +import ( + "fmt" + "sync" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/conversion" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/io" + "k8s.io/kubernetes/pkg/util/mount" + "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/pkg/watch" + + "github.com/golang/glog" +) + +// PersistentVolumeProvisionerController reconciles the state of all PersistentVolumes and PersistentVolumeClaims. +type PersistentVolumeProvisionerController struct { + volumeController *framework.Controller + volumeStore cache.Store + claimController *framework.Controller + claimStore cache.Store + client controllerClient + cloud cloudprovider.Interface + provisioner volume.ProvisionableVolumePlugin + pluginMgr volume.VolumePluginMgr + stopChannels map[string]chan struct{} + mutex sync.RWMutex + clusterName string +} + +// constant name values for the controllers stopChannels map. +// the controller uses these for graceful shutdown +const volumesStopChannel = "volumes" +const claimsStopChannel = "claims" + +// NewPersistentVolumeProvisionerController creates a new PersistentVolumeProvisionerController +func NewPersistentVolumeProvisionerController(client controllerClient, syncPeriod time.Duration, clusterName string, plugins []volume.VolumePlugin, provisioner volume.ProvisionableVolumePlugin, cloud cloudprovider.Interface) (*PersistentVolumeProvisionerController, error) { + controller := &PersistentVolumeProvisionerController{ + client: client, + cloud: cloud, + provisioner: provisioner, + clusterName: clusterName, + } + + if err := controller.pluginMgr.InitPlugins(plugins, controller); err != nil { + return nil, fmt.Errorf("Could not initialize volume plugins for PersistentVolumeProvisionerController: %+v", err) + } + + glog.V(5).Infof("Initializing provisioner: %s", controller.provisioner.Name()) + controller.provisioner.Init(controller) + + controller.volumeStore, controller.volumeController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return client.ListPersistentVolumes(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return client.WatchPersistentVolumes(options) + }, + }, + &api.PersistentVolume{}, + syncPeriod, + framework.ResourceEventHandlerFuncs{ + AddFunc: controller.handleAddVolume, + UpdateFunc: controller.handleUpdateVolume, + // delete handler not needed in this controller. + // volume deletion is handled by the recycler controller + }, + ) + controller.claimStore, controller.claimController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return client.ListPersistentVolumeClaims(api.NamespaceAll, options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return client.WatchPersistentVolumeClaims(api.NamespaceAll, options) + }, + }, + &api.PersistentVolumeClaim{}, + syncPeriod, + framework.ResourceEventHandlerFuncs{ + AddFunc: controller.handleAddClaim, + UpdateFunc: controller.handleUpdateClaim, + // delete handler not needed. + // normal recycling applies when a claim is deleted. + // recycling is handled by the binding controller. + }, + ) + + return controller, nil +} + +func (controller *PersistentVolumeProvisionerController) handleAddVolume(obj interface{}) { + controller.mutex.Lock() + defer controller.mutex.Unlock() + cachedPv, _, _ := controller.volumeStore.Get(obj) + if pv, ok := cachedPv.(*api.PersistentVolume); ok { + err := controller.reconcileVolume(pv) + if err != nil { + glog.Errorf("Error reconciling volume %s: %+v", pv.Name, err) + } + } +} + +func (controller *PersistentVolumeProvisionerController) handleUpdateVolume(oldObj, newObj interface{}) { + // The flow for Update is the same as Add. + // A volume is only provisioned if not done so already. + controller.handleAddVolume(newObj) +} + +func (controller *PersistentVolumeProvisionerController) handleAddClaim(obj interface{}) { + controller.mutex.Lock() + defer controller.mutex.Unlock() + cachedPvc, exists, _ := controller.claimStore.Get(obj) + if !exists { + glog.Errorf("PersistentVolumeClaim does not exist in the local cache: %+v", obj) + return + } + if pvc, ok := cachedPvc.(*api.PersistentVolumeClaim); ok { + err := controller.reconcileClaim(pvc) + if err != nil { + glog.Errorf("Error encoutered reconciling claim %s: %+v", pvc.Name, err) + } + } +} + +func (controller *PersistentVolumeProvisionerController) handleUpdateClaim(oldObj, newObj interface{}) { + // The flow for Update is the same as Add. + // A volume is only provisioned for a claim if not done so already. + controller.handleAddClaim(newObj) +} + +func (controller *PersistentVolumeProvisionerController) reconcileClaim(claim *api.PersistentVolumeClaim) error { + glog.V(5).Infof("Synchronizing PersistentVolumeClaim[%s] for dynamic provisioning", claim.Name) + + // The claim may have been modified by parallel call to reconcileClaim, load + // the current version. + newClaim, err := controller.client.GetPersistentVolumeClaim(claim.Namespace, claim.Name) + if err != nil { + return fmt.Errorf("Cannot reload claim %s/%s: %v", claim.Namespace, claim.Name, err) + } + claim = newClaim + err = controller.claimStore.Update(claim) + if err != nil { + return fmt.Errorf("Cannot update claim %s/%s: %v", claim.Namespace, claim.Name, err) + } + + if controller.provisioner == nil { + return fmt.Errorf("No provisioner configured for controller") + } + + // no provisioning requested, return Pending. Claim may be pending indefinitely without a match. + if !keyExists(qosProvisioningKey, claim.Annotations) { + glog.V(5).Infof("PersistentVolumeClaim[%s] no provisioning required", claim.Name) + return nil + } + if len(claim.Spec.VolumeName) != 0 { + glog.V(5).Infof("PersistentVolumeClaim[%s] already bound. No provisioning required", claim.Name) + return nil + } + if isAnnotationMatch(pvProvisioningRequiredAnnotationKey, pvProvisioningCompletedAnnotationValue, claim.Annotations) { + glog.V(5).Infof("PersistentVolumeClaim[%s] is already provisioned.", claim.Name) + return nil + } + + glog.V(5).Infof("PersistentVolumeClaim[%s] provisioning", claim.Name) + provisioner, err := controller.newProvisioner(controller.provisioner, claim, nil) + if err != nil { + return fmt.Errorf("Unexpected error getting new provisioner for claim %s: %v\n", claim.Name, err) + } + newVolume, err := provisioner.NewPersistentVolumeTemplate() + if err != nil { + return fmt.Errorf("Unexpected error getting new volume template for claim %s: %v\n", claim.Name, err) + } + + claimRef, err := api.GetReference(claim) + if err != nil { + return fmt.Errorf("Unexpected error getting claim reference for %s: %v\n", claim.Name, err) + } + + storageClass, _ := claim.Annotations[qosProvisioningKey] + + // the creation of this volume is the bind to the claim. + // The claim will match the volume during the next sync period when the volume is in the local cache + newVolume.Spec.ClaimRef = claimRef + newVolume.Annotations[pvProvisioningRequiredAnnotationKey] = "true" + newVolume.Annotations[qosProvisioningKey] = storageClass + newVolume, err = controller.client.CreatePersistentVolume(newVolume) + glog.V(5).Infof("Unprovisioned PersistentVolume[%s] created for PVC[%s], which will be fulfilled in the storage provider", newVolume.Name, claim.Name) + if err != nil { + return fmt.Errorf("PersistentVolumeClaim[%s] failed provisioning: %+v", claim.Name, err) + } + + claim.Annotations[pvProvisioningRequiredAnnotationKey] = pvProvisioningCompletedAnnotationValue + _, err = controller.client.UpdatePersistentVolumeClaim(claim) + if err != nil { + glog.Errorf("error updating persistent volume claim: %v", err) + } + + return nil +} + +func (controller *PersistentVolumeProvisionerController) reconcileVolume(pv *api.PersistentVolume) error { + glog.V(5).Infof("PersistentVolume[%s] reconciling", pv.Name) + + // The PV may have been modified by parallel call to reconcileVolume, load + // the current version. + newPv, err := controller.client.GetPersistentVolume(pv.Name) + if err != nil { + return fmt.Errorf("Cannot reload volume %s: %v", pv.Name, err) + } + pv = newPv + + if pv.Spec.ClaimRef == nil { + glog.V(5).Infof("PersistentVolume[%s] is not bound to a claim. No provisioning required", pv.Name) + return nil + } + + // TODO: fix this leaky abstraction. Had to make our own store key because ClaimRef fails the default keyfunc (no Meta on object). + obj, exists, _ := controller.claimStore.GetByKey(fmt.Sprintf("%s/%s", pv.Spec.ClaimRef.Namespace, pv.Spec.ClaimRef.Name)) + if !exists { + return fmt.Errorf("PersistentVolumeClaim[%s/%s] not found in local cache", pv.Spec.ClaimRef.Namespace, pv.Spec.ClaimRef.Name) + } + + claim, ok := obj.(*api.PersistentVolumeClaim) + if !ok { + return fmt.Errorf("PersistentVolumeClaim expected, but got %v", obj) + } + + // no provisioning required, volume is ready and Bound + if !keyExists(pvProvisioningRequiredAnnotationKey, pv.Annotations) { + glog.V(5).Infof("PersistentVolume[%s] does not require provisioning", pv.Name) + return nil + } + + // provisioning is completed, volume is ready. + if isProvisioningComplete(pv) { + glog.V(5).Infof("PersistentVolume[%s] is bound and provisioning is complete", pv.Name) + if pv.Spec.ClaimRef.Namespace != claim.Namespace || pv.Spec.ClaimRef.Name != claim.Name { + return fmt.Errorf("pre-bind mismatch - expected %s but found %s/%s", claimToClaimKey(claim), pv.Spec.ClaimRef.Namespace, pv.Spec.ClaimRef.Name) + } + return nil + } + + // provisioning is incomplete. Attempt to provision the volume. + glog.V(5).Infof("PersistentVolume[%s] provisioning in progress", pv.Name) + err = provisionVolume(pv, controller) + if err != nil { + return fmt.Errorf("Error provisioning PersistentVolume[%s]: %v", pv.Name, err) + } + + return nil +} + +// provisionVolume provisions a volume that has been created in the cluster but not yet fulfilled by +// the storage provider. +func provisionVolume(pv *api.PersistentVolume, controller *PersistentVolumeProvisionerController) error { + if isProvisioningComplete(pv) { + return fmt.Errorf("PersistentVolume[%s] is already provisioned", pv.Name) + } + + if _, exists := pv.Annotations[qosProvisioningKey]; !exists { + return fmt.Errorf("PersistentVolume[%s] does not contain a provisioning request. Provisioning not required.", pv.Name) + } + + if controller.provisioner == nil { + return fmt.Errorf("No provisioner found for volume: %s", pv.Name) + } + + // Find the claim in local cache + obj, exists, _ := controller.claimStore.GetByKey(fmt.Sprintf("%s/%s", pv.Spec.ClaimRef.Namespace, pv.Spec.ClaimRef.Name)) + if !exists { + return fmt.Errorf("Could not find PersistentVolumeClaim[%s/%s] in local cache", pv.Spec.ClaimRef.Name, pv.Name) + } + claim := obj.(*api.PersistentVolumeClaim) + + provisioner, _ := controller.newProvisioner(controller.provisioner, claim, pv) + err := provisioner.Provision(pv) + if err != nil { + glog.Errorf("Could not provision %s", pv.Name) + pv.Status.Phase = api.VolumeFailed + pv.Status.Message = err.Error() + if pv, apiErr := controller.client.UpdatePersistentVolumeStatus(pv); apiErr != nil { + return fmt.Errorf("PersistentVolume[%s] failed provisioning and also failed status update: %v - %v", pv.Name, err, apiErr) + } + return fmt.Errorf("PersistentVolume[%s] failed provisioning: %v", pv.Name, err) + } + + clone, err := conversion.NewCloner().DeepCopy(pv) + volumeClone, ok := clone.(*api.PersistentVolume) + if !ok { + return fmt.Errorf("Unexpected pv cast error : %v\n", volumeClone) + } + volumeClone.Annotations[pvProvisioningRequiredAnnotationKey] = pvProvisioningCompletedAnnotationValue + + pv, err = controller.client.UpdatePersistentVolume(volumeClone) + if err != nil { + // TODO: https://github.com/kubernetes/kubernetes/issues/14443 + // the volume was created in the infrastructure and likely has a PV name on it, + // but we failed to save the annotation that marks the volume as provisioned. + return fmt.Errorf("Error updating PersistentVolume[%s] with provisioning completed annotation. There is a potential for dupes and orphans.", volumeClone.Name) + } + return nil +} + +// Run starts all of this controller's control loops +func (controller *PersistentVolumeProvisionerController) Run() { + glog.V(5).Infof("Starting PersistentVolumeProvisionerController\n") + if controller.stopChannels == nil { + controller.stopChannels = make(map[string]chan struct{}) + } + + if _, exists := controller.stopChannels[volumesStopChannel]; !exists { + controller.stopChannels[volumesStopChannel] = make(chan struct{}) + go controller.volumeController.Run(controller.stopChannels[volumesStopChannel]) + } + + if _, exists := controller.stopChannels[claimsStopChannel]; !exists { + controller.stopChannels[claimsStopChannel] = make(chan struct{}) + go controller.claimController.Run(controller.stopChannels[claimsStopChannel]) + } +} + +// Stop gracefully shuts down this controller +func (controller *PersistentVolumeProvisionerController) Stop() { + glog.V(5).Infof("Stopping PersistentVolumeProvisionerController\n") + for name, stopChan := range controller.stopChannels { + close(stopChan) + delete(controller.stopChannels, name) + } +} + +func (controller *PersistentVolumeProvisionerController) newProvisioner(plugin volume.ProvisionableVolumePlugin, claim *api.PersistentVolumeClaim, pv *api.PersistentVolume) (volume.Provisioner, error) { + tags := make(map[string]string) + tags[cloudVolumeCreatedForClaimNamespaceTag] = claim.Namespace + tags[cloudVolumeCreatedForClaimNameTag] = claim.Name + + // pv can be nil when the provisioner has not created the PV yet + if pv != nil { + tags[cloudVolumeCreatedForVolumeNameTag] = pv.Name + } + + volumeOptions := volume.VolumeOptions{ + Capacity: claim.Spec.Resources.Requests[api.ResourceName(api.ResourceStorage)], + AccessModes: claim.Spec.AccessModes, + PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete, + CloudTags: &tags, + ClusterName: controller.clusterName, + } + + if pv != nil { + volumeOptions.PVName = pv.Name + } + + provisioner, err := plugin.NewProvisioner(volumeOptions) + return provisioner, err +} + +// controllerClient abstracts access to PVs and PVCs. Easy to mock for testing and wrap for real client. +type controllerClient interface { + CreatePersistentVolume(pv *api.PersistentVolume) (*api.PersistentVolume, error) + ListPersistentVolumes(options api.ListOptions) (*api.PersistentVolumeList, error) + WatchPersistentVolumes(options api.ListOptions) (watch.Interface, error) + GetPersistentVolume(name string) (*api.PersistentVolume, error) + UpdatePersistentVolume(volume *api.PersistentVolume) (*api.PersistentVolume, error) + DeletePersistentVolume(volume *api.PersistentVolume) error + UpdatePersistentVolumeStatus(volume *api.PersistentVolume) (*api.PersistentVolume, error) + + GetPersistentVolumeClaim(namespace, name string) (*api.PersistentVolumeClaim, error) + ListPersistentVolumeClaims(namespace string, options api.ListOptions) (*api.PersistentVolumeClaimList, error) + WatchPersistentVolumeClaims(namespace string, options api.ListOptions) (watch.Interface, error) + UpdatePersistentVolumeClaim(claim *api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) + UpdatePersistentVolumeClaimStatus(claim *api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) + + // provided to give VolumeHost and plugins access to the kube client + GetKubeClient() clientset.Interface +} + +func NewControllerClient(c clientset.Interface) controllerClient { + return &realControllerClient{c} +} + +var _ controllerClient = &realControllerClient{} + +type realControllerClient struct { + client clientset.Interface +} + +func (c *realControllerClient) GetPersistentVolume(name string) (*api.PersistentVolume, error) { + return c.client.Core().PersistentVolumes().Get(name) +} + +func (c *realControllerClient) ListPersistentVolumes(options api.ListOptions) (*api.PersistentVolumeList, error) { + return c.client.Core().PersistentVolumes().List(options) +} + +func (c *realControllerClient) WatchPersistentVolumes(options api.ListOptions) (watch.Interface, error) { + return c.client.Core().PersistentVolumes().Watch(options) +} + +func (c *realControllerClient) CreatePersistentVolume(pv *api.PersistentVolume) (*api.PersistentVolume, error) { + return c.client.Core().PersistentVolumes().Create(pv) +} + +func (c *realControllerClient) UpdatePersistentVolume(volume *api.PersistentVolume) (*api.PersistentVolume, error) { + return c.client.Core().PersistentVolumes().Update(volume) +} + +func (c *realControllerClient) DeletePersistentVolume(volume *api.PersistentVolume) error { + return c.client.Core().PersistentVolumes().Delete(volume.Name, nil) +} + +func (c *realControllerClient) UpdatePersistentVolumeStatus(volume *api.PersistentVolume) (*api.PersistentVolume, error) { + return c.client.Core().PersistentVolumes().UpdateStatus(volume) +} + +func (c *realControllerClient) GetPersistentVolumeClaim(namespace, name string) (*api.PersistentVolumeClaim, error) { + return c.client.Core().PersistentVolumeClaims(namespace).Get(name) +} + +func (c *realControllerClient) ListPersistentVolumeClaims(namespace string, options api.ListOptions) (*api.PersistentVolumeClaimList, error) { + return c.client.Core().PersistentVolumeClaims(namespace).List(options) +} + +func (c *realControllerClient) WatchPersistentVolumeClaims(namespace string, options api.ListOptions) (watch.Interface, error) { + return c.client.Core().PersistentVolumeClaims(namespace).Watch(options) +} + +func (c *realControllerClient) UpdatePersistentVolumeClaim(claim *api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) { + return c.client.Core().PersistentVolumeClaims(claim.Namespace).Update(claim) +} + +func (c *realControllerClient) UpdatePersistentVolumeClaimStatus(claim *api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) { + return c.client.Core().PersistentVolumeClaims(claim.Namespace).UpdateStatus(claim) +} + +func (c *realControllerClient) GetKubeClient() clientset.Interface { + return c.client +} + +func keyExists(key string, haystack map[string]string) bool { + _, exists := haystack[key] + return exists +} + +func isProvisioningComplete(pv *api.PersistentVolume) bool { + return isAnnotationMatch(pvProvisioningRequiredAnnotationKey, pvProvisioningCompletedAnnotationValue, pv.Annotations) +} + +func isAnnotationMatch(key, needle string, haystack map[string]string) bool { + value, exists := haystack[key] + if !exists { + return false + } + return value == needle +} + +func isRecyclable(policy api.PersistentVolumeReclaimPolicy) bool { + return policy == api.PersistentVolumeReclaimDelete || policy == api.PersistentVolumeReclaimRecycle +} + +// VolumeHost implementation +// PersistentVolumeRecycler is host to the volume plugins, but does not actually mount any volumes. +// Because no mounting is performed, most of the VolumeHost methods are not implemented. +func (c *PersistentVolumeProvisionerController) GetPluginDir(podUID string) string { + return "" +} + +func (c *PersistentVolumeProvisionerController) GetPodVolumeDir(podUID types.UID, pluginName, volumeName string) string { + return "" +} + +func (c *PersistentVolumeProvisionerController) GetPodPluginDir(podUID types.UID, pluginName string) string { + return "" +} + +func (c *PersistentVolumeProvisionerController) GetKubeClient() clientset.Interface { + return c.client.GetKubeClient() +} + +func (c *PersistentVolumeProvisionerController) NewWrapperMounter(volName string, spec volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { + return nil, fmt.Errorf("NewWrapperMounter not supported by PVClaimBinder's VolumeHost implementation") +} + +func (c *PersistentVolumeProvisionerController) NewWrapperUnmounter(volName string, spec volume.Spec, podUID types.UID) (volume.Unmounter, error) { + return nil, fmt.Errorf("NewWrapperUnmounter not supported by PVClaimBinder's VolumeHost implementation") +} + +func (c *PersistentVolumeProvisionerController) GetCloudProvider() cloudprovider.Interface { + return c.cloud +} + +func (c *PersistentVolumeProvisionerController) GetMounter() mount.Interface { + return nil +} + +func (c *PersistentVolumeProvisionerController) GetWriter() io.Writer { + return nil +} + +func (c *PersistentVolumeProvisionerController) GetHostName() string { + return "" +} + +const ( + // these pair of constants are used by the provisioner. + // The key is a kube namespaced key that denotes a volume requires provisioning. + // The value is set only when provisioning is completed. Any other value will tell the provisioner + // that provisioning has not yet occurred. + pvProvisioningRequiredAnnotationKey = "volume.experimental.kubernetes.io/provisioning-required" + pvProvisioningCompletedAnnotationValue = "volume.experimental.kubernetes.io/provisioning-completed" +) diff --git a/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_provisioner_controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_provisioner_controller_test.go new file mode 100644 index 000000000..c72e8e447 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_provisioner_controller_test.go @@ -0,0 +1,295 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 persistentvolume + +import ( + "fmt" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/testapi" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + fake_cloud "k8s.io/kubernetes/pkg/cloudprovider/providers/fake" + "k8s.io/kubernetes/pkg/util" + volumetest "k8s.io/kubernetes/pkg/volume/testing" + "k8s.io/kubernetes/pkg/watch" +) + +func TestProvisionerRunStop(t *testing.T) { + controller, _, _ := makeTestController() + + if len(controller.stopChannels) != 0 { + t.Errorf("Non-running provisioner should not have any stopChannels. Got %v", len(controller.stopChannels)) + } + + controller.Run() + + if len(controller.stopChannels) != 2 { + t.Errorf("Running provisioner should have exactly 2 stopChannels. Got %v", len(controller.stopChannels)) + } + + controller.Stop() + + if len(controller.stopChannels) != 0 { + t.Errorf("Non-running provisioner should not have any stopChannels. Got %v", len(controller.stopChannels)) + } +} + +func makeTestVolume() *api.PersistentVolume { + return &api.PersistentVolume{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{}, + Name: "pv01", + }, + Spec: api.PersistentVolumeSpec{ + PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete, + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10Gi"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{ + Path: "/somepath/data01", + }, + }, + }, + } +} + +func makeTestClaim() *api.PersistentVolumeClaim { + return &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{}, + Name: "claim01", + Namespace: "ns", + SelfLink: testapi.Default.SelfLink("pvc", ""), + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("8G"), + }, + }, + }, + } +} + +func makeTestController() (*PersistentVolumeProvisionerController, *mockControllerClient, *volumetest.FakeVolumePlugin) { + mockClient := &mockControllerClient{} + mockVolumePlugin := &volumetest.FakeVolumePlugin{} + controller, _ := NewPersistentVolumeProvisionerController(mockClient, 1*time.Second, "fake-kubernetes", nil, mockVolumePlugin, &fake_cloud.FakeCloud{}) + return controller, mockClient, mockVolumePlugin +} + +func TestReconcileClaim(t *testing.T) { + controller, mockClient, _ := makeTestController() + pvc := makeTestClaim() + + // watch would have added the claim to the store + controller.claimStore.Add(pvc) + // store it in fake API server + mockClient.UpdatePersistentVolumeClaim(pvc) + + err := controller.reconcileClaim(pvc) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // non-provisionable PVC should not have created a volume on reconciliation + if mockClient.volume != nil { + t.Error("Unexpected volume found in mock client. Expected nil") + } + + pvc.Annotations[qosProvisioningKey] = "foo" + // store it in fake API server + mockClient.UpdatePersistentVolumeClaim(pvc) + + err = controller.reconcileClaim(pvc) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // PVC requesting provisioning should have a PV created for it + if mockClient.volume == nil { + t.Error("Expected to find bound volume but got nil") + } + + if mockClient.volume.Spec.ClaimRef.Name != pvc.Name { + t.Errorf("Expected PV to be bound to %s but got %s", mockClient.volume.Spec.ClaimRef.Name, pvc.Name) + } + + // the PVC should have correct annotation + if mockClient.claim.Annotations[pvProvisioningRequiredAnnotationKey] != pvProvisioningCompletedAnnotationValue { + t.Errorf("Annotation %q not set", pvProvisioningRequiredAnnotationKey) + } + + // Run the syncClaim 2nd time to simulate periodic sweep running in parallel + // to the previous syncClaim. There is a lock in handleUpdateVolume(), so + // they will be called sequentially, but the second call will have old + // version of the claim. + oldPVName := mockClient.volume.Name + + // Make the "old" claim + pvc2 := makeTestClaim() + pvc2.Annotations[qosProvisioningKey] = "foo" + // Add a dummy annotation so we recognize the claim was updated (i.e. + // stored in mockClient) + pvc2.Annotations["test"] = "test" + + err = controller.reconcileClaim(pvc2) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // The 2nd PVC should be ignored, no new PV was created + if val, found := pvc2.Annotations[pvProvisioningRequiredAnnotationKey]; found { + t.Errorf("2nd PVC got unexpected annotation %q: %q", pvProvisioningRequiredAnnotationKey, val) + } + if mockClient.volume.Name != oldPVName { + t.Errorf("2nd PVC unexpectedly provisioned a new volume") + } + if _, found := mockClient.claim.Annotations["test"]; found { + t.Errorf("2nd PVC was unexpectedly updated") + } +} + +func checkTagValue(t *testing.T, tags map[string]string, tag string, expectedValue string) { + value, found := tags[tag] + if !found || value != expectedValue { + t.Errorf("Expected tag value %s = %s but value %s found", tag, expectedValue, value) + } +} + +func TestReconcileVolume(t *testing.T) { + + controller, mockClient, mockVolumePlugin := makeTestController() + pv := makeTestVolume() + pvc := makeTestClaim() + mockClient.volume = pv + + err := controller.reconcileVolume(pv) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + + // watch adds claim to the store. + // we need to add it to our mock client to mimic normal Get call + controller.claimStore.Add(pvc) + mockClient.claim = pvc + + // pretend the claim and volume are bound, no provisioning required + claimRef, _ := api.GetReference(pvc) + pv.Spec.ClaimRef = claimRef + mockClient.volume = pv + err = controller.reconcileVolume(pv) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + + pv.Annotations[pvProvisioningRequiredAnnotationKey] = "!pvProvisioningCompleted" + pv.Annotations[qosProvisioningKey] = "foo" + mockClient.volume = pv + err = controller.reconcileVolume(pv) + + if !isAnnotationMatch(pvProvisioningRequiredAnnotationKey, pvProvisioningCompletedAnnotationValue, mockClient.volume.Annotations) { + t.Errorf("Expected %s but got %s", pvProvisioningRequiredAnnotationKey, mockClient.volume.Annotations[pvProvisioningRequiredAnnotationKey]) + } + + // Check that the volume plugin was called with correct tags + tags := *mockVolumePlugin.LastProvisionerOptions.CloudTags + checkTagValue(t, tags, cloudVolumeCreatedForClaimNamespaceTag, pvc.Namespace) + checkTagValue(t, tags, cloudVolumeCreatedForClaimNameTag, pvc.Name) + checkTagValue(t, tags, cloudVolumeCreatedForVolumeNameTag, pv.Name) + +} + +var _ controllerClient = &mockControllerClient{} + +type mockControllerClient struct { + volume *api.PersistentVolume + claim *api.PersistentVolumeClaim +} + +func (c *mockControllerClient) GetPersistentVolume(name string) (*api.PersistentVolume, error) { + return c.volume, nil +} + +func (c *mockControllerClient) CreatePersistentVolume(pv *api.PersistentVolume) (*api.PersistentVolume, error) { + if pv.GenerateName != "" && pv.Name == "" { + pv.Name = fmt.Sprintf(pv.GenerateName, util.NewUUID()) + } + c.volume = pv + return c.volume, nil +} + +func (c *mockControllerClient) ListPersistentVolumes(options api.ListOptions) (*api.PersistentVolumeList, error) { + return &api.PersistentVolumeList{ + Items: []api.PersistentVolume{*c.volume}, + }, nil +} + +func (c *mockControllerClient) WatchPersistentVolumes(options api.ListOptions) (watch.Interface, error) { + return watch.NewFake(), nil +} + +func (c *mockControllerClient) UpdatePersistentVolume(pv *api.PersistentVolume) (*api.PersistentVolume, error) { + return c.CreatePersistentVolume(pv) +} + +func (c *mockControllerClient) DeletePersistentVolume(volume *api.PersistentVolume) error { + c.volume = nil + return nil +} + +func (c *mockControllerClient) UpdatePersistentVolumeStatus(volume *api.PersistentVolume) (*api.PersistentVolume, error) { + return volume, nil +} + +func (c *mockControllerClient) GetPersistentVolumeClaim(namespace, name string) (*api.PersistentVolumeClaim, error) { + if c.claim != nil { + return c.claim, nil + } else { + return nil, errors.NewNotFound(api.Resource("persistentvolumes"), name) + } +} + +func (c *mockControllerClient) ListPersistentVolumeClaims(namespace string, options api.ListOptions) (*api.PersistentVolumeClaimList, error) { + return &api.PersistentVolumeClaimList{ + Items: []api.PersistentVolumeClaim{*c.claim}, + }, nil +} + +func (c *mockControllerClient) WatchPersistentVolumeClaims(namespace string, options api.ListOptions) (watch.Interface, error) { + return watch.NewFake(), nil +} + +func (c *mockControllerClient) UpdatePersistentVolumeClaim(claim *api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) { + c.claim = claim + return c.claim, nil +} + +func (c *mockControllerClient) UpdatePersistentVolumeClaimStatus(claim *api.PersistentVolumeClaim) (*api.PersistentVolumeClaim, error) { + return claim, nil +} + +func (c *mockControllerClient) GetKubeClient() clientset.Interface { + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_recycler_controller.go b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_recycler_controller.go new file mode 100644 index 000000000..a0cb6aa81 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_recycler_controller.go @@ -0,0 +1,410 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 persistentvolume + +import ( + "fmt" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" + ioutil "k8s.io/kubernetes/pkg/util/io" + "k8s.io/kubernetes/pkg/util/mount" + "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/pkg/watch" +) + +var _ volume.VolumeHost = &PersistentVolumeRecycler{} + +// PersistentVolumeRecycler is a controller that watches for PersistentVolumes that are released from their claims. +// This controller will Recycle those volumes whose reclaim policy is set to PersistentVolumeReclaimRecycle and make them +// available again for a new claim. +type PersistentVolumeRecycler struct { + volumeController *framework.Controller + stopChannel chan struct{} + client recyclerClient + kubeClient clientset.Interface + pluginMgr volume.VolumePluginMgr + cloud cloudprovider.Interface + maximumRetry int + syncPeriod time.Duration + // Local cache of failed recycle / delete operations. Map volume.Name -> status of the volume. + // Only PVs in Released state have an entry here. + releasedVolumes map[string]releasedVolumeStatus +} + +// releasedVolumeStatus holds state of failed delete/recycle operation on a +// volume. The controller re-tries the operation several times and it stores +// retry count + timestamp of the last attempt here. +type releasedVolumeStatus struct { + // How many recycle/delete operations failed. + retryCount int + // Timestamp of the last attempt. + lastAttempt time.Time +} + +// NewPersistentVolumeRecycler creates a new PersistentVolumeRecycler +func NewPersistentVolumeRecycler(kubeClient clientset.Interface, syncPeriod time.Duration, maximumRetry int, plugins []volume.VolumePlugin, cloud cloudprovider.Interface) (*PersistentVolumeRecycler, error) { + recyclerClient := NewRecyclerClient(kubeClient) + recycler := &PersistentVolumeRecycler{ + client: recyclerClient, + kubeClient: kubeClient, + cloud: cloud, + maximumRetry: maximumRetry, + syncPeriod: syncPeriod, + releasedVolumes: make(map[string]releasedVolumeStatus), + } + + if err := recycler.pluginMgr.InitPlugins(plugins, recycler); err != nil { + return nil, fmt.Errorf("Could not initialize volume plugins for PVClaimBinder: %+v", err) + } + + _, volumeController := framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return kubeClient.Core().PersistentVolumes().List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return kubeClient.Core().PersistentVolumes().Watch(options) + }, + }, + &api.PersistentVolume{}, + syncPeriod, + framework.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + pv, ok := obj.(*api.PersistentVolume) + if !ok { + glog.Errorf("Error casting object to PersistentVolume: %v", obj) + return + } + recycler.reclaimVolume(pv) + }, + UpdateFunc: func(oldObj, newObj interface{}) { + pv, ok := newObj.(*api.PersistentVolume) + if !ok { + glog.Errorf("Error casting object to PersistentVolume: %v", newObj) + return + } + recycler.reclaimVolume(pv) + }, + DeleteFunc: func(obj interface{}) { + pv, ok := obj.(*api.PersistentVolume) + if !ok { + glog.Errorf("Error casting object to PersistentVolume: %v", obj) + return + } + recycler.removeReleasedVolume(pv) + }, + }, + ) + + recycler.volumeController = volumeController + return recycler, nil +} + +// shouldRecycle checks a volume and returns nil, if the volume should be +// recycled right now. Otherwise it returns an error with reason why it should +// not be recycled. +func (recycler *PersistentVolumeRecycler) shouldRecycle(pv *api.PersistentVolume) error { + if pv.Spec.ClaimRef == nil { + return fmt.Errorf("Volume does not have a reference to claim") + } + if pv.Status.Phase != api.VolumeReleased { + return fmt.Errorf("The volume is not in 'Released' phase") + } + + // The volume is Released, should we retry recycling? + status, found := recycler.releasedVolumes[pv.Name] + if !found { + // We don't know anything about this volume. The controller has been + // restarted or the volume has been marked as Released by another + // controller. Recycle/delete this volume as if it was just Released. + glog.V(5).Infof("PersistentVolume[%s] not found in local cache, recycling", pv.Name) + return nil + } + + // Check the timestamp + expectedRetry := status.lastAttempt.Add(recycler.syncPeriod) + if time.Now().After(expectedRetry) { + glog.V(5).Infof("PersistentVolume[%s] retrying recycle after timeout", pv.Name) + return nil + } + // It's too early + glog.V(5).Infof("PersistentVolume[%s] skipping recycle, it's too early: now: %v, next retry: %v", pv.Name, time.Now(), expectedRetry) + return fmt.Errorf("Too early after previous failure") +} + +func (recycler *PersistentVolumeRecycler) reclaimVolume(pv *api.PersistentVolume) error { + glog.V(5).Infof("Recycler: checking PersistentVolume[%s]\n", pv.Name) + // Always load the latest version of the volume + newPV, err := recycler.client.GetPersistentVolume(pv.Name) + if err != nil { + return fmt.Errorf("Could not find PersistentVolume %s", pv.Name) + } + pv = newPV + + err = recycler.shouldRecycle(pv) + if err == nil { + glog.V(5).Infof("Reclaiming PersistentVolume[%s]\n", pv.Name) + + // both handleRecycle and handleDelete block until completion + // TODO: allow parallel recycling operations to increase throughput + switch pv.Spec.PersistentVolumeReclaimPolicy { + case api.PersistentVolumeReclaimRecycle: + err = recycler.handleRecycle(pv) + case api.PersistentVolumeReclaimDelete: + err = recycler.handleDelete(pv) + case api.PersistentVolumeReclaimRetain: + glog.V(5).Infof("Volume %s is set to retain after release. Skipping.\n", pv.Name) + default: + err = fmt.Errorf("No PersistentVolumeReclaimPolicy defined for spec: %+v", pv) + } + if err != nil { + errMsg := fmt.Sprintf("Could not recycle volume spec: %+v", err) + glog.Errorf(errMsg) + return fmt.Errorf(errMsg) + } + return nil + } + glog.V(3).Infof("PersistentVolume[%s] phase %s - skipping: %v", pv.Name, pv.Status.Phase, err) + return nil +} + +// handleReleaseFailure evaluates a failed Recycle/Delete operation, updates +// internal controller state with new nr. of attempts and timestamp of the last +// attempt. Based on the number of failures it returns the next state of the +// volume (Released / Failed). +func (recycler *PersistentVolumeRecycler) handleReleaseFailure(pv *api.PersistentVolume) api.PersistentVolumePhase { + status, found := recycler.releasedVolumes[pv.Name] + if !found { + // First failure, set retryCount to 0 (will be inceremented few lines below) + status = releasedVolumeStatus{} + } + status.retryCount += 1 + + if status.retryCount > recycler.maximumRetry { + // This was the last attempt. Remove any internal state and mark the + // volume as Failed. + glog.V(3).Infof("PersistentVolume[%s] failed %d times - marking Failed", pv.Name, status.retryCount) + recycler.removeReleasedVolume(pv) + return api.VolumeFailed + } + + status.lastAttempt = time.Now() + recycler.releasedVolumes[pv.Name] = status + return api.VolumeReleased +} + +func (recycler *PersistentVolumeRecycler) removeReleasedVolume(pv *api.PersistentVolume) { + delete(recycler.releasedVolumes, pv.Name) +} + +func (recycler *PersistentVolumeRecycler) handleRecycle(pv *api.PersistentVolume) error { + glog.V(5).Infof("Recycling PersistentVolume[%s]\n", pv.Name) + + currentPhase := pv.Status.Phase + nextPhase := currentPhase + + spec := volume.NewSpecFromPersistentVolume(pv, false) + plugin, err := recycler.pluginMgr.FindRecyclablePluginBySpec(spec) + if err != nil { + nextPhase = api.VolumeFailed + pv.Status.Message = fmt.Sprintf("%v", err) + } + + // an error above means a suitable plugin for this volume was not found. + // we don't need to attempt recycling when plugin is nil, but we do need to persist the next/failed phase + // of the volume so that subsequent syncs won't attempt recycling through this handler func. + if plugin != nil { + volRecycler, err := plugin.NewRecycler(spec) + if err != nil { + return fmt.Errorf("Could not obtain Recycler for spec: %#v error: %v", spec, err) + } + // blocks until completion + if err := volRecycler.Recycle(); err != nil { + glog.Errorf("PersistentVolume[%s] failed recycling: %+v", pv.Name, err) + pv.Status.Message = fmt.Sprintf("Recycling error: %s", err) + nextPhase = recycler.handleReleaseFailure(pv) + } else { + glog.V(5).Infof("PersistentVolume[%s] successfully recycled\n", pv.Name) + // The volume has been recycled. Remove any internal state to make + // any subsequent bind+recycle cycle working. + recycler.removeReleasedVolume(pv) + nextPhase = api.VolumePending + } + } + + if currentPhase != nextPhase { + glog.V(5).Infof("PersistentVolume[%s] changing phase from %s to %s\n", pv.Name, currentPhase, nextPhase) + pv.Status.Phase = nextPhase + _, err := recycler.client.UpdatePersistentVolumeStatus(pv) + if err != nil { + // Rollback to previous phase + pv.Status.Phase = currentPhase + } + } + + return nil +} + +func (recycler *PersistentVolumeRecycler) handleDelete(pv *api.PersistentVolume) error { + glog.V(5).Infof("Deleting PersistentVolume[%s]\n", pv.Name) + + currentPhase := pv.Status.Phase + nextPhase := currentPhase + + spec := volume.NewSpecFromPersistentVolume(pv, false) + plugin, err := recycler.pluginMgr.FindDeletablePluginBySpec(spec) + if err != nil { + nextPhase = api.VolumeFailed + pv.Status.Message = fmt.Sprintf("%v", err) + } + + // an error above means a suitable plugin for this volume was not found. + // we don't need to attempt deleting when plugin is nil, but we do need to persist the next/failed phase + // of the volume so that subsequent syncs won't attempt deletion through this handler func. + if plugin != nil { + deleter, err := plugin.NewDeleter(spec) + if err != nil { + return fmt.Errorf("Could not obtain Deleter for spec: %#v error: %v", spec, err) + } + // blocks until completion + err = deleter.Delete() + if err != nil { + glog.Errorf("PersistentVolume[%s] failed deletion: %+v", pv.Name, err) + pv.Status.Message = fmt.Sprintf("Deletion error: %s", err) + nextPhase = recycler.handleReleaseFailure(pv) + } else { + glog.V(5).Infof("PersistentVolume[%s] successfully deleted through plugin\n", pv.Name) + recycler.removeReleasedVolume(pv) + // after successful deletion through the plugin, we can also remove the PV from the cluster + if err := recycler.client.DeletePersistentVolume(pv); err != nil { + return fmt.Errorf("error deleting persistent volume: %+v", err) + } + } + } + + if currentPhase != nextPhase { + glog.V(5).Infof("PersistentVolume[%s] changing phase from %s to %s\n", pv.Name, currentPhase, nextPhase) + pv.Status.Phase = nextPhase + _, err := recycler.client.UpdatePersistentVolumeStatus(pv) + if err != nil { + // Rollback to previous phase + pv.Status.Phase = currentPhase + } + } + + return nil +} + +// Run starts this recycler's control loops +func (recycler *PersistentVolumeRecycler) Run() { + glog.V(5).Infof("Starting PersistentVolumeRecycler\n") + if recycler.stopChannel == nil { + recycler.stopChannel = make(chan struct{}) + go recycler.volumeController.Run(recycler.stopChannel) + } +} + +// Stop gracefully shuts down this binder +func (recycler *PersistentVolumeRecycler) Stop() { + glog.V(5).Infof("Stopping PersistentVolumeRecycler\n") + if recycler.stopChannel != nil { + close(recycler.stopChannel) + recycler.stopChannel = nil + } +} + +// recyclerClient abstracts access to PVs +type recyclerClient interface { + GetPersistentVolume(name string) (*api.PersistentVolume, error) + UpdatePersistentVolume(volume *api.PersistentVolume) (*api.PersistentVolume, error) + DeletePersistentVolume(volume *api.PersistentVolume) error + UpdatePersistentVolumeStatus(volume *api.PersistentVolume) (*api.PersistentVolume, error) +} + +func NewRecyclerClient(c clientset.Interface) recyclerClient { + return &realRecyclerClient{c} +} + +type realRecyclerClient struct { + client clientset.Interface +} + +func (c *realRecyclerClient) GetPersistentVolume(name string) (*api.PersistentVolume, error) { + return c.client.Core().PersistentVolumes().Get(name) +} + +func (c *realRecyclerClient) UpdatePersistentVolume(volume *api.PersistentVolume) (*api.PersistentVolume, error) { + return c.client.Core().PersistentVolumes().Update(volume) +} + +func (c *realRecyclerClient) DeletePersistentVolume(volume *api.PersistentVolume) error { + return c.client.Core().PersistentVolumes().Delete(volume.Name, nil) +} + +func (c *realRecyclerClient) UpdatePersistentVolumeStatus(volume *api.PersistentVolume) (*api.PersistentVolume, error) { + return c.client.Core().PersistentVolumes().UpdateStatus(volume) +} + +// PersistentVolumeRecycler is host to the volume plugins, but does not actually mount any volumes. +// Because no mounting is performed, most of the VolumeHost methods are not implemented. +func (f *PersistentVolumeRecycler) GetPluginDir(podUID string) string { + return "" +} + +func (f *PersistentVolumeRecycler) GetPodVolumeDir(podUID types.UID, pluginName, volumeName string) string { + return "" +} + +func (f *PersistentVolumeRecycler) GetPodPluginDir(podUID types.UID, pluginName string) string { + return "" +} + +func (f *PersistentVolumeRecycler) GetKubeClient() clientset.Interface { + return f.kubeClient +} + +func (f *PersistentVolumeRecycler) NewWrapperMounter(volName string, spec volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { + return nil, fmt.Errorf("NewWrapperMounter not supported by PVClaimBinder's VolumeHost implementation") +} + +func (f *PersistentVolumeRecycler) NewWrapperUnmounter(volName string, spec volume.Spec, podUID types.UID) (volume.Unmounter, error) { + return nil, fmt.Errorf("NewWrapperUnmounter not supported by PVClaimBinder's VolumeHost implementation") +} + +func (f *PersistentVolumeRecycler) GetCloudProvider() cloudprovider.Interface { + return f.cloud +} + +func (f *PersistentVolumeRecycler) GetMounter() mount.Interface { + return nil +} + +func (f *PersistentVolumeRecycler) GetWriter() ioutil.Writer { + return nil +} + +func (f *PersistentVolumeRecycler) GetHostName() string { + return "" +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_recycler_controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_recycler_controller_test.go new file mode 100644 index 000000000..8312fd322 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/persistentvolume_recycler_controller_test.go @@ -0,0 +1,265 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 persistentvolume + +import ( + "fmt" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/pkg/volume/host_path" + volumetest "k8s.io/kubernetes/pkg/volume/testing" +) + +const ( + mySyncPeriod = 2 * time.Second + myMaximumRetry = 3 +) + +func TestFailedRecycling(t *testing.T) { + pv := preparePV() + + mockClient := &mockBinderClient{ + volume: pv, + } + + // no Init called for pluginMgr and no plugins are available. Volume should fail recycling. + plugMgr := volume.VolumePluginMgr{} + + recycler := &PersistentVolumeRecycler{ + kubeClient: fake.NewSimpleClientset(), + client: mockClient, + pluginMgr: plugMgr, + releasedVolumes: make(map[string]releasedVolumeStatus), + } + + err := recycler.reclaimVolume(pv) + if err != nil { + t.Errorf("Unexpected non-nil error: %v", err) + } + + if mockClient.volume.Status.Phase != api.VolumeFailed { + t.Errorf("Expected %s but got %s", api.VolumeFailed, mockClient.volume.Status.Phase) + } + + // Use a new volume for the next test + pv = preparePV() + mockClient.volume = pv + + pv.Spec.PersistentVolumeReclaimPolicy = api.PersistentVolumeReclaimDelete + err = recycler.reclaimVolume(pv) + if err != nil { + t.Errorf("Unexpected non-nil error: %v", err) + } + + if mockClient.volume.Status.Phase != api.VolumeFailed { + t.Errorf("Expected %s but got %s", api.VolumeFailed, mockClient.volume.Status.Phase) + } +} + +func TestRecyclingRetry(t *testing.T) { + // Test that recycler controller retries to recycle a volume several times, which succeeds eventually + pv := preparePV() + + mockClient := &mockBinderClient{ + volume: pv, + } + + plugMgr := volume.VolumePluginMgr{} + // Use a fake NewRecycler function + plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newFailingMockRecycler, volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil)) + // Reset a global call counter + failedCallCount = 0 + + recycler := &PersistentVolumeRecycler{ + kubeClient: fake.NewSimpleClientset(), + client: mockClient, + pluginMgr: plugMgr, + syncPeriod: mySyncPeriod, + maximumRetry: myMaximumRetry, + releasedVolumes: make(map[string]releasedVolumeStatus), + } + + // All but the last attempt will fail + testRecycleFailures(t, recycler, mockClient, pv, myMaximumRetry-1) + + // The last attempt should succeed + err := recycler.reclaimVolume(pv) + if err != nil { + t.Errorf("Last step: Recycler failed: %v", err) + } + + if mockClient.volume.Status.Phase != api.VolumePending { + t.Errorf("Last step: The volume should be Pending, but is %s instead", mockClient.volume.Status.Phase) + } + // Check the cache, it should not have any entry + status, found := recycler.releasedVolumes[pv.Name] + if found { + t.Errorf("Last step: Expected PV to be removed from cache, got %v", status) + } +} + +func TestRecyclingRetryAlwaysFail(t *testing.T) { + // Test that recycler controller retries to recycle a volume several times, which always fails. + pv := preparePV() + + mockClient := &mockBinderClient{ + volume: pv, + } + + plugMgr := volume.VolumePluginMgr{} + // Use a fake NewRecycler function + plugMgr.InitPlugins(host_path.ProbeRecyclableVolumePlugins(newAlwaysFailingMockRecycler, volume.VolumeConfig{}), volumetest.NewFakeVolumeHost("/tmp/fake", nil, nil)) + // Reset a global call counter + failedCallCount = 0 + + recycler := &PersistentVolumeRecycler{ + kubeClient: fake.NewSimpleClientset(), + client: mockClient, + pluginMgr: plugMgr, + syncPeriod: mySyncPeriod, + maximumRetry: myMaximumRetry, + releasedVolumes: make(map[string]releasedVolumeStatus), + } + + // myMaximumRetry recycle attempts will fail + testRecycleFailures(t, recycler, mockClient, pv, myMaximumRetry) + + // The volume should be failed after myMaximumRetry attempts + err := recycler.reclaimVolume(pv) + if err != nil { + t.Errorf("Last step: Recycler failed: %v", err) + } + + if mockClient.volume.Status.Phase != api.VolumeFailed { + t.Errorf("Last step: The volume should be Failed, but is %s instead", mockClient.volume.Status.Phase) + } + // Check the cache, it should not have any entry + status, found := recycler.releasedVolumes[pv.Name] + if found { + t.Errorf("Last step: Expected PV to be removed from cache, got %v", status) + } +} + +func preparePV() *api.PersistentVolume { + return &api.PersistentVolume{ + Spec: api.PersistentVolumeSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("8Gi"), + }, + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{ + Path: "/tmp/data02", + }, + }, + PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRecycle, + ClaimRef: &api.ObjectReference{ + Name: "foo", + Namespace: "bar", + }, + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeReleased, + }, + } +} + +// Test that `count` attempts to recycle a PV fails. +func testRecycleFailures(t *testing.T, recycler *PersistentVolumeRecycler, mockClient *mockBinderClient, pv *api.PersistentVolume, count int) { + for i := 1; i <= count; i++ { + err := recycler.reclaimVolume(pv) + if err != nil { + t.Errorf("STEP %d: Recycler faled: %v", i, err) + } + + // Check the status, it should be failed + if mockClient.volume.Status.Phase != api.VolumeReleased { + t.Errorf("STEP %d: The volume should be Released, but is %s instead", i, mockClient.volume.Status.Phase) + } + + // Check the failed volume cache + status, found := recycler.releasedVolumes[pv.Name] + if !found { + t.Errorf("STEP %d: cannot find released volume status", i) + } + if status.retryCount != i { + t.Errorf("STEP %d: Expected nr. of attempts to be %d, got %d", i, i, status.retryCount) + } + + // call reclaimVolume too early, it should not increment the retryCount + time.Sleep(mySyncPeriod / 2) + err = recycler.reclaimVolume(pv) + if err != nil { + t.Errorf("STEP %d: Recycler failed: %v", i, err) + } + + status, found = recycler.releasedVolumes[pv.Name] + if !found { + t.Errorf("STEP %d: cannot find released volume status", i) + } + if status.retryCount != i { + t.Errorf("STEP %d: Expected nr. of attempts to be %d, got %d", i, i, status.retryCount) + } + + // Call the next reclaimVolume() after full pvRecycleRetryPeriod + time.Sleep(mySyncPeriod / 2) + } +} + +func newFailingMockRecycler(spec *volume.Spec, host volume.VolumeHost, config volume.VolumeConfig) (volume.Recycler, error) { + return &failingMockRecycler{ + path: spec.PersistentVolume.Spec.HostPath.Path, + errorCount: myMaximumRetry - 1, // fail two times and then successfully recycle the volume + }, nil +} + +func newAlwaysFailingMockRecycler(spec *volume.Spec, host volume.VolumeHost, config volume.VolumeConfig) (volume.Recycler, error) { + return &failingMockRecycler{ + path: spec.PersistentVolume.Spec.HostPath.Path, + errorCount: 1000, // always fail + }, nil +} + +type failingMockRecycler struct { + path string + // How many times should the recycler fail before returning success. + errorCount int + volume.MetricsNil +} + +// Counter of failingMockRecycler.Recycle() calls. Global variable just for +// testing. It's too much code to create a custom volume plugin, which would +// hold this variable. +var failedCallCount = 0 + +func (r *failingMockRecycler) GetPath() string { + return r.path +} + +func (r *failingMockRecycler) Recycle() error { + failedCallCount += 1 + if failedCallCount <= r.errorCount { + return fmt.Errorf("Failing for %d. time", failedCallCount) + } + // return nil means recycle passed + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/types.go b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/types.go new file mode 100644 index 000000000..42ca36801 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/persistentvolume/types.go @@ -0,0 +1,267 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 persistentvolume + +import ( + "fmt" + "sort" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/cache" +) + +const ( + // A PVClaim can request a quality of service tier by adding this annotation. The value of the annotation + // is arbitrary. The values are pre-defined by a cluster admin and known to users when requesting a QoS. + // For example tiers might be gold, silver, and tin and the admin configures what that means for each volume plugin that can provision a volume. + // Values in the alpha version of this feature are not meaningful, but will be in the full version of this feature. + qosProvisioningKey = "volume.alpha.kubernetes.io/storage-class" + // Name of a tag attached to a real volume in cloud (e.g. AWS EBS or GCE PD) + // with namespace of a persistent volume claim used to create this volume. + cloudVolumeCreatedForClaimNamespaceTag = "kubernetes.io/created-for/pvc/namespace" + // Name of a tag attached to a real volume in cloud (e.g. AWS EBS or GCE PD) + // with name of a persistent volume claim used to create this volume. + cloudVolumeCreatedForClaimNameTag = "kubernetes.io/created-for/pvc/name" + // Name of a tag attached to a real volume in cloud (e.g. AWS EBS or GCE PD) + // with name of appropriate Kubernetes persistent volume . + cloudVolumeCreatedForVolumeNameTag = "kubernetes.io/created-for/pv/name" +) + +// persistentVolumeOrderedIndex is a cache.Store that keeps persistent volumes indexed by AccessModes and ordered by storage capacity. +type persistentVolumeOrderedIndex struct { + cache.Indexer +} + +var _ cache.Store = &persistentVolumeOrderedIndex{} // persistentVolumeOrderedIndex is a Store + +func NewPersistentVolumeOrderedIndex() *persistentVolumeOrderedIndex { + return &persistentVolumeOrderedIndex{ + cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{"accessmodes": accessModesIndexFunc}), + } +} + +// accessModesIndexFunc is an indexing function that returns a persistent volume's AccessModes as a string +func accessModesIndexFunc(obj interface{}) ([]string, error) { + if pv, ok := obj.(*api.PersistentVolume); ok { + modes := api.GetAccessModesAsString(pv.Spec.AccessModes) + return []string{modes}, nil + } + return []string{""}, fmt.Errorf("object is not a persistent volume: %v", obj) +} + +// ListByAccessModes returns all volumes with the given set of AccessModeTypes *in order* of their storage capacity (low to high) +func (pvIndex *persistentVolumeOrderedIndex) ListByAccessModes(modes []api.PersistentVolumeAccessMode) ([]*api.PersistentVolume, error) { + pv := &api.PersistentVolume{ + Spec: api.PersistentVolumeSpec{ + AccessModes: modes, + }, + } + + objs, err := pvIndex.Index("accessmodes", pv) + if err != nil { + return nil, err + } + + volumes := make([]*api.PersistentVolume, len(objs)) + for i, obj := range objs { + volumes[i] = obj.(*api.PersistentVolume) + } + + sort.Sort(byCapacity{volumes}) + return volumes, nil +} + +// matchPredicate is a function that indicates that a persistent volume matches another +type matchPredicate func(compareThis, toThis *api.PersistentVolume) bool + +// find returns the nearest PV from the ordered list or nil if a match is not found +func (pvIndex *persistentVolumeOrderedIndex) findByClaim(claim *api.PersistentVolumeClaim, matchPredicate matchPredicate) (*api.PersistentVolume, error) { + // PVs are indexed by their access modes to allow easier searching. Each index is the string representation of a set of access modes. + // There is a finite number of possible sets and PVs will only be indexed in one of them (whichever index matches the PV's modes). + // + // A request for resources will always specify its desired access modes. Any matching PV must have at least that number + // of access modes, but it can have more. For example, a user asks for ReadWriteOnce but a GCEPD is available, which is ReadWriteOnce+ReadOnlyMany. + // + // Searches are performed against a set of access modes, so we can attempt not only the exact matching modes but also + // potential matches (the GCEPD example above). + allPossibleModes := pvIndex.allPossibleMatchingAccessModes(claim.Spec.AccessModes) + + for _, modes := range allPossibleModes { + volumes, err := pvIndex.ListByAccessModes(modes) + if err != nil { + return nil, err + } + + // volumes are sorted by size but some may be bound or earmarked for a specific claim. + // filter those volumes for easy binary search by size + // return the exact pre-binding match, if found + unboundVolumes := []*api.PersistentVolume{} + for _, volume := range volumes { + // volume isn't currently bound or pre-bound. + if volume.Spec.ClaimRef == nil { + unboundVolumes = append(unboundVolumes, volume) + continue + } + + if claim.Name == volume.Spec.ClaimRef.Name && claim.Namespace == volume.Spec.ClaimRef.Namespace && claim.UID == volume.Spec.ClaimRef.UID { + // exact match! No search required. + return volume, nil + } + } + + // a claim requesting provisioning will have an exact match pre-bound to the claim. + // no need to search through unbound volumes. The matching volume will be created by the provisioner + // and will match above when the claim is re-processed by the binder. + if keyExists(qosProvisioningKey, claim.Annotations) { + return nil, nil + } + + searchPV := &api.PersistentVolume{ + Spec: api.PersistentVolumeSpec{ + AccessModes: claim.Spec.AccessModes, + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): claim.Spec.Resources.Requests[api.ResourceName(api.ResourceStorage)], + }, + }, + } + + i := sort.Search(len(unboundVolumes), func(i int) bool { return matchPredicate(searchPV, unboundVolumes[i]) }) + if i < len(unboundVolumes) { + return unboundVolumes[i], nil + } + } + return nil, nil +} + +// findBestMatchForClaim is a convenience method that finds a volume by the claim's AccessModes and requests for Storage +func (pvIndex *persistentVolumeOrderedIndex) findBestMatchForClaim(claim *api.PersistentVolumeClaim) (*api.PersistentVolume, error) { + return pvIndex.findByClaim(claim, matchStorageCapacity) +} + +// byCapacity is used to order volumes by ascending storage size +type byCapacity struct { + volumes []*api.PersistentVolume +} + +func (c byCapacity) Less(i, j int) bool { + return matchStorageCapacity(c.volumes[i], c.volumes[j]) +} + +func (c byCapacity) Swap(i, j int) { + c.volumes[i], c.volumes[j] = c.volumes[j], c.volumes[i] +} + +func (c byCapacity) Len() int { + return len(c.volumes) +} + +// matchStorageCapacity is a matchPredicate used to sort and find volumes +func matchStorageCapacity(pvA, pvB *api.PersistentVolume) bool { + aQty := pvA.Spec.Capacity[api.ResourceStorage] + bQty := pvB.Spec.Capacity[api.ResourceStorage] + aSize := aQty.Value() + bSize := bQty.Value() + return aSize <= bSize +} + +// allPossibleMatchingAccessModes returns an array of AccessMode arrays that can satisfy a user's requested modes. +// +// see comments in the Find func above regarding indexing. +// +// allPossibleMatchingAccessModes gets all stringified accessmodes from the index and returns all those that +// contain at least all of the requested mode. +// +// For example, assume the index contains 2 types of PVs where the stringified accessmodes are: +// +// "RWO,ROX" -- some number of GCEPDs +// "RWO,ROX,RWX" -- some number of NFS volumes +// +// A request for RWO could be satisfied by both sets of indexed volumes, so allPossibleMatchingAccessModes returns: +// +// [][]api.PersistentVolumeAccessMode { +// []api.PersistentVolumeAccessMode { +// api.ReadWriteOnce, api.ReadOnlyMany, +// }, +// []api.PersistentVolumeAccessMode { +// api.ReadWriteOnce, api.ReadOnlyMany, api.ReadWriteMany, +// }, +// } +// +// A request for RWX can be satisfied by only one set of indexed volumes, so the return is: +// +// [][]api.PersistentVolumeAccessMode { +// []api.PersistentVolumeAccessMode { +// api.ReadWriteOnce, api.ReadOnlyMany, api.ReadWriteMany, +// }, +// } +// +// This func returns modes with ascending levels of modes to give the user what is closest to what they actually asked for. +// +func (pvIndex *persistentVolumeOrderedIndex) allPossibleMatchingAccessModes(requestedModes []api.PersistentVolumeAccessMode) [][]api.PersistentVolumeAccessMode { + matchedModes := [][]api.PersistentVolumeAccessMode{} + keys := pvIndex.Indexer.ListIndexFuncValues("accessmodes") + for _, key := range keys { + indexedModes := api.GetAccessModesFromString(key) + if containedInAll(indexedModes, requestedModes) { + matchedModes = append(matchedModes, indexedModes) + } + } + + // sort by the number of modes in each array with the fewest number of modes coming first. + // this allows searching for volumes by the minimum number of modes required of the possible matches. + sort.Sort(byAccessModes{matchedModes}) + return matchedModes +} + +func contains(modes []api.PersistentVolumeAccessMode, mode api.PersistentVolumeAccessMode) bool { + for _, m := range modes { + if m == mode { + return true + } + } + return false +} + +func containedInAll(indexedModes []api.PersistentVolumeAccessMode, requestedModes []api.PersistentVolumeAccessMode) bool { + for _, mode := range requestedModes { + if !contains(indexedModes, mode) { + return false + } + } + return true +} + +// byAccessModes is used to order access modes by size, with the fewest modes first +type byAccessModes struct { + modes [][]api.PersistentVolumeAccessMode +} + +func (c byAccessModes) Less(i, j int) bool { + return len(c.modes[i]) < len(c.modes[j]) +} + +func (c byAccessModes) Swap(i, j int) { + c.modes[i], c.modes[j] = c.modes[j], c.modes[i] +} + +func (c byAccessModes) Len() int { + return len(c.modes) +} + +func claimToClaimKey(claim *api.PersistentVolumeClaim) string { + return fmt.Sprintf("%s/%s", claim.Namespace, claim.Name) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/podautoscaler/horizontal.go b/vendor/k8s.io/kubernetes/pkg/controller/podautoscaler/horizontal.go new file mode 100644 index 000000000..5e33493e6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/podautoscaler/horizontal.go @@ -0,0 +1,401 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 podautoscaler + +import ( + "encoding/json" + "fmt" + "math" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/cache" + unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + unversionedextensions "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/controller/podautoscaler/metrics" + "k8s.io/kubernetes/pkg/runtime" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/watch" +) + +const ( + // Usage shoud exceed the tolerance before we start downscale or upscale the pods. + // TODO: make it a flag or HPA spec element. + tolerance = 0.1 + + defaultTargetCPUUtilizationPercentage = 80 + + HpaCustomMetricsTargetAnnotationName = "alpha/target.custom-metrics.podautoscaler.kubernetes.io" + HpaCustomMetricsStatusAnnotationName = "alpha/status.custom-metrics.podautoscaler.kubernetes.io" +) + +type HorizontalController struct { + scaleNamespacer unversionedextensions.ScalesGetter + hpaNamespacer unversionedextensions.HorizontalPodAutoscalersGetter + + metricsClient metrics.MetricsClient + eventRecorder record.EventRecorder + + // A store of HPA objects, populated by the controller. + store cache.Store + // Watches changes to all HPA objects. + controller *framework.Controller +} + +var downscaleForbiddenWindow = 5 * time.Minute +var upscaleForbiddenWindow = 3 * time.Minute + +func newInformer(controller *HorizontalController, resyncPeriod time.Duration) (cache.Store, *framework.Controller) { + return framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return controller.hpaNamespacer.HorizontalPodAutoscalers(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return controller.hpaNamespacer.HorizontalPodAutoscalers(api.NamespaceAll).Watch(options) + }, + }, + &extensions.HorizontalPodAutoscaler{}, + resyncPeriod, + framework.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + hpa := obj.(*extensions.HorizontalPodAutoscaler) + hasCPUPolicy := hpa.Spec.CPUUtilization != nil + _, hasCustomMetricsPolicy := hpa.Annotations[HpaCustomMetricsTargetAnnotationName] + if !hasCPUPolicy && !hasCustomMetricsPolicy { + controller.eventRecorder.Event(hpa, api.EventTypeNormal, "DefaultPolicy", "No scaling policy specified - will use default one. See documentation for details") + } + err := controller.reconcileAutoscaler(hpa) + if err != nil { + glog.Warningf("Failed to reconcile %s: %v", hpa.Name, err) + } + }, + UpdateFunc: func(old, cur interface{}) { + hpa := cur.(*extensions.HorizontalPodAutoscaler) + err := controller.reconcileAutoscaler(hpa) + if err != nil { + glog.Warningf("Failed to reconcile %s: %v", hpa.Name, err) + } + }, + // We are not interested in deletions. + }, + ) +} + +func NewHorizontalController(evtNamespacer unversionedcore.EventsGetter, scaleNamespacer unversionedextensions.ScalesGetter, hpaNamespacer unversionedextensions.HorizontalPodAutoscalersGetter, metricsClient metrics.MetricsClient, resyncPeriod time.Duration) *HorizontalController { + broadcaster := record.NewBroadcaster() + broadcaster.StartRecordingToSink(&unversionedcore.EventSinkImpl{Interface: evtNamespacer.Events("")}) + recorder := broadcaster.NewRecorder(api.EventSource{Component: "horizontal-pod-autoscaler"}) + + controller := &HorizontalController{ + metricsClient: metricsClient, + eventRecorder: recorder, + scaleNamespacer: scaleNamespacer, + hpaNamespacer: hpaNamespacer, + } + store, frameworkController := newInformer(controller, resyncPeriod) + controller.store = store + controller.controller = frameworkController + + return controller +} + +func (a *HorizontalController) Run(stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + glog.Infof("Starting HPA Controller") + go a.controller.Run(stopCh) + <-stopCh + glog.Infof("Shutting down HPA Controller") +} + +func (a *HorizontalController) computeReplicasForCPUUtilization(hpa *extensions.HorizontalPodAutoscaler, scale *extensions.Scale) (int, *int, time.Time, error) { + targetUtilization := defaultTargetCPUUtilizationPercentage + if hpa.Spec.CPUUtilization != nil { + targetUtilization = hpa.Spec.CPUUtilization.TargetPercentage + } + currentReplicas := scale.Status.Replicas + + if scale.Status.Selector == nil { + errMsg := "selector is required" + a.eventRecorder.Event(hpa, api.EventTypeWarning, "SelectorRequired", errMsg) + return 0, nil, time.Time{}, fmt.Errorf(errMsg) + } + + selector, err := unversioned.LabelSelectorAsSelector(scale.Status.Selector) + if err != nil { + errMsg := fmt.Sprintf("couldn't convert selector string to a corresponding selector object: %v", err) + a.eventRecorder.Event(hpa, api.EventTypeWarning, "InvalidSelector", errMsg) + return 0, nil, time.Time{}, fmt.Errorf(errMsg) + } + currentUtilization, timestamp, err := a.metricsClient.GetCPUUtilization(hpa.Namespace, selector) + + // TODO: what to do on partial errors (like metrics obtained for 75% of pods). + if err != nil { + a.eventRecorder.Event(hpa, api.EventTypeWarning, "FailedGetMetrics", err.Error()) + return 0, nil, time.Time{}, fmt.Errorf("failed to get CPU utilization: %v", err) + } + + usageRatio := float64(*currentUtilization) / float64(targetUtilization) + if math.Abs(1.0-usageRatio) > tolerance { + return int(math.Ceil(usageRatio * float64(currentReplicas))), currentUtilization, timestamp, nil + } else { + return currentReplicas, currentUtilization, timestamp, nil + } +} + +// Computes the desired number of replicas based on the CustomMetrics passed in cmAnnotation as json-serialized +// extensions.CustomMetricsTargetList. +// Returns number of replicas, metric which required highest number of replicas, +// status string (also json-serialized extensions.CustomMetricsCurrentStatusList), +// last timestamp of the metrics involved in computations or error, if occurred. +func (a *HorizontalController) computeReplicasForCustomMetrics(hpa *extensions.HorizontalPodAutoscaler, scale *extensions.Scale, + cmAnnotation string) (replicas int, metric string, status string, timestamp time.Time, err error) { + + currentReplicas := scale.Status.Replicas + replicas = 0 + metric = "" + status = "" + timestamp = time.Time{} + err = nil + + if cmAnnotation == "" { + return + } + + var targetList extensions.CustomMetricTargetList + if err := json.Unmarshal([]byte(cmAnnotation), &targetList); err != nil { + return 0, "", "", time.Time{}, fmt.Errorf("failed to parse custom metrics annotation: %v", err) + } + if len(targetList.Items) == 0 { + return 0, "", "", time.Time{}, fmt.Errorf("no custom metrics in annotation") + } + + statusList := extensions.CustomMetricCurrentStatusList{ + Items: make([]extensions.CustomMetricCurrentStatus, 0), + } + + for _, customMetricTarget := range targetList.Items { + if scale.Status.Selector == nil { + errMsg := "selector is required" + a.eventRecorder.Event(hpa, api.EventTypeWarning, "SelectorRequired", errMsg) + return 0, "", "", time.Time{}, fmt.Errorf("selector is required") + } + + selector, err := unversioned.LabelSelectorAsSelector(scale.Status.Selector) + if err != nil { + errMsg := fmt.Sprintf("couldn't convert selector string to a corresponding selector object: %v", err) + a.eventRecorder.Event(hpa, api.EventTypeWarning, "InvalidSelector", errMsg) + return 0, "", "", time.Time{}, fmt.Errorf("couldn't convert selector string to a corresponding selector object: %v", err) + } + value, currentTimestamp, err := a.metricsClient.GetCustomMetric(customMetricTarget.Name, hpa.Namespace, selector) + // TODO: what to do on partial errors (like metrics obtained for 75% of pods). + if err != nil { + a.eventRecorder.Event(hpa, api.EventTypeWarning, "FailedGetCustomMetrics", err.Error()) + return 0, "", "", time.Time{}, fmt.Errorf("failed to get custom metric value: %v", err) + } + floatTarget := float64(customMetricTarget.TargetValue.MilliValue()) / 1000.0 + usageRatio := *value / floatTarget + + replicaCountProposal := 0 + if math.Abs(1.0-usageRatio) > tolerance { + replicaCountProposal = int(math.Ceil(usageRatio * float64(currentReplicas))) + } else { + replicaCountProposal = currentReplicas + } + if replicaCountProposal > replicas { + timestamp = currentTimestamp + replicas = replicaCountProposal + metric = fmt.Sprintf("Custom metric %s", customMetricTarget.Name) + } + quantity, err := resource.ParseQuantity(fmt.Sprintf("%.3f", *value)) + if err != nil { + return 0, "", "", time.Time{}, fmt.Errorf("failed to set custom metric value: %v", err) + } + statusList.Items = append(statusList.Items, extensions.CustomMetricCurrentStatus{ + Name: customMetricTarget.Name, + CurrentValue: *quantity, + }) + } + byteStatusList, err := json.Marshal(statusList) + if err != nil { + return 0, "", "", time.Time{}, fmt.Errorf("failed to serialize custom metric status: %v", err) + } + + return replicas, metric, string(byteStatusList), timestamp, nil +} + +func (a *HorizontalController) reconcileAutoscaler(hpa *extensions.HorizontalPodAutoscaler) error { + reference := fmt.Sprintf("%s/%s/%s", hpa.Spec.ScaleRef.Kind, hpa.Namespace, hpa.Spec.ScaleRef.Name) + + scale, err := a.scaleNamespacer.Scales(hpa.Namespace).Get(hpa.Spec.ScaleRef.Kind, hpa.Spec.ScaleRef.Name) + if err != nil { + a.eventRecorder.Event(hpa, api.EventTypeWarning, "FailedGetScale", err.Error()) + return fmt.Errorf("failed to query scale subresource for %s: %v", reference, err) + } + currentReplicas := scale.Status.Replicas + + cpuDesiredReplicas := 0 + var cpuCurrentUtilization *int = nil + cpuTimestamp := time.Time{} + + cmDesiredReplicas := 0 + cmMetric := "" + cmStatus := "" + cmTimestamp := time.Time{} + + desiredReplicas := 0 + rescaleReason := "" + timestamp := time.Now() + + if currentReplicas > hpa.Spec.MaxReplicas { + rescaleReason = "Current number of replicas above Spec.MaxReplicas" + desiredReplicas = hpa.Spec.MaxReplicas + } else if hpa.Spec.MinReplicas != nil && currentReplicas < *hpa.Spec.MinReplicas { + rescaleReason = "Current number of replicas below Spec.MinReplicas" + desiredReplicas = *hpa.Spec.MinReplicas + } else if currentReplicas == 0 { + rescaleReason = "Current number of replicas must be greater than 0" + desiredReplicas = 1 + } else { + // All basic scenarios covered, the state should be sane, lets use metrics. + cmAnnotation, cmAnnotationFound := hpa.Annotations[HpaCustomMetricsTargetAnnotationName] + + if hpa.Spec.CPUUtilization != nil || !cmAnnotationFound { + cpuDesiredReplicas, cpuCurrentUtilization, cpuTimestamp, err = a.computeReplicasForCPUUtilization(hpa, scale) + if err != nil { + a.updateCurrentReplicasInStatus(hpa, currentReplicas) + a.eventRecorder.Event(hpa, api.EventTypeWarning, "FailedComputeReplicas", err.Error()) + return fmt.Errorf("failed to compute desired number of replicas based on CPU utilization for %s: %v", reference, err) + } + } + + if cmAnnotationFound { + cmDesiredReplicas, cmMetric, cmStatus, cmTimestamp, err = a.computeReplicasForCustomMetrics(hpa, scale, cmAnnotation) + if err != nil { + a.updateCurrentReplicasInStatus(hpa, currentReplicas) + a.eventRecorder.Event(hpa, api.EventTypeWarning, "FailedComputeCMReplicas", err.Error()) + return fmt.Errorf("failed to compute desired number of replicas based on Custom Metrics for %s: %v", reference, err) + } + } + + rescaleMetric := "" + if cpuDesiredReplicas > desiredReplicas { + desiredReplicas = cpuDesiredReplicas + timestamp = cpuTimestamp + rescaleMetric = "CPU utilization" + } + if cmDesiredReplicas > desiredReplicas { + desiredReplicas = cmDesiredReplicas + timestamp = cmTimestamp + rescaleMetric = cmMetric + } + if desiredReplicas > currentReplicas { + rescaleReason = fmt.Sprintf("%s above target", rescaleMetric) + } else if desiredReplicas < currentReplicas { + rescaleReason = "All metrics below target" + } + + if hpa.Spec.MinReplicas != nil && desiredReplicas < *hpa.Spec.MinReplicas { + desiredReplicas = *hpa.Spec.MinReplicas + } + + // TODO: remove when pod idling is done. + if desiredReplicas == 0 { + desiredReplicas = 1 + } + + if desiredReplicas > hpa.Spec.MaxReplicas { + desiredReplicas = hpa.Spec.MaxReplicas + } + } + + rescale := shouldScale(hpa, currentReplicas, desiredReplicas, timestamp) + if rescale { + scale.Spec.Replicas = desiredReplicas + _, err = a.scaleNamespacer.Scales(hpa.Namespace).Update(hpa.Spec.ScaleRef.Kind, scale) + if err != nil { + a.eventRecorder.Eventf(hpa, api.EventTypeWarning, "FailedRescale", "New size: %d; reason: %s; error: %v", desiredReplicas, rescaleReason, err.Error()) + return fmt.Errorf("failed to rescale %s: %v", reference, err) + } + a.eventRecorder.Eventf(hpa, api.EventTypeNormal, "SuccessfulRescale", "New size: %d; reason: %s", desiredReplicas, rescaleReason) + glog.Infof("Successfull rescale of %s, old size: %d, new size: %d, reason: %s", + hpa.Name, currentReplicas, desiredReplicas, rescaleReason) + } else { + desiredReplicas = currentReplicas + } + + return a.updateStatus(hpa, currentReplicas, desiredReplicas, cpuCurrentUtilization, cmStatus, rescale) +} + +func shouldScale(hpa *extensions.HorizontalPodAutoscaler, currentReplicas, desiredReplicas int, timestamp time.Time) bool { + if desiredReplicas != currentReplicas { + // Going down only if the usageRatio dropped significantly below the target + // and there was no rescaling in the last downscaleForbiddenWindow. + if desiredReplicas < currentReplicas && + (hpa.Status.LastScaleTime == nil || + hpa.Status.LastScaleTime.Add(downscaleForbiddenWindow).Before(timestamp)) { + return true + } + + // Going up only if the usage ratio increased significantly above the target + // and there was no rescaling in the last upscaleForbiddenWindow. + if desiredReplicas > currentReplicas && + (hpa.Status.LastScaleTime == nil || + hpa.Status.LastScaleTime.Add(upscaleForbiddenWindow).Before(timestamp)) { + return true + } + } + return false +} + +func (a *HorizontalController) updateCurrentReplicasInStatus(hpa *extensions.HorizontalPodAutoscaler, currentReplicas int) { + err := a.updateStatus(hpa, currentReplicas, hpa.Status.DesiredReplicas, hpa.Status.CurrentCPUUtilizationPercentage, hpa.Annotations[HpaCustomMetricsStatusAnnotationName], false) + if err != nil { + glog.Errorf("%v", err) + } +} + +func (a *HorizontalController) updateStatus(hpa *extensions.HorizontalPodAutoscaler, currentReplicas, desiredReplicas int, cpuCurrentUtilization *int, cmStatus string, rescale bool) error { + hpa.Status = extensions.HorizontalPodAutoscalerStatus{ + CurrentReplicas: currentReplicas, + DesiredReplicas: desiredReplicas, + CurrentCPUUtilizationPercentage: cpuCurrentUtilization, + LastScaleTime: hpa.Status.LastScaleTime, + } + if cmStatus != "" { + hpa.Annotations[HpaCustomMetricsStatusAnnotationName] = cmStatus + } + + if rescale { + now := unversioned.NewTime(time.Now()) + hpa.Status.LastScaleTime = &now + } + + _, err := a.hpaNamespacer.HorizontalPodAutoscalers(hpa.Namespace).UpdateStatus(hpa) + if err != nil { + a.eventRecorder.Event(hpa, api.EventTypeWarning, "FailedUpdateStatus", err.Error()) + return fmt.Errorf("failed to update status for %s: %v", hpa.Name, err) + } + glog.V(2).Infof("Successfully updated status for %s", hpa.Name) + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/podautoscaler/horizontal_test.go b/vendor/k8s.io/kubernetes/pkg/controller/podautoscaler/horizontal_test.go new file mode 100644 index 000000000..2a9740a31 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/podautoscaler/horizontal_test.go @@ -0,0 +1,702 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 podautoscaler + +import ( + "encoding/json" + "fmt" + "io" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/unversioned" + _ "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/client/unversioned/testclient" + "k8s.io/kubernetes/pkg/controller/podautoscaler/metrics" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/watch" + + heapster "k8s.io/heapster/api/v1/types" + + "github.com/stretchr/testify/assert" +) + +func (w fakeResponseWrapper) DoRaw() ([]byte, error) { + return w.raw, nil +} + +func (w fakeResponseWrapper) Stream() (io.ReadCloser, error) { + return nil, nil +} + +func newFakeResponseWrapper(raw []byte) fakeResponseWrapper { + return fakeResponseWrapper{raw: raw} +} + +type fakeResponseWrapper struct { + raw []byte +} + +type fakeResource struct { + name string + apiVersion string + kind string +} + +type testCase struct { + minReplicas int + maxReplicas int + initialReplicas int + desiredReplicas int + // CPU target utilization as a percentage of the requested resources. + CPUTarget int + CPUCurrent int + verifyCPUCurrent bool + reportedLevels []uint64 + reportedCPURequests []resource.Quantity + cmTarget *extensions.CustomMetricTargetList + scaleUpdated bool + statusUpdated bool + eventCreated bool + verifyEvents bool + // Channel with names of HPA objects which we have reconciled. + processed chan string + + // Target resource information. + resource *fakeResource +} + +func (tc *testCase) computeCPUCurrent() { + if len(tc.reportedLevels) != len(tc.reportedCPURequests) || len(tc.reportedLevels) == 0 { + return + } + reported := 0 + for _, r := range tc.reportedLevels { + reported += int(r) + } + requested := 0 + for _, req := range tc.reportedCPURequests { + requested += int(req.MilliValue()) + } + tc.CPUCurrent = 100 * reported / requested +} + +func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset { + namespace := "test-namespace" + hpaName := "test-hpa" + podNamePrefix := "test-pod" + selector := &unversioned.LabelSelector{ + MatchLabels: map[string]string{"name": podNamePrefix}, + } + + tc.scaleUpdated = false + tc.statusUpdated = false + tc.eventCreated = false + tc.processed = make(chan string, 100) + tc.computeCPUCurrent() + + // TODO(madhusudancs): HPA only supports resources in extensions/v1beta1 right now. Add + // tests for "v1" replicationcontrollers when HPA adds support for cross-group scale. + if tc.resource == nil { + tc.resource = &fakeResource{ + name: "test-rc", + apiVersion: "extensions/v1beta1", + kind: "replicationcontrollers", + } + } + + fakeClient := &fake.Clientset{} + fakeClient.AddReactor("list", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) { + obj := &extensions.HorizontalPodAutoscalerList{ + Items: []extensions.HorizontalPodAutoscaler{ + { + ObjectMeta: api.ObjectMeta{ + Name: hpaName, + Namespace: namespace, + SelfLink: "experimental/v1/namespaces/" + namespace + "/horizontalpodautoscalers/" + hpaName, + }, + Spec: extensions.HorizontalPodAutoscalerSpec{ + ScaleRef: extensions.SubresourceReference{ + Kind: tc.resource.kind, + Name: tc.resource.name, + APIVersion: tc.resource.apiVersion, + Subresource: "scale", + }, + MinReplicas: &tc.minReplicas, + MaxReplicas: tc.maxReplicas, + }, + Status: extensions.HorizontalPodAutoscalerStatus{ + CurrentReplicas: tc.initialReplicas, + DesiredReplicas: tc.initialReplicas, + }, + }, + }, + } + if tc.CPUTarget > 0.0 { + obj.Items[0].Spec.CPUUtilization = &extensions.CPUTargetUtilization{TargetPercentage: tc.CPUTarget} + } + if tc.cmTarget != nil { + b, err := json.Marshal(tc.cmTarget) + if err != nil { + t.Fatalf("Failed to marshal cm: %v", err) + } + obj.Items[0].Annotations = make(map[string]string) + obj.Items[0].Annotations[HpaCustomMetricsTargetAnnotationName] = string(b) + } + return true, obj, nil + }) + + fakeClient.AddReactor("get", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { + obj := &extensions.Scale{ + ObjectMeta: api.ObjectMeta{ + Name: tc.resource.name, + Namespace: namespace, + }, + Spec: extensions.ScaleSpec{ + Replicas: tc.initialReplicas, + }, + Status: extensions.ScaleStatus{ + Replicas: tc.initialReplicas, + Selector: selector, + }, + } + return true, obj, nil + }) + + fakeClient.AddReactor("get", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) { + obj := &extensions.Scale{ + ObjectMeta: api.ObjectMeta{ + Name: tc.resource.name, + Namespace: namespace, + }, + Spec: extensions.ScaleSpec{ + Replicas: tc.initialReplicas, + }, + Status: extensions.ScaleStatus{ + Replicas: tc.initialReplicas, + Selector: selector, + }, + } + return true, obj, nil + }) + + fakeClient.AddReactor("get", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) { + obj := &extensions.Scale{ + ObjectMeta: api.ObjectMeta{ + Name: tc.resource.name, + Namespace: namespace, + }, + Spec: extensions.ScaleSpec{ + Replicas: tc.initialReplicas, + }, + Status: extensions.ScaleStatus{ + Replicas: tc.initialReplicas, + Selector: selector, + }, + } + return true, obj, nil + }) + + fakeClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { + obj := &api.PodList{} + for i := 0; i < len(tc.reportedCPURequests); i++ { + podName := fmt.Sprintf("%s-%d", podNamePrefix, i) + pod := api.Pod{ + Status: api.PodStatus{ + Phase: api.PodRunning, + }, + ObjectMeta: api.ObjectMeta{ + Name: podName, + Namespace: namespace, + Labels: map[string]string{ + "name": podNamePrefix, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceCPU: tc.reportedCPURequests[i], + }, + }, + }, + }, + }, + } + obj.Items = append(obj.Items, pod) + } + return true, obj, nil + }) + + fakeClient.AddProxyReactor("services", func(action core.Action) (handled bool, ret restclient.ResponseWrapper, err error) { + timestamp := time.Now() + metrics := heapster.MetricResultList{} + for _, level := range tc.reportedLevels { + metric := heapster.MetricResult{ + Metrics: []heapster.MetricPoint{{timestamp, level, nil}}, + LatestTimestamp: timestamp, + } + metrics.Items = append(metrics.Items, metric) + } + heapsterRawMemResponse, _ := json.Marshal(&metrics) + return true, newFakeResponseWrapper(heapsterRawMemResponse), nil + }) + + fakeClient.AddReactor("update", "replicationcontrollers", func(action core.Action) (handled bool, ret runtime.Object, err error) { + obj := action.(testclient.UpdateAction).GetObject().(*extensions.Scale) + replicas := action.(testclient.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas + assert.Equal(t, tc.desiredReplicas, replicas) + tc.scaleUpdated = true + return true, obj, nil + }) + + fakeClient.AddReactor("update", "deployments", func(action core.Action) (handled bool, ret runtime.Object, err error) { + obj := action.(testclient.UpdateAction).GetObject().(*extensions.Scale) + replicas := action.(testclient.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas + assert.Equal(t, tc.desiredReplicas, replicas) + tc.scaleUpdated = true + return true, obj, nil + }) + + fakeClient.AddReactor("update", "replicasets", func(action core.Action) (handled bool, ret runtime.Object, err error) { + obj := action.(testclient.UpdateAction).GetObject().(*extensions.Scale) + replicas := action.(testclient.UpdateAction).GetObject().(*extensions.Scale).Spec.Replicas + assert.Equal(t, tc.desiredReplicas, replicas) + tc.scaleUpdated = true + return true, obj, nil + }) + + fakeClient.AddReactor("update", "horizontalpodautoscalers", func(action core.Action) (handled bool, ret runtime.Object, err error) { + obj := action.(testclient.UpdateAction).GetObject().(*extensions.HorizontalPodAutoscaler) + assert.Equal(t, namespace, obj.Namespace) + assert.Equal(t, hpaName, obj.Name) + assert.Equal(t, tc.desiredReplicas, obj.Status.DesiredReplicas) + if tc.verifyCPUCurrent { + assert.NotNil(t, obj.Status.CurrentCPUUtilizationPercentage) + assert.Equal(t, tc.CPUCurrent, *obj.Status.CurrentCPUUtilizationPercentage) + } + tc.statusUpdated = true + // Every time we reconcile HPA object we are updating status. + tc.processed <- obj.Name + return true, obj, nil + }) + + fakeClient.AddReactor("*", "events", func(action core.Action) (handled bool, ret runtime.Object, err error) { + obj := action.(testclient.CreateAction).GetObject().(*api.Event) + if tc.verifyEvents { + assert.Equal(t, "SuccessfulRescale", obj.Reason) + assert.Equal(t, fmt.Sprintf("New size: %d; reason: CPU utilization above target", tc.desiredReplicas), obj.Message) + } + tc.eventCreated = true + return true, obj, nil + }) + + fakeWatch := watch.NewFake() + fakeClient.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) + + return fakeClient +} + +func (tc *testCase) verifyResults(t *testing.T) { + assert.Equal(t, tc.initialReplicas != tc.desiredReplicas, tc.scaleUpdated) + assert.True(t, tc.statusUpdated) + if tc.verifyEvents { + assert.Equal(t, tc.initialReplicas != tc.desiredReplicas, tc.eventCreated) + } +} + +func (tc *testCase) runTest(t *testing.T) { + testClient := tc.prepareTestClient(t) + metricsClient := metrics.NewHeapsterMetricsClient(testClient, metrics.DefaultHeapsterNamespace, metrics.DefaultHeapsterScheme, metrics.DefaultHeapsterService, metrics.DefaultHeapsterPort) + + broadcaster := record.NewBroadcasterForTests(0) + broadcaster.StartRecordingToSink(&unversionedcore.EventSinkImpl{Interface: testClient.Core().Events("")}) + recorder := broadcaster.NewRecorder(api.EventSource{Component: "horizontal-pod-autoscaler"}) + + hpaController := &HorizontalController{ + metricsClient: metricsClient, + eventRecorder: recorder, + scaleNamespacer: testClient.Extensions(), + hpaNamespacer: testClient.Extensions(), + } + + store, frameworkController := newInformer(hpaController, time.Minute) + hpaController.store = store + hpaController.controller = frameworkController + + stop := make(chan struct{}) + defer close(stop) + go hpaController.Run(stop) + + if tc.verifyEvents { + // We need to wait for events to be broadcasted (sleep for longer than record.sleepDuration). + time.Sleep(2 * time.Second) + } + // Wait for HPA to be processed. + <-tc.processed + tc.verifyResults(t) +} + +func TestDefaultScaleUpRC(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + initialReplicas: 4, + desiredReplicas: 5, + verifyCPUCurrent: true, + reportedLevels: []uint64{900, 950, 950, 1000}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + } + tc.runTest(t) +} + +func TestDefaultScaleUpDeployment(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + initialReplicas: 4, + desiredReplicas: 5, + verifyCPUCurrent: true, + reportedLevels: []uint64{900, 950, 950, 1000}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + resource: &fakeResource{ + name: "test-dep", + apiVersion: "extensions/v1beta1", + kind: "deployments", + }, + } + tc.runTest(t) +} + +func TestDefaultScaleUpReplicaSet(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + initialReplicas: 4, + desiredReplicas: 5, + verifyCPUCurrent: true, + reportedLevels: []uint64{900, 950, 950, 1000}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + resource: &fakeResource{ + name: "test-replicaset", + apiVersion: "extensions/v1beta1", + kind: "replicasets", + }, + } + tc.runTest(t) +} + +func TestScaleUp(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + initialReplicas: 3, + desiredReplicas: 5, + CPUTarget: 30, + verifyCPUCurrent: true, + reportedLevels: []uint64{300, 500, 700}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + } + tc.runTest(t) +} + +func TestScaleUpDeployment(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + initialReplicas: 3, + desiredReplicas: 5, + CPUTarget: 30, + verifyCPUCurrent: true, + reportedLevels: []uint64{300, 500, 700}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + resource: &fakeResource{ + name: "test-dep", + apiVersion: "extensions/v1beta1", + kind: "deployments", + }, + } + tc.runTest(t) +} + +func TestScaleUpReplicaSet(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + initialReplicas: 3, + desiredReplicas: 5, + CPUTarget: 30, + verifyCPUCurrent: true, + reportedLevels: []uint64{300, 500, 700}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + resource: &fakeResource{ + name: "test-replicaset", + apiVersion: "extensions/v1beta1", + kind: "replicasets", + }, + } + tc.runTest(t) +} + +func TestScaleUpCM(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + initialReplicas: 3, + desiredReplicas: 4, + CPUTarget: 0, + cmTarget: &extensions.CustomMetricTargetList{ + Items: []extensions.CustomMetricTarget{{ + Name: "qps", + TargetValue: resource.MustParse("15.0"), + }}, + }, + reportedLevels: []uint64{20, 10, 30}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + } + tc.runTest(t) +} + +func TestDefaultScaleDown(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + initialReplicas: 5, + desiredReplicas: 4, + verifyCPUCurrent: true, + reportedLevels: []uint64{400, 500, 600, 700, 800}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + } + tc.runTest(t) +} + +func TestScaleDown(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + initialReplicas: 5, + desiredReplicas: 3, + CPUTarget: 50, + verifyCPUCurrent: true, + reportedLevels: []uint64{100, 300, 500, 250, 250}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + } + tc.runTest(t) +} + +func TestScaleDownCM(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + initialReplicas: 5, + desiredReplicas: 3, + CPUTarget: 0, + cmTarget: &extensions.CustomMetricTargetList{ + Items: []extensions.CustomMetricTarget{{ + Name: "qps", + TargetValue: resource.MustParse("20"), + }}}, + reportedLevels: []uint64{12, 12, 12, 12, 12}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + } + tc.runTest(t) +} + +func TestTolerance(t *testing.T) { + tc := testCase{ + minReplicas: 1, + maxReplicas: 5, + initialReplicas: 3, + desiredReplicas: 3, + CPUTarget: 100, + reportedLevels: []uint64{1010, 1030, 1020}, + reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")}, + } + tc.runTest(t) +} + +func TestToleranceCM(t *testing.T) { + tc := testCase{ + minReplicas: 1, + maxReplicas: 5, + initialReplicas: 3, + desiredReplicas: 3, + cmTarget: &extensions.CustomMetricTargetList{ + Items: []extensions.CustomMetricTarget{{ + Name: "qps", + TargetValue: resource.MustParse("20"), + }}}, + reportedLevels: []uint64{20, 21, 21}, + reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")}, + } + tc.runTest(t) +} + +func TestMinReplicas(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 5, + initialReplicas: 3, + desiredReplicas: 2, + CPUTarget: 90, + reportedLevels: []uint64{10, 95, 10}, + reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")}, + } + tc.runTest(t) +} + +func TestZeroReplicas(t *testing.T) { + tc := testCase{ + minReplicas: 3, + maxReplicas: 5, + initialReplicas: 0, + desiredReplicas: 3, + CPUTarget: 90, + reportedLevels: []uint64{}, + reportedCPURequests: []resource.Quantity{}, + } + tc.runTest(t) +} + +func TestTooFewReplicas(t *testing.T) { + tc := testCase{ + minReplicas: 3, + maxReplicas: 5, + initialReplicas: 2, + desiredReplicas: 3, + CPUTarget: 90, + reportedLevels: []uint64{}, + reportedCPURequests: []resource.Quantity{}, + } + tc.runTest(t) +} + +func TestTooManyReplicas(t *testing.T) { + tc := testCase{ + minReplicas: 3, + maxReplicas: 5, + initialReplicas: 10, + desiredReplicas: 5, + CPUTarget: 90, + reportedLevels: []uint64{}, + reportedCPURequests: []resource.Quantity{}, + } + tc.runTest(t) +} + +func TestMaxReplicas(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 5, + initialReplicas: 3, + desiredReplicas: 5, + CPUTarget: 90, + reportedLevels: []uint64{8000, 9500, 1000}, + reportedCPURequests: []resource.Quantity{resource.MustParse("0.9"), resource.MustParse("1.0"), resource.MustParse("1.1")}, + } + tc.runTest(t) +} + +func TestSuperfluousMetrics(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + initialReplicas: 4, + desiredReplicas: 4, + CPUTarget: 100, + reportedLevels: []uint64{4000, 9500, 3000, 7000, 3200, 2000}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + } + tc.runTest(t) +} + +func TestMissingMetrics(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + initialReplicas: 4, + desiredReplicas: 4, + CPUTarget: 100, + reportedLevels: []uint64{400, 95}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + } + tc.runTest(t) +} + +func TestEmptyMetrics(t *testing.T) { + tc := testCase{ + minReplicas: 2, + maxReplicas: 6, + initialReplicas: 4, + desiredReplicas: 4, + CPUTarget: 100, + reportedLevels: []uint64{}, + reportedCPURequests: []resource.Quantity{resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0"), resource.MustParse("1.0")}, + } + tc.runTest(t) +} + +func TestEmptyCPURequest(t *testing.T) { + tc := testCase{ + minReplicas: 1, + maxReplicas: 5, + initialReplicas: 1, + desiredReplicas: 1, + CPUTarget: 100, + reportedLevels: []uint64{200}, + } + tc.runTest(t) +} + +func TestEventCreated(t *testing.T) { + tc := testCase{ + minReplicas: 1, + maxReplicas: 5, + initialReplicas: 1, + desiredReplicas: 2, + CPUTarget: 50, + reportedLevels: []uint64{200}, + reportedCPURequests: []resource.Quantity{resource.MustParse("0.2")}, + verifyEvents: true, + } + tc.runTest(t) +} + +func TestEventNotCreated(t *testing.T) { + tc := testCase{ + minReplicas: 1, + maxReplicas: 5, + initialReplicas: 2, + desiredReplicas: 2, + CPUTarget: 50, + reportedLevels: []uint64{200, 200}, + reportedCPURequests: []resource.Quantity{resource.MustParse("0.4"), resource.MustParse("0.4")}, + verifyEvents: true, + } + tc.runTest(t) +} + +// TODO: add more tests diff --git a/vendor/k8s.io/kubernetes/pkg/controller/podautoscaler/metrics/metrics_client.go b/vendor/k8s.io/kubernetes/pkg/controller/podautoscaler/metrics/metrics_client.go new file mode 100644 index 000000000..9e05767f3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/podautoscaler/metrics/metrics_client.go @@ -0,0 +1,268 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 metrics + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/labels" + + heapster "k8s.io/heapster/api/v1/types" +) + +const ( + DefaultHeapsterNamespace = "kube-system" + DefaultHeapsterScheme = "http" + DefaultHeapsterService = "heapster" + DefaultHeapsterPort = "" // use the first exposed port on the service +) + +var heapsterQueryStart = -5 * time.Minute + +// MetricsClient is an interface for getting metrics for pods. +type MetricsClient interface { + // GetCPUUtilization returns the average utilization over all pods represented as a percent of requested CPU + // (e.g. 70 means that an average pod uses 70% of the requested CPU) + // and the time of generation of the oldest of utilization reports for pods. + GetCPUUtilization(namespace string, selector labels.Selector) (*int, time.Time, error) + + // GetCustomMetric returns the average value of the given custom metrics from the + // pods picked using the namespace and selector passed as arguments. + GetCustomMetric(customMetricName string, namespace string, selector labels.Selector) (*float64, time.Time, error) +} + +type intAndFloat struct { + intValue int64 + floatValue float64 +} + +// Aggregates results into ResourceConsumption. Also returns number of pods included in the aggregation. +type metricAggregator func(heapster.MetricResultList) (intAndFloat, int, time.Time) + +type metricDefinition struct { + name string + aggregator metricAggregator +} + +// HeapsterMetricsClient is Heapster-based implementation of MetricsClient +type HeapsterMetricsClient struct { + client clientset.Interface + heapsterNamespace string + heapsterScheme string + heapsterService string + heapsterPort string +} + +var averageFunction = func(metrics heapster.MetricResultList) (intAndFloat, int, time.Time) { + sum, count, timestamp := calculateSumFromTimeSample(metrics, time.Minute) + result := intAndFloat{0, 0} + if count > 0 { + result.intValue = sum.intValue / int64(count) + result.floatValue = sum.floatValue / float64(count) + } + return result, count, timestamp +} + +var heapsterCpuUsageMetricDefinition = metricDefinition{"cpu-usage", averageFunction} + +func getHeapsterCustomMetricDefinition(metricName string) metricDefinition { + return metricDefinition{"custom/" + metricName, averageFunction} +} + +// NewHeapsterMetricsClient returns a new instance of Heapster-based implementation of MetricsClient interface. +func NewHeapsterMetricsClient(client clientset.Interface, namespace, scheme, service, port string) *HeapsterMetricsClient { + return &HeapsterMetricsClient{ + client: client, + heapsterNamespace: namespace, + heapsterScheme: scheme, + heapsterService: service, + heapsterPort: port, + } +} + +func (h *HeapsterMetricsClient) GetCPUUtilization(namespace string, selector labels.Selector) (*int, time.Time, error) { + avgConsumption, avgRequest, timestamp, err := h.GetCpuConsumptionAndRequestInMillis(namespace, selector) + if err != nil { + return nil, time.Time{}, fmt.Errorf("failed to get CPU consumption and request: %v", err) + } + utilization := int((avgConsumption * 100) / avgRequest) + return &utilization, timestamp, nil +} + +func (h *HeapsterMetricsClient) GetCpuConsumptionAndRequestInMillis(namespace string, selector labels.Selector) (avgConsumption int64, + avgRequest int64, timestamp time.Time, err error) { + + podList, err := h.client.Core().Pods(namespace). + List(api.ListOptions{LabelSelector: selector}) + + if err != nil { + return 0, 0, time.Time{}, fmt.Errorf("failed to get pod list: %v", err) + } + podNames := []string{} + requestSum := int64(0) + missing := false + for _, pod := range podList.Items { + if pod.Status.Phase == api.PodPending { + // Skip pending pods. + continue + } + + podNames = append(podNames, pod.Name) + for _, container := range pod.Spec.Containers { + containerRequest := container.Resources.Requests[api.ResourceCPU] + if containerRequest.Amount != nil { + requestSum += containerRequest.MilliValue() + } else { + missing = true + } + } + } + if len(podNames) == 0 && len(podList.Items) > 0 { + return 0, 0, time.Time{}, fmt.Errorf("no running pods") + } + if missing || requestSum == 0 { + return 0, 0, time.Time{}, fmt.Errorf("some pods do not have request for cpu") + } + glog.V(4).Infof("%s %s - sum of CPU requested: %d", namespace, selector, requestSum) + requestAvg := requestSum / int64(len(podList.Items)) + // Consumption is already averaged and in millis. + consumption, timestamp, err := h.getForPods(heapsterCpuUsageMetricDefinition, namespace, podNames) + if err != nil { + return 0, 0, time.Time{}, err + } + return consumption.intValue, requestAvg, timestamp, nil +} + +// GetCustomMetric returns the average value of the given custom metric from the +// pods picked using the namespace and selector passed as arguments. +func (h *HeapsterMetricsClient) GetCustomMetric(customMetricName string, namespace string, selector labels.Selector) (*float64, time.Time, error) { + metricSpec := getHeapsterCustomMetricDefinition(customMetricName) + + podList, err := h.client.Core().Pods(namespace).List(api.ListOptions{LabelSelector: selector}) + + if err != nil { + return nil, time.Time{}, fmt.Errorf("failed to get pod list: %v", err) + } + podNames := []string{} + for _, pod := range podList.Items { + if pod.Status.Phase == api.PodPending { + // Skip pending pods. + continue + } + podNames = append(podNames, pod.Name) + } + if len(podNames) == 0 && len(podList.Items) > 0 { + return nil, time.Time{}, fmt.Errorf("no running pods") + } + + value, timestamp, err := h.getForPods(metricSpec, namespace, podNames) + if err != nil { + return nil, time.Time{}, err + } + return &value.floatValue, timestamp, nil +} + +func (h *HeapsterMetricsClient) getForPods(metricSpec metricDefinition, namespace string, podNames []string) (*intAndFloat, time.Time, error) { + + now := time.Now() + + startTime := now.Add(heapsterQueryStart) + metricPath := fmt.Sprintf("/api/v1/model/namespaces/%s/pod-list/%s/metrics/%s", + namespace, + strings.Join(podNames, ","), + metricSpec.name) + + resultRaw, err := h.client.Core().Services(h.heapsterNamespace). + ProxyGet(h.heapsterScheme, h.heapsterService, h.heapsterPort, metricPath, map[string]string{"start": startTime.Format(time.RFC3339)}). + DoRaw() + + if err != nil { + return nil, time.Time{}, fmt.Errorf("failed to get pods metrics: %v", err) + } + + var metrics heapster.MetricResultList + err = json.Unmarshal(resultRaw, &metrics) + if err != nil { + return nil, time.Time{}, fmt.Errorf("failed to unmarshall heapster response: %v", err) + } + + glog.V(4).Infof("Heapster metrics result: %s", string(resultRaw)) + + sum, count, timestamp := metricSpec.aggregator(metrics) + if count != len(podNames) { + return nil, time.Time{}, fmt.Errorf("metrics obtained for %d/%d of pods", count, len(podNames)) + } + + return &sum, timestamp, nil +} + +func calculateSumFromTimeSample(metrics heapster.MetricResultList, duration time.Duration) (sum intAndFloat, count int, timestamp time.Time) { + sum = intAndFloat{0, 0} + count = 0 + timestamp = time.Time{} + var oldest *time.Time // creation time of the oldest of used samples across pods + oldest = nil + for _, metrics := range metrics.Items { + var newest *heapster.MetricPoint // creation time of the newest sample for pod + newest = nil + for i, metricPoint := range metrics.Metrics { + if newest == nil || newest.Timestamp.Before(metricPoint.Timestamp) { + newest = &metrics.Metrics[i] + } + } + if newest != nil { + if oldest == nil || newest.Timestamp.Before(*oldest) { + oldest = &newest.Timestamp + } + intervalSum := intAndFloat{0, 0} + intSumCount := 0 + floatSumCount := 0 + for _, metricPoint := range metrics.Metrics { + if metricPoint.Timestamp.Add(duration).After(newest.Timestamp) { + intervalSum.intValue += int64(metricPoint.Value) + intSumCount++ + if metricPoint.FloatValue != nil { + intervalSum.floatValue += *metricPoint.FloatValue + floatSumCount++ + } + } + } + if newest.FloatValue == nil { + if intSumCount > 0 { + sum.intValue += int64(intervalSum.intValue / int64(intSumCount)) + sum.floatValue += float64(intervalSum.intValue / int64(intSumCount)) + } + } else { + if floatSumCount > 0 { + sum.intValue += int64(intervalSum.floatValue / float64(floatSumCount)) + sum.floatValue += intervalSum.floatValue / float64(floatSumCount) + } + } + count++ + } + } + if oldest != nil { + timestamp = *oldest + } + return sum, count, timestamp +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/podautoscaler/metrics/metrics_client_test.go b/vendor/k8s.io/kubernetes/pkg/controller/podautoscaler/metrics/metrics_client_test.go new file mode 100644 index 000000000..7bb71c06a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/podautoscaler/metrics/metrics_client_test.go @@ -0,0 +1,455 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 metrics + +import ( + "encoding/json" + "fmt" + "io" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + _ "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" + + heapster "k8s.io/heapster/api/v1/types" + + "github.com/stretchr/testify/assert" +) + +var fixedTimestamp = time.Date(2015, time.November, 10, 12, 30, 0, 0, time.UTC) + +func (w fakeResponseWrapper) DoRaw() ([]byte, error) { + return w.raw, nil +} + +func (w fakeResponseWrapper) Stream() (io.ReadCloser, error) { + return nil, nil +} + +func newFakeResponseWrapper(raw []byte) fakeResponseWrapper { + return fakeResponseWrapper{raw: raw} +} + +type fakeResponseWrapper struct { + raw []byte +} + +// timestamp is used for establishing order on metricPoints +type metricPoint struct { + level uint64 + timestamp int +} + +type testCase struct { + replicas int + desiredValue float64 + desiredError error + targetResource string + targetTimestamp int + reportedMetricsPoints [][]metricPoint + namespace string + podListOverride *api.PodList + selector labels.Selector +} + +func (tc *testCase) prepareTestClient(t *testing.T) *fake.Clientset { + namespace := "test-namespace" + tc.namespace = namespace + podNamePrefix := "test-pod" + podLabels := map[string]string{"name": podNamePrefix} + tc.selector = labels.SelectorFromSet(podLabels) + + fakeClient := &fake.Clientset{} + + fakeClient.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) { + if tc.podListOverride != nil { + return true, tc.podListOverride, nil + } + obj := &api.PodList{} + for i := 0; i < tc.replicas; i++ { + podName := fmt.Sprintf("%s-%d", podNamePrefix, i) + pod := buildPod(namespace, podName, podLabels, api.PodRunning) + obj.Items = append(obj.Items, pod) + } + return true, obj, nil + }) + + fakeClient.AddProxyReactor("services", func(action core.Action) (handled bool, ret restclient.ResponseWrapper, err error) { + metrics := heapster.MetricResultList{} + var latestTimestamp time.Time + for _, reportedMetricPoints := range tc.reportedMetricsPoints { + var heapsterMetricPoints []heapster.MetricPoint + for _, reportedMetricPoint := range reportedMetricPoints { + timestamp := fixedTimestamp.Add(time.Duration(reportedMetricPoint.timestamp) * time.Minute) + if latestTimestamp.Before(timestamp) { + latestTimestamp = timestamp + } + heapsterMetricPoint := heapster.MetricPoint{Timestamp: timestamp, Value: reportedMetricPoint.level, FloatValue: nil} + heapsterMetricPoints = append(heapsterMetricPoints, heapsterMetricPoint) + } + metric := heapster.MetricResult{ + Metrics: heapsterMetricPoints, + LatestTimestamp: latestTimestamp, + } + metrics.Items = append(metrics.Items, metric) + } + heapsterRawMemResponse, _ := json.Marshal(&metrics) + return true, newFakeResponseWrapper(heapsterRawMemResponse), nil + }) + + return fakeClient +} + +func buildPod(namespace, podName string, podLabels map[string]string, phase api.PodPhase) api.Pod { + return api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: podName, + Namespace: namespace, + Labels: podLabels, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceCPU: resource.MustParse("10"), + }, + }, + }, + }, + }, + Status: api.PodStatus{ + Phase: phase, + }, + } +} + +func (tc *testCase) verifyResults(t *testing.T, val *float64, timestamp time.Time, err error) { + assert.Equal(t, tc.desiredError, err) + if tc.desiredError != nil { + return + } + assert.NotNil(t, val) + assert.True(t, tc.desiredValue-0.001 < *val) + assert.True(t, tc.desiredValue+0.001 > *val) + + targetTimestamp := fixedTimestamp.Add(time.Duration(tc.targetTimestamp) * time.Minute) + assert.Equal(t, targetTimestamp, timestamp) +} + +func (tc *testCase) runTest(t *testing.T) { + testClient := tc.prepareTestClient(t) + metricsClient := NewHeapsterMetricsClient(testClient, DefaultHeapsterNamespace, DefaultHeapsterScheme, DefaultHeapsterService, DefaultHeapsterPort) + if tc.targetResource == "cpu-usage" { + val, _, timestamp, err := metricsClient.GetCpuConsumptionAndRequestInMillis(tc.namespace, tc.selector) + fval := float64(val) + tc.verifyResults(t, &fval, timestamp, err) + } else { + val, timestamp, err := metricsClient.GetCustomMetric(tc.targetResource, tc.namespace, tc.selector) + tc.verifyResults(t, val, timestamp, err) + } +} + +func TestCPU(t *testing.T) { + tc := testCase{ + replicas: 3, + desiredValue: 5000, + targetResource: "cpu-usage", + targetTimestamp: 1, + reportedMetricsPoints: [][]metricPoint{{{5000, 1}}, {{5000, 1}}, {{5000, 1}}}, + } + tc.runTest(t) +} + +func TestCPUPending(t *testing.T) { + tc := testCase{ + replicas: 4, + desiredValue: 5000, + targetResource: "cpu-usage", + targetTimestamp: 1, + reportedMetricsPoints: [][]metricPoint{{{5000, 1}}, {{5000, 1}}, {{5000, 1}}}, + podListOverride: &api.PodList{}, + } + + namespace := "test-namespace" + podNamePrefix := "test-pod" + podLabels := map[string]string{"name": podNamePrefix} + for i := 0; i < tc.replicas; i++ { + podName := fmt.Sprintf("%s-%d", podNamePrefix, i) + pod := buildPod(namespace, podName, podLabels, api.PodRunning) + tc.podListOverride.Items = append(tc.podListOverride.Items, pod) + } + tc.podListOverride.Items[0].Status.Phase = api.PodPending + + tc.runTest(t) +} + +func TestCPUAllPending(t *testing.T) { + tc := testCase{ + replicas: 4, + targetResource: "cpu-usage", + targetTimestamp: 1, + reportedMetricsPoints: [][]metricPoint{}, + podListOverride: &api.PodList{}, + desiredError: fmt.Errorf("no running pods"), + } + + namespace := "test-namespace" + podNamePrefix := "test-pod" + podLabels := map[string]string{"name": podNamePrefix} + for i := 0; i < tc.replicas; i++ { + podName := fmt.Sprintf("%s-%d", podNamePrefix, i) + pod := buildPod(namespace, podName, podLabels, api.PodPending) + tc.podListOverride.Items = append(tc.podListOverride.Items, pod) + } + tc.runTest(t) +} + +func TestQPS(t *testing.T) { + tc := testCase{ + replicas: 3, + desiredValue: 13.33333, + targetResource: "qps", + targetTimestamp: 1, + reportedMetricsPoints: [][]metricPoint{{{10, 1}}, {{20, 1}}, {{10, 1}}}, + } + tc.runTest(t) +} + +func TestQPSPending(t *testing.T) { + tc := testCase{ + replicas: 4, + desiredValue: 13.33333, + targetResource: "qps", + targetTimestamp: 1, + reportedMetricsPoints: [][]metricPoint{{{10, 1}}, {{20, 1}}, {{10, 1}}}, + podListOverride: &api.PodList{}, + } + + namespace := "test-namespace" + podNamePrefix := "test-pod" + podLabels := map[string]string{"name": podNamePrefix} + for i := 0; i < tc.replicas; i++ { + podName := fmt.Sprintf("%s-%d", podNamePrefix, i) + pod := buildPod(namespace, podName, podLabels, api.PodRunning) + tc.podListOverride.Items = append(tc.podListOverride.Items, pod) + } + tc.podListOverride.Items[0].Status.Phase = api.PodPending + tc.runTest(t) +} + +func TestQPSAllPending(t *testing.T) { + tc := testCase{ + replicas: 4, + desiredError: fmt.Errorf("no running pods"), + targetResource: "qps", + targetTimestamp: 1, + reportedMetricsPoints: [][]metricPoint{}, + podListOverride: &api.PodList{}, + } + + namespace := "test-namespace" + podNamePrefix := "test-pod" + podLabels := map[string]string{"name": podNamePrefix} + for i := 0; i < tc.replicas; i++ { + podName := fmt.Sprintf("%s-%d", podNamePrefix, i) + pod := buildPod(namespace, podName, podLabels, api.PodPending) + tc.podListOverride.Items = append(tc.podListOverride.Items, pod) + } + tc.podListOverride.Items[0].Status.Phase = api.PodPending + tc.runTest(t) +} + +func TestCPUSumEqualZero(t *testing.T) { + tc := testCase{ + replicas: 3, + desiredValue: 0, + targetResource: "cpu-usage", + targetTimestamp: 0, + reportedMetricsPoints: [][]metricPoint{{{0, 0}}, {{0, 0}}, {{0, 0}}}, + } + tc.runTest(t) +} + +func TestQpsSumEqualZero(t *testing.T) { + tc := testCase{ + replicas: 3, + desiredValue: 0, + targetResource: "qps", + targetTimestamp: 0, + reportedMetricsPoints: [][]metricPoint{{{0, 0}}, {{0, 0}}, {{0, 0}}}, + } + tc.runTest(t) +} + +func TestCPUMoreMetrics(t *testing.T) { + tc := testCase{ + replicas: 5, + desiredValue: 5000, + targetResource: "cpu-usage", + targetTimestamp: 10, + reportedMetricsPoints: [][]metricPoint{ + {{0, 3}, {0, 6}, {5, 4}, {9000, 10}}, + {{5000, 2}, {10, 5}, {66, 1}, {0, 10}}, + {{5000, 3}, {80, 5}, {6000, 10}}, + {{5000, 3}, {40, 3}, {0, 9}, {200, 2}, {8000, 10}}, + {{5000, 2}, {20, 2}, {2000, 10}}}, + } + tc.runTest(t) +} + +func TestCPUResultIsFloat(t *testing.T) { + tc := testCase{ + replicas: 6, + desiredValue: 4783, + targetResource: "cpu-usage", + targetTimestamp: 4, + reportedMetricsPoints: [][]metricPoint{{{4000, 4}}, {{9500, 4}}, {{3000, 4}}, {{7000, 4}}, {{3200, 4}}, {{2000, 4}}}, + } + tc.runTest(t) +} + +func TestCPUSamplesWithRandomTimestamps(t *testing.T) { + tc := testCase{ + replicas: 3, + desiredValue: 3000, + targetResource: "cpu-usage", + targetTimestamp: 3, + reportedMetricsPoints: [][]metricPoint{ + {{1, 1}, {3000, 5}, {2, 2}}, + {{2, 2}, {1, 1}, {3000, 3}}, + {{3000, 4}, {1, 1}, {2, 2}}}, + } + tc.runTest(t) +} + +func TestCPUMissingMetrics(t *testing.T) { + tc := testCase{ + replicas: 3, + targetResource: "cpu-usage", + desiredError: fmt.Errorf("metrics obtained for 1/3 of pods"), + reportedMetricsPoints: [][]metricPoint{{{4000, 4}}}, + } + tc.runTest(t) +} + +func TestQpsMissingMetrics(t *testing.T) { + tc := testCase{ + replicas: 3, + targetResource: "qps", + desiredError: fmt.Errorf("metrics obtained for 1/3 of pods"), + reportedMetricsPoints: [][]metricPoint{{{4000, 4}}}, + } + tc.runTest(t) +} + +func TestCPUSuperfluousMetrics(t *testing.T) { + tc := testCase{ + replicas: 3, + targetResource: "cpu-usage", + desiredError: fmt.Errorf("metrics obtained for 6/3 of pods"), + reportedMetricsPoints: [][]metricPoint{{{1000, 1}}, {{2000, 4}}, {{2000, 1}}, {{4000, 5}}, {{2000, 1}}, {{4000, 4}}}, + } + tc.runTest(t) +} + +func TestQpsSuperfluousMetrics(t *testing.T) { + tc := testCase{ + replicas: 3, + targetResource: "qps", + desiredError: fmt.Errorf("metrics obtained for 6/3 of pods"), + reportedMetricsPoints: [][]metricPoint{{{1000, 1}}, {{2000, 4}}, {{2000, 1}}, {{4000, 5}}, {{2000, 1}}, {{4000, 4}}}, + } + tc.runTest(t) +} + +func TestCPUEmptyMetrics(t *testing.T) { + tc := testCase{ + replicas: 3, + targetResource: "cpu-usage", + desiredError: fmt.Errorf("metrics obtained for 0/3 of pods"), + reportedMetricsPoints: [][]metricPoint{}, + } + tc.runTest(t) +} + +func TestCPUZeroReplicas(t *testing.T) { + tc := testCase{ + replicas: 0, + targetResource: "cpu-usage", + desiredError: fmt.Errorf("some pods do not have request for cpu"), + reportedMetricsPoints: [][]metricPoint{}, + } + tc.runTest(t) +} + +func TestCPUEmptyMetricsForOnePod(t *testing.T) { + tc := testCase{ + replicas: 3, + targetResource: "cpu-usage", + desiredError: fmt.Errorf("metrics obtained for 2/3 of pods"), + reportedMetricsPoints: [][]metricPoint{{}, {{100, 1}}, {{400, 2}, {300, 3}}}, + } + tc.runTest(t) +} + +func TestAggregateSum(t *testing.T) { + //calculateSumFromTimeSample(metrics heapster.MetricResultList, duration time.Duration) (sum intAndFloat, count int, timestamp time.Time) { + now := time.Now() + result := heapster.MetricResultList{ + Items: []heapster.MetricResult{ + { + Metrics: []heapster.MetricPoint{ + {now, 50, nil}, + {now.Add(-15 * time.Second), 100, nil}, + {now.Add(-60 * time.Second), 100000, nil}}, + LatestTimestamp: now, + }, + }, + } + sum, cnt, _ := calculateSumFromTimeSample(result, time.Minute) + assert.Equal(t, int64(75), sum.intValue) + assert.InEpsilon(t, 75.0, sum.floatValue, 0.1) + assert.Equal(t, 1, cnt) +} + +func TestAggregateSumSingle(t *testing.T) { + now := time.Now() + result := heapster.MetricResultList{ + Items: []heapster.MetricResult{ + { + Metrics: []heapster.MetricPoint{ + {now, 50, nil}, + {now.Add(-65 * time.Second), 100000, nil}}, + LatestTimestamp: now, + }, + }, + } + sum, cnt, _ := calculateSumFromTimeSample(result, time.Minute) + assert.Equal(t, int64(50), sum.intValue) + assert.InEpsilon(t, 50.0, sum.floatValue, 0.1) + assert.Equal(t, 1, cnt) +} + +// TODO: add proper tests for request diff --git a/vendor/k8s.io/kubernetes/pkg/controller/replicaset/doc.go b/vendor/k8s.io/kubernetes/pkg/controller/replicaset/doc.go new file mode 100644 index 000000000..9d42796d9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/replicaset/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 replicaset contains logic for watching and synchronizing +// ReplicaSets. +package replicaset diff --git a/vendor/k8s.io/kubernetes/pkg/controller/replicaset/options/options.go b/vendor/k8s.io/kubernetes/pkg/controller/replicaset/options/options.go new file mode 100644 index 000000000..91951a549 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/replicaset/options/options.go @@ -0,0 +1,35 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 options + +import ( + "github.com/spf13/pflag" +) + +type ReplicasetControllerOptions struct { + ConcurrentRSSyncs int +} + +func NewReplicasetControllerOptions() ReplicasetControllerOptions { + return ReplicasetControllerOptions{ + ConcurrentRSSyncs: 5, + } +} + +func (o *ReplicasetControllerOptions) AddFlags(fs *pflag.FlagSet) { + fs.IntVar(&o.ConcurrentRSSyncs, "concurrent-replicaset-syncs", o.ConcurrentRSSyncs, "The number of replicasets that are allowed to sync concurrently. Larger number = more reponsive replica management, but more CPU (and network) load") +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go b/vendor/k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go new file mode 100644 index 000000000..68cfc5ec9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/replicaset/replica_set.go @@ -0,0 +1,571 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// If you make changes to this file, you should also make the corresponding change in ReplicationController. + +package replicaset + +import ( + "fmt" + "reflect" + "sort" + "sync" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/util/workqueue" + "k8s.io/kubernetes/pkg/watch" +) + +const ( + // We'll attempt to recompute the required replicas of all ReplicaSets + // that have fulfilled their expectations at least this often. This recomputation + // happens based on contents in local pod storage. + FullControllerResyncPeriod = 30 * time.Second + + // Realistic value of the burstReplica field for the replica set manager based off + // performance requirements for kubernetes 1.0. + BurstReplicas = 500 + + // We must avoid counting pods until the pod store has synced. If it hasn't synced, to + // avoid a hot loop, we'll wait this long between checks. + PodStoreSyncedPollPeriod = 100 * time.Millisecond + + // The number of times we retry updating a ReplicaSet's status. + statusUpdateRetries = 1 +) + +// ReplicaSetController is responsible for synchronizing ReplicaSet objects stored +// in the system with actual running pods. +type ReplicaSetController struct { + kubeClient clientset.Interface + podControl controller.PodControlInterface + + // A ReplicaSet is temporarily suspended after creating/deleting these many replicas. + // It resumes normal action after observing the watch events for them. + burstReplicas int + // To allow injection of syncReplicaSet for testing. + syncHandler func(rsKey string) error + + // A TTLCache of pod creates/deletes each rc expects to see. + expectations *controller.UIDTrackingControllerExpectations + + // A store of ReplicaSets, populated by the rsController + rsStore cache.StoreToReplicaSetLister + // Watches changes to all ReplicaSets + rsController *framework.Controller + // A store of pods, populated by the podController + podStore cache.StoreToPodLister + // Watches changes to all pods + podController *framework.Controller + // podStoreSynced returns true if the pod store has been synced at least once. + // Added as a member to the struct to allow injection for testing. + podStoreSynced func() bool + + lookupCache *controller.MatchingCache + + // Controllers that need to be synced + queue *workqueue.Type +} + +// NewReplicaSetController creates a new ReplicaSetController. +func NewReplicaSetController(kubeClient clientset.Interface, resyncPeriod controller.ResyncPeriodFunc, burstReplicas int, lookupCacheSize int) *ReplicaSetController { + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(glog.Infof) + eventBroadcaster.StartRecordingToSink(&unversionedcore.EventSinkImpl{Interface: kubeClient.Core().Events("")}) + + rsc := &ReplicaSetController{ + kubeClient: kubeClient, + podControl: controller.RealPodControl{ + KubeClient: kubeClient, + Recorder: eventBroadcaster.NewRecorder(api.EventSource{Component: "replicaset-controller"}), + }, + burstReplicas: burstReplicas, + expectations: controller.NewUIDTrackingControllerExpectations(controller.NewControllerExpectations()), + queue: workqueue.New(), + } + + rsc.rsStore.Store, rsc.rsController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return rsc.kubeClient.Extensions().ReplicaSets(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return rsc.kubeClient.Extensions().ReplicaSets(api.NamespaceAll).Watch(options) + }, + }, + &extensions.ReplicaSet{}, + // TODO: Can we have much longer period here? + FullControllerResyncPeriod, + framework.ResourceEventHandlerFuncs{ + AddFunc: rsc.enqueueReplicaSet, + UpdateFunc: func(old, cur interface{}) { + oldRS := old.(*extensions.ReplicaSet) + curRS := cur.(*extensions.ReplicaSet) + + // We should invalidate the whole lookup cache if a RS's selector has been updated. + // + // Imagine that you have two RSs: + // * old RS1 + // * new RS2 + // You also have a pod that is attached to RS2 (because it doesn't match RS1 selector). + // Now imagine that you are changing RS1 selector so that it is now matching that pod, + // in such case we must invalidate the whole cache so that pod could be adopted by RS1 + // + // This makes the lookup cache less helpful, but selector update does not happen often, + // so it's not a big problem + if !reflect.DeepEqual(oldRS.Spec.Selector, curRS.Spec.Selector) { + rsc.lookupCache.InvalidateAll() + } + + // You might imagine that we only really need to enqueue the + // replica set when Spec changes, but it is safer to sync any + // time this function is triggered. That way a full informer + // resync can requeue any replica set that don't yet have pods + // but whose last attempts at creating a pod have failed (since + // we don't block on creation of pods) instead of those + // replica sets stalling indefinitely. Enqueueing every time + // does result in some spurious syncs (like when Status.Replica + // is updated and the watch notification from it retriggers + // this function), but in general extra resyncs shouldn't be + // that bad as ReplicaSets that haven't met expectations yet won't + // sync, and all the listing is done using local stores. + if oldRS.Status.Replicas != curRS.Status.Replicas { + glog.V(4).Infof("Observed updated replica count for ReplicaSet: %v, %d->%d", curRS.Name, oldRS.Status.Replicas, curRS.Status.Replicas) + } + rsc.enqueueReplicaSet(cur) + }, + // This will enter the sync loop and no-op, because the replica set has been deleted from the store. + // Note that deleting a replica set immediately after scaling it to 0 will not work. The recommended + // way of achieving this is by performing a `stop` operation on the replica set. + DeleteFunc: rsc.enqueueReplicaSet, + }, + ) + + rsc.podStore.Store, rsc.podController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return rsc.kubeClient.Core().Pods(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return rsc.kubeClient.Core().Pods(api.NamespaceAll).Watch(options) + }, + }, + &api.Pod{}, + resyncPeriod(), + framework.ResourceEventHandlerFuncs{ + AddFunc: rsc.addPod, + // This invokes the ReplicaSet for every pod change, eg: host assignment. Though this might seem like + // overkill the most frequent pod update is status, and the associated ReplicaSet will only list from + // local storage, so it should be ok. + UpdateFunc: rsc.updatePod, + DeleteFunc: rsc.deletePod, + }, + ) + + rsc.syncHandler = rsc.syncReplicaSet + rsc.podStoreSynced = rsc.podController.HasSynced + rsc.lookupCache = controller.NewMatchingCache(lookupCacheSize) + return rsc +} + +// SetEventRecorder replaces the event recorder used by the ReplicaSetController +// with the given recorder. Only used for testing. +func (rsc *ReplicaSetController) SetEventRecorder(recorder record.EventRecorder) { + // TODO: Hack. We can't cleanly shutdown the event recorder, so benchmarks + // need to pass in a fake. + rsc.podControl = controller.RealPodControl{KubeClient: rsc.kubeClient, Recorder: recorder} +} + +// Run begins watching and syncing. +func (rsc *ReplicaSetController) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + go rsc.rsController.Run(stopCh) + go rsc.podController.Run(stopCh) + for i := 0; i < workers; i++ { + go wait.Until(rsc.worker, time.Second, stopCh) + } + <-stopCh + glog.Infof("Shutting down ReplicaSet Controller") + rsc.queue.ShutDown() +} + +// getPodReplicaSet returns the replica set managing the given pod. +// TODO: Surface that we are ignoring multiple replica sets for a single pod. +func (rsc *ReplicaSetController) getPodReplicaSet(pod *api.Pod) *extensions.ReplicaSet { + // look up in the cache, if cached and the cache is valid, just return cached value + if obj, cached := rsc.lookupCache.GetMatchingObject(pod); cached { + rs, ok := obj.(*extensions.ReplicaSet) + if !ok { + // This should not happen + glog.Errorf("lookup cache does not retuen a ReplicaSet object") + return nil + } + if cached && rsc.isCacheValid(pod, rs) { + return rs + } + } + + // if not cached or cached value is invalid, search all the rs to find the matching one, and update cache + rss, err := rsc.rsStore.GetPodReplicaSets(pod) + if err != nil { + glog.V(4).Infof("No ReplicaSets found for pod %v, ReplicaSet controller will avoid syncing", pod.Name) + return nil + } + // In theory, overlapping ReplicaSets is user error. This sorting will not prevent + // oscillation of replicas in all cases, eg: + // rs1 (older rs): [(k1=v1)], replicas=1 rs2: [(k2=v2)], replicas=2 + // pod: [(k1:v1), (k2:v2)] will wake both rs1 and rs2, and we will sync rs1. + // pod: [(k2:v2)] will wake rs2 which creates a new replica. + if len(rss) > 1 { + // More than two items in this list indicates user error. If two replicasets + // overlap, sort by creation timestamp, subsort by name, then pick + // the first. + glog.Errorf("user error! more than one ReplicaSet is selecting pods with labels: %+v", pod.Labels) + sort.Sort(overlappingReplicaSets(rss)) + } + + // update lookup cache + rsc.lookupCache.Update(pod, &rss[0]) + + return &rss[0] +} + +// isCacheValid check if the cache is valid +func (rsc *ReplicaSetController) isCacheValid(pod *api.Pod, cachedRS *extensions.ReplicaSet) bool { + _, exists, err := rsc.rsStore.Get(cachedRS) + // rs has been deleted or updated, cache is invalid + if err != nil || !exists || !isReplicaSetMatch(pod, cachedRS) { + return false + } + return true +} + +// isReplicaSetMatch take a Pod and ReplicaSet, return whether the Pod and ReplicaSet are matching +// TODO(mqliang): This logic is a copy from GetPodReplicaSets(), remove the duplication +func isReplicaSetMatch(pod *api.Pod, rs *extensions.ReplicaSet) bool { + if rs.Namespace != pod.Namespace { + return false + } + selector, err := unversioned.LabelSelectorAsSelector(rs.Spec.Selector) + if err != nil { + err = fmt.Errorf("invalid selector: %v", err) + return false + } + + // If a ReplicaSet with a nil or empty selector creeps in, it should match nothing, not everything. + if selector.Empty() || !selector.Matches(labels.Set(pod.Labels)) { + return false + } + return true +} + +// When a pod is created, enqueue the replica set that manages it and update it's expectations. +func (rsc *ReplicaSetController) addPod(obj interface{}) { + pod := obj.(*api.Pod) + glog.V(4).Infof("Pod %s created: %+v.", pod.Name, pod) + + rs := rsc.getPodReplicaSet(pod) + if rs == nil { + return + } + rsKey, err := controller.KeyFunc(rs) + if err != nil { + glog.Errorf("Couldn't get key for replica set %#v: %v", rs, err) + return + } + if pod.DeletionTimestamp != nil { + // on a restart of the controller manager, it's possible a new pod shows up in a state that + // is already pending deletion. Prevent the pod from being a creation observation. + rsc.deletePod(pod) + return + } + rsc.expectations.CreationObserved(rsKey) + rsc.enqueueReplicaSet(rs) +} + +// When a pod is updated, figure out what replica set/s manage it and wake them +// up. If the labels of the pod have changed we need to awaken both the old +// and new replica set. old and cur must be *api.Pod types. +func (rsc *ReplicaSetController) updatePod(old, cur interface{}) { + if api.Semantic.DeepEqual(old, cur) { + // A periodic relist will send update events for all known pods. + return + } + curPod := cur.(*api.Pod) + oldPod := old.(*api.Pod) + glog.V(4).Infof("Pod %s updated %+v -> %+v.", curPod.Name, oldPod, curPod) + rs := rsc.getPodReplicaSet(curPod) + if rs == nil { + return + } + + if curPod.DeletionTimestamp != nil { + // when a pod is deleted gracefully it's deletion timestamp is first modified to reflect a grace period, + // and after such time has passed, the kubelet actually deletes it from the store. We receive an update + // for modification of the deletion timestamp and expect an rs to create more replicas asap, not wait + // until the kubelet actually deletes the pod. This is different from the Phase of a pod changing, because + // an rs never initiates a phase change, and so is never asleep waiting for the same. + rsc.deletePod(curPod) + return + } + + rsc.enqueueReplicaSet(rs) + if !reflect.DeepEqual(curPod.Labels, oldPod.Labels) { + // If the old and new ReplicaSet are the same, the first one that syncs + // will set expectations preventing any damage from the second. + if oldRS := rsc.getPodReplicaSet(oldPod); oldRS != nil { + rsc.enqueueReplicaSet(oldRS) + } + } +} + +// When a pod is deleted, enqueue the replica set that manages the pod and update its expectations. +// obj could be an *api.Pod, or a DeletionFinalStateUnknown marker item. +func (rsc *ReplicaSetController) deletePod(obj interface{}) { + pod, ok := obj.(*api.Pod) + + // When a delete is dropped, the relist will notice a pod in the store not + // in the list, leading to the insertion of a tombstone object which contains + // the deleted key/value. Note that this value might be stale. If the pod + // changed labels the new ReplicaSet will not be woken up till the periodic resync. + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + glog.Errorf("Couldn't get object from tombstone %+v", obj) + return + } + pod, ok = tombstone.Obj.(*api.Pod) + if !ok { + glog.Errorf("Tombstone contained object that is not a pod %+v", obj) + return + } + } + glog.V(4).Infof("Pod %s/%s deleted through %v, timestamp %+v: %+v.", pod.Namespace, pod.Name, utilruntime.GetCaller(), pod.DeletionTimestamp, pod) + if rs := rsc.getPodReplicaSet(pod); rs != nil { + rsKey, err := controller.KeyFunc(rs) + if err != nil { + glog.Errorf("Couldn't get key for ReplicaSet %#v: %v", rs, err) + return + } + rsc.expectations.DeletionObserved(rsKey, controller.PodKey(pod)) + rsc.enqueueReplicaSet(rs) + } +} + +// obj could be an *extensions.ReplicaSet, or a DeletionFinalStateUnknown marker item. +func (rsc *ReplicaSetController) enqueueReplicaSet(obj interface{}) { + key, err := controller.KeyFunc(obj) + if err != nil { + glog.Errorf("Couldn't get key for object %+v: %v", obj, err) + return + } + + // TODO: Handle overlapping replica sets better. Either disallow them at admission time or + // deterministically avoid syncing replica sets that fight over pods. Currently, we only + // ensure that the same replica set is synced for a given pod. When we periodically relist + // all replica sets there will still be some replica instability. One way to handle this is + // by querying the store for all replica sets that this replica set overlaps, as well as all + // replica sets that overlap this ReplicaSet, and sorting them. + rsc.queue.Add(key) +} + +// worker runs a worker thread that just dequeues items, processes them, and marks them done. +// It enforces that the syncHandler is never invoked concurrently with the same key. +func (rsc *ReplicaSetController) worker() { + for { + func() { + key, quit := rsc.queue.Get() + if quit { + return + } + defer rsc.queue.Done(key) + err := rsc.syncHandler(key.(string)) + if err != nil { + glog.Errorf("Error syncing ReplicaSet: %v", err) + } + }() + } +} + +// manageReplicas checks and updates replicas for the given ReplicaSet. +func (rsc *ReplicaSetController) manageReplicas(filteredPods []*api.Pod, rs *extensions.ReplicaSet) { + diff := len(filteredPods) - rs.Spec.Replicas + rsKey, err := controller.KeyFunc(rs) + if err != nil { + glog.Errorf("Couldn't get key for ReplicaSet %#v: %v", rs, err) + return + } + if diff < 0 { + diff *= -1 + if diff > rsc.burstReplicas { + diff = rsc.burstReplicas + } + // TODO: Track UIDs of creates just like deletes. The problem currently + // is we'd need to wait on the result of a create to record the pod's + // UID, which would require locking *across* the create, which will turn + // into a performance bottleneck. We should generate a UID for the pod + // beforehand and store it via ExpectCreations. + rsc.expectations.ExpectCreations(rsKey, diff) + wait := sync.WaitGroup{} + wait.Add(diff) + glog.V(2).Infof("Too few %q/%q replicas, need %d, creating %d", rs.Namespace, rs.Name, rs.Spec.Replicas, diff) + for i := 0; i < diff; i++ { + go func() { + defer wait.Done() + if err := rsc.podControl.CreatePods(rs.Namespace, &rs.Spec.Template, rs); err != nil { + // Decrement the expected number of creates because the informer won't observe this pod + glog.V(2).Infof("Failed creation, decrementing expectations for replica set %q/%q", rs.Namespace, rs.Name) + rsc.expectations.CreationObserved(rsKey) + utilruntime.HandleError(err) + } + }() + } + wait.Wait() + } else if diff > 0 { + if diff > rsc.burstReplicas { + diff = rsc.burstReplicas + } + glog.V(2).Infof("Too many %q/%q replicas, need %d, deleting %d", rs.Namespace, rs.Name, rs.Spec.Replicas, diff) + // No need to sort pods if we are about to delete all of them + if rs.Spec.Replicas != 0 { + // Sort the pods in the order such that not-ready < ready, unscheduled + // < scheduled, and pending < running. This ensures that we delete pods + // in the earlier stages whenever possible. + sort.Sort(controller.ActivePods(filteredPods)) + } + // Snapshot the UIDs (ns/name) of the pods we're expecting to see + // deleted, so we know to record their expectations exactly once either + // when we see it as an update of the deletion timestamp, or as a delete. + // Note that if the labels on a pod/rs change in a way that the pod gets + // orphaned, the rs will only wake up after the expectations have + // expired even if other pods are deleted. + deletedPodKeys := []string{} + for i := 0; i < diff; i++ { + deletedPodKeys = append(deletedPodKeys, controller.PodKey(filteredPods[i])) + } + rsc.expectations.ExpectDeletions(rsKey, deletedPodKeys) + wait := sync.WaitGroup{} + wait.Add(diff) + for i := 0; i < diff; i++ { + go func(ix int) { + defer wait.Done() + if err := rsc.podControl.DeletePod(rs.Namespace, filteredPods[ix].Name, rs); err != nil { + // Decrement the expected number of deletes because the informer won't observe this deletion + podKey := controller.PodKey(filteredPods[ix]) + glog.V(2).Infof("Failed to delete %v, decrementing expectations for controller %q/%q", podKey, rs.Namespace, rs.Name) + rsc.expectations.DeletionObserved(rsKey, podKey) + utilruntime.HandleError(err) + } + }(i) + } + wait.Wait() + } +} + +// syncReplicaSet will sync the ReplicaSet with the given key if it has had its expectations fulfilled, +// meaning it did not expect to see any more of its pods created or deleted. This function is not meant to be +// invoked concurrently with the same key. +func (rsc *ReplicaSetController) syncReplicaSet(key string) error { + startTime := time.Now() + defer func() { + glog.V(4).Infof("Finished syncing replica set %q (%v)", key, time.Now().Sub(startTime)) + }() + + if !rsc.podStoreSynced() { + // Sleep so we give the pod reflector goroutine a chance to run. + time.Sleep(PodStoreSyncedPollPeriod) + glog.Infof("Waiting for pods controller to sync, requeuing ReplicaSet %v", key) + rsc.queue.Add(key) + return nil + } + + obj, exists, err := rsc.rsStore.Store.GetByKey(key) + if !exists { + glog.Infof("ReplicaSet has been deleted %v", key) + rsc.expectations.DeleteExpectations(key) + return nil + } + if err != nil { + glog.Infof("Unable to retrieve ReplicaSet %v from store: %v", key, err) + rsc.queue.Add(key) + return err + } + rs := *obj.(*extensions.ReplicaSet) + + // Check the expectations of the ReplicaSet before counting active pods, otherwise a new pod can sneak + // in and update the expectations after we've retrieved active pods from the store. If a new pod enters + // the store after we've checked the expectation, the ReplicaSet sync is just deferred till the next + // relist. + rsKey, err := controller.KeyFunc(&rs) + if err != nil { + glog.Errorf("Couldn't get key for ReplicaSet %#v: %v", rs, err) + return err + } + rsNeedsSync := rsc.expectations.SatisfiedExpectations(rsKey) + selector, err := unversioned.LabelSelectorAsSelector(rs.Spec.Selector) + if err != nil { + glog.Errorf("Error converting pod selector to selector: %v", err) + return err + } + podList, err := rsc.podStore.Pods(rs.Namespace).List(selector) + if err != nil { + glog.Errorf("Error getting pods for ReplicaSet %q: %v", key, err) + rsc.queue.Add(key) + return err + } + + // TODO: Do this in a single pass, or use an index. + filteredPods := controller.FilterActivePods(podList.Items) + if rsNeedsSync { + rsc.manageReplicas(filteredPods, &rs) + } + + // Count the number of pods that have labels matching the labels of the pod + // template of the replicaSet, the matching pods may have more labels than + // are in the template. Because the label of podTemplateSpec is a superset + // of the selector of the replicaset, so the possible matching pods must be + // part of the filteredPods. + fullyLabeledReplicasCount := 0 + templateLabel := labels.Set(rs.Spec.Template.Labels).AsSelector() + for _, pod := range filteredPods { + if templateLabel.Matches(labels.Set(pod.Labels)) { + fullyLabeledReplicasCount++ + } + } + + // Always updates status as pods come up or die. + if err := updateReplicaCount(rsc.kubeClient.Extensions().ReplicaSets(rs.Namespace), rs, len(filteredPods), fullyLabeledReplicasCount); err != nil { + // Multiple things could lead to this update failing. Requeuing the replica set ensures + // we retry with some fairness. + glog.V(2).Infof("Failed to update replica count for controller %v/%v; requeuing; error: %v", rs.Namespace, rs.Name, err) + rsc.enqueueReplicaSet(&rs) + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/replicaset/replica_set_test.go b/vendor/k8s.io/kubernetes/pkg/controller/replicaset/replica_set_test.go new file mode 100644 index 000000000..0cb393886 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/replicaset/replica_set_test.go @@ -0,0 +1,1037 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// If you make changes to this file, you should also make the corresponding change in ReplicationController. + +package replicaset + +import ( + "fmt" + "math/rand" + "net/http/httptest" + "strings" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/client/unversioned/testclient" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/securitycontext" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/sets" + utiltesting "k8s.io/kubernetes/pkg/util/testing" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/watch" +) + +var alwaysReady = func() bool { return true } + +func getKey(rs *extensions.ReplicaSet, t *testing.T) string { + if key, err := controller.KeyFunc(rs); err != nil { + t.Errorf("Unexpected error getting key for ReplicaSet %v: %v", rs.Name, err) + return "" + } else { + return key + } +} + +func newReplicaSet(replicas int, selectorMap map[string]string) *extensions.ReplicaSet { + rs := &extensions.ReplicaSet{ + TypeMeta: unversioned.TypeMeta{APIVersion: testapi.Default.GroupVersion().String()}, + ObjectMeta: api.ObjectMeta{ + UID: util.NewUUID(), + Name: "foobar", + Namespace: api.NamespaceDefault, + ResourceVersion: "18", + }, + Spec: extensions.ReplicaSetSpec{ + Replicas: replicas, + Selector: &unversioned.LabelSelector{MatchLabels: selectorMap}, + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{ + "name": "foo", + "type": "production", + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Image: "foo/bar", + TerminationMessagePath: api.TerminationMessagePathDefault, + ImagePullPolicy: api.PullIfNotPresent, + SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), + }, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSDefault, + NodeSelector: map[string]string{ + "baz": "blah", + }, + }, + }, + }, + } + return rs +} + +// create count pods with the given phase for the given ReplicaSet (same selectors and namespace), and add them to the store. +func newPodList(store cache.Store, count int, status api.PodPhase, labelMap map[string]string, rs *extensions.ReplicaSet, name string) *api.PodList { + pods := []api.Pod{} + for i := 0; i < count; i++ { + newPod := api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: fmt.Sprintf("%s%d", name, i), + Labels: labelMap, + Namespace: rs.Namespace, + }, + Status: api.PodStatus{Phase: status}, + } + if store != nil { + store.Add(&newPod) + } + pods = append(pods, newPod) + } + return &api.PodList{ + Items: pods, + } +} + +func validateSyncReplicaSet(t *testing.T, fakePodControl *controller.FakePodControl, expectedCreates, expectedDeletes int) { + if len(fakePodControl.Templates) != expectedCreates { + t.Errorf("Unexpected number of creates. Expected %d, saw %d\n", expectedCreates, len(fakePodControl.Templates)) + } + if len(fakePodControl.DeletePodName) != expectedDeletes { + t.Errorf("Unexpected number of deletes. Expected %d, saw %d\n", expectedDeletes, len(fakePodControl.DeletePodName)) + } +} + +func replicaSetResourceName() string { + return "replicasets" +} + +type serverResponse struct { + statusCode int + obj interface{} +} + +func TestSyncReplicaSetDoesNothing(t *testing.T) { + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + fakePodControl := controller.FakePodControl{} + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + // 2 running pods, a controller with 2 replicas, sync is a no-op + labelMap := map[string]string{"foo": "bar"} + rsSpec := newReplicaSet(2, labelMap) + manager.rsStore.Store.Add(rsSpec) + newPodList(manager.podStore.Store, 2, api.PodRunning, labelMap, rsSpec, "pod") + + manager.podControl = &fakePodControl + manager.syncReplicaSet(getKey(rsSpec, t)) + validateSyncReplicaSet(t, &fakePodControl, 0, 0) +} + +func TestSyncReplicaSetDeletes(t *testing.T) { + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + fakePodControl := controller.FakePodControl{} + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + manager.podControl = &fakePodControl + + // 2 running pods and a controller with 1 replica, one pod delete expected + labelMap := map[string]string{"foo": "bar"} + rsSpec := newReplicaSet(1, labelMap) + manager.rsStore.Store.Add(rsSpec) + newPodList(manager.podStore.Store, 2, api.PodRunning, labelMap, rsSpec, "pod") + + manager.syncReplicaSet(getKey(rsSpec, t)) + validateSyncReplicaSet(t, &fakePodControl, 0, 1) +} + +func TestDeleteFinalStateUnknown(t *testing.T) { + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + fakePodControl := controller.FakePodControl{} + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + manager.podControl = &fakePodControl + + received := make(chan string) + manager.syncHandler = func(key string) error { + received <- key + return nil + } + + // The DeletedFinalStateUnknown object should cause the ReplicaSet manager to insert + // the controller matching the selectors of the deleted pod into the work queue. + labelMap := map[string]string{"foo": "bar"} + rsSpec := newReplicaSet(1, labelMap) + manager.rsStore.Store.Add(rsSpec) + pods := newPodList(nil, 1, api.PodRunning, labelMap, rsSpec, "pod") + manager.deletePod(cache.DeletedFinalStateUnknown{Key: "foo", Obj: &pods.Items[0]}) + + go manager.worker() + + expected := getKey(rsSpec, t) + select { + case key := <-received: + if key != expected { + t.Errorf("Unexpected sync all for ReplicaSet %v, expected %v", key, expected) + } + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Processing DeleteFinalStateUnknown took longer than expected") + } +} + +func TestSyncReplicaSetCreates(t *testing.T) { + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + // A controller with 2 replicas and no pods in the store, 2 creates expected + labelMap := map[string]string{"foo": "bar"} + rs := newReplicaSet(2, labelMap) + manager.rsStore.Store.Add(rs) + + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + manager.syncReplicaSet(getKey(rs, t)) + validateSyncReplicaSet(t, &fakePodControl, 2, 0) +} + +func TestStatusUpdatesWithoutReplicasChange(t *testing.T) { + // Setup a fake server to listen for requests, and run the ReplicaSet controller in steady state + fakeHandler := utiltesting.FakeHandler{ + StatusCode: 200, + ResponseBody: "", + } + testServer := httptest.NewServer(&fakeHandler) + defer testServer.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + // Steady state for the ReplicaSet, no Status.Replicas updates expected + activePods := 5 + labelMap := map[string]string{"foo": "bar"} + rs := newReplicaSet(activePods, labelMap) + manager.rsStore.Store.Add(rs) + rs.Status = extensions.ReplicaSetStatus{Replicas: activePods} + newPodList(manager.podStore.Store, activePods, api.PodRunning, labelMap, rs, "pod") + + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + manager.syncReplicaSet(getKey(rs, t)) + + validateSyncReplicaSet(t, &fakePodControl, 0, 0) + if fakeHandler.RequestReceived != nil { + t.Errorf("Unexpected update when pods and ReplicaSets are in a steady state") + } + + // This response body is just so we don't err out decoding the http response, all + // we care about is the request body sent below. + response := runtime.EncodeOrDie(testapi.Extensions.Codec(), &extensions.ReplicaSet{}) + fakeHandler.ResponseBody = response + + rs.Generation = rs.Generation + 1 + manager.syncReplicaSet(getKey(rs, t)) + + rs.Status.ObservedGeneration = rs.Generation + updatedRc := runtime.EncodeOrDie(testapi.Extensions.Codec(), rs) + fakeHandler.ValidateRequest(t, testapi.Extensions.ResourcePath(replicaSetResourceName(), rs.Namespace, rs.Name)+"/status", "PUT", &updatedRc) +} + +func TestControllerUpdateReplicas(t *testing.T) { + // This is a happy server just to record the PUT request we expect for status.Replicas + fakeHandler := utiltesting.FakeHandler{ + StatusCode: 200, + ResponseBody: "", + } + testServer := httptest.NewServer(&fakeHandler) + defer testServer.Close() + + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + // Insufficient number of pods in the system, and Status.Replicas is wrong; + // Status.Replica should update to match number of pods in system, 1 new pod should be created. + labelMap := map[string]string{"foo": "bar"} + extraLabelMap := map[string]string{"foo": "bar", "extraKey": "extraValue"} + rs := newReplicaSet(5, labelMap) + rs.Spec.Template.Labels = extraLabelMap + manager.rsStore.Store.Add(rs) + rs.Status = extensions.ReplicaSetStatus{Replicas: 2, FullyLabeledReplicas: 6, ObservedGeneration: 0} + rs.Generation = 1 + newPodList(manager.podStore.Store, 2, api.PodRunning, labelMap, rs, "pod") + newPodList(manager.podStore.Store, 2, api.PodRunning, extraLabelMap, rs, "podWithExtraLabel") + + // This response body is just so we don't err out decoding the http response + response := runtime.EncodeOrDie(testapi.Extensions.Codec(), &extensions.ReplicaSet{}) + fakeHandler.ResponseBody = response + + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + + manager.syncReplicaSet(getKey(rs, t)) + + // 1. Status.Replicas should go up from 2->4 even though we created 5-4=1 pod. + // 2. Status.FullyLabeledReplicas should equal to the number of pods that + // has the extra labels, i.e., 2. + // 3. Every update to the status should include the Generation of the spec. + rs.Status = extensions.ReplicaSetStatus{Replicas: 4, FullyLabeledReplicas: 2, ObservedGeneration: 1} + + decRc := runtime.EncodeOrDie(testapi.Extensions.Codec(), rs) + fakeHandler.ValidateRequest(t, testapi.Extensions.ResourcePath(replicaSetResourceName(), rs.Namespace, rs.Name)+"/status", "PUT", &decRc) + validateSyncReplicaSet(t, &fakePodControl, 1, 0) +} + +func TestSyncReplicaSetDormancy(t *testing.T) { + // Setup a test server so we can lie about the current state of pods + fakeHandler := utiltesting.FakeHandler{ + StatusCode: 200, + ResponseBody: "", + } + testServer := httptest.NewServer(&fakeHandler) + defer testServer.Close() + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + + fakePodControl := controller.FakePodControl{} + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + manager.podControl = &fakePodControl + + labelMap := map[string]string{"foo": "bar"} + rsSpec := newReplicaSet(2, labelMap) + manager.rsStore.Store.Add(rsSpec) + newPodList(manager.podStore.Store, 1, api.PodRunning, labelMap, rsSpec, "pod") + + // Creates a replica and sets expectations + rsSpec.Status.Replicas = 1 + manager.syncReplicaSet(getKey(rsSpec, t)) + validateSyncReplicaSet(t, &fakePodControl, 1, 0) + + // Expectations prevents replicas but not an update on status + rsSpec.Status.Replicas = 0 + fakePodControl.Clear() + manager.syncReplicaSet(getKey(rsSpec, t)) + validateSyncReplicaSet(t, &fakePodControl, 0, 0) + + // Get the key for the controller + rsKey, err := controller.KeyFunc(rsSpec) + if err != nil { + t.Errorf("Couldn't get key for object %+v: %v", rsSpec, err) + } + + // Lowering expectations should lead to a sync that creates a replica, however the + // fakePodControl error will prevent this, leaving expectations at 0, 0 + manager.expectations.CreationObserved(rsKey) + rsSpec.Status.Replicas = 1 + fakePodControl.Clear() + fakePodControl.Err = fmt.Errorf("Fake Error") + + manager.syncReplicaSet(getKey(rsSpec, t)) + validateSyncReplicaSet(t, &fakePodControl, 0, 0) + + // This replica should not need a Lowering of expectations, since the previous create failed + fakePodControl.Err = nil + manager.syncReplicaSet(getKey(rsSpec, t)) + validateSyncReplicaSet(t, &fakePodControl, 1, 0) + + // 1 PUT for the ReplicaSet status during dormancy window. + // Note that the pod creates go through pod control so they're not recorded. + fakeHandler.ValidateRequestCount(t, 1) +} + +func TestPodControllerLookup(t *testing.T) { + manager := NewReplicaSetController(clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}), controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + testCases := []struct { + inRSs []*extensions.ReplicaSet + pod *api.Pod + outRSName string + }{ + // pods without labels don't match any ReplicaSets + { + inRSs: []*extensions.ReplicaSet{ + {ObjectMeta: api.ObjectMeta{Name: "basic"}}}, + pod: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo1", Namespace: api.NamespaceAll}}, + outRSName: "", + }, + // Matching labels, not namespace + { + inRSs: []*extensions.ReplicaSet{ + { + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: extensions.ReplicaSetSpec{ + Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + }, + }, + }, + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo2", Namespace: "ns", Labels: map[string]string{"foo": "bar"}}}, + outRSName: "", + }, + // Matching ns and labels returns the key to the ReplicaSet, not the ReplicaSet name + { + inRSs: []*extensions.ReplicaSet{ + { + ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "ns"}, + Spec: extensions.ReplicaSetSpec{ + Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, + }, + }, + }, + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo3", Namespace: "ns", Labels: map[string]string{"foo": "bar"}}}, + outRSName: "bar", + }, + } + for _, c := range testCases { + for _, r := range c.inRSs { + manager.rsStore.Add(r) + } + if rs := manager.getPodReplicaSet(c.pod); rs != nil { + if c.outRSName != rs.Name { + t.Errorf("Got replica set %+v expected %+v", rs.Name, c.outRSName) + } + } else if c.outRSName != "" { + t.Errorf("Expected a replica set %v pod %v, found none", c.outRSName, c.pod.Name) + } + } +} + +type FakeWatcher struct { + w *watch.FakeWatcher + *fake.Clientset +} + +func TestWatchControllers(t *testing.T) { + fakeWatch := watch.NewFake() + client := &fake.Clientset{} + client.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + var testRSSpec extensions.ReplicaSet + received := make(chan string) + + // The update sent through the fakeWatcher should make its way into the workqueue, + // and eventually into the syncHandler. The handler validates the received controller + // and closes the received channel to indicate that the test can finish. + manager.syncHandler = func(key string) error { + + obj, exists, err := manager.rsStore.Store.GetByKey(key) + if !exists || err != nil { + t.Errorf("Expected to find replica set under key %v", key) + } + rsSpec := *obj.(*extensions.ReplicaSet) + if !api.Semantic.DeepDerivative(rsSpec, testRSSpec) { + t.Errorf("Expected %#v, but got %#v", testRSSpec, rsSpec) + } + close(received) + return nil + } + // Start only the ReplicaSet watcher and the workqueue, send a watch event, + // and make sure it hits the sync method. + stopCh := make(chan struct{}) + defer close(stopCh) + go manager.rsController.Run(stopCh) + go wait.Until(manager.worker, 10*time.Millisecond, stopCh) + + testRSSpec.Name = "foo" + fakeWatch.Add(&testRSSpec) + + select { + case <-received: + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Expected 1 call but got 0") + } +} + +func TestWatchPods(t *testing.T) { + fakeWatch := watch.NewFake() + client := &fake.Clientset{} + client.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + // Put one ReplicaSet and one pod into the controller's stores + labelMap := map[string]string{"foo": "bar"} + testRSSpec := newReplicaSet(1, labelMap) + manager.rsStore.Store.Add(testRSSpec) + received := make(chan string) + // The pod update sent through the fakeWatcher should figure out the managing ReplicaSet and + // send it into the syncHandler. + manager.syncHandler = func(key string) error { + + obj, exists, err := manager.rsStore.Store.GetByKey(key) + if !exists || err != nil { + t.Errorf("Expected to find replica set under key %v", key) + } + rsSpec := obj.(*extensions.ReplicaSet) + if !api.Semantic.DeepDerivative(rsSpec, testRSSpec) { + t.Errorf("\nExpected %#v,\nbut got %#v", testRSSpec, rsSpec) + } + close(received) + return nil + } + // Start only the pod watcher and the workqueue, send a watch event, + // and make sure it hits the sync method for the right ReplicaSet. + stopCh := make(chan struct{}) + defer close(stopCh) + go manager.podController.Run(stopCh) + go wait.Until(manager.worker, 10*time.Millisecond, stopCh) + + pods := newPodList(nil, 1, api.PodRunning, labelMap, testRSSpec, "pod") + testPod := pods.Items[0] + testPod.Status.Phase = api.PodFailed + fakeWatch.Add(&testPod) + + select { + case <-received: + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Expected 1 call but got 0") + } +} + +func TestUpdatePods(t *testing.T) { + manager := NewReplicaSetController(fake.NewSimpleClientset(), controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + received := make(chan string) + + manager.syncHandler = func(key string) error { + obj, exists, err := manager.rsStore.Store.GetByKey(key) + if !exists || err != nil { + t.Errorf("Expected to find replica set under key %v", key) + } + received <- obj.(*extensions.ReplicaSet).Name + return nil + } + + stopCh := make(chan struct{}) + defer close(stopCh) + go wait.Until(manager.worker, 10*time.Millisecond, stopCh) + + // Put 2 ReplicaSets and one pod into the controller's stores + labelMap1 := map[string]string{"foo": "bar"} + testRSSpec1 := newReplicaSet(1, labelMap1) + manager.rsStore.Store.Add(testRSSpec1) + testRSSpec2 := *testRSSpec1 + labelMap2 := map[string]string{"bar": "foo"} + testRSSpec2.Spec.Selector = &unversioned.LabelSelector{MatchLabels: labelMap2} + testRSSpec2.Name = "barfoo" + manager.rsStore.Store.Add(&testRSSpec2) + + // Put one pod in the podStore + pod1 := newPodList(manager.podStore.Store, 1, api.PodRunning, labelMap1, testRSSpec1, "pod").Items[0] + pod2 := pod1 + pod2.Labels = labelMap2 + + // Send an update of the same pod with modified labels, and confirm we get a sync request for + // both controllers + manager.updatePod(&pod1, &pod2) + + expected := sets.NewString(testRSSpec1.Name, testRSSpec2.Name) + for _, name := range expected.List() { + t.Logf("Expecting update for %+v", name) + select { + case got := <-received: + if !expected.Has(got) { + t.Errorf("Expected keys %#v got %v", expected, got) + } + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Expected update notifications for replica sets within 100ms each") + } + } +} + +func TestControllerUpdateRequeue(t *testing.T) { + // This server should force a requeue of the controller because it fails to update status.Replicas. + fakeHandler := utiltesting.FakeHandler{ + StatusCode: 500, + ResponseBody: "", + } + testServer := httptest.NewServer(&fakeHandler) + defer testServer.Close() + + client := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + labelMap := map[string]string{"foo": "bar"} + rs := newReplicaSet(1, labelMap) + manager.rsStore.Store.Add(rs) + rs.Status = extensions.ReplicaSetStatus{Replicas: 2} + newPodList(manager.podStore.Store, 1, api.PodRunning, labelMap, rs, "pod") + + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + + manager.syncReplicaSet(getKey(rs, t)) + + ch := make(chan interface{}) + go func() { + item, _ := manager.queue.Get() + ch <- item + }() + select { + case key := <-ch: + expectedKey := getKey(rs, t) + if key != expectedKey { + t.Errorf("Expected requeue of replica set with key %s got %s", expectedKey, key) + } + case <-time.After(wait.ForeverTestTimeout): + manager.queue.ShutDown() + t.Errorf("Expected to find a ReplicaSet in the queue, found none.") + } + // 1 Update and 1 GET, both of which fail + fakeHandler.ValidateRequestCount(t, 2) +} + +func TestControllerUpdateStatusWithFailure(t *testing.T) { + rs := newReplicaSet(1, map[string]string{"foo": "bar"}) + fakeClient := &fake.Clientset{} + fakeClient.AddReactor("get", "replicasets", func(action core.Action) (bool, runtime.Object, error) { return true, rs, nil }) + fakeClient.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) { + return true, &extensions.ReplicaSet{}, fmt.Errorf("Fake error") + }) + fakeRSClient := fakeClient.Extensions().ReplicaSets("default") + numReplicas := 10 + updateReplicaCount(fakeRSClient, *rs, numReplicas, 0) + updates, gets := 0, 0 + for _, a := range fakeClient.Actions() { + if a.GetResource() != "replicasets" { + t.Errorf("Unexpected action %+v", a) + continue + } + + switch action := a.(type) { + case testclient.GetAction: + gets++ + // Make sure the get is for the right ReplicaSet even though the update failed. + if action.GetName() != rs.Name { + t.Errorf("Expected get for ReplicaSet %v, got %+v instead", rs.Name, action.GetName()) + } + case testclient.UpdateAction: + updates++ + // Confirm that the update has the right status.Replicas even though the Get + // returned a ReplicaSet with replicas=1. + if c, ok := action.GetObject().(*extensions.ReplicaSet); !ok { + t.Errorf("Expected a ReplicaSet as the argument to update, got %T", c) + } else if c.Status.Replicas != numReplicas { + t.Errorf("Expected update for ReplicaSet to contain replicas %v, got %v instead", + numReplicas, c.Status.Replicas) + } + default: + t.Errorf("Unexpected action %+v", a) + break + } + } + if gets != 1 || updates != 2 { + t.Errorf("Expected 1 get and 2 updates, got %d gets %d updates", gets, updates) + } +} + +// TODO: This test is too hairy for a unittest. It should be moved to an E2E suite. +func doTestControllerBurstReplicas(t *testing.T, burstReplicas, numReplicas int) { + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + fakePodControl := controller.FakePodControl{} + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, burstReplicas, 0) + manager.podStoreSynced = alwaysReady + manager.podControl = &fakePodControl + + labelMap := map[string]string{"foo": "bar"} + rsSpec := newReplicaSet(numReplicas, labelMap) + manager.rsStore.Store.Add(rsSpec) + + expectedPods := 0 + pods := newPodList(nil, numReplicas, api.PodPending, labelMap, rsSpec, "pod") + + rsKey, err := controller.KeyFunc(rsSpec) + if err != nil { + t.Errorf("Couldn't get key for object %+v: %v", rsSpec, err) + } + + // Size up the controller, then size it down, and confirm the expected create/delete pattern + for _, replicas := range []int{numReplicas, 0} { + + rsSpec.Spec.Replicas = replicas + manager.rsStore.Store.Add(rsSpec) + + for i := 0; i < numReplicas; i += burstReplicas { + manager.syncReplicaSet(getKey(rsSpec, t)) + + // The store accrues active pods. It's also used by the ReplicaSet to determine how many + // replicas to create. + activePods := len(manager.podStore.Store.List()) + if replicas != 0 { + // This is the number of pods currently "in flight". They were created by the + // ReplicaSet controller above, which then puts the ReplicaSet to sleep till + // all of them have been observed. + expectedPods = replicas - activePods + if expectedPods > burstReplicas { + expectedPods = burstReplicas + } + // This validates the ReplicaSet manager sync actually created pods + validateSyncReplicaSet(t, &fakePodControl, expectedPods, 0) + + // This simulates the watch events for all but 1 of the expected pods. + // None of these should wake the controller because it has expectations==BurstReplicas. + for i := 0; i < expectedPods-1; i++ { + manager.podStore.Store.Add(&pods.Items[i]) + manager.addPod(&pods.Items[i]) + } + + podExp, exists, err := manager.expectations.GetExpectations(rsKey) + if !exists || err != nil { + t.Fatalf("Did not find expectations for rc.") + } + if add, _ := podExp.GetExpectations(); add != 1 { + t.Fatalf("Expectations are wrong %v", podExp) + } + } else { + expectedPods = (replicas - activePods) * -1 + if expectedPods > burstReplicas { + expectedPods = burstReplicas + } + validateSyncReplicaSet(t, &fakePodControl, 0, expectedPods) + + // To accurately simulate a watch we must delete the exact pods + // the rs is waiting for. + expectedDels := manager.expectations.GetUIDs(getKey(rsSpec, t)) + podsToDelete := []*api.Pod{} + for _, key := range expectedDels.List() { + nsName := strings.Split(key, "/") + podsToDelete = append(podsToDelete, &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: nsName[1], + Namespace: nsName[0], + Labels: rsSpec.Spec.Selector.MatchLabels, + }, + }) + } + // Don't delete all pods because we confirm that the last pod + // has exactly one expectation at the end, to verify that we + // don't double delete. + for i := range podsToDelete[1:] { + manager.podStore.Delete(podsToDelete[i]) + manager.deletePod(podsToDelete[i]) + } + podExp, exists, err := manager.expectations.GetExpectations(rsKey) + if !exists || err != nil { + t.Fatalf("Did not find expectations for ReplicaSet.") + } + if _, del := podExp.GetExpectations(); del != 1 { + t.Fatalf("Expectations are wrong %v", podExp) + } + } + + // Check that the ReplicaSet didn't take any action for all the above pods + fakePodControl.Clear() + manager.syncReplicaSet(getKey(rsSpec, t)) + validateSyncReplicaSet(t, &fakePodControl, 0, 0) + + // Create/Delete the last pod + // The last add pod will decrease the expectation of the ReplicaSet to 0, + // which will cause it to create/delete the remaining replicas up to burstReplicas. + if replicas != 0 { + manager.podStore.Store.Add(&pods.Items[expectedPods-1]) + manager.addPod(&pods.Items[expectedPods-1]) + } else { + expectedDel := manager.expectations.GetUIDs(getKey(rsSpec, t)) + if expectedDel.Len() != 1 { + t.Fatalf("Waiting on unexpected number of deletes.") + } + nsName := strings.Split(expectedDel.List()[0], "/") + lastPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: nsName[1], + Namespace: nsName[0], + Labels: rsSpec.Spec.Selector.MatchLabels, + }, + } + manager.podStore.Store.Delete(lastPod) + manager.deletePod(lastPod) + } + pods.Items = pods.Items[expectedPods:] + } + + // Confirm that we've created the right number of replicas + activePods := len(manager.podStore.Store.List()) + if activePods != rsSpec.Spec.Replicas { + t.Fatalf("Unexpected number of active pods, expected %d, got %d", rsSpec.Spec.Replicas, activePods) + } + // Replenish the pod list, since we cut it down sizing up + pods = newPodList(nil, replicas, api.PodRunning, labelMap, rsSpec, "pod") + } +} + +func TestControllerBurstReplicas(t *testing.T) { + doTestControllerBurstReplicas(t, 5, 30) + doTestControllerBurstReplicas(t, 5, 12) + doTestControllerBurstReplicas(t, 3, 2) +} + +type FakeRSExpectations struct { + *controller.ControllerExpectations + satisfied bool + expSatisfied func() +} + +func (fe FakeRSExpectations) SatisfiedExpectations(controllerKey string) bool { + fe.expSatisfied() + return fe.satisfied +} + +// TestRSSyncExpectations tests that a pod cannot sneak in between counting active pods +// and checking expectations. +func TestRSSyncExpectations(t *testing.T) { + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + fakePodControl := controller.FakePodControl{} + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, 2, 0) + manager.podStoreSynced = alwaysReady + manager.podControl = &fakePodControl + + labelMap := map[string]string{"foo": "bar"} + rsSpec := newReplicaSet(2, labelMap) + manager.rsStore.Store.Add(rsSpec) + pods := newPodList(nil, 2, api.PodPending, labelMap, rsSpec, "pod") + manager.podStore.Store.Add(&pods.Items[0]) + postExpectationsPod := pods.Items[1] + + manager.expectations = controller.NewUIDTrackingControllerExpectations(FakeRSExpectations{ + controller.NewControllerExpectations(), true, func() { + // If we check active pods before checking expectataions, the + // ReplicaSet will create a new replica because it doesn't see + // this pod, but has fulfilled its expectations. + manager.podStore.Store.Add(&postExpectationsPod) + }, + }) + manager.syncReplicaSet(getKey(rsSpec, t)) + validateSyncReplicaSet(t, &fakePodControl, 0, 0) +} + +func TestDeleteControllerAndExpectations(t *testing.T) { + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, 10, 0) + manager.podStoreSynced = alwaysReady + + rs := newReplicaSet(1, map[string]string{"foo": "bar"}) + manager.rsStore.Store.Add(rs) + + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + + // This should set expectations for the ReplicaSet + manager.syncReplicaSet(getKey(rs, t)) + validateSyncReplicaSet(t, &fakePodControl, 1, 0) + fakePodControl.Clear() + + // Get the ReplicaSet key + rsKey, err := controller.KeyFunc(rs) + if err != nil { + t.Errorf("Couldn't get key for object %+v: %v", rs, err) + } + + // This is to simulate a concurrent addPod, that has a handle on the expectations + // as the controller deletes it. + podExp, exists, err := manager.expectations.GetExpectations(rsKey) + if !exists || err != nil { + t.Errorf("No expectations found for ReplicaSet") + } + manager.rsStore.Delete(rs) + manager.syncReplicaSet(getKey(rs, t)) + + if _, exists, err = manager.expectations.GetExpectations(rsKey); exists { + t.Errorf("Found expectaions, expected none since the ReplicaSet has been deleted.") + } + + // This should have no effect, since we've deleted the ReplicaSet. + podExp.Add(-1, 0) + manager.podStore.Store.Replace(make([]interface{}, 0), "0") + manager.syncReplicaSet(getKey(rs, t)) + validateSyncReplicaSet(t, &fakePodControl, 0, 0) +} + +func TestRSManagerNotReady(t *testing.T) { + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + fakePodControl := controller.FakePodControl{} + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, 2, 0) + manager.podControl = &fakePodControl + manager.podStoreSynced = func() bool { return false } + + // Simulates the ReplicaSet reflector running before the pod reflector. We don't + // want to end up creating replicas in this case until the pod reflector + // has synced, so the ReplicaSet controller should just requeue the ReplicaSet. + rsSpec := newReplicaSet(1, map[string]string{"foo": "bar"}) + manager.rsStore.Store.Add(rsSpec) + + rsKey := getKey(rsSpec, t) + manager.syncReplicaSet(rsKey) + validateSyncReplicaSet(t, &fakePodControl, 0, 0) + queueRS, _ := manager.queue.Get() + if queueRS != rsKey { + t.Fatalf("Expected to find key %v in queue, found %v", rsKey, queueRS) + } + + manager.podStoreSynced = alwaysReady + manager.syncReplicaSet(rsKey) + validateSyncReplicaSet(t, &fakePodControl, 1, 0) +} + +// shuffle returns a new shuffled list of container controllers. +func shuffle(controllers []*extensions.ReplicaSet) []*extensions.ReplicaSet { + numControllers := len(controllers) + randIndexes := rand.Perm(numControllers) + shuffled := make([]*extensions.ReplicaSet, numControllers) + for i := 0; i < numControllers; i++ { + shuffled[i] = controllers[randIndexes[i]] + } + return shuffled +} + +func TestOverlappingRSs(t *testing.T) { + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + labelMap := map[string]string{"foo": "bar"} + + for i := 0; i < 5; i++ { + manager := NewReplicaSetController(client, controller.NoResyncPeriodFunc, 10, 0) + manager.podStoreSynced = alwaysReady + + // Create 10 ReplicaSets, shuffled them randomly and insert them into the ReplicaSet controller's store + var controllers []*extensions.ReplicaSet + for j := 1; j < 10; j++ { + rsSpec := newReplicaSet(1, labelMap) + rsSpec.CreationTimestamp = unversioned.Date(2014, time.December, j, 0, 0, 0, 0, time.Local) + rsSpec.Name = string(util.NewUUID()) + controllers = append(controllers, rsSpec) + } + shuffledControllers := shuffle(controllers) + for j := range shuffledControllers { + manager.rsStore.Store.Add(shuffledControllers[j]) + } + // Add a pod and make sure only the oldest ReplicaSet is synced + pods := newPodList(nil, 1, api.PodPending, labelMap, controllers[0], "pod") + rsKey := getKey(controllers[0], t) + + manager.addPod(&pods.Items[0]) + queueRS, _ := manager.queue.Get() + if queueRS != rsKey { + t.Fatalf("Expected to find key %v in queue, found %v", rsKey, queueRS) + } + } +} + +func TestDeletionTimestamp(t *testing.T) { + c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + labelMap := map[string]string{"foo": "bar"} + manager := NewReplicaSetController(c, controller.NoResyncPeriodFunc, 10, 0) + manager.podStoreSynced = alwaysReady + + rs := newReplicaSet(1, labelMap) + manager.rsStore.Store.Add(rs) + rsKey, err := controller.KeyFunc(rs) + if err != nil { + t.Errorf("Couldn't get key for object %+v: %v", rs, err) + } + pod := newPodList(nil, 1, api.PodPending, labelMap, rs, "pod").Items[0] + pod.DeletionTimestamp = &unversioned.Time{Time: time.Now()} + manager.expectations.ExpectDeletions(rsKey, []string{controller.PodKey(&pod)}) + + // A pod added with a deletion timestamp should decrement deletions, not creations. + manager.addPod(&pod) + + queueRC, _ := manager.queue.Get() + if queueRC != rsKey { + t.Fatalf("Expected to find key %v in queue, found %v", rsKey, queueRC) + } + manager.queue.Done(rsKey) + + podExp, exists, err := manager.expectations.GetExpectations(rsKey) + if !exists || err != nil || !podExp.Fulfilled() { + t.Fatalf("Wrong expectations %+v", podExp) + } + + // An update from no deletion timestamp to having one should be treated + // as a deletion. + oldPod := newPodList(nil, 1, api.PodPending, labelMap, rs, "pod").Items[0] + manager.expectations.ExpectDeletions(rsKey, []string{controller.PodKey(&pod)}) + manager.updatePod(&oldPod, &pod) + + queueRC, _ = manager.queue.Get() + if queueRC != rsKey { + t.Fatalf("Expected to find key %v in queue, found %v", rsKey, queueRC) + } + manager.queue.Done(rsKey) + + podExp, exists, err = manager.expectations.GetExpectations(rsKey) + if !exists || err != nil || !podExp.Fulfilled() { + t.Fatalf("Wrong expectations %+v", podExp) + } + + // An update to the pod (including an update to the deletion timestamp) + // should not be counted as a second delete. + secondPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Namespace: pod.Namespace, + Name: "secondPod", + Labels: pod.Labels, + }, + } + manager.expectations.ExpectDeletions(rsKey, []string{controller.PodKey(secondPod)}) + oldPod.DeletionTimestamp = &unversioned.Time{Time: time.Now()} + manager.updatePod(&oldPod, &pod) + + podExp, exists, err = manager.expectations.GetExpectations(rsKey) + if !exists || err != nil || podExp.Fulfilled() { + t.Fatalf("Wrong expectations %+v", podExp) + } + + // A pod with a non-nil deletion timestamp should also be ignored by the + // delete handler, because it's already been counted in the update. + manager.deletePod(&pod) + podExp, exists, err = manager.expectations.GetExpectations(rsKey) + if !exists || err != nil || podExp.Fulfilled() { + t.Fatalf("Wrong expectations %+v", podExp) + } + + // Deleting the second pod should clear expectations. + manager.deletePod(secondPod) + + queueRC, _ = manager.queue.Get() + if queueRC != rsKey { + t.Fatalf("Expected to find key %v in queue, found %v", rsKey, queueRC) + } + manager.queue.Done(rsKey) + + podExp, exists, err = manager.expectations.GetExpectations(rsKey) + if !exists || err != nil || !podExp.Fulfilled() { + t.Fatalf("Wrong expectations %+v", podExp) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/replicaset/replica_set_utils.go b/vendor/k8s.io/kubernetes/pkg/controller/replicaset/replica_set_utils.go new file mode 100644 index 000000000..382f2aee4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/replicaset/replica_set_utils.go @@ -0,0 +1,77 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// If you make changes to this file, you should also make the corresponding change in ReplicationController. + +package replicaset + +import ( + "fmt" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/apis/extensions" + client "k8s.io/kubernetes/pkg/client/unversioned" +) + +// updateReplicaCount attempts to update the Status.Replicas of the given ReplicaSet, with a single GET/PUT retry. +func updateReplicaCount(rsClient client.ReplicaSetInterface, rs extensions.ReplicaSet, numReplicas, numFullyLabeledReplicas int) (updateErr error) { + // This is the steady state. It happens when the ReplicaSet doesn't have any expectations, since + // we do a periodic relist every 30s. If the generations differ but the replicas are + // the same, a caller might've resized to the same replica count. + if rs.Status.Replicas == numReplicas && + rs.Status.FullyLabeledReplicas == numFullyLabeledReplicas && + rs.Generation == rs.Status.ObservedGeneration { + return nil + } + // Save the generation number we acted on, otherwise we might wrongfully indicate + // that we've seen a spec update when we retry. + // TODO: This can clobber an update if we allow multiple agents to write to the + // same status. + generation := rs.Generation + + var getErr error + for i, rs := 0, &rs; ; i++ { + glog.V(4).Infof(fmt.Sprintf("Updating replica count for ReplicaSet: %s/%s, ", rs.Namespace, rs.Name) + + fmt.Sprintf("replicas %d->%d (need %d), ", rs.Status.Replicas, numReplicas, rs.Spec.Replicas) + + fmt.Sprintf("fullyLabeledReplicas %d->%d, ", rs.Status.FullyLabeledReplicas, numFullyLabeledReplicas) + + fmt.Sprintf("sequence No: %v->%v", rs.Status.ObservedGeneration, generation)) + + rs.Status = extensions.ReplicaSetStatus{Replicas: numReplicas, FullyLabeledReplicas: numFullyLabeledReplicas, ObservedGeneration: generation} + _, updateErr = rsClient.UpdateStatus(rs) + if updateErr == nil || i >= statusUpdateRetries { + return updateErr + } + // Update the ReplicaSet with the latest resource version for the next poll + if rs, getErr = rsClient.Get(rs.Name); getErr != nil { + // If the GET fails we can't trust status.Replicas anymore. This error + // is bound to be more interesting than the update failure. + return getErr + } + } +} + +// overlappingReplicaSets sorts a list of ReplicaSets by creation timestamp, using their names as a tie breaker. +type overlappingReplicaSets []extensions.ReplicaSet + +func (o overlappingReplicaSets) Len() int { return len(o) } +func (o overlappingReplicaSets) Swap(i, j int) { o[i], o[j] = o[j], o[i] } + +func (o overlappingReplicaSets) Less(i, j int) bool { + if o[i].CreationTimestamp.Equal(o[j].CreationTimestamp) { + return o[i].Name < o[j].Name + } + return o[i].CreationTimestamp.Before(o[j].CreationTimestamp) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/replication/doc.go b/vendor/k8s.io/kubernetes/pkg/controller/replication/doc.go new file mode 100644 index 000000000..b60e1d99c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/replication/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 replication contains logic for watching and synchronizing +// replication controllers. +package replication diff --git a/vendor/k8s.io/kubernetes/pkg/controller/replication/replication_controller.go b/vendor/k8s.io/kubernetes/pkg/controller/replication/replication_controller.go new file mode 100644 index 000000000..92c4145a0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/replication/replication_controller.go @@ -0,0 +1,564 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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. +*/ + +// If you make changes to this file, you should also make the corresponding change in ReplicaSet. + +package replication + +import ( + "reflect" + "sort" + "sync" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/util/workqueue" + "k8s.io/kubernetes/pkg/watch" +) + +const ( + // We'll attempt to recompute the required replicas of all replication controllers + // that have fulfilled their expectations at least this often. This recomputation + // happens based on contents in local pod storage. + FullControllerResyncPeriod = 30 * time.Second + + // Realistic value of the burstReplica field for the replication manager based off + // performance requirements for kubernetes 1.0. + BurstReplicas = 500 + + // We must avoid counting pods until the pod store has synced. If it hasn't synced, to + // avoid a hot loop, we'll wait this long between checks. + PodStoreSyncedPollPeriod = 100 * time.Millisecond + + // The number of times we retry updating a replication controller's status. + statusUpdateRetries = 1 +) + +// ReplicationManager is responsible for synchronizing ReplicationController objects stored +// in the system with actual running pods. +// TODO: this really should be called ReplicationController. The only reason why it's a Manager +// is to distinguish this type from API object "ReplicationController". We should fix this. +type ReplicationManager struct { + kubeClient clientset.Interface + podControl controller.PodControlInterface + + // An rc is temporarily suspended after creating/deleting these many replicas. + // It resumes normal action after observing the watch events for them. + burstReplicas int + // To allow injection of syncReplicationController for testing. + syncHandler func(rcKey string) error + + // A TTLCache of pod creates/deletes each rc expects to see. + expectations *controller.UIDTrackingControllerExpectations + + // A store of replication controllers, populated by the rcController + rcStore cache.StoreToReplicationControllerLister + // Watches changes to all replication controllers + rcController *framework.Controller + // A store of pods, populated by the podController + podStore cache.StoreToPodLister + // Watches changes to all pods + podController *framework.Controller + // podStoreSynced returns true if the pod store has been synced at least once. + // Added as a member to the struct to allow injection for testing. + podStoreSynced func() bool + + lookupCache *controller.MatchingCache + + // Controllers that need to be synced + queue *workqueue.Type +} + +// NewReplicationManager creates a new ReplicationManager. +func NewReplicationManager(kubeClient clientset.Interface, resyncPeriod controller.ResyncPeriodFunc, burstReplicas int, lookupCacheSize int) *ReplicationManager { + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(glog.Infof) + eventBroadcaster.StartRecordingToSink(&unversionedcore.EventSinkImpl{Interface: kubeClient.Core().Events("")}) + + rm := &ReplicationManager{ + kubeClient: kubeClient, + podControl: controller.RealPodControl{ + KubeClient: kubeClient, + Recorder: eventBroadcaster.NewRecorder(api.EventSource{Component: "replication-controller"}), + }, + burstReplicas: burstReplicas, + expectations: controller.NewUIDTrackingControllerExpectations(controller.NewControllerExpectations()), + queue: workqueue.New(), + } + + rm.rcStore.Store, rm.rcController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return rm.kubeClient.Core().ReplicationControllers(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return rm.kubeClient.Core().ReplicationControllers(api.NamespaceAll).Watch(options) + }, + }, + &api.ReplicationController{}, + // TODO: Can we have much longer period here? + FullControllerResyncPeriod, + framework.ResourceEventHandlerFuncs{ + AddFunc: rm.enqueueController, + UpdateFunc: func(old, cur interface{}) { + oldRC := old.(*api.ReplicationController) + curRC := cur.(*api.ReplicationController) + + // We should invalidate the whole lookup cache if a RC's selector has been updated. + // + // Imagine that you have two RCs: + // * old RC1 + // * new RC2 + // You also have a pod that is attached to RC2 (because it doesn't match RC1 selector). + // Now imagine that you are changing RC1 selector so that it is now matching that pod, + // in such case, we must invalidate the whole cache so that pod could be adopted by RC1 + // + // This makes the lookup cache less helpful, but selector update does not happen often, + // so it's not a big problem + if !reflect.DeepEqual(oldRC.Spec.Selector, curRC.Spec.Selector) { + rm.lookupCache.InvalidateAll() + } + + // You might imagine that we only really need to enqueue the + // controller when Spec changes, but it is safer to sync any + // time this function is triggered. That way a full informer + // resync can requeue any controllers that don't yet have pods + // but whose last attempts at creating a pod have failed (since + // we don't block on creation of pods) instead of those + // controllers stalling indefinitely. Enqueueing every time + // does result in some spurious syncs (like when Status.Replica + // is updated and the watch notification from it retriggers + // this function), but in general extra resyncs shouldn't be + // that bad as rcs that haven't met expectations yet won't + // sync, and all the listing is done using local stores. + if oldRC.Status.Replicas != curRC.Status.Replicas { + glog.V(4).Infof("Observed updated replica count for rc: %v, %d->%d", curRC.Name, oldRC.Status.Replicas, curRC.Status.Replicas) + } + rm.enqueueController(cur) + }, + // This will enter the sync loop and no-op, because the controller has been deleted from the store. + // Note that deleting a controller immediately after scaling it to 0 will not work. The recommended + // way of achieving this is by performing a `stop` operation on the controller. + DeleteFunc: rm.enqueueController, + }, + ) + + rm.podStore.Store, rm.podController = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return rm.kubeClient.Core().Pods(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return rm.kubeClient.Core().Pods(api.NamespaceAll).Watch(options) + }, + }, + &api.Pod{}, + resyncPeriod(), + framework.ResourceEventHandlerFuncs{ + AddFunc: rm.addPod, + // This invokes the rc for every pod change, eg: host assignment. Though this might seem like overkill + // the most frequent pod update is status, and the associated rc will only list from local storage, so + // it should be ok. + UpdateFunc: rm.updatePod, + DeleteFunc: rm.deletePod, + }, + ) + + rm.syncHandler = rm.syncReplicationController + rm.podStoreSynced = rm.podController.HasSynced + rm.lookupCache = controller.NewMatchingCache(lookupCacheSize) + return rm +} + +// SetEventRecorder replaces the event recorder used by the replication manager +// with the given recorder. Only used for testing. +func (rm *ReplicationManager) SetEventRecorder(recorder record.EventRecorder) { + // TODO: Hack. We can't cleanly shutdown the event recorder, so benchmarks + // need to pass in a fake. + rm.podControl = controller.RealPodControl{KubeClient: rm.kubeClient, Recorder: recorder} +} + +// Run begins watching and syncing. +func (rm *ReplicationManager) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + glog.Infof("Starting RC Manager") + go rm.rcController.Run(stopCh) + go rm.podController.Run(stopCh) + for i := 0; i < workers; i++ { + go wait.Until(rm.worker, time.Second, stopCh) + } + <-stopCh + glog.Infof("Shutting down RC Manager") + rm.queue.ShutDown() +} + +// getPodController returns the controller managing the given pod. +// TODO: Surface that we are ignoring multiple controllers for a single pod. +func (rm *ReplicationManager) getPodController(pod *api.Pod) *api.ReplicationController { + // look up in the cache, if cached and the cache is valid, just return cached value + if obj, cached := rm.lookupCache.GetMatchingObject(pod); cached { + controller, ok := obj.(*api.ReplicationController) + if !ok { + // This should not happen + glog.Errorf("lookup cache does not retuen a ReplicationController object") + return nil + } + if cached && rm.isCacheValid(pod, controller) { + return controller + } + } + + // if not cached or cached value is invalid, search all the rc to find the matching one, and update cache + controllers, err := rm.rcStore.GetPodControllers(pod) + if err != nil { + glog.V(4).Infof("No controllers found for pod %v, replication manager will avoid syncing", pod.Name) + return nil + } + // In theory, overlapping controllers is user error. This sorting will not prevent + // oscillation of replicas in all cases, eg: + // rc1 (older rc): [(k1=v1)], replicas=1 rc2: [(k2=v2)], replicas=2 + // pod: [(k1:v1), (k2:v2)] will wake both rc1 and rc2, and we will sync rc1. + // pod: [(k2:v2)] will wake rc2 which creates a new replica. + if len(controllers) > 1 { + // More than two items in this list indicates user error. If two replication-controller + // overlap, sort by creation timestamp, subsort by name, then pick + // the first. + glog.Errorf("user error! more than one replication controller is selecting pods with labels: %+v", pod.Labels) + sort.Sort(OverlappingControllers(controllers)) + } + + // update lookup cache + rm.lookupCache.Update(pod, &controllers[0]) + + return &controllers[0] +} + +// isCacheValid check if the cache is valid +func (rm *ReplicationManager) isCacheValid(pod *api.Pod, cachedRC *api.ReplicationController) bool { + _, exists, err := rm.rcStore.Get(cachedRC) + // rc has been deleted or updated, cache is invalid + if err != nil || !exists || !isControllerMatch(pod, cachedRC) { + return false + } + return true +} + +// isControllerMatch take a Pod and ReplicationController, return whether the Pod and ReplicationController are matching +// TODO(mqliang): This logic is a copy from GetPodControllers(), remove the duplication +func isControllerMatch(pod *api.Pod, rc *api.ReplicationController) bool { + if rc.Namespace != pod.Namespace { + return false + } + labelSet := labels.Set(rc.Spec.Selector) + selector := labels.Set(rc.Spec.Selector).AsSelector() + + // If an rc with a nil or empty selector creeps in, it should match nothing, not everything. + if labelSet.AsSelector().Empty() || !selector.Matches(labels.Set(pod.Labels)) { + return false + } + return true +} + +// When a pod is created, enqueue the controller that manages it and update it's expectations. +func (rm *ReplicationManager) addPod(obj interface{}) { + pod := obj.(*api.Pod) + + rc := rm.getPodController(pod) + if rc == nil { + return + } + rcKey, err := controller.KeyFunc(rc) + if err != nil { + glog.Errorf("Couldn't get key for replication controller %#v: %v", rc, err) + return + } + + if pod.DeletionTimestamp != nil { + // on a restart of the controller manager, it's possible a new pod shows up in a state that + // is already pending deletion. Prevent the pod from being a creation observation. + rm.deletePod(pod) + return + } + rm.expectations.CreationObserved(rcKey) + rm.enqueueController(rc) +} + +// When a pod is updated, figure out what controller/s manage it and wake them +// up. If the labels of the pod have changed we need to awaken both the old +// and new controller. old and cur must be *api.Pod types. +func (rm *ReplicationManager) updatePod(old, cur interface{}) { + if api.Semantic.DeepEqual(old, cur) { + // A periodic relist will send update events for all known pods. + return + } + curPod := cur.(*api.Pod) + rc := rm.getPodController(curPod) + if rc == nil { + return + } + oldPod := old.(*api.Pod) + + if curPod.DeletionTimestamp != nil { + // when a pod is deleted gracefully it's deletion timestamp is first modified to reflect a grace period, + // and after such time has passed, the kubelet actually deletes it from the store. We receive an update + // for modification of the deletion timestamp and expect an rc to create more replicas asap, not wait + // until the kubelet actually deletes the pod. This is different from the Phase of a pod changing, because + // an rc never initiates a phase change, and so is never asleep waiting for the same. + rm.deletePod(curPod) + return + } + rm.enqueueController(rc) + // Only need to get the old controller if the labels changed. + if !reflect.DeepEqual(curPod.Labels, oldPod.Labels) { + // If the old and new rc are the same, the first one that syncs + // will set expectations preventing any damage from the second. + if oldRC := rm.getPodController(oldPod); oldRC != nil { + rm.enqueueController(oldRC) + } + } +} + +// When a pod is deleted, enqueue the controller that manages the pod and update its expectations. +// obj could be an *api.Pod, or a DeletionFinalStateUnknown marker item. +func (rm *ReplicationManager) deletePod(obj interface{}) { + pod, ok := obj.(*api.Pod) + + // When a delete is dropped, the relist will notice a pod in the store not + // in the list, leading to the insertion of a tombstone object which contains + // the deleted key/value. Note that this value might be stale. If the pod + // changed labels the new rc will not be woken up till the periodic resync. + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + glog.Errorf("Couldn't get object from tombstone %+v", obj) + return + } + pod, ok = tombstone.Obj.(*api.Pod) + if !ok { + glog.Errorf("Tombstone contained object that is not a pod %+v", obj) + return + } + } + glog.V(4).Infof("Pod %s/%s deleted through %v, timestamp %+v, labels %+v.", pod.Namespace, pod.Name, utilruntime.GetCaller(), pod.DeletionTimestamp, pod.Labels) + if rc := rm.getPodController(pod); rc != nil { + rcKey, err := controller.KeyFunc(rc) + if err != nil { + glog.Errorf("Couldn't get key for replication controller %#v: %v", rc, err) + return + } + rm.expectations.DeletionObserved(rcKey, controller.PodKey(pod)) + rm.enqueueController(rc) + } +} + +// obj could be an *api.ReplicationController, or a DeletionFinalStateUnknown marker item. +func (rm *ReplicationManager) enqueueController(obj interface{}) { + key, err := controller.KeyFunc(obj) + if err != nil { + glog.Errorf("Couldn't get key for object %+v: %v", obj, err) + return + } + + // TODO: Handle overlapping controllers better. Either disallow them at admission time or + // deterministically avoid syncing controllers that fight over pods. Currently, we only + // ensure that the same controller is synced for a given pod. When we periodically relist + // all controllers there will still be some replica instability. One way to handle this is + // by querying the store for all controllers that this rc overlaps, as well as all + // controllers that overlap this rc, and sorting them. + rm.queue.Add(key) +} + +// worker runs a worker thread that just dequeues items, processes them, and marks them done. +// It enforces that the syncHandler is never invoked concurrently with the same key. +func (rm *ReplicationManager) worker() { + for { + func() { + key, quit := rm.queue.Get() + if quit { + return + } + defer rm.queue.Done(key) + err := rm.syncHandler(key.(string)) + if err != nil { + glog.Errorf("Error syncing replication controller: %v", err) + } + }() + } +} + +// manageReplicas checks and updates replicas for the given replication controller. +func (rm *ReplicationManager) manageReplicas(filteredPods []*api.Pod, rc *api.ReplicationController) { + diff := len(filteredPods) - rc.Spec.Replicas + rcKey, err := controller.KeyFunc(rc) + if err != nil { + glog.Errorf("Couldn't get key for replication controller %#v: %v", rc, err) + return + } + if diff < 0 { + diff *= -1 + if diff > rm.burstReplicas { + diff = rm.burstReplicas + } + // TODO: Track UIDs of creates just like deletes. The problem currently + // is we'd need to wait on the result of a create to record the pod's + // UID, which would require locking *across* the create, which will turn + // into a performance bottleneck. We should generate a UID for the pod + // beforehand and store it via ExpectCreations. + rm.expectations.ExpectCreations(rcKey, diff) + wait := sync.WaitGroup{} + wait.Add(diff) + glog.V(2).Infof("Too few %q/%q replicas, need %d, creating %d", rc.Namespace, rc.Name, rc.Spec.Replicas, diff) + for i := 0; i < diff; i++ { + go func() { + defer wait.Done() + if err := rm.podControl.CreatePods(rc.Namespace, rc.Spec.Template, rc); err != nil { + // Decrement the expected number of creates because the informer won't observe this pod + glog.V(2).Infof("Failed creation, decrementing expectations for controller %q/%q", rc.Namespace, rc.Name) + rm.expectations.CreationObserved(rcKey) + utilruntime.HandleError(err) + } + }() + } + wait.Wait() + } else if diff > 0 { + if diff > rm.burstReplicas { + diff = rm.burstReplicas + } + glog.V(2).Infof("Too many %q/%q replicas, need %d, deleting %d", rc.Namespace, rc.Name, rc.Spec.Replicas, diff) + // No need to sort pods if we are about to delete all of them + if rc.Spec.Replicas != 0 { + // Sort the pods in the order such that not-ready < ready, unscheduled + // < scheduled, and pending < running. This ensures that we delete pods + // in the earlier stages whenever possible. + sort.Sort(controller.ActivePods(filteredPods)) + } + // Snapshot the UIDs (ns/name) of the pods we're expecting to see + // deleted, so we know to record their expectations exactly once either + // when we see it as an update of the deletion timestamp, or as a delete. + // Note that if the labels on a pod/rc change in a way that the pod gets + // orphaned, the rs will only wake up after the expectations have + // expired even if other pods are deleted. + deletedPodKeys := []string{} + for i := 0; i < diff; i++ { + deletedPodKeys = append(deletedPodKeys, controller.PodKey(filteredPods[i])) + } + // We use pod namespace/name as a UID to wait for deletions, so if the + // labels on a pod/rc change in a way that the pod gets orphaned, the + // rc will only wake up after the expectation has expired. + rm.expectations.ExpectDeletions(rcKey, deletedPodKeys) + wait := sync.WaitGroup{} + wait.Add(diff) + for i := 0; i < diff; i++ { + go func(ix int) { + defer wait.Done() + if err := rm.podControl.DeletePod(rc.Namespace, filteredPods[ix].Name, rc); err != nil { + // Decrement the expected number of deletes because the informer won't observe this deletion + podKey := controller.PodKey(filteredPods[ix]) + glog.V(2).Infof("Failed to delete %v, decrementing expectations for controller %q/%q", podKey, rc.Namespace, rc.Name) + rm.expectations.DeletionObserved(rcKey, podKey) + utilruntime.HandleError(err) + } + }(i) + } + wait.Wait() + } +} + +// syncReplicationController will sync the rc with the given key if it has had its expectations fulfilled, meaning +// it did not expect to see any more of its pods created or deleted. This function is not meant to be invoked +// concurrently with the same key. +func (rm *ReplicationManager) syncReplicationController(key string) error { + startTime := time.Now() + defer func() { + glog.V(4).Infof("Finished syncing controller %q (%v)", key, time.Now().Sub(startTime)) + }() + + if !rm.podStoreSynced() { + // Sleep so we give the pod reflector goroutine a chance to run. + time.Sleep(PodStoreSyncedPollPeriod) + glog.Infof("Waiting for pods controller to sync, requeuing rc %v", key) + rm.queue.Add(key) + return nil + } + + obj, exists, err := rm.rcStore.Store.GetByKey(key) + if !exists { + glog.Infof("Replication Controller has been deleted %v", key) + rm.expectations.DeleteExpectations(key) + return nil + } + if err != nil { + glog.Infof("Unable to retrieve rc %v from store: %v", key, err) + rm.queue.Add(key) + return err + } + rc := *obj.(*api.ReplicationController) + + // Check the expectations of the rc before counting active pods, otherwise a new pod can sneak in + // and update the expectations after we've retrieved active pods from the store. If a new pod enters + // the store after we've checked the expectation, the rc sync is just deferred till the next relist. + rcKey, err := controller.KeyFunc(&rc) + if err != nil { + glog.Errorf("Couldn't get key for replication controller %#v: %v", rc, err) + return err + } + rcNeedsSync := rm.expectations.SatisfiedExpectations(rcKey) + podList, err := rm.podStore.Pods(rc.Namespace).List(labels.Set(rc.Spec.Selector).AsSelector()) + if err != nil { + glog.Errorf("Error getting pods for rc %q: %v", key, err) + rm.queue.Add(key) + return err + } + + // TODO: Do this in a single pass, or use an index. + filteredPods := controller.FilterActivePods(podList.Items) + if rcNeedsSync { + rm.manageReplicas(filteredPods, &rc) + } + + // Count the number of pods that have labels matching the labels of the pod + // template of the replication controller, the matching pods may have more + // labels than are in the template. Because the label of podTemplateSpec is + // a superset of the selector of the replication controller, so the possible + // matching pods must be part of the filteredPods. + fullyLabeledReplicasCount := 0 + templateLabel := labels.Set(rc.Spec.Template.Labels).AsSelector() + for _, pod := range filteredPods { + if templateLabel.Matches(labels.Set(pod.Labels)) { + fullyLabeledReplicasCount++ + } + } + + // Always updates status as pods come up or die. + if err := updateReplicaCount(rm.kubeClient.Core().ReplicationControllers(rc.Namespace), rc, len(filteredPods), fullyLabeledReplicasCount); err != nil { + // Multiple things could lead to this update failing. Requeuing the controller ensures + // we retry with some fairness. + glog.V(2).Infof("Failed to update replica count for controller %v/%v; requeuing; error: %v", rc.Namespace, rc.Name, err) + rm.enqueueController(&rc) + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/replication/replication_controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/replication/replication_controller_test.go new file mode 100644 index 000000000..790402944 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/replication/replication_controller_test.go @@ -0,0 +1,1105 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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. +*/ + +// If you make changes to this file, you should also make the corresponding change in ReplicaSet. + +package replication + +import ( + "fmt" + "math/rand" + "net/http/httptest" + "strings" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/securitycontext" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/sets" + utiltesting "k8s.io/kubernetes/pkg/util/testing" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/watch" +) + +var alwaysReady = func() bool { return true } + +func getKey(rc *api.ReplicationController, t *testing.T) string { + if key, err := controller.KeyFunc(rc); err != nil { + t.Errorf("Unexpected error getting key for rc %v: %v", rc.Name, err) + return "" + } else { + return key + } +} + +func newReplicationController(replicas int) *api.ReplicationController { + rc := &api.ReplicationController{ + TypeMeta: unversioned.TypeMeta{APIVersion: testapi.Default.GroupVersion().String()}, + ObjectMeta: api.ObjectMeta{ + UID: util.NewUUID(), + Name: "foobar", + Namespace: api.NamespaceDefault, + ResourceVersion: "18", + }, + Spec: api.ReplicationControllerSpec{ + Replicas: replicas, + Selector: map[string]string{"foo": "bar"}, + Template: &api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{ + "name": "foo", + "type": "production", + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Image: "foo/bar", + TerminationMessagePath: api.TerminationMessagePathDefault, + ImagePullPolicy: api.PullIfNotPresent, + SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), + }, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSDefault, + NodeSelector: map[string]string{ + "baz": "blah", + }, + }, + }, + }, + } + return rc +} + +// create count pods with the given phase for the given rc (same selectors and namespace), and add them to the store. +func newPodList(store cache.Store, count int, status api.PodPhase, rc *api.ReplicationController, name string) *api.PodList { + pods := []api.Pod{} + for i := 0; i < count; i++ { + newPod := api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: fmt.Sprintf("%s%d", name, i), + Labels: rc.Spec.Selector, + Namespace: rc.Namespace, + }, + Status: api.PodStatus{Phase: status}, + } + if store != nil { + store.Add(&newPod) + } + pods = append(pods, newPod) + } + return &api.PodList{ + Items: pods, + } +} + +func validateSyncReplication(t *testing.T, fakePodControl *controller.FakePodControl, expectedCreates, expectedDeletes int) { + if len(fakePodControl.Templates) != expectedCreates { + t.Errorf("Unexpected number of creates. Expected %d, saw %d\n", expectedCreates, len(fakePodControl.Templates)) + } + if len(fakePodControl.DeletePodName) != expectedDeletes { + t.Errorf("Unexpected number of deletes. Expected %d, saw %d\n", expectedDeletes, len(fakePodControl.DeletePodName)) + } +} + +func replicationControllerResourceName() string { + return "replicationcontrollers" +} + +type serverResponse struct { + statusCode int + obj interface{} +} + +func TestSyncReplicationControllerDoesNothing(t *testing.T) { + c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + fakePodControl := controller.FakePodControl{} + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + // 2 running pods, a controller with 2 replicas, sync is a no-op + controllerSpec := newReplicationController(2) + manager.rcStore.Store.Add(controllerSpec) + newPodList(manager.podStore.Store, 2, api.PodRunning, controllerSpec, "pod") + + manager.podControl = &fakePodControl + manager.syncReplicationController(getKey(controllerSpec, t)) + validateSyncReplication(t, &fakePodControl, 0, 0) +} + +func TestSyncReplicationControllerDeletes(t *testing.T) { + c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + fakePodControl := controller.FakePodControl{} + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + manager.podControl = &fakePodControl + + // 2 running pods and a controller with 1 replica, one pod delete expected + controllerSpec := newReplicationController(1) + manager.rcStore.Store.Add(controllerSpec) + newPodList(manager.podStore.Store, 2, api.PodRunning, controllerSpec, "pod") + + manager.syncReplicationController(getKey(controllerSpec, t)) + validateSyncReplication(t, &fakePodControl, 0, 1) +} + +func TestDeleteFinalStateUnknown(t *testing.T) { + c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + fakePodControl := controller.FakePodControl{} + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + manager.podControl = &fakePodControl + + received := make(chan string) + manager.syncHandler = func(key string) error { + received <- key + return nil + } + + // The DeletedFinalStateUnknown object should cause the rc manager to insert + // the controller matching the selectors of the deleted pod into the work queue. + controllerSpec := newReplicationController(1) + manager.rcStore.Store.Add(controllerSpec) + pods := newPodList(nil, 1, api.PodRunning, controllerSpec, "pod") + manager.deletePod(cache.DeletedFinalStateUnknown{Key: "foo", Obj: &pods.Items[0]}) + + go manager.worker() + + expected := getKey(controllerSpec, t) + select { + case key := <-received: + if key != expected { + t.Errorf("Unexpected sync all for rc %v, expected %v", key, expected) + } + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Processing DeleteFinalStateUnknown took longer than expected") + } +} + +func TestSyncReplicationControllerCreates(t *testing.T) { + c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + // A controller with 2 replicas and no pods in the store, 2 creates expected + rc := newReplicationController(2) + manager.rcStore.Store.Add(rc) + + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + manager.syncReplicationController(getKey(rc, t)) + validateSyncReplication(t, &fakePodControl, 2, 0) +} + +func TestStatusUpdatesWithoutReplicasChange(t *testing.T) { + // Setup a fake server to listen for requests, and run the rc manager in steady state + fakeHandler := utiltesting.FakeHandler{ + StatusCode: 200, + ResponseBody: "", + } + testServer := httptest.NewServer(&fakeHandler) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + c := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + // Steady state for the replication controller, no Status.Replicas updates expected + activePods := 5 + rc := newReplicationController(activePods) + manager.rcStore.Store.Add(rc) + rc.Status = api.ReplicationControllerStatus{Replicas: activePods} + newPodList(manager.podStore.Store, activePods, api.PodRunning, rc, "pod") + + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + manager.syncReplicationController(getKey(rc, t)) + + validateSyncReplication(t, &fakePodControl, 0, 0) + if fakeHandler.RequestReceived != nil { + t.Errorf("Unexpected update when pods and rcs are in a steady state") + } + + // This response body is just so we don't err out decoding the http response, all + // we care about is the request body sent below. + response := runtime.EncodeOrDie(testapi.Default.Codec(), &api.ReplicationController{}) + fakeHandler.ResponseBody = response + + rc.Generation = rc.Generation + 1 + manager.syncReplicationController(getKey(rc, t)) + + rc.Status.ObservedGeneration = rc.Generation + updatedRc := runtime.EncodeOrDie(testapi.Default.Codec(), rc) + fakeHandler.ValidateRequest(t, testapi.Default.ResourcePath(replicationControllerResourceName(), rc.Namespace, rc.Name)+"/status", "PUT", &updatedRc) +} + +func TestControllerUpdateReplicas(t *testing.T) { + // This is a happy server just to record the PUT request we expect for status.Replicas + fakeHandler := utiltesting.FakeHandler{ + StatusCode: 200, + ResponseBody: "", + } + testServer := httptest.NewServer(&fakeHandler) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + c := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + // Insufficient number of pods in the system, and Status.Replicas is wrong; + // Status.Replica should update to match number of pods in system, 1 new pod should be created. + rc := newReplicationController(5) + manager.rcStore.Store.Add(rc) + rc.Status = api.ReplicationControllerStatus{Replicas: 2, FullyLabeledReplicas: 6, ObservedGeneration: 0} + rc.Generation = 1 + newPodList(manager.podStore.Store, 2, api.PodRunning, rc, "pod") + rcCopy := *rc + extraLabelMap := map[string]string{"foo": "bar", "extraKey": "extraValue"} + rcCopy.Spec.Selector = extraLabelMap + newPodList(manager.podStore.Store, 2, api.PodRunning, &rcCopy, "podWithExtraLabel") + + // This response body is just so we don't err out decoding the http response + response := runtime.EncodeOrDie(testapi.Default.Codec(), &api.ReplicationController{}) + fakeHandler.ResponseBody = response + + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + + manager.syncReplicationController(getKey(rc, t)) + + // 1. Status.Replicas should go up from 2->4 even though we created 5-4=1 pod. + // 2. Status.FullyLabeledReplicas should equal to the number of pods that + // has the extra labels, i.e., 2. + // 3. Every update to the status should include the Generation of the spec. + rc.Status = api.ReplicationControllerStatus{Replicas: 4, ObservedGeneration: 1} + + decRc := runtime.EncodeOrDie(testapi.Default.Codec(), rc) + fakeHandler.ValidateRequest(t, testapi.Default.ResourcePath(replicationControllerResourceName(), rc.Namespace, rc.Name)+"/status", "PUT", &decRc) + validateSyncReplication(t, &fakePodControl, 1, 0) +} + +func TestSyncReplicationControllerDormancy(t *testing.T) { + // Setup a test server so we can lie about the current state of pods + fakeHandler := utiltesting.FakeHandler{ + StatusCode: 200, + ResponseBody: "{}", + } + testServer := httptest.NewServer(&fakeHandler) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + c := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + fakePodControl := controller.FakePodControl{} + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + manager.podControl = &fakePodControl + + controllerSpec := newReplicationController(2) + manager.rcStore.Store.Add(controllerSpec) + newPodList(manager.podStore.Store, 1, api.PodRunning, controllerSpec, "pod") + + // Creates a replica and sets expectations + controllerSpec.Status.Replicas = 1 + manager.syncReplicationController(getKey(controllerSpec, t)) + validateSyncReplication(t, &fakePodControl, 1, 0) + + // Expectations prevents replicas but not an update on status + controllerSpec.Status.Replicas = 0 + fakePodControl.Clear() + manager.syncReplicationController(getKey(controllerSpec, t)) + validateSyncReplication(t, &fakePodControl, 0, 0) + + // Get the key for the controller + rcKey, err := controller.KeyFunc(controllerSpec) + if err != nil { + t.Errorf("Couldn't get key for object %+v: %v", controllerSpec, err) + } + + // Lowering expectations should lead to a sync that creates a replica, however the + // fakePodControl error will prevent this, leaving expectations at 0, 0 + manager.expectations.CreationObserved(rcKey) + controllerSpec.Status.Replicas = 1 + fakePodControl.Clear() + fakePodControl.Err = fmt.Errorf("Fake Error") + + manager.syncReplicationController(getKey(controllerSpec, t)) + validateSyncReplication(t, &fakePodControl, 0, 0) + + // This replica should not need a Lowering of expectations, since the previous create failed + fakePodControl.Err = nil + manager.syncReplicationController(getKey(controllerSpec, t)) + validateSyncReplication(t, &fakePodControl, 1, 0) + + // 1 PUT for the rc status during dormancy window. + // Note that the pod creates go through pod control so they're not recorded. + fakeHandler.ValidateRequestCount(t, 1) +} + +func TestPodControllerLookup(t *testing.T) { + manager := NewReplicationManager(clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}), controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + testCases := []struct { + inRCs []*api.ReplicationController + pod *api.Pod + outRCName string + }{ + // pods without labels don't match any rcs + { + inRCs: []*api.ReplicationController{ + {ObjectMeta: api.ObjectMeta{Name: "basic"}}}, + pod: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo1", Namespace: api.NamespaceAll}}, + outRCName: "", + }, + // Matching labels, not namespace + { + inRCs: []*api.ReplicationController{ + { + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.ReplicationControllerSpec{ + Selector: map[string]string{"foo": "bar"}, + }, + }, + }, + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo2", Namespace: "ns", Labels: map[string]string{"foo": "bar"}}}, + outRCName: "", + }, + // Matching ns and labels returns the key to the rc, not the rc name + { + inRCs: []*api.ReplicationController{ + { + ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "ns"}, + Spec: api.ReplicationControllerSpec{ + Selector: map[string]string{"foo": "bar"}, + }, + }, + }, + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo3", Namespace: "ns", Labels: map[string]string{"foo": "bar"}}}, + outRCName: "bar", + }, + } + for _, c := range testCases { + for _, r := range c.inRCs { + manager.rcStore.Add(r) + } + if rc := manager.getPodController(c.pod); rc != nil { + if c.outRCName != rc.Name { + t.Errorf("Got controller %+v expected %+v", rc.Name, c.outRCName) + } + } else if c.outRCName != "" { + t.Errorf("Expected a controller %v pod %v, found none", c.outRCName, c.pod.Name) + } + } +} + +func TestWatchControllers(t *testing.T) { + fakeWatch := watch.NewFake() + c := &fake.Clientset{} + c.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + var testControllerSpec api.ReplicationController + received := make(chan string) + + // The update sent through the fakeWatcher should make its way into the workqueue, + // and eventually into the syncHandler. The handler validates the received controller + // and closes the received channel to indicate that the test can finish. + manager.syncHandler = func(key string) error { + + obj, exists, err := manager.rcStore.Store.GetByKey(key) + if !exists || err != nil { + t.Errorf("Expected to find controller under key %v", key) + } + controllerSpec := *obj.(*api.ReplicationController) + if !api.Semantic.DeepDerivative(controllerSpec, testControllerSpec) { + t.Errorf("Expected %#v, but got %#v", testControllerSpec, controllerSpec) + } + close(received) + return nil + } + // Start only the rc watcher and the workqueue, send a watch event, + // and make sure it hits the sync method. + stopCh := make(chan struct{}) + defer close(stopCh) + go manager.rcController.Run(stopCh) + go wait.Until(manager.worker, 10*time.Millisecond, stopCh) + + testControllerSpec.Name = "foo" + fakeWatch.Add(&testControllerSpec) + + select { + case <-received: + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Expected 1 call but got 0") + } +} + +func TestWatchPods(t *testing.T) { + fakeWatch := watch.NewFake() + c := &fake.Clientset{} + c.AddWatchReactor("*", core.DefaultWatchReactor(fakeWatch, nil)) + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + // Put one rc and one pod into the controller's stores + testControllerSpec := newReplicationController(1) + manager.rcStore.Store.Add(testControllerSpec) + received := make(chan string) + // The pod update sent through the fakeWatcher should figure out the managing rc and + // send it into the syncHandler. + manager.syncHandler = func(key string) error { + + obj, exists, err := manager.rcStore.Store.GetByKey(key) + if !exists || err != nil { + t.Errorf("Expected to find controller under key %v", key) + } + controllerSpec := obj.(*api.ReplicationController) + if !api.Semantic.DeepDerivative(controllerSpec, testControllerSpec) { + t.Errorf("\nExpected %#v,\nbut got %#v", testControllerSpec, controllerSpec) + } + close(received) + return nil + } + // Start only the pod watcher and the workqueue, send a watch event, + // and make sure it hits the sync method for the right rc. + stopCh := make(chan struct{}) + defer close(stopCh) + go manager.podController.Run(stopCh) + go wait.Until(manager.worker, 10*time.Millisecond, stopCh) + + pods := newPodList(nil, 1, api.PodRunning, testControllerSpec, "pod") + testPod := pods.Items[0] + testPod.Status.Phase = api.PodFailed + fakeWatch.Add(&testPod) + + select { + case <-received: + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Expected 1 call but got 0") + } +} + +func TestUpdatePods(t *testing.T) { + manager := NewReplicationManager(fake.NewSimpleClientset(), controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + received := make(chan string) + + manager.syncHandler = func(key string) error { + obj, exists, err := manager.rcStore.Store.GetByKey(key) + if !exists || err != nil { + t.Errorf("Expected to find controller under key %v", key) + } + received <- obj.(*api.ReplicationController).Name + return nil + } + + stopCh := make(chan struct{}) + defer close(stopCh) + go wait.Until(manager.worker, 10*time.Millisecond, stopCh) + + // Put 2 rcs and one pod into the controller's stores + testControllerSpec1 := newReplicationController(1) + manager.rcStore.Store.Add(testControllerSpec1) + testControllerSpec2 := *testControllerSpec1 + testControllerSpec2.Spec.Selector = map[string]string{"bar": "foo"} + testControllerSpec2.Name = "barfoo" + manager.rcStore.Store.Add(&testControllerSpec2) + + // Put one pod in the podStore + pod1 := newPodList(manager.podStore.Store, 1, api.PodRunning, testControllerSpec1, "pod").Items[0] + pod2 := pod1 + pod2.Labels = testControllerSpec2.Spec.Selector + + // Send an update of the same pod with modified labels, and confirm we get a sync request for + // both controllers + manager.updatePod(&pod1, &pod2) + + expected := sets.NewString(testControllerSpec1.Name, testControllerSpec2.Name) + for _, name := range expected.List() { + t.Logf("Expecting update for %+v", name) + select { + case got := <-received: + if !expected.Has(got) { + t.Errorf("Expected keys %#v got %v", expected, got) + } + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Expected update notifications for controllers within 100ms each") + } + } +} + +func TestControllerUpdateRequeue(t *testing.T) { + // This server should force a requeue of the controller because it fails to update status.Replicas. + fakeHandler := utiltesting.FakeHandler{ + StatusCode: 500, + ResponseBody: "", + } + testServer := httptest.NewServer(&fakeHandler) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + + c := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, BurstReplicas, 0) + manager.podStoreSynced = alwaysReady + + rc := newReplicationController(1) + manager.rcStore.Store.Add(rc) + rc.Status = api.ReplicationControllerStatus{Replicas: 2} + newPodList(manager.podStore.Store, 1, api.PodRunning, rc, "pod") + + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + + manager.syncReplicationController(getKey(rc, t)) + + ch := make(chan interface{}) + go func() { + item, _ := manager.queue.Get() + ch <- item + }() + select { + case key := <-ch: + expectedKey := getKey(rc, t) + if key != expectedKey { + t.Errorf("Expected requeue of controller with key %s got %s", expectedKey, key) + } + case <-time.After(wait.ForeverTestTimeout): + manager.queue.ShutDown() + t.Errorf("Expected to find an rc in the queue, found none.") + } + // 1 Update and 1 GET, both of which fail + fakeHandler.ValidateRequestCount(t, 2) +} + +func TestControllerUpdateStatusWithFailure(t *testing.T) { + rc := newReplicationController(1) + c := &fake.Clientset{} + c.AddReactor("get", "replicationcontrollers", func(action core.Action) (bool, runtime.Object, error) { + return true, rc, nil + }) + c.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) { + return true, &api.ReplicationController{}, fmt.Errorf("Fake error") + }) + fakeRCClient := c.Core().ReplicationControllers("default") + numReplicas := 10 + updateReplicaCount(fakeRCClient, *rc, numReplicas, 0) + updates, gets := 0, 0 + for _, a := range c.Actions() { + if a.GetResource() != "replicationcontrollers" { + t.Errorf("Unexpected action %+v", a) + continue + } + + switch action := a.(type) { + case core.GetAction: + gets++ + // Make sure the get is for the right rc even though the update failed. + if action.GetName() != rc.Name { + t.Errorf("Expected get for rc %v, got %+v instead", rc.Name, action.GetName()) + } + case core.UpdateAction: + updates++ + // Confirm that the update has the right status.Replicas even though the Get + // returned an rc with replicas=1. + if c, ok := action.GetObject().(*api.ReplicationController); !ok { + t.Errorf("Expected an rc as the argument to update, got %T", c) + } else if c.Status.Replicas != numReplicas { + t.Errorf("Expected update for rc to contain replicas %v, got %v instead", + numReplicas, c.Status.Replicas) + } + default: + t.Errorf("Unexpected action %+v", a) + break + } + } + if gets != 1 || updates != 2 { + t.Errorf("Expected 1 get and 2 updates, got %d gets %d updates", gets, updates) + } +} + +// TODO: This test is too hairy for a unittest. It should be moved to an E2E suite. +func doTestControllerBurstReplicas(t *testing.T, burstReplicas, numReplicas int) { + c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + fakePodControl := controller.FakePodControl{} + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, burstReplicas, 0) + manager.podStoreSynced = alwaysReady + manager.podControl = &fakePodControl + + controllerSpec := newReplicationController(numReplicas) + manager.rcStore.Store.Add(controllerSpec) + + expectedPods := 0 + pods := newPodList(nil, numReplicas, api.PodPending, controllerSpec, "pod") + + rcKey, err := controller.KeyFunc(controllerSpec) + if err != nil { + t.Errorf("Couldn't get key for object %+v: %v", controllerSpec, err) + } + + // Size up the controller, then size it down, and confirm the expected create/delete pattern + for _, replicas := range []int{numReplicas, 0} { + + controllerSpec.Spec.Replicas = replicas + manager.rcStore.Store.Add(controllerSpec) + + for i := 0; i < numReplicas; i += burstReplicas { + manager.syncReplicationController(getKey(controllerSpec, t)) + + // The store accrues active pods. It's also used by the rc to determine how many + // replicas to create. + activePods := len(manager.podStore.Store.List()) + if replicas != 0 { + // This is the number of pods currently "in flight". They were created by the rc manager above, + // which then puts the rc to sleep till all of them have been observed. + expectedPods = replicas - activePods + if expectedPods > burstReplicas { + expectedPods = burstReplicas + } + // This validates the rc manager sync actually created pods + validateSyncReplication(t, &fakePodControl, expectedPods, 0) + + // This simulates the watch events for all but 1 of the expected pods. + // None of these should wake the controller because it has expectations==BurstReplicas. + for i := 0; i < expectedPods-1; i++ { + manager.podStore.Store.Add(&pods.Items[i]) + manager.addPod(&pods.Items[i]) + } + + podExp, exists, err := manager.expectations.GetExpectations(rcKey) + if !exists || err != nil { + t.Fatalf("Did not find expectations for rc.") + } + if add, _ := podExp.GetExpectations(); add != 1 { + t.Fatalf("Expectations are wrong %v", podExp) + } + } else { + expectedPods = (replicas - activePods) * -1 + if expectedPods > burstReplicas { + expectedPods = burstReplicas + } + validateSyncReplication(t, &fakePodControl, 0, expectedPods) + + // To accurately simulate a watch we must delete the exact pods + // the rc is waiting for. + expectedDels := manager.expectations.GetUIDs(getKey(controllerSpec, t)) + podsToDelete := []*api.Pod{} + for _, key := range expectedDels.List() { + nsName := strings.Split(key, "/") + podsToDelete = append(podsToDelete, &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: nsName[1], + Namespace: nsName[0], + Labels: controllerSpec.Spec.Selector, + }, + }) + } + // Don't delete all pods because we confirm that the last pod + // has exactly one expectation at the end, to verify that we + // don't double delete. + for i := range podsToDelete[1:] { + manager.podStore.Delete(podsToDelete[i]) + manager.deletePod(podsToDelete[i]) + } + podExp, exists, err := manager.expectations.GetExpectations(rcKey) + if !exists || err != nil { + t.Fatalf("Did not find expectations for rc.") + } + if _, del := podExp.GetExpectations(); del != 1 { + t.Fatalf("Expectations are wrong %v", podExp) + } + } + + // Check that the rc didn't take any action for all the above pods + fakePodControl.Clear() + manager.syncReplicationController(getKey(controllerSpec, t)) + validateSyncReplication(t, &fakePodControl, 0, 0) + + // Create/Delete the last pod + // The last add pod will decrease the expectation of the rc to 0, + // which will cause it to create/delete the remaining replicas up to burstReplicas. + if replicas != 0 { + manager.podStore.Store.Add(&pods.Items[expectedPods-1]) + manager.addPod(&pods.Items[expectedPods-1]) + } else { + expectedDel := manager.expectations.GetUIDs(getKey(controllerSpec, t)) + if expectedDel.Len() != 1 { + t.Fatalf("Waiting on unexpected number of deletes.") + } + nsName := strings.Split(expectedDel.List()[0], "/") + lastPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: nsName[1], + Namespace: nsName[0], + Labels: controllerSpec.Spec.Selector, + }, + } + manager.podStore.Store.Delete(lastPod) + manager.deletePod(lastPod) + } + pods.Items = pods.Items[expectedPods:] + } + + // Confirm that we've created the right number of replicas + activePods := len(manager.podStore.Store.List()) + if activePods != controllerSpec.Spec.Replicas { + t.Fatalf("Unexpected number of active pods, expected %d, got %d", controllerSpec.Spec.Replicas, activePods) + } + // Replenish the pod list, since we cut it down sizing up + pods = newPodList(nil, replicas, api.PodRunning, controllerSpec, "pod") + } +} + +func TestControllerBurstReplicas(t *testing.T) { + doTestControllerBurstReplicas(t, 5, 30) + doTestControllerBurstReplicas(t, 5, 12) + doTestControllerBurstReplicas(t, 3, 2) +} + +type FakeRCExpectations struct { + *controller.ControllerExpectations + satisfied bool + expSatisfied func() +} + +func (fe FakeRCExpectations) SatisfiedExpectations(controllerKey string) bool { + fe.expSatisfied() + return fe.satisfied +} + +// TestRCSyncExpectations tests that a pod cannot sneak in between counting active pods +// and checking expectations. +func TestRCSyncExpectations(t *testing.T) { + c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + fakePodControl := controller.FakePodControl{} + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, 2, 0) + manager.podStoreSynced = alwaysReady + manager.podControl = &fakePodControl + + controllerSpec := newReplicationController(2) + manager.rcStore.Store.Add(controllerSpec) + pods := newPodList(nil, 2, api.PodPending, controllerSpec, "pod") + manager.podStore.Store.Add(&pods.Items[0]) + postExpectationsPod := pods.Items[1] + + manager.expectations = controller.NewUIDTrackingControllerExpectations(FakeRCExpectations{ + controller.NewControllerExpectations(), true, func() { + // If we check active pods before checking expectataions, the rc + // will create a new replica because it doesn't see this pod, but + // has fulfilled its expectations. + manager.podStore.Store.Add(&postExpectationsPod) + }, + }) + manager.syncReplicationController(getKey(controllerSpec, t)) + validateSyncReplication(t, &fakePodControl, 0, 0) +} + +func TestDeleteControllerAndExpectations(t *testing.T) { + c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, 10, 0) + manager.podStoreSynced = alwaysReady + + rc := newReplicationController(1) + manager.rcStore.Store.Add(rc) + + fakePodControl := controller.FakePodControl{} + manager.podControl = &fakePodControl + + // This should set expectations for the rc + manager.syncReplicationController(getKey(rc, t)) + validateSyncReplication(t, &fakePodControl, 1, 0) + fakePodControl.Clear() + + // Get the RC key + rcKey, err := controller.KeyFunc(rc) + if err != nil { + t.Errorf("Couldn't get key for object %+v: %v", rc, err) + } + + // This is to simulate a concurrent addPod, that has a handle on the expectations + // as the controller deletes it. + podExp, exists, err := manager.expectations.GetExpectations(rcKey) + if !exists || err != nil { + t.Errorf("No expectations found for rc") + } + manager.rcStore.Delete(rc) + manager.syncReplicationController(getKey(rc, t)) + + if _, exists, err = manager.expectations.GetExpectations(rcKey); exists { + t.Errorf("Found expectaions, expected none since the rc has been deleted.") + } + + // This should have no effect, since we've deleted the rc. + podExp.Add(-1, 0) + manager.podStore.Store.Replace(make([]interface{}, 0), "0") + manager.syncReplicationController(getKey(rc, t)) + validateSyncReplication(t, &fakePodControl, 0, 0) +} + +func TestRCManagerNotReady(t *testing.T) { + c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + fakePodControl := controller.FakePodControl{} + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, 2, 0) + manager.podControl = &fakePodControl + manager.podStoreSynced = func() bool { return false } + + // Simulates the rc reflector running before the pod reflector. We don't + // want to end up creating replicas in this case until the pod reflector + // has synced, so the rc manager should just requeue the rc. + controllerSpec := newReplicationController(1) + manager.rcStore.Store.Add(controllerSpec) + + rcKey := getKey(controllerSpec, t) + manager.syncReplicationController(rcKey) + validateSyncReplication(t, &fakePodControl, 0, 0) + queueRC, _ := manager.queue.Get() + if queueRC != rcKey { + t.Fatalf("Expected to find key %v in queue, found %v", rcKey, queueRC) + } + + manager.podStoreSynced = alwaysReady + manager.syncReplicationController(rcKey) + validateSyncReplication(t, &fakePodControl, 1, 0) +} + +// shuffle returns a new shuffled list of container controllers. +func shuffle(controllers []*api.ReplicationController) []*api.ReplicationController { + numControllers := len(controllers) + randIndexes := rand.Perm(numControllers) + shuffled := make([]*api.ReplicationController, numControllers) + for i := 0; i < numControllers; i++ { + shuffled[i] = controllers[randIndexes[i]] + } + return shuffled +} + +func TestOverlappingRCs(t *testing.T) { + c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + + for i := 0; i < 5; i++ { + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, 10, 0) + manager.podStoreSynced = alwaysReady + + // Create 10 rcs, shuffled them randomly and insert them into the rc manager's store + var controllers []*api.ReplicationController + for j := 1; j < 10; j++ { + controllerSpec := newReplicationController(1) + controllerSpec.CreationTimestamp = unversioned.Date(2014, time.December, j, 0, 0, 0, 0, time.Local) + controllerSpec.Name = string(util.NewUUID()) + controllers = append(controllers, controllerSpec) + } + shuffledControllers := shuffle(controllers) + for j := range shuffledControllers { + manager.rcStore.Store.Add(shuffledControllers[j]) + } + // Add a pod and make sure only the oldest rc is synced + pods := newPodList(nil, 1, api.PodPending, controllers[0], "pod") + rcKey := getKey(controllers[0], t) + + manager.addPod(&pods.Items[0]) + queueRC, _ := manager.queue.Get() + if queueRC != rcKey { + t.Fatalf("Expected to find key %v in queue, found %v", rcKey, queueRC) + } + } +} + +func TestDeletionTimestamp(t *testing.T) { + c := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewReplicationManager(c, controller.NoResyncPeriodFunc, 10, 0) + manager.podStoreSynced = alwaysReady + + controllerSpec := newReplicationController(1) + manager.rcStore.Store.Add(controllerSpec) + rcKey, err := controller.KeyFunc(controllerSpec) + if err != nil { + t.Errorf("Couldn't get key for object %+v: %v", controllerSpec, err) + } + pod := newPodList(nil, 1, api.PodPending, controllerSpec, "pod").Items[0] + pod.DeletionTimestamp = &unversioned.Time{Time: time.Now()} + manager.expectations.ExpectDeletions(rcKey, []string{controller.PodKey(&pod)}) + + // A pod added with a deletion timestamp should decrement deletions, not creations. + manager.addPod(&pod) + + queueRC, _ := manager.queue.Get() + if queueRC != rcKey { + t.Fatalf("Expected to find key %v in queue, found %v", rcKey, queueRC) + } + manager.queue.Done(rcKey) + + podExp, exists, err := manager.expectations.GetExpectations(rcKey) + if !exists || err != nil || !podExp.Fulfilled() { + t.Fatalf("Wrong expectations %+v", podExp) + } + + // An update from no deletion timestamp to having one should be treated + // as a deletion. + oldPod := newPodList(nil, 1, api.PodPending, controllerSpec, "pod").Items[0] + manager.expectations.ExpectDeletions(rcKey, []string{controller.PodKey(&pod)}) + manager.updatePod(&oldPod, &pod) + + queueRC, _ = manager.queue.Get() + if queueRC != rcKey { + t.Fatalf("Expected to find key %v in queue, found %v", rcKey, queueRC) + } + manager.queue.Done(rcKey) + + podExp, exists, err = manager.expectations.GetExpectations(rcKey) + if !exists || err != nil || !podExp.Fulfilled() { + t.Fatalf("Wrong expectations %+v", podExp) + } + + // An update to the pod (including an update to the deletion timestamp) + // should not be counted as a second delete. + secondPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Namespace: pod.Namespace, + Name: "secondPod", + Labels: pod.Labels, + }, + } + manager.expectations.ExpectDeletions(rcKey, []string{controller.PodKey(secondPod)}) + oldPod.DeletionTimestamp = &unversioned.Time{Time: time.Now()} + manager.updatePod(&oldPod, &pod) + + podExp, exists, err = manager.expectations.GetExpectations(rcKey) + if !exists || err != nil || podExp.Fulfilled() { + t.Fatalf("Wrong expectations %+v", podExp) + } + + // A pod with a non-nil deletion timestamp should also be ignored by the + // delete handler, because it's already been counted in the update. + manager.deletePod(&pod) + podExp, exists, err = manager.expectations.GetExpectations(rcKey) + if !exists || err != nil || podExp.Fulfilled() { + t.Fatalf("Wrong expectations %+v", podExp) + } + + // Deleting the second pod should clear expectations. + manager.deletePod(secondPod) + + queueRC, _ = manager.queue.Get() + if queueRC != rcKey { + t.Fatalf("Expected to find key %v in queue, found %v", rcKey, queueRC) + } + manager.queue.Done(rcKey) + + podExp, exists, err = manager.expectations.GetExpectations(rcKey) + if !exists || err != nil || !podExp.Fulfilled() { + t.Fatalf("Wrong expectations %+v", podExp) + } +} + +func BenchmarkGetPodControllerMultiNS(b *testing.B) { + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewReplicationManager(client, controller.NoResyncPeriodFunc, BurstReplicas, 0) + + const nsNum = 1000 + + pods := []api.Pod{} + for i := 0; i < nsNum; i++ { + ns := fmt.Sprintf("ns-%d", i) + for j := 0; j < 10; j++ { + rcName := fmt.Sprintf("rc-%d", j) + for k := 0; k < 10; k++ { + podName := fmt.Sprintf("pod-%d-%d", j, k) + pods = append(pods, api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: podName, + Namespace: ns, + Labels: map[string]string{"rcName": rcName}, + }, + }) + } + } + } + + for i := 0; i < nsNum; i++ { + ns := fmt.Sprintf("ns-%d", i) + for j := 0; j < 10; j++ { + rcName := fmt.Sprintf("rc-%d", j) + manager.rcStore.Add(&api.ReplicationController{ + ObjectMeta: api.ObjectMeta{Name: rcName, Namespace: ns}, + Spec: api.ReplicationControllerSpec{ + Selector: map[string]string{"rcName": rcName}, + }, + }) + } + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for _, pod := range pods { + manager.getPodController(&pod) + } + } +} + +func BenchmarkGetPodControllerSingleNS(b *testing.B) { + client := clientset.NewForConfigOrDie(&restclient.Config{Host: "", ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}) + manager := NewReplicationManager(client, controller.NoResyncPeriodFunc, BurstReplicas, 0) + + const rcNum = 1000 + const replicaNum = 3 + + pods := []api.Pod{} + for i := 0; i < rcNum; i++ { + rcName := fmt.Sprintf("rc-%d", i) + for j := 0; j < replicaNum; j++ { + podName := fmt.Sprintf("pod-%d-%d", i, j) + pods = append(pods, api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: podName, + Namespace: "foo", + Labels: map[string]string{"rcName": rcName}, + }, + }) + } + } + + for i := 0; i < rcNum; i++ { + rcName := fmt.Sprintf("rc-%d", i) + manager.rcStore.Add(&api.ReplicationController{ + ObjectMeta: api.ObjectMeta{Name: rcName, Namespace: "foo"}, + Spec: api.ReplicationControllerSpec{ + Selector: map[string]string{"rcName": rcName}, + }, + }) + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for _, pod := range pods { + manager.getPodController(&pod) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/replication/replication_controller_utils.go b/vendor/k8s.io/kubernetes/pkg/controller/replication/replication_controller_utils.go new file mode 100644 index 000000000..0af2530c1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/replication/replication_controller_utils.go @@ -0,0 +1,77 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// If you make changes to this file, you should also make the corresponding change in ReplicaSet. + +package replication + +import ( + "fmt" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" +) + +// updateReplicaCount attempts to update the Status.Replicas of the given controller, with a single GET/PUT retry. +func updateReplicaCount(rcClient unversionedcore.ReplicationControllerInterface, controller api.ReplicationController, numReplicas, numFullyLabeledReplicas int) (updateErr error) { + // This is the steady state. It happens when the rc doesn't have any expectations, since + // we do a periodic relist every 30s. If the generations differ but the replicas are + // the same, a caller might've resized to the same replica count. + if controller.Status.Replicas == numReplicas && + controller.Status.FullyLabeledReplicas == numFullyLabeledReplicas && + controller.Generation == controller.Status.ObservedGeneration { + return nil + } + // Save the generation number we acted on, otherwise we might wrongfully indicate + // that we've seen a spec update when we retry. + // TODO: This can clobber an update if we allow multiple agents to write to the + // same status. + generation := controller.Generation + + var getErr error + for i, rc := 0, &controller; ; i++ { + glog.V(4).Infof(fmt.Sprintf("Updating replica count for rc: %s/%s, ", controller.Namespace, controller.Name) + + fmt.Sprintf("replicas %d->%d (need %d), ", controller.Status.Replicas, numReplicas, controller.Spec.Replicas) + + fmt.Sprintf("fullyLabeledReplicas %d->%d, ", controller.Status.FullyLabeledReplicas, numFullyLabeledReplicas) + + fmt.Sprintf("sequence No: %v->%v", controller.Status.ObservedGeneration, generation)) + + rc.Status = api.ReplicationControllerStatus{Replicas: numReplicas, FullyLabeledReplicas: numFullyLabeledReplicas, ObservedGeneration: generation} + _, updateErr = rcClient.UpdateStatus(rc) + if updateErr == nil || i >= statusUpdateRetries { + return updateErr + } + // Update the controller with the latest resource version for the next poll + if rc, getErr = rcClient.Get(controller.Name); getErr != nil { + // If the GET fails we can't trust status.Replicas anymore. This error + // is bound to be more interesting than the update failure. + return getErr + } + } +} + +// OverlappingControllers sorts a list of controllers by creation timestamp, using their names as a tie breaker. +type OverlappingControllers []api.ReplicationController + +func (o OverlappingControllers) Len() int { return len(o) } +func (o OverlappingControllers) Swap(i, j int) { o[i], o[j] = o[j], o[i] } + +func (o OverlappingControllers) Less(i, j int) bool { + if o[i].CreationTimestamp.Equal(o[j].CreationTimestamp) { + return o[i].Name < o[j].Name + } + return o[i].CreationTimestamp.Before(o[j].CreationTimestamp) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/doc.go b/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/doc.go new file mode 100644 index 000000000..a83ad10dd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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. +*/ + +// resourcequota contains a controller that makes resource quota usage observations +package resourcequota diff --git a/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/replenishment_controller.go b/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/replenishment_controller.go new file mode 100644 index 000000000..a75ba291d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/replenishment_controller.go @@ -0,0 +1,222 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 resourcequota + +import ( + "fmt" + + "github.com/golang/glog" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/quota/evaluator/core" + "k8s.io/kubernetes/pkg/runtime" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/watch" +) + +// ReplenishmentFunc is a function that is invoked when controller sees a change +// that may require a quota to be replenished (i.e. object deletion, or object moved to terminal state) +type ReplenishmentFunc func(groupKind unversioned.GroupKind, namespace string, object runtime.Object) + +// ReplenishmentControllerOptions is an options struct that tells a factory +// how to configure a controller that can inform the quota system it should +// replenish quota +type ReplenishmentControllerOptions struct { + // The kind monitored for replenishment + GroupKind unversioned.GroupKind + // The period that should be used to re-sync the monitored resource + ResyncPeriod controller.ResyncPeriodFunc + // The function to invoke when a change is observed that should trigger + // replenishment + ReplenishmentFunc ReplenishmentFunc +} + +// PodReplenishmentUpdateFunc will replenish if the old pod was quota tracked but the new is not +func PodReplenishmentUpdateFunc(options *ReplenishmentControllerOptions) func(oldObj, newObj interface{}) { + return func(oldObj, newObj interface{}) { + oldPod := oldObj.(*api.Pod) + newPod := newObj.(*api.Pod) + if core.QuotaPod(oldPod) && !core.QuotaPod(newPod) { + options.ReplenishmentFunc(options.GroupKind, newPod.Namespace, newPod) + } + } +} + +// ObjectReplenenishmentDeleteFunc will replenish on every delete +func ObjectReplenishmentDeleteFunc(options *ReplenishmentControllerOptions) func(obj interface{}) { + return func(obj interface{}) { + metaObject, err := meta.Accessor(obj) + if err != nil { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + glog.Errorf("replenishment controller could not get object from tombstone %+v, could take up to %v before quota is replenished", obj, options.ResyncPeriod()) + utilruntime.HandleError(err) + return + } + metaObject, err = meta.Accessor(tombstone.Obj) + if err != nil { + glog.Errorf("replenishment controller tombstone contained object that is not a meta %+v, could take up to %v before quota is replenished", tombstone.Obj, options.ResyncPeriod()) + utilruntime.HandleError(err) + return + } + } + options.ReplenishmentFunc(options.GroupKind, metaObject.GetNamespace(), nil) + } +} + +// ReplenishmentControllerFactory knows how to build replenishment controllers +type ReplenishmentControllerFactory interface { + // NewController returns a controller configured with the specified options + NewController(options *ReplenishmentControllerOptions) (*framework.Controller, error) +} + +// replenishmentControllerFactory implements ReplenishmentControllerFactory +type replenishmentControllerFactory struct { + kubeClient clientset.Interface +} + +// NewReplenishmentControllerFactory returns a factory that knows how to build controllers +// to replenish resources when updated or deleted +func NewReplenishmentControllerFactory(kubeClient clientset.Interface) ReplenishmentControllerFactory { + return &replenishmentControllerFactory{ + kubeClient: kubeClient, + } +} + +func (r *replenishmentControllerFactory) NewController(options *ReplenishmentControllerOptions) (*framework.Controller, error) { + var result *framework.Controller + switch options.GroupKind { + case api.Kind("Pod"): + _, result = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return r.kubeClient.Core().Pods(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return r.kubeClient.Core().Pods(api.NamespaceAll).Watch(options) + }, + }, + &api.Pod{}, + options.ResyncPeriod(), + framework.ResourceEventHandlerFuncs{ + UpdateFunc: PodReplenishmentUpdateFunc(options), + DeleteFunc: ObjectReplenishmentDeleteFunc(options), + }, + ) + case api.Kind("Service"): + _, result = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return r.kubeClient.Core().Services(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return r.kubeClient.Core().Services(api.NamespaceAll).Watch(options) + }, + }, + &api.Service{}, + options.ResyncPeriod(), + framework.ResourceEventHandlerFuncs{ + UpdateFunc: ServiceReplenishmentUpdateFunc(options), + DeleteFunc: ObjectReplenishmentDeleteFunc(options), + }, + ) + case api.Kind("ReplicationController"): + _, result = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return r.kubeClient.Core().ReplicationControllers(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return r.kubeClient.Core().ReplicationControllers(api.NamespaceAll).Watch(options) + }, + }, + &api.ReplicationController{}, + options.ResyncPeriod(), + framework.ResourceEventHandlerFuncs{ + DeleteFunc: ObjectReplenishmentDeleteFunc(options), + }, + ) + case api.Kind("PersistentVolumeClaim"): + _, result = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return r.kubeClient.Core().PersistentVolumeClaims(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return r.kubeClient.Core().PersistentVolumeClaims(api.NamespaceAll).Watch(options) + }, + }, + &api.PersistentVolumeClaim{}, + options.ResyncPeriod(), + framework.ResourceEventHandlerFuncs{ + DeleteFunc: ObjectReplenishmentDeleteFunc(options), + }, + ) + case api.Kind("Secret"): + _, result = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return r.kubeClient.Core().Secrets(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return r.kubeClient.Core().Secrets(api.NamespaceAll).Watch(options) + }, + }, + &api.Secret{}, + options.ResyncPeriod(), + framework.ResourceEventHandlerFuncs{ + DeleteFunc: ObjectReplenishmentDeleteFunc(options), + }, + ) + case api.Kind("ConfigMap"): + _, result = framework.NewInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return r.kubeClient.Core().ConfigMaps(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return r.kubeClient.Core().ConfigMaps(api.NamespaceAll).Watch(options) + }, + }, + &api.ConfigMap{}, + options.ResyncPeriod(), + framework.ResourceEventHandlerFuncs{ + DeleteFunc: ObjectReplenishmentDeleteFunc(options), + }, + ) + default: + return nil, fmt.Errorf("no replenishment controller available for %s", options.GroupKind) + } + return result, nil +} + +// ServiceReplenishmentUpdateFunc will replenish if the old service was quota tracked but the new is not +func ServiceReplenishmentUpdateFunc(options *ReplenishmentControllerOptions) func(oldObj, newObj interface{}) { + return func(oldObj, newObj interface{}) { + oldService := oldObj.(*api.Service) + newService := newObj.(*api.Service) + if core.QuotaServiceType(oldService) && !core.QuotaServiceType(newService) { + options.ReplenishmentFunc(options.GroupKind, newService.Namespace, newService) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/replenishment_controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/replenishment_controller_test.go new file mode 100644 index 000000000..b7bb66502 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/replenishment_controller_test.go @@ -0,0 +1,121 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 resourcequota + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/intstr" +) + +// testReplenishment lets us test replenishment functions are invoked +type testReplenishment struct { + groupKind unversioned.GroupKind + namespace string +} + +// mock function that holds onto the last kind that was replenished +func (t *testReplenishment) Replenish(groupKind unversioned.GroupKind, namespace string, object runtime.Object) { + t.groupKind = groupKind + t.namespace = namespace +} + +func TestPodReplenishmentUpdateFunc(t *testing.T) { + mockReplenish := &testReplenishment{} + options := ReplenishmentControllerOptions{ + GroupKind: api.Kind("Pod"), + ReplenishmentFunc: mockReplenish.Replenish, + ResyncPeriod: controller.NoResyncPeriodFunc, + } + oldPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "pod"}, + Status: api.PodStatus{Phase: api.PodRunning}, + } + newPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "pod"}, + Status: api.PodStatus{Phase: api.PodFailed}, + } + updateFunc := PodReplenishmentUpdateFunc(&options) + updateFunc(oldPod, newPod) + if mockReplenish.groupKind != api.Kind("Pod") { + t.Errorf("Unexpected group kind %v", mockReplenish.groupKind) + } + if mockReplenish.namespace != oldPod.Namespace { + t.Errorf("Unexpected namespace %v", mockReplenish.namespace) + } +} + +func TestObjectReplenishmentDeleteFunc(t *testing.T) { + mockReplenish := &testReplenishment{} + options := ReplenishmentControllerOptions{ + GroupKind: api.Kind("Pod"), + ReplenishmentFunc: mockReplenish.Replenish, + ResyncPeriod: controller.NoResyncPeriodFunc, + } + oldPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "pod"}, + Status: api.PodStatus{Phase: api.PodRunning}, + } + deleteFunc := ObjectReplenishmentDeleteFunc(&options) + deleteFunc(oldPod) + if mockReplenish.groupKind != api.Kind("Pod") { + t.Errorf("Unexpected group kind %v", mockReplenish.groupKind) + } + if mockReplenish.namespace != oldPod.Namespace { + t.Errorf("Unexpected namespace %v", mockReplenish.namespace) + } +} + +func TestServiceReplenishmentUpdateFunc(t *testing.T) { + mockReplenish := &testReplenishment{} + options := ReplenishmentControllerOptions{ + GroupKind: api.Kind("Service"), + ReplenishmentFunc: mockReplenish.Replenish, + ResyncPeriod: controller.NoResyncPeriodFunc, + } + oldService := &api.Service{ + ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "mysvc"}, + Spec: api.ServiceSpec{ + Type: api.ServiceTypeNodePort, + Ports: []api.ServicePort{{ + Port: 80, + TargetPort: intstr.FromInt(80), + }}, + }, + } + newService := &api.Service{ + ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "mysvc"}, + Spec: api.ServiceSpec{ + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 80, + TargetPort: intstr.FromInt(80), + }}}, + } + updateFunc := ServiceReplenishmentUpdateFunc(&options) + updateFunc(oldService, newService) + if mockReplenish.groupKind != api.Kind("Service") { + t.Errorf("Unexpected group kind %v", mockReplenish.groupKind) + } + if mockReplenish.namespace != oldService.Namespace { + t.Errorf("Unexpected namespace %v", mockReplenish.namespace) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/resource_quota_controller.go b/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/resource_quota_controller.go new file mode 100644 index 000000000..0a21ebf57 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/resource_quota_controller.go @@ -0,0 +1,318 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 resourcequota + +import ( + "time" + + "github.com/golang/glog" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/runtime" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/util/workqueue" + "k8s.io/kubernetes/pkg/watch" +) + +// ResourceQuotaControllerOptions holds options for creating a quota controller +type ResourceQuotaControllerOptions struct { + // Must have authority to list all quotas, and update quota status + KubeClient clientset.Interface + // Controls full recalculation of quota usage + ResyncPeriod controller.ResyncPeriodFunc + // Knows how to calculate usage + Registry quota.Registry + // Knows how to build controllers that notify replenishment events + ControllerFactory ReplenishmentControllerFactory + // Controls full resync of objects monitored for replenihsment. + ReplenishmentResyncPeriod controller.ResyncPeriodFunc + // List of GroupKind objects that should be monitored for replenishment at + // a faster frequency than the quota controller recalculation interval + GroupKindsToReplenish []unversioned.GroupKind +} + +// ResourceQuotaController is responsible for tracking quota usage status in the system +type ResourceQuotaController struct { + // Must have authority to list all resources in the system, and update quota status + kubeClient clientset.Interface + // An index of resource quota objects by namespace + rqIndexer cache.Indexer + // Watches changes to all resource quota + rqController *framework.Controller + // ResourceQuota objects that need to be synchronized + queue *workqueue.Type + // To allow injection of syncUsage for testing. + syncHandler func(key string) error + // function that controls full recalculation of quota usage + resyncPeriod controller.ResyncPeriodFunc + // knows how to calculate usage + registry quota.Registry + // controllers monitoring to notify for replenishment + replenishmentControllers []*framework.Controller +} + +func NewResourceQuotaController(options *ResourceQuotaControllerOptions) *ResourceQuotaController { + // build the resource quota controller + rq := &ResourceQuotaController{ + kubeClient: options.KubeClient, + queue: workqueue.New(), + resyncPeriod: options.ResyncPeriod, + registry: options.Registry, + replenishmentControllers: []*framework.Controller{}, + } + + // set the synchronization handler + rq.syncHandler = rq.syncResourceQuotaFromKey + + // build the controller that observes quota + rq.rqIndexer, rq.rqController = framework.NewIndexerInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return rq.kubeClient.Core().ResourceQuotas(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return rq.kubeClient.Core().ResourceQuotas(api.NamespaceAll).Watch(options) + }, + }, + &api.ResourceQuota{}, + rq.resyncPeriod(), + framework.ResourceEventHandlerFuncs{ + AddFunc: rq.enqueueResourceQuota, + UpdateFunc: func(old, cur interface{}) { + // We are only interested in observing updates to quota.spec to drive updates to quota.status. + // We ignore all updates to quota.Status because they are all driven by this controller. + // IMPORTANT: + // We do not use this function to queue up a full quota recalculation. To do so, would require + // us to enqueue all quota.Status updates, and since quota.Status updates involve additional queries + // that cannot be backed by a cache and result in a full query of a namespace's content, we do not + // want to pay the price on spurious status updates. As a result, we have a separate routine that is + // responsible for enqueue of all resource quotas when doing a full resync (enqueueAll) + oldResourceQuota := old.(*api.ResourceQuota) + curResourceQuota := cur.(*api.ResourceQuota) + if quota.Equals(curResourceQuota.Spec.Hard, oldResourceQuota.Spec.Hard) { + return + } + rq.enqueueResourceQuota(curResourceQuota) + }, + // This will enter the sync loop and no-op, because the controller has been deleted from the store. + // Note that deleting a controller immediately after scaling it to 0 will not work. The recommended + // way of achieving this is by performing a `stop` operation on the controller. + DeleteFunc: rq.enqueueResourceQuota, + }, + cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc}, + ) + + for _, groupKindToReplenish := range options.GroupKindsToReplenish { + controllerOptions := &ReplenishmentControllerOptions{ + GroupKind: groupKindToReplenish, + ResyncPeriod: options.ReplenishmentResyncPeriod, + ReplenishmentFunc: rq.replenishQuota, + } + replenishmentController, err := options.ControllerFactory.NewController(controllerOptions) + if err != nil { + glog.Warningf("quota controller unable to replenish %s due to %v, changes only accounted during full resync", groupKindToReplenish, err) + } else { + rq.replenishmentControllers = append(rq.replenishmentControllers, replenishmentController) + } + } + return rq +} + +// enqueueAll is called at the fullResyncPeriod interval to force a full recalculation of quota usage statistics +func (rq *ResourceQuotaController) enqueueAll() { + defer glog.V(4).Infof("Resource quota controller queued all resource quota for full calculation of usage") + for _, k := range rq.rqIndexer.ListKeys() { + rq.queue.Add(k) + } +} + +// obj could be an *api.ResourceQuota, or a DeletionFinalStateUnknown marker item. +func (rq *ResourceQuotaController) enqueueResourceQuota(obj interface{}) { + key, err := controller.KeyFunc(obj) + if err != nil { + glog.Errorf("Couldn't get key for object %+v: %v", obj, err) + return + } + rq.queue.Add(key) +} + +// worker runs a worker thread that just dequeues items, processes them, and marks them done. +// It enforces that the syncHandler is never invoked concurrently with the same key. +func (rq *ResourceQuotaController) worker() { + for { + func() { + key, quit := rq.queue.Get() + if quit { + return + } + defer rq.queue.Done(key) + err := rq.syncHandler(key.(string)) + if err != nil { + utilruntime.HandleError(err) + rq.queue.Add(key) + } + }() + } +} + +// Run begins quota controller using the specified number of workers +func (rq *ResourceQuotaController) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + go rq.rqController.Run(stopCh) + // the controllers that replenish other resources to respond rapidly to state changes + for _, replenishmentController := range rq.replenishmentControllers { + go replenishmentController.Run(stopCh) + } + // the workers that chug through the quota calculation backlog + for i := 0; i < workers; i++ { + go wait.Until(rq.worker, time.Second, stopCh) + } + // the timer for how often we do a full recalculation across all quotas + go wait.Until(func() { rq.enqueueAll() }, rq.resyncPeriod(), stopCh) + <-stopCh + glog.Infof("Shutting down ResourceQuotaController") + rq.queue.ShutDown() +} + +// syncResourceQuotaFromKey syncs a quota key +func (rq *ResourceQuotaController) syncResourceQuotaFromKey(key string) (err error) { + startTime := time.Now() + defer func() { + glog.V(4).Infof("Finished syncing resource quota %q (%v)", key, time.Now().Sub(startTime)) + }() + + obj, exists, err := rq.rqIndexer.GetByKey(key) + if !exists { + glog.Infof("Resource quota has been deleted %v", key) + return nil + } + if err != nil { + glog.Infof("Unable to retrieve resource quota %v from store: %v", key, err) + rq.queue.Add(key) + return err + } + quota := *obj.(*api.ResourceQuota) + return rq.syncResourceQuota(quota) +} + +// syncResourceQuota runs a complete sync of resource quota status across all known kinds +func (rq *ResourceQuotaController) syncResourceQuota(resourceQuota api.ResourceQuota) (err error) { + // quota is dirty if any part of spec hard limits differs from the status hard limits + dirty := !api.Semantic.DeepEqual(resourceQuota.Spec.Hard, resourceQuota.Status.Hard) + + // dirty tracks if the usage status differs from the previous sync, + // if so, we send a new usage with latest status + // if this is our first sync, it will be dirty by default, since we need track usage + dirty = dirty || (resourceQuota.Status.Hard == nil || resourceQuota.Status.Used == nil) + + // Create a usage object that is based on the quota resource version that will handle updates + // by default, we preserve the past usage observation, and set hard to the current spec + previousUsed := api.ResourceList{} + if resourceQuota.Status.Used != nil { + previousUsed = quota.Add(api.ResourceList{}, resourceQuota.Status.Used) + } + usage := api.ResourceQuota{ + ObjectMeta: api.ObjectMeta{ + Name: resourceQuota.Name, + Namespace: resourceQuota.Namespace, + ResourceVersion: resourceQuota.ResourceVersion, + Labels: resourceQuota.Labels, + Annotations: resourceQuota.Annotations}, + Status: api.ResourceQuotaStatus{ + Hard: quota.Add(api.ResourceList{}, resourceQuota.Spec.Hard), + Used: previousUsed, + }, + } + + // find the intersection between the hard resources on the quota + // and the resources this controller can track to know what we can + // look to measure updated usage stats for + hardResources := quota.ResourceNames(usage.Status.Hard) + potentialResources := []api.ResourceName{} + evaluators := rq.registry.Evaluators() + for _, evaluator := range evaluators { + potentialResources = append(potentialResources, evaluator.MatchesResources()...) + } + matchedResources := quota.Intersection(hardResources, potentialResources) + + // sum the observed usage from each evaluator + newUsage := api.ResourceList{} + usageStatsOptions := quota.UsageStatsOptions{Namespace: resourceQuota.Namespace, Scopes: resourceQuota.Spec.Scopes} + for _, evaluator := range evaluators { + stats, err := evaluator.UsageStats(usageStatsOptions) + if err != nil { + return err + } + newUsage = quota.Add(newUsage, stats.Used) + } + + // mask the observed usage to only the set of resources tracked by this quota + // merge our observed usage with the quota usage status + // if the new usage is different than the last usage, we will need to do an update + newUsage = quota.Mask(newUsage, matchedResources) + for key, value := range newUsage { + usage.Status.Used[key] = value + } + + dirty = dirty || !quota.Equals(usage.Status.Used, resourceQuota.Status.Used) + + // there was a change observed by this controller that requires we update quota + if dirty { + _, err = rq.kubeClient.Core().ResourceQuotas(usage.Namespace).UpdateStatus(&usage) + return err + } + return nil +} + +// replenishQuota is a replenishment function invoked by a controller to notify that a quota should be recalculated +func (rq *ResourceQuotaController) replenishQuota(groupKind unversioned.GroupKind, namespace string, object runtime.Object) { + // check if the quota controller can evaluate this kind, if not, ignore it altogether... + evaluators := rq.registry.Evaluators() + evaluator, found := evaluators[groupKind] + if !found { + return + } + + // check if this namespace even has a quota... + indexKey := &api.ResourceQuota{} + indexKey.Namespace = namespace + resourceQuotas, err := rq.rqIndexer.Index("namespace", indexKey) + if err != nil { + glog.Errorf("quota controller could not find ResourceQuota associated with namespace: %s, could take up to %v before a quota replenishes", namespace, rq.resyncPeriod()) + } + if len(resourceQuotas) == 0 { + return + } + + // only queue those quotas that are tracking a resource associated with this kind. + matchedResources := evaluator.MatchesResources() + for i := range resourceQuotas { + resourceQuota := resourceQuotas[i].(*api.ResourceQuota) + resourceQuotaResources := quota.ResourceNames(resourceQuota.Status.Hard) + if len(quota.Intersection(matchedResources, resourceQuotaResources)) > 0 { + // TODO: make this support targeted replenishment to a specific kind, right now it does a full recalc on that quota. + rq.enqueueResourceQuota(resourceQuota) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/resource_quota_controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/resource_quota_controller_test.go new file mode 100644 index 000000000..b65fb1922 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/resourcequota/resource_quota_controller_test.go @@ -0,0 +1,302 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 resourcequota + +import ( + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/unversioned/testclient" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/quota/install" + "k8s.io/kubernetes/pkg/util/sets" +) + +func getResourceList(cpu, memory string) api.ResourceList { + res := api.ResourceList{} + if cpu != "" { + res[api.ResourceCPU] = resource.MustParse(cpu) + } + if memory != "" { + res[api.ResourceMemory] = resource.MustParse(memory) + } + return res +} + +func getResourceRequirements(requests, limits api.ResourceList) api.ResourceRequirements { + res := api.ResourceRequirements{} + res.Requests = requests + res.Limits = limits + return res +} + +func TestSyncResourceQuota(t *testing.T) { + podList := api.PodList{ + Items: []api.Pod{ + { + ObjectMeta: api.ObjectMeta{Name: "pod-running", Namespace: "testing"}, + Status: api.PodStatus{Phase: api.PodRunning}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))}}, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "pod-running-2", Namespace: "testing"}, + Status: api.PodStatus{Phase: api.PodRunning}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))}}, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "pod-failed", Namespace: "testing"}, + Status: api.PodStatus{Phase: api.PodFailed}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "vol"}}, + Containers: []api.Container{{Name: "ctr", Image: "image", Resources: getResourceRequirements(getResourceList("100m", "1Gi"), getResourceList("", ""))}}, + }, + }, + }, + } + resourceQuota := api.ResourceQuota{ + ObjectMeta: api.ObjectMeta{Name: "quota", Namespace: "testing"}, + Spec: api.ResourceQuotaSpec{ + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("3"), + api.ResourceMemory: resource.MustParse("100Gi"), + api.ResourcePods: resource.MustParse("5"), + }, + }, + } + expectedUsage := api.ResourceQuota{ + Status: api.ResourceQuotaStatus{ + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("3"), + api.ResourceMemory: resource.MustParse("100Gi"), + api.ResourcePods: resource.MustParse("5"), + }, + Used: api.ResourceList{ + api.ResourceCPU: resource.MustParse("200m"), + api.ResourceMemory: resource.MustParse("2Gi"), + api.ResourcePods: resource.MustParse("2"), + }, + }, + } + + kubeClient := fake.NewSimpleClientset(&podList, &resourceQuota) + resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{ + KubeClient: kubeClient, + ResyncPeriod: controller.NoResyncPeriodFunc, + Registry: install.NewRegistry(kubeClient), + GroupKindsToReplenish: []unversioned.GroupKind{ + api.Kind("Pod"), + api.Kind("Service"), + api.Kind("ReplicationController"), + api.Kind("PersistentVolumeClaim"), + }, + ControllerFactory: NewReplenishmentControllerFactory(kubeClient), + ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc, + } + quotaController := NewResourceQuotaController(resourceQuotaControllerOptions) + err := quotaController.syncResourceQuota(resourceQuota) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + expectedActionSet := sets.NewString( + strings.Join([]string{"list", "replicationcontrollers", ""}, "-"), + strings.Join([]string{"list", "services", ""}, "-"), + strings.Join([]string{"list", "pods", ""}, "-"), + strings.Join([]string{"list", "resourcequotas", ""}, "-"), + strings.Join([]string{"list", "secrets", ""}, "-"), + strings.Join([]string{"list", "persistentvolumeclaims", ""}, "-"), + strings.Join([]string{"update", "resourcequotas", "status"}, "-"), + ) + actionSet := sets.NewString() + for _, action := range kubeClient.Actions() { + actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource(), action.GetSubresource()}, "-")) + } + if !actionSet.HasAll(expectedActionSet.List()...) { + t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet)) + } + + lastActionIndex := len(kubeClient.Actions()) - 1 + usage := kubeClient.Actions()[lastActionIndex].(testclient.UpdateAction).GetObject().(*api.ResourceQuota) + + // ensure hard and used limits are what we expected + for k, v := range expectedUsage.Status.Hard { + actual := usage.Status.Hard[k] + actualValue := actual.String() + expectedValue := v.String() + if expectedValue != actualValue { + t.Errorf("Usage Hard: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue) + } + } + for k, v := range expectedUsage.Status.Used { + actual := usage.Status.Used[k] + actualValue := actual.String() + expectedValue := v.String() + if expectedValue != actualValue { + t.Errorf("Usage Used: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue) + } + } +} + +func TestSyncResourceQuotaSpecChange(t *testing.T) { + resourceQuota := api.ResourceQuota{ + Spec: api.ResourceQuotaSpec{ + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("4"), + }, + }, + Status: api.ResourceQuotaStatus{ + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("3"), + }, + Used: api.ResourceList{ + api.ResourceCPU: resource.MustParse("0"), + }, + }, + } + + expectedUsage := api.ResourceQuota{ + Status: api.ResourceQuotaStatus{ + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("4"), + }, + Used: api.ResourceList{ + api.ResourceCPU: resource.MustParse("0"), + }, + }, + } + + kubeClient := fake.NewSimpleClientset(&resourceQuota) + resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{ + KubeClient: kubeClient, + ResyncPeriod: controller.NoResyncPeriodFunc, + Registry: install.NewRegistry(kubeClient), + GroupKindsToReplenish: []unversioned.GroupKind{ + api.Kind("Pod"), + api.Kind("Service"), + api.Kind("ReplicationController"), + api.Kind("PersistentVolumeClaim"), + }, + ControllerFactory: NewReplenishmentControllerFactory(kubeClient), + ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc, + } + quotaController := NewResourceQuotaController(resourceQuotaControllerOptions) + err := quotaController.syncResourceQuota(resourceQuota) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + + expectedActionSet := sets.NewString( + strings.Join([]string{"list", "replicationcontrollers", ""}, "-"), + strings.Join([]string{"list", "services", ""}, "-"), + strings.Join([]string{"list", "pods", ""}, "-"), + strings.Join([]string{"list", "resourcequotas", ""}, "-"), + strings.Join([]string{"list", "secrets", ""}, "-"), + strings.Join([]string{"list", "persistentvolumeclaims", ""}, "-"), + strings.Join([]string{"update", "resourcequotas", "status"}, "-"), + ) + actionSet := sets.NewString() + for _, action := range kubeClient.Actions() { + actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource(), action.GetSubresource()}, "-")) + } + if !actionSet.HasAll(expectedActionSet.List()...) { + t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet)) + } + + lastActionIndex := len(kubeClient.Actions()) - 1 + usage := kubeClient.Actions()[lastActionIndex].(testclient.UpdateAction).GetObject().(*api.ResourceQuota) + + // ensure hard and used limits are what we expected + for k, v := range expectedUsage.Status.Hard { + actual := usage.Status.Hard[k] + actualValue := actual.String() + expectedValue := v.String() + if expectedValue != actualValue { + t.Errorf("Usage Hard: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue) + } + } + for k, v := range expectedUsage.Status.Used { + actual := usage.Status.Used[k] + actualValue := actual.String() + expectedValue := v.String() + if expectedValue != actualValue { + t.Errorf("Usage Used: Key: %v, Expected: %v, Actual: %v", k, expectedValue, actualValue) + } + } + +} + +func TestSyncResourceQuotaNoChange(t *testing.T) { + resourceQuota := api.ResourceQuota{ + Spec: api.ResourceQuotaSpec{ + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("4"), + }, + }, + Status: api.ResourceQuotaStatus{ + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("4"), + }, + Used: api.ResourceList{ + api.ResourceCPU: resource.MustParse("0"), + }, + }, + } + + kubeClient := fake.NewSimpleClientset(&api.PodList{}, &resourceQuota) + resourceQuotaControllerOptions := &ResourceQuotaControllerOptions{ + KubeClient: kubeClient, + ResyncPeriod: controller.NoResyncPeriodFunc, + Registry: install.NewRegistry(kubeClient), + GroupKindsToReplenish: []unversioned.GroupKind{ + api.Kind("Pod"), + api.Kind("Service"), + api.Kind("ReplicationController"), + api.Kind("PersistentVolumeClaim"), + }, + ControllerFactory: NewReplenishmentControllerFactory(kubeClient), + ReplenishmentResyncPeriod: controller.NoResyncPeriodFunc, + } + quotaController := NewResourceQuotaController(resourceQuotaControllerOptions) + err := quotaController.syncResourceQuota(resourceQuota) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + expectedActionSet := sets.NewString( + strings.Join([]string{"list", "replicationcontrollers", ""}, "-"), + strings.Join([]string{"list", "services", ""}, "-"), + strings.Join([]string{"list", "pods", ""}, "-"), + strings.Join([]string{"list", "resourcequotas", ""}, "-"), + strings.Join([]string{"list", "secrets", ""}, "-"), + strings.Join([]string{"list", "persistentvolumeclaims", ""}, "-"), + ) + actionSet := sets.NewString() + for _, action := range kubeClient.Actions() { + actionSet.Insert(strings.Join([]string{action.GetVerb(), action.GetResource(), action.GetSubresource()}, "-")) + } + if !actionSet.HasAll(expectedActionSet.List()...) { + t.Errorf("Expected actions:\n%v\n but got:\n%v\nDifference:\n%v", expectedActionSet, actionSet, expectedActionSet.Difference(actionSet)) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/route/doc.go b/vendor/k8s.io/kubernetes/pkg/controller/route/doc.go new file mode 100644 index 000000000..bc4ae60a2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/route/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 route contains code for syncing cloud routing rules with +// the list of registered nodes. +package route diff --git a/vendor/k8s.io/kubernetes/pkg/controller/route/routecontroller.go b/vendor/k8s.io/kubernetes/pkg/controller/route/routecontroller.go new file mode 100644 index 000000000..c297347cc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/route/routecontroller.go @@ -0,0 +1,144 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 route + +import ( + "fmt" + "net" + "sync" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/util/wait" +) + +type RouteController struct { + routes cloudprovider.Routes + kubeClient clientset.Interface + clusterName string + clusterCIDR *net.IPNet +} + +func New(routes cloudprovider.Routes, kubeClient clientset.Interface, clusterName string, clusterCIDR *net.IPNet) *RouteController { + return &RouteController{ + routes: routes, + kubeClient: kubeClient, + clusterName: clusterName, + clusterCIDR: clusterCIDR, + } +} + +func (rc *RouteController) Run(syncPeriod time.Duration) { + go wait.Until(func() { + if err := rc.reconcileNodeRoutes(); err != nil { + glog.Errorf("Couldn't reconcile node routes: %v", err) + } + }, syncPeriod, wait.NeverStop) +} + +func (rc *RouteController) reconcileNodeRoutes() error { + routeList, err := rc.routes.ListRoutes(rc.clusterName) + if err != nil { + return fmt.Errorf("error listing routes: %v", err) + } + // TODO (cjcullen): use pkg/controller/framework.NewInformer to watch this + // and reduce the number of lists needed. + nodeList, err := rc.kubeClient.Core().Nodes().List(api.ListOptions{}) + if err != nil { + return fmt.Errorf("error listing nodes: %v", err) + } + return rc.reconcile(nodeList.Items, routeList) +} + +func (rc *RouteController) reconcile(nodes []api.Node, routes []*cloudprovider.Route) error { + // nodeCIDRs maps nodeName->nodeCIDR + nodeCIDRs := make(map[string]string) + // routeMap maps routeTargetInstance->route + routeMap := make(map[string]*cloudprovider.Route) + for _, route := range routes { + routeMap[route.TargetInstance] = route + } + wg := sync.WaitGroup{} + for _, node := range nodes { + // Skip if the node hasn't been assigned a CIDR yet. + if node.Spec.PodCIDR == "" { + continue + } + // Check if we have a route for this node w/ the correct CIDR. + r := routeMap[node.Name] + if r == nil || r.DestinationCIDR != node.Spec.PodCIDR { + // If not, create the route. + route := &cloudprovider.Route{ + TargetInstance: node.Name, + DestinationCIDR: node.Spec.PodCIDR, + } + nameHint := string(node.UID) + wg.Add(1) + glog.Infof("Creating route for node %s %s with hint %s", node.Name, route.DestinationCIDR, nameHint) + go func(nodeName string, nameHint string, route *cloudprovider.Route, startTime time.Time) { + if err := rc.routes.CreateRoute(rc.clusterName, nameHint, route); err != nil { + glog.Errorf("Could not create route %s %s for node %s after %v: %v", nameHint, route.DestinationCIDR, nodeName, time.Now().Sub(startTime), err) + } else { + glog.Infof("Created route for node %s %s with hint %s after %v", nodeName, route.DestinationCIDR, nameHint, time.Now().Sub(startTime)) + } + wg.Done() + }(node.Name, nameHint, route, time.Now()) + } + nodeCIDRs[node.Name] = node.Spec.PodCIDR + } + for _, route := range routes { + if rc.isResponsibleForRoute(route) { + // Check if this route applies to a node we know about & has correct CIDR. + if nodeCIDRs[route.TargetInstance] != route.DestinationCIDR { + wg.Add(1) + // Delete the route. + glog.Infof("Deleting route %s %s", route.Name, route.DestinationCIDR) + go func(route *cloudprovider.Route, startTime time.Time) { + if err := rc.routes.DeleteRoute(rc.clusterName, route); err != nil { + glog.Errorf("Could not delete route %s %s after %v: %v", route.Name, route.DestinationCIDR, time.Now().Sub(startTime), err) + } else { + glog.Infof("Deleted route %s %s after %v", route.Name, route.DestinationCIDR, time.Now().Sub(startTime)) + } + wg.Done() + + }(route, time.Now()) + } + } + } + wg.Wait() + return nil +} + +func (rc *RouteController) isResponsibleForRoute(route *cloudprovider.Route) bool { + _, cidr, err := net.ParseCIDR(route.DestinationCIDR) + if err != nil { + glog.Errorf("Ignoring route %s, unparsable CIDR: %v", route.Name, err) + return false + } + // Not responsible if this route's CIDR is not within our clusterCIDR + lastIP := make([]byte, len(cidr.IP)) + for i := range lastIP { + lastIP[i] = cidr.IP[i] | ^cidr.Mask[i] + } + if !rc.clusterCIDR.Contains(cidr.IP) || !rc.clusterCIDR.Contains(lastIP) { + return false + } + return true +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/route/routecontroller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/route/routecontroller_test.go new file mode 100644 index 000000000..3db2079f5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/route/routecontroller_test.go @@ -0,0 +1,219 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 route + +import ( + "net" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/cloudprovider" + fakecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/fake" +) + +func TestIsResponsibleForRoute(t *testing.T) { + myClusterName := "my-awesome-cluster" + myClusterRoute := "my-awesome-cluster-12345678-90ab-cdef-1234-567890abcdef" + testCases := []struct { + clusterCIDR string + routeName string + routeCIDR string + expectedResponsible bool + }{ + // Routes that belong to this cluster + {"10.244.0.0/16", myClusterRoute, "10.244.0.0/24", true}, + {"10.244.0.0/16", myClusterRoute, "10.244.10.0/24", true}, + {"10.244.0.0/16", myClusterRoute, "10.244.255.0/24", true}, + {"10.244.0.0/14", myClusterRoute, "10.244.0.0/24", true}, + {"10.244.0.0/14", myClusterRoute, "10.247.255.0/24", true}, + // Routes that match our naming/tagging scheme, but are outside our cidr + {"10.244.0.0/16", myClusterRoute, "10.224.0.0/24", false}, + {"10.244.0.0/16", myClusterRoute, "10.0.10.0/24", false}, + {"10.244.0.0/16", myClusterRoute, "10.255.255.0/24", false}, + {"10.244.0.0/14", myClusterRoute, "10.248.0.0/24", false}, + {"10.244.0.0/14", myClusterRoute, "10.243.255.0/24", false}, + } + for i, testCase := range testCases { + _, cidr, err := net.ParseCIDR(testCase.clusterCIDR) + if err != nil { + t.Errorf("%d. Error in test case: unparsable cidr %q", i, testCase.clusterCIDR) + } + rc := New(nil, nil, myClusterName, cidr) + route := &cloudprovider.Route{ + Name: testCase.routeName, + TargetInstance: "doesnt-matter-for-this-test", + DestinationCIDR: testCase.routeCIDR, + } + if resp := rc.isResponsibleForRoute(route); resp != testCase.expectedResponsible { + t.Errorf("%d. isResponsibleForRoute() = %t; want %t", i, resp, testCase.expectedResponsible) + } + } +} + +func TestReconcile(t *testing.T) { + cluster := "my-k8s" + testCases := []struct { + nodes []api.Node + initialRoutes []*cloudprovider.Route + expectedRoutes []*cloudprovider.Route + }{ + // 2 nodes, routes already there + { + nodes: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: "node-1", UID: "01"}, Spec: api.NodeSpec{PodCIDR: "10.120.0.0/24"}}, + {ObjectMeta: api.ObjectMeta{Name: "node-2", UID: "02"}, Spec: api.NodeSpec{PodCIDR: "10.120.1.0/24"}}, + }, + initialRoutes: []*cloudprovider.Route{ + {cluster + "-01", "node-1", "10.120.0.0/24"}, + {cluster + "-02", "node-2", "10.120.1.0/24"}, + }, + expectedRoutes: []*cloudprovider.Route{ + {cluster + "-01", "node-1", "10.120.0.0/24"}, + {cluster + "-02", "node-2", "10.120.1.0/24"}, + }, + }, + // 2 nodes, one route already there + { + nodes: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: "node-1", UID: "01"}, Spec: api.NodeSpec{PodCIDR: "10.120.0.0/24"}}, + {ObjectMeta: api.ObjectMeta{Name: "node-2", UID: "02"}, Spec: api.NodeSpec{PodCIDR: "10.120.1.0/24"}}, + }, + initialRoutes: []*cloudprovider.Route{ + {cluster + "-01", "node-1", "10.120.0.0/24"}, + }, + expectedRoutes: []*cloudprovider.Route{ + {cluster + "-01", "node-1", "10.120.0.0/24"}, + {cluster + "-02", "node-2", "10.120.1.0/24"}, + }, + }, + // 2 nodes, no routes yet + { + nodes: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: "node-1", UID: "01"}, Spec: api.NodeSpec{PodCIDR: "10.120.0.0/24"}}, + {ObjectMeta: api.ObjectMeta{Name: "node-2", UID: "02"}, Spec: api.NodeSpec{PodCIDR: "10.120.1.0/24"}}, + }, + initialRoutes: []*cloudprovider.Route{}, + expectedRoutes: []*cloudprovider.Route{ + {cluster + "-01", "node-1", "10.120.0.0/24"}, + {cluster + "-02", "node-2", "10.120.1.0/24"}, + }, + }, + // 2 nodes, a few too many routes + { + nodes: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: "node-1", UID: "01"}, Spec: api.NodeSpec{PodCIDR: "10.120.0.0/24"}}, + {ObjectMeta: api.ObjectMeta{Name: "node-2", UID: "02"}, Spec: api.NodeSpec{PodCIDR: "10.120.1.0/24"}}, + }, + initialRoutes: []*cloudprovider.Route{ + {cluster + "-01", "node-1", "10.120.0.0/24"}, + {cluster + "-02", "node-2", "10.120.1.0/24"}, + {cluster + "-03", "node-3", "10.120.2.0/24"}, + {cluster + "-04", "node-4", "10.120.3.0/24"}, + }, + expectedRoutes: []*cloudprovider.Route{ + {cluster + "-01", "node-1", "10.120.0.0/24"}, + {cluster + "-02", "node-2", "10.120.1.0/24"}, + }, + }, + // 2 nodes, 2 routes, but only 1 is right + { + nodes: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: "node-1", UID: "01"}, Spec: api.NodeSpec{PodCIDR: "10.120.0.0/24"}}, + {ObjectMeta: api.ObjectMeta{Name: "node-2", UID: "02"}, Spec: api.NodeSpec{PodCIDR: "10.120.1.0/24"}}, + }, + initialRoutes: []*cloudprovider.Route{ + {cluster + "-01", "node-1", "10.120.0.0/24"}, + {cluster + "-03", "node-3", "10.120.2.0/24"}, + }, + expectedRoutes: []*cloudprovider.Route{ + {cluster + "-01", "node-1", "10.120.0.0/24"}, + {cluster + "-02", "node-2", "10.120.1.0/24"}, + }, + }, + // 2 nodes, one node without CIDR assigned. + { + nodes: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: "node-1", UID: "01"}, Spec: api.NodeSpec{PodCIDR: "10.120.0.0/24"}}, + {ObjectMeta: api.ObjectMeta{Name: "node-2", UID: "02"}, Spec: api.NodeSpec{PodCIDR: ""}}, + }, + initialRoutes: []*cloudprovider.Route{}, + expectedRoutes: []*cloudprovider.Route{ + {cluster + "-01", "node-1", "10.120.0.0/24"}, + }, + }, + } + for i, testCase := range testCases { + cloud := &fakecloud.FakeCloud{RouteMap: make(map[string]*fakecloud.FakeRoute)} + for _, route := range testCase.initialRoutes { + fakeRoute := &fakecloud.FakeRoute{} + fakeRoute.ClusterName = cluster + fakeRoute.Route = *route + cloud.RouteMap[route.Name] = fakeRoute + } + routes, ok := cloud.Routes() + if !ok { + t.Error("Error in test: fakecloud doesn't support Routes()") + } + _, cidr, _ := net.ParseCIDR("10.120.0.0/16") + rc := New(routes, nil, cluster, cidr) + if err := rc.reconcile(testCase.nodes, testCase.initialRoutes); err != nil { + t.Errorf("%d. Error from rc.reconcile(): %v", i, err) + } + var finalRoutes []*cloudprovider.Route + var err error + timeoutChan := time.After(200 * time.Millisecond) + tick := time.NewTicker(10 * time.Millisecond) + defer tick.Stop() + poll: + for { + select { + case <-tick.C: + if finalRoutes, err = routes.ListRoutes(cluster); err == nil && routeListEqual(finalRoutes, testCase.expectedRoutes) { + break poll + } + case <-timeoutChan: + t.Errorf("%d. rc.reconcile() = %v, routes:\n%v\nexpected: nil, routes:\n%v\n", i, err, flatten(finalRoutes), flatten(testCase.expectedRoutes)) + break poll + } + } + } +} + +func routeListEqual(list1, list2 []*cloudprovider.Route) bool { + if len(list1) != len(list2) { + return false + } + routeMap1 := make(map[string]*cloudprovider.Route) + for _, route1 := range list1 { + routeMap1[route1.Name] = route1 + } + for _, route2 := range list2 { + if route1, exists := routeMap1[route2.Name]; !exists || *route1 != *route2 { + return false + } + } + return true +} + +func flatten(list []*cloudprovider.Route) []cloudprovider.Route { + var structList []cloudprovider.Route + for _, route := range list { + structList = append(structList, *route) + } + return structList +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/service/doc.go b/vendor/k8s.io/kubernetes/pkg/controller/service/doc.go new file mode 100644 index 000000000..78c20eb96 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/service/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 service contains code for syncing cloud load balancers +// with the service registry. +package service diff --git a/vendor/k8s.io/kubernetes/pkg/controller/service/servicecontroller.go b/vendor/k8s.io/kubernetes/pkg/controller/service/servicecontroller.go new file mode 100644 index 000000000..1f50a40e3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/service/servicecontroller.go @@ -0,0 +1,764 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 service + +import ( + "fmt" + "sort" + "sync" + "time" + + "reflect" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + unversioned_core "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/runtime" +) + +const ( + workerGoroutines = 10 + + // How long to wait before retrying the processing of a service change. + // If this changes, the sleep in hack/jenkins/e2e.sh before downing a cluster + // should be changed appropriately. + minRetryDelay = 5 * time.Second + maxRetryDelay = 300 * time.Second + + clientRetryCount = 5 + clientRetryInterval = 5 * time.Second + + retryable = true + notRetryable = false + + doNotRetry = time.Duration(0) +) + +type cachedService struct { + // The last-known state of the service + lastState *api.Service + // The state as successfully applied to the load balancer + appliedState *api.Service + + // Ensures only one goroutine can operate on this service at any given time. + mu sync.Mutex + + // Controls error back-off + lastRetryDelay time.Duration +} + +type serviceCache struct { + mu sync.Mutex // protects serviceMap + serviceMap map[string]*cachedService +} + +type ServiceController struct { + cloud cloudprovider.Interface + kubeClient clientset.Interface + clusterName string + balancer cloudprovider.LoadBalancer + zone cloudprovider.Zone + cache *serviceCache + eventBroadcaster record.EventBroadcaster + eventRecorder record.EventRecorder + nodeLister cache.StoreToNodeLister +} + +// New returns a new service controller to keep cloud provider service resources +// (like load balancers) in sync with the registry. +func New(cloud cloudprovider.Interface, kubeClient clientset.Interface, clusterName string) *ServiceController { + broadcaster := record.NewBroadcaster() + broadcaster.StartRecordingToSink(&unversioned_core.EventSinkImpl{Interface: kubeClient.Core().Events("")}) + recorder := broadcaster.NewRecorder(api.EventSource{Component: "service-controller"}) + + return &ServiceController{ + cloud: cloud, + kubeClient: kubeClient, + clusterName: clusterName, + cache: &serviceCache{serviceMap: make(map[string]*cachedService)}, + eventBroadcaster: broadcaster, + eventRecorder: recorder, + nodeLister: cache.StoreToNodeLister{ + Store: cache.NewStore(cache.MetaNamespaceKeyFunc), + }, + } +} + +// Run starts a background goroutine that watches for changes to services that +// have (or had) LoadBalancers=true and ensures that they have +// load balancers created and deleted appropriately. +// serviceSyncPeriod controls how often we check the cluster's services to +// ensure that the correct load balancers exist. +// nodeSyncPeriod controls how often we check the cluster's nodes to determine +// if load balancers need to be updated to point to a new set. +// +// It's an error to call Run() more than once for a given ServiceController +// object. +func (s *ServiceController) Run(serviceSyncPeriod, nodeSyncPeriod time.Duration) error { + if err := s.init(); err != nil { + return err + } + + // We have to make this check beecause the ListWatch that we use in + // WatchServices requires Client functions that aren't in the interface + // for some reason. + if _, ok := s.kubeClient.(*clientset.Clientset); !ok { + return fmt.Errorf("ServiceController only works with real Client objects, but was passed something else satisfying the clientset.Interface.") + } + + // Get the currently existing set of services and then all future creates + // and updates of services. + // A delta compressor is needed for the DeltaFIFO queue because we only ever + // care about the most recent state. + serviceQueue := cache.NewDeltaFIFO( + cache.MetaNamespaceKeyFunc, + cache.DeltaCompressorFunc(func(d cache.Deltas) cache.Deltas { + if len(d) == 0 { + return d + } + return cache.Deltas{*d.Newest()} + }), + s.cache, + ) + lw := cache.NewListWatchFromClient(s.kubeClient.(*clientset.Clientset).CoreClient, "services", api.NamespaceAll, fields.Everything()) + cache.NewReflector(lw, &api.Service{}, serviceQueue, serviceSyncPeriod).Run() + for i := 0; i < workerGoroutines; i++ { + go s.watchServices(serviceQueue) + } + + nodeLW := cache.NewListWatchFromClient(s.kubeClient.(*clientset.Clientset).CoreClient, "nodes", api.NamespaceAll, fields.Everything()) + cache.NewReflector(nodeLW, &api.Node{}, s.nodeLister.Store, 0).Run() + go s.nodeSyncLoop(nodeSyncPeriod) + return nil +} + +func (s *ServiceController) init() error { + if s.cloud == nil { + return fmt.Errorf("ServiceController should not be run without a cloudprovider.") + } + + balancer, ok := s.cloud.LoadBalancer() + if !ok { + return fmt.Errorf("the cloud provider does not support external load balancers.") + } + s.balancer = balancer + + zones, ok := s.cloud.Zones() + if !ok { + return fmt.Errorf("the cloud provider does not support zone enumeration, which is required for creating load balancers.") + } + zone, err := zones.GetZone() + if err != nil { + return fmt.Errorf("failed to get zone from cloud provider, will not be able to create load balancers: %v", err) + } + s.zone = zone + return nil +} + +// Loop infinitely, processing all service updates provided by the queue. +func (s *ServiceController) watchServices(serviceQueue *cache.DeltaFIFO) { + for { + newItem := serviceQueue.Pop() + deltas, ok := newItem.(cache.Deltas) + if !ok { + glog.Errorf("Received object from service watcher that wasn't Deltas: %+v", newItem) + } + delta := deltas.Newest() + if delta == nil { + glog.Errorf("Received nil delta from watcher queue.") + continue + } + err, retryDelay := s.processDelta(delta) + if retryDelay != 0 { + // Add the failed service back to the queue so we'll retry it. + glog.Errorf("Failed to process service delta. Retrying in %s: %v", retryDelay, err) + go func(deltas cache.Deltas, delay time.Duration) { + time.Sleep(delay) + if err := serviceQueue.AddIfNotPresent(deltas); err != nil { + glog.Errorf("Error requeuing service delta - will not retry: %v", err) + } + }(deltas, retryDelay) + } else if err != nil { + runtime.HandleError(fmt.Errorf("Failed to process service delta. Not retrying: %v", err)) + } + } +} + +// Returns an error if processing the delta failed, along with a time.Duration +// indicating whether processing should be retried; zero means no-retry; otherwise +// we should retry in that Duration. +func (s *ServiceController) processDelta(delta *cache.Delta) (error, time.Duration) { + deltaService, ok := delta.Object.(*api.Service) + var namespacedName types.NamespacedName + var cachedService *cachedService + if !ok { + // If the DeltaFIFO saw a key in our cache that it didn't know about, it + // can send a deletion with an unknown state. Grab the service from our + // cache for deleting. + key, ok := delta.Object.(cache.DeletedFinalStateUnknown) + if !ok { + return fmt.Errorf("Delta contained object that wasn't a service or a deleted key: %+v", delta), doNotRetry + } + cachedService, ok = s.cache.get(key.Key) + if !ok { + return fmt.Errorf("Service %s not in cache even though the watcher thought it was. Ignoring the deletion.", key), doNotRetry + } + deltaService = cachedService.lastState + delta.Object = deltaService + namespacedName = types.NamespacedName{Namespace: deltaService.Namespace, Name: deltaService.Name} + } else { + namespacedName.Namespace = deltaService.Namespace + namespacedName.Name = deltaService.Name + cachedService = s.cache.getOrCreate(namespacedName.String()) + } + glog.V(2).Infof("Got new %s delta for service: %+v", delta.Type, deltaService) + + // Ensure that no other goroutine will interfere with our processing of the + // service. + cachedService.mu.Lock() + defer cachedService.mu.Unlock() + + // Get the most recent state of the service from the API directly rather than + // trusting the body of the delta. This avoids update re-ordering problems. + // TODO: Handle sync delta types differently rather than doing a get on every + // service every time we sync? + service, err := s.kubeClient.Core().Services(namespacedName.Namespace).Get(namespacedName.Name) + if err != nil && !errors.IsNotFound(err) { + glog.Warningf("Failed to get most recent state of service %v from API (will retry): %v", namespacedName, err) + return err, cachedService.nextRetryDelay() + } else if errors.IsNotFound(err) { + glog.V(2).Infof("Service %v not found, ensuring load balancer is deleted", namespacedName) + s.eventRecorder.Event(service, api.EventTypeNormal, "DeletingLoadBalancer", "Deleting load balancer") + err := s.balancer.EnsureLoadBalancerDeleted(deltaService) + if err != nil { + message := "Error deleting load balancer (will retry): " + err.Error() + s.eventRecorder.Event(deltaService, api.EventTypeWarning, "DeletingLoadBalancerFailed", message) + return err, cachedService.nextRetryDelay() + } + s.eventRecorder.Event(deltaService, api.EventTypeNormal, "DeletedLoadBalancer", "Deleted load balancer") + s.cache.delete(namespacedName.String()) + + cachedService.resetRetryDelay() + return nil, doNotRetry + } + + // Update the cached service (used above for populating synthetic deletes) + cachedService.lastState = service + + err, retry := s.createLoadBalancerIfNeeded(namespacedName, service, cachedService.appliedState) + if err != nil { + message := "Error creating load balancer" + if retry { + message += " (will retry): " + } else { + message += " (will not retry): " + } + message += err.Error() + s.eventRecorder.Event(service, api.EventTypeWarning, "CreatingLoadBalancerFailed", message) + + return err, cachedService.nextRetryDelay() + } + // Always update the cache upon success. + // NOTE: Since we update the cached service if and only if we successfully + // processed it, a cached service being nil implies that it hasn't yet + // been successfully processed. + cachedService.appliedState = service + s.cache.set(namespacedName.String(), cachedService) + + cachedService.resetRetryDelay() + return nil, doNotRetry +} + +// Returns whatever error occurred along with a boolean indicator of whether it +// should be retried. +func (s *ServiceController) createLoadBalancerIfNeeded(namespacedName types.NamespacedName, service, appliedState *api.Service) (error, bool) { + if appliedState != nil && !s.needsUpdate(appliedState, service) { + glog.Infof("LB doesn't need update for service %s", namespacedName) + return nil, notRetryable + } + + // Note: It is safe to just call EnsureLoadBalancer. But, on some clouds that requires a delete & create, + // which may involve service interruption. Also, we would like user-friendly events. + + // Save the state so we can avoid a write if it doesn't change + previousState := api.LoadBalancerStatusDeepCopy(&service.Status.LoadBalancer) + + if !wantsLoadBalancer(service) { + needDelete := true + if appliedState != nil { + if !wantsLoadBalancer(appliedState) { + needDelete = false + } + } else { + // If we don't have any cached memory of the load balancer, we have to ask + // the cloud provider for what it knows about it. + // Technically EnsureLoadBalancerDeleted can cope, but we want to post meaningful events + _, exists, err := s.balancer.GetLoadBalancer(service) + if err != nil { + return fmt.Errorf("Error getting LB for service %s: %v", namespacedName, err), retryable + } + if !exists { + needDelete = false + } + } + + if needDelete { + glog.Infof("Deleting existing load balancer for service %s that no longer needs a load balancer.", namespacedName) + s.eventRecorder.Event(service, api.EventTypeNormal, "DeletingLoadBalancer", "Deleting load balancer") + if err := s.balancer.EnsureLoadBalancerDeleted(service); err != nil { + return err, retryable + } + s.eventRecorder.Event(service, api.EventTypeNormal, "DeletedLoadBalancer", "Deleted load balancer") + } + + service.Status.LoadBalancer = api.LoadBalancerStatus{} + } else { + glog.V(2).Infof("Ensuring LB for service %s", namespacedName) + + // TODO: We could do a dry-run here if wanted to avoid the spurious cloud-calls & events when we restart + + // The load balancer doesn't exist yet, so create it. + s.eventRecorder.Event(service, api.EventTypeNormal, "CreatingLoadBalancer", "Creating load balancer") + err := s.createLoadBalancer(service) + if err != nil { + return fmt.Errorf("Failed to create load balancer for service %s: %v", namespacedName, err), retryable + } + s.eventRecorder.Event(service, api.EventTypeNormal, "CreatedLoadBalancer", "Created load balancer") + } + + // Write the state if changed + // TODO: Be careful here ... what if there were other changes to the service? + if !api.LoadBalancerStatusEqual(previousState, &service.Status.LoadBalancer) { + if err := s.persistUpdate(service); err != nil { + return fmt.Errorf("Failed to persist updated status to apiserver, even after retries. Giving up: %v", err), notRetryable + } + } else { + glog.V(2).Infof("Not persisting unchanged LoadBalancerStatus to registry.") + } + + return nil, notRetryable +} + +func (s *ServiceController) persistUpdate(service *api.Service) error { + var err error + for i := 0; i < clientRetryCount; i++ { + _, err = s.kubeClient.Core().Services(service.Namespace).UpdateStatus(service) + if err == nil { + return nil + } + // If the object no longer exists, we don't want to recreate it. Just bail + // out so that we can process the delete, which we should soon be receiving + // if we haven't already. + if errors.IsNotFound(err) { + glog.Infof("Not persisting update to service '%s/%s' that no longer exists: %v", + service.Namespace, service.Name, err) + return nil + } + // TODO: Try to resolve the conflict if the change was unrelated to load + // balancer status. For now, just rely on the fact that we'll + // also process the update that caused the resource version to change. + if errors.IsConflict(err) { + glog.V(4).Infof("Not persisting update to service '%s/%s' that has been changed since we received it: %v", + service.Namespace, service.Name, err) + return nil + } + glog.Warningf("Failed to persist updated LoadBalancerStatus to service '%s/%s' after creating its load balancer: %v", + service.Namespace, service.Name, err) + time.Sleep(clientRetryInterval) + } + return err +} + +func (s *ServiceController) createLoadBalancer(service *api.Service) error { + nodes, err := s.nodeLister.List() + if err != nil { + return err + } + + // - Only one protocol supported per service + // - Not all cloud providers support all protocols and the next step is expected to return + // an error for unsupported protocols + status, err := s.balancer.EnsureLoadBalancer(service, hostsFromNodeList(&nodes), service.ObjectMeta.Annotations) + if err != nil { + return err + } else { + service.Status.LoadBalancer = *status + } + + return nil +} + +// ListKeys implements the interface required by DeltaFIFO to list the keys we +// already know about. +func (s *serviceCache) ListKeys() []string { + s.mu.Lock() + defer s.mu.Unlock() + keys := make([]string, 0, len(s.serviceMap)) + for k := range s.serviceMap { + keys = append(keys, k) + } + return keys +} + +// GetByKey returns the value stored in the serviceMap under the given key +func (s *serviceCache) GetByKey(key string) (interface{}, bool, error) { + s.mu.Lock() + defer s.mu.Unlock() + if v, ok := s.serviceMap[key]; ok { + return v, true, nil + } + return nil, false, nil +} + +// ListKeys implements the interface required by DeltaFIFO to list the keys we +// already know about. +func (s *serviceCache) allServices() []*cachedService { + s.mu.Lock() + defer s.mu.Unlock() + services := make([]*cachedService, 0, len(s.serviceMap)) + for _, v := range s.serviceMap { + services = append(services, v) + } + return services +} + +func (s *serviceCache) get(serviceName string) (*cachedService, bool) { + s.mu.Lock() + defer s.mu.Unlock() + service, ok := s.serviceMap[serviceName] + return service, ok +} + +func (s *serviceCache) getOrCreate(serviceName string) *cachedService { + s.mu.Lock() + defer s.mu.Unlock() + service, ok := s.serviceMap[serviceName] + if !ok { + service = &cachedService{} + s.serviceMap[serviceName] = service + } + return service +} + +func (s *serviceCache) set(serviceName string, service *cachedService) { + s.mu.Lock() + defer s.mu.Unlock() + s.serviceMap[serviceName] = service +} + +func (s *serviceCache) delete(serviceName string) { + s.mu.Lock() + defer s.mu.Unlock() + delete(s.serviceMap, serviceName) +} + +func (s *ServiceController) needsUpdate(oldService *api.Service, newService *api.Service) bool { + if !wantsLoadBalancer(oldService) && !wantsLoadBalancer(newService) { + return false + } + if wantsLoadBalancer(oldService) != wantsLoadBalancer(newService) { + s.eventRecorder.Eventf(newService, api.EventTypeNormal, "Type", "%v -> %v", + oldService.Spec.Type, newService.Spec.Type) + return true + } + if !portsEqualForLB(oldService, newService) || oldService.Spec.SessionAffinity != newService.Spec.SessionAffinity { + return true + } + if !loadBalancerIPsAreEqual(oldService, newService) { + s.eventRecorder.Eventf(newService, api.EventTypeNormal, "LoadbalancerIP", "%v -> %v", + oldService.Spec.LoadBalancerIP, newService.Spec.LoadBalancerIP) + return true + } + if len(oldService.Spec.ExternalIPs) != len(newService.Spec.ExternalIPs) { + s.eventRecorder.Eventf(newService, api.EventTypeNormal, "ExternalIP", "Count: %v -> %v", + len(oldService.Spec.ExternalIPs), len(newService.Spec.ExternalIPs)) + return true + } + for i := range oldService.Spec.ExternalIPs { + if oldService.Spec.ExternalIPs[i] != newService.Spec.ExternalIPs[i] { + s.eventRecorder.Eventf(newService, api.EventTypeNormal, "ExternalIP", "Added: %v", + newService.Spec.ExternalIPs[i]) + return true + } + } + if !reflect.DeepEqual(oldService.Annotations, newService.Annotations) { + return true + } + if oldService.UID != newService.UID { + s.eventRecorder.Eventf(newService, api.EventTypeNormal, "UID", "%v -> %v", + oldService.UID, newService.UID) + return true + } + + return false +} + +func (s *ServiceController) loadBalancerName(service *api.Service) string { + return cloudprovider.GetLoadBalancerName(service) +} + +func getPortsForLB(service *api.Service) ([]*api.ServicePort, error) { + var protocol api.Protocol + + ports := []*api.ServicePort{} + for i := range service.Spec.Ports { + sp := &service.Spec.Ports[i] + // The check on protocol was removed here. The cloud provider itself is now responsible for all protocol validation + ports = append(ports, sp) + if protocol == "" { + protocol = sp.Protocol + } else if protocol != sp.Protocol && wantsLoadBalancer(service) { + // TODO: Convert error messages to use event recorder + return nil, fmt.Errorf("mixed protocol external load balancers are not supported.") + } + } + return ports, nil +} + +func portsEqualForLB(x, y *api.Service) bool { + xPorts, err := getPortsForLB(x) + if err != nil { + return false + } + yPorts, err := getPortsForLB(y) + if err != nil { + return false + } + return portSlicesEqualForLB(xPorts, yPorts) +} + +func portSlicesEqualForLB(x, y []*api.ServicePort) bool { + if len(x) != len(y) { + return false + } + + for i := range x { + if !portEqualForLB(x[i], y[i]) { + return false + } + } + return true +} + +func portEqualForLB(x, y *api.ServicePort) bool { + // TODO: Should we check name? (In theory, an LB could expose it) + if x.Name != y.Name { + return false + } + + if x.Protocol != y.Protocol { + return false + } + + if x.Port != y.Port { + return false + } + + if x.NodePort != y.NodePort { + return false + } + + // We don't check TargetPort; that is not relevant for load balancing + // TODO: Should we blank it out? Or just check it anyway? + + return true +} + +func intSlicesEqual(x, y []int) bool { + if len(x) != len(y) { + return false + } + if !sort.IntsAreSorted(x) { + sort.Ints(x) + } + if !sort.IntsAreSorted(y) { + sort.Ints(y) + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true +} + +func stringSlicesEqual(x, y []string) bool { + if len(x) != len(y) { + return false + } + if !sort.StringsAreSorted(x) { + sort.Strings(x) + } + if !sort.StringsAreSorted(y) { + sort.Strings(y) + } + for i := range x { + if x[i] != y[i] { + return false + } + } + return true +} + +func hostsFromNodeList(list *api.NodeList) []string { + result := []string{} + for ix := range list.Items { + if list.Items[ix].Spec.Unschedulable { + continue + } + result = append(result, list.Items[ix].Name) + } + return result +} + +func getNodeConditionPredicate() cache.NodeConditionPredicate { + return func(node api.Node) bool { + // We add the master to the node list, but its unschedulable. So we use this to filter + // the master. + // TODO: Use a node annotation to indicate the master + if node.Spec.Unschedulable { + return false + } + // If we have no info, don't accept + if len(node.Status.Conditions) == 0 { + return false + } + for _, cond := range node.Status.Conditions { + // We consider the node for load balancing only when its NodeReady condition status + // is ConditionTrue + if cond.Type == api.NodeReady && cond.Status != api.ConditionTrue { + glog.V(4).Infof("Ignoring node %v with %v condition status %v", node.Name, cond.Type, cond.Status) + return false + } + } + return true + } +} + +// nodeSyncLoop handles updating the hosts pointed to by all load +// balancers whenever the set of nodes in the cluster changes. +func (s *ServiceController) nodeSyncLoop(period time.Duration) { + var prevHosts []string + var servicesToUpdate []*cachedService + for range time.Tick(period) { + nodes, err := s.nodeLister.NodeCondition(getNodeConditionPredicate()).List() + if err != nil { + glog.Errorf("Failed to retrieve current set of nodes from node lister: %v", err) + continue + } + newHosts := hostsFromNodeList(&nodes) + if stringSlicesEqual(newHosts, prevHosts) { + // The set of nodes in the cluster hasn't changed, but we can retry + // updating any services that we failed to update last time around. + servicesToUpdate = s.updateLoadBalancerHosts(servicesToUpdate, newHosts) + continue + } + glog.Infof("Detected change in list of current cluster nodes. New node set: %v", newHosts) + + // Try updating all services, and save the ones that fail to try again next + // round. + servicesToUpdate = s.cache.allServices() + numServices := len(servicesToUpdate) + servicesToUpdate = s.updateLoadBalancerHosts(servicesToUpdate, newHosts) + glog.Infof("Successfully updated %d out of %d load balancers to direct traffic to the updated set of nodes", + numServices-len(servicesToUpdate), numServices) + + prevHosts = newHosts + } +} + +// updateLoadBalancerHosts updates all existing load balancers so that +// they will match the list of hosts provided. +// Returns the list of services that couldn't be updated. +func (s *ServiceController) updateLoadBalancerHosts(services []*cachedService, hosts []string) (servicesToRetry []*cachedService) { + for _, service := range services { + func() { + service.mu.Lock() + defer service.mu.Unlock() + // If the applied state is nil, that means it hasn't yet been successfully dealt + // with by the load balancer reconciler. We can trust the load balancer + // reconciler to ensure the service's load balancer is created to target + // the correct nodes. + if service.appliedState == nil { + return + } + if err := s.lockedUpdateLoadBalancerHosts(service.appliedState, hosts); err != nil { + glog.Errorf("External error while updating load balancer: %v.", err) + servicesToRetry = append(servicesToRetry, service) + } + }() + } + return servicesToRetry +} + +// Updates the load balancer of a service, assuming we hold the mutex +// associated with the service. +func (s *ServiceController) lockedUpdateLoadBalancerHosts(service *api.Service, hosts []string) error { + if !wantsLoadBalancer(service) { + return nil + } + + // This operation doesn't normally take very long (and happens pretty often), so we only record the final event + err := s.balancer.UpdateLoadBalancer(service, hosts) + if err == nil { + s.eventRecorder.Event(service, api.EventTypeNormal, "UpdatedLoadBalancer", "Updated load balancer with new hosts") + return nil + } + + // It's only an actual error if the load balancer still exists. + if _, exists, err := s.balancer.GetLoadBalancer(service); err != nil { + glog.Errorf("External error while checking if load balancer %q exists: name, %v", cloudprovider.GetLoadBalancerName(service), err) + } else if !exists { + return nil + } + + s.eventRecorder.Eventf(service, api.EventTypeWarning, "LoadBalancerUpdateFailed", "Error updating load balancer with new hosts %v: %v", hosts, err) + return err +} + +func wantsLoadBalancer(service *api.Service) bool { + return service.Spec.Type == api.ServiceTypeLoadBalancer +} + +func loadBalancerIPsAreEqual(oldService, newService *api.Service) bool { + return oldService.Spec.LoadBalancerIP == newService.Spec.LoadBalancerIP +} + +// Computes the next retry, using exponential backoff +// mutex must be held. +func (s *cachedService) nextRetryDelay() time.Duration { + s.lastRetryDelay = s.lastRetryDelay * 2 + if s.lastRetryDelay < minRetryDelay { + s.lastRetryDelay = minRetryDelay + } + if s.lastRetryDelay > maxRetryDelay { + s.lastRetryDelay = maxRetryDelay + } + return s.lastRetryDelay +} + +// Resets the retry exponential backoff. mutex must be held. +func (s *cachedService) resetRetryDelay() { + s.lastRetryDelay = time.Duration(0) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/service/servicecontroller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/service/servicecontroller_test.go new file mode 100644 index 000000000..a696be1c5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/service/servicecontroller_test.go @@ -0,0 +1,329 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 service + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + fakecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/fake" + "k8s.io/kubernetes/pkg/types" +) + +const region = "us-central" + +func newService(name string, uid types.UID, serviceType api.ServiceType) *api.Service { + return &api.Service{ObjectMeta: api.ObjectMeta{Name: name, Namespace: "namespace", UID: uid}, Spec: api.ServiceSpec{Type: serviceType}} +} + +func TestCreateExternalLoadBalancer(t *testing.T) { + table := []struct { + service *api.Service + expectErr bool + expectCreateAttempt bool + }{ + { + service: &api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "no-external-balancer", + Namespace: "default", + }, + Spec: api.ServiceSpec{ + Type: api.ServiceTypeClusterIP, + }, + }, + expectErr: false, + expectCreateAttempt: false, + }, + { + service: &api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "udp-service", + Namespace: "default", + }, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{{ + Port: 80, + Protocol: api.ProtocolUDP, + }}, + Type: api.ServiceTypeLoadBalancer, + }, + }, + expectErr: false, + expectCreateAttempt: true, + }, + { + service: &api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "basic-service1", + Namespace: "default", + }, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{{ + Port: 80, + Protocol: api.ProtocolTCP, + }}, + Type: api.ServiceTypeLoadBalancer, + }, + }, + expectErr: false, + expectCreateAttempt: true, + }, + } + + for _, item := range table { + cloud := &fakecloud.FakeCloud{} + cloud.Region = region + client := &fake.Clientset{} + controller := New(cloud, client, "test-cluster") + controller.init() + cloud.Calls = nil // ignore any cloud calls made in init() + client.ClearActions() // ignore any client calls made in init() + err, _ := controller.createLoadBalancerIfNeeded(types.NamespacedName{Namespace: "foo", Name: "bar"}, item.service, nil) + if !item.expectErr && err != nil { + t.Errorf("unexpected error: %v", err) + } else if item.expectErr && err == nil { + t.Errorf("expected error creating %v, got nil", item.service) + } + actions := client.Actions() + if !item.expectCreateAttempt { + if len(cloud.Calls) > 0 { + t.Errorf("unexpected cloud provider calls: %v", cloud.Calls) + } + if len(actions) > 0 { + t.Errorf("unexpected client actions: %v", actions) + } + } else { + var balancer *fakecloud.FakeBalancer + for k := range cloud.Balancers { + if balancer == nil { + b := cloud.Balancers[k] + balancer = &b + } else { + t.Errorf("expected one load balancer to be created, got %v", cloud.Balancers) + break + } + } + if balancer == nil { + t.Errorf("expected one load balancer to be created, got none") + } else if balancer.Name != controller.loadBalancerName(item.service) || + balancer.Region != region || + balancer.Ports[0].Port != item.service.Spec.Ports[0].Port { + t.Errorf("created load balancer has incorrect parameters: %v", balancer) + } + actionFound := false + for _, action := range actions { + if action.GetVerb() == "update" && action.GetResource() == "services" { + actionFound = true + } + } + if !actionFound { + t.Errorf("expected updated service to be sent to client, got these actions instead: %v", actions) + } + } + } +} + +// TODO: Finish converting and update comments +func TestUpdateNodesInExternalLoadBalancer(t *testing.T) { + hosts := []string{"node0", "node1", "node73"} + table := []struct { + services []*api.Service + expectedUpdateCalls []fakecloud.FakeUpdateBalancerCall + }{ + { + // No services present: no calls should be made. + services: []*api.Service{}, + expectedUpdateCalls: nil, + }, + { + // Services do not have external load balancers: no calls should be made. + services: []*api.Service{ + newService("s0", "111", api.ServiceTypeClusterIP), + newService("s1", "222", api.ServiceTypeNodePort), + }, + expectedUpdateCalls: nil, + }, + { + // Services does have an external load balancer: one call should be made. + services: []*api.Service{ + newService("s0", "333", api.ServiceTypeLoadBalancer), + }, + expectedUpdateCalls: []fakecloud.FakeUpdateBalancerCall{ + {newService("s0", "333", api.ServiceTypeLoadBalancer), hosts}, + }, + }, + { + // Three services have an external load balancer: three calls. + services: []*api.Service{ + newService("s0", "444", api.ServiceTypeLoadBalancer), + newService("s1", "555", api.ServiceTypeLoadBalancer), + newService("s2", "666", api.ServiceTypeLoadBalancer), + }, + expectedUpdateCalls: []fakecloud.FakeUpdateBalancerCall{ + {newService("s0", "444", api.ServiceTypeLoadBalancer), hosts}, + {newService("s1", "555", api.ServiceTypeLoadBalancer), hosts}, + {newService("s2", "666", api.ServiceTypeLoadBalancer), hosts}, + }, + }, + { + // Two services have an external load balancer and two don't: two calls. + services: []*api.Service{ + newService("s0", "777", api.ServiceTypeNodePort), + newService("s1", "888", api.ServiceTypeLoadBalancer), + newService("s3", "999", api.ServiceTypeLoadBalancer), + newService("s4", "123", api.ServiceTypeClusterIP), + }, + expectedUpdateCalls: []fakecloud.FakeUpdateBalancerCall{ + {newService("s1", "888", api.ServiceTypeLoadBalancer), hosts}, + {newService("s3", "999", api.ServiceTypeLoadBalancer), hosts}, + }, + }, + { + // One service has an external load balancer and one is nil: one call. + services: []*api.Service{ + newService("s0", "234", api.ServiceTypeLoadBalancer), + nil, + }, + expectedUpdateCalls: []fakecloud.FakeUpdateBalancerCall{ + {newService("s0", "234", api.ServiceTypeLoadBalancer), hosts}, + }, + }, + } + for _, item := range table { + cloud := &fakecloud.FakeCloud{} + + cloud.Region = region + client := &fake.Clientset{} + controller := New(cloud, client, "test-cluster2") + controller.init() + cloud.Calls = nil // ignore any cloud calls made in init() + + var services []*cachedService + for _, service := range item.services { + services = append(services, &cachedService{lastState: service, appliedState: service}) + } + if err := controller.updateLoadBalancerHosts(services, hosts); err != nil { + t.Errorf("unexpected error: %v", err) + } + if !reflect.DeepEqual(item.expectedUpdateCalls, cloud.UpdateCalls) { + t.Errorf("expected update calls mismatch, expected %+v, got %+v", item.expectedUpdateCalls, cloud.UpdateCalls) + } + } +} + +func TestHostsFromNodeList(t *testing.T) { + tests := []struct { + nodes *api.NodeList + expectedHosts []string + }{ + { + nodes: &api.NodeList{}, + expectedHosts: []string{}, + }, + { + nodes: &api.NodeList{ + Items: []api.Node{ + { + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Status: api.NodeStatus{Phase: api.NodeRunning}, + }, + { + ObjectMeta: api.ObjectMeta{Name: "bar"}, + Status: api.NodeStatus{Phase: api.NodeRunning}, + }, + }, + }, + expectedHosts: []string{"foo", "bar"}, + }, + { + nodes: &api.NodeList{ + Items: []api.Node{ + { + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Status: api.NodeStatus{Phase: api.NodeRunning}, + }, + { + ObjectMeta: api.ObjectMeta{Name: "bar"}, + Status: api.NodeStatus{Phase: api.NodeRunning}, + }, + { + ObjectMeta: api.ObjectMeta{Name: "unschedulable"}, + Spec: api.NodeSpec{Unschedulable: true}, + Status: api.NodeStatus{Phase: api.NodeRunning}, + }, + }, + }, + expectedHosts: []string{"foo", "bar"}, + }, + } + + for _, test := range tests { + hosts := hostsFromNodeList(test.nodes) + if !reflect.DeepEqual(hosts, test.expectedHosts) { + t.Errorf("expected: %v, saw: %v", test.expectedHosts, hosts) + } + } +} + +func TestGetNodeConditionPredicate(t *testing.T) { + tests := []struct { + node api.Node + expectAccept bool + name string + }{ + { + node: api.Node{}, + expectAccept: false, + name: "empty", + }, + { + node: api.Node{ + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + {Type: api.NodeReady, Status: api.ConditionTrue}, + }, + }, + }, + expectAccept: true, + name: "basic", + }, + { + node: api.Node{ + Spec: api.NodeSpec{Unschedulable: true}, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + {Type: api.NodeReady, Status: api.ConditionTrue}, + }, + }, + }, + expectAccept: false, + name: "unschedulable", + }, + } + pred := getNodeConditionPredicate() + for _, test := range tests { + accept := pred(test.node) + if accept != test.expectAccept { + t.Errorf("Test failed for %s, expected %v, saw %v", test.name, test.expectAccept, accept) + } + } +} + +// TODO(a-robinson): Add tests for update/sync/delete. diff --git a/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/doc.go b/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/doc.go new file mode 100644 index 000000000..b69d1a121 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 serviceaccount provides implementations +// to manage service accounts and service account tokens +package serviceaccount diff --git a/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/serviceaccounts_controller.go b/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/serviceaccounts_controller.go new file mode 100644 index 000000000..dd3853127 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/serviceaccounts_controller.go @@ -0,0 +1,256 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 serviceaccount + +import ( + "fmt" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + apierrs "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/watch" +) + +// nameIndexFunc is an index function that indexes based on an object's name +func nameIndexFunc(obj interface{}) ([]string, error) { + meta, err := meta.Accessor(obj) + if err != nil { + return []string{""}, fmt.Errorf("object has no meta: %v", err) + } + return []string{meta.GetName()}, nil +} + +// ServiceAccountsControllerOptions contains options for running a ServiceAccountsController +type ServiceAccountsControllerOptions struct { + // ServiceAccounts is the list of service accounts to ensure exist in every namespace + ServiceAccounts []api.ServiceAccount + + // ServiceAccountResync is the interval between full resyncs of ServiceAccounts. + // If non-zero, all service accounts will be re-listed this often. + // Otherwise, re-list will be delayed as long as possible (until the watch is closed or times out). + ServiceAccountResync time.Duration + + // NamespaceResync is the interval between full resyncs of Namespaces. + // If non-zero, all namespaces will be re-listed this often. + // Otherwise, re-list will be delayed as long as possible (until the watch is closed or times out). + NamespaceResync time.Duration +} + +func DefaultServiceAccountsControllerOptions() ServiceAccountsControllerOptions { + return ServiceAccountsControllerOptions{ + ServiceAccounts: []api.ServiceAccount{ + {ObjectMeta: api.ObjectMeta{Name: "default"}}, + }, + } +} + +// NewServiceAccountsController returns a new *ServiceAccountsController. +func NewServiceAccountsController(cl clientset.Interface, options ServiceAccountsControllerOptions) *ServiceAccountsController { + e := &ServiceAccountsController{ + client: cl, + serviceAccountsToEnsure: options.ServiceAccounts, + } + + accountSelector := fields.Everything() + if len(options.ServiceAccounts) == 1 { + // If we're maintaining a single account, we can scope the accounts we watch to just that name + accountSelector = fields.SelectorFromSet(map[string]string{api.ObjectNameField: options.ServiceAccounts[0].Name}) + } + e.serviceAccounts, e.serviceAccountController = framework.NewIndexerInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + options.FieldSelector = accountSelector + return e.client.Core().ServiceAccounts(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + options.FieldSelector = accountSelector + return e.client.Core().ServiceAccounts(api.NamespaceAll).Watch(options) + }, + }, + &api.ServiceAccount{}, + options.ServiceAccountResync, + framework.ResourceEventHandlerFuncs{ + DeleteFunc: e.serviceAccountDeleted, + }, + cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc}, + ) + + e.namespaces, e.namespaceController = framework.NewIndexerInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return e.client.Core().Namespaces().List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return e.client.Core().Namespaces().Watch(options) + }, + }, + &api.Namespace{}, + options.NamespaceResync, + framework.ResourceEventHandlerFuncs{ + AddFunc: e.namespaceAdded, + UpdateFunc: e.namespaceUpdated, + }, + cache.Indexers{"name": nameIndexFunc}, + ) + + return e +} + +// ServiceAccountsController manages ServiceAccount objects inside Namespaces +type ServiceAccountsController struct { + stopChan chan struct{} + + client clientset.Interface + serviceAccountsToEnsure []api.ServiceAccount + + serviceAccounts cache.Indexer + namespaces cache.Indexer + + // Since we join two objects, we'll watch both of them with controllers. + serviceAccountController *framework.Controller + namespaceController *framework.Controller +} + +// Runs controller loops and returns immediately +func (e *ServiceAccountsController) Run() { + if e.stopChan == nil { + e.stopChan = make(chan struct{}) + go e.serviceAccountController.Run(e.stopChan) + go e.namespaceController.Run(e.stopChan) + } +} + +// Stop gracefully shuts down this controller +func (e *ServiceAccountsController) Stop() { + if e.stopChan != nil { + close(e.stopChan) + e.stopChan = nil + } +} + +// serviceAccountDeleted reacts to a ServiceAccount deletion by recreating a default ServiceAccount in the namespace if needed +func (e *ServiceAccountsController) serviceAccountDeleted(obj interface{}) { + serviceAccount, ok := obj.(*api.ServiceAccount) + if !ok { + // Unknown type. If we missed a ServiceAccount deletion, the + // corresponding secrets will be cleaned up during the Secret re-list + return + } + // If the deleted service account is one we're maintaining, recreate it + for _, sa := range e.serviceAccountsToEnsure { + if sa.Name == serviceAccount.Name { + e.createServiceAccountIfNeeded(sa, serviceAccount.Namespace) + } + } +} + +// namespaceAdded reacts to a Namespace creation by creating a default ServiceAccount object +func (e *ServiceAccountsController) namespaceAdded(obj interface{}) { + namespace := obj.(*api.Namespace) + for _, sa := range e.serviceAccountsToEnsure { + e.createServiceAccountIfNeeded(sa, namespace.Name) + } +} + +// namespaceUpdated reacts to a Namespace update (or re-list) by creating a default ServiceAccount in the namespace if needed +func (e *ServiceAccountsController) namespaceUpdated(oldObj interface{}, newObj interface{}) { + newNamespace := newObj.(*api.Namespace) + for _, sa := range e.serviceAccountsToEnsure { + e.createServiceAccountIfNeeded(sa, newNamespace.Name) + } +} + +// createServiceAccountIfNeeded creates a ServiceAccount with the given name in the given namespace if: +// * the named ServiceAccount does not already exist +// * the specified namespace exists +// * the specified namespace is in the ACTIVE phase +func (e *ServiceAccountsController) createServiceAccountIfNeeded(sa api.ServiceAccount, namespace string) { + existingServiceAccount, err := e.getServiceAccount(sa.Name, namespace) + if err != nil { + glog.Error(err) + return + } + if existingServiceAccount != nil { + // If service account already exists, it doesn't need to be created + return + } + + namespaceObj, err := e.getNamespace(namespace) + if err != nil { + glog.Error(err) + return + } + if namespaceObj == nil { + // If namespace does not exist, no service account is needed + return + } + if namespaceObj.Status.Phase != api.NamespaceActive { + // If namespace is not active, we shouldn't try to create anything + return + } + + e.createServiceAccount(sa, namespace) +} + +// createDefaultServiceAccount creates a default ServiceAccount in the specified namespace +func (e *ServiceAccountsController) createServiceAccount(sa api.ServiceAccount, namespace string) { + sa.Namespace = namespace + if _, err := e.client.Core().ServiceAccounts(namespace).Create(&sa); err != nil && !apierrs.IsAlreadyExists(err) { + glog.Error(err) + } +} + +// getServiceAccount returns the ServiceAccount with the given name for the given namespace +func (e *ServiceAccountsController) getServiceAccount(name, namespace string) (*api.ServiceAccount, error) { + key := &api.ServiceAccount{ObjectMeta: api.ObjectMeta{Namespace: namespace}} + accounts, err := e.serviceAccounts.Index("namespace", key) + if err != nil { + return nil, err + } + + for _, obj := range accounts { + serviceAccount := obj.(*api.ServiceAccount) + if name == serviceAccount.Name { + return serviceAccount, nil + } + } + return nil, nil +} + +// getNamespace returns the Namespace with the given name +func (e *ServiceAccountsController) getNamespace(name string) (*api.Namespace, error) { + key := &api.Namespace{ObjectMeta: api.ObjectMeta{Name: name}} + namespaces, err := e.namespaces.Index("name", key) + if err != nil { + return nil, err + } + + if len(namespaces) == 0 { + return nil, nil + } + if len(namespaces) == 1 { + return namespaces[0].(*api.Namespace), nil + } + return nil, fmt.Errorf("%d namespaces with the name %s indexed", len(namespaces), name) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/serviceaccounts_controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/serviceaccounts_controller_test.go new file mode 100644 index 000000000..eafad0438 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/serviceaccounts_controller_test.go @@ -0,0 +1,197 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 serviceaccount + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/unversioned/testclient" + "k8s.io/kubernetes/pkg/util/sets" +) + +type serverResponse struct { + statusCode int + obj interface{} +} + +func TestServiceAccountCreation(t *testing.T) { + ns := api.NamespaceDefault + + defaultName := "default" + managedName := "managed" + + activeNS := &api.Namespace{ + ObjectMeta: api.ObjectMeta{Name: ns}, + Status: api.NamespaceStatus{ + Phase: api.NamespaceActive, + }, + } + terminatingNS := &api.Namespace{ + ObjectMeta: api.ObjectMeta{Name: ns}, + Status: api.NamespaceStatus{ + Phase: api.NamespaceTerminating, + }, + } + defaultServiceAccount := &api.ServiceAccount{ + ObjectMeta: api.ObjectMeta{ + Name: defaultName, + Namespace: ns, + ResourceVersion: "1", + }, + } + managedServiceAccount := &api.ServiceAccount{ + ObjectMeta: api.ObjectMeta{ + Name: managedName, + Namespace: ns, + ResourceVersion: "1", + }, + } + unmanagedServiceAccount := &api.ServiceAccount{ + ObjectMeta: api.ObjectMeta{ + Name: "other-unmanaged", + Namespace: ns, + ResourceVersion: "1", + }, + } + + testcases := map[string]struct { + ExistingNamespace *api.Namespace + ExistingServiceAccounts []*api.ServiceAccount + + AddedNamespace *api.Namespace + UpdatedNamespace *api.Namespace + DeletedServiceAccount *api.ServiceAccount + + ExpectCreatedServiceAccounts []string + }{ + "new active namespace missing serviceaccounts": { + ExistingServiceAccounts: []*api.ServiceAccount{}, + AddedNamespace: activeNS, + ExpectCreatedServiceAccounts: sets.NewString(defaultName, managedName).List(), + }, + "new active namespace missing serviceaccount": { + ExistingServiceAccounts: []*api.ServiceAccount{managedServiceAccount}, + AddedNamespace: activeNS, + ExpectCreatedServiceAccounts: []string{defaultName}, + }, + "new active namespace with serviceaccounts": { + ExistingServiceAccounts: []*api.ServiceAccount{defaultServiceAccount, managedServiceAccount}, + AddedNamespace: activeNS, + ExpectCreatedServiceAccounts: []string{}, + }, + + "new terminating namespace": { + ExistingServiceAccounts: []*api.ServiceAccount{}, + AddedNamespace: terminatingNS, + ExpectCreatedServiceAccounts: []string{}, + }, + + "updated active namespace missing serviceaccounts": { + ExistingServiceAccounts: []*api.ServiceAccount{}, + UpdatedNamespace: activeNS, + ExpectCreatedServiceAccounts: sets.NewString(defaultName, managedName).List(), + }, + "updated active namespace missing serviceaccount": { + ExistingServiceAccounts: []*api.ServiceAccount{defaultServiceAccount}, + UpdatedNamespace: activeNS, + ExpectCreatedServiceAccounts: []string{managedName}, + }, + "updated active namespace with serviceaccounts": { + ExistingServiceAccounts: []*api.ServiceAccount{defaultServiceAccount, managedServiceAccount}, + UpdatedNamespace: activeNS, + ExpectCreatedServiceAccounts: []string{}, + }, + "updated terminating namespace": { + ExistingServiceAccounts: []*api.ServiceAccount{}, + UpdatedNamespace: terminatingNS, + ExpectCreatedServiceAccounts: []string{}, + }, + + "deleted serviceaccount without namespace": { + DeletedServiceAccount: defaultServiceAccount, + ExpectCreatedServiceAccounts: []string{}, + }, + "deleted serviceaccount with active namespace": { + ExistingNamespace: activeNS, + DeletedServiceAccount: defaultServiceAccount, + ExpectCreatedServiceAccounts: []string{defaultName}, + }, + "deleted serviceaccount with terminating namespace": { + ExistingNamespace: terminatingNS, + DeletedServiceAccount: defaultServiceAccount, + ExpectCreatedServiceAccounts: []string{}, + }, + "deleted unmanaged serviceaccount with active namespace": { + ExistingNamespace: activeNS, + DeletedServiceAccount: unmanagedServiceAccount, + ExpectCreatedServiceAccounts: []string{}, + }, + "deleted unmanaged serviceaccount with terminating namespace": { + ExistingNamespace: terminatingNS, + DeletedServiceAccount: unmanagedServiceAccount, + ExpectCreatedServiceAccounts: []string{}, + }, + } + + for k, tc := range testcases { + client := fake.NewSimpleClientset(defaultServiceAccount, managedServiceAccount) + options := DefaultServiceAccountsControllerOptions() + options.ServiceAccounts = []api.ServiceAccount{ + {ObjectMeta: api.ObjectMeta{Name: defaultName}}, + {ObjectMeta: api.ObjectMeta{Name: managedName}}, + } + controller := NewServiceAccountsController(client, options) + + if tc.ExistingNamespace != nil { + controller.namespaces.Add(tc.ExistingNamespace) + } + for _, s := range tc.ExistingServiceAccounts { + controller.serviceAccounts.Add(s) + } + + if tc.AddedNamespace != nil { + controller.namespaces.Add(tc.AddedNamespace) + controller.namespaceAdded(tc.AddedNamespace) + } + if tc.UpdatedNamespace != nil { + controller.namespaces.Add(tc.UpdatedNamespace) + controller.namespaceUpdated(nil, tc.UpdatedNamespace) + } + if tc.DeletedServiceAccount != nil { + controller.serviceAccountDeleted(tc.DeletedServiceAccount) + } + + actions := client.Actions() + if len(tc.ExpectCreatedServiceAccounts) != len(actions) { + t.Errorf("%s: Expected to create accounts %#v. Actual actions were: %#v", k, tc.ExpectCreatedServiceAccounts, actions) + continue + } + for i, expectedName := range tc.ExpectCreatedServiceAccounts { + action := actions[i] + if !action.Matches("create", "serviceaccounts") { + t.Errorf("%s: Unexpected action %s", k, action) + break + } + createdAccount := action.(testclient.CreateAction).GetObject().(*api.ServiceAccount) + if createdAccount.Name != expectedName { + t.Errorf("%s: Expected %s to be created, got %s", k, expectedName, createdAccount.Name) + } + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokengetter.go b/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokengetter.go new file mode 100644 index 000000000..bd7fc827b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokengetter.go @@ -0,0 +1,77 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 serviceaccount + +import ( + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/secret" + secretetcd "k8s.io/kubernetes/pkg/registry/secret/etcd" + serviceaccountregistry "k8s.io/kubernetes/pkg/registry/serviceaccount" + serviceaccountetcd "k8s.io/kubernetes/pkg/registry/serviceaccount/etcd" + "k8s.io/kubernetes/pkg/serviceaccount" + "k8s.io/kubernetes/pkg/storage" +) + +// clientGetter implements ServiceAccountTokenGetter using a clientset.Interface +type clientGetter struct { + client clientset.Interface +} + +// NewGetterFromClient returns a ServiceAccountTokenGetter that +// uses the specified client to retrieve service accounts and secrets. +// The client should NOT authenticate using a service account token +// the returned getter will be used to retrieve, or recursion will result. +func NewGetterFromClient(c clientset.Interface) serviceaccount.ServiceAccountTokenGetter { + return clientGetter{c} +} +func (c clientGetter) GetServiceAccount(namespace, name string) (*api.ServiceAccount, error) { + return c.client.Core().ServiceAccounts(namespace).Get(name) +} +func (c clientGetter) GetSecret(namespace, name string) (*api.Secret, error) { + return c.client.Core().Secrets(namespace).Get(name) +} + +// registryGetter implements ServiceAccountTokenGetter using a service account and secret registry +type registryGetter struct { + serviceAccounts serviceaccountregistry.Registry + secrets secret.Registry +} + +// NewGetterFromRegistries returns a ServiceAccountTokenGetter that +// uses the specified registries to retrieve service accounts and secrets. +func NewGetterFromRegistries(serviceAccounts serviceaccountregistry.Registry, secrets secret.Registry) serviceaccount.ServiceAccountTokenGetter { + return ®istryGetter{serviceAccounts, secrets} +} +func (r *registryGetter) GetServiceAccount(namespace, name string) (*api.ServiceAccount, error) { + ctx := api.WithNamespace(api.NewContext(), namespace) + return r.serviceAccounts.GetServiceAccount(ctx, name) +} +func (r *registryGetter) GetSecret(namespace, name string) (*api.Secret, error) { + ctx := api.WithNamespace(api.NewContext(), namespace) + return r.secrets.GetSecret(ctx, name) +} + +// NewGetterFromStorageInterface returns a ServiceAccountTokenGetter that +// uses the specified storage to retrieve service accounts and secrets. +func NewGetterFromStorageInterface(s storage.Interface) serviceaccount.ServiceAccountTokenGetter { + return NewGetterFromRegistries( + serviceaccountregistry.NewRegistry(serviceaccountetcd.NewREST(generic.RESTOptions{Storage: s, Decorator: generic.UndecoratedStorage})), + secret.NewRegistry(secretetcd.NewREST(generic.RESTOptions{Storage: s, Decorator: generic.UndecoratedStorage})), + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokens_controller.go b/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokens_controller.go new file mode 100644 index 000000000..168941493 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokens_controller.go @@ -0,0 +1,522 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 serviceaccount + +import ( + "bytes" + "fmt" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + apierrors "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/controller/framework" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/registry/secret" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/serviceaccount" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/watch" +) + +// RemoveTokenBackoff is the recommended (empirical) retry interval for removing +// a secret reference from a service account when the secret is deleted. It is +// exported for use by custom secret controllers. +var RemoveTokenBackoff = wait.Backoff{ + Steps: 10, + Duration: 100 * time.Millisecond, + Jitter: 1.0, +} + +// TokensControllerOptions contains options for the TokensController +type TokensControllerOptions struct { + // TokenGenerator is the generator to use to create new tokens + TokenGenerator serviceaccount.TokenGenerator + // ServiceAccountResync is the time.Duration at which to fully re-list service accounts. + // If zero, re-list will be delayed as long as possible + ServiceAccountResync time.Duration + // SecretResync is the time.Duration at which to fully re-list secrets. + // If zero, re-list will be delayed as long as possible + SecretResync time.Duration + // This CA will be added in the secretes of service accounts + RootCA []byte +} + +// NewTokensController returns a new *TokensController. +func NewTokensController(cl clientset.Interface, options TokensControllerOptions) *TokensController { + e := &TokensController{ + client: cl, + token: options.TokenGenerator, + rootCA: options.RootCA, + } + + e.serviceAccounts, e.serviceAccountController = framework.NewIndexerInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return e.client.Core().ServiceAccounts(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return e.client.Core().ServiceAccounts(api.NamespaceAll).Watch(options) + }, + }, + &api.ServiceAccount{}, + options.ServiceAccountResync, + framework.ResourceEventHandlerFuncs{ + AddFunc: e.serviceAccountAdded, + UpdateFunc: e.serviceAccountUpdated, + DeleteFunc: e.serviceAccountDeleted, + }, + cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc}, + ) + + tokenSelector := fields.SelectorFromSet(map[string]string{api.SecretTypeField: string(api.SecretTypeServiceAccountToken)}) + e.secrets, e.secretController = framework.NewIndexerInformer( + &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + options.FieldSelector = tokenSelector + return e.client.Core().Secrets(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + options.FieldSelector = tokenSelector + return e.client.Core().Secrets(api.NamespaceAll).Watch(options) + }, + }, + &api.Secret{}, + options.SecretResync, + framework.ResourceEventHandlerFuncs{ + AddFunc: e.secretAdded, + UpdateFunc: e.secretUpdated, + DeleteFunc: e.secretDeleted, + }, + cache.Indexers{"namespace": cache.MetaNamespaceIndexFunc}, + ) + + e.serviceAccountsSynced = e.serviceAccountController.HasSynced + e.secretsSynced = e.secretController.HasSynced + + return e +} + +// TokensController manages ServiceAccountToken secrets for ServiceAccount objects +type TokensController struct { + stopChan chan struct{} + + client clientset.Interface + token serviceaccount.TokenGenerator + + rootCA []byte + + serviceAccounts cache.Indexer + secrets cache.Indexer + + // Since we join two objects, we'll watch both of them with controllers. + serviceAccountController *framework.Controller + secretController *framework.Controller + + // These are here so tests can inject a 'return true'. + serviceAccountsSynced func() bool + secretsSynced func() bool +} + +// Runs controller loops and returns immediately +func (e *TokensController) Run() { + if e.stopChan == nil { + e.stopChan = make(chan struct{}) + go e.serviceAccountController.Run(e.stopChan) + go e.secretController.Run(e.stopChan) + } +} + +// Stop gracefully shuts down this controller +func (e *TokensController) Stop() { + if e.stopChan != nil { + close(e.stopChan) + e.stopChan = nil + } +} + +// serviceAccountAdded reacts to a ServiceAccount creation by creating a corresponding ServiceAccountToken Secret +func (e *TokensController) serviceAccountAdded(obj interface{}) { + serviceAccount := obj.(*api.ServiceAccount) + err := e.createSecretIfNeeded(serviceAccount) + if err != nil { + glog.Error(err) + } +} + +// serviceAccountUpdated reacts to a ServiceAccount update (or re-list) by ensuring a corresponding ServiceAccountToken Secret exists +func (e *TokensController) serviceAccountUpdated(oldObj interface{}, newObj interface{}) { + newServiceAccount := newObj.(*api.ServiceAccount) + err := e.createSecretIfNeeded(newServiceAccount) + if err != nil { + glog.Error(err) + } +} + +// serviceAccountDeleted reacts to a ServiceAccount deletion by deleting all corresponding ServiceAccountToken Secrets +func (e *TokensController) serviceAccountDeleted(obj interface{}) { + serviceAccount, ok := obj.(*api.ServiceAccount) + if !ok { + // Unknown type. If we missed a ServiceAccount deletion, the + // corresponding secrets will be cleaned up during the Secret re-list + return + } + secrets, err := e.listTokenSecrets(serviceAccount) + if err != nil { + glog.Error(err) + return + } + for _, secret := range secrets { + glog.V(4).Infof("Deleting secret %s/%s because service account %s was deleted", secret.Namespace, secret.Name, serviceAccount.Name) + if err := e.deleteSecret(secret); err != nil { + glog.Errorf("Error deleting secret %s/%s: %v", secret.Namespace, secret.Name, err) + } + } +} + +// secretAdded reacts to a Secret create by ensuring the referenced ServiceAccount exists, and by adding a token to the secret if needed +func (e *TokensController) secretAdded(obj interface{}) { + secret := obj.(*api.Secret) + serviceAccount, err := e.getServiceAccount(secret, true) + if err != nil { + glog.Error(err) + return + } + if serviceAccount == nil { + glog.V(2).Infof( + "Deleting new secret %s/%s because service account %s (uid=%s) was not found", + secret.Namespace, secret.Name, + secret.Annotations[api.ServiceAccountNameKey], secret.Annotations[api.ServiceAccountUIDKey]) + if err := e.deleteSecret(secret); err != nil { + glog.Errorf("Error deleting secret %s/%s: %v", secret.Namespace, secret.Name, err) + } + } else { + e.generateTokenIfNeeded(serviceAccount, secret) + } +} + +// secretUpdated reacts to a Secret update (or re-list) by deleting the secret (if the referenced ServiceAccount does not exist) +func (e *TokensController) secretUpdated(oldObj interface{}, newObj interface{}) { + newSecret := newObj.(*api.Secret) + newServiceAccount, err := e.getServiceAccount(newSecret, true) + if err != nil { + glog.Error(err) + return + } + if newServiceAccount == nil { + glog.V(2).Infof( + "Deleting updated secret %s/%s because service account %s (uid=%s) was not found", + newSecret.Namespace, newSecret.Name, + newSecret.Annotations[api.ServiceAccountNameKey], newSecret.Annotations[api.ServiceAccountUIDKey]) + if err := e.deleteSecret(newSecret); err != nil { + glog.Errorf("Error deleting secret %s/%s: %v", newSecret.Namespace, newSecret.Name, err) + } + } else { + e.generateTokenIfNeeded(newServiceAccount, newSecret) + } +} + +// secretDeleted reacts to a Secret being deleted by removing a reference from the corresponding ServiceAccount if needed +func (e *TokensController) secretDeleted(obj interface{}) { + secret, ok := obj.(*api.Secret) + if !ok { + // Unknown type. If we missed a Secret deletion, the corresponding ServiceAccount (if it exists) + // will get a secret recreated (if needed) during the ServiceAccount re-list + return + } + + serviceAccount, err := e.getServiceAccount(secret, false) + if err != nil { + glog.Error(err) + return + } + if serviceAccount == nil { + return + } + + if err := client.RetryOnConflict(RemoveTokenBackoff, func() error { + return e.removeSecretReferenceIfNeeded(serviceAccount, secret.Name) + }); err != nil { + utilruntime.HandleError(err) + } +} + +// createSecretIfNeeded makes sure at least one ServiceAccountToken secret exists, and is included in the serviceAccount's Secrets list +func (e *TokensController) createSecretIfNeeded(serviceAccount *api.ServiceAccount) error { + // If the service account references no secrets, short-circuit and create a new one + if len(serviceAccount.Secrets) == 0 { + return e.createSecret(serviceAccount) + } + + // We shouldn't try to validate secret references until the secrets store is synced + if !e.secretsSynced() { + return nil + } + + // If any existing token secrets are referenced by the service account, return + allSecrets, err := e.listTokenSecrets(serviceAccount) + if err != nil { + return err + } + referencedSecrets := getSecretReferences(serviceAccount) + for _, secret := range allSecrets { + if referencedSecrets.Has(secret.Name) { + return nil + } + } + + // Otherwise create a new token secret + return e.createSecret(serviceAccount) +} + +// createSecret creates a secret of type ServiceAccountToken for the given ServiceAccount +func (e *TokensController) createSecret(serviceAccount *api.ServiceAccount) error { + // We don't want to update the cache's copy of the service account + // so add the secret to a freshly retrieved copy of the service account + serviceAccounts := e.client.Core().ServiceAccounts(serviceAccount.Namespace) + liveServiceAccount, err := serviceAccounts.Get(serviceAccount.Name) + if err != nil { + return err + } + if liveServiceAccount.ResourceVersion != serviceAccount.ResourceVersion { + // our view of the service account is not up to date + // we'll get notified of an update event later and get to try again + // this only prevent interactions between successive runs of this controller's event handlers, but that is useful + glog.V(2).Infof("View of ServiceAccount %s/%s is not up to date, skipping token creation", serviceAccount.Namespace, serviceAccount.Name) + return nil + } + + // Build the secret + secret := &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: secret.Strategy.GenerateName(fmt.Sprintf("%s-token-", serviceAccount.Name)), + Namespace: serviceAccount.Namespace, + Annotations: map[string]string{ + api.ServiceAccountNameKey: serviceAccount.Name, + api.ServiceAccountUIDKey: string(serviceAccount.UID), + }, + }, + Type: api.SecretTypeServiceAccountToken, + Data: map[string][]byte{}, + } + + // Generate the token + token, err := e.token.GenerateToken(*serviceAccount, *secret) + if err != nil { + return err + } + secret.Data[api.ServiceAccountTokenKey] = []byte(token) + secret.Data[api.ServiceAccountNamespaceKey] = []byte(serviceAccount.Namespace) + if e.rootCA != nil && len(e.rootCA) > 0 { + secret.Data[api.ServiceAccountRootCAKey] = e.rootCA + } + + // Save the secret + if createdToken, err := e.client.Core().Secrets(serviceAccount.Namespace).Create(secret); err != nil { + return err + } else { + // Manually add the new token to the cache store. + // This prevents the service account update (below) triggering another token creation, if the referenced token couldn't be found in the store + e.secrets.Add(createdToken) + } + + liveServiceAccount.Secrets = append(liveServiceAccount.Secrets, api.ObjectReference{Name: secret.Name}) + + _, err = serviceAccounts.Update(liveServiceAccount) + if err != nil { + // we weren't able to use the token, try to clean it up. + glog.V(2).Infof("Deleting secret %s/%s because reference couldn't be added (%v)", secret.Namespace, secret.Name, err) + if err := e.client.Core().Secrets(secret.Namespace).Delete(secret.Name, nil); err != nil { + glog.Error(err) // if we fail, just log it + } + } + if apierrors.IsConflict(err) { + // nothing to do. We got a conflict, that means that the service account was updated. We simply need to return because we'll get an update notification later + return nil + } + + return err +} + +// generateTokenIfNeeded populates the token data for the given Secret if not already set +func (e *TokensController) generateTokenIfNeeded(serviceAccount *api.ServiceAccount, secret *api.Secret) error { + if secret.Annotations == nil { + secret.Annotations = map[string]string{} + } + if secret.Data == nil { + secret.Data = map[string][]byte{} + } + + caData := secret.Data[api.ServiceAccountRootCAKey] + needsCA := len(e.rootCA) > 0 && bytes.Compare(caData, e.rootCA) != 0 + + needsNamespace := len(secret.Data[api.ServiceAccountNamespaceKey]) == 0 + + tokenData := secret.Data[api.ServiceAccountTokenKey] + needsToken := len(tokenData) == 0 + + if !needsCA && !needsToken && !needsNamespace { + return nil + } + + // Set the CA + if needsCA { + secret.Data[api.ServiceAccountRootCAKey] = e.rootCA + } + // Set the namespace + if needsNamespace { + secret.Data[api.ServiceAccountNamespaceKey] = []byte(secret.Namespace) + } + + // Generate the token + if needsToken { + token, err := e.token.GenerateToken(*serviceAccount, *secret) + if err != nil { + return err + } + secret.Data[api.ServiceAccountTokenKey] = []byte(token) + } + + // Set annotations + secret.Annotations[api.ServiceAccountNameKey] = serviceAccount.Name + secret.Annotations[api.ServiceAccountUIDKey] = string(serviceAccount.UID) + + // Save the secret + if _, err := e.client.Core().Secrets(secret.Namespace).Update(secret); err != nil { + return err + } + return nil +} + +// deleteSecret deletes the given secret +func (e *TokensController) deleteSecret(secret *api.Secret) error { + return e.client.Core().Secrets(secret.Namespace).Delete(secret.Name, nil) +} + +// removeSecretReferenceIfNeeded updates the given ServiceAccount to remove a reference to the given secretName if needed. +// Returns whether an update was performed, and any error that occurred +func (e *TokensController) removeSecretReferenceIfNeeded(serviceAccount *api.ServiceAccount, secretName string) error { + // We don't want to update the cache's copy of the service account + // so remove the secret from a freshly retrieved copy of the service account + serviceAccounts := e.client.Core().ServiceAccounts(serviceAccount.Namespace) + serviceAccount, err := serviceAccounts.Get(serviceAccount.Name) + if err != nil { + return err + } + + // Double-check to see if the account still references the secret + if !getSecretReferences(serviceAccount).Has(secretName) { + return nil + } + + secrets := []api.ObjectReference{} + for _, s := range serviceAccount.Secrets { + if s.Name != secretName { + secrets = append(secrets, s) + } + } + serviceAccount.Secrets = secrets + + _, err = serviceAccounts.Update(serviceAccount) + if err != nil { + return err + } + + return nil +} + +// getServiceAccount returns the ServiceAccount referenced by the given secret. If the secret is not +// of type ServiceAccountToken, or if the referenced ServiceAccount does not exist, nil is returned +func (e *TokensController) getServiceAccount(secret *api.Secret, fetchOnCacheMiss bool) (*api.ServiceAccount, error) { + name, _ := serviceAccountNameAndUID(secret) + if len(name) == 0 { + return nil, nil + } + + key := &api.ServiceAccount{ObjectMeta: api.ObjectMeta{Namespace: secret.Namespace}} + namespaceAccounts, err := e.serviceAccounts.Index("namespace", key) + if err != nil { + return nil, err + } + + for _, obj := range namespaceAccounts { + serviceAccount := obj.(*api.ServiceAccount) + + if serviceaccount.IsServiceAccountToken(secret, serviceAccount) { + return serviceAccount, nil + } + } + + if fetchOnCacheMiss { + serviceAccount, err := e.client.Core().ServiceAccounts(secret.Namespace).Get(name) + if apierrors.IsNotFound(err) { + return nil, nil + } + if err != nil { + return nil, err + } + + if serviceaccount.IsServiceAccountToken(secret, serviceAccount) { + return serviceAccount, nil + } + } + + return nil, nil +} + +// listTokenSecrets returns a list of all of the ServiceAccountToken secrets that +// reference the given service account's name and uid +func (e *TokensController) listTokenSecrets(serviceAccount *api.ServiceAccount) ([]*api.Secret, error) { + key := &api.Secret{ObjectMeta: api.ObjectMeta{Namespace: serviceAccount.Namespace}} + namespaceSecrets, err := e.secrets.Index("namespace", key) + if err != nil { + return nil, err + } + + items := []*api.Secret{} + for _, obj := range namespaceSecrets { + secret := obj.(*api.Secret) + + if serviceaccount.IsServiceAccountToken(secret, serviceAccount) { + items = append(items, secret) + } + } + return items, nil +} + +// serviceAccountNameAndUID is a helper method to get the ServiceAccount Name and UID from the given secret +// Returns "","" if the secret is not a ServiceAccountToken secret +// If the name or uid annotation is missing, "" is returned instead +func serviceAccountNameAndUID(secret *api.Secret) (string, string) { + if secret.Type != api.SecretTypeServiceAccountToken { + return "", "" + } + return secret.Annotations[api.ServiceAccountNameKey], secret.Annotations[api.ServiceAccountUIDKey] +} + +func getSecretReferences(serviceAccount *api.ServiceAccount) sets.String { + references := sets.NewString() + for _, secret := range serviceAccount.Secrets { + references.Insert(secret.Name) + } + return references +} diff --git a/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokens_controller_test.go b/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokens_controller_test.go new file mode 100644 index 000000000..fb67dac21 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokens_controller_test.go @@ -0,0 +1,561 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 serviceaccount + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/runtime" + utilrand "k8s.io/kubernetes/pkg/util/rand" +) + +type testGenerator struct { + GeneratedServiceAccounts []api.ServiceAccount + GeneratedSecrets []api.Secret + Token string + Err error +} + +func (t *testGenerator) GenerateToken(serviceAccount api.ServiceAccount, secret api.Secret) (string, error) { + t.GeneratedSecrets = append(t.GeneratedSecrets, secret) + t.GeneratedServiceAccounts = append(t.GeneratedServiceAccounts, serviceAccount) + return t.Token, t.Err +} + +// emptySecretReferences is used by a service account without any secrets +func emptySecretReferences() []api.ObjectReference { + return []api.ObjectReference{} +} + +// missingSecretReferences is used by a service account that references secrets which do no exist +func missingSecretReferences() []api.ObjectReference { + return []api.ObjectReference{{Name: "missing-secret-1"}} +} + +// regularSecretReferences is used by a service account that references secrets which are not ServiceAccountTokens +func regularSecretReferences() []api.ObjectReference { + return []api.ObjectReference{{Name: "regular-secret-1"}} +} + +// tokenSecretReferences is used by a service account that references a ServiceAccountToken secret +func tokenSecretReferences() []api.ObjectReference { + return []api.ObjectReference{{Name: "token-secret-1"}} +} + +// addTokenSecretReference adds a reference to the ServiceAccountToken that will be created +func addTokenSecretReference(refs []api.ObjectReference) []api.ObjectReference { + return append(refs, api.ObjectReference{Name: "default-token-fplln"}) +} + +// serviceAccount returns a service account with the given secret refs +func serviceAccount(secretRefs []api.ObjectReference) *api.ServiceAccount { + return &api.ServiceAccount{ + ObjectMeta: api.ObjectMeta{ + Name: "default", + UID: "12345", + Namespace: "default", + ResourceVersion: "1", + }, + Secrets: secretRefs, + } +} + +// updatedServiceAccount returns a service account with the resource version modified +func updatedServiceAccount(secretRefs []api.ObjectReference) *api.ServiceAccount { + sa := serviceAccount(secretRefs) + sa.ResourceVersion = "2" + return sa +} + +// opaqueSecret returns a persisted non-ServiceAccountToken secret named "regular-secret-1" +func opaqueSecret() *api.Secret { + return &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: "regular-secret-1", + Namespace: "default", + UID: "23456", + ResourceVersion: "1", + }, + Type: "Opaque", + Data: map[string][]byte{ + "mykey": []byte("mydata"), + }, + } +} + +// createdTokenSecret returns the ServiceAccountToken secret posted when creating a new token secret. +// Named "default-token-fplln", since that is the first generated name after rand.Seed(1) +func createdTokenSecret() *api.Secret { + return &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: "default-token-fplln", + Namespace: "default", + Annotations: map[string]string{ + api.ServiceAccountNameKey: "default", + api.ServiceAccountUIDKey: "12345", + }, + }, + Type: api.SecretTypeServiceAccountToken, + Data: map[string][]byte{ + "token": []byte("ABC"), + "ca.crt": []byte("CA Data"), + "namespace": []byte("default"), + }, + } +} + +// serviceAccountTokenSecret returns an existing ServiceAccountToken secret named "token-secret-1" +func serviceAccountTokenSecret() *api.Secret { + return &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: "token-secret-1", + Namespace: "default", + UID: "23456", + ResourceVersion: "1", + Annotations: map[string]string{ + api.ServiceAccountNameKey: "default", + api.ServiceAccountUIDKey: "12345", + }, + }, + Type: api.SecretTypeServiceAccountToken, + Data: map[string][]byte{ + "token": []byte("ABC"), + "ca.crt": []byte("CA Data"), + "namespace": []byte("default"), + }, + } +} + +// serviceAccountTokenSecretWithoutTokenData returns an existing ServiceAccountToken secret that lacks token data +func serviceAccountTokenSecretWithoutTokenData() *api.Secret { + secret := serviceAccountTokenSecret() + delete(secret.Data, api.ServiceAccountTokenKey) + return secret +} + +// serviceAccountTokenSecretWithoutCAData returns an existing ServiceAccountToken secret that lacks ca data +func serviceAccountTokenSecretWithoutCAData() *api.Secret { + secret := serviceAccountTokenSecret() + delete(secret.Data, api.ServiceAccountRootCAKey) + return secret +} + +// serviceAccountTokenSecretWithCAData returns an existing ServiceAccountToken secret with the specified ca data +func serviceAccountTokenSecretWithCAData(data []byte) *api.Secret { + secret := serviceAccountTokenSecret() + secret.Data[api.ServiceAccountRootCAKey] = data + return secret +} + +// serviceAccountTokenSecretWithoutNamespaceData returns an existing ServiceAccountToken secret that lacks namespace data +func serviceAccountTokenSecretWithoutNamespaceData() *api.Secret { + secret := serviceAccountTokenSecret() + delete(secret.Data, api.ServiceAccountNamespaceKey) + return secret +} + +// serviceAccountTokenSecretWithNamespaceData returns an existing ServiceAccountToken secret with the specified namespace data +func serviceAccountTokenSecretWithNamespaceData(data []byte) *api.Secret { + secret := serviceAccountTokenSecret() + secret.Data[api.ServiceAccountNamespaceKey] = data + return secret +} + +func TestTokenCreation(t *testing.T) { + testcases := map[string]struct { + ClientObjects []runtime.Object + + SecretsSyncPending bool + ServiceAccountsSyncPending bool + + ExistingServiceAccount *api.ServiceAccount + ExistingSecrets []*api.Secret + + AddedServiceAccount *api.ServiceAccount + UpdatedServiceAccount *api.ServiceAccount + DeletedServiceAccount *api.ServiceAccount + AddedSecret *api.Secret + UpdatedSecret *api.Secret + DeletedSecret *api.Secret + + ExpectedActions []core.Action + }{ + "new serviceaccount with no secrets": { + ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, + + AddedServiceAccount: serviceAccount(emptySecretReferences()), + ExpectedActions: []core.Action{ + core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), + core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), + core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))), + }, + }, + "new serviceaccount with no secrets with unsynced secret store": { + ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, + + SecretsSyncPending: true, + + AddedServiceAccount: serviceAccount(emptySecretReferences()), + ExpectedActions: []core.Action{ + core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), + core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), + core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))), + }, + }, + "new serviceaccount with missing secrets": { + ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()}, + + AddedServiceAccount: serviceAccount(missingSecretReferences()), + ExpectedActions: []core.Action{ + core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), + core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), + core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(missingSecretReferences()))), + }, + }, + "new serviceaccount with missing secrets with unsynced secret store": { + ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()}, + + SecretsSyncPending: true, + + AddedServiceAccount: serviceAccount(missingSecretReferences()), + ExpectedActions: []core.Action{}, + }, + "new serviceaccount with non-token secrets": { + ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), createdTokenSecret(), opaqueSecret()}, + + AddedServiceAccount: serviceAccount(regularSecretReferences()), + ExpectedActions: []core.Action{ + core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), + core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), + core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(regularSecretReferences()))), + }, + }, + "new serviceaccount with token secrets": { + ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences()), serviceAccountTokenSecret()}, + ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()}, + + AddedServiceAccount: serviceAccount(tokenSecretReferences()), + ExpectedActions: []core.Action{}, + }, + "new serviceaccount with no secrets with resource conflict": { + ClientObjects: []runtime.Object{updatedServiceAccount(emptySecretReferences()), createdTokenSecret()}, + + AddedServiceAccount: serviceAccount(emptySecretReferences()), + ExpectedActions: []core.Action{ + core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), + }, + }, + + "updated serviceaccount with no secrets": { + ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, + + UpdatedServiceAccount: serviceAccount(emptySecretReferences()), + ExpectedActions: []core.Action{ + core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), + core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), + core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))), + }, + }, + "updated serviceaccount with no secrets with unsynced secret store": { + ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()}, + + SecretsSyncPending: true, + + UpdatedServiceAccount: serviceAccount(emptySecretReferences()), + ExpectedActions: []core.Action{ + core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), + core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), + core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))), + }, + }, + "updated serviceaccount with missing secrets": { + ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()}, + + UpdatedServiceAccount: serviceAccount(missingSecretReferences()), + ExpectedActions: []core.Action{ + core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), + core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), + core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(missingSecretReferences()))), + }, + }, + "updated serviceaccount with missing secrets with unsynced secret store": { + ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences()), createdTokenSecret()}, + + SecretsSyncPending: true, + + UpdatedServiceAccount: serviceAccount(missingSecretReferences()), + ExpectedActions: []core.Action{}, + }, + "updated serviceaccount with non-token secrets": { + ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), createdTokenSecret(), opaqueSecret()}, + + UpdatedServiceAccount: serviceAccount(regularSecretReferences()), + ExpectedActions: []core.Action{ + core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), + core.NewCreateAction("secrets", api.NamespaceDefault, createdTokenSecret()), + core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(addTokenSecretReference(regularSecretReferences()))), + }, + }, + "updated serviceaccount with token secrets": { + ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()}, + + UpdatedServiceAccount: serviceAccount(tokenSecretReferences()), + ExpectedActions: []core.Action{}, + }, + "updated serviceaccount with no secrets with resource conflict": { + ClientObjects: []runtime.Object{updatedServiceAccount(emptySecretReferences()), createdTokenSecret()}, + + UpdatedServiceAccount: serviceAccount(emptySecretReferences()), + ExpectedActions: []core.Action{ + core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), + }, + }, + + "deleted serviceaccount with no secrets": { + DeletedServiceAccount: serviceAccount(emptySecretReferences()), + ExpectedActions: []core.Action{}, + }, + "deleted serviceaccount with missing secrets": { + DeletedServiceAccount: serviceAccount(missingSecretReferences()), + ExpectedActions: []core.Action{}, + }, + "deleted serviceaccount with non-token secrets": { + ClientObjects: []runtime.Object{opaqueSecret()}, + + DeletedServiceAccount: serviceAccount(regularSecretReferences()), + ExpectedActions: []core.Action{}, + }, + "deleted serviceaccount with token secrets": { + ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, + ExistingSecrets: []*api.Secret{serviceAccountTokenSecret()}, + + DeletedServiceAccount: serviceAccount(tokenSecretReferences()), + ExpectedActions: []core.Action{ + core.NewDeleteAction("secrets", api.NamespaceDefault, "token-secret-1"), + }, + }, + + "added secret without serviceaccount": { + ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, + + AddedSecret: serviceAccountTokenSecret(), + ExpectedActions: []core.Action{ + core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), + core.NewDeleteAction("secrets", api.NamespaceDefault, "token-secret-1"), + }, + }, + "added secret with serviceaccount": { + ExistingServiceAccount: serviceAccount(tokenSecretReferences()), + + AddedSecret: serviceAccountTokenSecret(), + ExpectedActions: []core.Action{}, + }, + "added token secret without token data": { + ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()}, + ExistingServiceAccount: serviceAccount(tokenSecretReferences()), + + AddedSecret: serviceAccountTokenSecretWithoutTokenData(), + ExpectedActions: []core.Action{ + core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), + }, + }, + "added token secret without ca data": { + ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutCAData()}, + ExistingServiceAccount: serviceAccount(tokenSecretReferences()), + + AddedSecret: serviceAccountTokenSecretWithoutCAData(), + ExpectedActions: []core.Action{ + core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), + }, + }, + "added token secret with mismatched ca data": { + ClientObjects: []runtime.Object{serviceAccountTokenSecretWithCAData([]byte("mismatched"))}, + ExistingServiceAccount: serviceAccount(tokenSecretReferences()), + + AddedSecret: serviceAccountTokenSecretWithCAData([]byte("mismatched")), + ExpectedActions: []core.Action{ + core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), + }, + }, + "added token secret without namespace data": { + ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutNamespaceData()}, + ExistingServiceAccount: serviceAccount(tokenSecretReferences()), + + AddedSecret: serviceAccountTokenSecretWithoutNamespaceData(), + ExpectedActions: []core.Action{ + core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), + }, + }, + "added token secret with custom namespace data": { + ClientObjects: []runtime.Object{serviceAccountTokenSecretWithNamespaceData([]byte("custom"))}, + ExistingServiceAccount: serviceAccount(tokenSecretReferences()), + + AddedSecret: serviceAccountTokenSecretWithNamespaceData([]byte("custom")), + ExpectedActions: []core.Action{ + // no update is performed... the custom namespace is preserved + }, + }, + + "updated secret without serviceaccount": { + ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, + + UpdatedSecret: serviceAccountTokenSecret(), + ExpectedActions: []core.Action{ + core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), + core.NewDeleteAction("secrets", api.NamespaceDefault, "token-secret-1"), + }, + }, + "updated secret with serviceaccount": { + ExistingServiceAccount: serviceAccount(tokenSecretReferences()), + + UpdatedSecret: serviceAccountTokenSecret(), + ExpectedActions: []core.Action{}, + }, + "updated token secret without token data": { + ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()}, + ExistingServiceAccount: serviceAccount(tokenSecretReferences()), + + UpdatedSecret: serviceAccountTokenSecretWithoutTokenData(), + ExpectedActions: []core.Action{ + core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), + }, + }, + "updated token secret without ca data": { + ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutCAData()}, + ExistingServiceAccount: serviceAccount(tokenSecretReferences()), + + UpdatedSecret: serviceAccountTokenSecretWithoutCAData(), + ExpectedActions: []core.Action{ + core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), + }, + }, + "updated token secret with mismatched ca data": { + ClientObjects: []runtime.Object{serviceAccountTokenSecretWithCAData([]byte("mismatched"))}, + ExistingServiceAccount: serviceAccount(tokenSecretReferences()), + + UpdatedSecret: serviceAccountTokenSecretWithCAData([]byte("mismatched")), + ExpectedActions: []core.Action{ + core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), + }, + }, + "updated token secret without namespace data": { + ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutNamespaceData()}, + ExistingServiceAccount: serviceAccount(tokenSecretReferences()), + + UpdatedSecret: serviceAccountTokenSecretWithoutNamespaceData(), + ExpectedActions: []core.Action{ + core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), + }, + }, + "updated token secret with custom namespace data": { + ClientObjects: []runtime.Object{serviceAccountTokenSecretWithNamespaceData([]byte("custom"))}, + ExistingServiceAccount: serviceAccount(tokenSecretReferences()), + + UpdatedSecret: serviceAccountTokenSecretWithNamespaceData([]byte("custom")), + ExpectedActions: []core.Action{ + // no update is performed... the custom namespace is preserved + }, + }, + + "deleted secret without serviceaccount": { + DeletedSecret: serviceAccountTokenSecret(), + ExpectedActions: []core.Action{}, + }, + "deleted secret with serviceaccount with reference": { + ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences())}, + ExistingServiceAccount: serviceAccount(tokenSecretReferences()), + + DeletedSecret: serviceAccountTokenSecret(), + ExpectedActions: []core.Action{ + core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), + core.NewUpdateAction("serviceaccounts", api.NamespaceDefault, serviceAccount(emptySecretReferences())), + }, + }, + "deleted secret with serviceaccount without reference": { + ExistingServiceAccount: serviceAccount(emptySecretReferences()), + + DeletedSecret: serviceAccountTokenSecret(), + ExpectedActions: []core.Action{ + core.NewGetAction("serviceaccounts", api.NamespaceDefault, "default"), + }, + }, + } + + for k, tc := range testcases { + + // Re-seed to reset name generation + utilrand.Seed(1) + + generator := &testGenerator{Token: "ABC"} + + client := fake.NewSimpleClientset(tc.ClientObjects...) + + controller := NewTokensController(client, TokensControllerOptions{TokenGenerator: generator, RootCA: []byte("CA Data")}) + + // Tell the token controller whether its stores have been synced + controller.serviceAccountsSynced = func() bool { return !tc.ServiceAccountsSyncPending } + controller.secretsSynced = func() bool { return !tc.SecretsSyncPending } + + if tc.ExistingServiceAccount != nil { + controller.serviceAccounts.Add(tc.ExistingServiceAccount) + } + for _, s := range tc.ExistingSecrets { + controller.secrets.Add(s) + } + + if tc.AddedServiceAccount != nil { + controller.serviceAccountAdded(tc.AddedServiceAccount) + } + if tc.UpdatedServiceAccount != nil { + controller.serviceAccountUpdated(nil, tc.UpdatedServiceAccount) + } + if tc.DeletedServiceAccount != nil { + controller.serviceAccountDeleted(tc.DeletedServiceAccount) + } + if tc.AddedSecret != nil { + controller.secretAdded(tc.AddedSecret) + } + if tc.UpdatedSecret != nil { + controller.secretUpdated(nil, tc.UpdatedSecret) + } + if tc.DeletedSecret != nil { + controller.secretDeleted(tc.DeletedSecret) + } + + actions := client.Actions() + for i, action := range actions { + if len(tc.ExpectedActions) < i+1 { + t.Errorf("%s: %d unexpected actions: %+v", k, len(actions)-len(tc.ExpectedActions), actions[i:]) + break + } + + expectedAction := tc.ExpectedActions[i] + if !reflect.DeepEqual(expectedAction, action) { + t.Errorf("%s: Expected\n\t%#v\ngot\n\t%#v", k, expectedAction, action) + continue + } + } + + if len(tc.ExpectedActions) > len(actions) { + t.Errorf("%s: %d additional expected actions:%+v", k, len(tc.ExpectedActions)-len(actions), tc.ExpectedActions[len(actions):]) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/conversion/OWNERS b/vendor/k8s.io/kubernetes/pkg/conversion/OWNERS new file mode 100644 index 000000000..a046efc0c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/conversion/OWNERS @@ -0,0 +1,5 @@ +assignees: + - derekwaynecarr + - lavalamp + - smarterclayton + - wojtek-t diff --git a/vendor/k8s.io/kubernetes/pkg/conversion/converter.go b/vendor/k8s.io/kubernetes/pkg/conversion/converter.go index 19cd4bef9..29c9fdc86 100644 --- a/vendor/k8s.io/kubernetes/pkg/conversion/converter.go +++ b/vendor/k8s.io/kubernetes/pkg/conversion/converter.go @@ -95,7 +95,7 @@ func NewConverter(nameFn NameFunc) *Converter { inputFieldMappingFuncs: make(map[reflect.Type]FieldMappingFunc), inputDefaultFlags: make(map[reflect.Type]FieldMatchingFlags), } - c.RegisterConversionFunc(ByteSliceCopy) + c.RegisterConversionFunc(Convert_Slice_byte_To_Slice_byte) return c } @@ -114,8 +114,8 @@ func (c *Converter) DefaultMeta(t reflect.Type) (FieldMatchingFlags, *Meta) { } } -// ByteSliceCopy prevents recursing into every byte -func ByteSliceCopy(in *[]byte, out *[]byte, s Scope) error { +// Convert_Slice_byte_To_Slice_byte prevents recursing into every byte +func Convert_Slice_byte_To_Slice_byte(in *[]byte, out *[]byte, s Scope) error { *out = make([]byte, len(*in)) copy(*out, *in) return nil diff --git a/vendor/k8s.io/kubernetes/pkg/conversion/converter_test.go b/vendor/k8s.io/kubernetes/pkg/conversion/converter_test.go index c639492a7..6dde9af96 100644 --- a/vendor/k8s.io/kubernetes/pkg/conversion/converter_test.go +++ b/vendor/k8s.io/kubernetes/pkg/conversion/converter_test.go @@ -27,7 +27,7 @@ import ( "github.com/google/gofuzz" flag "github.com/spf13/pflag" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" ) var fuzzIters = flag.Int("fuzz-iters", 50, "How many fuzzing iterations to do.") @@ -835,12 +835,12 @@ func objDiff(a, b interface{}) string { if err != nil { panic("b") } - return util.StringDiff(string(ab), string(bb)) + return diff.StringDiff(string(ab), string(bb)) // An alternate diff attempt, in case json isn't showing you // the difference. (reflect.DeepEqual makes a distinction between // nil and empty slices, for example.) - //return util.StringDiff( + //return diff.StringDiff( // fmt.Sprintf("%#v", a), // fmt.Sprintf("%#v", b), //) diff --git a/vendor/k8s.io/kubernetes/pkg/conversion/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/conversion/deep_copy_generated.go new file mode 100644 index 000000000..84e653019 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/conversion/deep_copy_generated.go @@ -0,0 +1,174 @@ +// +build !ignore_autogenerated + +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package conversion + +import ( + forked_reflect "k8s.io/kubernetes/third_party/forked/reflect" + reflect "reflect" +) + +func DeepCopy_conversion_Cloner(in Cloner, out *Cloner, c *Cloner) error { + if in.deepCopyFuncs != nil { + in, out := in.deepCopyFuncs, &out.deepCopyFuncs + *out = make(map[reflect.Type]reflect.Value) + for range in { + // FIXME: Copying unassignable keys unsupported reflect.Type + } + } else { + out.deepCopyFuncs = nil + } + if in.generatedDeepCopyFuncs != nil { + in, out := in.generatedDeepCopyFuncs, &out.generatedDeepCopyFuncs + *out = make(map[reflect.Type]reflect.Value) + for range in { + // FIXME: Copying unassignable keys unsupported reflect.Type + } + } else { + out.generatedDeepCopyFuncs = nil + } + return nil +} + +func DeepCopy_conversion_ConversionFuncs(in ConversionFuncs, out *ConversionFuncs, c *Cloner) error { + if in.fns != nil { + in, out := in.fns, &out.fns + *out = make(map[typePair]reflect.Value) + for range in { + // FIXME: Copying unassignable keys unsupported typePair + } + } else { + out.fns = nil + } + return nil +} + +func DeepCopy_conversion_Converter(in Converter, out *Converter, c *Cloner) error { + if err := DeepCopy_conversion_ConversionFuncs(in.conversionFuncs, &out.conversionFuncs, c); err != nil { + return err + } + if err := DeepCopy_conversion_ConversionFuncs(in.generatedConversionFuncs, &out.generatedConversionFuncs, c); err != nil { + return err + } + if in.ignoredConversions != nil { + in, out := in.ignoredConversions, &out.ignoredConversions + *out = make(map[typePair]struct{}) + for range in { + // FIXME: Copying unassignable keys unsupported typePair + } + } else { + out.ignoredConversions = nil + } + if in.structFieldDests != nil { + in, out := in.structFieldDests, &out.structFieldDests + *out = make(map[typeNamePair][]typeNamePair) + for range in { + // FIXME: Copying unassignable keys unsupported typeNamePair + } + } else { + out.structFieldDests = nil + } + if in.structFieldSources != nil { + in, out := in.structFieldSources, &out.structFieldSources + *out = make(map[typeNamePair][]typeNamePair) + for range in { + // FIXME: Copying unassignable keys unsupported typeNamePair + } + } else { + out.structFieldSources = nil + } + if in.defaultingFuncs != nil { + in, out := in.defaultingFuncs, &out.defaultingFuncs + *out = make(map[reflect.Type]reflect.Value) + for range in { + // FIXME: Copying unassignable keys unsupported reflect.Type + } + } else { + out.defaultingFuncs = nil + } + if in.defaultingInterfaces != nil { + in, out := in.defaultingInterfaces, &out.defaultingInterfaces + *out = make(map[reflect.Type]interface{}) + for range in { + // FIXME: Copying unassignable keys unsupported reflect.Type + } + } else { + out.defaultingInterfaces = nil + } + if in.inputFieldMappingFuncs != nil { + in, out := in.inputFieldMappingFuncs, &out.inputFieldMappingFuncs + *out = make(map[reflect.Type]FieldMappingFunc) + for range in { + // FIXME: Copying unassignable keys unsupported reflect.Type + } + } else { + out.inputFieldMappingFuncs = nil + } + if in.inputDefaultFlags != nil { + in, out := in.inputDefaultFlags, &out.inputDefaultFlags + *out = make(map[reflect.Type]FieldMatchingFlags) + for range in { + // FIXME: Copying unassignable keys unsupported reflect.Type + } + } else { + out.inputDefaultFlags = nil + } + if in.Debug == nil { + out.Debug = nil + } else if newVal, err := c.DeepCopy(in.Debug); err != nil { + return err + } else { + out.Debug = newVal.(DebugLogger) + } + if in.nameFunc == nil { + out.nameFunc = nil + } else if newVal, err := c.DeepCopy(in.nameFunc); err != nil { + return err + } else { + out.nameFunc = newVal.(func(reflect.Type) string) + } + return nil +} + +func DeepCopy_conversion_Equalities(in Equalities, out *Equalities, c *Cloner) error { + if in.Equalities != nil { + in, out := in.Equalities, &out.Equalities + *out = make(forked_reflect.Equalities) + for range in { + // FIXME: Copying unassignable keys unsupported reflect.Type + } + } else { + out.Equalities = nil + } + return nil +} + +func DeepCopy_conversion_Meta(in Meta, out *Meta, c *Cloner) error { + out.SrcVersion = in.SrcVersion + out.DestVersion = in.DestVersion + if in.KeyNameMapping == nil { + out.KeyNameMapping = nil + } else if newVal, err := c.DeepCopy(in.KeyNameMapping); err != nil { + return err + } else { + out.KeyNameMapping = newVal.(FieldMappingFunc) + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/conversion/queryparams/convert.go b/vendor/k8s.io/kubernetes/pkg/conversion/queryparams/convert.go index 0f04e7bb4..63c545697 100644 --- a/vendor/k8s.io/kubernetes/pkg/conversion/queryparams/convert.go +++ b/vendor/k8s.io/kubernetes/pkg/conversion/queryparams/convert.go @@ -23,6 +23,16 @@ import ( "strings" ) +// Marshaler converts an object to a query parameter string representation +type Marshaler interface { + MarshalQueryParameter() (string, error) +} + +// Unmarshaler converts a string representation to an object +type Unmarshaler interface { + UnmarshalQueryParameter(string) error +} + func jsonTag(field reflect.StructField) (string, bool) { structTag := field.Tag.Get("json") if len(structTag) == 0 { @@ -72,6 +82,31 @@ func zeroValue(value reflect.Value) bool { return reflect.DeepEqual(reflect.Zero(value.Type()).Interface(), value.Interface()) } +func customMarshalValue(value reflect.Value) (reflect.Value, bool) { + // Return unless we implement a custom query marshaler + if !value.CanInterface() { + return reflect.Value{}, false + } + + marshaler, ok := value.Interface().(Marshaler) + if !ok { + return reflect.Value{}, false + } + + // Don't invoke functions on nil pointers + // If the type implements MarshalQueryParameter, AND the tag is not omitempty, AND the value is a nil pointer, "" seems like a reasonable response + if isPointerKind(value.Kind()) && zeroValue(value) { + return reflect.ValueOf(""), true + } + + // Get the custom marshalled value + v, err := marshaler.MarshalQueryParameter() + if err != nil { + return reflect.Value{}, false + } + return reflect.ValueOf(v), true +} + func addParam(values url.Values, tag string, omitempty bool, value reflect.Value) { if omitempty && zeroValue(value) { return @@ -128,7 +163,8 @@ func convertStruct(result url.Values, st reflect.Type, sv reflect.Value) { kind := ft.Kind() if isPointerKind(kind) { - kind = ft.Elem().Kind() + ft = ft.Elem() + kind = ft.Kind() if !field.IsNil() { field = reflect.Indirect(field) } @@ -142,7 +178,11 @@ func convertStruct(result url.Values, st reflect.Type, sv reflect.Value) { addListOfParams(result, tag, omitempty, field) } case isStructKind(kind) && !(zeroValue(field) && omitempty): - convertStruct(result, ft, field) + if marshalValue, ok := customMarshalValue(field); ok { + addParam(result, tag, omitempty, marshalValue) + } else { + convertStruct(result, ft, field) + } } } } diff --git a/vendor/k8s.io/kubernetes/pkg/conversion/queryparams/convert_test.go b/vendor/k8s.io/kubernetes/pkg/conversion/queryparams/convert_test.go index 405357557..cbeeeca73 100644 --- a/vendor/k8s.io/kubernetes/pkg/conversion/queryparams/convert_test.go +++ b/vendor/k8s.io/kubernetes/pkg/conversion/queryparams/convert_test.go @@ -20,6 +20,7 @@ import ( "net/url" "reflect" "testing" + "time" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/conversion/queryparams" @@ -61,6 +62,19 @@ type baz struct { func (obj *baz) GetObjectKind() unversioned.ObjectKind { return unversioned.EmptyObjectKind } +// childStructs tests some of the types we serialize to query params for log API calls +// notably, the nested time struct +type childStructs struct { + Container string `json:"container,omitempty"` + Follow bool `json:"follow,omitempty"` + Previous bool `json:"previous,omitempty"` + SinceSeconds *int64 `json:"sinceSeconds,omitempty"` + SinceTime *unversioned.Time `json:"sinceTime,omitempty"` + EmptyTime *unversioned.Time `json:"emptyTime"` +} + +func (obj *childStructs) GetObjectKind() unversioned.ObjectKind { return unversioned.EmptyObjectKind } + func validateResult(t *testing.T, input interface{}, actual, expected url.Values) { local := url.Values{} for k, v := range expected { @@ -73,7 +87,6 @@ func validateResult(t *testing.T, input interface{}, actual, expected url.Values } else { t.Errorf("%#v: values don't match: actual: %#v, expected: %#v", input, v, ev) } - break } delete(local, k) } @@ -83,6 +96,9 @@ func validateResult(t *testing.T, input interface{}, actual, expected url.Values } func TestConvert(t *testing.T) { + sinceSeconds := int64(123) + sinceTime := unversioned.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC) + tests := []struct { input interface{} expected url.Values @@ -158,6 +174,27 @@ func TestConvert(t *testing.T) { }, expected: url.Values{"ptr": {"5"}}, }, + { + input: &childStructs{ + Container: "mycontainer", + Follow: true, + Previous: true, + SinceSeconds: &sinceSeconds, + SinceTime: &sinceTime, // test a custom marshaller + EmptyTime: nil, // test a nil custom marshaller without omitempty + }, + expected: url.Values{"container": {"mycontainer"}, "follow": {"true"}, "previous": {"true"}, "sinceSeconds": {"123"}, "sinceTime": {"2000-01-01T12:34:56Z"}, "emptyTime": {""}}, + }, + { + input: &childStructs{ + Container: "mycontainer", + Follow: true, + Previous: true, + SinceSeconds: &sinceSeconds, + SinceTime: nil, // test a nil custom marshaller with omitempty + }, + expected: url.Values{"container": {"mycontainer"}, "follow": {"true"}, "previous": {"true"}, "sinceSeconds": {"123"}, "emptyTime": {""}}, + }, } for _, test := range tests { diff --git a/vendor/k8s.io/kubernetes/pkg/credentialprovider/OWNERS b/vendor/k8s.io/kubernetes/pkg/credentialprovider/OWNERS new file mode 100644 index 000000000..766c481bd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/credentialprovider/OWNERS @@ -0,0 +1,3 @@ +assignees: + - erictune + - liggitt diff --git a/vendor/k8s.io/kubernetes/pkg/credentialprovider/aws/aws_credentials.go b/vendor/k8s.io/kubernetes/pkg/credentialprovider/aws/aws_credentials.go index 395c438ed..aceda3853 100644 --- a/vendor/k8s.io/kubernetes/pkg/credentialprovider/aws/aws_credentials.go +++ b/vendor/k8s.io/kubernetes/pkg/credentialprovider/aws/aws_credentials.go @@ -140,7 +140,7 @@ func (p *ecrProvider) Provide() credentialprovider.DockerConfig { data.AuthorizationToken != nil { decodedToken, err := base64.StdEncoding.DecodeString(aws.StringValue(data.AuthorizationToken)) if err != nil { - glog.Errorf("while decoding token for endpoint %s %v", data.ProxyEndpoint, err) + glog.Errorf("while decoding token for endpoint %v %v", data.ProxyEndpoint, err) return cfg } parts := strings.SplitN(string(decodedToken), ":", 2) diff --git a/vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/metadata_test.go b/vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/metadata_test.go index 5da1bcd3b..c2e275f1f 100644 --- a/vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/metadata_test.go +++ b/vendor/k8s.io/kubernetes/pkg/credentialprovider/gcp/metadata_test.go @@ -28,6 +28,7 @@ import ( "testing" "k8s.io/kubernetes/pkg/credentialprovider" + utilnet "k8s.io/kubernetes/pkg/util/net" ) func TestDockerKeyringFromGoogleDockerConfigMetadata(t *testing.T) { @@ -60,11 +61,11 @@ func TestDockerKeyringFromGoogleDockerConfigMetadata(t *testing.T) { // defer server.Close() // Make a transport that reroutes all traffic to the example server - transport := &http.Transport{ + transport := utilnet.SetTransportDefaults(&http.Transport{ Proxy: func(req *http.Request) (*url.URL, error) { return url.Parse(server.URL + req.URL.Path) }, - } + }) keyring := &credentialprovider.BasicDockerKeyring{} provider := &dockerConfigKeyProvider{ @@ -133,11 +134,11 @@ func TestDockerKeyringFromGoogleDockerConfigMetadataUrl(t *testing.T) { // defer server.Close() // Make a transport that reroutes all traffic to the example server - transport := &http.Transport{ + transport := utilnet.SetTransportDefaults(&http.Transport{ Proxy: func(req *http.Request) (*url.URL, error) { return url.Parse(server.URL + req.URL.Path) }, - } + }) keyring := &credentialprovider.BasicDockerKeyring{} provider := &dockerConfigUrlKeyProvider{ @@ -207,11 +208,11 @@ func TestContainerRegistryBasics(t *testing.T) { // defer server.Close() // Make a transport that reroutes all traffic to the example server - transport := &http.Transport{ + transport := utilnet.SetTransportDefaults(&http.Transport{ Proxy: func(req *http.Request) (*url.URL, error) { return url.Parse(server.URL + req.URL.Path) }, - } + }) keyring := &credentialprovider.BasicDockerKeyring{} provider := &containerRegistryProvider{ @@ -264,11 +265,11 @@ func TestContainerRegistryNoStorageScope(t *testing.T) { // defer server.Close() // Make a transport that reroutes all traffic to the example server - transport := &http.Transport{ + transport := utilnet.SetTransportDefaults(&http.Transport{ Proxy: func(req *http.Request) (*url.URL, error) { return url.Parse(server.URL + req.URL.Path) }, - } + }) provider := &containerRegistryProvider{ metadataProvider{Client: &http.Client{Transport: transport}}, @@ -298,11 +299,11 @@ func TestComputePlatformScopeSubstitutesStorageScope(t *testing.T) { // defer server.Close() // Make a transport that reroutes all traffic to the example server - transport := &http.Transport{ + transport := utilnet.SetTransportDefaults(&http.Transport{ Proxy: func(req *http.Request) (*url.URL, error) { return url.Parse(server.URL + req.URL.Path) }, - } + }) provider := &containerRegistryProvider{ metadataProvider{Client: &http.Client{Transport: transport}}, @@ -321,11 +322,11 @@ func TestAllProvidersNoMetadata(t *testing.T) { // defer server.Close() // Make a transport that reroutes all traffic to the example server - transport := &http.Transport{ + transport := utilnet.SetTransportDefaults(&http.Transport{ Proxy: func(req *http.Request) (*url.URL, error) { return url.Parse(server.URL + req.URL.Path) }, - } + }) providers := []credentialprovider.DockerConfigProvider{ &dockerConfigKeyProvider{ diff --git a/vendor/k8s.io/kubernetes/pkg/credentialprovider/keyring.go b/vendor/k8s.io/kubernetes/pkg/credentialprovider/keyring.go index 0ea079aee..2378156dc 100644 --- a/vendor/k8s.io/kubernetes/pkg/credentialprovider/keyring.go +++ b/vendor/k8s.io/kubernetes/pkg/credentialprovider/keyring.go @@ -67,7 +67,11 @@ func (dk *BasicDockerKeyring) Add(cfg DockerConfig) { Email: ident.Email, } - parsed, err := url.Parse(loc) + value := loc + if !strings.HasPrefix(value, "https://") && !strings.HasPrefix(value, "http://") { + value = "https://" + value + } + parsed, err := url.Parse(value) if err != nil { glog.Errorf("Entry %q in dockercfg invalid (%v), ignoring", loc, err) continue @@ -77,17 +81,20 @@ func (dk *BasicDockerKeyring) Add(cfg DockerConfig) { // foo.bar.com/namespace // Or hostname matches: // foo.bar.com + // It also considers /v2/ and /v1/ equivalent to the hostname // See ResolveAuthConfig in docker/registry/auth.go. - if parsed.Host != "" { - // NOTE: foo.bar.com comes through as Path. - dk.creds[parsed.Host] = append(dk.creds[parsed.Host], creds) - dk.index = append(dk.index, parsed.Host) + effectivePath := parsed.Path + if strings.HasPrefix(effectivePath, "/v2/") || strings.HasPrefix(effectivePath, "/v1/") { + effectivePath = effectivePath[3:] } - if (len(parsed.Path) > 0) && (parsed.Path != "/") { - key := parsed.Host + parsed.Path - dk.creds[key] = append(dk.creds[key], creds) - dk.index = append(dk.index, key) + var key string + if (len(effectivePath) > 0) && (effectivePath != "/") { + key = parsed.Host + effectivePath + } else { + key = parsed.Host } + dk.creds[key] = append(dk.creds[key], creds) + dk.index = append(dk.index, key) } eliminateDupes := sets.NewString(dk.index...) @@ -100,7 +107,10 @@ func (dk *BasicDockerKeyring) Add(cfg DockerConfig) { sort.Sort(sort.Reverse(sort.StringSlice(dk.index))) } -const defaultRegistryHost = "index.docker.io/v1/" +const ( + defaultRegistryHost = "index.docker.io" + defaultRegistryKey = defaultRegistryHost + "/v1/" +) // isDefaultRegistryMatch determines whether the given image will // pull from the default registry (DockerHub) based on the @@ -223,8 +233,10 @@ func (dk *BasicDockerKeyring) Lookup(image string) ([]docker.AuthConfiguration, } // Use credentials for the default registry if provided, and appropriate - if auth, ok := dk.creds[defaultRegistryHost]; ok && isDefaultRegistryMatch(image) { - return auth, true + if isDefaultRegistryMatch(image) { + if auth, ok := dk.creds[defaultRegistryHost]; ok { + return auth, true + } } return []docker.AuthConfiguration{}, false diff --git a/vendor/k8s.io/kubernetes/pkg/credentialprovider/keyring_test.go b/vendor/k8s.io/kubernetes/pkg/credentialprovider/keyring_test.go index cd92d79b2..72db024be 100644 --- a/vendor/k8s.io/kubernetes/pkg/credentialprovider/keyring_test.go +++ b/vendor/k8s.io/kubernetes/pkg/credentialprovider/keyring_test.go @@ -125,65 +125,77 @@ func TestDockerKeyringForGlob(t *testing.T) { targetUrl string }{ { - globUrl: "hello.kubernetes.io", + globUrl: "https://hello.kubernetes.io", targetUrl: "hello.kubernetes.io", }, { - globUrl: "*.docker.io", + globUrl: "https://*.docker.io", targetUrl: "prefix.docker.io", }, { - globUrl: "prefix.*.io", + globUrl: "https://prefix.*.io", targetUrl: "prefix.docker.io", }, { - globUrl: "prefix.docker.*", + globUrl: "https://prefix.docker.*", targetUrl: "prefix.docker.io", }, { - globUrl: "*.docker.io/path", + globUrl: "https://*.docker.io/path", targetUrl: "prefix.docker.io/path", }, { - globUrl: "prefix.*.io/path", + globUrl: "https://prefix.*.io/path", targetUrl: "prefix.docker.io/path/subpath", }, { - globUrl: "prefix.docker.*/path", + globUrl: "https://prefix.docker.*/path", targetUrl: "prefix.docker.io/path", }, { - globUrl: "*.docker.io:8888", + globUrl: "https://*.docker.io:8888", targetUrl: "prefix.docker.io:8888", }, { - globUrl: "prefix.*.io:8888", + globUrl: "https://prefix.*.io:8888", targetUrl: "prefix.docker.io:8888", }, { - globUrl: "prefix.docker.*:8888", + globUrl: "https://prefix.docker.*:8888", targetUrl: "prefix.docker.io:8888", }, { - globUrl: "*.docker.io/path:1111", + globUrl: "https://*.docker.io/path:1111", targetUrl: "prefix.docker.io/path:1111", }, { - globUrl: "prefix.*.io/path:1111", - targetUrl: "prefix.docker.io/path/subpath:1111", + globUrl: "https://*.docker.io/v1/", + targetUrl: "prefix.docker.io/path:1111", }, { - globUrl: "prefix.docker.*/path:1111", + globUrl: "https://*.docker.io/v2/", targetUrl: "prefix.docker.io/path:1111", }, + { + globUrl: "https://prefix.docker.*/path:1111", + targetUrl: "prefix.docker.io/path:1111", + }, + { + globUrl: "prefix.docker.io:1111", + targetUrl: "prefix.docker.io:1111/path", + }, + { + globUrl: "*.docker.io:1111", + targetUrl: "prefix.docker.io:1111/path", + }, } - for _, test := range tests { + for i, test := range tests { email := "foo@bar.baz" username := "foo" password := "bar" auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password))) sampleDockerConfig := fmt.Sprintf(`{ - "https://%s": { + "%s": { "email": %q, "auth": %q } @@ -198,8 +210,8 @@ func TestDockerKeyringForGlob(t *testing.T) { creds, ok := keyring.Lookup(test.targetUrl + "/foo/bar") if !ok { - t.Errorf("Didn't find expected URL: %s", test.targetUrl) - return + t.Errorf("%d: Didn't find expected URL: %s", i, test.targetUrl) + continue } val := creds[0] @@ -221,21 +233,29 @@ func TestKeyringMiss(t *testing.T) { lookupUrl string }{ { - globUrl: "hello.kubernetes.io", + globUrl: "https://hello.kubernetes.io", lookupUrl: "world.mesos.org/foo/bar", }, { - globUrl: "*.docker.com", + globUrl: "https://*.docker.com", lookupUrl: "prefix.docker.io", }, + { + globUrl: "https://suffix.*.io", + lookupUrl: "prefix.docker.io", + }, + { + globUrl: "https://prefix.docker.c*", + lookupUrl: "prefix.docker.io", + }, + { + globUrl: "https://prefix.*.io/path:1111", + lookupUrl: "prefix.docker.io/path/subpath:1111", + }, { globUrl: "suffix.*.io", lookupUrl: "prefix.docker.io", }, - { - globUrl: "prefix.docker.c*", - lookupUrl: "prefix.docker.io", - }, } for _, test := range tests { email := "foo@bar.baz" @@ -243,7 +263,7 @@ func TestKeyringMiss(t *testing.T) { password := "bar" auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password))) sampleDockerConfig := fmt.Sprintf(`{ - "https://%s": { + "%s": { "email": %q, "auth": %q } @@ -265,7 +285,7 @@ func TestKeyringMiss(t *testing.T) { } func TestKeyringMissWithDockerHubCredentials(t *testing.T) { - url := defaultRegistryHost + url := defaultRegistryKey email := "foo@bar.baz" username := "foo" password := "bar" @@ -291,7 +311,7 @@ func TestKeyringMissWithDockerHubCredentials(t *testing.T) { } func TestKeyringHitWithUnqualifiedDockerHub(t *testing.T) { - url := defaultRegistryHost + url := defaultRegistryKey email := "foo@bar.baz" username := "foo" password := "bar" @@ -332,7 +352,7 @@ func TestKeyringHitWithUnqualifiedDockerHub(t *testing.T) { } func TestKeyringHitWithUnqualifiedLibraryDockerHub(t *testing.T) { - url := defaultRegistryHost + url := defaultRegistryKey email := "foo@bar.baz" username := "foo" password := "bar" @@ -373,7 +393,7 @@ func TestKeyringHitWithUnqualifiedLibraryDockerHub(t *testing.T) { } func TestKeyringHitWithQualifiedDockerHub(t *testing.T) { - url := defaultRegistryHost + url := defaultRegistryKey email := "foo@bar.baz" username := "foo" password := "bar" diff --git a/vendor/k8s.io/kubernetes/pkg/credentialprovider/plugins.go b/vendor/k8s.io/kubernetes/pkg/credentialprovider/plugins.go index cc29ffd83..a871cc02b 100644 --- a/vendor/k8s.io/kubernetes/pkg/credentialprovider/plugins.go +++ b/vendor/k8s.io/kubernetes/pkg/credentialprovider/plugins.go @@ -38,7 +38,7 @@ func RegisterCredentialProvider(name string, provider DockerConfigProvider) { if found { glog.Fatalf("Credential provider %q was registered twice", name) } - glog.V(1).Infof("Registered credential provider %q", name) + glog.V(4).Infof("Registered credential provider %q", name) providers[name] = provider } @@ -53,7 +53,7 @@ func NewDockerKeyring() DockerKeyring { // introduce the notion of priorities for conflict resolution. for name, provider := range providers { if provider.Enabled() { - glog.Infof("Registering credential provider: %v", name) + glog.V(4).Infof("Registering credential provider: %v", name) keyring.Providers = append(keyring.Providers, provider) } } diff --git a/vendor/k8s.io/kubernetes/pkg/credentialprovider/provider.go b/vendor/k8s.io/kubernetes/pkg/credentialprovider/provider.go index f4f52c8fd..ede5765a3 100644 --- a/vendor/k8s.io/kubernetes/pkg/credentialprovider/provider.go +++ b/vendor/k8s.io/kubernetes/pkg/credentialprovider/provider.go @@ -88,7 +88,7 @@ func (d *CachingDockerConfigProvider) Provide() DockerConfig { return d.cacheDockerConfig } - glog.Infof("Refreshing cache for provider: %v", reflect.TypeOf(d.Provider).String()) + glog.V(2).Infof("Refreshing cache for provider: %v", reflect.TypeOf(d.Provider).String()) d.cacheDockerConfig = d.Provider.Provide() d.expiration = time.Now().Add(d.Lifetime) return d.cacheDockerConfig diff --git a/vendor/k8s.io/kubernetes/pkg/genericapiserver/OWNERS b/vendor/k8s.io/kubernetes/pkg/genericapiserver/OWNERS new file mode 100644 index 000000000..76e1b30e9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/genericapiserver/OWNERS @@ -0,0 +1,4 @@ +assignees: + - lavalamp + - nikhiljindal + - smarterclayton diff --git a/vendor/k8s.io/kubernetes/pkg/genericapiserver/doc.go b/vendor/k8s.io/kubernetes/pkg/genericapiserver/doc.go new file mode 100644 index 000000000..12238d568 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/genericapiserver/doc.go @@ -0,0 +1,23 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 genericapiserver contains code to setup a generic kubernetes-like API server. +// This does not contain any kubernetes API specific code. +// Note that this is a work in progress. We are pulling out generic code (specifically from +// pkg/master and pkg/apiserver) here. +// We plan to move this package into a separate repo on github once it is done. +// For more details: https://github.com/kubernetes/kubernetes/issues/2742 +package genericapiserver diff --git a/vendor/k8s.io/kubernetes/pkg/genericapiserver/genericapiserver.go b/vendor/k8s.io/kubernetes/pkg/genericapiserver/genericapiserver.go new file mode 100644 index 000000000..f6006a037 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/genericapiserver/genericapiserver.go @@ -0,0 +1,910 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 genericapiserver + +import ( + "crypto/tls" + "fmt" + "net" + "net/http" + "net/http/pprof" + "os" + "path" + "regexp" + "sort" + "strconv" + "strings" + "time" + + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apimachinery" + "k8s.io/kubernetes/pkg/apiserver" + "k8s.io/kubernetes/pkg/auth/authenticator" + "k8s.io/kubernetes/pkg/auth/authorizer" + "k8s.io/kubernetes/pkg/auth/handlers" + "k8s.io/kubernetes/pkg/registry/generic" + genericetcd "k8s.io/kubernetes/pkg/registry/generic/etcd" + ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" + "k8s.io/kubernetes/pkg/ui" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/crypto" + utilnet "k8s.io/kubernetes/pkg/util/net" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/sets" + + systemd "github.com/coreos/go-systemd/daemon" + "github.com/emicklei/go-restful" + "github.com/emicklei/go-restful/swagger" + "github.com/golang/glog" + "golang.org/x/net/context" +) + +const ( + DefaultEtcdPathPrefix = "/registry" + globalTimeout = time.Minute +) + +// StorageDestinations is a mapping from API group & resource to +// the underlying storage interfaces. +type StorageDestinations struct { + APIGroups map[string]*StorageDestinationsForAPIGroup +} + +type StorageDestinationsForAPIGroup struct { + Default storage.Interface + Overrides map[string]storage.Interface +} + +func NewStorageDestinations() StorageDestinations { + return StorageDestinations{ + APIGroups: map[string]*StorageDestinationsForAPIGroup{}, + } +} + +// AddAPIGroup replaces 'group' if it's already registered. +func (s *StorageDestinations) AddAPIGroup(group string, defaultStorage storage.Interface) { + glog.Infof("Adding storage destination for group %v", group) + s.APIGroups[group] = &StorageDestinationsForAPIGroup{ + Default: defaultStorage, + Overrides: map[string]storage.Interface{}, + } +} + +func (s *StorageDestinations) AddStorageOverride(group, resource string, override storage.Interface) { + if _, ok := s.APIGroups[group]; !ok { + s.AddAPIGroup(group, nil) + } + if s.APIGroups[group].Overrides == nil { + s.APIGroups[group].Overrides = map[string]storage.Interface{} + } + s.APIGroups[group].Overrides[resource] = override +} + +// Get finds the storage destination for the given group and resource. It will +// Fatalf if the group has no storage destination configured. +func (s *StorageDestinations) Get(group, resource string) storage.Interface { + apigroup, ok := s.APIGroups[group] + if !ok { + // TODO: return an error like a normal function. For now, + // Fatalf is better than just logging an error, because this + // condition guarantees future problems and this is a less + // mysterious failure point. + glog.Fatalf("No storage defined for API group: '%s'. Defined groups: %#v", group, s.APIGroups) + return nil + } + if apigroup.Overrides != nil { + if client, exists := apigroup.Overrides[resource]; exists { + return client + } + } + return apigroup.Default +} + +// Search is like Get, but can be used to search a list of groups. It tries the +// groups in order (and Fatalf's if none of them exist). The intention is for +// this to be used for resources that move between groups. +func (s *StorageDestinations) Search(groups []string, resource string) storage.Interface { + for _, group := range groups { + apigroup, ok := s.APIGroups[group] + if !ok { + continue + } + if apigroup.Overrides != nil { + if client, exists := apigroup.Overrides[resource]; exists { + return client + } + } + return apigroup.Default + } + // TODO: return an error like a normal function. For now, + // Fatalf is better than just logging an error, because this + // condition guarantees future problems and this is a less + // mysterious failure point. + glog.Fatalf("No storage defined for any of the groups: %v. Defined groups: %#v", groups, s.APIGroups) + return nil +} + +// Get all backends for all registered storage destinations. +// Used for getting all instances for health validations. +func (s *StorageDestinations) Backends() []string { + backends := sets.String{} + for _, group := range s.APIGroups { + if group.Default != nil { + for _, backend := range group.Default.Backends(context.TODO()) { + backends.Insert(backend) + } + } + if group.Overrides != nil { + for _, storage := range group.Overrides { + for _, backend := range storage.Backends(context.TODO()) { + backends.Insert(backend) + } + } + } + } + return backends.List() +} + +// Info about an API group. +type APIGroupInfo struct { + GroupMeta apimachinery.GroupMeta + // Info about the resources in this group. Its a map from version to resource to the storage. + VersionedResourcesStorageMap map[string]map[string]rest.Storage + // True, if this is the legacy group ("/v1"). + IsLegacyGroup bool + // OptionsExternalVersion controls the APIVersion used for common objects in the + // schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may + // define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. + // If nil, defaults to groupMeta.GroupVersion. + // TODO: Remove this when https://github.com/kubernetes/kubernetes/issues/19018 is fixed. + OptionsExternalVersion *unversioned.GroupVersion + + // Scheme includes all of the types used by this group and how to convert between them (or + // to convert objects from outside of this group that are accepted in this API). + // TODO: replace with interfaces + Scheme *runtime.Scheme + // NegotiatedSerializer controls how this group encodes and decodes data + NegotiatedSerializer runtime.NegotiatedSerializer + // NegotiatedStreamSerializer controls how streaming responses are encoded and decoded. + NegotiatedStreamSerializer runtime.NegotiatedSerializer + // ParameterCodec performs conversions for query parameters passed to API calls + ParameterCodec runtime.ParameterCodec + + // SubresourceGroupVersionKind contains the GroupVersionKind overrides for each subresource that is + // accessible from this API group version. The GroupVersionKind is that of the external version of + // the subresource. The key of this map should be the path of the subresource. The keys here should + // match the keys in the Storage map above for subresources. + SubresourceGroupVersionKind map[string]unversioned.GroupVersionKind +} + +// Config is a structure used to configure a GenericAPIServer. +type Config struct { + StorageDestinations StorageDestinations + // StorageVersions is a map between groups and their storage versions + StorageVersions map[string]string + // allow downstream consumers to disable the core controller loops + EnableLogsSupport bool + EnableUISupport bool + // Allow downstream consumers to disable swagger. + // This includes returning the generated swagger spec at /swaggerapi and swagger ui at /swagger-ui. + EnableSwaggerSupport bool + // Allow downstream consumers to disable swagger ui. + // Note that this is ignored if either EnableSwaggerSupport or EnableUISupport is false. + EnableSwaggerUI bool + // Allows api group versions or specific resources to be conditionally enabled/disabled. + APIResourceConfigSource APIResourceConfigSource + // allow downstream consumers to disable the index route + EnableIndex bool + EnableProfiling bool + EnableWatchCache bool + APIPrefix string + APIGroupPrefix string + CorsAllowedOriginList []string + Authenticator authenticator.Request + // TODO(roberthbailey): Remove once the server no longer supports http basic auth. + SupportsBasicAuth bool + Authorizer authorizer.Authorizer + AdmissionControl admission.Interface + MasterServiceNamespace string + + // Map requests to contexts. Exported so downstream consumers can provider their own mappers + RequestContextMapper api.RequestContextMapper + + // Required, the interface for serializing and converting objects to and from the wire + Serializer runtime.NegotiatedSerializer + + // If specified, all web services will be registered into this container + RestfulContainer *restful.Container + + // If specified, requests will be allocated a random timeout between this value, and twice this value. + // Note that it is up to the request handlers to ignore or honor this timeout. In seconds. + MinRequestTimeout int + + // Number of masters running; all masters must be started with the + // same value for this field. (Numbers > 1 currently untested.) + MasterCount int + + // The port on PublicAddress where a read-write server will be installed. + // Defaults to 6443 if not set. + ReadWritePort int + + // ExternalHost is the host name to use for external (public internet) facing URLs (e.g. Swagger) + ExternalHost string + + // PublicAddress is the IP address where members of the cluster (kubelet, + // kube-proxy, services, etc.) can reach the GenericAPIServer. + // If nil or 0.0.0.0, the host's default interface will be used. + PublicAddress net.IP + + // Control the interval that pod, node IP, and node heath status caches + // expire. + CacheTimeout time.Duration + + // The range of IPs to be assigned to services with type=ClusterIP or greater + ServiceClusterIPRange *net.IPNet + + // The IP address for the GenericAPIServer service (must be inside ServiceClusterIPRange) + ServiceReadWriteIP net.IP + + // Port for the apiserver service. + ServiceReadWritePort int + + // The range of ports to be assigned to services with type=NodePort or greater + ServiceNodePortRange utilnet.PortRange + + // Used to customize default proxy dial/tls options + ProxyDialer apiserver.ProxyDialerFunc + ProxyTLSClientConfig *tls.Config + + // Additional ports to be exposed on the GenericAPIServer service + // extraServicePorts is injectable in the event that more ports + // (other than the default 443/tcp) are exposed on the GenericAPIServer + // and those ports need to be load balanced by the GenericAPIServer + // service because this pkg is linked by out-of-tree projects + // like openshift which want to use the GenericAPIServer but also do + // more stuff. + ExtraServicePorts []api.ServicePort + // Additional ports to be exposed on the GenericAPIServer endpoints + // Port names should align with ports defined in ExtraServicePorts + ExtraEndpointPorts []api.EndpointPort + + KubernetesServiceNodePort int +} + +// GenericAPIServer contains state for a Kubernetes cluster api server. +type GenericAPIServer struct { + // "Inputs", Copied from Config + ServiceClusterIPRange *net.IPNet + ServiceNodePortRange utilnet.PortRange + cacheTimeout time.Duration + MinRequestTimeout time.Duration + + mux apiserver.Mux + MuxHelper *apiserver.MuxHelper + HandlerContainer *restful.Container + RootWebService *restful.WebService + enableLogsSupport bool + enableUISupport bool + enableSwaggerSupport bool + enableSwaggerUI bool + enableProfiling bool + enableWatchCache bool + APIPrefix string + APIGroupPrefix string + corsAllowedOriginList []string + authenticator authenticator.Request + authorizer authorizer.Authorizer + AdmissionControl admission.Interface + MasterCount int + RequestContextMapper api.RequestContextMapper + + // ExternalAddress is the address (hostname or IP and port) that should be used in + // external (public internet) URLs for this GenericAPIServer. + ExternalAddress string + // ClusterIP is the IP address of the GenericAPIServer within the cluster. + ClusterIP net.IP + PublicReadWritePort int + ServiceReadWriteIP net.IP + ServiceReadWritePort int + masterServices *util.Runner + ExtraServicePorts []api.ServicePort + ExtraEndpointPorts []api.EndpointPort + + // storage contains the RESTful endpoints exposed by this GenericAPIServer + storage map[string]rest.Storage + + // Serializer controls how common API objects not in a group/version prefix are serialized for this server. + // Individual APIGroups may define their own serializers. + Serializer runtime.NegotiatedSerializer + + // "Outputs" + Handler http.Handler + InsecureHandler http.Handler + + // Used for custom proxy dialing, and proxy TLS options + ProxyTransport http.RoundTripper + + KubernetesServiceNodePort int + + // Map storing information about all groups to be exposed in discovery response. + // The map is from name to the group. + apiGroupsForDiscovery map[string]unversioned.APIGroup +} + +func (s *GenericAPIServer) StorageDecorator() generic.StorageDecorator { + if s.enableWatchCache { + return genericetcd.StorageWithCacher + } + return generic.UndecoratedStorage +} + +// setDefaults fills in any fields not set that are required to have valid data. +func setDefaults(c *Config) { + if c.ServiceClusterIPRange == nil { + defaultNet := "10.0.0.0/24" + glog.Warningf("Network range for service cluster IPs is unspecified. Defaulting to %v.", defaultNet) + _, serviceClusterIPRange, err := net.ParseCIDR(defaultNet) + if err != nil { + glog.Fatalf("Unable to parse CIDR: %v", err) + } + if size := ipallocator.RangeSize(serviceClusterIPRange); size < 8 { + glog.Fatalf("The service cluster IP range must be at least %d IP addresses", 8) + } + c.ServiceClusterIPRange = serviceClusterIPRange + } + if c.ServiceReadWriteIP == nil { + // Select the first valid IP from ServiceClusterIPRange to use as the GenericAPIServer service IP. + serviceReadWriteIP, err := ipallocator.GetIndexedIP(c.ServiceClusterIPRange, 1) + if err != nil { + glog.Fatalf("Failed to generate service read-write IP for GenericAPIServer service: %v", err) + } + glog.V(4).Infof("Setting GenericAPIServer service IP to %q (read-write).", serviceReadWriteIP) + c.ServiceReadWriteIP = serviceReadWriteIP + } + if c.ServiceReadWritePort == 0 { + c.ServiceReadWritePort = 443 + } + if c.ServiceNodePortRange.Size == 0 { + // TODO: Currently no way to specify an empty range (do we need to allow this?) + // We should probably allow this for clouds that don't require NodePort to do load-balancing (GCE) + // but then that breaks the strict nestedness of ServiceType. + // Review post-v1 + defaultServiceNodePortRange := utilnet.PortRange{Base: 30000, Size: 2768} + c.ServiceNodePortRange = defaultServiceNodePortRange + glog.Infof("Node port range unspecified. Defaulting to %v.", c.ServiceNodePortRange) + } + if c.MasterCount == 0 { + // Clearly, there will be at least one GenericAPIServer. + c.MasterCount = 1 + } + if c.ReadWritePort == 0 { + c.ReadWritePort = 6443 + } + if c.CacheTimeout == 0 { + c.CacheTimeout = 5 * time.Second + } + if c.RequestContextMapper == nil { + c.RequestContextMapper = api.NewRequestContextMapper() + } + if len(c.ExternalHost) == 0 && c.PublicAddress != nil { + hostAndPort := c.PublicAddress.String() + if c.ReadWritePort != 0 { + hostAndPort = net.JoinHostPort(hostAndPort, strconv.Itoa(c.ServiceReadWritePort)) + } + c.ExternalHost = hostAndPort + } +} + +// New returns a new instance of GenericAPIServer from the given config. +// Certain config fields will be set to a default value if unset, +// including: +// ServiceClusterIPRange +// ServiceNodePortRange +// MasterCount +// ReadWritePort +// PublicAddress +// Public fields: +// Handler -- The returned GenericAPIServer has a field TopHandler which is an +// http.Handler which handles all the endpoints provided by the GenericAPIServer, +// including the API, the UI, and miscellaneous debugging endpoints. All +// these are subject to authorization and authentication. +// InsecureHandler -- an http.Handler which handles all the same +// endpoints as Handler, but no authorization and authentication is done. +// Public methods: +// HandleWithAuth -- Allows caller to add an http.Handler for an endpoint +// that uses the same authentication and authorization (if any is configured) +// as the GenericAPIServer's built-in endpoints. +// If the caller wants to add additional endpoints not using the GenericAPIServer's +// auth, then the caller should create a handler for those endpoints, which delegates the +// any unhandled paths to "Handler". +func New(c *Config) (*GenericAPIServer, error) { + if c.Serializer == nil { + return nil, fmt.Errorf("Genericapiserver.New() called with config.Serializer == nil") + } + setDefaults(c) + + s := &GenericAPIServer{ + ServiceClusterIPRange: c.ServiceClusterIPRange, + ServiceNodePortRange: c.ServiceNodePortRange, + RootWebService: new(restful.WebService), + enableLogsSupport: c.EnableLogsSupport, + enableUISupport: c.EnableUISupport, + enableSwaggerSupport: c.EnableSwaggerSupport, + enableSwaggerUI: c.EnableSwaggerUI, + enableProfiling: c.EnableProfiling, + enableWatchCache: c.EnableWatchCache, + APIPrefix: c.APIPrefix, + APIGroupPrefix: c.APIGroupPrefix, + corsAllowedOriginList: c.CorsAllowedOriginList, + authenticator: c.Authenticator, + authorizer: c.Authorizer, + AdmissionControl: c.AdmissionControl, + RequestContextMapper: c.RequestContextMapper, + Serializer: c.Serializer, + + cacheTimeout: c.CacheTimeout, + MinRequestTimeout: time.Duration(c.MinRequestTimeout) * time.Second, + + MasterCount: c.MasterCount, + ExternalAddress: c.ExternalHost, + ClusterIP: c.PublicAddress, + PublicReadWritePort: c.ReadWritePort, + ServiceReadWriteIP: c.ServiceReadWriteIP, + ServiceReadWritePort: c.ServiceReadWritePort, + ExtraServicePorts: c.ExtraServicePorts, + ExtraEndpointPorts: c.ExtraEndpointPorts, + + KubernetesServiceNodePort: c.KubernetesServiceNodePort, + apiGroupsForDiscovery: map[string]unversioned.APIGroup{}, + } + + var handlerContainer *restful.Container + if c.RestfulContainer != nil { + s.mux = c.RestfulContainer.ServeMux + handlerContainer = c.RestfulContainer + } else { + mux := http.NewServeMux() + s.mux = mux + handlerContainer = NewHandlerContainer(mux, c.Serializer) + } + s.HandlerContainer = handlerContainer + // Use CurlyRouter to be able to use regular expressions in paths. Regular expressions are required in paths for example for proxy (where the path is proxy/{kind}/{name}/{*}) + s.HandlerContainer.Router(restful.CurlyRouter{}) + s.MuxHelper = &apiserver.MuxHelper{Mux: s.mux, RegisteredPaths: []string{}} + + s.init(c) + + return s, nil +} + +func (s *GenericAPIServer) NewRequestInfoResolver() *apiserver.RequestInfoResolver { + return &apiserver.RequestInfoResolver{ + APIPrefixes: sets.NewString(strings.Trim(s.APIPrefix, "/"), strings.Trim(s.APIGroupPrefix, "/")), // all possible API prefixes + GrouplessAPIPrefixes: sets.NewString(strings.Trim(s.APIPrefix, "/")), // APIPrefixes that won't have groups (legacy) + } +} + +// HandleWithAuth adds an http.Handler for pattern to an http.ServeMux +// Applies the same authentication and authorization (if any is configured) +// to the request is used for the GenericAPIServer's built-in endpoints. +func (s *GenericAPIServer) HandleWithAuth(pattern string, handler http.Handler) { + // TODO: Add a way for plugged-in endpoints to translate their + // URLs into attributes that an Authorizer can understand, and have + // sensible policy defaults for plugged-in endpoints. This will be different + // for generic endpoints versus REST object endpoints. + // TODO: convert to go-restful + s.MuxHelper.Handle(pattern, handler) +} + +// HandleFuncWithAuth adds an http.Handler for pattern to an http.ServeMux +// Applies the same authentication and authorization (if any is configured) +// to the request is used for the GenericAPIServer's built-in endpoints. +func (s *GenericAPIServer) HandleFuncWithAuth(pattern string, handler func(http.ResponseWriter, *http.Request)) { + // TODO: convert to go-restful + s.MuxHelper.HandleFunc(pattern, handler) +} + +func NewHandlerContainer(mux *http.ServeMux, s runtime.NegotiatedSerializer) *restful.Container { + container := restful.NewContainer() + container.ServeMux = mux + apiserver.InstallRecoverHandler(s, container) + return container +} + +// init initializes GenericAPIServer. +func (s *GenericAPIServer) init(c *Config) { + + if c.ProxyDialer != nil || c.ProxyTLSClientConfig != nil { + s.ProxyTransport = utilnet.SetTransportDefaults(&http.Transport{ + Dial: c.ProxyDialer, + TLSClientConfig: c.ProxyTLSClientConfig, + }) + } + + // Register root handler. + // We do not register this using restful Webservice since we do not want to surface this in api docs. + // Allow GenericAPIServer to be embedded in contexts which already have something registered at the root + if c.EnableIndex { + s.mux.HandleFunc("/", apiserver.IndexHandler(s.HandlerContainer, s.MuxHelper)) + } + + if c.EnableLogsSupport { + apiserver.InstallLogsSupport(s.MuxHelper) + } + if c.EnableUISupport { + ui.InstallSupport(s.MuxHelper, s.enableSwaggerSupport && s.enableSwaggerUI) + } + + if c.EnableProfiling { + s.mux.HandleFunc("/debug/pprof/", pprof.Index) + s.mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + s.mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + } + + handler := http.Handler(s.mux.(*http.ServeMux)) + + // TODO: handle CORS and auth using go-restful + // See github.com/emicklei/go-restful/blob/GenericAPIServer/examples/restful-CORS-filter.go, and + // github.com/emicklei/go-restful/blob/GenericAPIServer/examples/restful-basic-authentication.go + + if len(c.CorsAllowedOriginList) > 0 { + allowedOriginRegexps, err := util.CompileRegexps(c.CorsAllowedOriginList) + if err != nil { + glog.Fatalf("Invalid CORS allowed origin, --cors-allowed-origins flag was set to %v - %v", strings.Join(c.CorsAllowedOriginList, ","), err) + } + handler = apiserver.CORS(handler, allowedOriginRegexps, nil, nil, "true") + } + + s.InsecureHandler = handler + + attributeGetter := apiserver.NewRequestAttributeGetter(s.RequestContextMapper, s.NewRequestInfoResolver()) + handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, s.authorizer) + + // Install Authenticator + if c.Authenticator != nil { + authenticatedHandler, err := handlers.NewRequestAuthenticator(s.RequestContextMapper, c.Authenticator, handlers.Unauthorized(c.SupportsBasicAuth), handler) + if err != nil { + glog.Fatalf("Could not initialize authenticator: %v", err) + } + handler = authenticatedHandler + } + + // TODO: Make this optional? Consumers of GenericAPIServer depend on this currently. + s.Handler = handler + + // After all wrapping is done, put a context filter around both handlers + if handler, err := api.NewRequestContextFilter(s.RequestContextMapper, s.Handler); err != nil { + glog.Fatalf("Could not initialize request context filter: %v", err) + } else { + s.Handler = handler + } + + if handler, err := api.NewRequestContextFilter(s.RequestContextMapper, s.InsecureHandler); err != nil { + glog.Fatalf("Could not initialize request context filter: %v", err) + } else { + s.InsecureHandler = handler + } + + s.installGroupsDiscoveryHandler() +} + +// Exposes the given group versions in API. Helper method to install multiple group versions at once. +func (s *GenericAPIServer) InstallAPIGroups(groupsInfo []APIGroupInfo) error { + for _, apiGroupInfo := range groupsInfo { + if err := s.InstallAPIGroup(&apiGroupInfo); err != nil { + return err + } + } + return nil +} + +// Installs handler at /apis to list all group versions for discovery +func (s *GenericAPIServer) installGroupsDiscoveryHandler() { + apiserver.AddApisWebService(s.Serializer, s.HandlerContainer, s.APIGroupPrefix, func(req *restful.Request) []unversioned.APIGroup { + // Return the list of supported groups in sorted order (to have a deterministic order). + groups := []unversioned.APIGroup{} + groupNames := make([]string, len(s.apiGroupsForDiscovery)) + var i int = 0 + for groupName := range s.apiGroupsForDiscovery { + groupNames[i] = groupName + i++ + } + sort.Strings(groupNames) + for _, groupName := range groupNames { + apiGroup := s.apiGroupsForDiscovery[groupName] + // Add ServerAddressByClientCIDRs. + apiGroup.ServerAddressByClientCIDRs = s.getServerAddressByClientCIDRs(req.Request) + groups = append(groups, apiGroup) + } + return groups + }) +} + +func (s *GenericAPIServer) Run(options *ServerRunOptions) { + if s.enableSwaggerSupport { + s.InstallSwaggerAPI() + } + // We serve on 2 ports. See docs/accessing_the_api.md + secureLocation := "" + if options.SecurePort != 0 { + secureLocation = net.JoinHostPort(options.BindAddress.String(), strconv.Itoa(options.SecurePort)) + } + insecureLocation := net.JoinHostPort(options.InsecureBindAddress.String(), strconv.Itoa(options.InsecurePort)) + + var sem chan bool + if options.MaxRequestsInFlight > 0 { + sem = make(chan bool, options.MaxRequestsInFlight) + } + + longRunningRE := regexp.MustCompile(options.LongRunningRequestRE) + longRunningRequestCheck := apiserver.BasicLongRunningRequestCheck(longRunningRE, map[string]string{"watch": "true"}) + longRunningTimeout := func(req *http.Request) (<-chan time.Time, string) { + // TODO unify this with apiserver.MaxInFlightLimit + if longRunningRequestCheck(req) { + return nil, "" + } + return time.After(globalTimeout), "" + } + + if secureLocation != "" { + handler := apiserver.TimeoutHandler(s.Handler, longRunningTimeout) + secureServer := &http.Server{ + Addr: secureLocation, + Handler: apiserver.MaxInFlightLimit(sem, longRunningRequestCheck, apiserver.RecoverPanics(handler)), + MaxHeaderBytes: 1 << 20, + TLSConfig: &tls.Config{ + // Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability) + MinVersion: tls.VersionTLS10, + }, + } + + if len(options.ClientCAFile) > 0 { + clientCAs, err := crypto.CertPoolFromFile(options.ClientCAFile) + if err != nil { + glog.Fatalf("Unable to load client CA file: %v", err) + } + // Populate PeerCertificates in requests, but don't reject connections without certificates + // This allows certificates to be validated by authenticators, while still allowing other auth types + secureServer.TLSConfig.ClientAuth = tls.RequestClientCert + // Specify allowed CAs for client certificates + secureServer.TLSConfig.ClientCAs = clientCAs + } + + glog.Infof("Serving securely on %s", secureLocation) + if options.TLSCertFile == "" && options.TLSPrivateKeyFile == "" { + options.TLSCertFile = path.Join(options.CertDirectory, "apiserver.crt") + options.TLSPrivateKeyFile = path.Join(options.CertDirectory, "apiserver.key") + // TODO (cjcullen): Is ClusterIP the right address to sign a cert with? + alternateIPs := []net.IP{s.ServiceReadWriteIP} + alternateDNS := []string{"kubernetes.default.svc", "kubernetes.default", "kubernetes"} + // It would be nice to set a fqdn subject alt name, but only the kubelets know, the apiserver is clueless + // alternateDNS = append(alternateDNS, "kubernetes.default.svc.CLUSTER.DNS.NAME") + if shouldGenSelfSignedCerts(options.TLSCertFile, options.TLSPrivateKeyFile) { + if err := crypto.GenerateSelfSignedCert(s.ClusterIP.String(), options.TLSCertFile, options.TLSPrivateKeyFile, alternateIPs, alternateDNS); err != nil { + glog.Errorf("Unable to generate self signed cert: %v", err) + } else { + glog.Infof("Using self-signed cert (%options, %options)", options.TLSCertFile, options.TLSPrivateKeyFile) + } + } + } + + go func() { + defer utilruntime.HandleCrash() + for { + // err == systemd.SdNotifyNoSocket when not running on a systemd system + if err := systemd.SdNotify("READY=1\n"); err != nil && err != systemd.SdNotifyNoSocket { + glog.Errorf("Unable to send systemd daemon successful start message: %v\n", err) + } + if err := secureServer.ListenAndServeTLS(options.TLSCertFile, options.TLSPrivateKeyFile); err != nil { + glog.Errorf("Unable to listen for secure (%v); will try again.", err) + } + time.Sleep(15 * time.Second) + } + }() + } else { + // err == systemd.SdNotifyNoSocket when not running on a systemd system + if err := systemd.SdNotify("READY=1\n"); err != nil && err != systemd.SdNotifyNoSocket { + glog.Errorf("Unable to send systemd daemon successful start message: %v\n", err) + } + } + + handler := apiserver.TimeoutHandler(s.InsecureHandler, longRunningTimeout) + http := &http.Server{ + Addr: insecureLocation, + Handler: apiserver.RecoverPanics(handler), + MaxHeaderBytes: 1 << 20, + } + glog.Infof("Serving insecurely on %s", insecureLocation) + glog.Fatal(http.ListenAndServe()) +} + +// If the file represented by path exists and +// readable, return true otherwise return false. +func canReadFile(path string) bool { + f, err := os.Open(path) + if err != nil { + return false + } + + defer f.Close() + + return true +} + +func shouldGenSelfSignedCerts(certPath, keyPath string) bool { + if canReadFile(certPath) || canReadFile(keyPath) { + glog.Infof("using existing apiserver.crt and apiserver.key files") + return false + } + + return true +} + +// Exposes the given group version in API. +func (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error { + apiPrefix := s.APIGroupPrefix + if apiGroupInfo.IsLegacyGroup { + apiPrefix = s.APIPrefix + } + + // Install REST handlers for all the versions in this group. + apiVersions := []string{} + for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions { + apiVersions = append(apiVersions, groupVersion.Version) + + apiGroupVersion, err := s.getAPIGroupVersion(apiGroupInfo, groupVersion, apiPrefix) + if err != nil { + return err + } + if apiGroupInfo.OptionsExternalVersion != nil { + apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion + } + + if err := apiGroupVersion.InstallREST(s.HandlerContainer); err != nil { + return fmt.Errorf("Unable to setup API %v: %v", apiGroupInfo, err) + } + } + // Install the version handler. + if apiGroupInfo.IsLegacyGroup { + // Add a handler at /api to enumerate the supported api versions. + apiserver.AddApiWebService(s.Serializer, s.HandlerContainer, apiPrefix, func(req *restful.Request) *unversioned.APIVersions { + apiVersionsForDiscovery := unversioned.APIVersions{ + ServerAddressByClientCIDRs: s.getServerAddressByClientCIDRs(req.Request), + Versions: apiVersions, + } + return &apiVersionsForDiscovery + }) + } else { + // Do not register empty group or empty version. Doing so claims /apis/ for the wrong entity to be returned. + // Catching these here places the error much closer to its origin + if len(apiGroupInfo.GroupMeta.GroupVersion.Group) == 0 { + return fmt.Errorf("cannot register handler with an empty group for %#v", *apiGroupInfo) + } + if len(apiGroupInfo.GroupMeta.GroupVersion.Version) == 0 { + return fmt.Errorf("cannot register handler with an empty version for %#v", *apiGroupInfo) + } + + // Add a handler at /apis/ to enumerate all versions supported by this group. + apiVersionsForDiscovery := []unversioned.GroupVersionForDiscovery{} + for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions { + apiVersionsForDiscovery = append(apiVersionsForDiscovery, unversioned.GroupVersionForDiscovery{ + GroupVersion: groupVersion.String(), + Version: groupVersion.Version, + }) + } + preferedVersionForDiscovery := unversioned.GroupVersionForDiscovery{ + GroupVersion: apiGroupInfo.GroupMeta.GroupVersion.String(), + Version: apiGroupInfo.GroupMeta.GroupVersion.Version, + } + apiGroup := unversioned.APIGroup{ + Name: apiGroupInfo.GroupMeta.GroupVersion.Group, + Versions: apiVersionsForDiscovery, + PreferredVersion: preferedVersionForDiscovery, + } + s.AddAPIGroupForDiscovery(apiGroup) + apiserver.AddGroupWebService(s.Serializer, s.HandlerContainer, apiPrefix+"/"+apiGroup.Name, apiGroup) + } + apiserver.InstallServiceErrorHandler(s.Serializer, s.HandlerContainer, s.NewRequestInfoResolver(), apiVersions) + return nil +} + +func (s *GenericAPIServer) AddAPIGroupForDiscovery(apiGroup unversioned.APIGroup) { + s.apiGroupsForDiscovery[apiGroup.Name] = apiGroup +} + +func (s *GenericAPIServer) RemoveAPIGroupForDiscovery(groupName string) { + delete(s.apiGroupsForDiscovery, groupName) +} + +func (s *GenericAPIServer) getServerAddressByClientCIDRs(req *http.Request) []unversioned.ServerAddressByClientCIDR { + addressCIDRMap := []unversioned.ServerAddressByClientCIDR{ + { + ClientCIDR: "0.0.0.0/0", + + ServerAddress: s.ExternalAddress, + }, + } + + // Add internal CIDR if the request came from internal IP. + clientIP := utilnet.GetClientIP(req) + clusterCIDR := s.ServiceClusterIPRange + if clusterCIDR.Contains(clientIP) { + addressCIDRMap = append(addressCIDRMap, unversioned.ServerAddressByClientCIDR{ + ClientCIDR: clusterCIDR.String(), + ServerAddress: net.JoinHostPort(s.ServiceReadWriteIP.String(), strconv.Itoa(s.ServiceReadWritePort)), + }) + } + return addressCIDRMap +} + +func (s *GenericAPIServer) getAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion unversioned.GroupVersion, apiPrefix string) (*apiserver.APIGroupVersion, error) { + storage := make(map[string]rest.Storage) + for k, v := range apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version] { + storage[strings.ToLower(k)] = v + } + version, err := s.newAPIGroupVersion(apiGroupInfo.GroupMeta, groupVersion) + version.Root = apiPrefix + version.Storage = storage + version.ParameterCodec = apiGroupInfo.ParameterCodec + version.Serializer = apiGroupInfo.NegotiatedSerializer + version.StreamSerializer = apiGroupInfo.NegotiatedStreamSerializer + version.Creater = apiGroupInfo.Scheme + version.Convertor = apiGroupInfo.Scheme + version.Typer = apiGroupInfo.Scheme + version.SubresourceGroupVersionKind = apiGroupInfo.SubresourceGroupVersionKind + return version, err +} + +func (s *GenericAPIServer) newAPIGroupVersion(groupMeta apimachinery.GroupMeta, groupVersion unversioned.GroupVersion) (*apiserver.APIGroupVersion, error) { + return &apiserver.APIGroupVersion{ + RequestInfoResolver: s.NewRequestInfoResolver(), + + GroupVersion: groupVersion, + Linker: groupMeta.SelfLinker, + Mapper: groupMeta.RESTMapper, + + Admit: s.AdmissionControl, + Context: s.RequestContextMapper, + + MinRequestTimeout: s.MinRequestTimeout, + }, nil +} + +// InstallSwaggerAPI installs the /swaggerapi/ endpoint to allow schema discovery +// and traversal. It is optional to allow consumers of the Kubernetes GenericAPIServer to +// register their own web services into the Kubernetes mux prior to initialization +// of swagger, so that other resource types show up in the documentation. +func (s *GenericAPIServer) InstallSwaggerAPI() { + hostAndPort := s.ExternalAddress + protocol := "https://" + webServicesUrl := protocol + hostAndPort + + // Enable swagger UI and discovery API + swaggerConfig := swagger.Config{ + WebServicesUrl: webServicesUrl, + WebServices: s.HandlerContainer.RegisteredWebServices(), + ApiPath: "/swaggerapi/", + SwaggerPath: "/swaggerui/", + SwaggerFilePath: "/swagger-ui/", + } + swagger.RegisterSwaggerService(swaggerConfig, s.HandlerContainer) +} diff --git a/vendor/k8s.io/kubernetes/pkg/genericapiserver/genericapiserver_test.go b/vendor/k8s.io/kubernetes/pkg/genericapiserver/genericapiserver_test.go new file mode 100644 index 000000000..545a67ff8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/genericapiserver/genericapiserver_test.go @@ -0,0 +1,405 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 genericapiserver + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/http/httptest" + "reflect" + "strconv" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + apiutil "k8s.io/kubernetes/pkg/api/util" + + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apiserver" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + utilnet "k8s.io/kubernetes/pkg/util/net" + + "github.com/stretchr/testify/assert" +) + +// setUp is a convience function for setting up for (most) tests. +func setUp(t *testing.T) (GenericAPIServer, *etcdtesting.EtcdTestServer, Config, *assert.Assertions) { + etcdServer := etcdtesting.NewEtcdTestClientServer(t) + + genericapiserver := GenericAPIServer{} + config := Config{} + config.PublicAddress = net.ParseIP("192.168.10.4") + + return genericapiserver, etcdServer, config, assert.New(t) +} + +func newMaster(t *testing.T) (*GenericAPIServer, *etcdtesting.EtcdTestServer, Config, *assert.Assertions) { + _, etcdserver, config, assert := setUp(t) + + config.ProxyDialer = func(network, addr string) (net.Conn, error) { return nil, nil } + config.ProxyTLSClientConfig = &tls.Config{} + config.Serializer = api.Codecs + config.APIPrefix = "/api" + config.APIGroupPrefix = "/apis" + + s, err := New(&config) + if err != nil { + t.Fatalf("Error in bringing up the server: %v", err) + } + return s, etcdserver, config, assert +} + +// TestNew verifies that the New function returns a GenericAPIServer +// using the configuration properly. +func TestNew(t *testing.T) { + s, etcdserver, config, assert := newMaster(t) + defer etcdserver.Terminate(t) + + // Verify many of the variables match their config counterparts + assert.Equal(s.enableLogsSupport, config.EnableLogsSupport) + assert.Equal(s.enableUISupport, config.EnableUISupport) + assert.Equal(s.enableSwaggerSupport, config.EnableSwaggerSupport) + assert.Equal(s.enableSwaggerUI, config.EnableSwaggerUI) + assert.Equal(s.enableProfiling, config.EnableProfiling) + assert.Equal(s.APIPrefix, config.APIPrefix) + assert.Equal(s.APIGroupPrefix, config.APIGroupPrefix) + assert.Equal(s.corsAllowedOriginList, config.CorsAllowedOriginList) + assert.Equal(s.authenticator, config.Authenticator) + assert.Equal(s.authorizer, config.Authorizer) + assert.Equal(s.AdmissionControl, config.AdmissionControl) + assert.Equal(s.RequestContextMapper, config.RequestContextMapper) + assert.Equal(s.cacheTimeout, config.CacheTimeout) + assert.Equal(s.ExternalAddress, config.ExternalHost) + assert.Equal(s.ClusterIP, config.PublicAddress) + assert.Equal(s.PublicReadWritePort, config.ReadWritePort) + assert.Equal(s.ServiceReadWriteIP, config.ServiceReadWriteIP) + + // These functions should point to the same memory location + serverDialer, _ := utilnet.Dialer(s.ProxyTransport) + serverDialerFunc := fmt.Sprintf("%p", serverDialer) + configDialerFunc := fmt.Sprintf("%p", config.ProxyDialer) + assert.Equal(serverDialerFunc, configDialerFunc) + + assert.Equal(s.ProxyTransport.(*http.Transport).TLSClientConfig, config.ProxyTLSClientConfig) +} + +// Verifies that AddGroupVersions works as expected. +func TestInstallAPIGroups(t *testing.T) { + _, etcdserver, config, assert := setUp(t) + defer etcdserver.Terminate(t) + + config.ProxyDialer = func(network, addr string) (net.Conn, error) { return nil, nil } + config.ProxyTLSClientConfig = &tls.Config{} + config.APIPrefix = "/apiPrefix" + config.APIGroupPrefix = "/apiGroupPrefix" + config.Serializer = api.Codecs + + s, err := New(&config) + if err != nil { + t.Fatalf("Error in bringing up the server: %v", err) + } + + apiGroupMeta := registered.GroupOrDie(api.GroupName) + extensionsGroupMeta := registered.GroupOrDie(extensions.GroupName) + apiGroupsInfo := []APIGroupInfo{ + { + // legacy group version + GroupMeta: *apiGroupMeta, + VersionedResourcesStorageMap: map[string]map[string]rest.Storage{}, + IsLegacyGroup: true, + ParameterCodec: api.ParameterCodec, + NegotiatedSerializer: api.Codecs, + NegotiatedStreamSerializer: api.StreamCodecs, + }, + { + // extensions group version + GroupMeta: *extensionsGroupMeta, + VersionedResourcesStorageMap: map[string]map[string]rest.Storage{}, + OptionsExternalVersion: &apiGroupMeta.GroupVersion, + ParameterCodec: api.ParameterCodec, + NegotiatedSerializer: api.Codecs, + NegotiatedStreamSerializer: api.StreamCodecs, + }, + } + s.InstallAPIGroups(apiGroupsInfo) + + // TODO: Close() this server when fix #19254 + server := httptest.NewServer(s.HandlerContainer.ServeMux) + validPaths := []string{ + // "/api" + config.APIPrefix, + // "/api/v1" + config.APIPrefix + "/" + apiGroupMeta.GroupVersion.Version, + // "/apis/extensions" + config.APIGroupPrefix + "/" + extensionsGroupMeta.GroupVersion.Group, + // "/apis/extensions/v1beta1" + config.APIGroupPrefix + "/" + extensionsGroupMeta.GroupVersion.String(), + } + for _, path := range validPaths { + _, err := http.Get(server.URL + path) + if !assert.NoError(err) { + t.Errorf("unexpected error: %v, for path: %s", err, path) + } + } +} + +// TestNewHandlerContainer verifies that NewHandlerContainer uses the +// mux provided +func TestNewHandlerContainer(t *testing.T) { + assert := assert.New(t) + mux := http.NewServeMux() + container := NewHandlerContainer(mux, nil) + assert.Equal(mux, container.ServeMux, "ServerMux's do not match") +} + +// TestHandleWithAuth verifies HandleWithAuth adds the path +// to the MuxHelper.RegisteredPaths. +func TestHandleWithAuth(t *testing.T) { + server, etcdserver, _, assert := setUp(t) + defer etcdserver.Terminate(t) + + mh := apiserver.MuxHelper{Mux: http.NewServeMux()} + server.MuxHelper = &mh + handler := func(r http.ResponseWriter, w *http.Request) { w.Write(nil) } + server.HandleWithAuth("/test", http.HandlerFunc(handler)) + + assert.Contains(server.MuxHelper.RegisteredPaths, "/test", "Path not found in MuxHelper") +} + +// TestHandleFuncWithAuth verifies HandleFuncWithAuth adds the path +// to the MuxHelper.RegisteredPaths. +func TestHandleFuncWithAuth(t *testing.T) { + server, etcdserver, _, assert := setUp(t) + defer etcdserver.Terminate(t) + + mh := apiserver.MuxHelper{Mux: http.NewServeMux()} + server.MuxHelper = &mh + handler := func(r http.ResponseWriter, w *http.Request) { w.Write(nil) } + server.HandleFuncWithAuth("/test", handler) + + assert.Contains(server.MuxHelper.RegisteredPaths, "/test", "Path not found in MuxHelper") +} + +// TestInstallSwaggerAPI verifies that the swagger api is added +// at the proper endpoint. +func TestInstallSwaggerAPI(t *testing.T) { + server, etcdserver, _, assert := setUp(t) + defer etcdserver.Terminate(t) + + mux := http.NewServeMux() + server.HandlerContainer = NewHandlerContainer(mux, nil) + + // Ensure swagger isn't installed without the call + ws := server.HandlerContainer.RegisteredWebServices() + if !assert.Equal(len(ws), 0) { + for x := range ws { + assert.NotEqual("/swaggerapi", ws[x].RootPath(), "SwaggerAPI was installed without a call to InstallSwaggerAPI()") + } + } + + // Install swagger and test + server.InstallSwaggerAPI() + ws = server.HandlerContainer.RegisteredWebServices() + if assert.NotEqual(0, len(ws), "SwaggerAPI not installed.") { + assert.Equal("/swaggerapi/", ws[0].RootPath(), "SwaggerAPI did not install to the proper path. %s != /swaggerapi", ws[0].RootPath()) + } + + // Empty externalHost verification + mux = http.NewServeMux() + server.HandlerContainer = NewHandlerContainer(mux, nil) + server.ExternalAddress = "" + server.ClusterIP = net.IPv4(10, 10, 10, 10) + server.PublicReadWritePort = 1010 + server.InstallSwaggerAPI() + if assert.NotEqual(0, len(ws), "SwaggerAPI not installed.") { + assert.Equal("/swaggerapi/", ws[0].RootPath(), "SwaggerAPI did not install to the proper path. %s != /swaggerapi", ws[0].RootPath()) + } +} + +func decodeResponse(resp *http.Response, obj interface{}) error { + defer resp.Body.Close() + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + if err := json.Unmarshal(data, obj); err != nil { + return err + } + return nil +} + +func getGroupList(server *httptest.Server) (*unversioned.APIGroupList, error) { + resp, err := http.Get(server.URL + "/apis") + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected server response, expected %d, actual: %d", http.StatusOK, resp.StatusCode) + } + + groupList := unversioned.APIGroupList{} + err = decodeResponse(resp, &groupList) + return &groupList, err +} + +func TestDiscoveryAtAPIS(t *testing.T) { + master, etcdserver, config, assert := newMaster(t) + defer etcdserver.Terminate(t) + + server := httptest.NewServer(master.HandlerContainer.ServeMux) + groupList, err := getGroupList(server) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + assert.Equal(0, len(groupList.Groups)) + + // Add a Group. + extensionsGroupName := extensions.GroupName + extensionsVersions := []unversioned.GroupVersionForDiscovery{ + { + GroupVersion: testapi.Extensions.GroupVersion().String(), + Version: testapi.Extensions.GroupVersion().Version, + }, + } + extensionsPreferredVersion := unversioned.GroupVersionForDiscovery{ + GroupVersion: config.StorageVersions[extensions.GroupName], + Version: apiutil.GetVersion(config.StorageVersions[extensions.GroupName]), + } + master.AddAPIGroupForDiscovery(unversioned.APIGroup{ + Name: extensionsGroupName, + Versions: extensionsVersions, + PreferredVersion: extensionsPreferredVersion, + }) + + groupList, err = getGroupList(server) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + assert.Equal(1, len(groupList.Groups)) + groupListGroup := groupList.Groups[0] + assert.Equal(extensionsGroupName, groupListGroup.Name) + assert.Equal(extensionsVersions, groupListGroup.Versions) + assert.Equal(extensionsPreferredVersion, groupListGroup.PreferredVersion) + assert.Equal(master.getServerAddressByClientCIDRs(&http.Request{}), groupListGroup.ServerAddressByClientCIDRs) + + // Remove the group. + master.RemoveAPIGroupForDiscovery(extensionsGroupName) + groupList, err = getGroupList(server) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + assert.Equal(0, len(groupList.Groups)) +} + +func TestGetServerAddressByClientCIDRs(t *testing.T) { + s, etcdserver, _, _ := newMaster(t) + defer etcdserver.Terminate(t) + + publicAddressCIDRMap := []unversioned.ServerAddressByClientCIDR{ + { + ClientCIDR: "0.0.0.0/0", + + ServerAddress: s.ExternalAddress, + }, + } + internalAddressCIDRMap := []unversioned.ServerAddressByClientCIDR{ + publicAddressCIDRMap[0], + { + ClientCIDR: s.ServiceClusterIPRange.String(), + ServerAddress: net.JoinHostPort(s.ServiceReadWriteIP.String(), strconv.Itoa(s.ServiceReadWritePort)), + }, + } + internalIP := "10.0.0.1" + publicIP := "1.1.1.1" + testCases := []struct { + Request http.Request + ExpectedMap []unversioned.ServerAddressByClientCIDR + }{ + { + Request: http.Request{}, + ExpectedMap: publicAddressCIDRMap, + }, + { + Request: http.Request{ + Header: map[string][]string{ + "X-Real-Ip": {internalIP}, + }, + }, + ExpectedMap: internalAddressCIDRMap, + }, + { + Request: http.Request{ + Header: map[string][]string{ + "X-Real-Ip": {publicIP}, + }, + }, + ExpectedMap: publicAddressCIDRMap, + }, + { + Request: http.Request{ + Header: map[string][]string{ + "X-Forwarded-For": {internalIP}, + }, + }, + ExpectedMap: internalAddressCIDRMap, + }, + { + Request: http.Request{ + Header: map[string][]string{ + "X-Forwarded-For": {publicIP}, + }, + }, + ExpectedMap: publicAddressCIDRMap, + }, + + { + Request: http.Request{ + RemoteAddr: internalIP, + }, + ExpectedMap: internalAddressCIDRMap, + }, + { + Request: http.Request{ + RemoteAddr: publicIP, + }, + ExpectedMap: publicAddressCIDRMap, + }, + { + Request: http.Request{ + RemoteAddr: "invalidIP", + }, + ExpectedMap: publicAddressCIDRMap, + }, + } + + for i, test := range testCases { + if a, e := s.getServerAddressByClientCIDRs(&test.Request), test.ExpectedMap; reflect.DeepEqual(e, a) != true { + t.Fatalf("test case %d failed. expected: %v, actual: %v", i+1, e, a) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/genericapiserver/resource_config.go b/vendor/k8s.io/kubernetes/pkg/genericapiserver/resource_config.go new file mode 100644 index 000000000..0f40e3774 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/genericapiserver/resource_config.go @@ -0,0 +1,172 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 genericapiserver + +import ( + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/util/sets" +) + +// APIResourceConfigSource is the interface to determine which versions and resources are enabled +type APIResourceConfigSource interface { + AnyVersionOfResourceEnabled(resource unversioned.GroupResource) bool + ResourceEnabled(resource unversioned.GroupVersionResource) bool + AllResourcesForVersionEnabled(version unversioned.GroupVersion) bool + AnyResourcesForVersionEnabled(version unversioned.GroupVersion) bool +} + +// Specifies the overrides for various API group versions. +// This can be used to enable/disable entire group versions or specific resources. +type GroupVersionResourceConfig struct { + // Whether to enable or disable this entire group version. This dominates any enablement check. + // Enable=true means the group version is enabled, and EnabledResources/DisabledResources are considered. + // Enable=false means the group version is disabled, and EnabledResources/DisabledResources are not considered. + Enable bool + + // DisabledResources lists the resources that are specifically disabled for a group/version + // DisabledResources trumps EnabledResources + DisabledResources sets.String + + // EnabledResources lists the resources that should be enabled by default. This is a little + // unusual, but we need it for compatibility with old code for now. An empty set means + // enable all, a non-empty set means that all other resources are disabled. + EnabledResources sets.String +} + +var _ APIResourceConfigSource = &ResourceConfig{} + +type ResourceConfig struct { + GroupVersionResourceConfigs map[unversioned.GroupVersion]*GroupVersionResourceConfig +} + +func NewResourceConfig() *ResourceConfig { + return &ResourceConfig{GroupVersionResourceConfigs: map[unversioned.GroupVersion]*GroupVersionResourceConfig{}} +} + +func NewGroupVersionResourceConfig() *GroupVersionResourceConfig { + return &GroupVersionResourceConfig{Enable: true, DisabledResources: sets.String{}, EnabledResources: sets.String{}} +} + +// DisableVersions disables the versions entirely. No resources (even those whitelisted in EnabledResources) will be enabled +func (o *ResourceConfig) DisableVersions(versions ...unversioned.GroupVersion) { + for _, version := range versions { + _, versionExists := o.GroupVersionResourceConfigs[version] + if !versionExists { + o.GroupVersionResourceConfigs[version] = NewGroupVersionResourceConfig() + } + + o.GroupVersionResourceConfigs[version].Enable = false + } +} + +func (o *ResourceConfig) EnableVersions(versions ...unversioned.GroupVersion) { + for _, version := range versions { + _, versionExists := o.GroupVersionResourceConfigs[version] + if !versionExists { + o.GroupVersionResourceConfigs[version] = NewGroupVersionResourceConfig() + } + + o.GroupVersionResourceConfigs[version].Enable = true + } +} + +func (o *ResourceConfig) DisableResources(resources ...unversioned.GroupVersionResource) { + for _, resource := range resources { + version := resource.GroupVersion() + _, versionExists := o.GroupVersionResourceConfigs[version] + if !versionExists { + o.GroupVersionResourceConfigs[version] = NewGroupVersionResourceConfig() + } + + o.GroupVersionResourceConfigs[version].DisabledResources.Insert(resource.Resource) + } +} + +func (o *ResourceConfig) EnableResources(resources ...unversioned.GroupVersionResource) { + for _, resource := range resources { + version := resource.GroupVersion() + _, versionExists := o.GroupVersionResourceConfigs[version] + if !versionExists { + o.GroupVersionResourceConfigs[version] = NewGroupVersionResourceConfig() + } + + o.GroupVersionResourceConfigs[version].EnabledResources.Insert(resource.Resource) + o.GroupVersionResourceConfigs[version].DisabledResources.Delete(resource.Resource) + } +} + +// AnyResourcesForVersionEnabled only considers matches based on exactly group/resource lexical matching. This means that +// resource renames across versions are NOT considered to be the same resource by this method. You'll need to manually check +// using the ResourceEnabled function. +func (o *ResourceConfig) AnyVersionOfResourceEnabled(resource unversioned.GroupResource) bool { + for version := range o.GroupVersionResourceConfigs { + if version.Group != resource.Group { + continue + } + + if o.ResourceEnabled(version.WithResource(resource.Resource)) { + return true + } + } + + return false +} + +func (o *ResourceConfig) ResourceEnabled(resource unversioned.GroupVersionResource) bool { + versionOverride, versionExists := o.GroupVersionResourceConfigs[resource.GroupVersion()] + if !versionExists { + return false + } + if !versionOverride.Enable { + return false + } + + if versionOverride.DisabledResources.Has(resource.Resource) { + return false + } + + if len(versionOverride.EnabledResources) > 0 { + return versionOverride.EnabledResources.Has(resource.Resource) + } + + return true +} + +func (o *ResourceConfig) AllResourcesForVersionEnabled(version unversioned.GroupVersion) bool { + versionOverride, versionExists := o.GroupVersionResourceConfigs[version] + if !versionExists { + return false + } + if !versionOverride.Enable { + return false + } + + if len(versionOverride.EnabledResources) == 0 && len(versionOverride.DisabledResources) == 0 { + return true + } + + return false +} + +func (o *ResourceConfig) AnyResourcesForVersionEnabled(version unversioned.GroupVersion) bool { + versionOverride, versionExists := o.GroupVersionResourceConfigs[version] + if !versionExists { + return false + } + + return versionOverride.Enable +} diff --git a/vendor/k8s.io/kubernetes/pkg/genericapiserver/resource_config_test.go b/vendor/k8s.io/kubernetes/pkg/genericapiserver/resource_config_test.go new file mode 100644 index 000000000..0fbd6651f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/genericapiserver/resource_config_test.go @@ -0,0 +1,99 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 genericapiserver + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api/unversioned" +) + +func TestDisabledVersion(t *testing.T) { + g1v1 := unversioned.GroupVersion{Group: "group1", Version: "version1"} + g1v2 := unversioned.GroupVersion{Group: "group1", Version: "version2"} + g2v1 := unversioned.GroupVersion{Group: "group2", Version: "version1"} + g3v1 := unversioned.GroupVersion{Group: "group3", Version: "version1"} + + resourceType := "the-resource" + disabledResourceType := "the-disabled-resource" + + config := NewResourceConfig() + + config.DisableVersions(g1v1) + config.EnableVersions(g1v2, g3v1) + config.EnableResources(g1v1.WithResource(resourceType), g2v1.WithResource(resourceType)) + config.DisableResources(g1v2.WithResource(disabledResourceType)) + + expectedEnabledResources := []unversioned.GroupVersionResource{ + g1v2.WithResource(resourceType), + g2v1.WithResource(resourceType), + } + expectedDisabledResources := []unversioned.GroupVersionResource{ + g1v1.WithResource(resourceType), g1v1.WithResource(disabledResourceType), + g1v2.WithResource(disabledResourceType), + g2v1.WithResource(disabledResourceType), + } + + for _, expectedResource := range expectedEnabledResources { + if !config.ResourceEnabled(expectedResource) { + t.Errorf("expected enabled for %v, from %v", expectedResource, config) + } + } + for _, expectedResource := range expectedDisabledResources { + if config.ResourceEnabled(expectedResource) { + t.Errorf("expected disabled for %v, from %v", expectedResource, config) + } + } + + if e, a := false, config.AnyResourcesForVersionEnabled(g1v1); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := false, config.AllResourcesForVersionEnabled(g1v1); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := true, config.AnyResourcesForVersionEnabled(g1v2); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := false, config.AllResourcesForVersionEnabled(g1v2); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := true, config.AnyResourcesForVersionEnabled(g3v1); e != a { + t.Errorf("expected %v, got %v", e, a) + } + if e, a := true, config.AllResourcesForVersionEnabled(g3v1); e != a { + t.Errorf("expected %v, got %v", e, a) + } + + expectedEnabledAnyVersionResources := []unversioned.GroupResource{ + {Group: "group1", Resource: resourceType}, + } + expectedDisabledAnyResources := []unversioned.GroupResource{ + {Group: "group1", Resource: disabledResourceType}, + } + + for _, expectedResource := range expectedEnabledAnyVersionResources { + if !config.AnyVersionOfResourceEnabled(expectedResource) { + t.Errorf("expected enabled for %v, from %v", expectedResource, config) + } + } + for _, expectedResource := range expectedDisabledAnyResources { + if config.AnyVersionOfResourceEnabled(expectedResource) { + t.Errorf("expected disabled for %v, from %v", expectedResource, config) + } + } + +} diff --git a/vendor/k8s.io/kubernetes/pkg/genericapiserver/server_run_options.go b/vendor/k8s.io/kubernetes/pkg/genericapiserver/server_run_options.go new file mode 100644 index 000000000..2ebc3d77a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/genericapiserver/server_run_options.go @@ -0,0 +1,51 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 genericapiserver + +import ( + "net" +) + +const ( + // TODO: This can be tightened up. It still matches objects named watch or proxy. + defaultLongRunningRequestRE = "(/|^)((watch|proxy)(/|$)|(logs?|portforward|exec|attach)/?$)" +) + +// ServerRunOptions contains the options while running a generic api server. +type ServerRunOptions struct { + BindAddress net.IP + CertDirectory string + ClientCAFile string + InsecureBindAddress net.IP + InsecurePort int + LongRunningRequestRE string + MaxRequestsInFlight int + SecurePort int + TLSCertFile string + TLSPrivateKeyFile string +} + +func NewServerRunOptions() *ServerRunOptions { + return &ServerRunOptions{ + BindAddress: net.ParseIP("0.0.0.0"), + CertDirectory: "/var/run/kubernetes", + InsecureBindAddress: net.ParseIP("127.0.0.1"), + InsecurePort: 8080, + LongRunningRequestRE: defaultLongRunningRequestRE, + SecurePort: 6443, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/healthz/doc.go b/vendor/k8s.io/kubernetes/pkg/healthz/doc.go new file mode 100644 index 000000000..37a95b806 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/healthz/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 healthz implements basic http server health checking. +// Usage: +// import _ "healthz" registers a handler on the path '/healthz', that serves 200s +package healthz diff --git a/vendor/k8s.io/kubernetes/pkg/healthz/healthz.go b/vendor/k8s.io/kubernetes/pkg/healthz/healthz.go new file mode 100644 index 000000000..5a9af7aa1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/healthz/healthz.go @@ -0,0 +1,133 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 healthz + +import ( + "bytes" + "fmt" + "net/http" + "sync" +) + +// HealthzChecker is a named healthz check. +type HealthzChecker interface { + Name() string + Check(req *http.Request) error +} + +var defaultHealthz = sync.Once{} + +// DefaultHealthz installs the default healthz check to the http.DefaultServeMux. +func DefaultHealthz(checks ...HealthzChecker) { + defaultHealthz.Do(func() { + InstallHandler(http.DefaultServeMux, checks...) + }) +} + +// PingHealthz returns true automatically when checked +var PingHealthz HealthzChecker = ping{} + +// ping implements the simplest possible health checker. +type ping struct{} + +func (ping) Name() string { + return "ping" +} + +// PingHealthz is a health check that returns true. +func (ping) Check(_ *http.Request) error { + return nil +} + +// NamedCheck returns a health checker for the given name and function. +func NamedCheck(name string, check func(r *http.Request) error) HealthzChecker { + return &healthzCheck{name, check} +} + +// InstallHandler registers a handler for health checking on the path "/healthz" to mux. +func InstallHandler(mux mux, checks ...HealthzChecker) { + if len(checks) == 0 { + checks = []HealthzChecker{PingHealthz} + } + mux.Handle("/healthz", handleRootHealthz(checks...)) + for _, check := range checks { + mux.Handle(fmt.Sprintf("/healthz/%v", check.Name()), adaptCheckToHandler(check.Check)) + } +} + +// mux is an interface describing the methods InstallHandler requires. +type mux interface { + Handle(pattern string, handler http.Handler) +} + +// healthzCheck implements HealthzChecker on an arbitrary name and check function. +type healthzCheck struct { + name string + check func(r *http.Request) error +} + +var _ HealthzChecker = &healthzCheck{} + +func (c *healthzCheck) Name() string { + return c.name +} + +func (c *healthzCheck) Check(r *http.Request) error { + return c.check(r) +} + +// handleRootHealthz returns an http.HandlerFunc that serves the provided checks. +func handleRootHealthz(checks ...HealthzChecker) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + failed := false + var verboseOut bytes.Buffer + for _, check := range checks { + err := check.Check(r) + if err != nil { + fmt.Fprintf(&verboseOut, "[-]%v failed: %v\n", check.Name(), err) + failed = true + } else { + fmt.Fprintf(&verboseOut, "[+]%v ok\n", check.Name()) + } + } + // always be verbose on failure + if failed { + http.Error(w, fmt.Sprintf("%vhealthz check failed", verboseOut.String()), http.StatusInternalServerError) + return + } + + if _, found := r.URL.Query()["verbose"]; !found { + fmt.Fprint(w, "ok") + return + } + + verboseOut.WriteTo(w) + fmt.Fprint(w, "healthz check passed\n") + }) +} + +// adaptCheckToHandler returns an http.HandlerFunc that serves the provided checks. +func adaptCheckToHandler(c func(r *http.Request) error) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + err := c(r) + if err != nil { + http.Error(w, fmt.Sprintf("Internal server error: %v", err), http.StatusInternalServerError) + } else { + fmt.Fprint(w, "ok") + } + }) +} diff --git a/vendor/k8s.io/kubernetes/pkg/healthz/healthz_test.go b/vendor/k8s.io/kubernetes/pkg/healthz/healthz_test.go new file mode 100644 index 000000000..bfd833eb8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/healthz/healthz_test.go @@ -0,0 +1,82 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 healthz + +import ( + "errors" + "fmt" + "net/http" + "net/http/httptest" + "testing" +) + +func TestInstallHandler(t *testing.T) { + mux := http.NewServeMux() + InstallHandler(mux) + req, err := http.NewRequest("GET", "http://example.com/healthz", nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + w := httptest.NewRecorder() + mux.ServeHTTP(w, req) + if w.Code != http.StatusOK { + t.Errorf("Expected %v, got %v", http.StatusOK, w.Code) + } + if w.Body.String() != "ok" { + t.Errorf("Expected %v, got %v", "ok", w.Body.String()) + } +} + +func TestMulitipleChecks(t *testing.T) { + tests := []struct { + path string + expectedResponse string + expectedStatus int + addBadCheck bool + }{ + {"/healthz?verbose", "[+]ping ok\nhealthz check passed\n", http.StatusOK, false}, + {"/healthz/ping", "ok", http.StatusOK, false}, + {"/healthz", "ok", http.StatusOK, false}, + {"/healthz?verbose", "[+]ping ok\n[-]bad failed: this will fail\nhealthz check failed\n", http.StatusInternalServerError, true}, + {"/healthz/ping", "ok", http.StatusOK, true}, + {"/healthz/bad", "Internal server error: this will fail\n", http.StatusInternalServerError, true}, + {"/healthz", "[+]ping ok\n[-]bad failed: this will fail\nhealthz check failed\n", http.StatusInternalServerError, true}, + } + + for i, test := range tests { + mux := http.NewServeMux() + checks := []HealthzChecker{PingHealthz} + if test.addBadCheck { + checks = append(checks, NamedCheck("bad", func(_ *http.Request) error { + return errors.New("this will fail") + })) + } + InstallHandler(mux, checks...) + req, err := http.NewRequest("GET", fmt.Sprintf("http://example.com%v", test.path), nil) + if err != nil { + t.Fatalf("case[%d] Unexpected error: %v", i, err) + } + w := httptest.NewRecorder() + mux.ServeHTTP(w, req) + if w.Code != test.expectedStatus { + t.Errorf("case[%d] Expected: %v, got: %v", i, test.expectedStatus, w.Code) + } + if w.Body.String() != test.expectedResponse { + t.Errorf("case[%d] Expected:\n%v\ngot:\n%v\n", i, test.expectedResponse, w.Body.String()) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/httplog/doc.go b/vendor/k8s.io/kubernetes/pkg/httplog/doc.go new file mode 100644 index 000000000..99973e2d7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/httplog/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 httplog contains a helper object and functions to maintain a log +// along with an http response. +package httplog diff --git a/vendor/k8s.io/kubernetes/pkg/httplog/log.go b/vendor/k8s.io/kubernetes/pkg/httplog/log.go new file mode 100644 index 000000000..2696019f0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/httplog/log.go @@ -0,0 +1,219 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 httplog + +import ( + "bufio" + "fmt" + "net" + "net/http" + "runtime" + "time" + + "github.com/golang/glog" +) + +// Handler wraps all HTTP calls to delegate with nice logging. +// delegate may use LogOf(w).Addf(...) to write additional info to +// the per-request log message. +// +// Intended to wrap calls to your ServeMux. +func Handler(delegate http.Handler, pred StacktracePred) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + defer NewLogged(req, &w).StacktraceWhen(pred).Log() + delegate.ServeHTTP(w, req) + }) +} + +// StacktracePred returns true if a stacktrace should be logged for this status. +type StacktracePred func(httpStatus int) (logStacktrace bool) + +type logger interface { + Addf(format string, data ...interface{}) +} + +// Add a layer on top of ResponseWriter, so we can track latency and error +// message sources. +// +// TODO now that we're using go-restful, we shouldn't need to be wrapping +// the http.ResponseWriter. We can recover panics from go-restful, and +// the logging value is questionable. +type respLogger struct { + hijacked bool + statusRecorded bool + status int + statusStack string + addedInfo string + startTime time.Time + + req *http.Request + w http.ResponseWriter + + logStacktracePred StacktracePred +} + +// Simple logger that logs immediately when Addf is called +type passthroughLogger struct{} + +// Addf logs info immediately. +func (passthroughLogger) Addf(format string, data ...interface{}) { + glog.InfoDepth(1, fmt.Sprintf(format, data...)) +} + +// DefaultStacktracePred is the default implementation of StacktracePred. +func DefaultStacktracePred(status int) bool { + return (status < http.StatusOK || status >= http.StatusInternalServerError) && status != http.StatusSwitchingProtocols +} + +// NewLogged turns a normal response writer into a logged response writer. +// +// Usage: +// +// defer NewLogged(req, &w).StacktraceWhen(StatusIsNot(200, 202)).Log() +// +// (Only the call to Log() is deferred, so you can set everything up in one line!) +// +// Note that this *changes* your writer, to route response writing actions +// through the logger. +// +// Use LogOf(w).Addf(...) to log something along with the response result. +func NewLogged(req *http.Request, w *http.ResponseWriter) *respLogger { + if _, ok := (*w).(*respLogger); ok { + // Don't double-wrap! + panic("multiple NewLogged calls!") + } + rl := &respLogger{ + startTime: time.Now(), + req: req, + w: *w, + logStacktracePred: DefaultStacktracePred, + } + *w = rl // hijack caller's writer! + return rl +} + +// LogOf returns the logger hiding in w. If there is not an existing logger +// then a passthroughLogger will be created which will log to stdout immediately +// when Addf is called. +func LogOf(req *http.Request, w http.ResponseWriter) logger { + if _, exists := w.(*respLogger); !exists { + pl := &passthroughLogger{} + return pl + } + if rl, ok := w.(*respLogger); ok { + return rl + } + panic("Unable to find or create the logger!") +} + +// Unlogged returns the original ResponseWriter, or w if it is not our inserted logger. +func Unlogged(w http.ResponseWriter) http.ResponseWriter { + if rl, ok := w.(*respLogger); ok { + return rl.w + } + return w +} + +// StacktraceWhen sets the stacktrace logging predicate, which decides when to log a stacktrace. +// There's a default, so you don't need to call this unless you don't like the default. +func (rl *respLogger) StacktraceWhen(pred StacktracePred) *respLogger { + rl.logStacktracePred = pred + return rl +} + +// StatusIsNot returns a StacktracePred which will cause stacktraces to be logged +// for any status *not* in the given list. +func StatusIsNot(statuses ...int) StacktracePred { + return func(status int) bool { + for _, s := range statuses { + if status == s { + return false + } + } + return true + } +} + +// Addf adds additional data to be logged with this request. +func (rl *respLogger) Addf(format string, data ...interface{}) { + rl.addedInfo += "\n" + fmt.Sprintf(format, data...) +} + +// Log is intended to be called once at the end of your request handler, via defer +func (rl *respLogger) Log() { + latency := time.Since(rl.startTime) + if glog.V(2) { + if !rl.hijacked { + glog.InfoDepth(1, fmt.Sprintf("%s %s: (%v) %v%v%v [%s %s]", rl.req.Method, rl.req.RequestURI, latency, rl.status, rl.statusStack, rl.addedInfo, rl.req.Header["User-Agent"], rl.req.RemoteAddr)) + } else { + glog.InfoDepth(1, fmt.Sprintf("%s %s: (%v) hijacked [%s %s]", rl.req.Method, rl.req.RequestURI, latency, rl.req.Header["User-Agent"], rl.req.RemoteAddr)) + } + } +} + +// Header implements http.ResponseWriter. +func (rl *respLogger) Header() http.Header { + return rl.w.Header() +} + +// Write implements http.ResponseWriter. +func (rl *respLogger) Write(b []byte) (int, error) { + if !rl.statusRecorded { + rl.recordStatus(http.StatusOK) // Default if WriteHeader hasn't been called + } + return rl.w.Write(b) +} + +// Flush implements http.Flusher even if the underlying http.Writer doesn't implement it. +// Flush is used for streaming purposes and allows to flush buffered data to the client. +func (rl *respLogger) Flush() { + if flusher, ok := rl.w.(http.Flusher); ok { + flusher.Flush() + } else if glog.V(2) { + glog.InfoDepth(1, fmt.Sprintf("Unable to convert %+v into http.Flusher", rl.w)) + } +} + +// WriteHeader implements http.ResponseWriter. +func (rl *respLogger) WriteHeader(status int) { + rl.recordStatus(status) + rl.w.WriteHeader(status) +} + +// Hijack implements http.Hijacker. +func (rl *respLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) { + rl.hijacked = true + return rl.w.(http.Hijacker).Hijack() +} + +// CloseNotify implements http.CloseNotifier +func (rl *respLogger) CloseNotify() <-chan bool { + return rl.w.(http.CloseNotifier).CloseNotify() +} + +func (rl *respLogger) recordStatus(status int) { + rl.status = status + rl.statusRecorded = true + if rl.logStacktracePred(status) { + // Only log stacks for errors + stack := make([]byte, 2048) + stack = stack[:runtime.Stack(stack, false)] + rl.statusStack = "\n" + string(stack) + } else { + rl.statusStack = "" + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/httplog/log_test.go b/vendor/k8s.io/kubernetes/pkg/httplog/log_test.go new file mode 100644 index 000000000..a5d72b1cc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/httplog/log_test.go @@ -0,0 +1,161 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 httplog + +import ( + "bytes" + "net/http" + "net/http/httptest" + "reflect" + "testing" +) + +func TestHandler(t *testing.T) { + want := &httptest.ResponseRecorder{ + HeaderMap: make(http.Header), + Body: new(bytes.Buffer), + } + want.WriteHeader(http.StatusOK) + mux := http.NewServeMux() + handler := Handler(mux, DefaultStacktracePred) + mux.HandleFunc("/kube", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + req, err := http.NewRequest("GET", "http://example.com/kube", nil) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + if !reflect.DeepEqual(want, w) { + t.Errorf("Expected %v, got %v", want, w) + } +} + +func TestStatusIsNot(t *testing.T) { + statusTestTable := []struct { + status int + statuses []int + want bool + }{ + {http.StatusOK, []int{}, true}, + {http.StatusOK, []int{http.StatusOK}, false}, + {http.StatusCreated, []int{http.StatusOK, http.StatusAccepted}, true}, + } + for _, tt := range statusTestTable { + sp := StatusIsNot(tt.statuses...) + got := sp(tt.status) + if got != tt.want { + t.Errorf("Expected %v, got %v", tt.want, got) + } + } +} + +func TestNewLogged(t *testing.T) { + req, err := http.NewRequest("GET", "http://example.com", nil) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + handler := func(w http.ResponseWriter, r *http.Request) { + NewLogged(req, &w) + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected NewLogged to panic") + } + }() + NewLogged(req, &w) + } + w := httptest.NewRecorder() + handler(w, req) +} + +func TestLogOf(t *testing.T) { + logOfTests := []bool{true, false} + for _, makeLogger := range logOfTests { + req, err := http.NewRequest("GET", "http://example.com", nil) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + handler := func(w http.ResponseWriter, r *http.Request) { + var want string + if makeLogger { + NewLogged(req, &w) + want = "*httplog.respLogger" + } else { + want = "*httplog.passthroughLogger" + } + got := reflect.TypeOf(LogOf(r, w)).String() + if want != got { + t.Errorf("Expected %v, got %v", want, got) + } + } + w := httptest.NewRecorder() + handler(w, req) + } +} + +func TestUnlogged(t *testing.T) { + unloggedTests := []bool{true, false} + for _, makeLogger := range unloggedTests { + req, err := http.NewRequest("GET", "http://example.com", nil) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + handler := func(w http.ResponseWriter, r *http.Request) { + want := w + if makeLogger { + NewLogged(req, &w) + } + got := Unlogged(w) + if want != got { + t.Errorf("Expected %v, got %v", want, got) + } + } + w := httptest.NewRecorder() + handler(w, req) + } +} + +type testResponseWriter struct{} + +func (*testResponseWriter) Header() http.Header { return nil } +func (*testResponseWriter) Write([]byte) (int, error) { return 0, nil } +func (*testResponseWriter) WriteHeader(int) {} + +func TestLoggedStatus(t *testing.T) { + req, err := http.NewRequest("GET", "http://example.com", nil) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + var tw http.ResponseWriter = new(testResponseWriter) + logger := NewLogged(req, &tw) + logger.Write(nil) + + if logger.status != http.StatusOK { + t.Errorf("expected status after write to be %v, got %v", http.StatusOK, logger.status) + } + + tw = new(testResponseWriter) + logger = NewLogged(req, &tw) + logger.WriteHeader(http.StatusForbidden) + logger.Write(nil) + + if logger.status != http.StatusForbidden { + t.Errorf("expected status after write to remain %v, got %v", http.StatusForbidden, logger.status) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/hyperkube/doc.go b/vendor/k8s.io/kubernetes/pkg/hyperkube/doc.go new file mode 100644 index 000000000..88b6e2e69 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/hyperkube/doc.go @@ -0,0 +1,30 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 hyperkube is a framework for kubernetes server components. It +// allows us to combine all of the kubernetes server components into a single +// binary where the user selects which components to run in any individual +// process. +// +// Currently, only one server component can be run at once. As such there is +// no need to harmonize flags or identify logs across the various servers. In +// the future we will support launching and running many servers -- either by +// managing processes or running in-proc. +// +// This package is inspired by https://github.com/spf13/cobra. However, as +// the eventual goal is to run *multiple* servers from one call, a new package +// was needed. +package hyperkube diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/OWNERS b/vendor/k8s.io/kubernetes/pkg/kubectl/OWNERS new file mode 100644 index 000000000..66431ddd0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/OWNERS @@ -0,0 +1,7 @@ +assignees: + - bgrant0607 + - brendandburns + - deads2k + - janetkuo + - jlowdermilk + - smarterclayton diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/autoscale.go b/vendor/k8s.io/kubernetes/pkg/kubectl/autoscale.go index e831bca70..e41058d4e 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/autoscale.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/autoscale.go @@ -104,7 +104,7 @@ func (HorizontalPodAutoscalerV1Beta1) Generate(genericParams map[string]interfac scaler.Spec.MinReplicas = &min } if cpu >= 0 { - scaler.Spec.CPUUtilization = &extensions.CPUTargetUtilization{cpu} + scaler.Spec.CPUUtilization = &extensions.CPUTargetUtilization{TargetPercentage: cpu} } return &scaler, nil } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/annotate.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/annotate.go index daebf57d1..9177559b3 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/annotate.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/annotate.go @@ -26,6 +26,7 @@ import ( "github.com/golang/glog" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -52,6 +53,8 @@ type AnnotateOptions struct { f *cmdutil.Factory out io.Writer cmd *cobra.Command + + recursive bool } const ( @@ -68,23 +71,23 @@ limitranges (limits), persistentvolumes (pv), persistentvolumeclaims (pvc), horizontalpodautoscalers (hpa), resourcequotas (quota) or secrets.` annotate_example = `# Update pod 'foo' with the annotation 'description' and the value 'my frontend'. # If the same annotation is set multiple times, only the last value will be applied -$ kubectl annotate pods foo description='my frontend' +kubectl annotate pods foo description='my frontend' # Update a pod identified by type and name in "pod.json" -$ kubectl annotate -f pod.json description='my frontend' +kubectl annotate -f pod.json description='my frontend' # Update pod 'foo' with the annotation 'description' and the value 'my frontend running nginx', overwriting any existing value. -$ kubectl annotate --overwrite pods foo description='my frontend running nginx' +kubectl annotate --overwrite pods foo description='my frontend running nginx' # Update all pods in the namespace -$ kubectl annotate pods --all description='my frontend running nginx' +kubectl annotate pods --all description='my frontend running nginx' # Update pod 'foo' only if the resource is unchanged from version 1. -$ kubectl annotate pods foo description='my frontend running nginx' --resource-version=1 +kubectl annotate pods foo description='my frontend running nginx' --resource-version=1 # Update pod 'foo' by removing an annotation named 'description' if it exists. # Does not require the --overwrite flag. -$ kubectl annotate pods foo description-` +kubectl annotate pods foo description-` ) func NewCmdAnnotate(f *cmdutil.Factory, out io.Writer) *cobra.Command { @@ -108,12 +111,14 @@ func NewCmdAnnotate(f *cmdutil.Factory, out io.Writer) *cobra.Command { }, } cmdutil.AddPrinterFlags(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) cmd.Flags().StringVarP(&options.selector, "selector", "l", "", "Selector (label query) to filter on") cmd.Flags().BoolVar(&options.overwrite, "overwrite", false, "If true, allow annotations to be overwritten, otherwise reject annotation updates that overwrite existing annotations.") cmd.Flags().BoolVar(&options.all, "all", false, "select all resources in the namespace of the specified resource types") cmd.Flags().StringVar(&options.resourceVersion, "resource-version", "", "If non-empty, the annotation update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource.") usage := "Filename, directory, or URL to a file identifying the resource to update the annotation" kubectl.AddJsonFilenameFlag(cmd, &options.filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &options.recursive) cmdutil.AddRecordFlag(cmd) return cmd } @@ -158,11 +163,11 @@ func (o *AnnotateOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra o.recordChangeCause = cmdutil.GetRecordFlag(cmd) o.changeCause = f.Command() - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) o.builder = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). - FilenameParam(enforceNamespace, o.filenames...). + FilenameParam(enforceNamespace, o.recursive, o.filenames...). SelectorParam(o.selector). ResourceTypeOrNameArgs(o.all, o.resources...). Flatten(). @@ -201,7 +206,11 @@ func (o AnnotateOptions) RunAnnotate() error { return err } - name, namespace, obj := info.Name, info.Namespace, info.Object + obj, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion().String()) + if err != nil { + return err + } + name, namespace := info.Name, info.Namespace oldData, err := json.Marshal(obj) if err != nil { return err @@ -239,11 +248,13 @@ func (o AnnotateOptions) RunAnnotate() error { if err != nil { return err } + + mapper, _ := o.f.Object(cmdutil.GetIncludeThirdPartyAPIs(o.cmd)) outputFormat := cmdutil.GetFlagString(o.cmd, "output") if outputFormat != "" { - return o.f.PrintObject(o.cmd, outputObj, o.out) + return o.f.PrintObject(o.cmd, mapper, outputObj, o.out) } - mapper, _ := o.f.Object() + cmdutil.PrintSuccess(mapper, false, o.out, info.Mapping.Resource, info.Name, "annotated") return nil }) @@ -300,14 +311,14 @@ func validateAnnotations(removeAnnotations []string, newAnnotations map[string]s } // validateNoAnnotationOverwrites validates that when overwrite is false, to-be-updated annotations don't exist in the object annotation map (yet) -func validateNoAnnotationOverwrites(meta *api.ObjectMeta, annotations map[string]string) error { +func validateNoAnnotationOverwrites(accessor meta.Object, annotations map[string]string) error { var buf bytes.Buffer for key := range annotations { // change-cause annotation can always be overwritten if key == kubectl.ChangeCauseAnnotation { continue } - if value, found := meta.Annotations[key]; found { + if value, found := accessor.GetAnnotations()[key]; found { if buf.Len() > 0 { buf.WriteString("; ") } @@ -322,29 +333,31 @@ func validateNoAnnotationOverwrites(meta *api.ObjectMeta, annotations map[string // updateAnnotations updates annotations of obj func (o AnnotateOptions) updateAnnotations(obj runtime.Object) error { - meta, err := api.ObjectMetaFor(obj) + accessor, err := meta.Accessor(obj) if err != nil { return err } if !o.overwrite { - if err := validateNoAnnotationOverwrites(meta, o.newAnnotations); err != nil { + if err := validateNoAnnotationOverwrites(accessor, o.newAnnotations); err != nil { return err } } - if meta.Annotations == nil { - meta.Annotations = make(map[string]string) + annotations := accessor.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) } for key, value := range o.newAnnotations { - meta.Annotations[key] = value + annotations[key] = value } for _, annotation := range o.removeAnnotations { - delete(meta.Annotations, annotation) + delete(annotations, annotation) } + accessor.SetAnnotations(annotations) if len(o.resourceVersion) != 0 { - meta.ResourceVersion = o.resourceVersion + accessor.SetResourceVersion(o.resourceVersion) } return nil } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/annotate_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/annotate_test.go index d2f58bf65..56e91666f 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/annotate_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/annotate_test.go @@ -25,7 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/fake" "k8s.io/kubernetes/pkg/runtime" ) @@ -392,7 +392,7 @@ func TestAnnotateErrors(t *testing.T) { f, tf, _ := NewAPIFactory() tf.Printer = &testPrinter{} tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdAnnotate(f, buf) @@ -448,7 +448,7 @@ func TestAnnotateObject(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdAnnotate(f, buf) @@ -498,7 +498,7 @@ func TestAnnotateObjectFromFile(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdAnnotate(f, buf) @@ -551,7 +551,7 @@ func TestAnnotateMultipleObjects(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdAnnotate(f, buf) diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apiversions.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apiversions.go index bc04d3eb5..3c53a5fd1 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apiversions.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apiversions.go @@ -24,7 +24,7 @@ import ( "github.com/spf13/cobra" - unversioned_client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/api/unversioned" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" ) @@ -56,7 +56,7 @@ func RunApiVersions(f *cmdutil.Factory, w io.Writer) error { if err != nil { return fmt.Errorf("Couldn't get available api versions from server: %v\n", err) } - apiVersions := unversioned_client.ExtractGroupVersions(groupList) + apiVersions := unversioned.ExtractGroupVersions(groupList) sort.Strings(apiVersions) for _, v := range apiVersions { fmt.Fprintln(w, v) diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply.go index 54aabc86b..f673ecddb 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply.go @@ -35,6 +35,7 @@ import ( // add them here instead of referencing the cmd.Flags() type ApplyOptions struct { Filenames []string + Recursive bool } const ( @@ -43,10 +44,10 @@ The resource will be created if it doesn't exist yet. JSON and YAML formats are accepted.` apply_example = `# Apply the configuration in pod.json to a pod. -$ kubectl apply -f ./pod.json +kubectl apply -f ./pod.json # Apply the JSON passed into stdin to a pod. -$ cat pod.json | kubectl apply -f -` +cat pod.json | kubectl apply -f -` ) func NewCmdApply(f *cmdutil.Factory, out io.Writer) *cobra.Command { @@ -68,8 +69,10 @@ func NewCmdApply(f *cmdutil.Factory, out io.Writer) *cobra.Command { kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) cmd.MarkFlagRequired("filename") cmdutil.AddValidateFlags(cmd) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) cmdutil.AddOutputFlagsForMutation(cmd) cmdutil.AddRecordFlag(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } @@ -93,12 +96,12 @@ func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *Ap return err } - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). Flatten(). Do() err = r.Err() diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply_test.go index 93a4d304e..a46e0699d 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/apply_test.go @@ -28,6 +28,7 @@ import ( "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/client/unversioned/fake" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -94,33 +95,36 @@ func readServiceFromFile(t *testing.T, filename string) *api.Service { } func annotateRuntimeObject(t *testing.T, originalObj, currentObj runtime.Object, kind string) (string, []byte) { - originalMeta, err := api.ObjectMetaFor(originalObj) + originalAccessor, err := meta.Accessor(originalObj) if err != nil { t.Fatal(err) } - originalMeta.Labels["DELETE_ME"] = "DELETE_ME" + originalLabels := originalAccessor.GetLabels() + originalLabels["DELETE_ME"] = "DELETE_ME" + originalAccessor.SetLabels(originalLabels) original, err := json.Marshal(originalObj) if err != nil { t.Fatal(err) } - currentMeta, err := api.ObjectMetaFor(currentObj) + currentAccessor, err := meta.Accessor(currentObj) if err != nil { t.Fatal(err) } - if currentMeta.Annotations == nil { - currentMeta.Annotations = map[string]string{} + currentAnnotations := currentAccessor.GetAnnotations() + if currentAnnotations == nil { + currentAnnotations = make(map[string]string) } - - currentMeta.Annotations[kubectl.LastAppliedConfigAnnotation] = string(original) + currentAnnotations[kubectl.LastAppliedConfigAnnotation] = string(original) + currentAccessor.SetAnnotations(currentAnnotations) current, err := json.Marshal(currentObj) if err != nil { t.Fatal(err) } - return currentMeta.Name, current + return currentAccessor.GetName(), current } func readAndAnnotateReplicationController(t *testing.T, filename string) (string, []byte) { diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach.go index 22f1ddb3b..3150bccae 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach.go @@ -20,30 +20,31 @@ import ( "fmt" "io" "net/url" - "os" - "os/signal" - "syscall" - "github.com/docker/docker/pkg/term" "github.com/golang/glog" "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/restclient" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/remotecommand" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand" utilerrors "k8s.io/kubernetes/pkg/util/errors" + "k8s.io/kubernetes/pkg/util/interrupt" + "k8s.io/kubernetes/pkg/util/term" ) const ( attach_example = `# Get output from running pod 123456-7890, using the first container by default -$ kubectl attach 123456-7890 +kubectl attach 123456-7890 # Get output from ruby-container from pod 123456-7890 -$ kubectl attach 123456-7890 -c ruby-container +kubectl attach 123456-7890 -c ruby-container # Switch to raw terminal mode, sends stdin to 'bash' in ruby-container from pod 123456-7890 # and sends stdout/stderr from 'bash' back to the client -$ kubectl attach 123456-7890 -c ruby-container -i -t` +kubectl attach 123456-7890 -c ruby-container -i -t` ) func NewCmdAttach(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command { @@ -52,6 +53,8 @@ func NewCmdAttach(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) Out: cmdOut, Err: cmdErr, + CommandName: "kubectl attach", + Attach: &DefaultRemoteAttach{}, } cmd := &cobra.Command{ @@ -74,18 +77,18 @@ func NewCmdAttach(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) // RemoteAttach defines the interface accepted by the Attach command - provided for test stubbing type RemoteAttach interface { - Attach(method string, url *url.URL, config *client.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error + Attach(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error } // DefaultRemoteAttach is the standard implementation of attaching type DefaultRemoteAttach struct{} -func (*DefaultRemoteAttach) Attach(method string, url *url.URL, config *client.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error { +func (*DefaultRemoteAttach) Attach(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error { exec, err := remotecommand.NewExecutor(config, method, url) if err != nil { return err } - return exec.Stream(stdin, stdout, stderr, tty) + return exec.Stream(remotecommandserver.SupportedStreamingProtocols, stdin, stdout, stderr, tty) } // AttachOptions declare the arguments accepted by the Exec command @@ -95,14 +98,20 @@ type AttachOptions struct { ContainerName string Stdin bool TTY bool + CommandName string + + // InterruptParent, if set, is used to handle interrupts while attached + InterruptParent *interrupt.Handler In io.Reader Out io.Writer Err io.Writer + Pod *api.Pod + Attach RemoteAttach Client *client.Client - Config *client.Config + Config *restclient.Config } // Complete verifies command line arguments and loads data from the command environment @@ -153,80 +162,65 @@ func (p *AttachOptions) Validate() error { // Run executes a validated remote execution against a pod. func (p *AttachOptions) Run() error { - pod, err := p.Client.Pods(p.Namespace).Get(p.PodName) - if err != nil { - return err + if p.Pod == nil { + pod, err := p.Client.Pods(p.Namespace).Get(p.PodName) + if err != nil { + return err + } + if pod.Status.Phase != api.PodRunning { + return fmt.Errorf("pod %s is not running and cannot be attached to; current phase is %s", p.PodName, pod.Status.Phase) + } + p.Pod = pod + // TODO: convert this to a clean "wait" behavior } + pod := p.Pod - if pod.Status.Phase != api.PodRunning { - return fmt.Errorf("pod %s is not running and cannot be attached to; current phase is %s", p.PodName, pod.Status.Phase) - } + // ensure we can recover the terminal while attached + t := term.TTY{Parent: p.InterruptParent} - var stdin io.Reader + // check for TTY tty := p.TTY - containerToAttach := p.GetContainer(pod) if tty && !containerToAttach.TTY { tty = false - fmt.Fprintf(p.Err, "Unable to use a TTY - container %s doesn't allocate one\n", containerToAttach.Name) + fmt.Fprintf(p.Err, "Unable to use a TTY - container %s did not allocate one\n", containerToAttach.Name) } - - // TODO: refactor with terminal helpers from the edit utility once that is merged if p.Stdin { - stdin = p.In - if tty { - if file, ok := stdin.(*os.File); ok { - inFd := file.Fd() - if term.IsTerminal(inFd) { - oldState, err := term.SetRawTerminal(inFd) - if err != nil { - glog.Fatal(err) - } - fmt.Fprintln(p.Out, "\nHit enter for command prompt") - // this handles a clean exit, where the command finished - defer term.RestoreTerminal(inFd, oldState) - - // SIGINT is handled by term.SetRawTerminal (it runs a goroutine that listens - // for SIGINT and restores the terminal before exiting) - - // this handles SIGTERM - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGTERM) - go func() { - <-sigChan - term.RestoreTerminal(inFd, oldState) - os.Exit(0) - }() - } else { - fmt.Fprintln(p.Err, "STDIN is not a terminal") - } - } else { - tty = false - fmt.Fprintln(p.Err, "Unable to use a TTY - input is not the right kind of file") - } + t.In = p.In + if tty && !t.IsTerminal() { + tty = false + fmt.Fprintln(p.Err, "Unable to use a TTY - input is not a terminal or the right kind of file") } } + t.Raw = tty - // TODO: consider abstracting into a client invocation or client helper - req := p.Client.RESTClient.Post(). - Resource("pods"). - Name(pod.Name). - Namespace(pod.Namespace). - SubResource("attach") - req.VersionedParams(&api.PodAttachOptions{ - Container: containerToAttach.Name, - Stdin: stdin != nil, - Stdout: p.Out != nil, - Stderr: p.Err != nil, - TTY: tty, - }, api.ParameterCodec) + fn := func() error { + if tty { + fmt.Fprintln(p.Out, "\nHit enter for command prompt") + } + // TODO: consider abstracting into a client invocation or client helper + req := p.Client.RESTClient.Post(). + Resource("pods"). + Name(pod.Name). + Namespace(pod.Namespace). + SubResource("attach") + req.VersionedParams(&api.PodAttachOptions{ + Container: containerToAttach.Name, + Stdin: p.In != nil, + Stdout: p.Out != nil, + Stderr: p.Err != nil, + TTY: tty, + }, api.ParameterCodec) - err = p.Attach.Attach("POST", req.URL(), p.Config, stdin, p.Out, p.Err, tty) - if err != nil { + return p.Attach.Attach("POST", req.URL(), p.Config, p.In, p.Out, p.Err, tty) + } + + if err := t.Safe(fn); err != nil { return err } + if p.Stdin && tty && pod.Spec.RestartPolicy == api.RestartPolicyAlways { - fmt.Fprintf(p.Out, "Session ended, resume using 'kubectl attach %s -c %s -i -t' command when the pod is running\n", pod.Name, containerToAttach.Name) + fmt.Fprintf(p.Out, "Session ended, resume using '%s %s -c %s -i -t' command when the pod is running\n", p.CommandName, pod.Name, containerToAttach.Name) } return nil } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach_test.go index 7fc4b7fe6..fcefb74fb 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach_test.go @@ -30,7 +30,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/fake" ) @@ -40,7 +40,7 @@ type fakeRemoteAttach struct { attachErr error } -func (f *fakeRemoteAttach) Attach(method string, url *url.URL, config *client.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error { +func (f *fakeRemoteAttach) Attach(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error { f.method = method f.url = url return f.attachErr @@ -87,7 +87,7 @@ func TestPodAndContainerAttach(t *testing.T) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { return nil, nil }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{} + tf.ClientConfig = &restclient.Config{} cmd := &cobra.Command{} options := test.p @@ -150,7 +150,7 @@ func TestAttach(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: test.version}}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: test.version}}} bufOut := bytes.NewBuffer([]byte{}) bufErr := bytes.NewBuffer([]byte{}) bufIn := bytes.NewBuffer([]byte{}) @@ -208,7 +208,7 @@ func TestAttachWarnings(t *testing.T) { pod: attachPod(), stdin: true, tty: true, - expectedErr: "Unable to use a TTY - container bar doesn't allocate one", + expectedErr: "Unable to use a TTY - container bar did not allocate one", }, } for _, test := range tests { @@ -227,7 +227,7 @@ func TestAttachWarnings(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: test.version}}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: test.version}}} bufOut := bytes.NewBuffer([]byte{}) bufErr := bytes.NewBuffer([]byte{}) bufIn := bytes.NewBuffer([]byte{}) diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/autoscale.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/autoscale.go index 343c3a8a5..fcb190521 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/autoscale.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/autoscale.go @@ -28,28 +28,36 @@ import ( "github.com/spf13/cobra" ) +// AutoscaleOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of +// referencing the cmd.Flags() +type AutoscaleOptions struct { + Filenames []string + Recursive bool +} + const ( autoscaleLong = `Creates an autoscaler that automatically chooses and sets the number of pods that run in a kubernetes cluster. -Looks up a deployment or replication controller by name and creates an autoscaler that uses this deployment or replication controller as a reference. +Looks up a Deployment, ReplicaSet, or ReplicationController by name and creates an autoscaler that uses the given resource as a reference. An autoscaler can automatically increase or decrease number of pods deployed within the system as needed.` autoscaleExample = `# Auto scale a deployment "foo", with the number of pods between 2 to 10, target CPU utilization at a default value that server applies: -$ kubectl autoscale deployment foo --min=2 --max=10 +kubectl autoscale deployment foo --min=2 --max=10 # Auto scale a replication controller "foo", with the number of pods between 1 to 5, target CPU utilization at 80%: -$ kubectl autoscale rc foo --max=5 --cpu-percent=80` +kubectl autoscale rc foo --max=5 --cpu-percent=80` ) func NewCmdAutoscale(f *cmdutil.Factory, out io.Writer) *cobra.Command { - filenames := []string{} + options := &AutoscaleOptions{} + cmd := &cobra.Command{ Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU] [flags]", - Short: "Auto-scale a deployment or replication controller", + Short: "Auto-scale a Deployment, ReplicaSet, or ReplicationController", Long: autoscaleLong, Example: autoscaleExample, Run: func(cmd *cobra.Command, args []string) { - err := RunAutoscale(f, out, cmd, args, filenames) + err := RunAutoscale(f, out, cmd, args, options) cmdutil.CheckErr(err) }, } @@ -62,13 +70,15 @@ func NewCmdAutoscale(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().String("name", "", "The name for the newly created object. If not specified, the name of the input resource will be used.") cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without creating it.") usage := "Filename, directory, or URL to a file identifying the resource to autoscale." - kubectl.AddJsonFilenameFlag(cmd, &filenames, usage) + kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddRecordFlag(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } -func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, filenames []string) error { +func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *AutoscaleOptions) error { namespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err @@ -79,11 +89,11 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] return err } - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). - FilenameParam(enforceNamespace, filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() @@ -136,7 +146,7 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), Decoder: f.Decoder(true), } - hpa, err := resourceMapper.InfoForObject(object) + hpa, err := resourceMapper.InfoForObject(object, nil) if err != nil { return err } @@ -148,7 +158,7 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] } // TODO: extract this flag to a central location, when such a location exists. if cmdutil.GetFlagBool(cmd, "dry-run") { - return f.PrintObject(cmd, object, out) + return f.PrintObject(cmd, mapper, object, out) } if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), hpa, f.JSONEncoder()); err != nil { @@ -161,7 +171,7 @@ func RunAutoscale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] } if len(cmdutil.GetFlagString(cmd, "output")) > 0 { - return f.PrintObject(cmd, object, out) + return f.PrintObject(cmd, mapper, object, out) } cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, "autoscaled") return nil diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/clusterinfo.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/clusterinfo.go index c76e28f7c..66843bf31 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/clusterinfo.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/clusterinfo.go @@ -42,6 +42,7 @@ func NewCmdClusterInfo(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmdutil.CheckErr(err) }, } + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } @@ -56,7 +57,7 @@ func RunClusterInfo(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command) error } printService(out, "Kubernetes master", client.Host) - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) cmdNamespace := cmdutil.GetFlagString(cmd, "namespace") if cmdNamespace == "" { cmdNamespace = api.NamespaceSystem diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd.go index 2abffaf37..0c47e3b5a 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd.go @@ -23,7 +23,7 @@ import ( cmdconfig "k8s.io/kubernetes/pkg/kubectl/cmd/config" "k8s.io/kubernetes/pkg/kubectl/cmd/rollout" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/flag" "github.com/spf13/cobra" ) @@ -110,8 +110,14 @@ __custom_func() { esac } ` + + // If you add a resource to this list, please also take a look at pkg/kubectl/kubectl.go + // and add a short forms entry in expandResourceShortcut() when appropriate. valid_resources = `Valid resource types include: * componentstatuses (aka 'cs') + * configmaps + * daemonsets (aka 'ds') + * deployments * events (aka 'ev') * endpoints (aka 'ep') * horizontalpodautoscalers (aka 'hpa') @@ -128,7 +134,7 @@ __custom_func() { * replicasets (aka 'rs') * replicationcontrollers (aka 'rc') * secrets - * serviceaccounts + * serviceaccounts (aka 'sa') * services (aka 'svc') ` ) @@ -147,9 +153,10 @@ Find more information at https://github.com/kubernetes/kubernetes.`, } f.BindFlags(cmds.PersistentFlags()) + f.BindExternalFlags(cmds.PersistentFlags()) // From this point and forward we get warnings on flags that contain "_" separators - cmds.SetGlobalNormalizationFunc(util.WarnWordSepNormalizeFunc) + cmds.SetGlobalNormalizationFunc(flag.WarnWordSepNormalizeFunc) cmds.AddCommand(NewCmdGet(f, out)) cmds.AddCommand(NewCmdDescribe(f, out)) @@ -157,7 +164,7 @@ Find more information at https://github.com/kubernetes/kubernetes.`, cmds.AddCommand(NewCmdReplace(f, out)) cmds.AddCommand(NewCmdPatch(f, out)) cmds.AddCommand(NewCmdDelete(f, out)) - cmds.AddCommand(NewCmdEdit(f, out)) + cmds.AddCommand(NewCmdEdit(f, out, err)) cmds.AddCommand(NewCmdApply(f, out)) cmds.AddCommand(NewCmdNamespace(out)) diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd_test.go index 82783c34f..e0b638937 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/cmd_test.go @@ -32,6 +32,7 @@ import ( "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/client/restclient" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/fake" "k8s.io/kubernetes/pkg/kubectl" @@ -167,7 +168,7 @@ type testFactory struct { Printer kubectl.ResourcePrinter Validator validation.Schema Namespace string - ClientConfig *client.Config + ClientConfig *restclient.Config Err error } @@ -179,8 +180,17 @@ func NewTestFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) { Typer: scheme, } return &cmdutil.Factory{ - Object: func() (meta.RESTMapper, runtime.ObjectTyper) { - return t.Mapper, t.Typer + Object: func(discovery bool) (meta.RESTMapper, runtime.ObjectTyper) { + priorityRESTMapper := meta.PriorityRESTMapper{ + Delegate: t.Mapper, + ResourcePriority: []unversioned.GroupVersionResource{ + {Group: meta.AnyGroup, Version: "v1", Resource: meta.AnyResource}, + }, + KindPriority: []unversioned.GroupVersionKind{ + {Group: meta.AnyGroup, Version: "v1", Kind: meta.AnyKind}, + }, + } + return priorityRESTMapper, t.Typer }, ClientForMapping: func(*meta.RESTMapping) (resource.RESTClient, error) { return t.Client, t.Err @@ -203,7 +213,7 @@ func NewTestFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) { DefaultNamespace: func() (string, bool, error) { return t.Namespace, false, t.Err }, - ClientConfig: func() (*client.Config, error) { + ClientConfig: func() (*restclient.Config, error) { return t.ClientConfig, t.Err }, }, t, codec @@ -211,8 +221,17 @@ func NewTestFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) { func NewMixedFactory(apiClient resource.RESTClient) (*cmdutil.Factory, *testFactory, runtime.Codec) { f, t, c := NewTestFactory() - f.Object = func() (meta.RESTMapper, runtime.ObjectTyper) { - return meta.MultiRESTMapper{t.Mapper, testapi.Default.RESTMapper()}, runtime.MultiObjectTyper{t.Typer, api.Scheme} + f.Object = func(discovery bool) (meta.RESTMapper, runtime.ObjectTyper) { + priorityRESTMapper := meta.PriorityRESTMapper{ + Delegate: meta.MultiRESTMapper{t.Mapper, testapi.Default.RESTMapper()}, + ResourcePriority: []unversioned.GroupVersionResource{ + {Group: meta.AnyGroup, Version: "v1", Resource: meta.AnyResource}, + }, + KindPriority: []unversioned.GroupVersionKind{ + {Group: meta.AnyGroup, Version: "v1", Kind: meta.AnyKind}, + }, + } + return priorityRESTMapper, runtime.MultiObjectTyper{t.Typer, api.Scheme} } f.ClientForMapping = func(m *meta.RESTMapping) (resource.RESTClient, error) { if m.ObjectConvertor == api.Scheme { @@ -229,7 +248,7 @@ func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) { } f := &cmdutil.Factory{ - Object: func() (meta.RESTMapper, runtime.ObjectTyper) { + Object: func(discovery bool) (meta.RESTMapper, runtime.ObjectTyper) { return testapi.Default.RESTMapper(), api.Scheme }, Client: func() (*client.Client, error) { @@ -261,13 +280,13 @@ func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) { DefaultNamespace: func() (string, bool, error) { return t.Namespace, false, t.Err }, - ClientConfig: func() (*client.Config, error) { + ClientConfig: func() (*restclient.Config, error) { return t.ClientConfig, t.Err }, Generators: func(cmdName string) map[string]kubectl.Generator { return cmdutil.DefaultGenerators(cmdName) }, - LogsForObject: func(object, options runtime.Object) (*client.Request, error) { + LogsForObject: func(object, options runtime.Object) (*restclient.Request, error) { fakeClient := t.Client.(*fake.RESTClient) c := client.NewOrDie(t.ClientConfig) c.Client = fakeClient.Client @@ -325,7 +344,7 @@ func stringBody(body string) io.ReadCloser { // } //} -func ExamplePrintReplicationControllerWithNamespace() { +func Example_printReplicationControllerWithNamespace() { f, tf, codec := NewAPIFactory() tf.Printer = kubectl.NewHumanReadablePrinter(false, true, false, false, false, false, []string{}) tf.Client = &fake.RESTClient{ @@ -357,17 +376,21 @@ func ExamplePrintReplicationControllerWithNamespace() { }, }, }, + Status: api.ReplicationControllerStatus{ + Replicas: 1, + }, } - err := f.PrintObject(cmd, ctrl, os.Stdout) + mapper, _ := f.Object(false) + err := f.PrintObject(cmd, mapper, ctrl, os.Stdout) if err != nil { fmt.Printf("Unexpected error: %v", err) } // Output: - // NAMESPACE CONTROLLER REPLICAS AGE - // beep foo 1 10y + // NAMESPACE NAME DESIRED CURRENT AGE + // beep foo 1 1 10y } -func ExamplePrintReplicationControllerWithWide() { +func Example_printMultiContainersReplicationControllerWithWide() { f, tf, codec := NewAPIFactory() tf.Printer = kubectl.NewHumanReadablePrinter(false, false, true, false, false, false, []string{}) tf.Client = &fake.RESTClient{ @@ -394,21 +417,78 @@ func ExamplePrintReplicationControllerWithWide() { Name: "foo", Image: "someimage", }, + { + Name: "foo2", + Image: "someimage2", + }, }, }, }, }, + Status: api.ReplicationControllerStatus{ + Replicas: 1, + }, } - err := f.PrintObject(cmd, ctrl, os.Stdout) + mapper, _ := f.Object(false) + err := f.PrintObject(cmd, mapper, ctrl, os.Stdout) if err != nil { fmt.Printf("Unexpected error: %v", err) } // Output: - // CONTROLLER REPLICAS AGE CONTAINER(S) IMAGE(S) SELECTOR - // foo 1 10y foo someimage foo=bar + // NAME DESIRED CURRENT AGE CONTAINER(S) IMAGE(S) SELECTOR + // foo 1 1 10y foo,foo2 someimage,someimage2 foo=bar } -func ExamplePrintPodWithWideFormat() { +func Example_printReplicationController() { + f, tf, codec := NewAPIFactory() + tf.Printer = kubectl.NewHumanReadablePrinter(false, false, false, false, false, false, []string{}) + tf.Client = &fake.RESTClient{ + Codec: codec, + Client: nil, + } + cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr) + ctrl := &api.ReplicationController{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"foo": "bar"}, + CreationTimestamp: unversioned.Time{Time: time.Now().AddDate(-10, 0, 0)}, + }, + Spec: api.ReplicationControllerSpec{ + Replicas: 1, + Selector: map[string]string{"foo": "bar"}, + Template: &api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"foo": "bar"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "foo", + Image: "someimage", + }, + { + Name: "foo2", + Image: "someimage", + }, + }, + }, + }, + }, + Status: api.ReplicationControllerStatus{ + Replicas: 1, + }, + } + mapper, _ := f.Object(false) + err := f.PrintObject(cmd, mapper, ctrl, os.Stdout) + if err != nil { + fmt.Printf("Unexpected error: %v", err) + } + // Output: + // NAME DESIRED CURRENT AGE + // foo 1 1 10y +} + +func Example_printPodWithWideFormat() { f, tf, codec := NewAPIFactory() tf.Printer = kubectl.NewHumanReadablePrinter(false, false, true, false, false, false, []string{}) tf.Client = &fake.RESTClient{ @@ -434,7 +514,8 @@ func ExamplePrintPodWithWideFormat() { }, }, } - err := f.PrintObject(cmd, pod, os.Stdout) + mapper, _ := f.Object(false) + err := f.PrintObject(cmd, mapper, pod, os.Stdout) if err != nil { fmt.Printf("Unexpected error: %v", err) } @@ -443,7 +524,7 @@ func ExamplePrintPodWithWideFormat() { // test1 1/2 podPhase 6 10y kubernetes-minion-abcd } -func ExamplePrintPodWithShowLabels() { +func Example_printPodWithShowLabels() { f, tf, codec := NewAPIFactory() tf.Printer = kubectl.NewHumanReadablePrinter(false, false, false, false, true, false, []string{}) tf.Client = &fake.RESTClient{ @@ -473,7 +554,8 @@ func ExamplePrintPodWithShowLabels() { }, }, } - err := f.PrintObject(cmd, pod, os.Stdout) + mapper, _ := f.Object(false) + err := f.PrintObject(cmd, mapper, pod, os.Stdout) if err != nil { fmt.Printf("Unexpected error: %v", err) } @@ -489,7 +571,7 @@ func newAllPhasePodList() *api.PodList { { ObjectMeta: api.ObjectMeta{ Name: "test1", - CreationTimestamp: unversioned.Time{time.Now().AddDate(-10, 0, 0)}, + CreationTimestamp: unversioned.Time{Time: time.Now().AddDate(-10, 0, 0)}, }, Spec: api.PodSpec{ Containers: make([]api.Container, 2), @@ -506,7 +588,7 @@ func newAllPhasePodList() *api.PodList { { ObjectMeta: api.ObjectMeta{ Name: "test2", - CreationTimestamp: unversioned.Time{time.Now().AddDate(-10, 0, 0)}, + CreationTimestamp: unversioned.Time{Time: time.Now().AddDate(-10, 0, 0)}, }, Spec: api.PodSpec{ Containers: make([]api.Container, 2), @@ -523,7 +605,7 @@ func newAllPhasePodList() *api.PodList { { ObjectMeta: api.ObjectMeta{ Name: "test3", - CreationTimestamp: unversioned.Time{time.Now().AddDate(-10, 0, 0)}, + CreationTimestamp: unversioned.Time{Time: time.Now().AddDate(-10, 0, 0)}, }, Spec: api.PodSpec{ Containers: make([]api.Container, 2), @@ -540,7 +622,7 @@ func newAllPhasePodList() *api.PodList { { ObjectMeta: api.ObjectMeta{ Name: "test4", - CreationTimestamp: unversioned.Time{time.Now().AddDate(-10, 0, 0)}, + CreationTimestamp: unversioned.Time{Time: time.Now().AddDate(-10, 0, 0)}, }, Spec: api.PodSpec{ Containers: make([]api.Container, 2), @@ -557,7 +639,7 @@ func newAllPhasePodList() *api.PodList { { ObjectMeta: api.ObjectMeta{ Name: "test5", - CreationTimestamp: unversioned.Time{time.Now().AddDate(-10, 0, 0)}, + CreationTimestamp: unversioned.Time{Time: time.Now().AddDate(-10, 0, 0)}, }, Spec: api.PodSpec{ Containers: make([]api.Container, 2), @@ -574,7 +656,7 @@ func newAllPhasePodList() *api.PodList { } } -func ExamplePrintPodHideTerminated() { +func Example_printPodHideTerminated() { f, tf, codec := NewAPIFactory() tf.Printer = kubectl.NewHumanReadablePrinter(false, false, false, false, false, false, []string{}) tf.Client = &fake.RESTClient{ @@ -583,7 +665,8 @@ func ExamplePrintPodHideTerminated() { } cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr) podList := newAllPhasePodList() - err := f.PrintObject(cmd, podList, os.Stdout) + mapper, _ := f.Object(false) + err := f.PrintObject(cmd, mapper, podList, os.Stdout) if err != nil { fmt.Printf("Unexpected error: %v", err) } @@ -594,7 +677,7 @@ func ExamplePrintPodHideTerminated() { // test5 1/2 Unknown 6 10y } -func ExamplePrintPodShowAll() { +func Example_printPodShowAll() { f, tf, codec := NewAPIFactory() tf.Printer = kubectl.NewHumanReadablePrinter(false, false, false, true, false, false, []string{}) tf.Client = &fake.RESTClient{ @@ -603,7 +686,8 @@ func ExamplePrintPodShowAll() { } cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr) podList := newAllPhasePodList() - err := f.PrintObject(cmd, podList, os.Stdout) + mapper, _ := f.Object(false) + err := f.PrintObject(cmd, mapper, podList, os.Stdout) if err != nil { fmt.Printf("Unexpected error: %v", err) } @@ -616,7 +700,7 @@ func ExamplePrintPodShowAll() { // test5 1/2 Unknown 6 10y } -func ExamplePrintServiceWithNamespacesAndLabels() { +func Example_printServiceWithNamespacesAndLabels() { f, tf, codec := NewAPIFactory() tf.Printer = kubectl.NewHumanReadablePrinter(false, true, false, false, false, false, []string{"l1"}) tf.Client = &fake.RESTClient{ @@ -671,7 +755,9 @@ func ExamplePrintServiceWithNamespacesAndLabels() { } ld := util.NewLineDelimiter(os.Stdout, "|") defer ld.Flush() - err := f.PrintObject(cmd, svc, ld) + + mapper, _ := f.Object(false) + err := f.PrintObject(cmd, mapper, svc, ld) if err != nil { fmt.Printf("Unexpected error: %v", err) } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/config_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/config_test.go index 8f33734a0..73237ba34 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/config_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/config_test.go @@ -27,10 +27,9 @@ import ( "testing" "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" ) func newRedFederalCowHammerConfig() clientcmdapi.Config { @@ -45,7 +44,7 @@ func newRedFederalCowHammerConfig() clientcmdapi.Config { } } -func ExampleView() { +func Example_view() { expectedConfig := newRedFederalCowHammerConfig() test := configCommandTest{ args: []string{"view"}, @@ -584,13 +583,12 @@ func TestNewEmptyCluster(t *testing.T) { func TestAdditionalCluster(t *testing.T) { expectedConfig := newRedFederalCowHammerConfig() cluster := clientcmdapi.NewCluster() - cluster.APIVersion = testapi.Default.GroupVersion().String() cluster.CertificateAuthority = "/ca-location" cluster.InsecureSkipTLSVerify = false cluster.Server = "serverlocation" expectedConfig.Clusters["different-cluster"] = cluster test := configCommandTest{ - args: []string{"set-cluster", "different-cluster", "--" + clientcmd.FlagAPIServer + "=serverlocation", "--" + clientcmd.FlagInsecure + "=false", "--" + clientcmd.FlagCAFile + "=/ca-location", "--" + clientcmd.FlagAPIVersion + "=" + testapi.Default.GroupVersion().String()}, + args: []string{"set-cluster", "different-cluster", "--" + clientcmd.FlagAPIServer + "=serverlocation", "--" + clientcmd.FlagInsecure + "=false", "--" + clientcmd.FlagCAFile + "=/ca-location"}, startingConfig: newRedFederalCowHammerConfig(), expectedConfig: expectedConfig, } @@ -727,7 +725,7 @@ func (test configCommandTest) run(t *testing.T) string { testClearLocationOfOrigin(&actualConfig) if !api.Semantic.DeepEqual(test.expectedConfig, actualConfig) { - t.Errorf("diff: %v", util.ObjectDiff(test.expectedConfig, actualConfig)) + t.Errorf("diff: %v", diff.ObjectDiff(test.expectedConfig, actualConfig)) t.Errorf("expected: %#v\n actual: %#v", test.expectedConfig, actualConfig) } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_authinfo.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_authinfo.go index c61cd8c12..468a390a1 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_authinfo.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_authinfo.go @@ -29,6 +29,7 @@ import ( "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/flag" ) type createAuthInfoOptions struct { @@ -40,7 +41,7 @@ type createAuthInfoOptions struct { token util.StringFlag username util.StringFlag password util.StringFlag - embedCertData util.BoolFlag + embedCertData flag.Tristate } var create_authinfo_long = fmt.Sprintf(`Sets a user entry in kubeconfig @@ -60,13 +61,13 @@ Specifying a name that already exists will merge new fields on top of existing v const create_authinfo_example = `# Set only the "client-key" field on the "cluster-admin" # entry, without touching other values: -$ kubectl config set-credentials cluster-admin --client-key=~/.kube/admin.key +kubectl config set-credentials cluster-admin --client-key=~/.kube/admin.key # Set basic auth for the "cluster-admin" entry -$ kubectl config set-credentials cluster-admin --username=admin --password=uXFGweU9l35qcif +kubectl config set-credentials cluster-admin --username=admin --password=uXFGweU9l35qcif # Embed client certificate data in the "cluster-admin" entry -$ kubectl config set-credentials cluster-admin --client-certificate=~/.kube/admin.crt --embed-certs=true` +kubectl config set-credentials cluster-admin --client-certificate=~/.kube/admin.crt --embed-certs=true` func NewCmdConfigSetAuthInfo(out io.Writer, configAccess ConfigAccess) *cobra.Command { options := &createAuthInfoOptions{configAccess: configAccess} @@ -90,8 +91,10 @@ func NewCmdConfigSetAuthInfo(out io.Writer, configAccess ConfigAccess) *cobra.Co }, } - cmd.Flags().Var(&options.clientCertificate, clientcmd.FlagCertFile, "path to "+clientcmd.FlagCertFile+" for the user entry in kubeconfig") - cmd.Flags().Var(&options.clientKey, clientcmd.FlagKeyFile, "path to "+clientcmd.FlagKeyFile+" for the user entry in kubeconfig") + cmd.Flags().Var(&options.clientCertificate, clientcmd.FlagCertFile, "path to "+clientcmd.FlagCertFile+" file for the user entry in kubeconfig") + cmd.MarkFlagFilename(clientcmd.FlagCertFile) + cmd.Flags().Var(&options.clientKey, clientcmd.FlagKeyFile, "path to "+clientcmd.FlagKeyFile+" file for the user entry in kubeconfig") + cmd.MarkFlagFilename(clientcmd.FlagKeyFile) cmd.Flags().Var(&options.token, clientcmd.FlagBearerToken, clientcmd.FlagBearerToken+" for the user entry in kubeconfig") cmd.Flags().Var(&options.username, clientcmd.FlagUsername, clientcmd.FlagUsername+" for the user entry in kubeconfig") cmd.Flags().Var(&options.password, clientcmd.FlagPassword, clientcmd.FlagPassword+" for the user entry in kubeconfig") diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_cluster.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_cluster.go index 6f463a1b4..80daa98d7 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_cluster.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_cluster.go @@ -28,6 +28,7 @@ import ( "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/flag" ) type createClusterOptions struct { @@ -35,29 +36,29 @@ type createClusterOptions struct { name string server util.StringFlag apiVersion util.StringFlag - insecureSkipTLSVerify util.BoolFlag + insecureSkipTLSVerify flag.Tristate certificateAuthority util.StringFlag - embedCAData util.BoolFlag + embedCAData flag.Tristate } const ( create_cluster_long = `Sets a cluster entry in kubeconfig. Specifying a name that already exists will merge new fields on top of existing values for those fields.` create_cluster_example = `# Set only the server field on the e2e cluster entry without touching other values. -$ kubectl config set-cluster e2e --server=https://1.2.3.4 +kubectl config set-cluster e2e --server=https://1.2.3.4 # Embed certificate authority data for the e2e cluster entry -$ kubectl config set-cluster e2e --certificate-authority=~/.kube/e2e/kubernetes.ca.crt +kubectl config set-cluster e2e --certificate-authority=~/.kube/e2e/kubernetes.ca.crt # Disable cert checking for the dev cluster entry -$ kubectl config set-cluster e2e --insecure-skip-tls-verify=true` +kubectl config set-cluster e2e --insecure-skip-tls-verify=true` ) func NewCmdConfigSetCluster(out io.Writer, configAccess ConfigAccess) *cobra.Command { options := &createClusterOptions{configAccess: configAccess} cmd := &cobra.Command{ - Use: fmt.Sprintf("set-cluster NAME [--%v=server] [--%v=path/to/certficate/authority] [--%v=apiversion] [--%v=true]", clientcmd.FlagAPIServer, clientcmd.FlagCAFile, clientcmd.FlagAPIVersion, clientcmd.FlagInsecure), + Use: fmt.Sprintf("set-cluster NAME [--%v=server] [--%v=path/to/certficate/authority] [--%v=true]", clientcmd.FlagAPIServer, clientcmd.FlagCAFile, clientcmd.FlagInsecure), Short: "Sets a cluster entry in kubeconfig", Long: create_cluster_long, Example: create_cluster_example, @@ -81,7 +82,8 @@ func NewCmdConfigSetCluster(out io.Writer, configAccess ConfigAccess) *cobra.Com cmd.Flags().Var(&options.apiVersion, clientcmd.FlagAPIVersion, clientcmd.FlagAPIVersion+" for the cluster entry in kubeconfig") f := cmd.Flags().VarPF(&options.insecureSkipTLSVerify, clientcmd.FlagInsecure, "", clientcmd.FlagInsecure+" for the cluster entry in kubeconfig") f.NoOptDefVal = "true" - cmd.Flags().Var(&options.certificateAuthority, clientcmd.FlagCAFile, "path to "+clientcmd.FlagCAFile+" for the cluster entry in kubeconfig") + cmd.Flags().Var(&options.certificateAuthority, clientcmd.FlagCAFile, "path to "+clientcmd.FlagCAFile+" file for the cluster entry in kubeconfig") + cmd.MarkFlagFilename(clientcmd.FlagCAFile) f = cmd.Flags().VarPF(&options.embedCAData, clientcmd.FlagEmbedCerts, "", clientcmd.FlagEmbedCerts+" for the cluster entry in kubeconfig") f.NoOptDefVal = "true" @@ -120,9 +122,6 @@ func (o *createClusterOptions) modifyCluster(existingCluster clientcmdapi.Cluste if o.server.Provided() { modifiedCluster.Server = o.server.Value() } - if o.apiVersion.Provided() { - modifiedCluster.APIVersion = o.apiVersion.Value() - } if o.insecureSkipTLSVerify.Provided() { modifiedCluster.InsecureSkipTLSVerify = o.insecureSkipTLSVerify.Value() // Specifying insecure mode clears any certificate authority diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_context.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_context.go index 093ce944c..e3d165e07 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_context.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/create_context.go @@ -40,7 +40,7 @@ const ( create_context_long = `Sets a context entry in kubeconfig Specifying a name that already exists will merge new fields on top of existing values for those fields.` create_context_example = `# Set the user field on the gce context entry without touching other values -$ kubectl config set-context gce --user=cluster-admin` +kubectl config set-context gce --user=cluster-admin` ) func NewCmdConfigSetContext(out io.Writer, configAccess ConfigAccess) *cobra.Command { diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/current_context.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/current_context.go index 66e8f78b3..fe5bcff69 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/current_context.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/current_context.go @@ -31,7 +31,7 @@ type CurrentContextOptions struct { const ( current_context_long = `Displays the current-context` current_context_example = `# Display the current-context -$ kubectl config current-context` +kubectl config current-context` ) func NewCmdConfigCurrentContext(out io.Writer, configAccess ConfigAccess) *cobra.Command { diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/navigation_step_parser_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/navigation_step_parser_test.go index 3ec3c40ff..2bca8d089 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/navigation_step_parser_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/navigation_step_parser_test.go @@ -22,7 +22,7 @@ import ( "testing" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" ) type stepParserTest struct { @@ -90,7 +90,7 @@ func (test stepParserTest) run(t *testing.T) { } if !reflect.DeepEqual(test.expectedNavigationSteps, *actualSteps) { - t.Errorf("diff: %v", util.ObjectDiff(test.expectedNavigationSteps, *actualSteps)) + t.Errorf("diff: %v", diff.ObjectDiff(test.expectedNavigationSteps, *actualSteps)) t.Errorf("expected: %#v\n actual: %#v", test.expectedNavigationSteps, *actualSteps) } } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/view.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/view.go index 50bdb30ad..bcdf91788 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/view.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/config/view.go @@ -28,12 +28,12 @@ import ( "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/flag" ) type ViewOptions struct { ConfigAccess ConfigAccess - Merge util.BoolFlag + Merge flag.Tristate Flatten bool Minify bool RawByteData bool @@ -44,10 +44,10 @@ const ( You can use --output jsonpath={...} to extract specific values using a jsonpath expression.` view_example = `# Show Merged kubeconfig settings. -$ kubectl config view +kubectl config view # Get the password for the e2e user -$ kubectl config view -o jsonpath='{.users[?(@.name == "e2e")].user.password}'` +kubectl config view -o jsonpath='{.users[?(@.name == "e2e")].user.password}'` ) func NewCmdConfigView(out io.Writer, ConfigAccess ConfigAccess) *cobra.Command { diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/convert.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/convert.go index e7e04d5b9..d6b8003b2 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/convert.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/convert.go @@ -43,14 +43,14 @@ The default output will be printed to stdout in YAML format. One can use -o opti to change to output destination. ` convert_example = `# Convert 'pod.yaml' to latest version and print to stdout. -$ kubectl convert -f pod.yaml +kubectl convert -f pod.yaml # Convert the live state of the resource specified by 'pod.yaml' to the latest version # and print to stdout in json format. -$ kubectl convert -f pod.yaml --local -o json +kubectl convert -f pod.yaml --local -o json # Convert all files under current directory to latest version and create them all. -$ kubectl convert -f . | kubectl create -f - +kubectl convert -f . | kubectl create -f - ` ) @@ -74,11 +74,12 @@ func NewCmdConvert(f *cmdutil.Factory, out io.Writer) *cobra.Command { usage := "Filename, directory, or URL to file to need to get converted." kubectl.AddJsonFilenameFlag(cmd, &options.filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &options.recursive) cmd.MarkFlagRequired("filename") cmdutil.AddValidateFlags(cmd) cmdutil.AddPrinterFlags(cmd) cmd.Flags().BoolVar(&options.local, "local", true, "If true, convert will NOT try to contact api-server but run locally.") - + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } @@ -93,6 +94,8 @@ type ConvertOptions struct { printer kubectl.ResourcePrinter outputVersion unversioned.GroupVersion + + recursive bool } // Complete collects information required to run Convert command from command line. @@ -106,11 +109,12 @@ func (o *ConvertOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra. } // build the builder - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) clientMapper := resource.ClientMapperFunc(f.ClientForMapping) + if o.local { fmt.Fprintln(out, "running in local mode...") - o.builder = resource.NewBuilder(mapper, typer, resource.DisabledClientForMapping{clientMapper}, f.Decoder(true)) + o.builder = resource.NewBuilder(mapper, typer, resource.DisabledClientForMapping{ClientMapper: clientMapper}, f.Decoder(true)) } else { o.builder = resource.NewBuilder(mapper, typer, clientMapper, f.Decoder(true)) schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir")) @@ -125,7 +129,7 @@ func (o *ConvertOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra. } o.builder = o.builder.NamespaceParam(cmdNamespace). ContinueOnError(). - FilenameParam(false, o.filenames...). + FilenameParam(false, o.recursive, o.filenames...). Flatten() // build the printer diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create.go index 6c4061ea0..81eedf5ef 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create.go @@ -35,6 +35,7 @@ import ( // referencing the cmd.Flags() type CreateOptions struct { Filenames []string + Recursive bool } const ( @@ -42,10 +43,10 @@ const ( JSON and YAML formats are accepted.` create_example = `# Create a pod using the data in pod.json. -$ kubectl create -f ./pod.json +kubectl create -f ./pod.json # Create a pod based on the JSON passed into stdin. -$ cat pod.json | kubectl create -f -` +cat pod.json | kubectl create -f -` ) func NewCmdCreate(f *cmdutil.Factory, out io.Writer) *cobra.Command { @@ -71,13 +72,17 @@ func NewCmdCreate(f *cmdutil.Factory, out io.Writer) *cobra.Command { kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) cmd.MarkFlagRequired("filename") cmdutil.AddValidateFlags(cmd) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) cmdutil.AddOutputFlagsForMutation(cmd) cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddRecordFlag(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) // create subcommands cmd.AddCommand(NewCmdCreateNamespace(f, out)) cmd.AddCommand(NewCmdCreateSecret(f, out)) + cmd.AddCommand(NewCmdCreateConfigMap(f, out)) + cmd.AddCommand(NewCmdCreateServiceAccount(f, out)) return cmd } @@ -99,12 +104,12 @@ func RunCreate(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *C return err } - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). Flatten(). Do() err = r.Err() @@ -219,7 +224,7 @@ func RunCreateSubcommand(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, if err != nil { return err } - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) gvk, err := typer.ObjectKind(obj) mapping, err := mapper.RESTMapping(unversioned.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version) if err != nil { @@ -234,7 +239,7 @@ func RunCreateSubcommand(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, RESTMapper: mapper, ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), } - info, err := resourceMapper.InfoForObject(obj) + info, err := resourceMapper.InfoForObject(obj, nil) if err != nil { return err } @@ -253,5 +258,5 @@ func RunCreateSubcommand(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, return nil } - return f.PrintObject(cmd, obj, out) + return f.PrintObject(cmd, mapper, obj, out) } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_configmap.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_configmap.go new file mode 100644 index 000000000..729ac3983 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_configmap.go @@ -0,0 +1,96 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 cmd + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + + "k8s.io/kubernetes/pkg/kubectl" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +const ( + configMapLong = `Create a configmap based on a file, directory, or specified literal value. + +A single configmap may package one or more key/value pairs. + +When creating a configmap based on a file, the key will default to the basename of the file, and the value will +default to the file content. If the basename is an invalid key, you may specify an alternate key. + +When creating a configmap based on a directory, each file whose basename is a valid key in the directory will be +packaged into the configmap. Any directory entries except regular files are ignored (e.g. subdirectories, +symlinks, devices, pipes, etc). +` + + configMapExample = ` # Create a new configmap named my-config with keys for each file in folder bar + kubectl create configmap generic my-config --from-file=path/to/bar + + # Create a new configmap named my-config with specified keys instead of names on disk + kubectl create configmap generic my-config --from-file=ssh-privatekey=~/.ssh/id_rsa --from-file=ssh-publickey=~/.ssh/id_rsa.pub + + # Create a new configMap named my-config with key1=config1 and key2=config2 + kubectl create configmap generic my-config --from-literal=key1=config1 --from-literal=key2=config2` +) + +// ConfigMap is a command to ease creating ConfigMaps. +func NewCmdCreateConfigMap(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "configmap NAME [--type=string] [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run]", + Short: "Create a configMap from a local file, directory or literal value.", + Long: configMapLong, + Example: configMapExample, + Run: func(cmd *cobra.Command, args []string) { + err := CreateConfigMap(f, cmdOut, cmd, args) + cmdutil.CheckErr(err) + }, + } + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddPrinterFlags(cmd) + cmdutil.AddGeneratorFlags(cmd, cmdutil.ConfigMapV1GeneratorName) + cmd.Flags().StringSlice("from-file", []string{}, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is a valid configmap key.") + cmd.Flags().StringSlice("from-literal", []string{}, "Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)") + return cmd +} + +// CreateConfigMap is the implementation of the create configmap generic command. +func CreateConfigMap(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + var generator kubectl.StructuredGenerator + switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName { + case cmdutil.ConfigMapV1GeneratorName: + generator = &kubectl.ConfigMapGeneratorV1{ + Name: name, + FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"), + LiteralSources: cmdutil.GetFlagStringSlice(cmd, "from-literal"), + } + default: + return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName)) + } + return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{ + Name: name, + StructuredGenerator: generator, + DryRun: cmdutil.GetFlagBool(cmd, "dry-run"), + OutputFormat: cmdutil.GetFlagString(cmd, "output"), + }) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_configmap_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_configmap_test.go new file mode 100644 index 000000000..9c3971e36 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_configmap_test.go @@ -0,0 +1,54 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 cmd + +import ( + "bytes" + "net/http" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/unversioned/fake" +) + +func TestCreateConfigMap(t *testing.T) { + configMap := &api.ConfigMap{} + configMap.Name = "my-configmap" + f, tf, codec := NewAPIFactory() + tf.Printer = &testPrinter{} + tf.Client = &fake.RESTClient{ + Codec: codec, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == "/namespaces/test/configmaps" && m == "POST": + return &http.Response{StatusCode: 201, Body: objBody(codec, configMap)}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdCreateConfigMap(f, buf) + cmd.Flags().Set("output", "name") + cmd.Run(cmd, []string{configMap.Name}) + expectedOutput := "configmap/" + configMap.Name + "\n" + if buf.String() != expectedOutput { + t.Errorf("expected output: %s, but got: %s", buf.String(), expectedOutput) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_namespace.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_namespace.go index a66055fb1..299c5e940 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_namespace.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_namespace.go @@ -31,7 +31,7 @@ const ( Create a namespace with the specified name.` namespaceExample = ` # Create a new namespace named my-namespace - $ kubectl create namespace my-namespace` + kubectl create namespace my-namespace` ) // NewCmdCreateNamespace is a macro command to create a new namespace @@ -51,6 +51,7 @@ func NewCmdCreateNamespace(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command cmdutil.AddValidateFlags(cmd) cmdutil.AddPrinterFlags(cmd) cmdutil.AddGeneratorFlags(cmd, cmdutil.NamespaceV1GeneratorName) + return cmd } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_secret.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_secret.go index 0a4c66b18..48cb8f99b 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_secret.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_secret.go @@ -38,12 +38,13 @@ func NewCmdCreateSecret(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command { } cmd.AddCommand(NewCmdCreateSecretDockerRegistry(f, cmdOut)) cmd.AddCommand(NewCmdCreateSecretGeneric(f, cmdOut)) + return cmd } const ( secretLong = ` -Create a secret based on a file, directory, or specified literal value +Create a secret based on a file, directory, or specified literal value. A single secret may package one or more key/value pairs. @@ -56,13 +57,13 @@ symlinks, devices, pipes, etc). ` secretExample = ` # Create a new secret named my-secret with keys for each file in folder bar - $ kubectl create secret generic my-secret --from-file=path/to/bar + kubectl create secret generic my-secret --from-file=path/to/bar # Create a new secret named my-secret with specified keys instead of names on disk - $ kubectl create secret generic my-secret --from-file=ssh-privatekey=~/.ssh/id_rsa --from-file=ssh-publickey=~/.ssh/id_rsa.pub + kubectl create secret generic my-secret --from-file=ssh-privatekey=~/.ssh/id_rsa --from-file=ssh-publickey=~/.ssh/id_rsa.pub # Create a new secret named my-secret with key1=supersecret and key2=topsecret - $ kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret` + kubectl create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret` ) // NewCmdCreateSecretGeneric is a command to create generic secrets from files, directories, or literal values @@ -87,7 +88,7 @@ func NewCmdCreateSecretGeneric(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Comm return cmd } -// CreateSecretGeneric is the implementation the create secret generic command +// CreateSecretGeneric is the implementation of the create secret generic command func CreateSecretGeneric(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error { name, err := NameFromCommandArgs(cmd, args) if err != nil { @@ -155,6 +156,7 @@ func NewCmdCreateSecretDockerRegistry(f *cmdutil.Factory, cmdOut io.Writer) *cob cmd.Flags().String("docker-email", "", "Email for Docker registry") cmd.MarkFlagRequired("docker-email") cmd.Flags().String("docker-server", "https://index.docker.io/v1/", "Server location for Docker registry") + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_serviceaccount.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_serviceaccount.go new file mode 100644 index 000000000..61d2d3cc5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_serviceaccount.go @@ -0,0 +1,77 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 cmd + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + + "k8s.io/kubernetes/pkg/kubectl" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +const ( + serviceAccountLong = ` +Create a service account with the specified name.` + + serviceAccountExample = ` # Create a new service account named my-service-account + $ kubectl create serviceaccount my-service-account` +) + +// NewCmdCreateServiceAccount is a macro command to create a new service account +func NewCmdCreateServiceAccount(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "serviceaccount NAME [--dry-run]", + Aliases: []string{"sa"}, + Short: "Create a service account with the specified name.", + Long: serviceAccountLong, + Example: serviceAccountExample, + Run: func(cmd *cobra.Command, args []string) { + err := CreateServiceAccount(f, cmdOut, cmd, args) + cmdutil.CheckErr(err) + }, + } + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddPrinterFlags(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) + cmdutil.AddGeneratorFlags(cmd, cmdutil.ServiceAccountV1GeneratorName) + return cmd +} + +// CreateServiceAccount implements the behavior to run the create service account command +func CreateServiceAccount(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + var generator kubectl.StructuredGenerator + switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName { + case cmdutil.ServiceAccountV1GeneratorName: + generator = &kubectl.ServiceAccountGeneratorV1{Name: name} + default: + return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName)) + } + return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{ + Name: name, + StructuredGenerator: generator, + DryRun: cmdutil.GetFlagBool(cmd, "dry-run"), + OutputFormat: cmdutil.GetFlagString(cmd, "output"), + }) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_serviceaccount_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_serviceaccount_test.go new file mode 100644 index 000000000..6573e6d02 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_serviceaccount_test.go @@ -0,0 +1,54 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 cmd + +import ( + "bytes" + "net/http" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/unversioned/fake" +) + +func TestCreateServiceAccount(t *testing.T) { + serviceAccountObject := &api.ServiceAccount{} + serviceAccountObject.Name = "my-service-account" + f, tf, codec := NewAPIFactory() + tf.Printer = &testPrinter{} + tf.Client = &fake.RESTClient{ + Codec: codec, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == "/namespaces/test/serviceaccounts" && m == "POST": + return &http.Response{StatusCode: 201, Body: objBody(codec, serviceAccountObject)}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdCreateServiceAccount(f, buf) + cmd.Flags().Set("output", "name") + cmd.Run(cmd, []string{serviceAccountObject.Name}) + expectedOutput := "serviceaccount/" + serviceAccountObject.Name + "\n" + if buf.String() != expectedOutput { + t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String()) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_test.go index 9fdee69f1..005b5faee 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/create_test.go @@ -60,7 +60,7 @@ func TestCreateObject(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdCreate(f, buf) - cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") + cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) @@ -94,7 +94,7 @@ func TestCreateMultipleObject(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdCreate(f, buf) - cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") + cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml") cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) @@ -107,7 +107,7 @@ func TestCreateMultipleObject(t *testing.T) { func TestCreateDirectory(t *testing.T) { initTestErrorHandler(t) - _, svc, rc := testData() + _, _, rc := testData() rc.Items[0].Name = "name" f, tf, codec := NewAPIFactory() @@ -116,8 +116,6 @@ func TestCreateDirectory(t *testing.T) { Codec: codec, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { - case p == "/namespaces/test/services" && m == "POST": - return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil case p == "/namespaces/test/replicationcontrollers" && m == "POST": return &http.Response{StatusCode: 201, Body: objBody(codec, &rc.Items[0])}, nil default: @@ -130,11 +128,11 @@ func TestCreateDirectory(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdCreate(f, buf) - cmd.Flags().Set("filename", "../../../examples/guestbook") + cmd.Flags().Set("filename", "../../../examples/guestbook/legacy") cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) - if buf.String() != "replicationcontroller/name\nservice/baz\nreplicationcontroller/name\nservice/baz\nreplicationcontroller/name\nservice/baz\n" { + if buf.String() != "replicationcontroller/name\nreplicationcontroller/name\nreplicationcontroller/name\n" { t.Errorf("unexpected output: %s", buf.String()) } } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete.go index 7dffd4f99..a6c07e243 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete.go @@ -35,6 +35,7 @@ import ( // referencing the cmd.Flags() type DeleteOptions struct { Filenames []string + Recursive bool } const ( @@ -48,22 +49,22 @@ Note that the delete command does NOT do resource version checks, so if someone submits an update to a resource right when you submit a delete, their update will be lost along with the rest of the resource.` delete_example = `# Delete a pod using the type and name specified in pod.json. -$ kubectl delete -f ./pod.json +kubectl delete -f ./pod.json # Delete a pod based on the type and name in the JSON passed into stdin. -$ cat pod.json | kubectl delete -f - +cat pod.json | kubectl delete -f - # Delete pods and services with same names "baz" and "foo" -$ kubectl delete pod,service baz foo +kubectl delete pod,service baz foo # Delete pods and services with label name=myLabel. -$ kubectl delete pods,services -l name=myLabel +kubectl delete pods,services -l name=myLabel # Delete a pod with UID 1234-56-7890-234234-456456. -$ kubectl delete pod 1234-56-7890-234234-456456 +kubectl delete pod 1234-56-7890-234234-456456 # Delete all pods -$ kubectl delete pods --all` +kubectl delete pods --all` ) func NewCmdDelete(f *cmdutil.Factory, out io.Writer) *cobra.Command { @@ -91,6 +92,7 @@ func NewCmdDelete(f *cmdutil.Factory, out io.Writer) *cobra.Command { } usage := "Filename, directory, or URL to a file containing the resource to delete." kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on.") cmd.Flags().Bool("all", false, "[-all] to select all the specified resources.") cmd.Flags().Bool("ignore-not-found", false, "Treat \"resource not found\" as a successful delete. Defaults to \"true\" when --all is specified.") @@ -98,6 +100,7 @@ func NewCmdDelete(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Int("grace-period", -1, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative.") cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object") cmdutil.AddOutputFlagsForMutation(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } @@ -107,11 +110,11 @@ func RunDelete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str return err } deleteAll := cmdutil.GetFlagBool(cmd, "all") - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). SelectorParam(cmdutil.GetFlagString(cmd, "selector")). SelectAllParam(deleteAll). ResourceTypeOrNameArgs(false, args...).RequireObject(false). diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete_test.go index 61da45f4f..5e3cf3c99 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/delete_test.go @@ -114,7 +114,7 @@ func TestDeleteObject(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdDelete(f, buf) - cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") + cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) @@ -145,7 +145,7 @@ func TestDeleteObjectNotFound(t *testing.T) { cmd := NewCmdDelete(f, buf) options := &DeleteOptions{ - Filenames: []string{"../../../examples/guestbook/redis-master-controller.yaml"}, + Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"}, } cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") @@ -174,7 +174,7 @@ func TestDeleteObjectIgnoreNotFound(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdDelete(f, buf) - cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") + cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("ignore-not-found", "true") cmd.Flags().Set("output", "name") @@ -290,7 +290,7 @@ func TestDeleteMultipleObject(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdDelete(f, buf) - cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") + cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") @@ -325,7 +325,7 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) { cmd := NewCmdDelete(f, buf) options := &DeleteOptions{ - Filenames: []string{"../../../examples/guestbook/redis-master-controller.yaml", "../../../examples/guestbook/frontend-service.yaml"}, + Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml", "../../../examples/guestbook/frontend-service.yaml"}, } cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") @@ -375,7 +375,7 @@ func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) { } func TestDeleteDirectory(t *testing.T) { - _, svc, rc := testData() + _, _, rc := testData() f, tf, codec := NewAPIFactory() tf.Printer = &testPrinter{} @@ -383,8 +383,6 @@ func TestDeleteDirectory(t *testing.T) { Codec: codec, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { - case strings.HasPrefix(p, "/namespaces/test/services/") && m == "DELETE": - return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && m == "DELETE": return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil default: @@ -397,12 +395,12 @@ func TestDeleteDirectory(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdDelete(f, buf) - cmd.Flags().Set("filename", "../../../examples/guestbook") + cmd.Flags().Set("filename", "../../../examples/guestbook/legacy") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) - if buf.String() != "replicationcontroller/frontend\nservice/frontend\nreplicationcontroller/redis-master\nservice/redis-master\nreplicationcontroller/redis-slave\nservice/redis-slave\n" { + if buf.String() != "replicationcontroller/frontend\nreplicationcontroller/redis-master\nreplicationcontroller/redis-slave\n" { t.Errorf("unexpected output: %s", buf.String()) } } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/describe.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/describe.go index 3ea83ecdc..85e80da48 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/describe.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/describe.go @@ -36,6 +36,7 @@ import ( // referencing the cmd.Flags() type DescribeOptions struct { Filenames []string + Recursive bool } const ( @@ -49,29 +50,25 @@ $ kubectl describe TYPE NAME_PREFIX will first check for an exact match on TYPE and NAME_PREFIX. If no such resource exists, it will output details for every resource that has a name prefixed with NAME_PREFIX -Possible resource types include (case insensitive): pods (po), services (svc), -replicationcontrollers (rc), nodes (no), events (ev), limitranges (limits), -persistentvolumes (pv), persistentvolumeclaims (pvc), resourcequotas (quota), -namespaces (ns), serviceaccounts, horizontalpodautoscalers (hpa), -endpoints (ep) or secrets.` +` + kubectl.PossibleResourceTypes describe_example = `# Describe a node -$ kubectl describe nodes kubernetes-minion-emt8.c.myproject.internal +kubectl describe nodes kubernetes-minion-emt8.c.myproject.internal # Describe a pod -$ kubectl describe pods/nginx +kubectl describe pods/nginx # Describe a pod identified by type and name in "pod.json" -$ kubectl describe -f pod.json +kubectl describe -f pod.json # Describe all pods -$ kubectl describe pods +kubectl describe pods # Describe pods by label name=myLabel -$ kubectl describe po -l name=myLabel +kubectl describe po -l name=myLabel # Describe all pods managed by the 'frontend' replication controller (rc-created pods # get the name of the rc as a prefix in the pod the name). -$ kubectl describe pods frontend` +kubectl describe pods frontend` ) func NewCmdDescribe(f *cmdutil.Factory, out io.Writer) *cobra.Command { @@ -90,7 +87,9 @@ func NewCmdDescribe(f *cmdutil.Factory, out io.Writer) *cobra.Command { } usage := "Filename, directory, or URL to a file containing the resource to describe" kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on") + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } @@ -105,11 +104,11 @@ func RunDescribe(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s return cmdutil.UsageError(cmd, "Required resource not specified.") } - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). SelectorParam(selector). ResourceTypeOrNameArgs(true, args...). Flatten(). diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/describe_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/describe_test.go index 35a87f2e5..cc7312ee8 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/describe_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/describe_test.go @@ -70,7 +70,7 @@ func TestDescribeObject(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdDescribe(f, buf) - cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") + cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml") cmd.Run(cmd, []string{}) if d.Name != "redis-master" || d.Namespace != "test" { diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/drain.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/drain.go index 89cba686d..679628275 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/drain.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/drain.go @@ -53,14 +53,14 @@ const ( cordon_long = `Mark node as unschedulable. ` cordon_example = `# Mark node "foo" as unschedulable. -$ kubectl cordon foo +kubectl cordon foo ` ) func NewCmdCordon(f *cmdutil.Factory, out io.Writer) *cobra.Command { options := &DrainOptions{factory: f, out: out} - return &cobra.Command{ + cmd := &cobra.Command{ Use: "cordon NODE", Short: "Mark node as unschedulable", Long: cordon_long, @@ -70,6 +70,7 @@ func NewCmdCordon(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmdutil.CheckErr(options.RunCordonOrUncordon(true)) }, } + return cmd } const ( @@ -83,7 +84,7 @@ $ kubectl uncordon foo func NewCmdUncordon(f *cmdutil.Factory, out io.Writer) *cobra.Command { options := &DrainOptions{factory: f, out: out} - return &cobra.Command{ + cmd := &cobra.Command{ Use: "uncordon NODE", Short: "Mark node as schedulable", Long: uncordon_long, @@ -93,6 +94,7 @@ func NewCmdUncordon(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmdutil.CheckErr(options.RunCordonOrUncordon(false)) }, } + return cmd } const ( @@ -103,7 +105,7 @@ Then drain deletes all pods except mirror pods (which cannot be deleted through the API server). If there are DaemonSet-managed pods, drain will not proceed without --ignore-daemonsets, and regardless it will not delete any DaemonSet-managed pods, because those pods would be immediately replaced by the -DaemonSet controller, which ignores unschedulable marknigs. If there are any +DaemonSet controller, which ignores unschedulable markings. If there are any pods that are neither mirror pods nor managed--by ReplicationController, DaemonSet or Job--, then drain will not delete any pods unless you use --force. @@ -149,14 +151,14 @@ func (o *DrainOptions) SetupDrain(cmd *cobra.Command, args []string) error { return err } - o.mapper, o.typer = o.factory.Object() + o.mapper, o.typer = o.factory.Object(false) cmdNamespace, _, err := o.factory.DefaultNamespace() if err != nil { return err } - r := o.factory.NewBuilder(). + r := o.factory.NewBuilder(cmdutil.GetIncludeThirdPartyAPIs(cmd)). NamespaceParam(cmdNamespace).DefaultNamespace(). ResourceNames("node", args[0]). Do() @@ -242,7 +244,7 @@ func (o *DrainOptions) getPodsForDeletion() ([]api.Pod, error) { daemonset_pod = true } } else if sr.Reference.Kind == "Job" { - job, err := o.client.Jobs(sr.Reference.Namespace).Get(sr.Reference.Name) + job, err := o.client.ExtensionsClient.Jobs(sr.Reference.Namespace).Get(sr.Reference.Name) // Assume the only reason for an error is because the Job is // gone/missing, not for any other cause. TODO(mml): something more diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/drain_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/drain_test.go index b0aa36649..571651b1d 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/drain_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/drain_test.go @@ -34,7 +34,7 @@ import ( "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/fake" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/conversion" @@ -50,7 +50,7 @@ func TestMain(m *testing.M) { node = &api.Node{ ObjectMeta: api.ObjectMeta{ Name: "node", - CreationTimestamp: unversioned.Time{time.Now()}, + CreationTimestamp: unversioned.Time{Time: time.Now()}, }, Spec: api.NodeSpec{ ExternalID: "node", @@ -165,7 +165,7 @@ func TestCordon(t *testing.T) { } }), } - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} buf := bytes.NewBuffer([]byte{}) cmd := test.cmd(f, buf) @@ -209,7 +209,7 @@ func TestDrain(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "rc", Namespace: "default", - CreationTimestamp: unversioned.Time{time.Now()}, + CreationTimestamp: unversioned.Time{Time: time.Now()}, Labels: labels, SelfLink: testapi.Default.SelfLink("replicationcontrollers", "rc"), }, @@ -225,7 +225,7 @@ func TestDrain(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "bar", Namespace: "default", - CreationTimestamp: unversioned.Time{time.Now()}, + CreationTimestamp: unversioned.Time{Time: time.Now()}, Labels: labels, Annotations: rc_anno, }, @@ -238,7 +238,7 @@ func TestDrain(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "ds", Namespace: "default", - CreationTimestamp: unversioned.Time{time.Now()}, + CreationTimestamp: unversioned.Time{Time: time.Now()}, SelfLink: "/apis/extensions/v1beta1/namespaces/default/daemonsets/ds", }, Spec: extensions.DaemonSetSpec{ @@ -253,7 +253,7 @@ func TestDrain(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "bar", Namespace: "default", - CreationTimestamp: unversioned.Time{time.Now()}, + CreationTimestamp: unversioned.Time{Time: time.Now()}, Labels: labels, Annotations: ds_anno, }, @@ -266,7 +266,7 @@ func TestDrain(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "job", Namespace: "default", - CreationTimestamp: unversioned.Time{time.Now()}, + CreationTimestamp: unversioned.Time{Time: time.Now()}, SelfLink: "/apis/extensions/v1beta1/namespaces/default/jobs/job", }, Spec: extensions.JobSpec{ @@ -278,7 +278,7 @@ func TestDrain(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "bar", Namespace: "default", - CreationTimestamp: unversioned.Time{time.Now()}, + CreationTimestamp: unversioned.Time{Time: time.Now()}, Labels: labels, Annotations: map[string]string{controller.CreatedByAnnotation: refJson(t, &job)}, }, @@ -288,7 +288,7 @@ func TestDrain(t *testing.T) { ObjectMeta: api.ObjectMeta{ Name: "bar", Namespace: "default", - CreationTimestamp: unversioned.Time{time.Now()}, + CreationTimestamp: unversioned.Time{Time: time.Now()}, Labels: labels, }, Spec: api.PodSpec{ @@ -431,7 +431,7 @@ func TestDrain(t *testing.T) { } }), } - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdDrain(f, buf) diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/edit.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/edit.go index 7543c4a0e..abfa32b00 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/edit.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/edit.go @@ -19,10 +19,10 @@ package cmd import ( "bufio" "bytes" - "encoding/json" "fmt" "io" "os" + "path" gruntime "runtime" "strings" @@ -35,7 +35,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl/cmd/util/jsonmerge" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/crlf" "k8s.io/kubernetes/pkg/util/strategicpatch" "k8s.io/kubernetes/pkg/util/yaml" @@ -65,26 +65,34 @@ to apply your changes to the newer version of the resource, or update your tempo saved copy to include the latest resource version.` editExample = ` # Edit the service named 'docker-registry': - $ kubectl edit svc/docker-registry + kubectl edit svc/docker-registry # Use an alternative editor - $ KUBE_EDITOR="nano" kubectl edit svc/docker-registry + KUBE_EDITOR="nano" kubectl edit svc/docker-registry # Edit the service 'docker-registry' in JSON using the v1 API format: - $ kubectl edit svc/docker-registry --output-version=v1 -o json` + kubectl edit svc/docker-registry --output-version=v1 -o json` ) +// EditOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of +// referencing the cmd.Flags() +type EditOptions struct { + Filenames []string + Recursive bool +} + var errExit = fmt.Errorf("exit directly") -func NewCmdEdit(f *cmdutil.Factory, out io.Writer) *cobra.Command { - filenames := []string{} +func NewCmdEdit(f *cmdutil.Factory, out, errOut io.Writer) *cobra.Command { + options := &EditOptions{} + cmd := &cobra.Command{ Use: "edit (RESOURCE/NAME | -f FILENAME)", Short: "Edit a resource on the server", Long: editLong, Example: fmt.Sprintf(editExample), Run: func(cmd *cobra.Command, args []string) { - err := RunEdit(f, out, cmd, args, filenames) + err := RunEdit(f, out, errOut, cmd, args, options) if err == errExit { os.Exit(1) } @@ -92,16 +100,18 @@ func NewCmdEdit(f *cmdutil.Factory, out io.Writer) *cobra.Command { }, } usage := "Filename, directory, or URL to file to use to edit the resource" - kubectl.AddJsonFilenameFlag(cmd, &filenames, usage) + kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) cmd.Flags().StringP("output", "o", "yaml", "Output format. One of: yaml|json.") - cmd.Flags().String("output-version", "", "Output the formatted object with the given version (default api-version).") + cmd.Flags().String("output-version", "", "Output the formatted object with the given group version (for ex: 'extensions/v1beta1').") cmd.Flags().Bool("windows-line-endings", gruntime.GOOS == "windows", "Use Windows line-endings (default Unix line-endings)") cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddRecordFlag(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } -func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, filenames []string) error { +func RunEdit(f *cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args []string, options *EditOptions) error { var printer kubectl.ResourcePrinter var ext string switch format := cmdutil.GetFlagString(cmd, "output"); format { @@ -120,7 +130,7 @@ func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin return err } - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) resourceMapper := &resource.Mapper{ ObjectTyper: typer, RESTMapper: mapper, @@ -130,7 +140,7 @@ func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). ResourceTypeOrNameArgs(true, args...). Latest(). Flatten(). @@ -151,72 +161,89 @@ func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin } encoder := f.JSONEncoder() - - windowsLineEndings := cmdutil.GetFlagBool(cmd, "windows-line-endings") - edit := editor.NewDefaultEditor(f.EditorEnvs()) defaultVersion, err := cmdutil.OutputVersion(cmd, clientConfig.GroupVersion) if err != nil { return err } - results := editResults{} - for { - objs, err := resource.AsVersionedObjects(infos, defaultVersion.String(), encoder) - if err != nil { - return preservedFile(err, results.file, out) - } - // if input object is a list, traverse and edit each item one at a time - for _, obj := range objs { - // TODO: add an annotating YAML printer that can print inline comments on each field, - // including descriptions or validation errors + objs, err := resource.AsVersionedObjects(infos, defaultVersion.String(), encoder) + if err != nil { + return err + } + var ( + windowsLineEndings = cmdutil.GetFlagBool(cmd, "windows-line-endings") + edit = editor.NewDefaultEditor(f.EditorEnvs()) + results = editResults{} + original = []byte{} + edited = []byte{} + file string + ) + +outter: + for i := range objs { + obj := objs[i] + // some bookkeeping + results.header.flush() + containsError := false + + for { // generate the file to edit buf := &bytes.Buffer{} var w io.Writer = buf if windowsLineEndings { - w = util.NewCRLFWriter(w) + w = crlf.NewCRLFWriter(w) } if err := results.header.writeTo(w); err != nil { - return preservedFile(err, results.file, out) + return preservedFile(err, results.file, errOut) } - if err := printer.PrintObj(obj, w); err != nil { - return preservedFile(err, results.file, out) + if !containsError { + if err := printer.PrintObj(obj, w); err != nil { + return preservedFile(err, results.file, errOut) + } + original = buf.Bytes() + } else { + // In case of an error, preserve the edited file. + // Remove the comments (header) from it since we already + // have included the latest header in the buffer above. + buf.Write(manualStrip(edited)) } - original := buf.Bytes() // launch the editor - edited, file, err := edit.LaunchTempFile("kubectl-edit-", ext, buf) + editedDiff := edited + edited, file, err = edit.LaunchTempFile(fmt.Sprintf("%s-edit-", path.Base(os.Args[0])), ext, buf) if err != nil { - return preservedFile(err, results.file, out) + return preservedFile(err, results.file, errOut) + } + if bytes.Equal(stripComments(editedDiff), stripComments(edited)) { + // Ugly hack right here. We will hit this either (1) when we try to + // save the same changes we tried to save in the previous iteration + // which means our changes are invalid or (2) when we exit the second + // time. The second case is more usual so we can probably live with it. + // TODO: A less hacky fix would be welcome :) + fmt.Fprintln(errOut, "Edit cancelled, no valid changes were saved.") + continue outter } // cleanup any file from the previous pass if len(results.file) > 0 { os.Remove(results.file) } - glog.V(4).Infof("User edited:\n%s", string(edited)) - lines, err := hasLines(bytes.NewBuffer(edited)) - if err != nil { - return preservedFile(err, file, out) - } + // Compare content without comments if bytes.Equal(stripComments(original), stripComments(edited)) { - if len(results.edit) > 0 { - preservedFile(nil, file, out) - } else { - os.Remove(file) - } - fmt.Fprintln(out, "Edit cancelled, no changes made.") - continue + os.Remove(file) + fmt.Fprintln(errOut, "Edit cancelled, no changes made.") + continue outter + } + lines, err := hasLines(bytes.NewBuffer(edited)) + if err != nil { + return preservedFile(err, file, errOut) } if !lines { - if len(results.edit) > 0 { - preservedFile(nil, file, out) - } else { - os.Remove(file) - } - fmt.Fprintln(out, "Edit cancelled, saved file was empty.") - continue + os.Remove(file) + fmt.Fprintln(errOut, "Edit cancelled, saved file was empty.") + continue outter } results = editResults{ @@ -226,12 +253,17 @@ func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin // parse the edited file updates, err := resourceMapper.InfoForData(edited, "edited-file") if err != nil { - return fmt.Errorf("The edited file had a syntax error: %v", err) + // syntax error + containsError = true + results.header.reasons = append(results.header.reasons, editReason{head: fmt.Sprintf("The edited file had a syntax error: %v", err)}) + continue } + // not a syntax error as it turns out... + containsError = false // put configuration annotation in "updates" if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), updates, encoder); err != nil { - return preservedFile(err, file, out) + return preservedFile(err, file, errOut) } if cmdutil.ShouldRecord(cmd, updates) { err = cmdutil.RecordChangeCause(updates.Object, f.Command()) @@ -239,26 +271,26 @@ func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin return err } } - // encode updates back to "edited" since we'll only generate patch from "edited" - if edited, err = runtime.Encode(encoder, updates.Object); err != nil { - return preservedFile(err, file, out) + editedCopy := edited + if editedCopy, err = runtime.Encode(encoder, updates.Object); err != nil { + return preservedFile(err, file, errOut) } visitor := resource.NewFlattenListVisitor(updates, resourceMapper) // need to make sure the original namespace wasn't changed while editing if err = visitor.Visit(resource.RequireNamespace(cmdNamespace)); err != nil { - return preservedFile(err, file, out) + return preservedFile(err, file, errOut) } // use strategic merge to create a patch originalJS, err := yaml.ToJSON(original) if err != nil { - return preservedFile(err, file, out) + return preservedFile(err, file, errOut) } - editedJS, err := yaml.ToJSON(edited) + editedJS, err := yaml.ToJSON(editedCopy) if err != nil { - return preservedFile(err, file, out) + return preservedFile(err, file, errOut) } patch, err := strategicpatch.CreateStrategicMergePatch(originalJS, editedJS, obj) // TODO: change all jsonmerge to strategicpatch @@ -266,7 +298,7 @@ func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin preconditions := []jsonmerge.PreconditionFunc{} if err != nil { glog.V(4).Infof("Unable to calculate diff, no merge is possible: %v", err) - return preservedFile(err, file, out) + return preservedFile(err, file, errOut) } else { preconditions = append(preconditions, jsonmerge.RequireKeyUnchanged("apiVersion")) preconditions = append(preconditions, jsonmerge.RequireKeyUnchanged("kind")) @@ -275,60 +307,47 @@ func RunEdit(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin } if hold, msg := jsonmerge.TestPreconditionsHold(patch, preconditions); !hold { - fmt.Fprintf(out, "error: %s", msg) - return preservedFile(nil, file, out) + fmt.Fprintf(errOut, "error: %s\n", msg) + return preservedFile(nil, file, errOut) } + errorMsg := "" err = visitor.Visit(func(info *resource.Info, err error) error { patched, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, api.StrategicMergePatchType, patch) if err != nil { - fmt.Fprintln(out, results.addError(err, info)) - return nil + errorMsg = results.addError(err, info) + return err } info.Refresh(patched, true) cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, "edited") return nil }) - if err != nil { - return preservedFile(err, file, out) + if err == nil { + os.Remove(file) + continue outter } - + // Handle all possible errors + // + // 1. retryable: propose kubectl replace -f + // 2. notfound: indicate the location of the saved configuration of the deleted resource + // 3. invalid: retry those on the spot by looping ie. reloading the editor if results.retryable > 0 { - fmt.Fprintf(out, "You can run `kubectl replace -f %s` to try this update again.\n", file) - return errExit + fmt.Fprintln(errOut, errorMsg) + fmt.Fprintf(errOut, "You can run `%s replace -f %s` to try this update again.\n", path.Base(os.Args[0]), file) + continue outter } - if results.conflict > 0 { - fmt.Fprintf(out, "You must update your local resource version and run `kubectl replace -f %s` to overwrite the remote changes.\n", file) - return errExit - } - if len(results.edit) == 0 { - if results.notfound == 0 { - os.Remove(file) - } else { - fmt.Fprintf(out, "The edits you made on deleted resources have been saved to %q\n", file) - } + if results.notfound > 0 { + fmt.Fprintln(errOut, errorMsg) + fmt.Fprintf(errOut, "The edits you made on deleted resources have been saved to %q\n", file) + continue outter } + // validation error + containsError = true } - if len(results.edit) == 0 { - return nil - } - - // loop again and edit the remaining items - infos = results.edit } return nil } -// print json file (such as patch file) content for debugging -func printJson(out io.Writer, file []byte) error { - diff := make(map[string]interface{}) - if err := json.Unmarshal(file, &diff); err != nil { - return err - } - fmt.Fprintf(out, "%v\n", diff) - return nil -} - // editReason preserves a message about the reason this file must be edited again type editReason struct { head string @@ -361,12 +380,15 @@ func (h *editHeader) writeTo(w io.Writer) error { return nil } +func (h *editHeader) flush() { + h.reasons = []editReason{} +} + // editResults capture the result of an update type editResults struct { header editHeader retryable int notfound int - conflict int edit []*resource.Info file string @@ -378,23 +400,23 @@ func (r *editResults) addError(err error, info *resource.Info) string { case errors.IsInvalid(err): r.edit = append(r.edit, info) reason := editReason{ - head: fmt.Sprintf("%s %s was not valid", info.Mapping.Kind, info.Name), + head: fmt.Sprintf("%s %q was not valid", info.Mapping.Resource, info.Name), } if err, ok := err.(errors.APIStatus); ok { if details := err.Status().Details; details != nil { for _, cause := range details.Causes { - reason.other = append(reason.other, cause.Message) + reason.other = append(reason.other, fmt.Sprintf("%s: %s", cause.Field, cause.Message)) } } } r.header.reasons = append(r.header.reasons, reason) - return fmt.Sprintf("Error: the %s %s is invalid", info.Mapping.Kind, info.Name) + return fmt.Sprintf("error: %s %q is invalid", info.Mapping.Resource, info.Name) case errors.IsNotFound(err): r.notfound++ - return fmt.Sprintf("Error: the %s %s could not be found on the server", info.Mapping.Kind, info.Name) + return fmt.Sprintf("error: %s %q could not be found on the server", info.Mapping.Resource, info.Name) default: r.retryable++ - return fmt.Sprintf("Error: the %s %s could not be patched: %v", info.Mapping.Kind, info.Name, err) + return fmt.Sprintf("error: %s %q could not be patched: %v", info.Mapping.Resource, info.Name, err) } } @@ -432,7 +454,8 @@ func hasLines(r io.Reader) (bool, error) { // in it. Note that if the given file has a syntax error, the transformation will // fail and we will manually drop all comments from the file. func stripComments(file []byte) []byte { - stripped, err := yaml.ToJSON(file) + stripped := file + stripped, err := yaml.ToJSON(stripped) if err != nil { stripped = manualStrip(file) } @@ -442,12 +465,15 @@ func stripComments(file []byte) []byte { // manualStrip is used for dropping comments from a YAML file func manualStrip(file []byte) []byte { stripped := []byte{} - for _, line := range bytes.Split(file, []byte("\n")) { + lines := bytes.Split(file, []byte("\n")) + for i, line := range lines { if bytes.HasPrefix(bytes.TrimSpace(line), []byte("#")) { continue } stripped = append(stripped, line...) - stripped = append(stripped, '\n') + if i < len(lines)-1 { + stripped = append(stripped, '\n') + } } return stripped } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/exec.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/exec.go index eec6da62f..e59925873 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/exec.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/exec.go @@ -28,21 +28,23 @@ import ( "github.com/golang/glog" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/restclient" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/remotecommand" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand" ) const ( exec_example = `# Get output from running 'date' from pod 123456-7890, using the first container by default -$ kubectl exec 123456-7890 date +kubectl exec 123456-7890 date # Get output from running 'date' in ruby-container from pod 123456-7890 -$ kubectl exec 123456-7890 -c ruby-container date +kubectl exec 123456-7890 -c ruby-container date # Switch to raw terminal mode, sends stdin to 'bash' in ruby-container from pod 123456-7890 # and sends stdout/stderr from 'bash' back to the client -$ kubectl exec 123456-7890 -c ruby-container -i -t -- bash -il` +kubectl exec 123456-7890 -c ruby-container -i -t -- bash -il` ) func NewCmdExec(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command { @@ -75,18 +77,18 @@ func NewCmdExec(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) * // RemoteExecutor defines the interface accepted by the Exec command - provided for test stubbing type RemoteExecutor interface { - Execute(method string, url *url.URL, config *client.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error + Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error } // DefaultRemoteExecutor is the standard implementation of remote command execution type DefaultRemoteExecutor struct{} -func (*DefaultRemoteExecutor) Execute(method string, url *url.URL, config *client.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error { +func (*DefaultRemoteExecutor) Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error { exec, err := remotecommand.NewExecutor(config, method, url) if err != nil { return err } - return exec.Stream(stdin, stdout, stderr, tty) + return exec.Stream(remotecommandserver.SupportedStreamingProtocols, stdin, stdout, stderr, tty) } // ExecOptions declare the arguments accepted by the Exec command @@ -104,7 +106,7 @@ type ExecOptions struct { Executor RemoteExecutor Client *client.Client - Config *client.Config + Config *restclient.Config } // Complete verifies command line arguments and loads data from the command environment diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/exec_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/exec_test.go index 8317db936..001f59a47 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/exec_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/exec_test.go @@ -30,7 +30,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/fake" ) @@ -40,7 +40,7 @@ type fakeRemoteExecutor struct { execErr error } -func (f *fakeRemoteExecutor) Execute(method string, url *url.URL, config *client.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error { +func (f *fakeRemoteExecutor) Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error { f.method = method f.url = url return f.execErr @@ -130,7 +130,7 @@ func TestPodAndContainer(t *testing.T) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { return nil, nil }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{} + tf.ClientConfig = &restclient.Config{} cmd := &cobra.Command{} options := test.p @@ -196,7 +196,7 @@ func TestExec(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: test.version}}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: test.version}}} bufOut := bytes.NewBuffer([]byte{}) bufErr := bytes.NewBuffer([]byte{}) bufIn := bytes.NewBuffer([]byte{}) diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/explain.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/explain.go index 765091c03..ce959e394 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/explain.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/explain.go @@ -29,18 +29,14 @@ import ( const ( explainExamples = `# Get the documentation of the resource and its fields -$ kubectl explain pods +kubectl explain pods # Get the documentation of a specific field of a resource -$ kubectl explain pods.spec.containers` +kubectl explain pods.spec.containers` explainLong = `Documentation of resources. -Possible resource types include: pods (po), services (svc), -replicationcontrollers (rc), nodes (no), events (ev), componentstatuses (cs), -limitranges (limits), persistentvolumes (pv), persistentvolumeclaims (pvc), -resourcequotas (quota), namespaces (ns), horizontalpodautoscalers (hpa) -or endpoints (ep).` +` + kubectl.PossibleResourceTypes ) // NewCmdExplain returns a cobra command for swagger docs @@ -56,6 +52,7 @@ func NewCmdExplain(f *cmdutil.Factory, out io.Writer) *cobra.Command { }, } cmd.Flags().Bool("recursive", false, "Print the fields of fields (Currently only 1 level deep)") + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } @@ -69,7 +66,7 @@ func RunExplain(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st apiVersionString := cmdutil.GetFlagString(cmd, "api-version") apiVersion := unversioned.GroupVersion{} - mapper, _ := f.Object() + mapper, _ := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) // TODO: After we figured out the new syntax to separate group and resource, allow // the users to use it in explain (kubectl explain ). // Refer to issue #16039 for why we do this. Refer to PR #15808 that used "/" syntax. @@ -79,9 +76,16 @@ func RunExplain(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st } // TODO: We should deduce the group for a resource by discovering the supported resources at server. - gvk, err := mapper.KindFor(unversioned.GroupVersionResource{Resource: inModel}) - if err != nil { - return err + fullySpecifiedGVR, groupResource := unversioned.ParseResourceArg(inModel) + gvk := unversioned.GroupVersionKind{} + if fullySpecifiedGVR != nil { + gvk, _ = mapper.KindFor(*fullySpecifiedGVR) + } + if gvk.IsEmpty() { + gvk, err = mapper.KindFor(groupResource.WithVersion("")) + if err != nil { + return err + } } if len(apiVersionString) == 0 { diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/expose.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/expose.go index 48b624436..6a7e804fc 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/expose.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/expose.go @@ -34,35 +34,39 @@ import ( // referencing the cmd.Flags() type ExposeOptions struct { Filenames []string + Recursive bool } const ( - expose_long = `Take a replication controller, service, replica set or pod and expose it as a new Kubernetes service. + expose_long = `Take a deployment, service, replica set, replication controller, or pod and expose it as a new Kubernetes service. -Looks up a replication controller, service, replica set or pod by name and uses the selector for that -resource as the selector for a new service on the specified port. A replica set will be exposed as a -service only if it's selector is convertible to a selector that service supports, i.e. when the -replica set selector contains only the matchLabels component. Note that if no port is specified -via --port and the exposed resource has multiple ports, all will be re-used by the new service. Also -if no labels are specified, the new service will re-use the labels from the resource it exposes.` +Looks up a deployment, service, replica set, replication controller or pod by name and uses the selector +for that resource as the selector for a new service on the specified port. A deployment or replica set +will be exposed as a service only if its selector is convertible to a selector that service supports, +i.e. when the selector contains only the matchLabels component. Note that if no port is specified via +--port and the exposed resource has multiple ports, all will be re-used by the new service. Also if no +labels are specified, the new service will re-use the labels from the resource it exposes.` expose_example = `# Create a service for a replicated nginx, which serves on port 80 and connects to the containers on port 8000. -$ kubectl expose rc nginx --port=80 --target-port=8000 +kubectl expose rc nginx --port=80 --target-port=8000 # Create a service for a replication controller identified by type and name specified in "nginx-controller.yaml", which serves on port 80 and connects to the containers on port 8000. -$ kubectl expose -f nginx-controller.yaml --port=80 --target-port=8000 +kubectl expose -f nginx-controller.yaml --port=80 --target-port=8000 # Create a service for a pod valid-pod, which serves on port 444 with the name "frontend" -$ kubectl expose pod valid-pod --port=444 --name=frontend +kubectl expose pod valid-pod --port=444 --name=frontend # Create a second service based on the above service, exposing the container port 8443 as port 443 with the name "nginx-https" -$ kubectl expose service nginx --port=443 --target-port=8443 --name=nginx-https +kubectl expose service nginx --port=443 --target-port=8443 --name=nginx-https # Create a service for a replicated streaming application on port 4100 balancing UDP traffic and named 'video-stream'. -$ kubectl expose rc streamer --port=4100 --protocol=udp --name=video-stream +kubectl expose rc streamer --port=4100 --protocol=udp --name=video-stream # Create a service for a replicated nginx using replica set, which serves on port 80 and connects to the containers on port 8000. -$ kubectl expose rs nginx --port=80 --target-port=8000` +kubectl expose rs nginx --port=80 --target-port=8000 + +# Create a service for an nginx deployment, which serves on port 80 and connects to the containers on port 8000. +kubectl expose deployment nginx --port=80 --target-port=8000` ) func NewCmdExposeService(f *cmdutil.Factory, out io.Writer) *cobra.Command { @@ -70,7 +74,7 @@ func NewCmdExposeService(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "expose (-f FILENAME | TYPE NAME) [--port=port] [--protocol=TCP|UDP] [--target-port=number-or-name] [--name=name] [--external-ip=external-ip-of-service] [--type=type]", - Short: "Take a replication controller, service or pod and expose it as a new Kubernetes Service", + Short: "Take a replication controller, service, deployment or pod and expose it as a new Kubernetes Service", Long: expose_long, Example: expose_example, Run: func(cmd *cobra.Command, args []string) { @@ -99,6 +103,7 @@ func NewCmdExposeService(f *cmdutil.Factory, out io.Writer) *cobra.Command { usage := "Filename, directory, or URL to a file identifying the resource to expose a service" kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddRecordFlag(cmd) return cmd @@ -110,11 +115,11 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str return err } - mapper, typer := f.Object() + mapper, typer := f.Object(false) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() @@ -212,7 +217,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), Decoder: f.Decoder(true), } - info, err = resourceMapper.InfoForObject(object) + info, err = resourceMapper.InfoForObject(object, nil) if err != nil { return err } @@ -224,7 +229,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str info.Refresh(object, true) // TODO: extract this flag to a central location, when such a location exists. if cmdutil.GetFlagBool(cmd, "dry-run") { - return f.PrintObject(cmd, object, out) + return f.PrintObject(cmd, mapper, object, out) } if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, f.JSONEncoder()); err != nil { return err @@ -237,7 +242,7 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str } if len(cmdutil.GetFlagString(cmd, "output")) > 0 { - return f.PrintObject(cmd, object, out) + return f.PrintObject(cmd, mapper, object, out) } cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, "exposed") return nil diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/get.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/get.go index 86fd73130..493fff9f4 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/get.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/get.go @@ -33,42 +33,39 @@ import ( // referencing the cmd.Flags() type GetOptions struct { Filenames []string + Recursive bool } const ( get_long = `Display one or many resources. -Possible resource types include (case insensitive): pods (po), services (svc), -replicationcontrollers (rc), nodes (no), events (ev), componentstatuses (cs), -limitranges (limits), persistentvolumes (pv), persistentvolumeclaims (pvc), -resourcequotas (quota), namespaces (ns), endpoints (ep), -horizontalpodautoscalers (hpa), serviceaccounts or secrets. +` + kubectl.PossibleResourceTypes + ` By specifying the output as 'template' and providing a Go template as the value of the --template flag, you can filter the attributes of the fetched resource(s).` get_example = `# List all pods in ps output format. -$ kubectl get pods +kubectl get pods # List all pods in ps output format with more information (such as node name). -$ kubectl get pods -o wide +kubectl get pods -o wide # List a single replication controller with specified NAME in ps output format. -$ kubectl get replicationcontroller web +kubectl get replicationcontroller web # List a single pod in JSON output format. -$ kubectl get -o json pod web-pod-13je7 +kubectl get -o json pod web-pod-13je7 # List a pod identified by type and name specified in "pod.yaml" in JSON output format. -$ kubectl get -f pod.yaml -o json +kubectl get -f pod.yaml -o json # Return only the phase value of the specified pod. -$ kubectl get -o template pod/web-pod-13je7 --template={{.status.phase}} --api-version=v1 +kubectl get -o template pod/web-pod-13je7 --template={{.status.phase}} # List all replication controllers and services together in ps output format. -$ kubectl get rc,services +kubectl get rc,services # List one or more resources by their type and names. -$ kubectl get rc/web service/frontend pods/web-pod-13je7` +kubectl get rc/web service/frontend pods/web-pod-13je7` ) // NewCmdGet creates a command object for the generic "get" action, which @@ -104,6 +101,8 @@ func NewCmdGet(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Bool("export", false, "If true, use 'export' for the resources. Exported resources are stripped of cluster-specific information.") usage := "Filename, directory, or URL to a file identifying the resource to get from a server." kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } @@ -112,7 +111,7 @@ func NewCmdGet(f *cmdutil.Factory, out io.Writer) *cobra.Command { func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *GetOptions) error { selector := cmdutil.GetFlagString(cmd, "selector") allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces") - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { @@ -143,7 +142,7 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string if isWatch || isWatchOnly { r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). SelectorParam(selector). ExportParam(export). ResourceTypeOrNameArgs(true, args...). @@ -198,7 +197,7 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). SelectorParam(selector). ExportParam(export). ResourceTypeOrNameArgs(true, args...). @@ -248,6 +247,23 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string sorting, err := cmd.Flags().GetString("sort-by") var sorter *kubectl.RuntimeSort if err == nil && len(sorting) > 0 && len(objs) > 1 { + clientConfig, err := f.ClientConfig() + if err != nil { + return err + } + + version, err := cmdutil.OutputVersion(cmd, clientConfig.GroupVersion) + if err != nil { + return err + } + + for ix := range infos { + objs[ix], err = infos[ix].Mapping.ConvertToVersion(infos[ix].Object, version.String()) + if err != nil { + return err + } + } + // TODO: questionable if sorter, err = kubectl.SortObjects(f.Decoder(true), objs, sorting); err != nil { return err @@ -262,10 +278,13 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string for ix := range objs { var mapping *meta.RESTMapping + var original runtime.Object if sorter != nil { mapping = infos[sorter.OriginalPosition(ix)].Mapping + original = infos[sorter.OriginalPosition(ix)].Object } else { mapping = infos[ix].Mapping + original = infos[ix].Object } if printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource { printer, err = f.PrinterForMapping(cmd, mapping, allNamespaces) @@ -275,12 +294,12 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string lastMapping = mapping } if _, found := printer.(*kubectl.HumanReadablePrinter); found { - if err := printer.PrintObj(objs[ix], w); err != nil { + if err := printer.PrintObj(original, w); err != nil { return err } continue } - if err := printer.PrintObj(objs[ix], out); err != nil { + if err := printer.PrintObj(original, w); err != nil { return err } } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/get_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/get_test.go index 4d4ac5e18..1fff2650f 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/get_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/get_test.go @@ -32,10 +32,10 @@ import ( "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" "k8s.io/kubernetes/pkg/api/unversioned" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/fake" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" "k8s.io/kubernetes/pkg/watch" "k8s.io/kubernetes/pkg/watch/json" ) @@ -122,7 +122,7 @@ func TestGetUnknownSchemaObject(t *testing.T) { Resp: &http.Response{StatusCode: 200, Body: objBody(codec, &internalType{Name: "foo"})}, } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdGet(f, buf) @@ -194,7 +194,7 @@ func TestGetUnknownSchemaObjectListGeneric(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdGet(f, buf) cmd.SetOutput(buf) @@ -236,7 +236,7 @@ func TestGetSchemaObject(t *testing.T) { Resp: &http.Response{StatusCode: 200, Body: objBody(codec, &api.ReplicationController{ObjectMeta: api.ObjectMeta{Name: "foo"}})}, } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: "v1"}}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: "v1"}}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdGet(f, buf) @@ -273,6 +273,56 @@ func TestGetObjects(t *testing.T) { } } +func TestGetSortedObjects(t *testing.T) { + pods := &api.PodList{ + ListMeta: unversioned.ListMeta{ + ResourceVersion: "15", + }, + Items: []api.Pod{ + { + ObjectMeta: api.ObjectMeta{Name: "c", Namespace: "test", ResourceVersion: "10"}, + Spec: apitesting.DeepEqualSafePodSpec(), + }, + { + ObjectMeta: api.ObjectMeta{Name: "b", Namespace: "test", ResourceVersion: "11"}, + Spec: apitesting.DeepEqualSafePodSpec(), + }, + { + ObjectMeta: api.ObjectMeta{Name: "a", Namespace: "test", ResourceVersion: "9"}, + Spec: apitesting.DeepEqualSafePodSpec(), + }, + }, + } + + f, tf, codec := NewAPIFactory() + tf.Printer = &testPrinter{} + tf.Client = &fake.RESTClient{ + Codec: codec, + Resp: &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, + } + tf.Namespace = "test" + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: "v1"}}} + + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdGet(f, buf) + cmd.SetOutput(buf) + + // sorting with metedata.name + cmd.Flags().Set("sort-by", ".metadata.name") + cmd.Run(cmd, []string{"pods"}) + + // expect sorted: a,b,c + expected := []runtime.Object{&pods.Items[2], &pods.Items[1], &pods.Items[0]} + actual := tf.Printer.(*testPrinter).Objects + if !reflect.DeepEqual(expected, actual) { + t.Errorf("unexpected object: %#v", actual) + } + if len(buf.String()) == 0 { + t.Errorf("unexpected empty output") + } + +} + func TestGetObjectsIdentifiedByFile(t *testing.T) { pods, _, _ := testData() @@ -461,7 +511,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdGet(f, buf) @@ -583,7 +633,7 @@ func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) { expected := []runtime.Object{&svc.Items[0], node} actual := tf.Printer.(*testPrinter).Objects if !api.Semantic.DeepEqual(expected, actual) { - t.Errorf("unexpected object: %s", util.ObjectDiff(expected, actual)) + t.Errorf("unexpected object: %s", diff.ObjectDiff(expected, actual)) } if len(buf.String()) == 0 { t.Errorf("unexpected empty output") diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/label.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/label.go index 39c401d52..4693959c2 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/label.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/label.go @@ -26,6 +26,7 @@ import ( "github.com/golang/glog" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -39,6 +40,7 @@ import ( // referencing the cmd.Flags() type LabelOptions struct { Filenames []string + Recursive bool } const ( @@ -48,23 +50,23 @@ A label must begin with a letter or number, and may contain letters, numbers, hy If --overwrite is true, then existing labels can be overwritten, otherwise attempting to overwrite a label will result in an error. If --resource-version is specified, then updates will use this resource version, otherwise the existing resource-version will be used.` label_example = `# Update pod 'foo' with the label 'unhealthy' and the value 'true'. -$ kubectl label pods foo unhealthy=true +kubectl label pods foo unhealthy=true # Update pod 'foo' with the label 'status' and the value 'unhealthy', overwriting any existing value. -$ kubectl label --overwrite pods foo status=unhealthy +kubectl label --overwrite pods foo status=unhealthy # Update all pods in the namespace -$ kubectl label pods --all status=unhealthy +kubectl label pods --all status=unhealthy # Update a pod identified by the type and name in "pod.json" -$ kubectl label -f pod.json status=unhealthy +kubectl label -f pod.json status=unhealthy # Update pod 'foo' only if the resource is unchanged from version 1. -$ kubectl label pods foo status=unhealthy --resource-version=1 +kubectl label pods foo status=unhealthy --resource-version=1 # Update pod 'foo' by removing a label named 'bar' if it exists. # Does not require the --overwrite flag. -$ kubectl label pods foo bar-` +kubectl label pods foo bar-` ) func NewCmdLabel(f *cmdutil.Factory, out io.Writer) *cobra.Command { @@ -96,16 +98,18 @@ func NewCmdLabel(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().String("resource-version", "", "If non-empty, the labels update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource.") usage := "Filename, directory, or URL to a file identifying the resource to update the labels" kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.") cmdutil.AddRecordFlag(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } -func validateNoOverwrites(meta *api.ObjectMeta, labels map[string]string) error { +func validateNoOverwrites(accessor meta.Object, labels map[string]string) error { allErrs := []error{} for key := range labels { - if value, found := meta.Labels[key]; found { + if value, found := accessor.GetLabels()[key]; found { allErrs = append(allErrs, fmt.Errorf("'%s' already has a value (%s), and --overwrite is false", key, value)) } } @@ -137,29 +141,31 @@ func parseLabels(spec []string) (map[string]string, []string, error) { } func labelFunc(obj runtime.Object, overwrite bool, resourceVersion string, labels map[string]string, remove []string) error { - meta, err := api.ObjectMetaFor(obj) + accessor, err := meta.Accessor(obj) if err != nil { return err } if !overwrite { - if err := validateNoOverwrites(meta, labels); err != nil { + if err := validateNoOverwrites(accessor, labels); err != nil { return err } } - if meta.Labels == nil { - meta.Labels = make(map[string]string) + objLabels := accessor.GetLabels() + if objLabels == nil { + objLabels = make(map[string]string) } for key, value := range labels { - meta.Labels[key] = value + objLabels[key] = value } for _, label := range remove { - delete(meta.Labels, label) + delete(objLabels, label) } + accessor.SetLabels(objLabels) if len(resourceVersion) != 0 { - meta.ResourceVersion = resourceVersion + accessor.SetResourceVersion(resourceVersion) } return nil } @@ -202,11 +208,11 @@ func RunLabel(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri if err != nil { return cmdutil.UsageError(cmd, err.Error()) } - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). SelectorParam(selector). ResourceTypeOrNameArgs(all, resources...). Flatten(). @@ -238,14 +244,21 @@ func RunLabel(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri } outputObj = info.Object } else { - name, namespace, obj := info.Name, info.Namespace, info.Object + obj, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion().String()) + if err != nil { + return err + } + name, namespace := info.Name, info.Namespace oldData, err := json.Marshal(obj) if err != nil { return err } - meta, err := api.ObjectMetaFor(obj) + accessor, err := meta.Accessor(obj) + if err != nil { + return err + } for _, label := range remove { - if _, ok := meta.Labels[label]; !ok { + if _, ok := accessor.GetLabels()[label]; !ok { fmt.Fprintf(out, "label %q not found.\n", label) } } @@ -289,7 +302,7 @@ func RunLabel(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri } outputFormat := cmdutil.GetFlagString(cmd, "output") if outputFormat != "" { - return f.PrintObject(cmd, outputObj, out) + return f.PrintObject(cmd, mapper, outputObj, out) } cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Resource, info.Name, dataChangeMsg) return nil diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/label_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/label_test.go index 5267bc3cc..50859fd60 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/label_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/label_test.go @@ -25,7 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/fake" "k8s.io/kubernetes/pkg/runtime" ) @@ -301,7 +301,7 @@ func TestLabelErrors(t *testing.T) { f, tf, _ := NewAPIFactory() tf.Printer = &testPrinter{} tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdLabel(f, buf) @@ -354,7 +354,7 @@ func TestLabelForResourceFromFile(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdLabel(f, buf) @@ -403,7 +403,7 @@ func TestLabelMultipleObjects(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdLabel(f, buf) diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/logs.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/logs.go index 369d41d8a..b79d8c07d 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/logs.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/logs.go @@ -28,7 +28,7 @@ import ( "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/validation" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/runtime" @@ -36,19 +36,19 @@ import ( const ( logs_example = `# Return snapshot logs from pod nginx with only one container -$ kubectl logs nginx +kubectl logs nginx # Return snapshot of previous terminated ruby container logs from pod web-1 -$ kubectl logs -p -c ruby web-1 +kubectl logs -p -c ruby web-1 # Begin streaming the logs of the ruby container in pod web-1 -$ kubectl logs -f -c ruby web-1 +kubectl logs -f -c ruby web-1 # Display only the most recent 20 lines of output in pod nginx -$ kubectl logs --tail=20 nginx +kubectl logs --tail=20 nginx # Show all logs from pod nginx written in the last hour -$ kubectl logs --since=1h nginx` +kubectl logs --since=1h nginx` ) type LogsOptions struct { @@ -61,7 +61,8 @@ type LogsOptions struct { ClientMapper resource.ClientMapper Decoder runtime.Decoder - LogsForObject func(object, options runtime.Object) (*client.Request, error) + Object runtime.Object + LogsForObject func(object, options runtime.Object) (*restclient.Request, error) Out io.Writer } @@ -100,6 +101,7 @@ func NewCmdLogs(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Bool("interactive", false, "If true, prompt the user for input when required.") cmd.Flags().MarkDeprecated("interactive", "This flag is no longer respected and there is no replacement.") + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } @@ -150,14 +152,27 @@ func (o *LogsOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Com logOptions.SinceSeconds = &sec } o.Options = logOptions - - o.Mapper, o.Typer = f.Object() - o.Decoder = f.Decoder(true) - o.ClientMapper = resource.ClientMapperFunc(f.ClientForMapping) o.LogsForObject = f.LogsForObject - + o.ClientMapper = resource.ClientMapperFunc(f.ClientForMapping) o.Out = out + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) + decoder := f.Decoder(true) + if o.Object == nil { + infos, err := resource.NewBuilder(mapper, typer, o.ClientMapper, decoder). + NamespaceParam(o.Namespace).DefaultNamespace(). + ResourceNames("pods", o.ResourceArg). + SingleResourceType(). + Do().Infos() + if err != nil { + return err + } + if len(infos) != 1 { + return errors.New("expected a resource") + } + o.Object = infos[0].Object + } + return nil } @@ -178,20 +193,7 @@ func (o LogsOptions) Validate() error { // RunLogs retrieves a pod log func (o LogsOptions) RunLogs() (int64, error) { - infos, err := resource.NewBuilder(o.Mapper, o.Typer, o.ClientMapper, o.Decoder). - NamespaceParam(o.Namespace).DefaultNamespace(). - ResourceNames("pods", o.ResourceArg). - SingleResourceType(). - Do().Infos() - if err != nil { - return 0, err - } - if len(infos) != 1 { - return 0, errors.New("expected a resource") - } - info := infos[0] - - req, err := o.LogsForObject(info.Object, o.Options) + req, err := o.LogsForObject(o.Object, o.Options) if err != nil { return 0, err } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/logs_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/logs_test.go index caa867031..9f1205f71 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/logs_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/logs_test.go @@ -28,7 +28,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/fake" ) @@ -66,7 +66,7 @@ func TestLog(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: test.version}}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: test.version}}} buf := bytes.NewBuffer([]byte{}) cmd := NewCmdLogs(f, buf) @@ -130,7 +130,6 @@ func TestValidateLogFlags(t *testing.T) { cmd.Run = func(cmd *cobra.Command, args []string) { o.Complete(f, os.Stdout, cmd, args) out = o.Validate().Error() - o.RunLogs() } cmd.Run(cmd, []string{"foo"}) diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/patch.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/patch.go index cd35b9d30..093a82543 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/patch.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/patch.go @@ -37,6 +37,7 @@ var patchTypes = map[string]api.PatchType{"json": api.JSONPatchType, "merge": ap // referencing the cmd.Flags() type PatchOptions struct { Filenames []string + Recursive bool } const ( @@ -79,9 +80,11 @@ func NewCmdPatch(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().String("type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List())) cmdutil.AddOutputFlagsForMutation(cmd) cmdutil.AddRecordFlag(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) usage := "Filename, directory, or URL to a file identifying the resource to update" kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) return cmd } @@ -110,11 +113,11 @@ func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri return fmt.Errorf("unable to parse %q: %v", patch, err) } - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() @@ -139,18 +142,17 @@ func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri } helper := resource.NewHelper(client, mapping) - _, err = helper.Patch(namespace, name, patchType, patchBytes) + patchedObject, err := helper.Patch(namespace, name, patchType, patchBytes) if err != nil { return err } if cmdutil.ShouldRecord(cmd, info) { - patchBytes, err = cmdutil.ChangeResourcePatch(info, f.Command()) - if err != nil { - return err - } - _, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes) - if err != nil { - return err + if err := cmdutil.RecordChangeCause(patchedObject, f.Command()); err == nil { + // don't return an error on failure. The patch itself succeeded, its only the hint for that change that failed + // don't bother checking for failures of this replace, because a failure to indicate the hint doesn't fail the command + // also, don't force the replacement. If the replacement fails on a resourceVersion conflict, then it means this + // record hint is likely to be invalid anyway, so avoid the bad hint + resource.NewHelper(client, mapping).Replace(namespace, name, false, patchedObject) } } cmdutil.PrintSuccess(mapper, shortOutput, out, "", name, "patched") diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/portforward.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/portforward.go index 942a1c500..3fb2d3a4d 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/portforward.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/portforward.go @@ -24,7 +24,7 @@ import ( "github.com/golang/glog" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/portforward" "k8s.io/kubernetes/pkg/client/unversioned/remotecommand" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -33,16 +33,16 @@ import ( const ( portforward_example = ` # Listen on ports 5000 and 6000 locally, forwarding data to/from ports 5000 and 6000 in the pod -$ kubectl port-forward mypod 5000 6000 +kubectl port-forward mypod 5000 6000 # Listen on port 8888 locally, forwarding to 5000 in the pod -$ kubectl port-forward mypod 8888:5000 +kubectl port-forward mypod 8888:5000 # Listen on a random port locally, forwarding to 5000 in the pod -$ kubectl port-forward mypod :5000 +kubectl port-forward mypod :5000 # Listen on a random port locally, forwarding to 5000 in the pod -$ kubectl port-forward mypod 0:5000` +kubectl port-forward mypod 0:5000` ) func NewCmdPortForward(f *cmdutil.Factory) *cobra.Command { @@ -62,12 +62,12 @@ func NewCmdPortForward(f *cmdutil.Factory) *cobra.Command { } type portForwarder interface { - ForwardPorts(method string, url *url.URL, config *client.Config, ports []string, stopChan <-chan struct{}) error + ForwardPorts(method string, url *url.URL, config *restclient.Config, ports []string, stopChan <-chan struct{}) error } type defaultPortForwarder struct{} -func (*defaultPortForwarder) ForwardPorts(method string, url *url.URL, config *client.Config, ports []string, stopChan <-chan struct{}) error { +func (*defaultPortForwarder) ForwardPorts(method string, url *url.URL, config *restclient.Config, ports []string, stopChan <-chan struct{}) error { dialer, err := remotecommand.NewExecutor(config, method, url) if err != nil { return err diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/portforward_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/portforward_test.go index eebda28b8..d88ab90f4 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/portforward_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/portforward_test.go @@ -27,7 +27,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/fake" ) @@ -37,7 +37,7 @@ type fakePortForwarder struct { pfErr error } -func (f *fakePortForwarder) ForwardPorts(method string, url *url.URL, config *client.Config, ports []string, stopChan <-chan struct{}) error { +func (f *fakePortForwarder) ForwardPorts(method string, url *url.URL, config *restclient.Config, ports []string, stopChan <-chan struct{}) error { f.method = method f.url = url return f.pfErr @@ -84,7 +84,7 @@ func TestPortForward(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: test.version}}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: test.version}}} ff := &fakePortForwarder{} if test.pfErr { ff.pfErr = fmt.Errorf("pf error") @@ -154,7 +154,7 @@ func TestPortForwardWithPFlag(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: test.version}}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: test.version}}} ff := &fakePortForwarder{} if test.pfErr { ff.pfErr = fmt.Errorf("pf error") diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/proxy.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/proxy.go index 130e2ae59..77330af76 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/proxy.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/proxy.go @@ -32,15 +32,15 @@ import ( const ( default_port = 8001 proxy_example = `# Run a proxy to kubernetes apiserver on port 8011, serving static content from ./local/www/ -$ kubectl proxy --port=8011 --www=./local/www/ +kubectl proxy --port=8011 --www=./local/www/ # Run a proxy to kubernetes apiserver on an arbitrary local port. # The chosen port for the server will be output to stdout. -$ kubectl proxy --port=0 +kubectl proxy --port=0 # Run a proxy to kubernetes apiserver, changing the api prefix to k8s-api # This makes e.g. the pods api available at localhost:8011/k8s-api/v1/pods/ -$ kubectl proxy --api-prefix=/k8s-api` +kubectl proxy --api-prefix=/k8s-api` ) func NewCmdProxy(f *cmdutil.Factory, out io.Writer) *cobra.Command { diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/replace.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/replace.go index 81eacecbd..e7157ebb3 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/replace.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/replace.go @@ -35,6 +35,7 @@ import ( // referencing the cmd.Flags() type ReplaceOptions struct { Filenames []string + Recursive bool } const ( @@ -46,10 +47,10 @@ $ kubectl get TYPE NAME -o yaml Please refer to the models in https://htmlpreview.github.io/?https://github.com/kubernetes/kubernetes/blob/HEAD/docs/api-reference/v1/definitions.html to find if a field is mutable.` replace_example = `# Replace a pod using the data in pod.json. -$ kubectl replace -f ./pod.json +kubectl replace -f ./pod.json # Replace a pod based on the JSON passed into stdin. -$ cat pod.json | kubectl replace -f - +cat pod.json | kubectl replace -f - # Update a single-container pod's image version (tag) to v4 kubectl get pod mypod -o yaml | sed 's/\(image: myimage\):.*$/\1:v4/' | kubectl replace -f - @@ -82,9 +83,12 @@ func NewCmdReplace(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Int("grace-period", -1, "Only relevant during a force replace. Period of time in seconds given to the old resource to terminate gracefully. Ignored if negative.") cmd.Flags().Duration("timeout", 0, "Only relevant during a force replace. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object") cmdutil.AddValidateFlags(cmd) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) cmdutil.AddOutputFlagsForMutation(cmd) cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddRecordFlag(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) + return cmd } @@ -112,12 +116,12 @@ func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st return forceReplace(f, out, cmd, args, shortOutput, options) } - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). Flatten(). Do() err = r.Err() @@ -180,11 +184,11 @@ func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] } } - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). ResourceTypeOrNameArgs(false, args...).RequireObject(false). Flatten(). Do() @@ -209,7 +213,7 @@ func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). Flatten(). Do() err = r.Err() diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/replace_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/replace_test.go index 5ca2b2ec8..e511f92b8 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/replace_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/replace_test.go @@ -48,7 +48,7 @@ func TestReplaceObject(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdReplace(f, buf) - cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") + cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) @@ -95,7 +95,7 @@ func TestReplaceMultipleObject(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdReplace(f, buf) - cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") + cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml") cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) @@ -116,7 +116,7 @@ func TestReplaceMultipleObject(t *testing.T) { } func TestReplaceDirectory(t *testing.T) { - _, svc, rc := testData() + _, _, rc := testData() f, tf, codec := NewAPIFactory() tf.Printer = &testPrinter{} @@ -124,12 +124,8 @@ func TestReplaceDirectory(t *testing.T) { Codec: codec, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { - case strings.HasPrefix(p, "/namespaces/test/services/") && (m == "GET" || m == "PUT" || m == "DELETE"): - return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && (m == "GET" || m == "PUT" || m == "DELETE"): return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil - case strings.HasPrefix(p, "/namespaces/test/services") && m == "POST": - return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers") && m == "POST": return &http.Response{StatusCode: 201, Body: objBody(codec, &rc.Items[0])}, nil default: @@ -142,12 +138,12 @@ func TestReplaceDirectory(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdReplace(f, buf) - cmd.Flags().Set("filename", "../../../examples/guestbook") + cmd.Flags().Set("filename", "../../../examples/guestbook/legacy") cmd.Flags().Set("namespace", "test") cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) - if buf.String() != "replicationcontroller/rc1\nservice/baz\nreplicationcontroller/rc1\nservice/baz\nreplicationcontroller/rc1\nservice/baz\n" { + if buf.String() != "replicationcontroller/rc1\nreplicationcontroller/rc1\nreplicationcontroller/rc1\n" { t.Errorf("unexpected output: %s", buf.String()) } @@ -156,8 +152,8 @@ func TestReplaceDirectory(t *testing.T) { cmd.Flags().Set("cascade", "false") cmd.Run(cmd, []string{}) - if buf.String() != "replicationcontroller/frontend\nservice/frontend\nreplicationcontroller/redis-master\nservice/redis-master\nreplicationcontroller/redis-slave\nservice/redis-slave\n"+ - "replicationcontroller/rc1\nservice/baz\nreplicationcontroller/rc1\nservice/baz\nreplicationcontroller/rc1\nservice/baz\n" { + if buf.String() != "replicationcontroller/frontend\nreplicationcontroller/redis-master\nreplicationcontroller/redis-slave\n"+ + "replicationcontroller/rc1\nreplicationcontroller/rc1\nreplicationcontroller/rc1\n" { t.Errorf("unexpected output: %s", buf.String()) } } @@ -185,7 +181,7 @@ func TestForceReplaceObjectNotFound(t *testing.T) { buf := bytes.NewBuffer([]byte{}) cmd := NewCmdReplace(f, buf) - cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") + cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("force", "true") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("output", "name") diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollingupdate.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollingupdate.go index c3e796371..cdc8b44d8 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollingupdate.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollingupdate.go @@ -30,7 +30,6 @@ import ( "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/v1" - "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -50,20 +49,20 @@ Replaces the specified replication controller with a new replication controller new PodTemplate. The new-controller.json must specify the same namespace as the existing replication controller and overwrite at least one (common) label in its replicaSelector.` rollingUpdate_example = `# Update pods of frontend-v1 using new replication controller data in frontend-v2.json. -$ kubectl rolling-update frontend-v1 -f frontend-v2.json +kubectl rolling-update frontend-v1 -f frontend-v2.json # Update pods of frontend-v1 using JSON data passed into stdin. -$ cat frontend-v2.json | kubectl rolling-update frontend-v1 -f - +cat frontend-v2.json | kubectl rolling-update frontend-v1 -f - # Update the pods of frontend-v1 to frontend-v2 by just changing the image, and switching the # name of the replication controller. -$ kubectl rolling-update frontend-v1 frontend-v2 --image=image:v2 +kubectl rolling-update frontend-v1 frontend-v2 --image=image:v2 # Update the pods of frontend by just changing the image, and keeping the old name. -$ kubectl rolling-update frontend --image=image:v2 +kubectl rolling-update frontend --image=image:v2 # Abort and reverse an existing rollout in progress (from frontend-v1 to frontend-v2). -$ kubectl rolling-update frontend-v1 frontend-v2 --rollback +kubectl rolling-update frontend-v1 frontend-v2 --rollback ` ) @@ -102,6 +101,8 @@ func NewCmdRollingUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Bool("rollback", false, "If true, this is a request to abort an existing rollout that is partially rolled out. It effectively reverses current and next and runs a rollout") cmdutil.AddValidateFlags(cmd) cmdutil.AddPrinterFlags(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) + return cmd } @@ -190,7 +191,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg var keepOldName bool var replicasDefaulted bool - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) if len(filename) != 0 { schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"), cmdutil.GetFlagString(cmd, "schema-cache-dir")) @@ -201,7 +202,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg request := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). Schema(schema). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, filename). + FilenameParam(enforceNamespace, false, filename). Do() obj, err := request.Object() if err != nil { @@ -235,7 +236,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg // than the old rc. This selector is the hash of the rc, which will differ because the new rc has a // different image. if len(image) != 0 { - codec := registered.GroupOrDie(client.APIVersion().Group).Codec + codec := api.Codecs.LegacyCodec(client.APIVersion()) keepOldName = len(args) == 1 newName := findNewName(args, oldRc) if newRc, err = kubectl.LoadExistingNextReplicationController(client, cmdNamespace, newName); err != nil { @@ -311,10 +312,10 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg oldRcData.WriteString(oldRc.Name) newRcData.WriteString(newRc.Name) } else { - if err := f.PrintObject(cmd, oldRc, oldRcData); err != nil { + if err := f.PrintObject(cmd, mapper, oldRc, oldRcData); err != nil { return err } - if err := f.PrintObject(cmd, newRc, newRcData); err != nil { + if err := f.PrintObject(cmd, mapper, newRc, newRcData); err != nil { return err } } @@ -333,7 +334,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg Interval: interval, Timeout: timeout, CleanupPolicy: updateCleanupPolicy, - MaxUnavailable: intstr.FromInt(1), + MaxUnavailable: intstr.FromInt(0), MaxSurge: intstr.FromInt(1), } if rollback { @@ -359,7 +360,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg return err } if outputFormat != "" { - return f.PrintObject(cmd, newRc, out) + return f.PrintObject(cmd, mapper, newRc, out) } kind, err := api.Scheme.ObjectKind(newRc) if err != nil { diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout.go index 8fbb53595..93e9f2362 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout.go @@ -24,9 +24,9 @@ import ( ) const ( - rollout_long = `rollout manages a deployment using subcommands like "kubectl rollout undo deployment/abc"` + rollout_long = `Manages a deployment using subcommands like "kubectl rollout undo deployment/abc"` rollout_example = `# Rollback to the previous deployment -$ kubectl rollout undo deployment/abc` +kubectl rollout undo deployment/abc` rollout_valid_resources = `Valid resource types include: * deployments ` @@ -43,7 +43,6 @@ func NewCmdRollout(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Help() }, } - // subcommands cmd.AddCommand(NewCmdRolloutHistory(f, out)) cmd.AddCommand(NewCmdRolloutPause(f, out)) diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_history.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_history.go index f3eeb4a71..09fddde63 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_history.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_history.go @@ -32,12 +32,16 @@ import ( // referencing the cmd.Flags() type HistoryOptions struct { Filenames []string + Recursive bool } const ( - history_long = `view previous rollout revisions and configurations.` + history_long = `View previous rollout revisions and configurations.` history_example = `# View the rollout history of a deployment -$ kubectl rollout history deployment/abc` +kubectl rollout history deployment/abc + +# View the details of deployment revision 3 +kubectl rollout history deployment/abc --revision=3` ) func NewCmdRolloutHistory(f *cmdutil.Factory, out io.Writer) *cobra.Command { @@ -56,6 +60,7 @@ func NewCmdRolloutHistory(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Int64("revision", 0, "See the details, including podTemplate of the revision specified") usage := "Filename, directory, or URL to a file identifying the resource to get from a server." kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) return cmd } @@ -65,7 +70,7 @@ func RunHistory(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []st } revisionDetail := cmdutil.GetFlagInt64(cmd, "revision") - mapper, typer := f.Object() + mapper, typer := f.Object(false) cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { @@ -74,7 +79,7 @@ func RunHistory(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []st infos, err := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). ResourceTypeOrNameArgs(true, args...). Latest(). Flatten(). @@ -98,7 +103,6 @@ func RunHistory(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []st continue } - formattedOutput := "" if revisionDetail > 0 { // Print details of a specific revision template, ok := historyInfo.RevisionToTemplate[revisionDetail] @@ -106,16 +110,16 @@ func RunHistory(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, args []st return fmt.Errorf("unable to find revision %d of %s %q", revisionDetail, mapping.Resource, info.Name) } fmt.Fprintf(out, "%s %q revision %d\n", mapping.Resource, info.Name, revisionDetail) - formattedOutput, err = kubectl.DescribePodTemplate(template) + kubectl.DescribePodTemplate(template, out) } else { // Print all revisions - formattedOutput, err = kubectl.PrintRolloutHistory(historyInfo, mapping.Resource, info.Name) + formattedOutput, printErr := kubectl.PrintRolloutHistory(historyInfo, mapping.Resource, info.Name) + if printErr != nil { + errs = append(errs, printErr) + continue + } + fmt.Fprintf(out, "%s\n", formattedOutput) } - if err != nil { - errs = append(errs, err) - continue - } - fmt.Fprintf(out, "%s\n", formattedOutput) } return errors.NewAggregate(errs) diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_pause.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_pause.go index ae07dcefd..5eed89fde 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_pause.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_pause.go @@ -39,6 +39,7 @@ type PauseConfig struct { Out io.Writer Filenames []string + Recursive bool } const ( @@ -51,7 +52,7 @@ Currently only deployments support being paused.` pause_example = `# Mark the nginx deployment as paused. Any current state of # the deployment will continue its function, new updates to the deployment will not # have an effect as long as the deployment is paused. -$ kubectl rollout pause deployment/nginx` +kubectl rollout pause deployment/nginx` ) func NewCmdRolloutPause(f *cmdutil.Factory, out io.Writer) *cobra.Command { @@ -70,6 +71,7 @@ func NewCmdRolloutPause(f *cmdutil.Factory, out io.Writer) *cobra.Command { usage := "Filename, directory, or URL to a file identifying the resource to get from a server." kubectl.AddJsonFilenameFlag(cmd, &opts.Filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &opts.Recursive) return cmd } @@ -78,7 +80,7 @@ func (o *PauseConfig) CompletePause(f *cmdutil.Factory, cmd *cobra.Command, out return cmdutil.UsageError(cmd, cmd.Use) } - o.Mapper, o.Typer = f.Object() + o.Mapper, o.Typer = f.Object(false) o.PauseObject = f.PauseObject o.Out = out @@ -89,7 +91,7 @@ func (o *PauseConfig) CompletePause(f *cmdutil.Factory, cmd *cobra.Command, out infos, err := resource.NewBuilder(o.Mapper, o.Typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, o.Filenames...). + FilenameParam(enforceNamespace, o.Recursive, o.Filenames...). ResourceTypeOrNameArgs(true, args...). SingleResourceType(). Latest(). diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_resume.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_resume.go index a38147e75..cbdfc0dc3 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_resume.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_resume.go @@ -39,6 +39,7 @@ type ResumeConfig struct { Out io.Writer Filenames []string + Recursive bool } const ( @@ -49,7 +50,7 @@ resource, we allow it to be reconciled again. Currently only deployments support being resumed.` resume_example = `# Resume an already paused deployment -$ kubectl rollout resume deployment/nginx` +kubectl rollout resume deployment/nginx` ) func NewCmdRolloutResume(f *cmdutil.Factory, out io.Writer) *cobra.Command { @@ -68,6 +69,7 @@ func NewCmdRolloutResume(f *cmdutil.Factory, out io.Writer) *cobra.Command { usage := "Filename, directory, or URL to a file identifying the resource to get from a server." kubectl.AddJsonFilenameFlag(cmd, &opts.Filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &opts.Recursive) return cmd } @@ -76,7 +78,7 @@ func (o *ResumeConfig) CompleteResume(f *cmdutil.Factory, cmd *cobra.Command, ou return cmdutil.UsageError(cmd, cmd.Use) } - o.Mapper, o.Typer = f.Object() + o.Mapper, o.Typer = f.Object(false) o.ResumeObject = f.ResumeObject o.Out = out @@ -87,7 +89,7 @@ func (o *ResumeConfig) CompleteResume(f *cmdutil.Factory, cmd *cobra.Command, ou infos, err := resource.NewBuilder(o.Mapper, o.Typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, o.Filenames...). + FilenameParam(enforceNamespace, o.Recursive, o.Filenames...). ResourceTypeOrNameArgs(true, args...). SingleResourceType(). Latest(). diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_undo.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_undo.go index ccaf429ee..b70b003d8 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_undo.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/rollout/rollout_undo.go @@ -37,14 +37,19 @@ type UndoOptions struct { Typer runtime.ObjectTyper Info *resource.Info ToRevision int64 - Out io.Writer - Filenames []string + + Out io.Writer + Filenames []string + Recursive bool } const ( - undo_long = `undo rolls back to a previous rollout.` + undo_long = `Rollback to a previous rollout.` undo_example = `# Rollback to the previous deployment -$ kubectl rollout undo deployment/abc` +kubectl rollout undo deployment/abc + +# Rollback to deployment revision 3 +kubectl rollout undo deployment/abc --to-revision=3` ) func NewCmdRolloutUndo(f *cmdutil.Factory, out io.Writer) *cobra.Command { @@ -64,6 +69,7 @@ func NewCmdRolloutUndo(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Int64("to-revision", 0, "The revision to rollback to. Default to 0 (last revision).") usage := "Filename, directory, or URL to a file identifying the resource to get from a server." kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) return cmd } @@ -73,7 +79,7 @@ func (o *UndoOptions) CompleteUndo(f *cmdutil.Factory, cmd *cobra.Command, out i } o.ToRevision = cmdutil.GetFlagInt64(cmd, "to-revision") - o.Mapper, o.Typer = f.Object() + o.Mapper, o.Typer = f.Object(false) o.Out = out cmdNamespace, enforceNamespace, err := f.DefaultNamespace() @@ -83,7 +89,7 @@ func (o *UndoOptions) CompleteUndo(f *cmdutil.Factory, cmd *cobra.Command, out i infos, err := resource.NewBuilder(o.Mapper, o.Typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, o.Filenames...). + FilenameParam(enforceNamespace, o.Recursive, o.Filenames...). ResourceTypeOrNameArgs(true, args...). Latest(). Flatten(). diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/run.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/run.go index f95759a58..17cd31ed1 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/run.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/run.go @@ -25,6 +25,9 @@ import ( "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" + batchv1 "k8s.io/kubernetes/pkg/apis/batch/v1" + "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -36,34 +39,34 @@ const ( run_long = `Create and run a particular image, possibly replicated. Creates a deployment or job to manage the created container(s).` run_example = `# Start a single instance of nginx. -$ kubectl run nginx --image=nginx +kubectl run nginx --image=nginx # Start a single instance of hazelcast and let the container expose port 5701 . -$ kubectl run hazelcast --image=hazelcast --port=5701 +kubectl run hazelcast --image=hazelcast --port=5701 # Start a single instance of hazelcast and set environment variables "DNS_DOMAIN=cluster" and "POD_NAMESPACE=default" in the container. -$ kubectl run hazelcast --image=hazelcast --env="DNS_DOMAIN=cluster" --env="POD_NAMESPACE=default" +kubectl run hazelcast --image=hazelcast --env="DNS_DOMAIN=cluster" --env="POD_NAMESPACE=default" # Start a replicated instance of nginx. -$ kubectl run nginx --image=nginx --replicas=5 +kubectl run nginx --image=nginx --replicas=5 # Dry run. Print the corresponding API objects without creating them. -$ kubectl run nginx --image=nginx --dry-run +kubectl run nginx --image=nginx --dry-run # Start a single instance of nginx, but overload the spec of the deployment with a partial set of values parsed from JSON. -$ kubectl run nginx --image=nginx --overrides='{ "apiVersion": "v1", "spec": { ... } }' +kubectl run nginx --image=nginx --overrides='{ "apiVersion": "v1", "spec": { ... } }' # Start a single instance of busybox and keep it in the foreground, don't restart it if it exits. -$ kubectl run -i --tty busybox --image=busybox --restart=Never +kubectl run -i --tty busybox --image=busybox --restart=Never # Start the nginx container using the default command, but use custom arguments (arg1 .. argN) for that command. -$ kubectl run nginx --image=nginx -- ... +kubectl run nginx --image=nginx -- ... # Start the nginx container using a different command and custom arguments. -$ kubectl run nginx --image=nginx --command -- ... +kubectl run nginx --image=nginx --command -- ... # Start the perl container to compute π to 2000 places and print it out. -$ kubectl run pi --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'` +kubectl run pi --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'` ) func NewCmdRun(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command { @@ -84,11 +87,12 @@ func NewCmdRun(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *c addRunFlags(cmd) cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddRecordFlag(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } func addRunFlags(cmd *cobra.Command) { - cmd.Flags().String("generator", "", "The name of the API generator to use. Default is 'deployment/v1beta1' if --restart=Always, otherwise the default is 'job/v1beta1'.") + cmd.Flags().String("generator", "", "The name of the API generator to use. Default is 'deployment/v1beta1' if --restart=Always, otherwise the default is 'job/v1'. This will happen only for cluster version at least 1.2, for olders we will fallback to 'run/v1' for --restart=Always, 'run-pod/v1' for others.") cmd.Flags().String("image", "", "The image for the container to run.") cmd.MarkFlagRequired("image") cmd.Flags().IntP("replicas", "r", 1, "Number of replicas to create for this container. Default is 1.") @@ -146,10 +150,29 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob generatorName := cmdutil.GetFlagString(cmd, "generator") if len(generatorName) == 0 { + client, err := f.Client() + if err != nil { + return err + } + resourcesList, err := client.Discovery().ServerResources() + if err != nil { + // this cover the cases where old servers do not expose discovery + resourcesList = nil + } if restartPolicy == api.RestartPolicyAlways { - generatorName = "deployment/v1beta1" + if contains(resourcesList, v1beta1.SchemeGroupVersion.WithResource("deployments")) { + generatorName = "deployment/v1beta1" + } else { + generatorName = "run/v1" + } } else { - generatorName = "job/v1beta1" + if contains(resourcesList, batchv1.SchemeGroupVersion.WithResource("jobs")) { + generatorName = "job/v1" + } else if contains(resourcesList, v1beta1.SchemeGroupVersion.WithResource("jobs")) { + generatorName = "job/v1beta1" + } else { + generatorName = "run-pod/v1" + } } } generators := f.Generators("run") @@ -234,7 +257,7 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob if err != nil { return err } - _, typer := f.Object() + _, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). @@ -248,12 +271,29 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob outputFormat := cmdutil.GetFlagString(cmd, "output") if outputFormat != "" { - return f.PrintObject(cmd, obj, cmdOut) + return f.PrintObject(cmd, mapper, obj, cmdOut) } cmdutil.PrintSuccess(mapper, false, cmdOut, mapping.Resource, args[0], "created") return nil } +// TODO turn this into reusable method checking available resources +func contains(resourcesList map[string]*unversioned.APIResourceList, resource unversioned.GroupVersionResource) bool { + if resourcesList == nil { + return false + } + resourcesGroup, ok := resourcesList[resource.GroupVersion().String()] + if !ok { + return false + } + for _, item := range resourcesGroup.APIResources { + if resource.Resource == item.Name { + return true + } + } + return false +} + func waitForPodRunning(c *client.Client, pod *api.Pod, out io.Writer) (status api.PodPhase, err error) { for { pod, err := c.Pods(pod.Namespace).Get(pod.Name) @@ -382,7 +422,7 @@ func generateService(f *cmdutil.Factory, cmd *cobra.Command, args []string, serv } if cmdutil.GetFlagString(cmd, "output") != "" { - return f.PrintObject(cmd, obj, out) + return f.PrintObject(cmd, mapper, obj, out) } cmdutil.PrintSuccess(mapper, false, out, mapping.Resource, args[0], "created") @@ -401,7 +441,7 @@ func createGeneratedObject(f *cmdutil.Factory, cmd *cobra.Command, generator kub return nil, "", nil, nil, err } - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) groupVersionKind, err := typer.ObjectKind(obj) if err != nil { return nil, "", nil, nil, err @@ -441,7 +481,7 @@ func createGeneratedObject(f *cmdutil.Factory, cmd *cobra.Command, generator kub ClientMapper: resource.ClientMapperFunc(f.ClientForMapping), Decoder: f.Decoder(true), } - info, err := resourceMapper.InfoForObject(obj) + info, err := resourceMapper.InfoForObject(obj, nil) if err != nil { return nil, "", nil, nil, err } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/run_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/run_test.go index a728a810e..1894deca8 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/run_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/run_test.go @@ -28,7 +28,7 @@ import ( "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/unversioned/fake" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/runtime" @@ -157,7 +157,7 @@ func TestRunArgsFollowDashRules(t *testing.T) { }), } tf.Namespace = "test" - tf.ClientConfig = &client.Config{} + tf.ClientConfig = &restclient.Config{} cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr) cmd.Flags().Set("image", "nginx") cmd.Flags().Set("generator", "run/v1") @@ -265,7 +265,7 @@ func TestGenerateService(t *testing.T) { for _, test := range tests { sawPOST := false f, tf, codec := NewAPIFactory() - tf.ClientConfig = &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} tf.Client = &fake.RESTClient{ Codec: codec, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -302,6 +302,7 @@ func TestGenerateService(t *testing.T) { cmd.Flags().String("output", "", "") cmd.Flags().Bool(cmdutil.ApplyAnnotationsFlag, false, "") cmd.Flags().Bool("record", false, "Record current kubectl command in the resource annotation.") + cmdutil.AddInclude3rdPartyFlags(cmd) addRunFlags(cmd) if !test.expectPOST { @@ -325,7 +326,7 @@ func TestGenerateService(t *testing.T) { t.Errorf("unexpected error: %v", err) } if test.expectPOST != sawPOST { - t.Error("expectPost: %v, sawPost: %v", test.expectPOST, sawPOST) + t.Errorf("expectPost: %v, sawPost: %v", test.expectPOST, sawPOST) } } } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/scale.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/scale.go index 6ffd43566..9c8c6f01b 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/scale.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/scale.go @@ -34,29 +34,30 @@ import ( // referencing the cmd.Flags() type ScaleOptions struct { Filenames []string + Recursive bool } const ( - scale_long = `Set a new size for a Replication Controller, Job, or Deployment. + scale_long = `Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job. Scale also allows users to specify one or more preconditions for the scale action. If --current-replicas or --resource-version is specified, it is validated before the scale is attempted, and it is guaranteed that the precondition holds true when the scale is sent to the server.` - scale_example = `# Scale replication controller named 'foo' to 3. -$ kubectl scale --replicas=3 rc/foo + scale_example = `# Scale a replicaset named 'foo' to 3. +kubectl scale --replicas=3 rs/foo # Scale a resource identified by type and name specified in "foo.yaml" to 3. -$ kubectl scale --replicas=3 -f foo.yaml +kubectl scale --replicas=3 -f foo.yaml # If the deployment named mysql's current size is 2, scale mysql to 3. -$ kubectl scale --current-replicas=2 --replicas=3 deployment/mysql +kubectl scale --current-replicas=2 --replicas=3 deployment/mysql # Scale multiple replication controllers. -$ kubectl scale --replicas=5 rc/foo rc/bar rc/baz +kubectl scale --replicas=5 rc/foo rc/bar rc/baz # Scale job named 'cron' to 3. -$ kubectl scale --replicas=3 job/cron` +kubectl scale --replicas=3 job/cron` ) // NewCmdScale returns a cobra command with the appropriate configuration and flags to run scale @@ -67,7 +68,7 @@ func NewCmdScale(f *cmdutil.Factory, out io.Writer) *cobra.Command { Use: "scale [--resource-version=version] [--current-replicas=count] --replicas=COUNT (-f FILENAME | TYPE NAME)", // resize is deprecated Aliases: []string{"resize"}, - Short: "Set a new size for a Replication Controller, Job, or Deployment.", + Short: "Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job.", Long: scale_long, Example: scale_example, Run: func(cmd *cobra.Command, args []string) { @@ -84,9 +85,11 @@ func NewCmdScale(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a scale operation, zero means don't wait.") cmdutil.AddOutputFlagsForMutation(cmd) cmdutil.AddRecordFlag(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) usage := "Filename, directory, or URL to a file identifying the resource to set a new size" kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) return cmd } @@ -106,11 +109,11 @@ func RunScale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri return err } - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). ResourceTypeOrNameArgs(false, args...). Flatten(). Do() diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/stop.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/stop.go index 606986617..fc25be9ae 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/stop.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/stop.go @@ -30,6 +30,7 @@ import ( // referencing the cmd.Flags() type StopOptions struct { Filenames []string + Recursive bool } const ( @@ -41,16 +42,16 @@ See 'kubectl delete --help' for more details. Attempts to shut down and delete a resource that supports graceful termination. If the resource is scalable it will be scaled to 0 before deletion.` stop_example = `# Shut down foo. -$ kubectl stop replicationcontroller foo +kubectl stop replicationcontroller foo # Stop pods and services with label name=myLabel. -$ kubectl stop pods,services -l name=myLabel +kubectl stop pods,services -l name=myLabel # Shut down the service defined in service.json -$ kubectl stop -f service.json +kubectl stop -f service.json # Shut down all resources in the path/to/resources directory -$ kubectl stop -f path/to/resources` +kubectl stop -f path/to/resources` ) func NewCmdStop(f *cmdutil.Factory, out io.Writer) *cobra.Command { @@ -69,12 +70,14 @@ func NewCmdStop(f *cmdutil.Factory, out io.Writer) *cobra.Command { } usage := "Filename, directory, or URL to file of resource(s) to be stopped." kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) + cmdutil.AddRecursiveFlag(cmd, &options.Recursive) cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on.") cmd.Flags().Bool("all", false, "[-all] to select all the specified resources.") cmd.Flags().Bool("ignore-not-found", false, "Treat \"resource not found\" as a successful stop.") cmd.Flags().Int("grace-period", -1, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative.") cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object") cmdutil.AddOutputFlagsForMutation(cmd) + cmdutil.AddInclude3rdPartyFlags(cmd) return cmd } @@ -84,12 +87,12 @@ func RunStop(f *cmdutil.Factory, cmd *cobra.Command, args []string, out io.Write return err } - mapper, typer := f.Object() + mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd)) r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). ResourceTypeOrNameArgs(false, args...). - FilenameParam(enforceNamespace, options.Filenames...). + FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). SelectorParam(cmdutil.GetFlagString(cmd, "selector")). SelectAllParam(cmdutil.GetFlagBool(cmd, "all")). Flatten(). diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/clientcache.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/clientcache.go index 523c57750..5e6551cde 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/clientcache.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/clientcache.go @@ -19,6 +19,7 @@ package util import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/client/restclient" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" ) @@ -26,7 +27,7 @@ import ( func NewClientCache(loader clientcmd.ClientConfig) *ClientCache { return &ClientCache{ clients: make(map[unversioned.GroupVersion]*client.Client), - configs: make(map[unversioned.GroupVersion]*client.Config), + configs: make(map[unversioned.GroupVersion]*restclient.Config), loader: loader, } } @@ -36,14 +37,14 @@ func NewClientCache(loader clientcmd.ClientConfig) *ClientCache { type ClientCache struct { loader clientcmd.ClientConfig clients map[unversioned.GroupVersion]*client.Client - configs map[unversioned.GroupVersion]*client.Config - defaultConfig *client.Config + configs map[unversioned.GroupVersion]*restclient.Config + defaultConfig *restclient.Config defaultClient *client.Client matchVersion bool } // ClientConfigForVersion returns the correct config for a server -func (c *ClientCache) ClientConfigForVersion(version *unversioned.GroupVersion) (*client.Config, error) { +func (c *ClientCache) ClientConfigForVersion(version *unversioned.GroupVersion) (*restclient.Config, error) { if c.defaultConfig == nil { config, err := c.loader.ClientConfig() if err != nil { diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/editor/editor.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/editor/editor.go index f77f8e137..1c58d846b 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/editor/editor.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/editor/editor.go @@ -23,13 +23,13 @@ import ( "math/rand" "os" "os/exec" - "os/signal" "path/filepath" "runtime" "strings" - "github.com/docker/docker/pkg/term" "github.com/golang/glog" + + "k8s.io/kubernetes/pkg/util/term" ) const ( @@ -125,7 +125,7 @@ func (e Editor) Launch(path string) error { cmd.Stderr = os.Stderr cmd.Stdin = os.Stdin glog.V(5).Infof("Opening file with editor %v", args) - if err := withSafeTTYAndInterrupts(cmd.Run); err != nil { + if err := (term.TTY{In: os.Stdin, TryDev: true}).Safe(cmd.Run); err != nil { if err, ok := err.(*exec.Error); ok { if err.Err == exec.ErrNotFound { return fmt.Errorf("unable to launch the editor %q", strings.Join(e.Args, " ")) @@ -160,40 +160,6 @@ func (e Editor) LaunchTempFile(prefix, suffix string, r io.Reader) ([]byte, stri return bytes, path, err } -// withSafeTTYAndInterrupts invokes the provided function after the terminal -// state has been stored, and then on any error or termination attempts to -// restore the terminal state to its prior behavior. It also eats signals -// for the duration of the function. -func withSafeTTYAndInterrupts(fn func() error) error { - ch := make(chan os.Signal, 1) - signal.Notify(ch, childSignals...) - defer signal.Stop(ch) - - inFd := os.Stdin.Fd() - if !term.IsTerminal(inFd) { - if f, err := os.Open("/dev/tty"); err == nil { - defer f.Close() - inFd = f.Fd() - } - } - - if term.IsTerminal(inFd) { - state, err := term.SaveState(inFd) - if err != nil { - return err - } - go func() { - if _, ok := <-ch; !ok { - return - } - term.RestoreTerminal(inFd, state) - }() - defer term.RestoreTerminal(inFd, state) - return fn() - } - return fn() -} - func tempFile(prefix, suffix string) (f *os.File, err error) { dir := os.TempDir() diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/factory.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/factory.go index ce75c476b..d04cc132c 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/factory.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/factory.go @@ -26,6 +26,7 @@ import ( "os" "os/user" "path" + "path/filepath" "strconv" "strings" "time" @@ -38,17 +39,23 @@ import ( "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/apimachinery" "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/apis/autoscaling" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" - clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/apis/metrics" + "k8s.io/kubernetes/pkg/client/restclient" client "k8s.io/kubernetes/pkg/client/unversioned" + clientset "k8s.io/kubernetes/pkg/client/unversioned/adapters/internalclientset" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime/serializer/json" - "k8s.io/kubernetes/pkg/util" + utilflag "k8s.io/kubernetes/pkg/util/flag" ) const ( @@ -63,10 +70,10 @@ const ( type Factory struct { clients *ClientCache flags *pflag.FlagSet - cmd string - // Returns interfaces for dealing with arbitrary runtime.Objects. - Object func() (meta.RESTMapper, runtime.ObjectTyper) + // Returns interfaces for dealing with arbitrary runtime.Objects. If thirdPartyDiscovery is true, performs API calls + // to discovery dynamic API objects registered by third parties. + Object func(thirdPartyDiscovery bool) (meta.RESTMapper, runtime.ObjectTyper) // Returns interfaces for decoding objects - if toInternal is set, decoded objects will be converted // into their internal form (if possible). Eventually the internal form will be removed as an option, // and only versioned objects will be returned. @@ -76,7 +83,7 @@ type Factory struct { // Returns a client for accessing Kubernetes resources or an error. Client func() (*client.Client, error) // Returns a client.Config for accessing the Kubernetes server. - ClientConfig func() (*client.Config, error) + ClientConfig func() (*restclient.Config, error) // Returns a RESTClient for working with the specified RESTMapping or an error. This is intended // for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer. ClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error) @@ -103,7 +110,7 @@ type Factory struct { // LabelsForObject returns the labels associated with the provided object LabelsForObject func(object runtime.Object) (map[string]string, error) // LogsForObject returns a request for the logs associated with the provided object - LogsForObject func(object, options runtime.Object) (*client.Request, error) + LogsForObject func(object, options runtime.Object) (*restclient.Request, error) // PauseObject marks the provided object as paused ie. it will not be reconciled by its controller. PauseObject func(object runtime.Object) (bool, error) // ResumeObject resumes a paused object ie. it will be reconciled by its controller. @@ -135,12 +142,15 @@ const ( RunPodV1GeneratorName = "run-pod/v1" ServiceV1GeneratorName = "service/v1" ServiceV2GeneratorName = "service/v2" + ServiceAccountV1GeneratorName = "serviceaccount/v1" HorizontalPodAutoscalerV1Beta1GeneratorName = "horizontalpodautoscaler/v1beta1" DeploymentV1Beta1GeneratorName = "deployment/v1beta1" JobV1Beta1GeneratorName = "job/v1beta1" + JobV1GeneratorName = "job/v1" NamespaceV1GeneratorName = "namespace/v1" SecretV1GeneratorName = "secret/v1" SecretForDockerRegistryV1GeneratorName = "secret-for-docker-registry/v1" + ConfigMapV1GeneratorName = "configmap/v1" ) // DefaultGenerators returns the set of default generators for use in Factory instances @@ -155,6 +165,7 @@ func DefaultGenerators(cmdName string) map[string]kubectl.Generator { RunPodV1GeneratorName: kubectl.BasicPod{}, DeploymentV1Beta1GeneratorName: kubectl.DeploymentV1Beta1{}, JobV1Beta1GeneratorName: kubectl.JobV1Beta1{}, + JobV1GeneratorName: kubectl.JobV1{}, } generators["autoscale"] = map[string]kubectl.Generator{ HorizontalPodAutoscalerV1Beta1GeneratorName: kubectl.HorizontalPodAutoscalerV1Beta1{}, @@ -171,14 +182,39 @@ func DefaultGenerators(cmdName string) map[string]kubectl.Generator { return generators[cmdName] } +func getGroupVersionKinds(gvks []unversioned.GroupVersionKind, group string) []unversioned.GroupVersionKind { + result := []unversioned.GroupVersionKind{} + for ix := range gvks { + if gvks[ix].Group == group { + result = append(result, gvks[ix]) + } + } + return result +} + +func makeInterfacesFor(versionList []unversioned.GroupVersion) func(version unversioned.GroupVersion) (*meta.VersionInterfaces, error) { + accessor := meta.NewAccessor() + return func(version unversioned.GroupVersion) (*meta.VersionInterfaces, error) { + for ix := range versionList { + if versionList[ix].String() == version.String() { + return &meta.VersionInterfaces{ + ObjectConvertor: thirdpartyresourcedata.NewThirdPartyObjectConverter(api.Scheme), + MetadataAccessor: accessor, + }, nil + } + } + return nil, fmt.Errorf("unsupported storage version: %s (valid: %v)", version, versionList) + } +} + // NewFactory creates a factory with the default Kubernetes resources defined // if optionalClientConfig is nil, then flags will be bound to a new clientcmd.ClientConfig. // if optionalClientConfig is not nil, then this factory will make use of it. func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { - mapper := kubectl.ShortcutExpander{RESTMapper: api.RESTMapper} + mapper := kubectl.ShortcutExpander{RESTMapper: registered.RESTMapper()} flags := pflag.NewFlagSet("", pflag.ContinueOnError) - flags.SetNormalizeFunc(util.WarnWordSepNormalizeFunc) // Warn for "_" flags + flags.SetNormalizeFunc(utilflag.WarnWordSepNormalizeFunc) // Warn for "_" flags clientConfig := optionalClientConfig if optionalClientConfig == nil { @@ -190,37 +226,117 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { return &Factory{ clients: clients, flags: flags, - cmd: recordCommand(os.Args), - Object: func() (meta.RESTMapper, runtime.ObjectTyper) { + // If discoverDynamicAPIs is true, make API calls to the discovery service to find APIs that + // have been dynamically added to the apiserver + Object: func(discoverDynamicAPIs bool) (meta.RESTMapper, runtime.ObjectTyper) { cfg, err := clientConfig.ClientConfig() CheckErr(err) cmdApiVersion := unversioned.GroupVersion{} if cfg.GroupVersion != nil { cmdApiVersion = *cfg.GroupVersion } + if discoverDynamicAPIs { + client, err := clients.ClientForVersion(&unversioned.GroupVersion{Version: "v1"}) + CheckErr(err) - return kubectl.OutputVersionMapper{RESTMapper: mapper, OutputVersions: []unversioned.GroupVersion{cmdApiVersion}}, api.Scheme + versions, gvks, err := GetThirdPartyGroupVersions(client.Discovery()) + CheckErr(err) + if len(versions) > 0 { + priorityMapper, ok := mapper.RESTMapper.(meta.PriorityRESTMapper) + if !ok { + CheckErr(fmt.Errorf("expected PriorityMapper, saw: %v", mapper.RESTMapper)) + return nil, nil + } + multiMapper, ok := priorityMapper.Delegate.(meta.MultiRESTMapper) + if !ok { + CheckErr(fmt.Errorf("unexpected type: %v", mapper.RESTMapper)) + return nil, nil + } + groupsMap := map[string][]unversioned.GroupVersion{} + for _, version := range versions { + groupsMap[version.Group] = append(groupsMap[version.Group], version) + } + for group, versionList := range groupsMap { + preferredExternalVersion := versionList[0] + + thirdPartyMapper, err := kubectl.NewThirdPartyResourceMapper(versionList, getGroupVersionKinds(gvks, group)) + CheckErr(err) + accessor := meta.NewAccessor() + groupMeta := apimachinery.GroupMeta{ + GroupVersion: preferredExternalVersion, + GroupVersions: versionList, + RESTMapper: thirdPartyMapper, + SelfLinker: runtime.SelfLinker(accessor), + InterfacesFor: makeInterfacesFor(versionList), + } + + CheckErr(registered.RegisterGroup(groupMeta)) + registered.AddThirdPartyAPIGroupVersions(versionList...) + multiMapper = append(meta.MultiRESTMapper{thirdPartyMapper}, multiMapper...) + } + priorityMapper.Delegate = multiMapper + // Re-assign to the RESTMapper here because priorityMapper is actually a copy, so if we + // don't re-assign, the above assignement won't actually update mapper.RESTMapper + mapper.RESTMapper = priorityMapper + } + } + outputRESTMapper := kubectl.OutputVersionMapper{RESTMapper: mapper, OutputVersions: []unversioned.GroupVersion{cmdApiVersion}} + priorityRESTMapper := meta.PriorityRESTMapper{ + Delegate: outputRESTMapper, + ResourcePriority: []unversioned.GroupVersionResource{ + {Group: api.GroupName, Version: meta.AnyVersion, Resource: meta.AnyResource}, + {Group: extensions.GroupName, Version: meta.AnyVersion, Resource: meta.AnyResource}, + {Group: metrics.GroupName, Version: meta.AnyVersion, Resource: meta.AnyResource}, + }, + KindPriority: []unversioned.GroupVersionKind{ + {Group: api.GroupName, Version: meta.AnyVersion, Kind: meta.AnyKind}, + {Group: extensions.GroupName, Version: meta.AnyVersion, Kind: meta.AnyKind}, + {Group: metrics.GroupName, Version: meta.AnyVersion, Kind: meta.AnyKind}, + }, + } + return priorityRESTMapper, api.Scheme }, Client: func() (*client.Client, error) { return clients.ClientForVersion(nil) }, - ClientConfig: func() (*client.Config, error) { + ClientConfig: func() (*restclient.Config, error) { return clients.ClientConfigForVersion(nil) }, ClientForMapping: func(mapping *meta.RESTMapping) (resource.RESTClient, error) { + gvk := mapping.GroupVersionKind mappingVersion := mapping.GroupVersionKind.GroupVersion() - client, err := clients.ClientForVersion(&mappingVersion) + c, err := clients.ClientForVersion(&mappingVersion) if err != nil { return nil, err } - switch mapping.GroupVersionKind.Group { + switch gvk.Group { case api.GroupName: - return client.RESTClient, nil + return c.RESTClient, nil + case autoscaling.GroupName: + return c.AutoscalingClient.RESTClient, nil + case batch.GroupName: + return c.BatchClient.RESTClient, nil case extensions.GroupName: - return client.ExtensionsClient.RESTClient, nil + return c.ExtensionsClient.RESTClient, nil + case api.SchemeGroupVersion.Group: + return c.RESTClient, nil + case extensions.SchemeGroupVersion.Group: + return c.ExtensionsClient.RESTClient, nil + default: + if !registered.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) { + return nil, fmt.Errorf("unknown api group/version: %s", gvk.String()) + } + cfg, err := clientConfig.ClientConfig() + if err != nil { + return nil, err + } + gv := gvk.GroupVersion() + cfg.GroupVersion = &gv + cfg.APIPath = "/apis" + cfg.Codec = thirdpartyresourcedata.NewCodec(c.ExtensionsClient.RESTClient.Codec(), gvk.Kind) + return restclient.RESTClientFor(cfg) } - return nil, fmt.Errorf("unable to get RESTClient for resource '%s'", mapping.Resource) }, Describer: func(mapping *meta.RESTMapping) (kubectl.Describer, error) { mappingVersion := mapping.GroupVersionKind.GroupVersion() @@ -299,14 +415,14 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { // TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals // operator, DoubleEquals operator and In operator with only one element in the set. if len(t.Spec.Selector.MatchExpressions) > 0 { - return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format") + return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions) } return kubectl.MakeLabels(t.Spec.Selector.MatchLabels), nil case *extensions.ReplicaSet: // TODO(madhusudancs): Make this smarter by admitting MatchExpressions with Equals // operator, DoubleEquals operator and In operator with only one element in the set. if len(t.Spec.Selector.MatchExpressions) > 0 { - return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format") + return "", fmt.Errorf("couldn't convert expressions - \"%+v\" to map-based selector format", t.Spec.Selector.MatchExpressions) } return kubectl.MakeLabels(t.Spec.Selector.MatchLabels), nil default: @@ -341,7 +457,7 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { LabelsForObject: func(object runtime.Object) (map[string]string, error) { return meta.NewAccessor().Labels(object) }, - LogsForObject: func(object, options runtime.Object) (*client.Request, error) { + LogsForObject: func(object, options runtime.Object) (*restclient.Request, error) { c, err := clients.ClientForVersion(nil) if err != nil { return nil, err @@ -354,6 +470,42 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { return nil, errors.New("provided options object is not a PodLogOptions") } return c.Pods(t.Namespace).GetLogs(t.Name, opts), nil + + case *api.ReplicationController: + opts, ok := options.(*api.PodLogOptions) + if !ok { + return nil, errors.New("provided options object is not a PodLogOptions") + } + selector := labels.SelectorFromSet(t.Spec.Selector) + pod, numPods, err := GetFirstPod(c, t.Namespace, selector) + if err != nil { + return nil, err + } + if numPods > 1 { + fmt.Fprintf(os.Stderr, "Found %v pods, using pod/%v\n", numPods, pod.Name) + } + + return c.Pods(pod.Namespace).GetLogs(pod.Name, opts), nil + + case *extensions.ReplicaSet: + opts, ok := options.(*api.PodLogOptions) + if !ok { + return nil, errors.New("provided options object is not a PodLogOptions") + } + selector, err := unversioned.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return nil, fmt.Errorf("invalid label selector: %v", err) + } + pod, numPods, err := GetFirstPod(c, t.Namespace, selector) + if err != nil { + return nil, err + } + if numPods > 1 { + fmt.Fprintf(os.Stderr, "Found %v pods, using pod/%v\n", numPods, pod.Name) + } + + return c.Pods(pod.Namespace).GetLogs(pod.Name, opts), nil + default: gvk, err := api.Scheme.ObjectKind(object) if err != nil { @@ -486,7 +638,7 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { }, CanBeAutoscaled: func(kind unversioned.GroupKind) error { switch kind { - case api.Kind("ReplicationController"), extensions.Kind("Deployment"): + case api.Kind("ReplicationController"), extensions.Kind("Deployment"), extensions.Kind("ReplicaSet"): // nothing to do here default: return fmt.Errorf("cannot autoscale a %v", kind) @@ -501,19 +653,22 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { switch t := object.(type) { case *api.ReplicationController: selector := labels.SelectorFromSet(t.Spec.Selector) - return GetFirstPod(client, t.Namespace, selector) + pod, _, err := GetFirstPod(client, t.Namespace, selector) + return pod, err case *extensions.Deployment: selector, err := unversioned.LabelSelectorAsSelector(t.Spec.Selector) if err != nil { return nil, fmt.Errorf("invalid label selector: %v", err) } - return GetFirstPod(client, t.Namespace, selector) + pod, _, err := GetFirstPod(client, t.Namespace, selector) + return pod, err case *extensions.Job: selector, err := unversioned.LabelSelectorAsSelector(t.Spec.Selector) if err != nil { return nil, fmt.Errorf("invalid label selector: %v", err) } - return GetFirstPod(client, t.Namespace, selector) + pod, _, err := GetFirstPod(client, t.Namespace, selector) + return pod, err case *api.Pod: return t, nil default: @@ -530,39 +685,37 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { } } -// GetFirstPod returns the first pod of an object from its namespace and selector -func GetFirstPod(client *client.Client, namespace string, selector labels.Selector) (*api.Pod, error) { +// GetFirstPod returns the first pod of an object from its namespace and selector and the number of matching pods +func GetFirstPod(client *client.Client, namespace string, selector labels.Selector) (*api.Pod, int, error) { var pods *api.PodList for pods == nil || len(pods.Items) == 0 { var err error options := api.ListOptions{LabelSelector: selector} if pods, err = client.Pods(namespace).List(options); err != nil { - return nil, err + return nil, 0, err } if len(pods.Items) == 0 { time.Sleep(2 * time.Second) } } pod := &pods.Items[0] - return pod, nil -} - -func recordCommand(args []string) string { - if len(args) > 0 { - args[0] = "kubectl" - } - return strings.Join(args, " ") + return pod, len(pods.Items), nil } +// Command will stringify and return all environment arguments ie. a command run by a client +// using the factory. +// TODO: We need to filter out stuff like secrets. func (f *Factory) Command() string { - return f.cmd + if len(os.Args) == 0 { + return "" + } + base := filepath.Base(os.Args[0]) + args := append([]string{base}, os.Args[1:]...) + return strings.Join(args, " ") } // BindFlags adds any flags that are common to all kubectl sub commands. func (f *Factory) BindFlags(flags *pflag.FlagSet) { - // any flags defined by external projects (not part of pflags) - flags.AddGoFlagSet(flag.CommandLine) - // Merge factory's flags flags.AddFlagSet(f.flags) @@ -574,7 +727,13 @@ func (f *Factory) BindFlags(flags *pflag.FlagSet) { // Normalize all flags that are coming from other packages or pre-configurations // a.k.a. change all "_" to "-". e.g. glog package - flags.SetNormalizeFunc(util.WordSepNormalizeFunc) + flags.SetNormalizeFunc(utilflag.WordSepNormalizeFunc) +} + +// BindCommonFlags adds any flags defined by external projects (not part of pflags) +func (f *Factory) BindExternalFlags(flags *pflag.FlagSet) { + // any flags defined by external projects (not part of pflags) + flags.AddGoFlagSet(flag.CommandLine) } func getPorts(spec api.PodSpec) []string { @@ -605,7 +764,7 @@ type clientSwaggerSchema struct { const schemaFileName = "schema.json" type schemaClient interface { - Get() *client.Request + Get() *restclient.Request } func recursiveSplit(dir string) []string { @@ -660,6 +819,7 @@ func writeSchemaFile(schemaData []byte, cacheDir, cacheFile, prefix, groupVersio func getSchemaAndValidate(c schemaClient, data []byte, prefix, groupVersion, cacheDir string) (err error) { var schemaData []byte + var firstSeen bool fullDir, err := substituteUserHome(cacheDir) if err != nil { return err @@ -672,24 +832,50 @@ func getSchemaAndValidate(c schemaClient, data []byte, prefix, groupVersion, cac } } if schemaData == nil { - schemaData, err = c.Get(). - AbsPath("/swaggerapi", prefix, groupVersion). - Do(). - Raw() + firstSeen = true + schemaData, err = downloadSchemaAndStore(c, cacheDir, fullDir, cacheFile, prefix, groupVersion) if err != nil { return err } - if len(cacheDir) != 0 { - if err := writeSchemaFile(schemaData, fullDir, cacheFile, prefix, groupVersion); err != nil { - return err - } - } } schema, err := validation.NewSwaggerSchemaFromBytes(schemaData) if err != nil { return err } - return schema.ValidateBytes(data) + err = schema.ValidateBytes(data) + if _, ok := err.(validation.TypeNotFoundError); ok && !firstSeen { + // As a temporay hack, kubectl would re-get the schema if validation + // fails for type not found reason. + // TODO: runtime-config settings needs to make into the file's name + schemaData, err = downloadSchemaAndStore(c, cacheDir, fullDir, cacheFile, prefix, groupVersion) + if err != nil { + return err + } + schema, err := validation.NewSwaggerSchemaFromBytes(schemaData) + if err != nil { + return err + } + return schema.ValidateBytes(data) + } + + return err +} + +// Download swagger schema from apiserver and store it to file. +func downloadSchemaAndStore(c schemaClient, cacheDir, fullDir, cacheFile, prefix, groupVersion string) (schemaData []byte, err error) { + schemaData, err = c.Get(). + AbsPath("/swaggerapi", prefix, groupVersion). + Do(). + Raw() + if err != nil { + return + } + if len(cacheDir) != 0 { + if err = writeSchemaFile(schemaData, fullDir, cacheFile, prefix, groupVersion); err != nil { + return + } + } + return } func (c *clientSwaggerSchema) ValidateBytes(data []byte) error { @@ -700,6 +886,22 @@ func (c *clientSwaggerSchema) ValidateBytes(data []byte) error { if ok := registered.IsEnabledVersion(gvk.GroupVersion()); !ok { return fmt.Errorf("API version %q isn't supported, only supports API versions %q", gvk.GroupVersion().String(), registered.EnabledVersions()) } + if gvk.Group == autoscaling.GroupName { + if c.c.AutoscalingClient == nil { + return errors.New("unable to validate: no autoscaling client") + } + return getSchemaAndValidate(c.c.AutoscalingClient.RESTClient, data, "apis/", gvk.GroupVersion().String(), c.cacheDir) + } + if gvk.Group == batch.GroupName { + if c.c.BatchClient == nil { + return errors.New("unable to validate: no batch client") + } + return getSchemaAndValidate(c.c.BatchClient.RESTClient, data, "apis/", gvk.GroupVersion().String(), c.cacheDir) + } + if registered.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) { + // Don't attempt to validate third party objects + return nil + } if gvk.Group == extensions.GroupName { if c.c.ExtensionsClient == nil { return errors.New("unable to validate: no experimental client") @@ -764,8 +966,7 @@ func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig { } // PrintObject prints an api object given command line flags to modify the output format -func (f *Factory) PrintObject(cmd *cobra.Command, obj runtime.Object, out io.Writer) error { - mapper, _ := f.Object() +func (f *Factory) PrintObject(cmd *cobra.Command, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error { gvk, err := api.Scheme.ObjectKind(obj) if err != nil { return err @@ -826,8 +1027,8 @@ func (f *Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMappin } // One stop shopping for a Builder -func (f *Factory) NewBuilder() *resource.Builder { - mapper, typer := f.Object() +func (f *Factory) NewBuilder(thirdPartyDiscovery bool) *resource.Builder { + mapper, typer := f.Object(thirdPartyDiscovery) return resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)) } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/factory_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/factory_test.go index be9484fae..459b34d6c 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/factory_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/factory_test.go @@ -32,12 +32,13 @@ import ( "k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api" "k8s.io/kubernetes/pkg/client/unversioned/fake" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/flag" ) func TestNewFactoryDefaultFlagBindings(t *testing.T) { @@ -197,7 +198,7 @@ func TestCanBeExposed(t *testing.T) { func TestFlagUnderscoreRenaming(t *testing.T) { factory := NewFactory(nil) - factory.flags.SetNormalizeFunc(util.WordSepNormalizeFunc) + factory.flags.SetNormalizeFunc(flag.WordSepNormalizeFunc) factory.flags.Bool("valid_flag", false, "bool value") // In case of failure of this test check this PR: spf13/pflag#23 @@ -215,6 +216,63 @@ func loadSchemaForTest() (validation.Schema, error) { return validation.NewSwaggerSchemaFromBytes(data) } +func TestRefetchSchemaWhenValidationFails(t *testing.T) { + schema, err := loadSchemaForTest() + if err != nil { + t.Errorf("Error loading schema: %v", err) + t.FailNow() + } + output, err := json.Marshal(schema) + if err != nil { + t.Errorf("Error serializing schema: %v", err) + t.FailNow() + } + requests := map[string]int{} + + c := &fake.RESTClient{ + Codec: testapi.Default.Codec(), + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case strings.HasPrefix(p, "/swaggerapi") && m == "GET": + requests[p] = requests[p] + 1 + return &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewBuffer(output))}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + dir := os.TempDir() + "/schemaCache" + os.RemoveAll(dir) + + fullDir, err := substituteUserHome(dir) + if err != nil { + t.Errorf("Error getting fullDir: %v", err) + t.FailNow() + } + cacheFile := path.Join(fullDir, "foo", "bar", schemaFileName) + err = writeSchemaFile(output, fullDir, cacheFile, "foo", "bar") + if err != nil { + t.Errorf("Error building old cache schema: %v", err) + t.FailNow() + } + + obj := &extensions.Deployment{} + data, err := runtime.Encode(testapi.Extensions.Codec(), obj) + if err != nil { + t.Errorf("unexpected error: %v", err) + t.FailNow() + } + + // Re-get request, should use HTTP and write + if getSchemaAndValidate(c, data, "foo", "bar", dir); err != nil { + t.Errorf("unexpected error validating: %v", err) + } + if requests["/swaggerapi/foo/bar"] != 1 { + t.Errorf("expected 1 schema request, saw: %d", requests["/swaggerapi/foo/bar"]) + } +} + func TestValidateCachesSchema(t *testing.T) { schema, err := loadSchemaForTest() if err != nil { @@ -266,7 +324,7 @@ func TestValidateCachesSchema(t *testing.T) { if getSchemaAndValidate(c, data, "foo", "bar", dir); err != nil { t.Errorf("unexpected error validating: %v", err) } - if requests["/swaggerapi/foo/bar"] != 1 { + if requests["/swaggerapi/foo/bar"] != 2 { t.Errorf("expected 1 schema request, saw: %d", requests["/swaggerapi/foo/bar"]) } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/helpers.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/helpers.go index 6b2ef69dc..bda52ad50 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/helpers.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/helpers.go @@ -28,10 +28,11 @@ import ( "strings" "time" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/client/typed/discovery" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -171,10 +172,15 @@ func StandardErrorMessage(err error) (string, bool) { if debugErr, ok := err.(debugError); ok { glog.V(4).Infof(debugErr.DebugError()) } - _, isStatus := err.(errors.APIStatus) + status, isStatus := err.(errors.APIStatus) switch { case isStatus: - return fmt.Sprintf("Error from server: %s", err.Error()), true + switch s := status.Status(); { + case s.Reason == "Unauthorized": + return fmt.Sprintf("error: You must be logged in to the server (%s)", s.Message), true + default: + return fmt.Sprintf("Error from server: %s", err.Error()), true + } case errors.IsUnexpectedObjectError(err): return fmt.Sprintf("Server returned an unexpected response: %s", err.Error()), true } @@ -324,6 +330,11 @@ func GetFlagDuration(cmd *cobra.Command, flag string) time.Duration { func AddValidateFlags(cmd *cobra.Command) { cmd.Flags().Bool("validate", true, "If true, use a schema to validate the input before sending it") cmd.Flags().String("schema-cache-dir", fmt.Sprintf("~/%s/%s", clientcmd.RecommendedHomeDir, clientcmd.RecommendedSchemaName), fmt.Sprintf("If non-empty, load/store cached API schemas in this directory, default is '$HOME/%s/%s'", clientcmd.RecommendedHomeDir, clientcmd.RecommendedSchemaName)) + cmd.MarkFlagFilename("schema-cache-dir") +} + +func AddRecursiveFlag(cmd *cobra.Command, value *bool) { + cmd.Flags().BoolVarP(value, "recursive", "R", *value, "If true, process directory recursively.") } func AddApplyAnnotationFlags(cmd *cobra.Command) { @@ -350,7 +361,7 @@ func ReadConfigDataFromReader(reader io.Reader, source string) ([]byte, error) { return data, nil } -// ReadConfigData reads the bytes from the specified filesytem or network +// ReadConfigData reads the bytes from the specified filesystem or network // location or from stdin if location == "-". // TODO: replace with resource.Builder func ReadConfigData(location string) ([]byte, error) { @@ -464,14 +475,16 @@ func GetRecordFlag(cmd *cobra.Command) bool { // RecordChangeCause annotate change-cause to input runtime object. func RecordChangeCause(obj runtime.Object, changeCause string) error { - meta, err := api.ObjectMetaFor(obj) + accessor, err := meta.Accessor(obj) if err != nil { return err } - if meta.Annotations == nil { - meta.Annotations = make(map[string]string) + annotations := accessor.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) } - meta.Annotations[kubectl.ChangeCauseAnnotation] = changeCause + annotations[kubectl.ChangeCauseAnnotation] = changeCause + accessor.SetAnnotations(annotations) return nil } @@ -505,3 +518,53 @@ func ContainsChangeCause(info *resource.Info) bool { func ShouldRecord(cmd *cobra.Command, info *resource.Info) bool { return GetRecordFlag(cmd) || ContainsChangeCause(info) } + +func GetThirdPartyGroupVersions(discovery discovery.DiscoveryInterface) ([]unversioned.GroupVersion, []unversioned.GroupVersionKind, error) { + result := []unversioned.GroupVersion{} + gvks := []unversioned.GroupVersionKind{} + + groupList, err := discovery.ServerGroups() + if err != nil { + // On forbidden or not found, just return empty lists. + if errors.IsForbidden(err) || errors.IsNotFound(err) { + return result, gvks, nil + } + + return nil, nil, err + } + + for ix := range groupList.Groups { + group := &groupList.Groups[ix] + for jx := range group.Versions { + gv, err2 := unversioned.ParseGroupVersion(group.Versions[jx].GroupVersion) + if err2 != nil { + return nil, nil, err + } + // Skip GroupVersionKinds that have been statically registered. + if registered.IsRegisteredVersion(gv) { + continue + } + result = append(result, gv) + + resourceList, err := discovery.ServerResourcesForGroupVersion(group.Versions[jx].GroupVersion) + if err != nil { + return nil, nil, err + } + for kx := range resourceList.APIResources { + gvks = append(gvks, gv.WithKind(resourceList.APIResources[kx].Kind)) + } + } + } + return result, gvks, nil +} + +func GetIncludeThirdPartyAPIs(cmd *cobra.Command) bool { + if cmd.Flags().Lookup("include-extended-apis") == nil { + return false + } + return GetFlagBool(cmd, "include-extended-apis") +} + +func AddInclude3rdPartyFlags(cmd *cobra.Command) { + cmd.Flags().Bool("include-extended-apis", true, "If true, include definitions of new APIs via calls to the API server. [default true]") +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/printing.go b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/printing.go index 40d7c07fa..1e1f943d7 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/printing.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/printing.go @@ -31,14 +31,15 @@ import ( // AddPrinterFlags adds printing related flags to a command (e.g. output format, no headers, template path) func AddPrinterFlags(cmd *cobra.Command) { cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md].") - cmd.Flags().String("output-version", "", "Output the formatted object with the given version (default api-version).") + cmd.Flags().String("output-version", "", "Output the formatted object with the given group version (for ex: 'extensions/v1beta1').") cmd.Flags().Bool("no-headers", false, "When using the default output, don't print headers.") cmd.Flags().Bool("show-labels", false, "When printing, show all labels as the last column (default hide labels column)") // template shorthand -t is deprecated to support -t for --tty // TODO: remove template flag shorthand -t cmd.Flags().StringP("template", "t", "", "Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].") + cmd.MarkFlagFilename("template") cmd.Flags().MarkShorthandDeprecated("template", "please use --template instead") - cmd.Flags().String("sort-by", "", "If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. 'ObjectMeta.Name'). The field in the API resource specified by this JSONPath expression must be an integer or a string.") + cmd.Flags().String("sort-by", "", "If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string.") cmd.Flags().BoolP("show-all", "a", false, "When printing, show all resources (default hide terminated pods.)") } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/configmap.go b/vendor/k8s.io/kubernetes/pkg/kubectl/configmap.go new file mode 100644 index 000000000..04ed4aa6a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/configmap.go @@ -0,0 +1,212 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 kubectl + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "strings" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/runtime" +) + +// ConfigMapGeneratorV1 supports stable generation of a configMap. +type ConfigMapGeneratorV1 struct { + // Name of configMap (required) + Name string + // Type of configMap (optional) + Type string + // FileSources to derive the configMap from (optional) + FileSources []string + // LiteralSources to derive the configMap from (optional) + LiteralSources []string +} + +// Ensure it supports the generator pattern that uses parameter injection. +var _ Generator = &ConfigMapGeneratorV1{} + +// Ensure it supports the generator pattern that uses parameters specified during construction. +var _ StructuredGenerator = &ConfigMapGeneratorV1{} + +// Generate returns a configMap using the specified parameters. +func (s ConfigMapGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) { + err := ValidateParams(s.ParamNames(), genericParams) + if err != nil { + return nil, err + } + delegate := &ConfigMapGeneratorV1{} + fromFileStrings, found := genericParams["from-file"] + if found { + fromFileArray, isArray := fromFileStrings.([]string) + if !isArray { + return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings) + } + delegate.FileSources = fromFileArray + delete(genericParams, "from-file") + } + fromLiteralStrings, found := genericParams["from-literal"] + if found { + fromLiteralArray, isArray := fromLiteralStrings.([]string) + if !isArray { + return nil, fmt.Errorf("expected []string, found :%v", fromFileStrings) + } + delegate.LiteralSources = fromLiteralArray + delete(genericParams, "from-literal") + } + params := map[string]string{} + for key, value := range genericParams { + strVal, isString := value.(string) + if !isString { + return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key) + } + params[key] = strVal + } + delegate.Name = params["name"] + delegate.Type = params["type"] + return delegate.StructuredGenerate() +} + +// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern. +func (s ConfigMapGeneratorV1) ParamNames() []GeneratorParam { + return []GeneratorParam{ + {"name", true}, + {"type", false}, + {"from-file", false}, + {"from-literal", false}, + {"force", false}, + } +} + +// StructuredGenerate outputs a configMap object using the configured fields. +func (s ConfigMapGeneratorV1) StructuredGenerate() (runtime.Object, error) { + if err := s.validate(); err != nil { + return nil, err + } + configMap := &api.ConfigMap{} + configMap.Name = s.Name + configMap.Data = map[string]string{} + if len(s.FileSources) > 0 { + if err := handleConfigMapFromFileSources(configMap, s.FileSources); err != nil { + return nil, err + } + } + if len(s.LiteralSources) > 0 { + if err := handleConfigMapFromLiteralSources(configMap, s.LiteralSources); err != nil { + return nil, err + } + } + return configMap, nil +} + +// validate validates required fields are set to support structured generation. +func (s ConfigMapGeneratorV1) validate() error { + if len(s.Name) == 0 { + return fmt.Errorf("name must be specified") + } + return nil +} + +// handleConfigMapFromLiteralSources adds the specified literal source +// information into the provided configMap. +func handleConfigMapFromLiteralSources(configMap *api.ConfigMap, literalSources []string) error { + for _, literalSource := range literalSources { + keyName, value, err := parseLiteralSource(literalSource) + if err != nil { + return err + } + err = addKeyFromLiteralToConfigMap(configMap, keyName, value) + if err != nil { + return err + } + } + return nil +} + +// handleConfigMapFromFileSources adds the specified file source information +// into the provided configMap +func handleConfigMapFromFileSources(configMap *api.ConfigMap, fileSources []string) error { + for _, fileSource := range fileSources { + keyName, filePath, err := parseFileSource(fileSource) + if err != nil { + return err + } + info, err := os.Stat(filePath) + if err != nil { + switch err := err.(type) { + case *os.PathError: + return fmt.Errorf("error reading %s: %v", filePath, err.Err) + default: + return fmt.Errorf("error reading %s: %v", filePath, err) + } + } + if info.IsDir() { + if strings.Contains(fileSource, "=") { + return fmt.Errorf("cannot give a key name for a directory path.") + } + fileList, err := ioutil.ReadDir(filePath) + if err != nil { + return fmt.Errorf("error listing files in %s: %v", filePath, err) + } + for _, item := range fileList { + itemPath := path.Join(filePath, item.Name()) + if item.Mode().IsRegular() { + keyName = item.Name() + err = addKeyFromFileToConfigMap(configMap, keyName, itemPath) + if err != nil { + return err + } + } + } + } else { + err = addKeyFromFileToConfigMap(configMap, keyName, filePath) + if err != nil { + return err + } + } + } + + return nil +} + +// addKeyFromFileToConfigMap adds a key with the given name to a ConfigMap, populating +// the value with the content of the given file path, or returns an error. +func addKeyFromFileToConfigMap(configMap *api.ConfigMap, keyName, filePath string) error { + data, err := ioutil.ReadFile(filePath) + if err != nil { + return err + } + return addKeyFromLiteralToConfigMap(configMap, keyName, string(data)) +} + +// addKeyFromLiteralToConfigMap adds the given key and data to the given config map, +// returning an error if the key is not valid or if the key already exists. +func addKeyFromLiteralToConfigMap(configMap *api.ConfigMap, keyName, data string) error { + // Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys + // to be consistent; validation.IsSecretKey is used here intentionally. + if !validation.IsSecretKey(keyName) { + return fmt.Errorf("%v is not a valid key name for a configMap", keyName) + } + if _, entryExists := configMap.Data[keyName]; entryExists { + return fmt.Errorf("cannot add key %s, another key by that name already exists: %v.", keyName, configMap.Data) + } + configMap.Data[keyName] = data + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/configmap_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/configmap_test.go new file mode 100644 index 000000000..2fc936c9e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/configmap_test.go @@ -0,0 +1,108 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 kubectl + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" +) + +func TestConfigMapGenerate(t *testing.T) { + tests := []struct { + params map[string]interface{} + expected *api.ConfigMap + expectErr bool + }{ + { + params: map[string]interface{}{ + "name": "foo", + }, + expected: &api.ConfigMap{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + }, + Data: map[string]string{}, + }, + expectErr: false, + }, + { + params: map[string]interface{}{ + "name": "foo", + "type": "my-type", + }, + expected: &api.ConfigMap{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + }, + Data: map[string]string{}, + }, + expectErr: false, + }, + { + params: map[string]interface{}{ + "name": "foo", + "from-literal": []string{"key1=value1", "key2=value2"}, + }, + expected: &api.ConfigMap{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + }, + Data: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + }, + expectErr: false, + }, + { + params: map[string]interface{}{ + "name": "foo", + "from-literal": []string{"key1value1"}, + }, + expectErr: true, + }, + { + params: map[string]interface{}{ + "name": "foo", + "from-file": []string{"key1=/file=2"}, + }, + expectErr: true, + }, + { + params: map[string]interface{}{ + "name": "foo", + "from-file": []string{"key1==value"}, + }, + expectErr: true, + }, + } + generator := ConfigMapGeneratorV1{} + for _, test := range tests { + obj, err := generator.Generate(test.params) + if !test.expectErr && err != nil { + t.Errorf("unexpected error: %v", err) + } + if test.expectErr && err != nil { + continue + } + if !reflect.DeepEqual(obj.(*api.ConfigMap), test.expected) { + t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*api.ConfigMap)) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/custom_column_printer.go b/vendor/k8s.io/kubernetes/pkg/kubectl/custom_column_printer.go index fb29a0a7e..255ad1de8 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/custom_column_printer.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/custom_column_printer.go @@ -191,10 +191,10 @@ func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jso columns := make([]string, len(parsers)) switch u := obj.(type) { case *runtime.Unknown: - if len(u.RawJSON) > 0 { + if len(u.Raw) > 0 { var err error - if obj, err = runtime.Decode(s.Decoder, u.RawJSON); err != nil { - return fmt.Errorf("can't decode object for printing: %v (%s)", err, u.RawJSON) + if obj, err = runtime.Decode(s.Decoder, u.Raw); err != nil { + return fmt.Errorf("can't decode object for printing: %v (%s)", err, u.Raw) } } } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/describe.go b/vendor/k8s.io/kubernetes/pkg/kubectl/describe.go index 72a60e72e..0dfeb8156 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/describe.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/describe.go @@ -21,6 +21,8 @@ import ( "encoding/json" "fmt" "io" + "net" + "net/url" "reflect" "sort" "strings" @@ -31,9 +33,12 @@ import ( "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/autoscaling" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" client "k8s.io/kubernetes/pkg/client/unversioned" + adapter "k8s.io/kubernetes/pkg/client/unversioned/adapters/internalclientset" "k8s.io/kubernetes/pkg/fieldpath" "k8s.io/kubernetes/pkg/fields" qosutil "k8s.io/kubernetes/pkg/kubelet/qos/util" @@ -45,7 +50,7 @@ import ( ) // Describer generates output for the named resource or an error -// if the output could not be generated. Implementors typically +// if the output could not be generated. Implementers typically // abstract the retrieval of the named object from a remote server. type Describer interface { Describe(namespace, name string) (output string, err error) @@ -53,7 +58,7 @@ type Describer interface { // ObjectDescriber is an interface for displaying arbitrary objects with extra // information. Use when an object is in hand (on disk, or already retrieved). -// Implementors may ignore the additional information passed on extra, or use it +// Implementers may ignore the additional information passed on extra, or use it // by default. ObjectDescribers may return ErrNoDescriber if no suitable describer // is found. type ObjectDescriber interface { @@ -87,12 +92,14 @@ func describerMap(c *client.Client) map[unversioned.GroupKind]Describer { api.Kind("Endpoints"): &EndpointsDescriber{c}, api.Kind("ConfigMap"): &ConfigMapDescriber{c}, - extensions.Kind("ReplicaSet"): &ReplicaSetDescriber{c}, - extensions.Kind("HorizontalPodAutoscaler"): &HorizontalPodAutoscalerDescriber{c}, - extensions.Kind("DaemonSet"): &DaemonSetDescriber{c}, - extensions.Kind("Deployment"): &DeploymentDescriber{clientset.FromUnversionedClient(c)}, - extensions.Kind("Job"): &JobDescriber{c}, - extensions.Kind("Ingress"): &IngressDescriber{c}, + extensions.Kind("ReplicaSet"): &ReplicaSetDescriber{c}, + extensions.Kind("HorizontalPodAutoscaler"): &HorizontalPodAutoscalerDescriber{c}, + autoscaling.Kind("HorizontalPodAutoscaler"): &HorizontalPodAutoscalerDescriber{c}, + extensions.Kind("DaemonSet"): &DaemonSetDescriber{c}, + extensions.Kind("Deployment"): &DeploymentDescriber{adapter.FromUnversionedClient(c)}, + extensions.Kind("Job"): &JobDescriber{c}, + batch.Kind("Job"): &JobDescriber{c}, + extensions.Kind("Ingress"): &IngressDescriber{c}, } return m @@ -256,36 +263,41 @@ func DescribeResourceQuotas(quotas *api.ResourceQuotaList, w io.Writer) { fmt.Fprint(w, "No resource quota.\n") return } - resources := []api.ResourceName{} - hard := map[api.ResourceName]resource.Quantity{} - used := map[api.ResourceName]resource.Quantity{} + sort.Sort(SortableResourceQuotas(quotas.Items)) + + fmt.Fprint(w, "Resource Quotas") for _, q := range quotas.Items { + fmt.Fprintf(w, "\n Name:\t%s\n", q.Name) + if len(q.Spec.Scopes) > 0 { + scopes := []string{} + for _, scope := range q.Spec.Scopes { + scopes = append(scopes, string(scope)) + } + sort.Strings(scopes) + fmt.Fprintf(w, " Scopes:\t%s\n", strings.Join(scopes, ", ")) + for _, scope := range scopes { + helpText := helpTextForResourceQuotaScope(api.ResourceQuotaScope(scope)) + if len(helpText) > 0 { + fmt.Fprintf(w, " * %s\n", helpText) + } + } + } + + fmt.Fprintf(w, " Resource\tUsed\tHard\n") + fmt.Fprint(w, " --------\t---\t---\n") + + resources := []api.ResourceName{} for resource := range q.Status.Hard { resources = append(resources, resource) + } + sort.Sort(SortableResourceNames(resources)) + + for _, resource := range resources { hardQuantity := q.Status.Hard[resource] usedQuantity := q.Status.Used[resource] - - // if for some reason there are multiple quota documents, we take least permissive - prevQuantity, ok := hard[resource] - if ok { - if hardQuantity.Value() < prevQuantity.Value() { - hard[resource] = hardQuantity - } - } else { - hard[resource] = hardQuantity - } - used[resource] = usedQuantity + fmt.Fprintf(w, " %s\t%s\t%s\n", string(resource), usedQuantity.String(), hardQuantity.String()) } } - - sort.Sort(SortableResourceNames(resources)) - fmt.Fprint(w, "Resource Quotas\n Resource\tUsed\tHard\n") - fmt.Fprint(w, " ---\t---\t---\n") - for _, resource := range resources { - hardQuantity := hard[resource] - usedQuantity := used[resource] - fmt.Fprintf(w, " %s\t%s\t%s\n", string(resource), usedQuantity.String(), hardQuantity.String()) - } } // LimitRangeDescriber generates information about a limit range @@ -391,10 +403,38 @@ func (d *ResourceQuotaDescriber) Describe(namespace, name string) (string, error return describeQuota(resourceQuota) } +func helpTextForResourceQuotaScope(scope api.ResourceQuotaScope) string { + switch scope { + case api.ResourceQuotaScopeTerminating: + return "Matches all pods that have an active deadline." + case api.ResourceQuotaScopeNotTerminating: + return "Matches all pods that do not have an active deadline." + case api.ResourceQuotaScopeBestEffort: + return "Matches all pods that have best effort quality of service." + case api.ResourceQuotaScopeNotBestEffort: + return "Matches all pods that do not have best effort quality of service." + default: + return "" + } +} func describeQuota(resourceQuota *api.ResourceQuota) (string, error) { return tabbedString(func(out io.Writer) error { fmt.Fprintf(out, "Name:\t%s\n", resourceQuota.Name) fmt.Fprintf(out, "Namespace:\t%s\n", resourceQuota.Namespace) + if len(resourceQuota.Spec.Scopes) > 0 { + scopes := []string{} + for _, scope := range resourceQuota.Spec.Scopes { + scopes = append(scopes, string(scope)) + } + sort.Strings(scopes) + fmt.Fprintf(out, "Scopes:\t%s\n", strings.Join(scopes, ", ")) + for _, scope := range scopes { + helpText := helpTextForResourceQuotaScope(api.ResourceQuotaScope(scope)) + if len(helpText) > 0 { + fmt.Fprintf(out, " * %s\n", helpText) + } + } + } fmt.Fprintf(out, "Resource\tUsed\tHard\n") fmt.Fprintf(out, "--------\t----\t----\n") @@ -453,7 +493,6 @@ func describePod(pod *api.Pod, events *api.EventList) (string, error) { return tabbedString(func(out io.Writer) error { fmt.Fprintf(out, "Name:\t%s\n", pod.Name) fmt.Fprintf(out, "Namespace:\t%s\n", pod.Namespace) - fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(&pod.Spec)) fmt.Fprintf(out, "Node:\t%s\n", pod.Spec.NodeName+"/"+pod.Status.HostIP) if pod.Status.StartTime != nil { fmt.Fprintf(out, "Start Time:\t%s\n", pod.Status.StartTime.Time.Format(time.RFC1123Z)) @@ -465,12 +504,15 @@ func describePod(pod *api.Pod, events *api.EventList) (string, error) { } else { fmt.Fprintf(out, "Status:\t%s\n", string(pod.Status.Phase)) } - fmt.Fprintf(out, "Reason:\t%s\n", pod.Status.Reason) - fmt.Fprintf(out, "Message:\t%s\n", pod.Status.Message) + if len(pod.Status.Reason) > 0 { + fmt.Fprintf(out, "Reason:\t%s\n", pod.Status.Reason) + } + if len(pod.Status.Message) > 0 { + fmt.Fprintf(out, "Message:\t%s\n", pod.Status.Message) + } fmt.Fprintf(out, "IP:\t%s\n", pod.Status.PodIP) fmt.Fprintf(out, "Controllers:\t%s\n", printControllers(pod.Annotations)) - fmt.Fprintf(out, "Containers:\n") - describeContainers(pod, out) + describeContainers(pod.Spec.Containers, pod.Status.ContainerStatuses, EnvValueRetriever(pod), out, "") if len(pod.Status.Conditions) > 0 { fmt.Fprint(out, "Conditions:\n Type\tStatus\n") for _, c := range pod.Status.Conditions { @@ -479,7 +521,7 @@ func describePod(pod *api.Pod, events *api.EventList) (string, error) { c.Status) } } - describeVolumes(pod.Spec.Volumes, out) + describeVolumes(pod.Spec.Volumes, out, "") if events != nil { DescribeEvents(events, out) } @@ -499,14 +541,19 @@ func printControllers(annotation map[string]string) string { return "" } -func describeVolumes(volumes []api.Volume, out io.Writer) { +// TODO: Do a better job at indenting, maybe by using a prefix writer +func describeVolumes(volumes []api.Volume, out io.Writer, space string) { if volumes == nil || len(volumes) == 0 { - fmt.Fprint(out, "No volumes.\n") + fmt.Fprintf(out, "%sNo volumes.\n", space) return } - fmt.Fprint(out, "Volumes:\n") + fmt.Fprintf(out, "%sVolumes:\n", space) for _, volume := range volumes { - fmt.Fprintf(out, " %v:\n", volume.Name) + nameIndent := "" + if len(space) > 0 { + nameIndent = " " + } + fmt.Fprintf(out, " %s%v:\n", nameIndent, volume.Name) switch { case volume.VolumeSource.HostPath != nil: printHostPathVolumeSource(volume.VolumeSource.HostPath, out) @@ -520,6 +567,8 @@ func describeVolumes(volumes []api.Volume, out io.Writer) { printGitRepoVolumeSource(volume.VolumeSource.GitRepo, out) case volume.VolumeSource.Secret != nil: printSecretVolumeSource(volume.VolumeSource.Secret, out) + case volume.VolumeSource.ConfigMap != nil: + printConfigMapVolumeSource(volume.VolumeSource.ConfigMap, out) case volume.VolumeSource.NFS != nil: printNFSVolumeSource(volume.VolumeSource.NFS, out) case volume.VolumeSource.ISCSI != nil: @@ -530,8 +579,10 @@ func describeVolumes(volumes []api.Volume, out io.Writer) { printPersistentVolumeClaimVolumeSource(volume.VolumeSource.PersistentVolumeClaim, out) case volume.VolumeSource.RBD != nil: printRBDVolumeSource(volume.VolumeSource.RBD, out) + case volume.VolumeSource.DownwardAPI != nil: + printDownwardAPIVolumeSource(volume.VolumeSource.DownwardAPI, out) default: - fmt.Fprintf(out, " \n") + fmt.Fprintf(out, " \n") } } } @@ -572,10 +623,15 @@ func printGitRepoVolumeSource(git *api.GitRepoVolumeSource, out io.Writer) { } func printSecretVolumeSource(secret *api.SecretVolumeSource, out io.Writer) { - fmt.Fprintf(out, " Type:\tSecret (a secret that should populate this volume)\n"+ + fmt.Fprintf(out, " Type:\tSecret (a volume populated by a Secret)\n"+ " SecretName:\t%v\n", secret.SecretName) } +func printConfigMapVolumeSource(configMap *api.ConfigMapVolumeSource, out io.Writer) { + fmt.Fprintf(out, " Type:\tConfigMap (a volume populated by a ConfigMap)\n"+ + " Name:\t%v\n", configMap.Name) +} + func printNFSVolumeSource(nfs *api.NFSVolumeSource, out io.Writer) { fmt.Fprintf(out, " Type:\tNFS (an NFS mount that lasts the lifetime of a pod)\n"+ " Server:\t%v\n"+ @@ -623,6 +679,13 @@ func printRBDVolumeSource(rbd *api.RBDVolumeSource, out io.Writer) { rbd.CephMonitors, rbd.RBDImage, rbd.FSType, rbd.RBDPool, rbd.RadosUser, rbd.Keyring, rbd.SecretRef, rbd.ReadOnly) } +func printDownwardAPIVolumeSource(d *api.DownwardAPIVolumeSource, out io.Writer) { + fmt.Fprintf(out, " Type:\tDownwardAPI (a volume populated by information about the pod)\n Items:\n") + for _, mapping := range d.Items { + fmt.Fprintf(out, " %v -> %v\n", mapping.FieldRef.FieldPath, mapping.Path) + } +} + type PersistentVolumeDescriber struct { client.Interface } @@ -707,20 +770,33 @@ func (d *PersistentVolumeClaimDescriber) Describe(namespace, name string) (strin }) } -func describeContainers(pod *api.Pod, out io.Writer) { +// TODO: Do a better job at indenting, maybe by using a prefix writer +func describeContainers(containers []api.Container, containerStatuses []api.ContainerStatus, resolverFn EnvVarResolverFunc, out io.Writer, space string) { statuses := map[string]api.ContainerStatus{} - for _, status := range pod.Status.ContainerStatuses { + for _, status := range containerStatuses { statuses[status.Name] = status } - - for _, container := range pod.Spec.Containers { - status := statuses[container.Name] - state := status.State - - fmt.Fprintf(out, " %v:\n", container.Name) - fmt.Fprintf(out, " Container ID:\t%s\n", status.ContainerID) + fmt.Fprintf(out, "%sContainers:\n", space) + for _, container := range containers { + status, ok := statuses[container.Name] + nameIndent := "" + if len(space) > 0 { + nameIndent = " " + } + fmt.Fprintf(out, " %s%v:\n", nameIndent, container.Name) + if ok { + fmt.Fprintf(out, " Container ID:\t%s\n", status.ContainerID) + } fmt.Fprintf(out, " Image:\t%s\n", container.Image) - fmt.Fprintf(out, " Image ID:\t%s\n", status.ImageID) + if ok { + fmt.Fprintf(out, " Image ID:\t%s\n", status.ImageID) + } + portString := describeContainerPorts(container.Ports) + if strings.Contains(portString, ",") { + fmt.Fprintf(out, " Ports:\t%s\n", portString) + } else { + fmt.Fprintf(out, " Port:\t%s\n", portString) + } if len(container.Command) > 0 { fmt.Fprintf(out, " Command:\n") @@ -757,16 +833,34 @@ func describeContainers(pod *api.Pod, out io.Writer) { fmt.Fprintf(out, " %s:\t%s\n", name, quantity.String()) } - describeStatus("State", state, out) - if status.LastTerminationState.Terminated != nil { - describeStatus("Last Termination State", status.LastTerminationState, out) + if ok { + describeStatus("State", status.State, out) + if status.LastTerminationState.Terminated != nil { + describeStatus("Last State", status.LastTerminationState, out) + } + fmt.Fprintf(out, " Ready:\t%v\n", printBool(status.Ready)) + fmt.Fprintf(out, " Restart Count:\t%d\n", status.RestartCount) } - fmt.Fprintf(out, " Ready:\t%v\n", printBool(status.Ready)) - fmt.Fprintf(out, " Restart Count:\t%d\n", status.RestartCount) - fmt.Fprintf(out, " Environment Variables:\n") + + if container.LivenessProbe != nil { + probe := DescribeProbe(container.LivenessProbe) + fmt.Fprintf(out, " Liveness:\t%s\n", probe) + } + if container.ReadinessProbe != nil { + probe := DescribeProbe(container.ReadinessProbe) + fmt.Fprintf(out, " Readiness:\t%s\n", probe) + } + none := "" + if len(container.Env) == 0 { + none = "\t" + } + fmt.Fprintf(out, " Environment Variables:%s\n", none) for _, e := range container.Env { if e.ValueFrom != nil && e.ValueFrom.FieldRef != nil { - valueFrom := envValueFrom(pod, e) + var valueFrom string + if resolverFn != nil { + valueFrom = resolverFn(e) + } fmt.Fprintf(out, " %s:\t%s (%s:%s)\n", e.Name, valueFrom, e.ValueFrom.FieldRef.APIVersion, e.ValueFrom.FieldRef.FieldPath) } else { fmt.Fprintf(out, " %s:\t%s\n", e.Name, e.Value) @@ -775,18 +869,53 @@ func describeContainers(pod *api.Pod, out io.Writer) { } } -func envValueFrom(pod *api.Pod, e api.EnvVar) string { - internalFieldPath, _, err := api.Scheme.ConvertFieldLabel(e.ValueFrom.FieldRef.APIVersion, "Pod", e.ValueFrom.FieldRef.FieldPath, "") - if err != nil { - return "" // pod validation should catch this on create +func describeContainerPorts(cPorts []api.ContainerPort) string { + ports := []string{} + for _, cPort := range cPorts { + ports = append(ports, fmt.Sprintf("%d/%s", cPort.ContainerPort, cPort.Protocol)) } + return strings.Join(ports, ", ") +} - valueFrom, err := fieldpath.ExtractFieldPathAsString(pod, internalFieldPath) - if err != nil { - return "" // pod validation should catch this on create +// DescribeProbe is exported for consumers in other API groups that have probes +func DescribeProbe(probe *api.Probe) string { + attrs := fmt.Sprintf("delay=%ds timeout=%ds period=%ds #success=%d #failure=%d", probe.InitialDelaySeconds, probe.TimeoutSeconds, probe.PeriodSeconds, probe.SuccessThreshold, probe.FailureThreshold) + switch { + case probe.Exec != nil: + return fmt.Sprintf("exec %v %s", probe.Exec.Command, attrs) + case probe.HTTPGet != nil: + url := &url.URL{} + url.Scheme = strings.ToLower(string(probe.HTTPGet.Scheme)) + if len(probe.HTTPGet.Port.String()) > 0 { + url.Host = net.JoinHostPort(probe.HTTPGet.Host, probe.HTTPGet.Port.String()) + } else { + url.Host = probe.HTTPGet.Host + } + url.Path = probe.HTTPGet.Path + return fmt.Sprintf("http-get %s %s", url.String(), attrs) + case probe.TCPSocket != nil: + return fmt.Sprintf("tcp-socket :%s %s", probe.TCPSocket.Port.String(), attrs) } + return fmt.Sprintf("unknown %s", attrs) +} - return valueFrom +type EnvVarResolverFunc func(e api.EnvVar) string + +// EnvValueFrom is exported for use by describers in other packages +func EnvValueRetriever(pod *api.Pod) EnvVarResolverFunc { + return func(e api.EnvVar) string { + internalFieldPath, _, err := api.Scheme.ConvertFieldLabel(e.ValueFrom.FieldRef.APIVersion, "Pod", e.ValueFrom.FieldRef.FieldPath, "") + if err != nil { + return "" // pod validation should catch this on create + } + + valueFrom, err := fieldpath.ExtractFieldPathAsString(pod, internalFieldPath) + if err != nil { + return "" // pod validation should catch this on create + } + + return valueFrom + } } func describeStatus(stateName string, state api.ContainerState, out io.Writer) { @@ -858,14 +987,14 @@ func describeReplicationController(controller *api.ReplicationController, events if controller.Spec.Template != nil { fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(&controller.Spec.Template.Spec)) } else { - fmt.Fprintf(out, "Image(s):\t%s\n", "") + fmt.Fprintf(out, "Image(s):\t%s\n", "") } fmt.Fprintf(out, "Selector:\t%s\n", labels.FormatLabels(controller.Spec.Selector)) fmt.Fprintf(out, "Labels:\t%s\n", labels.FormatLabels(controller.Labels)) fmt.Fprintf(out, "Replicas:\t%d current / %d desired\n", controller.Status.Replicas, controller.Spec.Replicas) fmt.Fprintf(out, "Pods Status:\t%d Running / %d Waiting / %d Succeeded / %d Failed\n", running, waiting, succeeded, failed) if controller.Spec.Template != nil { - describeVolumes(controller.Spec.Template.Spec.Volumes, out) + describeVolumes(controller.Spec.Template.Spec.Volumes, out, "") } if events != nil { DescribeEvents(events, out) @@ -874,18 +1003,20 @@ func describeReplicationController(controller *api.ReplicationController, events }) } -func DescribePodTemplate(template *api.PodTemplateSpec) (string, error) { - return tabbedString(func(out io.Writer) error { - if template == nil { - fmt.Fprintf(out, "") - return nil - } - fmt.Fprintf(out, "Labels:\t%s\n", labels.FormatLabels(template.Labels)) - fmt.Fprintf(out, "Annotations:\t%s\n", labels.FormatLabels(template.Annotations)) - fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(&template.Spec)) - describeVolumes(template.Spec.Volumes, out) - return nil - }) +func DescribePodTemplate(template *api.PodTemplateSpec, out io.Writer) { + if template == nil { + fmt.Fprintf(out, " ") + return + } + fmt.Fprintf(out, " Labels:\t%s\n", labels.FormatLabels(template.Labels)) + if len(template.Annotations) > 0 { + fmt.Fprintf(out, " Annotations:\t%s\n", labels.FormatLabels(template.Annotations)) + } + if len(template.Spec.ServiceAccountName) > 0 { + fmt.Fprintf(out, " Service Account:\t%s\n", template.Spec.ServiceAccountName) + } + describeContainers(template.Spec.Containers, nil, nil, out, " ") + describeVolumes(template.Spec.Volumes, out, " ") } // ReplicaSetDescriber generates information about a ReplicaSet and the pods it has created. @@ -921,18 +1052,12 @@ func describeReplicaSet(rs *extensions.ReplicaSet, events *api.EventList, runnin return tabbedString(func(out io.Writer) error { fmt.Fprintf(out, "Name:\t%s\n", rs.Name) fmt.Fprintf(out, "Namespace:\t%s\n", rs.Namespace) - if rs.Spec.Template != nil { - fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(&rs.Spec.Template.Spec)) - } else { - fmt.Fprintf(out, "Image(s):\t%s\n", "") - } + fmt.Fprintf(out, "Image(s):\t%s\n", makeImageList(&rs.Spec.Template.Spec)) fmt.Fprintf(out, "Selector:\t%s\n", unversioned.FormatLabelSelector(rs.Spec.Selector)) fmt.Fprintf(out, "Labels:\t%s\n", labels.FormatLabels(rs.Labels)) fmt.Fprintf(out, "Replicas:\t%d current / %d desired\n", rs.Status.Replicas, rs.Spec.Replicas) fmt.Fprintf(out, "Pods Status:\t%d Running / %d Waiting / %d Succeeded / %d Failed\n", running, waiting, succeeded, failed) - if rs.Spec.Template != nil { - describeVolumes(rs.Spec.Template.Spec.Volumes, out) - } + describeVolumes(rs.Spec.Template.Spec.Volumes, out, "") if events != nil { DescribeEvents(events, out) } @@ -967,7 +1092,7 @@ func describeJob(job *extensions.Job, events *api.EventList) (string, error) { if job.Spec.Completions != nil { fmt.Fprintf(out, "Completions:\t%d\n", *job.Spec.Completions) } else { - fmt.Fprintf(out, "Completions:\tNot Set\n") + fmt.Fprintf(out, "Completions:\t\n") } if job.Status.StartTime != nil { fmt.Fprintf(out, "Start Time:\t%s\n", job.Status.StartTime.Time.Format(time.RFC1123Z)) @@ -977,7 +1102,7 @@ func describeJob(job *extensions.Job, events *api.EventList) (string, error) { } fmt.Fprintf(out, "Labels:\t%s\n", labels.FormatLabels(job.Labels)) fmt.Fprintf(out, "Pods Statuses:\t%d Running / %d Succeeded / %d Failed\n", job.Status.Active, job.Status.Succeeded, job.Status.Failed) - describeVolumes(job.Spec.Template.Spec.Volumes, out) + describeVolumes(job.Spec.Template.Spec.Volumes, out, "") if events != nil { DescribeEvents(events, out) } @@ -1152,7 +1277,11 @@ func (i *IngressDescriber) describeIngress(ing *extensions.Ingress) (string, err func describeIngressTLS(out io.Writer, ingTLS []extensions.IngressTLS) { fmt.Fprintf(out, "TLS:\n") for _, t := range ingTLS { - fmt.Fprintf(out, " %v terminates %v\n", t.SecretName, strings.Join(t.Hosts, ",")) + if t.SecretName == "" { + fmt.Fprintf(out, " SNI routes %v\n", strings.Join(t.Hosts, ",")) + } else { + fmt.Fprintf(out, " %v terminates %v\n", t.SecretName, strings.Join(t.Hosts, ",")) + } } return } @@ -1226,7 +1355,7 @@ func describeService(service *api.Service, endpoints *api.Endpoints, events *api name := sp.Name if name == "" { - name = "" + name = "" } fmt.Fprintf(out, "Port:\t%s\t%d/%s\n", name, sp.Port, sp.Protocol) if sp.NodePort != 0 { @@ -1297,7 +1426,7 @@ func describeEndpoints(ep *api.Endpoints, events *api.EventList) (string, error) for _, port := range subset.Ports { name := port.Name if len(name) == 0 { - name = "" + name = "" } fmt.Fprintf(out, " %s\t%d\t%s\n", name, port.Port, port.Protocol) } @@ -1327,7 +1456,7 @@ func (d *ServiceAccountDescriber) Describe(namespace, name string) (string, erro tokens := []api.Secret{} - tokenSelector := fields.SelectorFromSet(map[string]string{client.SecretType: string(api.SecretTypeServiceAccountToken)}) + tokenSelector := fields.SelectorFromSet(map[string]string{api.SecretTypeField: string(api.SecretTypeServiceAccountToken)}) options := api.ListOptions{FieldSelector: tokenSelector} secrets, err := d.Secrets(namespace).List(options) if err == nil { @@ -1507,6 +1636,7 @@ func (d *HorizontalPodAutoscalerDescriber) Describe(namespace, name string) (str fmt.Fprintf(out, "Name:\t%s\n", hpa.Name) fmt.Fprintf(out, "Namespace:\t%s\n", hpa.Namespace) fmt.Fprintf(out, "Labels:\t%s\n", labels.FormatLabels(hpa.Labels)) + fmt.Fprintf(out, "Annotations:\t%s\n", labels.FormatLabels(hpa.Annotations)) fmt.Fprintf(out, "CreationTimestamp:\t%s\n", hpa.CreationTimestamp.Time.Format(time.RFC1123Z)) fmt.Fprintf(out, "Reference:\t%s/%s/%s\n", hpa.Spec.ScaleRef.Kind, @@ -1518,7 +1648,7 @@ func (d *HorizontalPodAutoscalerDescriber) Describe(namespace, name string) (str if hpa.Status.CurrentCPUUtilizationPercentage != nil { fmt.Fprintf(out, "%d%%\n", *hpa.Status.CurrentCPUUtilizationPercentage) } else { - fmt.Fprintf(out, "\n") + fmt.Fprintf(out, "\n") } } minReplicas := "" @@ -1538,6 +1668,11 @@ func (d *HorizontalPodAutoscalerDescriber) Describe(namespace, name string) (str fmt.Fprintf(out, "failed to check Replication Controller\n") } } + + events, _ := d.client.Events(namespace).Search(hpa) + if events != nil { + DescribeEvents(events, out) + } return nil }) } @@ -1665,11 +1800,11 @@ func (dd *DeploymentDescriber) Describe(namespace, name string) (string, error) ru := d.Spec.Strategy.RollingUpdate fmt.Fprintf(out, "RollingUpdateStrategy:\t%s max unavailable, %s max surge\n", ru.MaxUnavailable.String(), ru.MaxSurge.String()) } - oldRSs, _, err := deploymentutil.GetOldReplicaSets(*d, dd) + oldRSs, _, err := deploymentutil.GetOldReplicaSets(d, dd) if err == nil { fmt.Fprintf(out, "OldReplicaSets:\t%s\n", printReplicaSetsByLabels(oldRSs)) } - newRS, err := deploymentutil.GetNewReplicaSet(*d, dd) + newRS, err := deploymentutil.GetNewReplicaSet(d, dd) if err == nil { var newRSs []*extensions.ReplicaSet if newRS != nil { diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/describe_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/describe_test.go index 43420fa14..6098865b7 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/describe_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/describe_test.go @@ -28,7 +28,7 @@ import ( "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" - "k8s.io/kubernetes/pkg/client/testing/fake" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/testclient" ) @@ -268,7 +268,7 @@ func TestDescribeContainers(t *testing.T) { ContainerStatuses: []api.ContainerStatus{testCase.status}, }, } - describeContainers(&pod, out) + describeContainers(pod.Spec.Containers, pod.Status.ContainerStatuses, EnvValueRetriever(&pod), out, "") output := out.String() for _, expected := range testCase.expectedElements { if !strings.Contains(output, expected) { diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/history.go b/vendor/k8s.io/kubernetes/pkg/kubectl/history.go index 01995f376..09b8e6b97 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/history.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/history.go @@ -23,6 +23,7 @@ import ( "strconv" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" @@ -67,11 +68,11 @@ func (h *DeploymentHistoryViewer) History(namespace, name string) (HistoryInfo, if err != nil { return historyInfo, fmt.Errorf("failed to retrieve deployment %s: %v", name, err) } - _, allOldRSs, err := deploymentutil.GetOldReplicaSets(*deployment, h.c) + _, allOldRSs, err := deploymentutil.GetOldReplicaSets(deployment, h.c) if err != nil { return historyInfo, fmt.Errorf("failed to retrieve old replica sets from deployment %s: %v", name, err) } - newRS, err := deploymentutil.GetNewReplicaSet(*deployment, h.c) + newRS, err := deploymentutil.GetNewReplicaSet(deployment, h.c) if err != nil { return historyInfo, fmt.Errorf("failed to retrieve new replica set from deployment %s: %v", name, err) } @@ -81,12 +82,14 @@ func (h *DeploymentHistoryViewer) History(namespace, name string) (HistoryInfo, if err != nil { continue } - historyInfo.RevisionToTemplate[v] = rs.Spec.Template + historyInfo.RevisionToTemplate[v] = &rs.Spec.Template changeCause := getChangeCause(rs) if historyInfo.RevisionToTemplate[v].Annotations == nil { historyInfo.RevisionToTemplate[v].Annotations = make(map[string]string) } - historyInfo.RevisionToTemplate[v].Annotations[ChangeCauseAnnotation] = changeCause + if len(changeCause) > 0 { + historyInfo.RevisionToTemplate[v].Annotations[ChangeCauseAnnotation] = changeCause + } } return historyInfo, nil } @@ -126,9 +129,9 @@ func PrintRolloutHistory(historyInfo HistoryInfo, resource, name string) (string // getChangeCause returns the change-cause annotation of the input object func getChangeCause(obj runtime.Object) string { - meta, err := api.ObjectMetaFor(obj) + accessor, err := meta.Accessor(obj) if err != nil { return "" } - return meta.Annotations[ChangeCauseAnnotation] + return accessor.GetAnnotations()[ChangeCauseAnnotation] } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/interfaces.go b/vendor/k8s.io/kubernetes/pkg/kubectl/interfaces.go index 890947b5c..8f1e6f197 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/interfaces.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/interfaces.go @@ -18,7 +18,7 @@ package kubectl import ( "k8s.io/kubernetes/pkg/api" - client "k8s.io/kubernetes/pkg/client/unversioned" + client "k8s.io/kubernetes/pkg/client/restclient" ) // RESTClient is a client helper for dealing with RESTful resources diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/kubectl.go b/vendor/k8s.io/kubernetes/pkg/kubectl/kubectl.go index 2f9c7c4e8..2b6b0007a 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/kubectl.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/kubectl.go @@ -18,15 +18,25 @@ limitations under the License. package kubectl import ( + "errors" + "fmt" + "path" "strings" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/apis/extensions" ) -const kubectlAnnotationPrefix = "kubectl.kubernetes.io/" +const ( + kubectlAnnotationPrefix = "kubectl.kubernetes.io/" + // TODO: auto-generate this + PossibleResourceTypes = `Possible resource types include (case insensitive): pods (po), services (svc), deployments, +replicasets (rs), replicationcontrollers (rc), nodes (no), events (ev), limitranges (limits), +persistentvolumes (pv), persistentvolumeclaims (pvc), resourcequotas (quota), namespaces (ns), +serviceaccounts (sa), ingresses (ing), horizontalpodautoscalers (hpa), daemonsets (ds), configmaps, +componentstatuses (cs), endpoints (ep), and secrets.` +) type NamespaceInfo struct { Namespace string @@ -44,6 +54,28 @@ func makeImageList(spec *api.PodSpec) string { return strings.Join(listOfImages(spec), ",") } +func NewThirdPartyResourceMapper(gvs []unversioned.GroupVersion, gvks []unversioned.GroupVersionKind) (meta.RESTMapper, error) { + mapper := meta.NewDefaultRESTMapper(gvs, func(gv unversioned.GroupVersion) (*meta.VersionInterfaces, error) { + for ix := range gvs { + if gvs[ix].Group == gv.Group && gvs[ix].Version == gv.Version { + return &meta.VersionInterfaces{ + ObjectConvertor: api.Scheme, + MetadataAccessor: meta.NewAccessor(), + }, nil + } + } + groupVersions := []string{} + for ix := range gvs { + groupVersions = append(groupVersions, gvs[ix].String()) + } + return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", gv.String(), strings.Join(groupVersions, ", ")) + }) + for ix := range gvks { + mapper.Add(gvks[ix], meta.RESTScopeNamespace) + } + return mapper, nil +} + // OutputVersionMapper is a RESTMapper that will prefer mappings that // correspond to a preferred output version (if feasible) type OutputVersionMapper struct { @@ -72,51 +104,106 @@ func (m OutputVersionMapper) RESTMapping(gk unversioned.GroupKind, versions ...s } // ShortcutExpander is a RESTMapper that can be used for Kubernetes -// resources. +// resources. It expands the resource first, then invokes the wrapped RESTMapper type ShortcutExpander struct { - meta.RESTMapper + RESTMapper meta.RESTMapper } var _ meta.RESTMapper = &ShortcutExpander{} -// KindFor implements meta.RESTMapper. It expands the resource first, then invokes the wrapped -// mapper. func (e ShortcutExpander) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) { - resource = expandResourceShortcut(resource) - return e.RESTMapper.KindFor(resource) + return e.RESTMapper.KindFor(expandResourceShortcut(resource)) +} + +func (e ShortcutExpander) KindsFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) { + return e.RESTMapper.KindsFor(expandResourceShortcut(resource)) +} + +func (e ShortcutExpander) ResourcesFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) { + return e.RESTMapper.ResourcesFor(expandResourceShortcut(resource)) +} + +func (e ShortcutExpander) ResourceFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) { + return e.RESTMapper.ResourceFor(expandResourceShortcut(resource)) } -// ResourceSingularizer expands the named resource and then singularizes it. func (e ShortcutExpander) ResourceSingularizer(resource string) (string, error) { return e.RESTMapper.ResourceSingularizer(expandResourceShortcut(unversioned.GroupVersionResource{Resource: resource}).Resource) } +func (e ShortcutExpander) RESTMapping(gk unversioned.GroupKind, versions ...string) (*meta.RESTMapping, error) { + return e.RESTMapper.RESTMapping(gk, versions...) +} + +func (e ShortcutExpander) AliasesForResource(resource string) ([]string, bool) { + return e.RESTMapper.AliasesForResource(expandResourceShortcut(unversioned.GroupVersionResource{Resource: resource}).Resource) +} + +// shortForms is the list of short names to their expanded names +var shortForms = map[string]string{ + // Please keep this alphabetized + // If you add an entry here, please also take a look at pkg/kubectl/cmd/cmd.go + // and add an entry to valid_resources when appropriate. + "cs": "componentstatuses", + "ds": "daemonsets", + "ep": "endpoints", + "ev": "events", + "hpa": "horizontalpodautoscalers", + "ing": "ingresses", + "limits": "limitranges", + "no": "nodes", + "ns": "namespaces", + "po": "pods", + "psp": "podSecurityPolicies", + "pvc": "persistentvolumeclaims", + "pv": "persistentvolumes", + "quota": "resourcequotas", + "rc": "replicationcontrollers", + "rs": "replicasets", + "sa": "serviceaccounts", + "svc": "services", +} + // expandResourceShortcut will return the expanded version of resource // (something that a pkg/api/meta.RESTMapper can understand), if it is // indeed a shortcut. Otherwise, will return resource unmodified. func expandResourceShortcut(resource unversioned.GroupVersionResource) unversioned.GroupVersionResource { - shortForms := map[string]unversioned.GroupVersionResource{ - // Please keep this alphabetized - "cs": api.SchemeGroupVersion.WithResource("componentstatuses"), - "ds": extensions.SchemeGroupVersion.WithResource("daemonsets"), - "ep": api.SchemeGroupVersion.WithResource("endpoints"), - "ev": api.SchemeGroupVersion.WithResource("events"), - "hpa": extensions.SchemeGroupVersion.WithResource("horizontalpodautoscalers"), - "ing": extensions.SchemeGroupVersion.WithResource("ingresses"), - "limits": api.SchemeGroupVersion.WithResource("limitranges"), - "no": api.SchemeGroupVersion.WithResource("nodes"), - "ns": api.SchemeGroupVersion.WithResource("namespaces"), - "po": api.SchemeGroupVersion.WithResource("pods"), - "psp": api.SchemeGroupVersion.WithResource("podSecurityPolicies"), - "pvc": api.SchemeGroupVersion.WithResource("persistentvolumeclaims"), - "pv": api.SchemeGroupVersion.WithResource("persistentvolumes"), - "quota": api.SchemeGroupVersion.WithResource("resourcequotas"), - "rc": api.SchemeGroupVersion.WithResource("replicationcontrollers"), - "rs": extensions.SchemeGroupVersion.WithResource("replicasets"), - "svc": api.SchemeGroupVersion.WithResource("services"), - } if expanded, ok := shortForms[resource.Resource]; ok { - return expanded + // don't change the group or version that's already been specified + resource.Resource = expanded } return resource } + +// parseFileSource parses the source given. Acceptable formats include: +// +// 1. source-path: the basename will become the key name +// 2. source-name=source-path: the source-name will become the key name and source-path is the path to the key file +// +// Key names cannot include '='. +func parseFileSource(source string) (keyName, filePath string, err error) { + numSeparators := strings.Count(source, "=") + switch { + case numSeparators == 0: + return path.Base(source), source, nil + case numSeparators == 1 && strings.HasPrefix(source, "="): + return "", "", fmt.Errorf("key name for file path %v missing.", strings.TrimPrefix(source, "=")) + case numSeparators == 1 && strings.HasSuffix(source, "="): + return "", "", fmt.Errorf("file path for key name %v missing.", strings.TrimSuffix(source, "=")) + case numSeparators > 1: + return "", "", errors.New("Key names or file paths cannot contain '='.") + default: + components := strings.Split(source, "=") + return components[0], components[1], nil + } +} + +// parseLiteralSource parses the source key=val pair +func parseLiteralSource(source string) (keyName, value string, err error) { + items := strings.Split(source, "=") + if len(items) != 2 { + return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source) + } + + return items[0], items[1], nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/kubectl_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/kubectl_test.go new file mode 100644 index 000000000..6381b4920 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/kubectl_test.go @@ -0,0 +1,194 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 kubectl + +import ( + "testing" +) + +func TestParseFileSource(t *testing.T) { + cases := []struct { + name string + input string + key string + filepath string + err bool + }{ + { + name: "success 1", + input: "boo=zoo", + key: "boo", + filepath: "zoo", + err: false, + }, + { + name: "success 2", + input: "boo=/path/to/zoo", + key: "boo", + filepath: "/path/to/zoo", + err: false, + }, + { + name: "success 3", + input: "boo-2=/1/2/3/4/5/zab.txt", + key: "boo-2", + filepath: "/1/2/3/4/5/zab.txt", + err: false, + }, + { + name: "success 4", + input: "boo-=this/seems/weird.txt", + key: "boo-", + filepath: "this/seems/weird.txt", + err: false, + }, + { + name: "success 5", + input: "-key=some/path", + key: "-key", + filepath: "some/path", + err: false, + }, + { + name: "invalid 1", + input: "key==some/path", + err: true, + }, + { + name: "invalid 2", + input: "=key=some/path", + err: true, + }, + { + name: "invalid 3", + input: "==key=/some/other/path", + err: true, + }, + { + name: "invalid 4", + input: "=key", + err: true, + }, + { + name: "invalid 5", + input: "key=", + err: true, + }, + } + + for _, tc := range cases { + key, filepath, err := parseFileSource(tc.input) + if err != nil { + if tc.err { + continue + } + + t.Errorf("%v: unexpected error: %v", tc.name, err) + continue + } + + if tc.err { + t.Errorf("%v: unexpected success", tc.name) + continue + } + + if e, a := tc.key, key; e != a { + t.Errorf("%v: expected key %v; got %v", tc.name, e, a) + continue + } + + if e, a := tc.filepath, filepath; e != a { + t.Errorf("%v: expected filepath %v; got %v", tc.name, e, a) + } + } +} + +func TestParseLiteralSource(t *testing.T) { + cases := []struct { + name string + input string + key string + value string + err bool + }{ + { + name: "success 1", + input: "key=value", + key: "key", + value: "value", + err: false, + }, + { + name: "success 2", + input: "key=value/with/slashes", + key: "key", + value: "value/with/slashes", + err: false, + }, + { + name: "err 1", + input: "key==value", + err: true, + }, + { + name: "err 2", + input: "key=value=", + err: true, + }, + { + name: "err 3", + input: "key2=value==", + err: true, + }, + { + name: "err 4", + input: "==key", + err: true, + }, + { + name: "err 5", + input: "=key=", + err: true, + }, + } + + for _, tc := range cases { + key, value, err := parseLiteralSource(tc.input) + if err != nil { + if tc.err { + continue + } + + t.Errorf("%v: unexpected error: %v", tc.name, err) + continue + } + + if tc.err { + t.Errorf("%v: unexpected success", tc.name) + continue + } + + if e, a := tc.key, key; e != a { + t.Errorf("%v: expected key %v; got %v", tc.name, e, a) + continue + } + + if e, a := tc.value, value; e != a { + t.Errorf("%v: expected value %v; got %v", tc.name, e, a) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/proxy_server.go b/vendor/k8s.io/kubernetes/pkg/kubectl/proxy_server.go index bfbb9b6a0..082b542fc 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/proxy_server.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/proxy_server.go @@ -28,7 +28,7 @@ import ( "time" "github.com/golang/glog" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/util" ) @@ -146,7 +146,7 @@ type ProxyServer struct { // NewProxyServer creates and installs a new ProxyServer. // It automatically registers the created ProxyServer to http.DefaultServeMux. // 'filter', if non-nil, protects requests to the api only. -func NewProxyServer(filebase string, apiProxyPrefix string, staticPrefix string, filter *FilterServer, cfg *client.Config) (*ProxyServer, error) { +func NewProxyServer(filebase string, apiProxyPrefix string, staticPrefix string, filter *FilterServer, cfg *restclient.Config) (*ProxyServer, error) { host := cfg.Host if !strings.HasSuffix(host, "/") { host = host + "/" @@ -156,7 +156,7 @@ func NewProxyServer(filebase string, apiProxyPrefix string, staticPrefix string, return nil, err } proxy := newProxy(target) - if proxy.Transport, err = client.TransportFor(cfg); err != nil { + if proxy.Transport, err = restclient.TransportFor(cfg); err != nil { return nil, err } proxyServer := http.Handler(proxy) diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/proxy_server_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/proxy_server_test.go index 494b926cc..2d403149f 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/proxy_server_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/proxy_server_test.go @@ -26,7 +26,7 @@ import ( "strings" "testing" - client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/restclient" ) func TestAccept(t *testing.T) { @@ -293,7 +293,7 @@ func TestPathHandling(t *testing.T) { {"/custom/", "/custom/api/v1/pods/", "/api/v1/pods/"}, } - cc := &client.Config{ + cc := &restclient.Config{ Host: ts.URL, } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder.go b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder.go index 61b0f1131..0a40e7b21 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder.go @@ -98,7 +98,7 @@ func (b *Builder) Schema(schema validation.Schema) *Builder { // will cause an error. // If ContinueOnError() is set prior to this method, objects on the path that are not // recognized will be ignored (but logged at V(2)). -func (b *Builder) FilenameParam(enforceNamespace bool, paths ...string) *Builder { +func (b *Builder) FilenameParam(enforceNamespace, recursive bool, paths ...string) *Builder { for _, s := range paths { switch { case s == "-": @@ -111,7 +111,7 @@ func (b *Builder) FilenameParam(enforceNamespace bool, paths ...string) *Builder } b.URL(url) default: - b.Path(s) + b.Path(recursive, s) } } @@ -126,9 +126,8 @@ func (b *Builder) FilenameParam(enforceNamespace bool, paths ...string) *Builder func (b *Builder) URL(urls ...*url.URL) *Builder { for _, u := range urls { b.paths = append(b.paths, &URLVisitor{ - Mapper: b.mapper, - URL: u, - Schema: b.schema, + URL: u, + StreamVisitor: NewStreamVisitor(nil, b.mapper, u.String(), b.schema), }) } return b @@ -158,7 +157,7 @@ func (b *Builder) Stream(r io.Reader, name string) *Builder { // FileVisitor is streaming the content to a StreamVisitor. If ContinueOnError() is set // prior to this method being called, objects on the path that are unrecognized will be // ignored (but logged at V(2)). -func (b *Builder) Path(paths ...string) *Builder { +func (b *Builder) Path(recursive bool, paths ...string) *Builder { for _, p := range paths { _, err := os.Stat(p) if os.IsNotExist(err) { @@ -170,7 +169,7 @@ func (b *Builder) Path(paths ...string) *Builder { continue } - visitors, err := ExpandPathsToFileVisitors(b.mapper, p, false, FileExtensions, b.schema) + visitors, err := ExpandPathsToFileVisitors(b.mapper, p, recursive, FileExtensions, b.schema) if err != nil { b.errs = append(b.errs, fmt.Errorf("error reading %q: %v", p, err)) } @@ -434,20 +433,36 @@ func (b *Builder) SingleResourceType() *Builder { return b } +// mappingFor returns the RESTMapping for the Kind referenced by the resource. +// prefers a fully specified GroupVersionResource match. If we don't have one match on GroupResource +func (b *Builder) mappingFor(resourceArg string) (*meta.RESTMapping, error) { + fullySpecifiedGVR, groupResource := unversioned.ParseResourceArg(resourceArg) + gvk := unversioned.GroupVersionKind{} + if fullySpecifiedGVR != nil { + gvk, _ = b.mapper.KindFor(*fullySpecifiedGVR) + } + if gvk.IsEmpty() { + var err error + gvk, err = b.mapper.KindFor(groupResource.WithVersion("")) + if err != nil { + return nil, err + } + } + + return b.mapper.RESTMapping(gvk.GroupKind(), gvk.Version) +} + func (b *Builder) resourceMappings() ([]*meta.RESTMapping, error) { if len(b.resources) > 1 && b.singleResourceType { return nil, fmt.Errorf("you may only specify a single resource type") } mappings := []*meta.RESTMapping{} for _, r := range b.resources { - gvk, err := b.mapper.KindFor(unversioned.GroupVersionResource{Resource: r}) - if err != nil { - return nil, err - } - mapping, err := b.mapper.RESTMapping(gvk.GroupKind(), gvk.Version) + mapping, err := b.mappingFor(r) if err != nil { return nil, err } + mappings = append(mappings, mapping) } return mappings, nil @@ -460,14 +475,11 @@ func (b *Builder) resourceTupleMappings() (map[string]*meta.RESTMapping, error) if _, ok := mappings[r.Resource]; ok { continue } - gvk, err := b.mapper.KindFor(unversioned.GroupVersionResource{Resource: r.Resource}) - if err != nil { - return nil, err - } - mapping, err := b.mapper.RESTMapping(gvk.GroupKind(), gvk.Version) + mapping, err := b.mappingFor(r.Resource) if err != nil { return nil, err } + mappings[mapping.Resource] = mapping mappings[r.Resource] = mapping canonical[mapping.Resource] = struct{}{} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder_test.go index 1fad662b6..3100cfa43 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder_test.go @@ -23,7 +23,9 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "os" "reflect" + "strings" "testing" "github.com/ghodss/yaml" @@ -38,6 +40,7 @@ import ( "k8s.io/kubernetes/pkg/client/unversioned/fake" "k8s.io/kubernetes/pkg/runtime" utilerrors "k8s.io/kubernetes/pkg/util/errors" + utiltesting "k8s.io/kubernetes/pkg/util/testing" "k8s.io/kubernetes/pkg/watch" watchjson "k8s.io/kubernetes/pkg/watch/json" ) @@ -178,9 +181,74 @@ func (v *testVisitor) Objects() []runtime.Object { return objects } +var aPod string = ` +{ + "kind": "Pod", + "apiVersion": "` + testapi.Default.GroupVersion().String() + `", + "metadata": { + "name": "busybox{id}", + "labels": { + "name": "busybox{id}" + } + }, + "spec": { + "containers": [ + { + "name": "busybox", + "image": "busybox", + "command": [ + "sleep", + "3600" + ], + "imagePullPolicy": "IfNotPresent" + } + ], + "restartPolicy": "Always" + } +} +` + +var aRC string = ` +{ + "kind": "ReplicationController", + "apiVersion": "` + testapi.Default.GroupVersion().String() + `", + "metadata": { + "name": "busybox{id}", + "labels": { + "app": "busybox" + } + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "name": "busybox{id}", + "labels": { + "app": "busybox{id}" + } + }, + "spec": { + "containers": [ + { + "name": "busybox", + "image": "busybox", + "command": [ + "sleep", + "3600" + ], + "imagePullPolicy": "IfNotPresent" + } + ], + "restartPolicy": "Always" + } + } + } +} +` + func TestPathBuilderAndVersionedObjectNotDefaulted(t *testing.T) { b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). - FilenameParam(false, "../../../docs/user-guide/update-demo/kitten-rc.yaml") + FilenameParam(false, false, "../../../docs/user-guide/update-demo/kitten-rc.yaml") test := &testVisitor{} singular := false @@ -233,40 +301,138 @@ func TestNodeBuilder(t *testing.T) { } } +func createTestDir(t *testing.T, path string) { + if err := os.MkdirAll(path, 0750); err != nil { + t.Fatalf("error creating test dir: %v", err) + } +} + +func writeTestFile(t *testing.T, path string, contents string) { + if err := ioutil.WriteFile(path, []byte(contents), 0644); err != nil { + t.Fatalf("error creating test file %#v", err) + } +} + func TestPathBuilderWithMultiple(t *testing.T) { - b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). - FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml"). - FilenameParam(false, "../../../examples/pod"). - NamespaceParam("test").DefaultNamespace() + // create test dirs + tmpDir, err := utiltesting.MkTmpdir("recursive_test_multiple") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + createTestDir(t, fmt.Sprintf("%s/%s", tmpDir, "recursive/pod/pod_1")) + createTestDir(t, fmt.Sprintf("%s/%s", tmpDir, "recursive/rc/rc_1")) + createTestDir(t, fmt.Sprintf("%s/%s", tmpDir, "inode/hardlink")) + defer os.RemoveAll(tmpDir) - test := &testVisitor{} - singular := false - - err := b.Do().IntoSingular(&singular).Visit(test.Handle) - if err != nil || singular || len(test.Infos) != 2 { - t.Fatalf("unexpected response: %v %t %#v", err, singular, test.Infos) + // create test files + writeTestFile(t, fmt.Sprintf("%s/recursive/pod/busybox.json", tmpDir), strings.Replace(aPod, "{id}", "0", -1)) + writeTestFile(t, fmt.Sprintf("%s/recursive/pod/pod_1/busybox.json", tmpDir), strings.Replace(aPod, "{id}", "1", -1)) + writeTestFile(t, fmt.Sprintf("%s/recursive/rc/busybox.json", tmpDir), strings.Replace(aRC, "{id}", "0", -1)) + writeTestFile(t, fmt.Sprintf("%s/recursive/rc/rc_1/busybox.json", tmpDir), strings.Replace(aRC, "{id}", "1", -1)) + writeTestFile(t, fmt.Sprintf("%s/inode/hardlink/busybox.json", tmpDir), strings.Replace(aPod, "{id}", "0", -1)) + if err := os.Link(fmt.Sprintf("%s/inode/hardlink/busybox.json", tmpDir), fmt.Sprintf("%s/inode/hardlink/busybox-link.json", tmpDir)); err != nil { + t.Fatalf("error creating test file: %v", err) } - info := test.Infos[0] - if _, ok := info.Object.(*api.ReplicationController); !ok || info.Name != "redis-master" || info.Namespace != "test" { - t.Errorf("unexpected info: %#v", info) + tests := []struct { + name string + object runtime.Object + recursive bool + directory string + expectedNames []string + }{ + {"pod", &api.Pod{}, false, "../../../examples/pod", []string{"nginx"}}, + {"recursive-pod", &api.Pod{}, true, fmt.Sprintf("%s/recursive/pod", tmpDir), []string{"busybox0", "busybox1"}}, + {"rc", &api.ReplicationController{}, false, "../../../examples/guestbook/legacy/redis-master-controller.yaml", []string{"redis-master"}}, + {"recursive-rc", &api.ReplicationController{}, true, fmt.Sprintf("%s/recursive/rc", tmpDir), []string{"busybox0", "busybox1"}}, + {"hardlink", &api.Pod{}, false, fmt.Sprintf("%s/inode/hardlink/busybox-link.json", tmpDir), []string{"busybox0"}}, + {"hardlink", &api.Pod{}, true, fmt.Sprintf("%s/inode/hardlink/busybox-link.json", tmpDir), []string{"busybox0"}}, } - info = test.Infos[1] - if _, ok := info.Object.(*api.Pod); !ok || info.Name != "nginx" || info.Namespace != "test" { - t.Errorf("unexpected info: %#v", info) + + for _, test := range tests { + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). + FilenameParam(false, test.recursive, test.directory). + NamespaceParam("test").DefaultNamespace() + + testVisitor := &testVisitor{} + singular := false + + err := b.Do().IntoSingular(&singular).Visit(testVisitor.Handle) + if err != nil { + t.Fatalf("unexpected response: %v %t %#v %s", err, singular, testVisitor.Infos, test.name) + } + + info := testVisitor.Infos + + for i, v := range info { + switch test.object.(type) { + case *api.Pod: + if _, ok := v.Object.(*api.Pod); !ok || v.Name != test.expectedNames[i] || v.Namespace != "test" { + t.Errorf("unexpected info: %#v", v) + } + case *api.ReplicationController: + if _, ok := v.Object.(*api.ReplicationController); !ok || v.Name != test.expectedNames[i] || v.Namespace != "test" { + t.Errorf("unexpected info: %#v", v) + } + } + } + } +} + +func TestPathBuilderWithMultipleInvalid(t *testing.T) { + // create test dirs + tmpDir, err := utiltesting.MkTmpdir("recursive_test_multiple_invalid") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + createTestDir(t, fmt.Sprintf("%s/%s", tmpDir, "inode/symlink/pod")) + defer os.RemoveAll(tmpDir) + + // create test files + writeTestFile(t, fmt.Sprintf("%s/inode/symlink/pod/busybox.json", tmpDir), strings.Replace(aPod, "{id}", "0", -1)) + if err := os.Symlink(fmt.Sprintf("%s/inode/symlink/pod", tmpDir), fmt.Sprintf("%s/inode/symlink/pod-link", tmpDir)); err != nil { + t.Fatalf("error creating test file: %v", err) + } + if err := os.Symlink(fmt.Sprintf("%s/inode/symlink/loop", tmpDir), fmt.Sprintf("%s/inode/symlink/loop", tmpDir)); err != nil { + t.Fatalf("error creating test file: %v", err) + } + + tests := []struct { + name string + recursive bool + directory string + }{ + {"symlink", false, fmt.Sprintf("%s/inode/symlink/pod-link", tmpDir)}, + {"symlink", true, fmt.Sprintf("%s/inode/symlink/pod-link", tmpDir)}, + {"loop", false, fmt.Sprintf("%s/inode/symlink/loop", tmpDir)}, + {"loop", true, fmt.Sprintf("%s/inode/symlink/loop", tmpDir)}, + } + + for _, test := range tests { + b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). + FilenameParam(false, test.recursive, test.directory). + NamespaceParam("test").DefaultNamespace() + + testVisitor := &testVisitor{} + singular := false + + err := b.Do().IntoSingular(&singular).Visit(testVisitor.Handle) + if err == nil { + t.Fatalf("unexpected response: %v %t %#v %s", err, singular, testVisitor.Infos, test.name) + } } } func TestDirectoryBuilder(t *testing.T) { b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). - FilenameParam(false, "../../../examples/guestbook"). + FilenameParam(false, false, "../../../examples/guestbook/legacy"). NamespaceParam("test").DefaultNamespace() test := &testVisitor{} singular := false err := b.Do().IntoSingular(&singular).Visit(test.Handle) - if err != nil || singular || len(test.Infos) < 4 { + if err != nil || singular || len(test.Infos) < 3 { t.Fatalf("unexpected response: %v %t %#v", err, singular, test.Infos) } @@ -290,7 +456,7 @@ func TestNamespaceOverride(t *testing.T) { // defer s.Close() b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). - FilenameParam(false, s.URL). + FilenameParam(false, false, s.URL). NamespaceParam("test") test := &testVisitor{} @@ -301,7 +467,7 @@ func TestNamespaceOverride(t *testing.T) { } b = NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). - FilenameParam(true, s.URL). + FilenameParam(true, false, s.URL). NamespaceParam("test") test = &testVisitor{} @@ -316,25 +482,31 @@ func TestURLBuilder(t *testing.T) { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), &api.Pod{ObjectMeta: api.ObjectMeta{Namespace: "foo", Name: "test"}}))) + w.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), &api.Pod{ObjectMeta: api.ObjectMeta{Namespace: "foo", Name: "test1"}}))) })) // TODO: Uncomment when fix #19254 // defer s.Close() b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). - FilenameParam(false, s.URL). - NamespaceParam("test") + FilenameParam(false, false, s.URL). + NamespaceParam("foo") test := &testVisitor{} - singular := false - err := b.Do().IntoSingular(&singular).Visit(test.Handle) - if err != nil || !singular || len(test.Infos) != 1 { - t.Fatalf("unexpected response: %v %t %#v", err, singular, test.Infos) + err := b.Do().Visit(test.Handle) + if err != nil || len(test.Infos) != 2 { + t.Fatalf("unexpected response: %v %#v", err, test.Infos) } info := test.Infos[0] if info.Name != "test" || info.Namespace != "foo" || info.Object == nil { t.Errorf("unexpected info: %#v", info) } + + info = test.Infos[1] + if info.Name != "test1" || info.Namespace != "foo" || info.Object == nil { + t.Errorf("unexpected info: %#v", info) + } + } func TestURLBuilderRequireNamespace(t *testing.T) { @@ -346,7 +518,7 @@ func TestURLBuilderRequireNamespace(t *testing.T) { // defer s.Close() b := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). - FilenameParam(false, s.URL). + FilenameParam(false, false, s.URL). NamespaceParam("test").RequireNamespace() test := &testVisitor{} @@ -744,7 +916,7 @@ func TestContinueOnErrorVisitor(t *testing.T) { func TestSingularObject(t *testing.T) { obj, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). NamespaceParam("test").DefaultNamespace(). - FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml"). + FilenameParam(false, false, "../../../examples/guestbook/legacy/redis-master-controller.yaml"). Flatten(). Do().Object() @@ -764,7 +936,7 @@ func TestSingularObject(t *testing.T) { func TestSingularObjectNoExtension(t *testing.T) { obj, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). NamespaceParam("test").DefaultNamespace(). - FilenameParam(false, "../../../examples/pod"). + FilenameParam(false, false, "../../../examples/pod"). Flatten(). Do().Object() @@ -875,7 +1047,7 @@ func TestWatch(t *testing.T) { }), }), testapi.Default.Codec()). NamespaceParam("test").DefaultNamespace(). - FilenameParam(false, "../../../examples/guestbook/redis-master-service.yaml").Flatten(). + FilenameParam(false, false, "../../../examples/guestbook/redis-master-service.yaml").Flatten(). Do().Watch("12") if err != nil { @@ -902,8 +1074,8 @@ func TestWatch(t *testing.T) { func TestWatchMultipleError(t *testing.T) { _, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClient(), testapi.Default.Codec()). NamespaceParam("test").DefaultNamespace(). - FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml").Flatten(). - FilenameParam(false, "../../../examples/guestbook/redis-master-controller.yaml").Flatten(). + FilenameParam(false, false, "../../../examples/guestbook/legacy/redis-master-controller.yaml").Flatten(). + FilenameParam(false, false, "../../../examples/guestbook/legacy/redis-master-controller.yaml").Flatten(). Do().Watch("") if err == nil { diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper.go b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper.go index fcce5b0ca..849a6c040 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper.go @@ -144,7 +144,7 @@ func (m *Helper) Replace(namespace, name string, overwrite bool, obj runtime.Obj } if version == "" && overwrite { // Retrieve the current version of the object to overwrite the server object - serverObj, err := c.Get().Namespace(namespace).Resource(m.Resource).Name(name).Do().Get() + serverObj, err := c.Get().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(m.Resource).Name(name).Do().Get() if err != nil { // The object does not exist, but we want it to be created return m.replaceResource(c, m.Resource, namespace, name, obj) diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper_test.go index 541daf880..bfb05f7f7 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper_test.go @@ -357,40 +357,42 @@ func TestHelperList(t *testing.T) { } func TestHelperReplace(t *testing.T) { - expectPut := func(req *http.Request) bool { + expectPut := func(path string, req *http.Request) bool { if req.Method != "PUT" { t.Errorf("unexpected method: %#v", req) return false } - parts := splitPath(req.URL.Path) - if parts[1] != "bar" { - t.Errorf("url doesn't contain namespace: %#v", req.URL) - return false - } - if parts[2] != "foo" { - t.Errorf("url doesn't contain name: %#v", req) + if req.URL.Path != path { + t.Errorf("unexpected url: %v", req.URL) return false } return true } tests := []struct { - Resp *http.Response - HTTPClient *http.Client - HttpErr error - Overwrite bool - Object runtime.Object + Resp *http.Response + HTTPClient *http.Client + HttpErr error + Overwrite bool + Object runtime.Object + Namespace string + NamespaceScoped bool + ExpectPath string ExpectObject runtime.Object Err bool - Req func(*http.Request) bool + Req func(string, *http.Request) bool }{ { - HttpErr: errors.New("failure"), - Err: true, + Namespace: "bar", + NamespaceScoped: true, + HttpErr: errors.New("failure"), + Err: true, }, { - Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, + Namespace: "bar", + NamespaceScoped: true, + Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, Resp: &http.Response{ StatusCode: http.StatusNotFound, Body: objBody(&unversioned.Status{Status: unversioned.StatusFailure}), @@ -398,19 +400,26 @@ func TestHelperReplace(t *testing.T) { Err: true, }, { - Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, - ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, + Namespace: "bar", + NamespaceScoped: true, + Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, + ExpectPath: "/namespaces/bar/foo", + ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, Resp: &http.Response{ StatusCode: http.StatusOK, Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess}), }, Req: expectPut, }, + // namespace scoped resource { + Namespace: "bar", + NamespaceScoped: true, Object: &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: apitesting.DeepEqualSafePodSpec(), }, + ExpectPath: "/namespaces/bar/foo", ExpectObject: &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}, Spec: apitesting.DeepEqualSafePodSpec(), @@ -424,11 +433,32 @@ func TestHelperReplace(t *testing.T) { }), Req: expectPut, }, + // cluster scoped resource { - Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, - ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, - Resp: &http.Response{StatusCode: http.StatusOK, Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess})}, - Req: expectPut, + Object: &api.Node{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + }, + ExpectObject: &api.Node{ + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}, + }, + Overwrite: true, + ExpectPath: "/foo", + HTTPClient: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + if req.Method == "PUT" { + return &http.Response{StatusCode: http.StatusOK, Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess})}, nil + } + return &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Node{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil + }), + Req: expectPut, + }, + { + Namespace: "bar", + NamespaceScoped: true, + Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, + ExpectPath: "/namespaces/bar/foo", + ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}}, + Resp: &http.Response{StatusCode: http.StatusOK, Body: objBody(&unversioned.Status{Status: unversioned.StatusSuccess})}, + Req: expectPut, }, } for i, test := range tests { @@ -441,23 +471,22 @@ func TestHelperReplace(t *testing.T) { modifier := &Helper{ RESTClient: client, Versioner: testapi.Default.MetadataAccessor(), - NamespaceScoped: true, + NamespaceScoped: test.NamespaceScoped, } - _, err := modifier.Replace("bar", "foo", test.Overwrite, test.Object) + _, err := modifier.Replace(test.Namespace, "foo", test.Overwrite, test.Object) if (err != nil) != test.Err { t.Errorf("%d: unexpected error: %t %v", i, test.Err, err) } if err != nil { continue } - if test.Req != nil && !test.Req(client.Req) { + if test.Req != nil && !test.Req(test.ExpectPath, client.Req) { t.Errorf("%d: unexpected request: %#v", i, client.Req) } body, err := ioutil.ReadAll(client.Req.Body) if err != nil { t.Fatalf("%d: unexpected error: %#v", i, err) } - t.Logf("got body: %s", string(body)) expect := []byte{} if test.ExpectObject != nil { expect = []byte(runtime.EncodeOrDie(testapi.Default.Codec(), test.ExpectObject)) diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/interfaces.go b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/interfaces.go index 54d20dfbf..2639a61ec 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/interfaces.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/interfaces.go @@ -19,7 +19,7 @@ package resource import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" - client "k8s.io/kubernetes/pkg/client/unversioned" + client "k8s.io/kubernetes/pkg/client/restclient" ) // RESTClient is a client helper for dealing with RESTful resources diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/mapper.go b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/mapper.go index 0839ca4a8..27e85043b 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/mapper.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/mapper.go @@ -21,6 +21,9 @@ import ( "reflect" "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata" "k8s.io/kubernetes/pkg/runtime" ) @@ -49,8 +52,16 @@ type Mapper struct { func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) { versions := &runtime.VersionedObjects{} _, gvk, err := m.Decode(data, nil, versions) + var obj runtime.Object + var versioned runtime.Object + if registered.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) { + obj, err = runtime.Decode(thirdpartyresourcedata.NewCodec(nil, gvk.Kind), data) + versioned = obj + } else { + obj, versioned = versions.Last(), versions.First() + } if err != nil { - return nil, fmt.Errorf("unable to decode %q: %v", source, err) + return nil, fmt.Errorf("unable to decode %q: %v [%v]", source, err, gvk) } mapping, err := m.RESTMapping(gvk.GroupKind(), gvk.Version) if err != nil { @@ -62,10 +73,6 @@ func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) { return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err) } - // TODO: decoding the version object is convenient, but questionable. This is used by apply - // and rolling-update today, but both of those cases should probably be requesting the raw - // object and performing their own decoding. - obj, versioned := versions.Last(), versions.First() name, _ := mapping.MetadataAccessor.Name(obj) namespace, _ := mapping.MetadataAccessor.Namespace(obj) resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj) @@ -85,11 +92,17 @@ func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) { // InfoForObject creates an Info object for the given Object. An error is returned // if the object cannot be introspected. Name and namespace will be set into Info // if the mapping's MetadataAccessor can retrieve them. -func (m *Mapper) InfoForObject(obj runtime.Object) (*Info, error) { - groupVersionKind, err := m.ObjectKind(obj) +func (m *Mapper) InfoForObject(obj runtime.Object, preferredGVKs []unversioned.GroupVersionKind) (*Info, error) { + groupVersionKinds, err := m.ObjectKinds(obj) if err != nil { return nil, fmt.Errorf("unable to get type info from the object %q: %v", reflect.TypeOf(obj), err) } + + groupVersionKind := groupVersionKinds[0] + if len(groupVersionKinds) > 1 && len(preferredGVKs) > 0 { + groupVersionKind = preferredObjectKind(groupVersionKinds, preferredGVKs) + } + mapping, err := m.RESTMapping(groupVersionKind.GroupKind(), groupVersionKind.Version) if err != nil { return nil, fmt.Errorf("unable to recognize %v: %v", groupVersionKind, err) @@ -111,3 +124,39 @@ func (m *Mapper) InfoForObject(obj runtime.Object) (*Info, error) { ResourceVersion: resourceVersion, }, nil } + +// preferredObjectKind picks the possibility that most closely matches the priority list in this order: +// GroupVersionKind matches (exact match) +// GroupKind matches +// Group matches +func preferredObjectKind(possibilities []unversioned.GroupVersionKind, preferences []unversioned.GroupVersionKind) unversioned.GroupVersionKind { + // Exact match + for _, priority := range preferences { + for _, possibility := range possibilities { + if possibility == priority { + return possibility + } + } + } + + // GroupKind match + for _, priority := range preferences { + for _, possibility := range possibilities { + if possibility.GroupKind() == priority.GroupKind() { + return possibility + } + } + } + + // Group match + for _, priority := range preferences { + for _, possibility := range possibilities { + if possibility.Group == priority.Group { + return possibility + } + } + } + + // Just pick the first + return possibilities[0] +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/result.go b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/result.go index 8d726ab7b..f382da0d7 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/result.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/result.go @@ -24,6 +24,7 @@ import ( "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/runtime" utilerrors "k8s.io/kubernetes/pkg/util/errors" "k8s.io/kubernetes/pkg/util/sets" @@ -241,6 +242,11 @@ func AsVersionedObjects(infos []*Info, version string, encoder runtime.Encoder) } // TODO: use info.VersionedObject as the value? + switch obj := info.Object.(type) { + case *extensions.ThirdPartyResourceData: + objects = append(objects, &runtime.Unknown{Raw: obj.Data}) + continue + } // objects that are not part of api.Scheme must be converted to JSON // TODO: convert to map[string]interface{}, attach to runtime.Unknown? @@ -251,7 +257,8 @@ func AsVersionedObjects(infos []*Info, version string, encoder runtime.Encoder) if err != nil { return nil, err } - objects = append(objects, &runtime.Unknown{RawJSON: data}) + // TODO: Set ContentEncoding and ContentType. + objects = append(objects, &runtime.Unknown{Raw: data}) continue } } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/visitor.go b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/visitor.go index a187919a4..be138b210 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/resource/visitor.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/resource/visitor.go @@ -20,13 +20,13 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "net/http" "net/url" "os" "path/filepath" "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/runtime" utilerrors "k8s.io/kubernetes/pkg/util/errors" @@ -219,9 +219,8 @@ func ValidateSchema(data []byte, schema validation.Schema) error { // URLVisitor downloads the contents of a URL, and if successful, returns // an info object representing the downloaded object. type URLVisitor struct { - *Mapper - URL *url.URL - Schema validation.Schema + URL *url.URL + *StreamVisitor } func (v *URLVisitor) Visit(fn VisitorFunc) error { @@ -233,18 +232,9 @@ func (v *URLVisitor) Visit(fn VisitorFunc) error { if res.StatusCode != 200 { return fmt.Errorf("unable to read URL %q, server reported %d %s", v.URL, res.StatusCode, res.Status) } - data, err := ioutil.ReadAll(res.Body) - if err != nil { - return fmt.Errorf("unable to read URL %q: %v\n", v.URL, err) - } - if err := ValidateSchema(data, v.Schema); err != nil { - return fmt.Errorf("error validating %q: %v", v.URL, err) - } - info, err := v.Mapper.InfoForData(data, v.URL.String()) - if err != nil { - return err - } - return fn(info, nil) + + v.StreamVisitor.Reader = res.Body + return v.StreamVisitor.Visit(fn) } // DecoratedVisitor will invoke the decorators in order prior to invoking the visitor function @@ -348,8 +338,15 @@ func (v FlattenListVisitor) Visit(fn VisitorFunc) error { }{v.Mapper, v.Mapper.Decoder}); len(errs) > 0 { return utilerrors.NewAggregate(errs) } + + // If we have a GroupVersionKind on the list, prioritize that when asking for info on the objects contained in the list + var preferredGVKs []unversioned.GroupVersionKind + if info.Mapping != nil && !info.Mapping.GroupVersionKind.IsEmpty() { + preferredGVKs = append(preferredGVKs, info.Mapping.GroupVersionKind) + } + for i := range items { - item, err := v.InfoForObject(items[i]) + item, err := v.InfoForObject(items[i], preferredGVKs) if err != nil { return err } @@ -477,14 +474,15 @@ func (v *StreamVisitor) Visit(fn VisitorFunc) error { } return err } - ext.RawJSON = bytes.TrimSpace(ext.RawJSON) - if len(ext.RawJSON) == 0 || bytes.Equal(ext.RawJSON, []byte("null")) { + // TODO: This needs to be able to handle object in other encodings and schemas. + ext.Raw = bytes.TrimSpace(ext.Raw) + if len(ext.Raw) == 0 || bytes.Equal(ext.Raw, []byte("null")) { continue } - if err := ValidateSchema(ext.RawJSON, v.Schema); err != nil { + if err := ValidateSchema(ext.Raw, v.Schema); err != nil { return fmt.Errorf("error validating %q: %v", v.Source, err) } - info, err := v.InfoForData(ext.RawJSON, v.Source) + info, err := v.InfoForData(ext.Raw, v.Source) if err != nil { if fnErr := fn(info, err); fnErr != nil { return fnErr diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/resource_printer.go b/vendor/k8s.io/kubernetes/pkg/kubectl/resource_printer.go index d33bb92f0..32eaa2e16 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/resource_printer.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/resource_printer.go @@ -255,6 +255,12 @@ type JSONPrinter struct { // PrintObj is an implementation of ResourcePrinter.PrintObj which simply writes the object to the Writer. func (p *JSONPrinter) PrintObj(obj runtime.Object, w io.Writer) error { + switch obj := obj.(type) { + case *runtime.Unknown: + _, err := w.Write(obj.Raw) + return err + } + data, err := json.Marshal(obj) if err != nil { return err @@ -281,6 +287,16 @@ type YAMLPrinter struct { // PrintObj prints the data as YAML. func (p *YAMLPrinter) PrintObj(obj runtime.Object, w io.Writer) error { + switch obj := obj.(type) { + case *runtime.Unknown: + data, err := yaml.JSONToYAML(obj.Raw) + if err != nil { + return err + } + _, err = w.Write(data) + return err + } + output, err := yaml.Marshal(obj) if err != nil { return err @@ -396,14 +412,14 @@ func (h *HumanReadablePrinter) HandledResources() []string { // pkg/kubectl/cmd/get.go to reflect the new resource type. var podColumns = []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE"} var podTemplateColumns = []string{"TEMPLATE", "CONTAINER(S)", "IMAGE(S)", "PODLABELS"} -var replicationControllerColumns = []string{"CONTROLLER", "REPLICAS", "AGE"} -var replicaSetColumns = []string{"CONTROLLER", "REPLICAS", "AGE"} -var jobColumns = []string{"JOB", "SUCCESSFUL"} +var replicationControllerColumns = []string{"NAME", "DESIRED", "CURRENT", "AGE"} +var replicaSetColumns = []string{"NAME", "DESIRED", "CURRENT", "AGE"} +var jobColumns = []string{"NAME", "DESIRED", "SUCCESSFUL", "AGE"} var serviceColumns = []string{"NAME", "CLUSTER-IP", "EXTERNAL-IP", "PORT(S)", "AGE"} -var ingressColumns = []string{"NAME", "RULE", "BACKEND", "ADDRESS"} +var ingressColumns = []string{"NAME", "RULE", "BACKEND", "ADDRESS", "AGE"} var endpointColumns = []string{"NAME", "ENDPOINTS", "AGE"} var nodeColumns = []string{"NAME", "STATUS", "AGE"} -var daemonSetColumns = []string{"NAME", "NODE-SELECTOR"} +var daemonSetColumns = []string{"NAME", "DESIRED", "CURRENT", "NODE-SELECTOR", "AGE"} var eventColumns = []string{"FIRSTSEEN", "LASTSEEN", "COUNT", "NAME", "KIND", "SUBOBJECT", "TYPE", "REASON", "SOURCE", "MESSAGE"} var limitRangeColumns = []string{"NAME", "AGE"} var resourceQuotaColumns = []string{"NAME", "AGE"} @@ -414,6 +430,9 @@ var persistentVolumeColumns = []string{"NAME", "CAPACITY", "ACCESSMODES", "STATU var persistentVolumeClaimColumns = []string{"NAME", "STATUS", "VOLUME", "CAPACITY", "ACCESSMODES", "AGE"} var componentStatusColumns = []string{"NAME", "STATUS", "MESSAGE", "ERROR"} var thirdPartyResourceColumns = []string{"NAME", "DESCRIPTION", "VERSION(S)"} + +// TODO: consider having 'KIND' for third party resource data +var thirdPartyResourceDataColumns = []string{"NAME", "LABELS", "DATA"} var horizontalPodAutoscalerColumns = []string{"NAME", "REFERENCE", "TARGET", "CURRENT", "MINPODS", "MAXPODS", "AGE"} var withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too. var deploymentColumns = []string{"NAME", "DESIRED", "CURRENT", "UP-TO-DATE", "AVAILABLE", "AGE"} @@ -470,6 +489,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() { h.Handler(configMapColumns, printConfigMapList) h.Handler(podSecurityPolicyColumns, printPodSecurityPolicy) h.Handler(podSecurityPolicyColumns, printPodSecurityPolicyList) + h.Handler(thirdPartyResourceDataColumns, printThirdPartyResourceData) + h.Handler(thirdPartyResourceDataColumns, printThirdPartyResourceDataList) } func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error { @@ -638,22 +659,19 @@ func printPodTemplate(pod *api.PodTemplate, w io.Writer, options PrintOptions) e namespace := pod.Namespace containers := pod.Template.Spec.Containers - var firstContainer api.Container - if len(containers) > 0 { - firstContainer, containers = containers[0], containers[1:] - } if options.WithNamespace { if _, err := fmt.Fprintf(w, "%s\t", namespace); err != nil { return err } } - if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s", - name, - firstContainer.Name, - firstContainer.Image, - labels.FormatLabels(pod.Template.Labels), - ); err != nil { + if _, err := fmt.Fprintf(w, "%s", name); err != nil { + return err + } + if err := layoutContainers(containers, w); err != nil { + return err + } + if _, err := fmt.Fprintf(w, "\t%s", labels.FormatLabels(pod.Template.Labels)); err != nil { return err } if _, err := fmt.Fprint(w, appendLabels(pod.Labels, options.ColumnLabels)); err != nil { @@ -663,20 +681,6 @@ func printPodTemplate(pod *api.PodTemplate, w io.Writer, options PrintOptions) e return err } - // Lay out all the other containers on separate lines. - extraLinePrefix := "\t" - if options.WithNamespace { - extraLinePrefix = "\t\t" - } - for _, container := range containers { - _, err := fmt.Fprintf(w, "%s%s\t%s\t%s", extraLinePrefix, container.Name, container.Image, "") - if err != nil { - return err - } - if _, err := fmt.Fprint(w, appendLabelTabs(options.ColumnLabels)); err != nil { - return err - } - } return nil } @@ -689,33 +693,34 @@ func printPodTemplateList(podList *api.PodTemplateList, w io.Writer, options Pri return nil } +// TODO(AdoHe): try to put wide output in a single method func printReplicationController(controller *api.ReplicationController, w io.Writer, options PrintOptions) error { name := controller.Name namespace := controller.Namespace containers := controller.Spec.Template.Spec.Containers - var firstContainer api.Container - if len(containers) > 0 { - firstContainer, containers = containers[0], containers[1:] - } if options.WithNamespace { if _, err := fmt.Fprintf(w, "%s\t", namespace); err != nil { return err } } - if _, err := fmt.Fprintf(w, "%s\t%d\t%s", + + desiredReplicas := controller.Spec.Replicas + currentReplicas := controller.Status.Replicas + if _, err := fmt.Fprintf(w, "%s\t%d\t%d\t%s", name, - controller.Spec.Replicas, + desiredReplicas, + currentReplicas, translateTimestamp(controller.CreationTimestamp), ); err != nil { return err } + if options.Wide { - if _, err := fmt.Fprintf(w, "\t%s\t%s\t%s", - firstContainer.Name, - firstContainer.Image, - labels.FormatLabels(controller.Spec.Selector), - ); err != nil { + if err := layoutContainers(containers, w); err != nil { + return err + } + if _, err := fmt.Fprintf(w, "\t%s", labels.FormatLabels(controller.Spec.Selector)); err != nil { return err } } @@ -726,20 +731,6 @@ func printReplicationController(controller *api.ReplicationController, w io.Writ return err } - // Lay out all the other containers on separate lines. - extraLinePrefix := "\t" - if options.WithNamespace { - extraLinePrefix = "\t\t" - } - for _, container := range containers { - _, err := fmt.Fprintf(w, "%s%s\t%s\t%s\t%s", extraLinePrefix, container.Name, container.Image, "", "") - if err != nil { - return err - } - if _, err := fmt.Fprint(w, appendLabelTabs(options.ColumnLabels)); err != nil { - return err - } - } return nil } @@ -756,29 +747,28 @@ func printReplicaSet(rs *extensions.ReplicaSet, w io.Writer, options PrintOption name := rs.Name namespace := rs.Namespace containers := rs.Spec.Template.Spec.Containers - var firstContainer api.Container - if len(containers) > 0 { - firstContainer, containers = containers[0], containers[1:] - } if options.WithNamespace { if _, err := fmt.Fprintf(w, "%s\t", namespace); err != nil { return err } } - if _, err := fmt.Fprintf(w, "%s\t%d\t%s", + + desiredReplicas := rs.Spec.Replicas + currentReplicas := rs.Status.Replicas + if _, err := fmt.Fprintf(w, "%s\t%d\t%d\t%s", name, - rs.Spec.Replicas, + desiredReplicas, + currentReplicas, translateTimestamp(rs.CreationTimestamp), ); err != nil { return err } if options.Wide { - if _, err := fmt.Fprintf(w, "\t%s\t%s\t%s", - firstContainer.Name, - firstContainer.Image, - unversioned.FormatLabelSelector(rs.Spec.Selector), - ); err != nil { + if err := layoutContainers(containers, w); err != nil { + return err + } + if _, err := fmt.Fprintf(w, "\t%s", unversioned.FormatLabelSelector(rs.Spec.Selector)); err != nil { return err } } @@ -789,20 +779,6 @@ func printReplicaSet(rs *extensions.ReplicaSet, w io.Writer, options PrintOption return err } - // Lay out all the other containers on separate lines. - extraLinePrefix := "\t" - if options.WithNamespace { - extraLinePrefix = "\t\t" - } - for _, container := range containers { - _, err := fmt.Fprintf(w, "%s%s\t%s\t%s\t%s", extraLinePrefix, container.Name, container.Image, "", "") - if err != nil { - return err - } - if _, err := fmt.Fprint(w, appendLabelTabs(options.ColumnLabels)); err != nil { - return err - } - } return nil } @@ -819,31 +795,44 @@ func printJob(job *extensions.Job, w io.Writer, options PrintOptions) error { name := job.Name namespace := job.Namespace containers := job.Spec.Template.Spec.Containers - var firstContainer api.Container - if len(containers) > 0 { - firstContainer, containers = containers[0], containers[1:] - } + if options.WithNamespace { if _, err := fmt.Fprintf(w, "%s\t", namespace); err != nil { return err } } - selector, _ := unversioned.LabelSelectorAsSelector(job.Spec.Selector) - _, err := fmt.Fprintf(w, "%s\t%d", - name, - job.Status.Succeeded) + selector, err := unversioned.LabelSelectorAsSelector(job.Spec.Selector) if err != nil { + // this shouldn't happen if LabelSelector passed validation return err } - if options.Wide { - if _, err := fmt.Fprintf(w, "\t%s\t%s\t%s", - firstContainer.Name, - firstContainer.Image, - selector.String(), + if job.Spec.Completions != nil { + if _, err := fmt.Fprintf(w, "%s\t%d\t%d\t%s", + name, + *job.Spec.Completions, + job.Status.Succeeded, + translateTimestamp(job.CreationTimestamp), ); err != nil { return err } + } else { + if _, err := fmt.Fprintf(w, "%s\t%s\t%d\t%s", + name, + "", + job.Status.Succeeded, + translateTimestamp(job.CreationTimestamp), + ); err != nil { + return err + } + } + if options.Wide { + if err := layoutContainers(containers, w); err != nil { + return err + } + if _, err := fmt.Fprintf(w, "\t%s", selector.String()); err != nil { + return err + } } if _, err := fmt.Fprint(w, appendLabels(job.Labels, options.ColumnLabels)); err != nil { return err @@ -852,20 +841,6 @@ func printJob(job *extensions.Job, w io.Writer, options PrintOptions) error { return err } - // Lay out all the other containers on separate lines. - extraLinePrefix := "\t" - if options.WithNamespace { - extraLinePrefix = "\t\t" - } - for _, container := range containers { - _, err := fmt.Fprintf(w, "%s%s\t%s\t%s\t%s", extraLinePrefix, container.Name, container.Image, "", "") - if err != nil { - return err - } - if _, err := fmt.Fprint(w, appendLabelTabs(options.ColumnLabels)); err != nil { - return err - } - } return nil } @@ -983,11 +958,13 @@ func printIngress(ingress *extensions.Ingress, w io.Writer, options PrintOptions } } - if _, err := fmt.Fprintf(w, "%s\t%v\t%v\t%v", + if _, err := fmt.Fprintf(w, "%s\t%v\t%v\t%v\t%s", name, "-", backendStringer(ingress.Spec.Backend), - loadBalancerStatusStringer(ingress.Status.LoadBalancer)); err != nil { + loadBalancerStatusStringer(ingress.Status.LoadBalancer), + translateTimestamp(ingress.CreationTimestamp), + ); err != nil { return err } if _, err := fmt.Fprint(w, appendLabels(ingress.Labels, options.ColumnLabels)); err != nil { @@ -998,7 +975,8 @@ func printIngress(ingress *extensions.Ingress, w io.Writer, options PrintOptions return err } - // Lay out all the rules on separate lines. + // Lay out all the rules on separate lines if use wide output. + // TODO(AdoHe): improve ingress output extraLinePrefix := "" if options.WithNamespace { extraLinePrefix = "\t" @@ -1024,6 +1002,7 @@ func printIngress(ingress *extensions.Ingress, w io.Writer, options PrintOptions } } } + return nil } @@ -1041,33 +1020,34 @@ func printDaemonSet(ds *extensions.DaemonSet, w io.Writer, options PrintOptions) namespace := ds.Namespace containers := ds.Spec.Template.Spec.Containers - var firstContainer api.Container - if len(containers) > 0 { - firstContainer, containers = containers[0], containers[1:] - } if options.WithNamespace { if _, err := fmt.Fprintf(w, "%s\t", namespace); err != nil { return err } } + + desiredScheduled := ds.Status.DesiredNumberScheduled + currentScheduled := ds.Status.CurrentNumberScheduled selector, err := unversioned.LabelSelectorAsSelector(ds.Spec.Selector) if err != nil { // this shouldn't happen if LabelSelector passed validation return err } - if _, err := fmt.Fprintf(w, "%s\t%s", + if _, err := fmt.Fprintf(w, "%s\t%d\t%d\t%s\t%s", name, + desiredScheduled, + currentScheduled, labels.FormatLabels(ds.Spec.Template.Spec.NodeSelector), + translateTimestamp(ds.CreationTimestamp), ); err != nil { return err } if options.Wide { - if _, err := fmt.Fprintf(w, "\t%s\t%s\t%s", - firstContainer.Name, - firstContainer.Image, - selector, - ); err != nil { + if err := layoutContainers(containers, w); err != nil { + return err + } + if _, err := fmt.Fprintf(w, "\t%s", selector.String()); err != nil { return err } } @@ -1078,20 +1058,6 @@ func printDaemonSet(ds *extensions.DaemonSet, w io.Writer, options PrintOptions) return err } - // Lay out all the other containers on separate lines. - extraLinePrefix := "\t" - if options.WithNamespace { - extraLinePrefix = "\t\t" - } - for _, container := range containers { - _, err := fmt.Fprintf(w, "%s%s\t%s\t%s\t%s", extraLinePrefix, container.Name, container.Image, "", "") - if err != nil { - return err - } - if _, err := fmt.Fprint(w, appendLabelTabs(options.ColumnLabels)); err != nil { - return err - } - } return nil } @@ -1507,7 +1473,7 @@ func printThirdPartyResource(rsrc *extensions.ThirdPartyResource, w io.Writer, o versions := make([]string, len(rsrc.Versions)) for ix := range rsrc.Versions { version := &rsrc.Versions[ix] - versions[ix] = fmt.Sprintf("%s/%s", version.APIGroup, version.Name) + versions[ix] = fmt.Sprintf("%s", version.Name) } versionsString := strings.Join(versions, ",") if _, err := fmt.Fprintf(w, "%s\t%s\t%s\n", rsrc.Name, rsrc.Description, versionsString); err != nil { @@ -1526,6 +1492,35 @@ func printThirdPartyResourceList(list *extensions.ThirdPartyResourceList, w io.W return nil } +func truncate(str string, maxLen int) string { + if len(str) > maxLen { + return str[0:maxLen] + "..." + } + return str +} + +func printThirdPartyResourceData(rsrc *extensions.ThirdPartyResourceData, w io.Writer, options PrintOptions) error { + l := labels.FormatLabels(rsrc.Labels) + truncateCols := 50 + if options.Wide { + truncateCols = 100 + } + if _, err := fmt.Fprintf(w, "%s\t%s\t%s\n", rsrc.Name, l, truncate(string(rsrc.Data), truncateCols)); err != nil { + return err + } + return nil +} + +func printThirdPartyResourceDataList(list *extensions.ThirdPartyResourceDataList, w io.Writer, options PrintOptions) error { + for _, item := range list.Items { + if err := printThirdPartyResourceData(&item, w, options); err != nil { + return err + } + } + + return nil +} + func printDeployment(deployment *extensions.Deployment, w io.Writer, options PrintOptions) error { if options.WithNamespace { if _, err := fmt.Fprintf(w, "%s\t", deployment.Namespace); err != nil { @@ -1639,9 +1634,9 @@ func printConfigMapList(list *api.ConfigMapList, w io.Writer, options PrintOptio } func printPodSecurityPolicy(item *extensions.PodSecurityPolicy, w io.Writer, options PrintOptions) error { - _, err := fmt.Fprintf(w, "%s\t%t\t%v\t%t\t%s\t%s\n", item.Name, item.Spec.Privileged, - item.Spec.Capabilities, item.Spec.Volumes, item.Spec.SELinuxContext.Type, - item.Spec.RunAsUser.Type) + _, err := fmt.Fprintf(w, "%s\t%t\t%v\t%v\t%s\t%s\n", item.Name, item.Spec.Privileged, + item.Spec.Capabilities, item.Spec.Volumes, item.Spec.SELinux.Rule, + item.Spec.RunAsUser.Rule) return err } @@ -1700,6 +1695,26 @@ func appendLabelTabs(columnLabels []string) string { return buffer.String() } +// Lay out all the containers on one line if use wide output. +func layoutContainers(containers []api.Container, w io.Writer) error { + var namesBuffer bytes.Buffer + var imagesBuffer bytes.Buffer + + for i, container := range containers { + namesBuffer.WriteString(container.Name) + imagesBuffer.WriteString(container.Image) + if i != len(containers)-1 { + namesBuffer.WriteString(",") + imagesBuffer.WriteString(",") + } + } + _, err := fmt.Fprintf(w, "\t%s\t%s", namesBuffer.String(), imagesBuffer.String()) + if err != nil { + return err + } + return nil +} + func formatLabelHeaders(columnLabels []string) []string { formHead := make([]string, len(columnLabels)) for i, l := range columnLabels { diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/resource_printer_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/resource_printer_test.go index 52e286db1..ccc1f9b9d 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/resource_printer_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/resource_printer_test.go @@ -34,7 +34,7 @@ import ( kubectltesting "k8s.io/kubernetes/pkg/kubectl/testing" "k8s.io/kubernetes/pkg/runtime" yamlserializer "k8s.io/kubernetes/pkg/runtime/serializer/yaml" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" "k8s.io/kubernetes/pkg/util/intstr" "k8s.io/kubernetes/pkg/util/sets" @@ -182,7 +182,7 @@ func testPrinter(t *testing.T, printer ResourcePrinter, unmarshalFunc func(data t.Fatal(err) } if !reflect.DeepEqual(testData, poutput) { - t.Errorf("Test data and unmarshaled data are not equal: %v", util.ObjectDiff(poutput, testData)) + t.Errorf("Test data and unmarshaled data are not equal: %v", diff.ObjectDiff(poutput, testData)) } obj := &api.Pod{ @@ -202,7 +202,7 @@ func testPrinter(t *testing.T, printer ResourcePrinter, unmarshalFunc func(data t.Fatal(err) } if !reflect.DeepEqual(obj, &objOut) { - t.Errorf("Unexpected inequality:\n%v", util.ObjectDiff(obj, &objOut)) + t.Errorf("Unexpected inequality:\n%v", diff.ObjectDiff(obj, &objOut)) } } @@ -299,10 +299,10 @@ func TestNamePrinter(t *testing.T) { }, Items: []runtime.RawExtension{ { - RawJSON: []byte(`{"kind": "Pod", "apiVersion": "v1", "metadata": { "name": "foo"}}`), + Raw: []byte(`{"kind": "Pod", "apiVersion": "v1", "metadata": { "name": "foo"}}`), }, { - RawJSON: []byte(`{"kind": "Pod", "apiVersion": "v1", "metadata": { "name": "bar"}}`), + Raw: []byte(`{"kind": "Pod", "apiVersion": "v1", "metadata": { "name": "bar"}}`), }, }, }, @@ -1255,9 +1255,9 @@ func TestTranslateTimestamp(t *testing.T) { {"30 seconds ago", translateTimestamp(unversioned.Time{Time: time.Now().Add(-3e10)}), "30s"}, {"5 minutes ago", translateTimestamp(unversioned.Time{Time: time.Now().Add(-3e11)}), "5m"}, {"an hour ago", translateTimestamp(unversioned.Time{Time: time.Now().Add(-6e12)}), "1h"}, - {"2 days ago", translateTimestamp(unversioned.Time{Time: time.Now().AddDate(0, 0, -2)}), "2d"}, - {"months ago", translateTimestamp(unversioned.Time{Time: time.Now().AddDate(0, 0, -90)}), "90d"}, - {"10 years ago", translateTimestamp(unversioned.Time{Time: time.Now().AddDate(-10, 0, 0)}), "10y"}, + {"2 days ago", translateTimestamp(unversioned.Time{Time: time.Now().UTC().AddDate(0, 0, -2)}), "2d"}, + {"months ago", translateTimestamp(unversioned.Time{Time: time.Now().UTC().AddDate(0, 0, -90)}), "90d"}, + {"10 years ago", translateTimestamp(unversioned.Time{Time: time.Now().UTC().AddDate(-10, 0, 0)}), "10y"}, } for _, test := range tl { if test.got != test.exp { @@ -1305,6 +1305,89 @@ func TestPrintDeployment(t *testing.T) { } } +func TestPrintDaemonSet(t *testing.T) { + tests := []struct { + ds extensions.DaemonSet + startsWith string + }{ + { + extensions.DaemonSet{ + ObjectMeta: api.ObjectMeta{ + Name: "test1", + CreationTimestamp: unversioned.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: extensions.DaemonSetSpec{ + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{Containers: make([]api.Container, 2)}, + }, + }, + Status: extensions.DaemonSetStatus{ + CurrentNumberScheduled: 2, + DesiredNumberScheduled: 3, + }, + }, + "test1\t3\t2\t\t0s\n", + }, + } + + buf := bytes.NewBuffer([]byte{}) + for _, test := range tests { + printDaemonSet(&test.ds, buf, PrintOptions{false, false, false, false, false, false, []string{}}) + if !strings.HasPrefix(buf.String(), test.startsWith) { + t.Fatalf("Expected to start with %s but got %s", test.startsWith, buf.String()) + } + buf.Reset() + } +} + +func TestPrintJob(t *testing.T) { + completions := 2 + tests := []struct { + job extensions.Job + expect string + }{ + { + extensions.Job{ + ObjectMeta: api.ObjectMeta{ + Name: "job1", + CreationTimestamp: unversioned.Time{Time: time.Now().Add(1.9e9)}, + }, + Spec: extensions.JobSpec{ + Completions: &completions, + }, + Status: extensions.JobStatus{ + Succeeded: 1, + }, + }, + "job1\t2\t1\t0s\n", + }, + { + extensions.Job{ + ObjectMeta: api.ObjectMeta{ + Name: "job2", + CreationTimestamp: unversioned.Time{Time: time.Now().AddDate(-10, 0, 0)}, + }, + Spec: extensions.JobSpec{ + Completions: nil, + }, + Status: extensions.JobStatus{ + Succeeded: 0, + }, + }, + "job2\t\t0\t10y\n", + }, + } + + buf := bytes.NewBuffer([]byte{}) + for _, test := range tests { + printJob(&test.job, buf, PrintOptions{false, false, false, true, false, false, []string{}}) + if buf.String() != test.expect { + t.Fatalf("Expected: %s, got: %s", test.expect, buf.String()) + } + buf.Reset() + } +} + func TestPrintPodShowLabels(t *testing.T) { tests := []struct { pod api.Pod diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/rollback.go b/vendor/k8s.io/kubernetes/pkg/kubectl/rollback.go index 9f53b25af..2e4f92b30 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/rollback.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/rollback.go @@ -49,6 +49,10 @@ type DeploymentRollbacker struct { } func (r *DeploymentRollbacker) Rollback(namespace, name string, updatedAnnotations map[string]string, toRevision int64, obj runtime.Object) (string, error) { + d := obj.(*extensions.Deployment) + if d.Spec.Paused { + return "", fmt.Errorf("you cannot rollback a paused deployment; resume it first with 'kubectl rollout resume' and try again") + } deploymentRollback := &extensions.DeploymentRollback{ Name: name, UpdatedAnnotations: updatedAnnotations, diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/rolling_updater.go b/vendor/k8s.io/kubernetes/pkg/kubectl/rolling_updater.go index d734b153d..e69d889a7 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/rolling_updater.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/rolling_updater.go @@ -20,7 +20,6 @@ import ( goerrors "errors" "fmt" "io" - "math" "strconv" "strings" "time" @@ -30,6 +29,7 @@ import ( client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/deployment" "k8s.io/kubernetes/pkg/util/integer" "k8s.io/kubernetes/pkg/util/intstr" "k8s.io/kubernetes/pkg/util/wait" @@ -113,10 +113,8 @@ type RollingUpdater struct { getOrCreateTargetController func(controller *api.ReplicationController, sourceId string) (*api.ReplicationController, bool, error) // cleanup performs post deployment cleanup tasks for newRc and oldRc. cleanup func(oldRc, newRc *api.ReplicationController, config *RollingUpdaterConfig) error - // waitForReadyPods should block until there are >0 total pods ready amongst - // the old and new controllers, and should return the amount of old and new - // ready. - waitForReadyPods func(interval, timeout time.Duration, oldRc, newRc *api.ReplicationController) (int, int, error) + // getReadyPods returns the amount of old and new ready pods. + getReadyPods func(oldRc, newRc *api.ReplicationController) (int, int, error) } // NewRollingUpdater creates a RollingUpdater from a client. @@ -128,7 +126,7 @@ func NewRollingUpdater(namespace string, client client.Interface) *RollingUpdate // Inject real implementations. updater.scaleAndWait = updater.scaleAndWaitWithScaler updater.getOrCreateTargetController = updater.getOrCreateTargetControllerWithClient - updater.waitForReadyPods = updater.pollForReadyPods + updater.getReadyPods = updater.readyPods updater.cleanup = updater.cleanupWithClients return updater } @@ -194,18 +192,9 @@ func (r *RollingUpdater) Update(config *RollingUpdaterConfig) error { } oldRc = updated } - original, err := strconv.Atoi(oldRc.Annotations[originalReplicasAnnotation]) - if err != nil { - return fmt.Errorf("Unable to parse annotation for %s: %s=%s\n", - oldRc.Name, originalReplicasAnnotation, oldRc.Annotations[originalReplicasAnnotation]) - } - // The maximum pods which can go unavailable during the update. - maxUnavailable, err := extractMaxValue(config.MaxUnavailable, "maxUnavailable", desired) - if err != nil { - return err - } - // The maximum scaling increment. - maxSurge, err := extractMaxValue(config.MaxSurge, "maxSurge", desired) + // maxSurge is the maximum scaling increment and maxUnavailable are the maximum pods + // that can be unavailable during a rollout. + maxSurge, maxUnavailable, err := deployment.ResolveFenceposts(&config.MaxSurge, &config.MaxUnavailable, desired) if err != nil { return err } @@ -220,12 +209,12 @@ func (r *RollingUpdater) Update(config *RollingUpdaterConfig) error { // the effective scale of the old RC regardless of the configuration // (equivalent to 100% maxUnavailable). if desired == 0 { - maxUnavailable = original + maxUnavailable = oldRc.Spec.Replicas minAvailable = 0 } fmt.Fprintf(out, "Scaling up %s from %d to %d, scaling down %s from %d to 0 (keep %d pods available, don't exceed %d pods)\n", - newRc.Name, newRc.Spec.Replicas, desired, oldRc.Name, oldRc.Spec.Replicas, minAvailable, original+maxSurge) + newRc.Name, newRc.Spec.Replicas, desired, oldRc.Name, oldRc.Spec.Replicas, minAvailable, desired+maxSurge) // Scale newRc and oldRc until newRc has the desired number of replicas and // oldRc has 0 replicas. @@ -236,7 +225,7 @@ func (r *RollingUpdater) Update(config *RollingUpdaterConfig) error { oldReplicas := oldRc.Spec.Replicas // Scale up as much as possible. - scaledRc, err := r.scaleUp(newRc, oldRc, original, desired, maxSurge, maxUnavailable, scaleRetryParams, config) + scaledRc, err := r.scaleUp(newRc, oldRc, desired, maxSurge, maxUnavailable, scaleRetryParams, config) if err != nil { return err } @@ -269,14 +258,14 @@ func (r *RollingUpdater) Update(config *RollingUpdaterConfig) error { // scaleUp scales up newRc to desired by whatever increment is possible given // the configured surge threshold. scaleUp will safely no-op as necessary when // it detects redundancy or other relevant conditions. -func (r *RollingUpdater) scaleUp(newRc, oldRc *api.ReplicationController, original, desired, maxSurge, maxUnavailable int, scaleRetryParams *RetryParams, config *RollingUpdaterConfig) (*api.ReplicationController, error) { +func (r *RollingUpdater) scaleUp(newRc, oldRc *api.ReplicationController, desired, maxSurge, maxUnavailable int, scaleRetryParams *RetryParams, config *RollingUpdaterConfig) (*api.ReplicationController, error) { // If we're already at the desired, do nothing. if newRc.Spec.Replicas == desired { return newRc, nil } // Scale up as far as we can based on the surge limit. - increment := (original + maxSurge) - (oldRc.Spec.Replicas + newRc.Spec.Replicas) + increment := (desired + maxSurge) - (oldRc.Spec.Replicas + newRc.Spec.Replicas) // If the old is already scaled down, go ahead and scale all the way up. if oldRc.Spec.Replicas == 0 { increment = desired - newRc.Spec.Replicas @@ -299,7 +288,7 @@ func (r *RollingUpdater) scaleUp(newRc, oldRc *api.ReplicationController, origin return scaledRc, nil } -// scaleDown scales down oldRc to 0 at whatever increment possible given the +// scaleDown scales down oldRc to 0 at whatever decrement possible given the // thresholds defined on the config. scaleDown will safely no-op as necessary // when it detects redundancy or other relevant conditions. func (r *RollingUpdater) scaleDown(newRc, oldRc *api.ReplicationController, desired, minAvailable, maxUnavailable, maxSurge int, config *RollingUpdaterConfig) (*api.ReplicationController, error) { @@ -307,15 +296,19 @@ func (r *RollingUpdater) scaleDown(newRc, oldRc *api.ReplicationController, desi if oldRc.Spec.Replicas == 0 { return oldRc, nil } - // Block until there are any pods ready. - _, newAvailable, err := r.waitForReadyPods(config.Interval, config.Timeout, oldRc, newRc) + // Get ready pods. We shouldn't block, otherwise in case both old and new + // pods are unavailable then the rolling update process blocks. + // Timeout-wise we are already covered by the progress check. + _, newAvailable, err := r.getReadyPods(oldRc, newRc) if err != nil { return nil, err } // The old controller is considered as part of the total because we want to // maintain minimum availability even with a volatile old controller. // Scale down as much as possible while maintaining minimum availability - decrement := oldRc.Spec.Replicas + newAvailable - minAvailable + allPods := oldRc.Spec.Replicas + newRc.Spec.Replicas + newUnavailable := newRc.Spec.Replicas - newAvailable + decrement := allPods - minAvailable - newUnavailable // The decrement normally shouldn't drop below 0 because the available count // always starts below the old replica count, but the old replica count can // decrement due to externalities like pods death in the replica set. This @@ -360,40 +353,34 @@ func (r *RollingUpdater) scaleAndWaitWithScaler(rc *api.ReplicationController, r return r.c.ReplicationControllers(rc.Namespace).Get(rc.Name) } -// pollForReadyPods polls oldRc and newRc each interval and returns the old -// and new ready counts for their pods. If a pod is observed as being ready, -// it's considered ready even if it later becomes notReady. -func (r *RollingUpdater) pollForReadyPods(interval, timeout time.Duration, oldRc, newRc *api.ReplicationController) (int, int, error) { +// readyPods returns the old and new ready counts for their pods. +// If a pod is observed as being ready, it's considered ready even +// if it later becomes notReady. +func (r *RollingUpdater) readyPods(oldRc, newRc *api.ReplicationController) (int, int, error) { controllers := []*api.ReplicationController{oldRc, newRc} oldReady := 0 newReady := 0 - err := wait.Poll(interval, timeout, func() (done bool, err error) { - anyReady := false - for _, controller := range controllers { - selector := labels.Set(controller.Spec.Selector).AsSelector() - options := api.ListOptions{LabelSelector: selector} - pods, err := r.c.Pods(controller.Namespace).List(options) - if err != nil { - return false, err - } - for _, pod := range pods.Items { - if api.IsPodReady(&pod) { - switch controller.Name { - case oldRc.Name: - oldReady++ - case newRc.Name: - newReady++ - } - anyReady = true + + for i := range controllers { + controller := controllers[i] + selector := labels.Set(controller.Spec.Selector).AsSelector() + options := api.ListOptions{LabelSelector: selector} + pods, err := r.c.Pods(controller.Namespace).List(options) + if err != nil { + return 0, 0, err + } + for _, pod := range pods.Items { + if api.IsPodReady(&pod) { + switch controller.Name { + case oldRc.Name: + oldReady++ + case newRc.Name: + newReady++ } } } - if anyReady { - return true, nil - } - return false, nil - }) - return oldReady, newReady, err + } + return oldReady, newReady, nil } // getOrCreateTargetControllerWithClient looks for an existing controller with @@ -490,29 +477,6 @@ func (r *RollingUpdater) cleanupWithClients(oldRc, newRc *api.ReplicationControl } } -// func extractMaxValue is a helper to extract config max values as either -// absolute numbers or based on percentages of the given value. -func extractMaxValue(field intstr.IntOrString, name string, value int) (int, error) { - switch field.Type { - case intstr.Int: - if field.IntVal < 0 { - return 0, fmt.Errorf("%s must be >= 0", name) - } - return field.IntValue(), nil - case intstr.String: - s := strings.Replace(field.StrVal, "%", "", -1) - v, err := strconv.Atoi(s) - if err != nil { - return 0, fmt.Errorf("invalid %s value %q: %v", name, field.StrVal, err) - } - if v < 0 { - return 0, fmt.Errorf("%s must be >= 0", name) - } - return int(math.Ceil(float64(value) * (float64(v)) / 100)), nil - } - return 0, fmt.Errorf("invalid kind %q for %s", field.Type, name) -} - func Rename(c client.ReplicationControllersNamespacer, rc *api.ReplicationController, newName string) error { oldName := rc.Name rc.Name = newName diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/rolling_updater_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/rolling_updater_test.go index 4ba556847..727e66abc 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/rolling_updater_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/rolling_updater_test.go @@ -29,6 +29,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/client/restclient" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/fake" "k8s.io/kubernetes/pkg/client/unversioned/testclient" @@ -430,20 +431,21 @@ Scaling foo-v1 down to 0 Scaling foo-v2 up to 10 `, }, { - name: "1->1 10/0 fast readiness", + name: "1->1 25/25 maintain minimum availability", oldRc: oldRc(1, 1), newRc: newRc(0, 1), newRcExists: false, - maxUnavail: intstr.FromString("10%"), - maxSurge: intstr.FromString("0%"), + maxUnavail: intstr.FromString("25%"), + maxSurge: intstr.FromString("25%"), expected: []interface{}{ - down{oldReady: 1, newReady: 0, to: 0}, up{1}, + down{oldReady: 1, newReady: 0, noop: true}, + down{oldReady: 1, newReady: 1, to: 0}, }, output: `Created foo-v2 -Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 0 pods available, don't exceed 1 pods) -Scaling foo-v1 down to 0 +Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 1 pods available, don't exceed 2 pods) Scaling foo-v2 up to 1 +Scaling foo-v1 down to 0 `, }, { name: "1->1 0/10 delayed readiness", @@ -475,7 +477,7 @@ Scaling foo-v1 down to 0 down{oldReady: 1, newReady: 1, to: 0}, }, output: `Created foo-v2 -Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 0 pods available, don't exceed 2 pods) +Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 1 pods available, don't exceed 2 pods) Scaling foo-v2 up to 1 Scaling foo-v1 down to 0 `, @@ -539,7 +541,7 @@ Scaling foo-v1 down to 0 down{oldReady: 10, newReady: 20, to: 0}, }, output: `Created foo-v2 -Scaling up foo-v2 from 0 to 20, scaling down foo-v1 from 10 to 0 (keep 20 pods available, don't exceed 70 pods) +Scaling up foo-v2 from 0 to 20, scaling down foo-v1 from 10 to 0 (keep 20 pods available, don't exceed 80 pods) Scaling foo-v2 up to 20 Scaling foo-v1 down to 0 `, @@ -570,7 +572,7 @@ Scaling foo-v1 down to 0 down{oldReady: 3, newReady: 0, to: 0}, }, output: `Continuing update with existing controller foo-v2. -Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 3 to 0 (keep 0 pods available, don't exceed 4 pods) +Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 3 to 0 (keep 0 pods available, don't exceed 1 pods) Scaling foo-v1 down to 0 `, }, @@ -585,7 +587,7 @@ Scaling foo-v1 down to 0 down{oldReady: 3, newReady: 0, to: 0}, }, output: `Continuing update with existing controller foo-v2. -Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 3 to 0 (keep 0 pods available, don't exceed 3 pods) +Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 3 to 0 (keep 0 pods available, don't exceed 0 pods) Scaling foo-v1 down to 0 `, }, @@ -600,7 +602,7 @@ Scaling foo-v1 down to 0 down{oldReady: 3, newReady: 0, to: 0}, }, output: `Created foo-v2 -Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 3 to 0 (keep 0 pods available, don't exceed 3 pods) +Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 3 to 0 (keep 0 pods available, don't exceed 0 pods) Scaling foo-v1 down to 0 `, }, @@ -626,14 +628,113 @@ Scaling up foo-v2 from 0 to 0, scaling down foo-v1 from 0 to 0 (keep 0 pods avai maxSurge: intstr.FromInt(0), expected: []interface{}{ down{oldReady: 30, newReady: 0, to: 1}, + up{1}, + down{oldReady: 1, newReady: 2, to: 0}, + up{2}, + }, + output: `Created foo-v2 +Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 30 to 0 (keep 1 pods available, don't exceed 2 pods) +Scaling foo-v1 down to 1 +Scaling foo-v2 up to 1 +Scaling foo-v1 down to 0 +Scaling foo-v2 up to 2 +`, + }, + { + name: "2->2 1/0 blocked oldRc", + oldRc: oldRc(2, 2), + newRc: newRc(0, 2), + newRcExists: false, + maxUnavail: intstr.FromInt(1), + maxSurge: intstr.FromInt(0), + expected: []interface{}{ + down{oldReady: 1, newReady: 0, to: 1}, + up{1}, + down{oldReady: 1, newReady: 1, to: 0}, + up{2}, + }, + output: `Created foo-v2 +Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 2 to 0 (keep 1 pods available, don't exceed 2 pods) +Scaling foo-v1 down to 1 +Scaling foo-v2 up to 1 +Scaling foo-v1 down to 0 +Scaling foo-v2 up to 2 +`, + }, + { + name: "1->1 1/0 allow maxUnavailability", + oldRc: oldRc(1, 1), + newRc: newRc(0, 1), + newRcExists: false, + maxUnavail: intstr.FromString("1%"), + maxSurge: intstr.FromInt(0), + expected: []interface{}{ + down{oldReady: 1, newReady: 0, to: 0}, + up{1}, + }, + output: `Created foo-v2 +Scaling up foo-v2 from 0 to 1, scaling down foo-v1 from 1 to 0 (keep 0 pods available, don't exceed 1 pods) +Scaling foo-v1 down to 0 +Scaling foo-v2 up to 1 +`, + }, + { + name: "1->2 25/25 complex asymetric deployment", + oldRc: oldRc(1, 1), + newRc: newRc(0, 2), + newRcExists: false, + maxUnavail: intstr.FromString("25%"), + maxSurge: intstr.FromString("25%"), + expected: []interface{}{ up{2}, down{oldReady: 1, newReady: 2, to: 0}, }, output: `Created foo-v2 -Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 30 to 0 (keep 1 pods available, don't exceed 30 pods) +Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 1 to 0 (keep 2 pods available, don't exceed 3 pods) +Scaling foo-v2 up to 2 +Scaling foo-v1 down to 0 +`, + }, + { + name: "2->2 25/1 maxSurge trumps maxUnavailable", + oldRc: oldRc(2, 2), + newRc: newRc(0, 2), + newRcExists: false, + maxUnavail: intstr.FromString("25%"), + maxSurge: intstr.FromString("1%"), + expected: []interface{}{ + up{1}, + down{oldReady: 2, newReady: 1, to: 1}, + up{2}, + down{oldReady: 1, newReady: 2, to: 0}, + }, + output: `Created foo-v2 +Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 2 to 0 (keep 2 pods available, don't exceed 3 pods) +Scaling foo-v2 up to 1 Scaling foo-v1 down to 1 Scaling foo-v2 up to 2 Scaling foo-v1 down to 0 +`, + }, + { + name: "2->2 25/0 maxUnavailable resolves to zero, then one", + oldRc: oldRc(2, 2), + newRc: newRc(0, 2), + newRcExists: false, + maxUnavail: intstr.FromString("25%"), + maxSurge: intstr.FromString("0%"), + expected: []interface{}{ + down{oldReady: 2, newReady: 0, to: 1}, + up{1}, + down{oldReady: 1, newReady: 1, to: 0}, + up{2}, + }, + output: `Created foo-v2 +Scaling up foo-v2 from 0 to 2, scaling down foo-v1 from 2 to 0 (keep 1 pods available, don't exceed 2 pods) +Scaling foo-v1 down to 1 +Scaling foo-v2 up to 1 +Scaling foo-v1 down to 0 +Scaling foo-v2 up to 2 `, }, } @@ -685,10 +786,10 @@ Scaling foo-v1 down to 0 expected := -1 switch { case rc == test.newRc: - t.Logf("scaling up %s:%d", rc.Name, rc.Spec.Replicas) + t.Logf("scaling up %s to %d", rc.Name, rc.Spec.Replicas) expected = next(&upTo) case rc == test.oldRc: - t.Logf("scaling down %s:%d", rc.Name, rc.Spec.Replicas) + t.Logf("scaling down %s to %d", rc.Name, rc.Spec.Replicas) expected = next(&downTo) } if expected == -1 { @@ -709,13 +810,13 @@ Scaling foo-v1 down to 0 }, } // Set up a mock readiness check which handles the test assertions. - updater.waitForReadyPods = func(interval, timeout time.Duration, oldRc, newRc *api.ReplicationController) (int, int, error) { + updater.getReadyPods = func(oldRc, newRc *api.ReplicationController) (int, int, error) { // Return simulated readiness, and throw an error if this call has no // expectations defined. oldReady := next(&oldReady) newReady := next(&newReady) if oldReady == -1 || newReady == -1 { - t.Fatalf("unexpected waitForReadyPods call for:\noldRc: %+v\nnewRc: %+v", oldRc, newRc) + t.Fatalf("unexpected getReadyPods call for:\noldRc: %+v\nnewRc: %+v", oldRc, newRc) } return oldReady, newReady, nil } @@ -759,7 +860,7 @@ func TestUpdate_progressTimeout(t *testing.T) { return nil }, } - updater.waitForReadyPods = func(interval, timeout time.Duration, oldRc, newRc *api.ReplicationController) (int, int, error) { + updater.getReadyPods = func(oldRc, newRc *api.ReplicationController) (int, int, error) { // Coerce a timeout by pods never becoming ready. return 0, 0, nil } @@ -812,7 +913,7 @@ func TestUpdate_assignOriginalAnnotation(t *testing.T) { cleanup: func(oldRc, newRc *api.ReplicationController, config *RollingUpdaterConfig) error { return nil }, - waitForReadyPods: func(interval, timeout time.Duration, oldRc, newRc *api.ReplicationController) (int, int, error) { + getReadyPods: func(oldRc, newRc *api.ReplicationController) (int, int, error) { return 1, 1, nil }, } @@ -1328,7 +1429,7 @@ func TestUpdateWithRetries(t *testing.T) { } }), } - clientConfig := &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + clientConfig := &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} client := client.NewOrDie(clientConfig) client.Client = fakeClient.Client @@ -1425,7 +1526,7 @@ func TestAddDeploymentHash(t *testing.T) { } }), } - clientConfig := &client.Config{ContentConfig: client.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} + clientConfig := &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}} client := client.NewOrDie(clientConfig) client.Client = fakeClient.Client @@ -1442,7 +1543,7 @@ func TestAddDeploymentHash(t *testing.T) { } } -func TestRollingUpdater_pollForReadyPods(t *testing.T) { +func TestRollingUpdater_readyPods(t *testing.T) { mkpod := func(owner *api.ReplicationController, ready bool) *api.Pod { labels := map[string]string{} for k, v := range owner.Spec.Selector { @@ -1538,7 +1639,7 @@ func TestRollingUpdater_pollForReadyPods(t *testing.T) { ns: "default", c: client, } - oldReady, newReady, err := updater.pollForReadyPods(time.Millisecond, time.Second, test.oldRc, test.newRc) + oldReady, newReady, err := updater.readyPods(test.oldRc, test.newRc) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -1550,73 +1651,3 @@ func TestRollingUpdater_pollForReadyPods(t *testing.T) { } } } - -func TestRollingUpdater_extractMaxValue(t *testing.T) { - tests := []struct { - field intstr.IntOrString - original int - expected int - valid bool - }{ - { - field: intstr.FromInt(1), - original: 100, - expected: 1, - valid: true, - }, - { - field: intstr.FromInt(0), - original: 100, - expected: 0, - valid: true, - }, - { - field: intstr.FromInt(-1), - original: 100, - valid: false, - }, - { - field: intstr.FromString("10%"), - original: 100, - expected: 10, - valid: true, - }, - { - field: intstr.FromString("100%"), - original: 100, - expected: 100, - valid: true, - }, - { - field: intstr.FromString("200%"), - original: 100, - expected: 200, - valid: true, - }, - { - field: intstr.FromString("0%"), - original: 100, - expected: 0, - valid: true, - }, - { - field: intstr.FromString("-1%"), - original: 100, - valid: false, - }, - } - - for i, test := range tests { - t.Logf("evaluating test %d", i) - max, err := extractMaxValue(test.field, "field", test.original) - if test.valid && err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !test.valid && err == nil { - t.Fatalf("expected an error") - } - if e, a := test.expected, max; e != a { - t.Fatalf("expected max %d, got %d", e, a) - } - } -} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/run.go b/vendor/k8s.io/kubernetes/pkg/kubectl/run.go index e0ed8ada8..688b570e7 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/run.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/run.go @@ -24,6 +24,8 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" + batchv1 "k8s.io/kubernetes/pkg/apis/batch/v1" "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/validation" @@ -187,6 +189,24 @@ func getEnvs(genericParams map[string]interface{}) ([]api.EnvVar, error) { return envs, nil } +func getV1Envs(genericParams map[string]interface{}) ([]v1.EnvVar, error) { + var envs []v1.EnvVar + envStrings, found := genericParams["env"] + if found { + if envStringArray, isArray := envStrings.([]string); isArray { + var err error + envs, err = parseV1Envs(envStringArray) + if err != nil { + return nil, err + } + delete(genericParams, "env") + } else { + return nil, fmt.Errorf("expected []string, found: %v", envStrings) + } + } + return envs, nil +} + type JobV1Beta1 struct{} func (JobV1Beta1) ParamNames() []GeneratorParam { @@ -256,7 +276,7 @@ func (JobV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object restartPolicy := api.RestartPolicy(params["restart"]) if len(restartPolicy) == 0 { - restartPolicy = api.RestartPolicyAlways + restartPolicy = api.RestartPolicyNever } podSpec.RestartPolicy = restartPolicy @@ -269,6 +289,7 @@ func (JobV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object Selector: &unversioned.LabelSelector{ MatchLabels: labels, }, + ManualSelector: newBool(true), Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: labels, @@ -281,6 +302,97 @@ func (JobV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object return &job, nil } +type JobV1 struct{} + +func (JobV1) ParamNames() []GeneratorParam { + return []GeneratorParam{ + {"labels", false}, + {"default-name", false}, + {"name", true}, + {"image", true}, + {"port", false}, + {"hostport", false}, + {"stdin", false}, + {"leave-stdin-open", false}, + {"tty", false}, + {"command", false}, + {"args", false}, + {"env", false}, + {"requests", false}, + {"limits", false}, + {"restart", false}, + } +} + +func (JobV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) { + args, err := getArgs(genericParams) + if err != nil { + return nil, err + } + + envs, err := getV1Envs(genericParams) + if err != nil { + return nil, err + } + + params, err := getParams(genericParams) + if err != nil { + return nil, err + } + + name, err := getName(params) + if err != nil { + return nil, err + } + + labels, err := getLabels(params, true, name) + if err != nil { + return nil, err + } + + podSpec, err := makeV1PodSpec(params, name) + if err != nil { + return nil, err + } + + if err = updateV1PodContainers(params, args, envs, podSpec); err != nil { + return nil, err + } + + leaveStdinOpen, err := GetBool(params, "leave-stdin-open", false) + if err != nil { + return nil, err + } + podSpec.Containers[0].StdinOnce = !leaveStdinOpen && podSpec.Containers[0].Stdin + + if err := updateV1PodPorts(params, podSpec); err != nil { + return nil, err + } + + restartPolicy := v1.RestartPolicy(params["restart"]) + if len(restartPolicy) == 0 { + restartPolicy = v1.RestartPolicyNever + } + podSpec.RestartPolicy = restartPolicy + + job := batchv1.Job{ + ObjectMeta: v1.ObjectMeta{ + Name: name, + Labels: labels, + }, + Spec: batchv1.JobSpec{ + Template: v1.PodTemplateSpec{ + ObjectMeta: v1.ObjectMeta{ + Labels: labels, + }, + Spec: *podSpec, + }, + }, + } + + return &job, nil +} + type BasicReplicationController struct{} func (BasicReplicationController) ParamNames() []GeneratorParam { @@ -326,6 +438,30 @@ func populateResourceList(spec string) (api.ResourceList, error) { return result, nil } +// populateResourceList takes strings of form =,= +func populateV1ResourceList(spec string) (v1.ResourceList, error) { + // empty input gets a nil response to preserve generator test expected behaviors + if spec == "" { + return nil, nil + } + + result := v1.ResourceList{} + resourceStatements := strings.Split(spec, ",") + for _, resourceStatement := range resourceStatements { + parts := strings.Split(resourceStatement, "=") + if len(parts) != 2 { + return nil, fmt.Errorf("Invalid argument syntax %v, expected =", resourceStatement) + } + resourceName := v1.ResourceName(parts[0]) + resourceQuantity, err := resource.ParseQuantity(parts[1]) + if err != nil { + return nil, err + } + result[resourceName] = *resourceQuantity + } + return result, nil +} + // HandleResourceRequirements parses the limits and requests parameters if specified func HandleResourceRequirements(params map[string]string) (api.ResourceRequirements, error) { result := api.ResourceRequirements{} @@ -342,6 +478,22 @@ func HandleResourceRequirements(params map[string]string) (api.ResourceRequireme return result, nil } +// HandleResourceRequirements parses the limits and requests parameters if specified +func handleV1ResourceRequirements(params map[string]string) (v1.ResourceRequirements, error) { + result := v1.ResourceRequirements{} + limits, err := populateV1ResourceList(params["limits"]) + if err != nil { + return result, err + } + result.Limits = limits + requests, err := populateV1ResourceList(params["requests"]) + if err != nil { + return result, err + } + result.Requests = requests + return result, nil +} + func makePodSpec(params map[string]string, name string) (*api.PodSpec, error) { stdin, err := GetBool(params, "stdin", false) if err != nil { @@ -372,6 +524,36 @@ func makePodSpec(params map[string]string, name string) (*api.PodSpec, error) { return &spec, nil } +func makeV1PodSpec(params map[string]string, name string) (*v1.PodSpec, error) { + stdin, err := GetBool(params, "stdin", false) + if err != nil { + return nil, err + } + + tty, err := GetBool(params, "tty", false) + if err != nil { + return nil, err + } + + resourceRequirements, err := handleV1ResourceRequirements(params) + if err != nil { + return nil, err + } + + spec := v1.PodSpec{ + Containers: []v1.Container{ + { + Name: name, + Image: params["image"], + Stdin: stdin, + TTY: tty, + Resources: resourceRequirements, + }, + }, + } + return &spec, nil +} + func (BasicReplicationController) Generate(genericParams map[string]interface{}) (runtime.Object, error) { args, err := getArgs(genericParams) if err != nil { @@ -454,6 +636,25 @@ func updatePodContainers(params map[string]string, args []string, envs []api.Env return nil } +func updateV1PodContainers(params map[string]string, args []string, envs []v1.EnvVar, podSpec *v1.PodSpec) error { + if len(args) > 0 { + command, err := GetBool(params, "command", false) + if err != nil { + return err + } + if command { + podSpec.Containers[0].Command = args + } else { + podSpec.Containers[0].Args = args + } + } + + if len(envs) > 0 { + podSpec.Containers[0].Env = envs + } + return nil +} + func updatePodPorts(params map[string]string, podSpec *api.PodSpec) (err error) { port := -1 hostPort := -1 @@ -488,6 +689,40 @@ func updatePodPorts(params map[string]string, podSpec *api.PodSpec) (err error) return nil } +func updateV1PodPorts(params map[string]string, podSpec *v1.PodSpec) (err error) { + port := -1 + hostPort := -1 + if len(params["port"]) > 0 { + port, err = strconv.Atoi(params["port"]) + if err != nil { + return err + } + } + + if len(params["hostport"]) > 0 { + hostPort, err = strconv.Atoi(params["hostport"]) + if err != nil { + return err + } + if hostPort > 0 && port < 0 { + return fmt.Errorf("--hostport requires --port to be specified") + } + } + + // Don't include the port if it was not specified. + if port > 0 { + podSpec.Containers[0].Ports = []v1.ContainerPort{ + { + ContainerPort: int32(port), + }, + } + if hostPort > 0 { + podSpec.Containers[0].Ports[0].HostPort = int32(hostPort) + } + } + return nil +} + type BasicPod struct{} func (BasicPod) ParamNames() []GeneratorParam { @@ -607,3 +842,27 @@ func parseEnvs(envArray []string) ([]api.EnvVar, error) { } return envs, nil } + +func parseV1Envs(envArray []string) ([]v1.EnvVar, error) { + envs := []v1.EnvVar{} + for _, env := range envArray { + pos := strings.Index(env, "=") + if pos == -1 { + return nil, fmt.Errorf("invalid env: %v", env) + } + name := env[:pos] + value := env[pos+1:] + if len(name) == 0 || !validation.IsCIdentifier(name) || len(value) == 0 { + return nil, fmt.Errorf("invalid env: %v", env) + } + envVar := v1.EnvVar{Name: name, Value: value} + envs = append(envs, envVar) + } + return envs, nil +} + +func newBool(val bool) *bool { + p := new(bool) + *p = val + return p +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/run_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/run_test.go index 27a366c41..46ca5984e 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/run_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/run_test.go @@ -749,6 +749,7 @@ func TestGenerateJob(t *testing.T) { Selector: &unversioned.LabelSelector{ MatchLabels: map[string]string{"foo": "bar", "baz": "blah"}, }, + ManualSelector: newBool(true), Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: map[string]string{"foo": "bar", "baz": "blah"}, diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/scale.go b/vendor/k8s.io/kubernetes/pkg/kubectl/scale.go index 963f3050a..14951c028 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/scale.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/scale.go @@ -24,6 +24,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/util/wait" @@ -46,8 +47,8 @@ func ScalerFor(kind unversioned.GroupKind, c client.Interface) (Scaler, error) { return &ReplicationControllerScaler{c}, nil case extensions.Kind("ReplicaSet"): return &ReplicaSetScaler{c.Extensions()}, nil - case extensions.Kind("Job"): - return &JobScaler{c.Extensions()}, nil + case extensions.Kind("Job"), batch.Kind("Job"): + return &JobScaler{c.Extensions()}, nil // Either kind of job can be scaled with Extensions interface. case extensions.Kind("Deployment"): return &DeploymentScaler{c.Extensions()}, nil } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/secret.go b/vendor/k8s.io/kubernetes/pkg/kubectl/secret.go index 62de79008..e5b7cc33e 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/secret.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/secret.go @@ -17,7 +17,6 @@ limitations under the License. package kubectl import ( - "errors" "fmt" "io/ioutil" "os" @@ -206,33 +205,3 @@ func addKeyFromLiteralToSecret(secret *api.Secret, keyName string, data []byte) secret.Data[keyName] = data return nil } - -// parseFileSource parses the source given. Acceptable formats include: -// source-name=source-path, where source-name will become the key name and source-path is the path to the key file -// source-path, where source-path is a path to a file or directory, and key names will default to file names -// Key names cannot include '='. -func parseFileSource(source string) (keyName, filePath string, err error) { - numSeparators := strings.Count(source, "=") - switch { - case numSeparators == 0: - return path.Base(source), source, nil - case numSeparators == 1 && strings.HasPrefix(source, "="): - return "", "", fmt.Errorf("key name for file path %v missing.", strings.TrimPrefix(source, "=")) - case numSeparators == 1 && strings.HasSuffix(source, "="): - return "", "", fmt.Errorf("file path for key name %v missing.", strings.TrimSuffix(source, "=")) - case numSeparators > 1: - return "", "", errors.New("Key names or file paths cannot contain '='.") - default: - components := strings.Split(source, "=") - return components[0], components[1], nil - } -} - -// parseLiteralSource parses the source key=val pair -func parseLiteralSource(source string) (keyName, value string, err error) { - items := strings.Split(source, "=") - if len(items) != 2 { - return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source) - } - return items[0], items[1], nil -} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/serviceaccount.go b/vendor/k8s.io/kubernetes/pkg/kubectl/serviceaccount.go new file mode 100644 index 000000000..2be08dd2d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/serviceaccount.go @@ -0,0 +1,51 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 kubectl + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/runtime" +) + +// ServiceAccountGeneratorV1 supports stable generation of a service account +type ServiceAccountGeneratorV1 struct { + // Name of service account + Name string +} + +// Ensure it supports the generator pattern that uses parameters specified during construction +var _ StructuredGenerator = &ServiceAccountGeneratorV1{} + +// StructuredGenerate outputs a service account object using the configured fields +func (g *ServiceAccountGeneratorV1) StructuredGenerate() (runtime.Object, error) { + if err := g.validate(); err != nil { + return nil, err + } + serviceAccount := &api.ServiceAccount{} + serviceAccount.Name = g.Name + return serviceAccount, nil +} + +// validate validates required fields are set to support structured generation +func (g *ServiceAccountGeneratorV1) validate() error { + if len(g.Name) == 0 { + return fmt.Errorf("name must be specified") + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/serviceaccount_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/serviceaccount_test.go new file mode 100644 index 000000000..ca000f85f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/serviceaccount_test.go @@ -0,0 +1,60 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 kubectl + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" +) + +func TestServiceAccountGenerate(t *testing.T) { + tests := []struct { + name string + expected *api.ServiceAccount + expectErr bool + }{ + { + name: "foo", + expected: &api.ServiceAccount{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + }, + }, + expectErr: false, + }, + { + expectErr: true, + }, + } + for _, test := range tests { + generator := ServiceAccountGeneratorV1{ + Name: test.name, + } + obj, err := generator.StructuredGenerate() + if !test.expectErr && err != nil { + t.Errorf("unexpected error: %v", err) + } + if test.expectErr && err != nil { + continue + } + if !reflect.DeepEqual(obj.(*api.ServiceAccount), test.expected) { + t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*api.ServiceAccount)) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/sorted_resource_name_list.go b/vendor/k8s.io/kubernetes/pkg/kubectl/sorted_resource_name_list.go index 6df122eac..ffaa08ee4 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/sorted_resource_name_list.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/sorted_resource_name_list.go @@ -33,3 +33,17 @@ func (list SortableResourceNames) Swap(i, j int) { func (list SortableResourceNames) Less(i, j int) bool { return list[i] < list[j] } + +type SortableResourceQuotas []api.ResourceQuota + +func (list SortableResourceQuotas) Len() int { + return len(list) +} + +func (list SortableResourceQuotas) Swap(i, j int) { + list[i], list[j] = list[j], list[i] +} + +func (list SortableResourceQuotas) Less(i, j int) bool { + return list[i].Name < list[j].Name +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/sorting_printer.go b/vendor/k8s.io/kubernetes/pkg/kubectl/sorting_printer.go index 49e36b882..aefc2a96d 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/sorting_printer.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/sorting_printer.go @@ -97,7 +97,7 @@ func SortObjects(decoder runtime.Decoder, objs []runtime.Object, fieldInput stri switch u := item.(type) { case *runtime.Unknown: var err error - if objs[ix], _, err = decoder.Decode(u.RawJSON, nil, nil); err != nil { + if objs[ix], _, err = decoder.Decode(u.Raw, nil, nil); err != nil { return nil, err } } diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/sorting_printer_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/sorting_printer_test.go index f1d1d0c65..4839159a3 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/sorting_printer_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/sorting_printer_test.go @@ -190,16 +190,16 @@ func TestSortingPrinter(t *testing.T) { name: "v1.List in order", obj: &api.List{ Items: []runtime.RawExtension{ - {RawJSON: encodeOrDie(a)}, - {RawJSON: encodeOrDie(b)}, - {RawJSON: encodeOrDie(c)}, + {Raw: encodeOrDie(a)}, + {Raw: encodeOrDie(b)}, + {Raw: encodeOrDie(c)}, }, }, sort: &api.List{ Items: []runtime.RawExtension{ - {RawJSON: encodeOrDie(a)}, - {RawJSON: encodeOrDie(b)}, - {RawJSON: encodeOrDie(c)}, + {Raw: encodeOrDie(a)}, + {Raw: encodeOrDie(b)}, + {Raw: encodeOrDie(c)}, }, }, field: "{.metadata.name}", @@ -208,16 +208,16 @@ func TestSortingPrinter(t *testing.T) { name: "v1.List in reverse", obj: &api.List{ Items: []runtime.RawExtension{ - {RawJSON: encodeOrDie(c)}, - {RawJSON: encodeOrDie(b)}, - {RawJSON: encodeOrDie(a)}, + {Raw: encodeOrDie(c)}, + {Raw: encodeOrDie(b)}, + {Raw: encodeOrDie(a)}, }, }, sort: &api.List{ Items: []runtime.RawExtension{ - {RawJSON: encodeOrDie(a)}, - {RawJSON: encodeOrDie(b)}, - {RawJSON: encodeOrDie(c)}, + {Raw: encodeOrDie(a)}, + {Raw: encodeOrDie(b)}, + {Raw: encodeOrDie(c)}, }, }, field: "{.metadata.name}", diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/stop.go b/vendor/k8s.io/kubernetes/pkg/kubectl/stop.go index cf7cb1a9e..9112decf7 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/stop.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/stop.go @@ -25,10 +25,12 @@ import ( "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/util" + deploymentutil "k8s.io/kubernetes/pkg/util/deployment" utilerrors "k8s.io/kubernetes/pkg/util/errors" "k8s.io/kubernetes/pkg/util/wait" ) @@ -40,7 +42,8 @@ const ( // A Reaper handles terminating an object as gracefully as possible. // timeout is how long we'll wait for the termination to be successful -// gracePeriod is time given to an API object for it to delete itself cleanly (e.g. pod shutdown) +// gracePeriod is time given to an API object for it to delete itself cleanly, +// e.g., pod shutdown. It may or may not be supported by the API object. type Reaper interface { Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error } @@ -75,7 +78,7 @@ func ReaperFor(kind unversioned.GroupKind, c client.Interface) (Reaper, error) { case api.Kind("Service"): return &ServiceReaper{c}, nil - case extensions.Kind("Job"): + case extensions.Kind("Job"), batch.Kind("Job"): return &JobReaper{c, Interval, Timeout}, nil case extensions.Kind("Deployment"): @@ -270,7 +273,7 @@ func (reaper *ReplicaSetReaper) Stop(namespace, name string, timeout time.Durati } } - if err := rsc.Delete(name, gracePeriod); err != nil { + if err := rsc.Delete(name, nil); err != nil { return err } return nil @@ -353,7 +356,7 @@ func (reaper *JobReaper) Stop(namespace, name string, timeout time.Duration, gra return utilerrors.NewAggregate(errList) } // once we have all the pods removed we can safely remove the job itself - return jobs.Delete(name, gracePeriod) + return jobs.Delete(name, nil) } func (reaper *DeploymentReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error { @@ -361,65 +364,30 @@ func (reaper *DeploymentReaper) Stop(namespace, name string, timeout time.Durati replicaSets := reaper.Extensions().ReplicaSets(namespace) rsReaper, _ := ReaperFor(extensions.Kind("ReplicaSet"), reaper) - deployment, err := deployments.Get(name) + deployment, err := reaper.updateDeploymentWithRetries(namespace, name, func(d *extensions.Deployment) { + // set deployment's history and scale to 0 + // TODO replace with patch when available: https://github.com/kubernetes/kubernetes/issues/20527 + d.Spec.RevisionHistoryLimit = util.IntPtr(0) + d.Spec.Replicas = 0 + d.Spec.Paused = true + }) if err != nil { return err } - // set deployment's history and scale to 0 - // TODO replace with patch when available: https://github.com/kubernetes/kubernetes/issues/20527 - zero := 0 - deployment.Spec.RevisionHistoryLimit = &zero - deployment.Spec.Replicas = 0 - // TODO: un-pausing should not be necessary, remove when this is fixed: - // https://github.com/kubernetes/kubernetes/issues/20966 - // Instead deployment should be Paused at this point and not at next TODO. - deployment.Spec.Paused = false - deployment, err = deployments.Update(deployment) - if err != nil { + // Use observedGeneration to determine if the deployment controller noticed the pause. + if err := deploymentutil.WaitForObservedDeployment(func() (*extensions.Deployment, error) { + return deployments.Get(name) + }, deployment.Generation, 10*time.Millisecond, 1*time.Minute); err != nil { return err } - // wait for total no of pods drop to 0 - if err := wait.Poll(reaper.pollInterval, reaper.timeout, func() (bool, error) { - curr, err := deployments.Get(name) - // if deployment was not found it must have been deleted, error out - if err != nil && errors.IsNotFound(err) { - return false, err - } - // if other errors happen, retry - if err != nil { - return false, nil - } - // check if deployment wasn't recreated with the same name - // TODO use generations when deployment will have them - if curr.UID != deployment.UID { - return false, errors.NewNotFound(extensions.Resource("Deployment"), name) - } - return curr.Status.Replicas == 0, nil - }); err != nil { - return err - } - - // TODO: When deployments will allow running cleanup policy while being - // paused, move pausing to above update operation. Without it, we need to - // pause deployment before stopping RSs, to prevent creating new RSs. - // See https://github.com/kubernetes/kubernetes/issues/20966 - deployment, err = deployments.Get(name) - if err != nil { - return err - } - deployment.Spec.Paused = true - deployment, err = deployments.Update(deployment) - if err != nil { - return err - } - - // remove remaining RSs + // Stop all replica sets. selector, err := unversioned.LabelSelectorAsSelector(deployment.Spec.Selector) if err != nil { return err } + options := api.ListOptions{LabelSelector: selector} rsList, err := replicaSets.List(options) if err != nil { @@ -437,8 +405,27 @@ func (reaper *DeploymentReaper) Stop(namespace, name string, timeout time.Durati return utilerrors.NewAggregate(errList) } - // and finally deployment - return deployments.Delete(name, gracePeriod) + // Delete deployment at the end. + // Note: We delete deployment at the end so that if removing RSs fails, we atleast have the deployment to retry. + return deployments.Delete(name, nil) +} + +type updateDeploymentFunc func(d *extensions.Deployment) + +func (reaper *DeploymentReaper) updateDeploymentWithRetries(namespace, name string, applyUpdate updateDeploymentFunc) (deployment *extensions.Deployment, err error) { + deployments := reaper.Extensions().Deployments(namespace) + err = wait.Poll(10*time.Millisecond, 1*time.Minute, func() (bool, error) { + if deployment, err = deployments.Get(name); err != nil { + return false, err + } + // Apply the update, then attempt to push it to the apiserver. + applyUpdate(deployment) + if deployment, err = deployments.Update(deployment); err == nil { + return true, nil + } + return false, nil + }) + return deployment, err } func (reaper *PodReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error { diff --git a/vendor/k8s.io/kubernetes/pkg/kubectl/stop_test.go b/vendor/k8s.io/kubernetes/pkg/kubectl/stop_test.go index 32a5f52d0..9274ab408 100644 --- a/vendor/k8s.io/kubernetes/pkg/kubectl/stop_test.go +++ b/vendor/k8s.io/kubernetes/pkg/kubectl/stop_test.go @@ -512,7 +512,7 @@ func TestDeploymentStop(t *testing.T) { Replicas: 0, }, } - template := deploymentutil.GetNewReplicaSetTemplate(deployment) + template := deploymentutil.GetNewReplicaSetTemplate(&deployment) tests := []struct { Name string Objs []runtime.Object @@ -538,8 +538,7 @@ func TestDeploymentStop(t *testing.T) { }, StopError: nil, ExpectedActions: []string{"get:deployments", "update:deployments", - "get:deployments", "get:deployments", "update:deployments", - "list:replicasets", "delete:deployments"}, + "get:deployments", "list:replicasets", "delete:deployments"}, }, { Name: "Deployment with single replicaset", @@ -553,7 +552,7 @@ func TestDeploymentStop(t *testing.T) { Namespace: ns, }, Spec: extensions.ReplicaSetSpec{ - Template: &template, + Template: template, }, }, }, @@ -561,10 +560,9 @@ func TestDeploymentStop(t *testing.T) { }, StopError: nil, ExpectedActions: []string{"get:deployments", "update:deployments", - "get:deployments", "get:deployments", "update:deployments", - "list:replicasets", "get:replicasets", "get:replicasets", - "update:replicasets", "get:replicasets", "get:replicasets", - "delete:replicasets", "delete:deployments"}, + "get:deployments", "list:replicasets", "get:replicasets", + "get:replicasets", "update:replicasets", "get:replicasets", + "get:replicasets", "delete:replicasets", "delete:deployments"}, }, } diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/OWNERS b/vendor/k8s.io/kubernetes/pkg/kubelet/OWNERS new file mode 100644 index 000000000..c0b8d3ac6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/OWNERS @@ -0,0 +1,4 @@ +assignees: + - dchen1107 + - vishh + - yujuhong diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats/types.go b/vendor/k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats/types.go new file mode 100644 index 000000000..ce3144685 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats/types.go @@ -0,0 +1,202 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 stats + +import ( + "k8s.io/kubernetes/pkg/api/unversioned" +) + +// Summary is a top-level container for holding NodeStats and PodStats. +type Summary struct { + // Overall node stats. + Node NodeStats `json:"node"` + // Per-pod stats. + Pods []PodStats `json:"pods"` +} + +// NodeStats holds node-level unprocessed sample stats. +type NodeStats struct { + // Reference to the measured Node. + NodeName string `json:"nodeName"` + // Stats of system daemons tracked as raw containers. + // The system containers are named according to the SystemContainer* constants. + SystemContainers []ContainerStats `json:"systemContainers,omitempty" patchStrategy:"merge" patchMergeKey:"name"` + // The time at which data collection for the node-scoped (i.e. aggregate) stats was (re)started. + StartTime unversioned.Time `json:"startTime"` + // Stats pertaining to CPU resources. + CPU *CPUStats `json:"cpu,omitempty"` + // Stats pertaining to memory (RAM) resources. + Memory *MemoryStats `json:"memory,omitempty"` + // Stats pertaining to network resources. + Network *NetworkStats `json:"network,omitempty"` + // Stats pertaining to total usage of filesystem resources on the rootfs used by node k8s components. + // NodeFs.Used is the total bytes used on the filesystem. + Fs *FsStats `json:"fs,omitempty"` +} + +const ( + // Container name for the system container tracking Kubelet usage. + SystemContainerKubelet = "kubelet" + // Container name for the system container tracking the runtime (e.g. docker or rkt) usage. + SystemContainerRuntime = "runtime" + // Container name for the system container tracking non-kubernetes processes. + SystemContainerMisc = "misc" +) + +// PodStats holds pod-level unprocessed sample stats. +type PodStats struct { + // Reference to the measured Pod. + PodRef PodReference `json:"podRef"` + // The time at which data collection for the pod-scoped (e.g. network) stats was (re)started. + StartTime unversioned.Time `json:"startTime"` + // Stats of containers in the measured pod. + Containers []ContainerStats `json:"containers" patchStrategy:"merge" patchMergeKey:"name"` + // Stats pertaining to network resources. + Network *NetworkStats `json:"network,omitempty"` + // Stats pertaining to volume usage of filesystem resources. + // VolumeStats.UsedBytes is the number of bytes used by the Volume + VolumeStats []VolumeStats `json:"volume,omitempty" patchStrategy:"merge" patchMergeKey:"name"` +} + +// ContainerStats holds container-level unprocessed sample stats. +type ContainerStats struct { + // Reference to the measured container. + Name string `json:"name"` + // The time at which data collection for this container was (re)started. + StartTime unversioned.Time `json:"startTime"` + // Stats pertaining to CPU resources. + CPU *CPUStats `json:"cpu,omitempty"` + // Stats pertaining to memory (RAM) resources. + Memory *MemoryStats `json:"memory,omitempty"` + // Stats pertaining to container rootfs usage of filesystem resources. + // Rootfs.UsedBytes is the number of bytes used for the container write layer. + Rootfs *FsStats `json:"rootfs,omitempty"` + // Stats pertaining to container logs usage of filesystem resources. + // Logs.UsedBytes is the number of bytes used for the container logs. + Logs *FsStats `json:"logs,omitempty"` + // User defined metrics that are exposed by containers in the pod. Typically, we expect only one container in the pod to be exposing user defined metrics. In the event of multiple containers exposing metrics, they will be combined here. + UserDefinedMetrics []UserDefinedMetric `json:"userDefinedMetrics,omitmepty" patchStrategy:"merge" patchMergeKey:"name"` +} + +// PodReference contains enough information to locate the referenced pod. +type PodReference struct { + Name string `json:"name"` + Namespace string `json:"namespace"` + UID string `json:"uid"` +} + +// NetworkStats contains data about network resources. +type NetworkStats struct { + // The time at which these stats were updated. + Time unversioned.Time `json:"time"` + // Cumulative count of bytes received. + RxBytes *uint64 `json:"rxBytes,omitempty"` + // Cumulative count of receive errors encountered. + RxErrors *uint64 `json:"rxErrors,omitempty"` + // Cumulative count of bytes transmitted. + TxBytes *uint64 `json:"txBytes,omitempty"` + // Cumulative count of transmit errors encountered. + TxErrors *uint64 `json:"txErrors,omitempty"` +} + +// CPUStats contains data about CPU usage. +type CPUStats struct { + // The time at which these stats were updated. + Time unversioned.Time `json:"time"` + // Total CPU usage (sum of all cores) averaged over the sample window. + // The "core" unit can be interpreted as CPU core-nanoseconds per second. + UsageNanoCores *uint64 `json:"usageNanoCores,omitempty"` + // Cumulative CPU usage (sum of all cores) since object creation. + UsageCoreNanoSeconds *uint64 `json:"usageCoreNanoSeconds,omitempty"` +} + +// MemoryStats contains data about memory usage. +type MemoryStats struct { + // The time at which these stats were updated. + Time unversioned.Time `json:"time"` + // Total memory in use. This includes all memory regardless of when it was accessed. + UsageBytes *uint64 `json:"usageBytes,omitempty"` + // The amount of working set memory. This includes recently accessed memory, + // dirty memory, and kernel memory. UsageBytes is <= TotalBytes. + WorkingSetBytes *uint64 `json:"workingSetBytes,omitempty"` + // The amount of anonymous and swap cache memory (includes transparent + // hugepages). + RSSBytes *uint64 `json:"rssBytes,omitempty"` + // Cumulative number of minor page faults. + PageFaults *uint64 `json:"pageFaults,omitempty"` + // Cumulative number of major page faults. + MajorPageFaults *uint64 `json:"majorPageFaults,omitempty"` +} + +// VolumeStats contains data about Volume filesystem usage. +type VolumeStats struct { + // Embedded FsStats + FsStats + // Name is the name given to the Volume + Name string `json:"name,omitempty"` +} + +// FsStats contains data about filesystem usage. +type FsStats struct { + // AvailableBytes represents the storage space available (bytes) for the filesystem. + AvailableBytes *uint64 `json:"availableBytes,omitempty"` + // CapacityBytes represents the total capacity (bytes) of the filesystems underlying storage. + CapacityBytes *uint64 `json:"capacityBytes,omitempty"` + // UsedBytes represents the bytes used for a specific task on the filesystem. + // This may differ from the total bytes used on the filesystem and may not equal CapacityBytes - AvailableBytes. + // e.g. For ContainerStats.Rootfs this is the bytes used by the container rootfs on the filesystem. + UsedBytes *uint64 `json:"usedBytes,omitempty"` +} + +// UserDefinedMetricType defines how the metric should be interpreted by the user. +type UserDefinedMetricType string + +const ( + // Instantaneous value. May increase or decrease. + MetricGauge UserDefinedMetricType = "gauge" + + // A counter-like value that is only expected to increase. + MetricCumulative UserDefinedMetricType = "cumulative" + + // Rate over a time period. + MetricDelta UserDefinedMetricType = "delta" +) + +// UserDefinedMetricDescriptor contains metadata that describes a user defined metric. +type UserDefinedMetricDescriptor struct { + // The name of the metric. + Name string `json:"name"` + + // Type of the metric. + Type UserDefinedMetricType `json:"type"` + + // Display Units for the stats. + Units string `json:"units"` + + // Metadata labels associated with this metric. + Labels map[string]string `json:"labels,omitempty"` +} + +// UserDefinedMetric represents a metric defined and generate by users. +type UserDefinedMetric struct { + UserDefinedMetricDescriptor `json:",inline"` + // The time at which these stats were updated. + Time unversioned.Time `json:"time"` + // Value of the metric. Float64s have 53 bit precision. + // We do not forsee any metrics exceeding that value. + Value float64 `json:"value"` +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/cadvisor_linux.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/cadvisor_linux.go new file mode 100644 index 000000000..f7691b638 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/cadvisor_linux.go @@ -0,0 +1,192 @@ +// +build cgo,linux + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 cadvisor + +import ( + "flag" + "fmt" + "net/http" + "regexp" + "time" + + "github.com/golang/glog" + "github.com/google/cadvisor/cache/memory" + cadvisorMetrics "github.com/google/cadvisor/container" + "github.com/google/cadvisor/events" + cadvisorfs "github.com/google/cadvisor/fs" + cadvisorhttp "github.com/google/cadvisor/http" + cadvisorapi "github.com/google/cadvisor/info/v1" + cadvisorapiv2 "github.com/google/cadvisor/info/v2" + "github.com/google/cadvisor/manager" + "github.com/google/cadvisor/utils/sysfs" + "k8s.io/kubernetes/pkg/util/runtime" +) + +type cadvisorClient struct { + manager.Manager +} + +var _ Interface = new(cadvisorClient) + +// TODO(vmarmol): Make configurable. +// The amount of time for which to keep stats in memory. +const statsCacheDuration = 2 * time.Minute +const maxHousekeepingInterval = 15 * time.Second +const defaultHousekeepingInterval = 10 * time.Second +const allowDynamicHousekeeping = true + +func init() { + // Override the default cAdvisor housekeeping interval. + if f := flag.Lookup("housekeeping_interval"); f != nil { + f.DefValue = defaultHousekeepingInterval.String() + f.Value.Set(f.DefValue) + } +} + +// Creates a cAdvisor and exports its API on the specified port if port > 0. +func New(port uint) (Interface, error) { + sysFs, err := sysfs.NewRealSysFs() + if err != nil { + return nil, err + } + + // Create and start the cAdvisor container manager. + m, err := manager.New(memory.New(statsCacheDuration, nil), sysFs, maxHousekeepingInterval, allowDynamicHousekeeping, cadvisorMetrics.MetricSet{cadvisorMetrics.NetworkTcpUsageMetrics: struct{}{}}) + if err != nil { + return nil, err + } + + cadvisorClient := &cadvisorClient{ + Manager: m, + } + + err = cadvisorClient.exportHTTP(port) + if err != nil { + return nil, err + } + return cadvisorClient, nil +} + +func (cc *cadvisorClient) Start() error { + return cc.Manager.Start() +} + +func (cc *cadvisorClient) exportHTTP(port uint) error { + // Register the handlers regardless as this registers the prometheus + // collector properly. + mux := http.NewServeMux() + err := cadvisorhttp.RegisterHandlers(mux, cc, "", "", "", "") + if err != nil { + return err + } + + re := regexp.MustCompile(`^k8s_(?P[^_\.]+)[^_]+_(?P[^_]+)_(?P[^_]+)`) + reCaptureNames := re.SubexpNames() + cadvisorhttp.RegisterPrometheusHandler(mux, cc, "/metrics", func(name string) map[string]string { + extraLabels := map[string]string{} + matches := re.FindStringSubmatch(name) + for i, match := range matches { + if len(reCaptureNames[i]) > 0 { + extraLabels[re.SubexpNames()[i]] = match + } + } + return extraLabels + }) + + // Only start the http server if port > 0 + if port > 0 { + serv := &http.Server{ + Addr: fmt.Sprintf(":%d", port), + Handler: mux, + } + + // TODO(vmarmol): Remove this when the cAdvisor port is once again free. + // If export failed, retry in the background until we are able to bind. + // This allows an existing cAdvisor to be killed before this one registers. + go func() { + defer runtime.HandleCrash() + + err := serv.ListenAndServe() + for err != nil { + glog.Infof("Failed to register cAdvisor on port %d, retrying. Error: %v", port, err) + time.Sleep(time.Minute) + err = serv.ListenAndServe() + } + }() + } + + return nil +} + +func (cc *cadvisorClient) ContainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) { + return cc.GetContainerInfo(name, req) +} + +func (cc *cadvisorClient) ContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) { + return cc.GetContainerInfoV2(name, options) +} + +func (cc *cadvisorClient) VersionInfo() (*cadvisorapi.VersionInfo, error) { + return cc.GetVersionInfo() +} + +func (cc *cadvisorClient) SubcontainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) { + infos, err := cc.SubcontainersInfo(name, req) + if err != nil { + return nil, err + } + + result := make(map[string]*cadvisorapi.ContainerInfo, len(infos)) + for _, info := range infos { + result[info.Name] = info + } + return result, nil +} + +func (cc *cadvisorClient) MachineInfo() (*cadvisorapi.MachineInfo, error) { + return cc.GetMachineInfo() +} + +func (cc *cadvisorClient) DockerImagesFsInfo() (cadvisorapiv2.FsInfo, error) { + return cc.getFsInfo(cadvisorfs.LabelDockerImages) +} + +func (cc *cadvisorClient) RootFsInfo() (cadvisorapiv2.FsInfo, error) { + return cc.getFsInfo(cadvisorfs.LabelSystemRoot) +} + +func (cc *cadvisorClient) getFsInfo(label string) (cadvisorapiv2.FsInfo, error) { + res, err := cc.GetFsInfo(label) + if err != nil { + return cadvisorapiv2.FsInfo{}, err + } + if len(res) == 0 { + return cadvisorapiv2.FsInfo{}, fmt.Errorf("failed to find information for the filesystem labeled %q", label) + } + // TODO(vmarmol): Handle this better when a label has more than one image filesystem. + if len(res) > 1 { + glog.Warningf("More than one filesystem labeled %q: %#v. Only using the first one", label, res) + } + + return res[0], nil +} + +func (cc *cadvisorClient) WatchEvents(request *events.Request) (*events.EventChannel, error) { + return cc.WatchForEvents(request) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/cadvisor_unsupported.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/cadvisor_unsupported.go new file mode 100644 index 000000000..e0bcd4d6d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/cadvisor_unsupported.go @@ -0,0 +1,78 @@ +// +build !cgo !linux + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 cadvisor + +import ( + "errors" + + "github.com/google/cadvisor/events" + cadvisorapi "github.com/google/cadvisor/info/v1" + cadvisorapiv2 "github.com/google/cadvisor/info/v2" +) + +type cadvisorUnsupported struct { +} + +var _ Interface = new(cadvisorUnsupported) + +func New(port uint) (Interface, error) { + return &cadvisorUnsupported{}, nil +} + +var unsupportedErr = errors.New("cAdvisor is unsupported in this build") + +func (cu *cadvisorUnsupported) Start() error { + return unsupportedErr +} + +func (cu *cadvisorUnsupported) DockerContainer(name string, req *cadvisorapi.ContainerInfoRequest) (cadvisorapi.ContainerInfo, error) { + return cadvisorapi.ContainerInfo{}, unsupportedErr +} + +func (cu *cadvisorUnsupported) ContainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) { + return nil, unsupportedErr +} + +func (cu *cadvisorUnsupported) ContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) { + return nil, unsupportedErr +} + +func (cu *cadvisorUnsupported) SubcontainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) { + return nil, unsupportedErr +} + +func (cu *cadvisorUnsupported) MachineInfo() (*cadvisorapi.MachineInfo, error) { + return nil, unsupportedErr +} + +func (cu *cadvisorUnsupported) VersionInfo() (*cadvisorapi.VersionInfo, error) { + return nil, unsupportedErr +} + +func (cu *cadvisorUnsupported) DockerImagesFsInfo() (cadvisorapiv2.FsInfo, error) { + return cadvisorapiv2.FsInfo{}, unsupportedErr +} + +func (cu *cadvisorUnsupported) RootFsInfo() (cadvisorapiv2.FsInfo, error) { + return cadvisorapiv2.FsInfo{}, unsupportedErr +} + +func (cu *cadvisorUnsupported) WatchEvents(request *events.Request) (*events.EventChannel, error) { + return nil, unsupportedErr +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/doc.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/doc.go new file mode 100644 index 000000000..8e1c076c6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// Kubelet interactions with cAdvisor. +package cadvisor diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/testing/cadvisor_fake.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/testing/cadvisor_fake.go new file mode 100644 index 000000000..d29dea2d8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/testing/cadvisor_fake.go @@ -0,0 +1,75 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 testing + +import ( + "github.com/google/cadvisor/events" + cadvisorapi "github.com/google/cadvisor/info/v1" + cadvisorapiv2 "github.com/google/cadvisor/info/v2" + "k8s.io/kubernetes/pkg/kubelet/cadvisor" +) + +// Fake cAdvisor implementation. +type Fake struct { +} + +var _ cadvisor.Interface = new(Fake) + +func (c *Fake) Start() error { + return nil +} + +func (c *Fake) ContainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) { + return new(cadvisorapi.ContainerInfo), nil +} + +func (c *Fake) ContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) { + return map[string]cadvisorapiv2.ContainerInfo{}, nil +} + +func (c *Fake) SubcontainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) { + return map[string]*cadvisorapi.ContainerInfo{}, nil +} + +func (c *Fake) DockerContainer(name string, req *cadvisorapi.ContainerInfoRequest) (cadvisorapi.ContainerInfo, error) { + return cadvisorapi.ContainerInfo{}, nil +} + +func (c *Fake) MachineInfo() (*cadvisorapi.MachineInfo, error) { + // Simulate a matchin with 1 core and 3.75GB of memory. + // We set it to non-zero values to make non-zero-capacity machines in Kubemark. + return &cadvisorapi.MachineInfo{ + NumCores: 1, + MemoryCapacity: 4026531840, + }, nil +} + +func (c *Fake) VersionInfo() (*cadvisorapi.VersionInfo, error) { + return new(cadvisorapi.VersionInfo), nil +} + +func (c *Fake) DockerImagesFsInfo() (cadvisorapiv2.FsInfo, error) { + return cadvisorapiv2.FsInfo{}, nil +} + +func (c *Fake) RootFsInfo() (cadvisorapiv2.FsInfo, error) { + return cadvisorapiv2.FsInfo{}, nil +} + +func (c *Fake) WatchEvents(request *events.Request) (*events.EventChannel, error) { + return new(events.EventChannel), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/testing/cadvisor_mock.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/testing/cadvisor_mock.go new file mode 100644 index 000000000..37671afa8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/testing/cadvisor_mock.go @@ -0,0 +1,85 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 testing + +import ( + "github.com/google/cadvisor/events" + cadvisorapi "github.com/google/cadvisor/info/v1" + cadvisorapiv2 "github.com/google/cadvisor/info/v2" + "github.com/stretchr/testify/mock" + "k8s.io/kubernetes/pkg/kubelet/cadvisor" +) + +type Mock struct { + mock.Mock +} + +var _ cadvisor.Interface = new(Mock) + +func (c *Mock) Start() error { + args := c.Called() + return args.Error(0) +} + +// ContainerInfo is a mock implementation of Interface.ContainerInfo. +func (c *Mock) ContainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) { + args := c.Called(name, req) + return args.Get(0).(*cadvisorapi.ContainerInfo), args.Error(1) +} + +// ContainerInfoV2 is a mock implementation of Interface.ContainerInfoV2. +func (c *Mock) ContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) { + args := c.Called(name, options) + return args.Get(0).(map[string]cadvisorapiv2.ContainerInfo), args.Error(1) +} + +func (c *Mock) SubcontainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) { + args := c.Called(name, req) + return args.Get(0).(map[string]*cadvisorapi.ContainerInfo), args.Error(1) +} + +// DockerContainer is a mock implementation of Interface.DockerContainer. +func (c *Mock) DockerContainer(name string, req *cadvisorapi.ContainerInfoRequest) (cadvisorapi.ContainerInfo, error) { + args := c.Called(name, req) + return args.Get(0).(cadvisorapi.ContainerInfo), args.Error(1) +} + +// MachineInfo is a mock implementation of Interface.MachineInfo. +func (c *Mock) MachineInfo() (*cadvisorapi.MachineInfo, error) { + args := c.Called() + return args.Get(0).(*cadvisorapi.MachineInfo), args.Error(1) +} + +func (c *Mock) VersionInfo() (*cadvisorapi.VersionInfo, error) { + args := c.Called() + return args.Get(0).(*cadvisorapi.VersionInfo), args.Error(1) +} + +func (c *Mock) DockerImagesFsInfo() (cadvisorapiv2.FsInfo, error) { + args := c.Called() + return args.Get(0).(cadvisorapiv2.FsInfo), args.Error(1) +} + +func (c *Mock) RootFsInfo() (cadvisorapiv2.FsInfo, error) { + args := c.Called() + return args.Get(0).(cadvisorapiv2.FsInfo), args.Error(1) +} + +func (c *Mock) WatchEvents(request *events.Request) (*events.EventChannel, error) { + args := c.Called() + return args.Get(0).(*events.EventChannel), args.Error(1) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/types.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/types.go new file mode 100644 index 000000000..5bfbd2a0f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/types.go @@ -0,0 +1,44 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 cadvisor + +import ( + "github.com/google/cadvisor/events" + cadvisorapi "github.com/google/cadvisor/info/v1" + cadvisorapiv2 "github.com/google/cadvisor/info/v2" +) + +// Interface is an abstract interface for testability. It abstracts the interface to cAdvisor. +type Interface interface { + Start() error + DockerContainer(name string, req *cadvisorapi.ContainerInfoRequest) (cadvisorapi.ContainerInfo, error) + ContainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) + ContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) + SubcontainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) + MachineInfo() (*cadvisorapi.MachineInfo, error) + + VersionInfo() (*cadvisorapi.VersionInfo, error) + + // Returns usage information about the filesystem holding Docker images. + DockerImagesFsInfo() (cadvisorapiv2.FsInfo, error) + + // Returns usage information about the root filesystem. + RootFsInfo() (cadvisorapiv2.FsInfo, error) + + // Get events streamed through passedChannel that fit the request. + WatchEvents(request *events.Request) (*events.EventChannel, error) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/util.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/util.go new file mode 100644 index 000000000..2dac21756 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/util.go @@ -0,0 +1,35 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 cadvisor + +import ( + cadvisorApi "github.com/google/cadvisor/info/v1" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" +) + +func CapacityFromMachineInfo(info *cadvisorApi.MachineInfo) api.ResourceList { + c := api.ResourceList{ + api.ResourceCPU: *resource.NewMilliQuantity( + int64(info.NumCores*1000), + resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity( + int64(info.MemoryCapacity), + resource.BinarySI), + } + return c +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/client/kubelet_client.go b/vendor/k8s.io/kubernetes/pkg/kubelet/client/kubelet_client.go new file mode 100644 index 000000000..cd48f05c3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/client/kubelet_client.go @@ -0,0 +1,137 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 client + +import ( + "errors" + "fmt" + "net" + "net/http" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/client/transport" + utilnet "k8s.io/kubernetes/pkg/util/net" +) + +type KubeletClientConfig struct { + // Default port - used if no information about Kubelet port can be found in Node.NodeStatus.DaemonEndpoints. + Port uint + EnableHttps bool + + // TLSClientConfig contains settings to enable transport layer security + restclient.TLSClientConfig + + // Server requires Bearer authentication + BearerToken string + + // HTTPTimeout is used by the client to timeout http requests to Kubelet. + HTTPTimeout time.Duration + + // Dial is a custom dialer used for the client + Dial func(net, addr string) (net.Conn, error) +} + +// KubeletClient is an interface for all kubelet functionality +type KubeletClient interface { + ConnectionInfoGetter +} + +type ConnectionInfoGetter interface { + GetConnectionInfo(ctx api.Context, nodeName string) (scheme string, port uint, transport http.RoundTripper, err error) +} + +// HTTPKubeletClient is the default implementation of KubeletHealthchecker, accesses the kubelet over HTTP. +type HTTPKubeletClient struct { + Client *http.Client + Config *KubeletClientConfig +} + +func MakeTransport(config *KubeletClientConfig) (http.RoundTripper, error) { + tlsConfig, err := transport.TLSConfigFor(config.transportConfig()) + if err != nil { + return nil, err + } + + rt := http.DefaultTransport + if config.Dial != nil || tlsConfig != nil { + rt = utilnet.SetTransportDefaults(&http.Transport{ + Dial: config.Dial, + TLSClientConfig: tlsConfig, + }) + } + + return transport.HTTPWrappersForConfig(config.transportConfig(), rt) +} + +// TODO: this structure is questionable, it should be using client.Config and overriding defaults. +func NewStaticKubeletClient(config *KubeletClientConfig) (KubeletClient, error) { + transport, err := MakeTransport(config) + if err != nil { + return nil, err + } + c := &http.Client{ + Transport: transport, + Timeout: config.HTTPTimeout, + } + return &HTTPKubeletClient{ + Client: c, + Config: config, + }, nil +} + +// In default HTTPKubeletClient ctx is unused. +func (c *HTTPKubeletClient) GetConnectionInfo(ctx api.Context, nodeName string) (string, uint, http.RoundTripper, error) { + if ok, msg := validation.ValidateNodeName(nodeName, false); !ok { + return "", 0, nil, fmt.Errorf("invalid node name: %s", msg) + } + scheme := "http" + if c.Config.EnableHttps { + scheme = "https" + } + return scheme, c.Config.Port, c.Client.Transport, nil +} + +// FakeKubeletClient is a fake implementation of KubeletClient which returns an error +// when called. It is useful to pass to the master in a test configuration with +// no kubelets. +type FakeKubeletClient struct{} + +func (c FakeKubeletClient) GetConnectionInfo(ctx api.Context, nodeName string) (string, uint, http.RoundTripper, error) { + return "", 0, nil, errors.New("Not Implemented") +} + +// transportConfig converts a client config to an appropriate transport config. +func (c *KubeletClientConfig) transportConfig() *transport.Config { + cfg := &transport.Config{ + TLS: transport.TLSConfig{ + CAFile: c.CAFile, + CAData: c.CAData, + CertFile: c.CertFile, + CertData: c.CertData, + KeyFile: c.KeyFile, + KeyData: c.KeyData, + }, + BearerToken: c.BearerToken, + } + if c.EnableHttps && !cfg.HasCA() { + cfg.TLS.Insecure = true + } + return cfg +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/client/kubelet_client_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/client/kubelet_client_test.go new file mode 100644 index 000000000..19c816b16 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/client/kubelet_client_test.go @@ -0,0 +1,127 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 client + +import ( + "encoding/json" + "net/http/httptest" + "net/url" + "testing" + + "k8s.io/kubernetes/pkg/client/restclient" + "k8s.io/kubernetes/pkg/probe" + utiltesting "k8s.io/kubernetes/pkg/util/testing" +) + +func TestHTTPKubeletClient(t *testing.T) { + expectObj := probe.Success + body, err := json.Marshal(expectObj) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + fakeHandler := utiltesting.FakeHandler{ + StatusCode: 200, + ResponseBody: string(body), + } + testServer := httptest.NewServer(&fakeHandler) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + + if _, err := url.Parse(testServer.URL); err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func TestNewKubeletClient(t *testing.T) { + config := &KubeletClientConfig{ + EnableHttps: false, + } + + client, err := NewStaticKubeletClient(config) + if err != nil { + t.Errorf("Error while trying to create a client: %v", err) + } + if client == nil { + t.Error("client is nil.") + } +} + +func TestNewKubeletClientTLSInvalid(t *testing.T) { + config := &KubeletClientConfig{ + EnableHttps: true, + //Invalid certificate and key path + TLSClientConfig: restclient.TLSClientConfig{ + CertFile: "../../client/testdata/mycertinvalid.cer", + KeyFile: "../../client/testdata/mycertinvalid.key", + CAFile: "../../client/testdata/myCA.cer", + }, + } + + client, err := NewStaticKubeletClient(config) + if err == nil { + t.Errorf("Expected an error") + } + if client != nil { + t.Error("client should be nil as we provided invalid cert file") + } +} + +func TestNewKubeletClientTLSValid(t *testing.T) { + config := &KubeletClientConfig{ + Port: 1234, + EnableHttps: true, + TLSClientConfig: restclient.TLSClientConfig{ + CertFile: "../../client/testdata/mycertvalid.cer", + // TLS Configuration, only applies if EnableHttps is true. + KeyFile: "../../client/testdata/mycertvalid.key", + // TLS Configuration, only applies if EnableHttps is true. + CAFile: "../../client/testdata/myCA.cer", + }, + } + + client, err := NewStaticKubeletClient(config) + if err != nil { + t.Errorf("Not expecting an error #%v", err) + } + if client == nil { + t.Error("client should not be nil") + } + + { + scheme, port, transport, err := client.GetConnectionInfo(nil, "foo") + if err != nil { + t.Errorf("Error getting info: %v", err) + } + if scheme != "https" { + t.Errorf("Expected https, got %s", scheme) + } + if port != 1234 { + t.Errorf("Expected 1234, got %d", port) + } + if transport == nil { + t.Errorf("Expected transport, got nil") + } + } + + { + _, _, _, err := client.GetConnectionInfo(nil, "foo bar") + if err == nil { + t.Errorf("Expected error getting connection info for invalid node name, got none") + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager.go new file mode 100644 index 000000000..e18bc6865 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager.go @@ -0,0 +1,51 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 cm + +import ( + "k8s.io/kubernetes/pkg/api" +) + +// Manages the containers running on a machine. +type ContainerManager interface { + // Runs the container manager's housekeeping. + // - Ensures that the Docker daemon is in a container. + // - Creates the system container where all non-containerized processes run. + Start() error + + // Returns resources allocated to system cgroups in the machine. + // These cgroups include the system and Kubernetes services. + SystemCgroupsLimit() api.ResourceList + + // Returns a NodeConfig that is being used by the container manager. + GetNodeConfig() NodeConfig + + // Returns internal Status. + Status() Status +} + +type NodeConfig struct { + RuntimeCgroupsName string + SystemCgroupsName string + KubeletCgroupsName string + ContainerRuntime string +} + +type Status struct { + // Any soft requirements that were unsatisfied. + SoftRequirements error +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_linux.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_linux.go new file mode 100644 index 000000000..45497dcb8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_linux.go @@ -0,0 +1,552 @@ +// +build linux + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 cm + +import ( + "fmt" + "os" + "os/exec" + "path" + "strconv" + "strings" + "sync" + "time" + + "github.com/golang/glog" + "github.com/opencontainers/runc/libcontainer/cgroups" + "github.com/opencontainers/runc/libcontainer/cgroups/fs" + "github.com/opencontainers/runc/libcontainer/configs" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/kubelet/cadvisor" + "k8s.io/kubernetes/pkg/util" + utilerrors "k8s.io/kubernetes/pkg/util/errors" + "k8s.io/kubernetes/pkg/util/mount" + "k8s.io/kubernetes/pkg/util/oom" + "k8s.io/kubernetes/pkg/util/sets" + utilsysctl "k8s.io/kubernetes/pkg/util/sysctl" + "k8s.io/kubernetes/pkg/util/wait" +) + +const ( + // The percent of the machine memory capacity. The value is used to calculate + // docker memory resource container's hardlimit to workaround docker memory + // leakage issue. Please see kubernetes/issues/9881 for more detail. + DockerMemoryLimitThresholdPercent = 70 + // The minimum memory limit allocated to docker container: 150Mi + MinDockerMemoryLimit = 150 * 1024 * 1024 +) + +// A non-user container tracked by the Kubelet. +type systemContainer struct { + // Absolute name of the container. + name string + + // CPU limit in millicores. + cpuMillicores int64 + + // Function that ensures the state of the container. + // m is the cgroup manager for the specified container. + ensureStateFunc func(m *fs.Manager) error + + // Manager for the cgroups of the external container. + manager *fs.Manager +} + +func newSystemCgroups(containerName string) *systemContainer { + return &systemContainer{ + name: containerName, + manager: createManager(containerName), + } +} + +type containerManagerImpl struct { + sync.RWMutex + cadvisorInterface cadvisor.Interface + mountUtil mount.Interface + NodeConfig + status Status + // External containers being managed. + systemContainers []*systemContainer + periodicTasks []func() +} + +type features struct { + cpuHardcapping bool +} + +var _ ContainerManager = &containerManagerImpl{} + +// checks if the required cgroups subsystems are mounted. +// As of now, only 'cpu' and 'memory' are required. +// cpu quota is a soft requirement. +func validateSystemRequirements(mountUtil mount.Interface) (features, error) { + const ( + cgroupMountType = "cgroup" + localErr = "system validation failed" + ) + var ( + cpuMountPoint string + f features + ) + mountPoints, err := mountUtil.List() + if err != nil { + return f, fmt.Errorf("%s - %v", localErr, err) + } + + expectedCgroups := sets.NewString("cpu", "cpuacct", "cpuset", "memory") + for _, mountPoint := range mountPoints { + if mountPoint.Type == cgroupMountType { + for _, opt := range mountPoint.Opts { + if expectedCgroups.Has(opt) { + expectedCgroups.Delete(opt) + } + if opt == "cpu" { + cpuMountPoint = mountPoint.Path + } + } + } + } + + if expectedCgroups.Len() > 0 { + return f, fmt.Errorf("%s - Following Cgroup subsystem not mounted: %v", localErr, expectedCgroups.List()) + } + + // Check if cpu quota is available. + // CPU cgroup is required and so it expected to be mounted at this point. + periodExists, err := util.FileExists(path.Join(cpuMountPoint, "cpu.cfs_period_us")) + if err != nil { + glog.Errorf("failed to detect if CPU cgroup cpu.cfs_period_us is available - %v", err) + } + quotaExists, err := util.FileExists(path.Join(cpuMountPoint, "cpu.cfs_quota_us")) + if err != nil { + glog.Errorf("failed to detect if CPU cgroup cpu.cfs_quota_us is available - %v", err) + } + if quotaExists && periodExists { + f.cpuHardcapping = true + } + return f, nil +} + +// TODO(vmarmol): Add limits to the system containers. +// Takes the absolute name of the specified containers. +// Empty container name disables use of the specified container. +func NewContainerManager(mountUtil mount.Interface, cadvisorInterface cadvisor.Interface, nodeConfig NodeConfig) (ContainerManager, error) { + return &containerManagerImpl{ + cadvisorInterface: cadvisorInterface, + mountUtil: mountUtil, + NodeConfig: nodeConfig, + }, nil +} + +// Create a cgroup container manager. +func createManager(containerName string) *fs.Manager { + return &fs.Manager{ + Cgroups: &configs.Cgroup{ + Parent: "/", + Name: containerName, + Resources: &configs.Resources{ + AllowAllDevices: true, + }, + }, + } +} + +// TODO: plumb this up as a flag to Kubelet in a future PR +type KernelTunableBehavior string + +const ( + KernelTunableWarn KernelTunableBehavior = "warn" + KernelTunableError KernelTunableBehavior = "error" + KernelTunableModify KernelTunableBehavior = "modify" +) + +// setupKernelTunables validates kernel tunable flags are set as expected +// depending upon the specified option, it will either warn, error, or modify the kernel tunable flags +func setupKernelTunables(option KernelTunableBehavior) error { + desiredState := map[string]int{ + utilsysctl.VmOvercommitMemory: utilsysctl.VmOvercommitMemoryAlways, + utilsysctl.VmPanicOnOOM: utilsysctl.VmPanicOnOOMInvokeOOMKiller, + utilsysctl.KernelPanic: utilsysctl.KernelPanicRebootTimeout, + utilsysctl.KernelPanicOnOops: utilsysctl.KernelPanicOnOopsAlways, + } + + errList := []error{} + for flag, expectedValue := range desiredState { + val, err := utilsysctl.GetSysctl(flag) + if err != nil { + errList = append(errList, err) + continue + } + if val == expectedValue { + continue + } + + switch option { + case KernelTunableError: + errList = append(errList, fmt.Errorf("Invalid kernel flag: %v, expected value: %v, actual value: %v", flag, expectedValue, val)) + case KernelTunableWarn: + glog.V(2).Infof("Invalid kernel flag: %v, expected value: %v, actual value: %v", flag, expectedValue, val) + case KernelTunableModify: + glog.V(2).Infof("Updating kernel flag: %v, expected value: %v, actual value: %v", flag, expectedValue, val) + err = utilsysctl.SetSysctl(flag, expectedValue) + if err != nil { + errList = append(errList, err) + } + } + } + return utilerrors.NewAggregate(errList) +} + +func (cm *containerManagerImpl) setupNode() error { + f, err := validateSystemRequirements(cm.mountUtil) + if err != nil { + return err + } + if !f.cpuHardcapping { + cm.status.SoftRequirements = fmt.Errorf("CPU hardcapping unsupported") + } + // TODO: plumb kernel tunable options into container manager, right now, we modify by default + if err := setupKernelTunables(KernelTunableModify); err != nil { + return err + } + + systemContainers := []*systemContainer{} + if cm.ContainerRuntime == "docker" { + if cm.RuntimeCgroupsName != "" { + cont := newSystemCgroups(cm.RuntimeCgroupsName) + info, err := cm.cadvisorInterface.MachineInfo() + var capacity = api.ResourceList{} + if err != nil { + } else { + capacity = cadvisor.CapacityFromMachineInfo(info) + } + memoryLimit := (int64(capacity.Memory().Value() * DockerMemoryLimitThresholdPercent / 100)) + if memoryLimit < MinDockerMemoryLimit { + glog.Warningf("Memory limit %d for container %s is too small, reset it to %d", memoryLimit, cm.RuntimeCgroupsName, MinDockerMemoryLimit) + memoryLimit = MinDockerMemoryLimit + } + + glog.V(2).Infof("Configure resource-only container %s with memory limit: %d", cm.RuntimeCgroupsName, memoryLimit) + + dockerContainer := &fs.Manager{ + Cgroups: &configs.Cgroup{ + Parent: "/", + Name: cm.RuntimeCgroupsName, + Resources: &configs.Resources{ + Memory: memoryLimit, + MemorySwap: -1, + AllowAllDevices: true, + }, + }, + } + cont.ensureStateFunc = func(manager *fs.Manager) error { + return ensureDockerInContainer(cm.cadvisorInterface, -900, dockerContainer) + } + systemContainers = append(systemContainers, cont) + } else { + cm.periodicTasks = append(cm.periodicTasks, func() { + cont, err := getContainerNameForProcess("docker") + if err != nil { + glog.Error(err) + return + } + cm.Lock() + defer cm.Unlock() + cm.RuntimeCgroupsName = cont + }) + } + } + + if cm.SystemCgroupsName != "" { + if cm.SystemCgroupsName == "/" { + return fmt.Errorf("system container cannot be root (\"/\")") + } + cont := newSystemCgroups(cm.SystemCgroupsName) + rootContainer := &fs.Manager{ + Cgroups: &configs.Cgroup{ + Parent: "/", + Name: "/", + }, + } + cont.ensureStateFunc = func(manager *fs.Manager) error { + return ensureSystemCgroups(rootContainer, manager) + } + systemContainers = append(systemContainers, cont) + } + + if cm.KubeletCgroupsName != "" { + cont := newSystemCgroups(cm.KubeletCgroupsName) + manager := fs.Manager{ + Cgroups: &configs.Cgroup{ + Parent: "/", + Name: cm.KubeletCgroupsName, + Resources: &configs.Resources{ + AllowAllDevices: true, + }, + }, + } + cont.ensureStateFunc = func(_ *fs.Manager) error { + return manager.Apply(os.Getpid()) + } + systemContainers = append(systemContainers, cont) + } else { + cm.periodicTasks = append(cm.periodicTasks, func() { + cont, err := getContainer(os.Getpid()) + if err != nil { + glog.Errorf("failed to find cgroups of kubelet - %v", err) + return + } + cm.Lock() + defer cm.Unlock() + + cm.KubeletCgroupsName = cont + }) + } + + cm.systemContainers = systemContainers + return nil +} + +func getContainerNameForProcess(name string) (string, error) { + pids, err := getPidsForProcess(name) + if err != nil { + return "", fmt.Errorf("failed to detect process id for %q - %v", name, err) + } + if len(pids) == 0 { + return "", nil + } + cont, err := getContainer(pids[0]) + if err != nil { + return "", err + } + return cont, nil +} + +func (cm *containerManagerImpl) GetNodeConfig() NodeConfig { + cm.RLock() + defer cm.RUnlock() + return cm.NodeConfig +} + +func (cm *containerManagerImpl) Status() Status { + cm.RLock() + defer cm.RUnlock() + return cm.status +} + +func (cm *containerManagerImpl) Start() error { + // Setup the node + if err := cm.setupNode(); err != nil { + return err + } + // Don't run a background thread if there are no ensureStateFuncs. + numEnsureStateFuncs := 0 + for _, cont := range cm.systemContainers { + if cont.ensureStateFunc != nil { + numEnsureStateFuncs++ + } + } + if numEnsureStateFuncs >= 0 { + go wait.Until(func() { + for _, cont := range cm.systemContainers { + if cont.ensureStateFunc != nil { + if err := cont.ensureStateFunc(cont.manager); err != nil { + glog.Warningf("[ContainerManager] Failed to ensure state of %q: %v", cont.name, err) + } + } + } + }, time.Minute, wait.NeverStop) + + } + + // Run ensure state functions every minute. + if len(cm.periodicTasks) > 0 { + go wait.Until(func() { + for _, task := range cm.periodicTasks { + if task != nil { + task() + } + } + }, 5*time.Minute, wait.NeverStop) + } + + return nil +} + +func (cm *containerManagerImpl) SystemCgroupsLimit() api.ResourceList { + cpuLimit := int64(0) + + // Sum up resources of all external containers. + for _, cont := range cm.systemContainers { + cpuLimit += cont.cpuMillicores + } + + return api.ResourceList{ + api.ResourceCPU: *resource.NewMilliQuantity( + cpuLimit, + resource.DecimalSI), + } +} + +func isProcessRunningInHost(pid int) (bool, error) { + // Get init mount namespace. Mount namespace is unique for all containers. + initMntNs, err := os.Readlink("/proc/1/ns/mnt") + if err != nil { + return false, fmt.Errorf("failed to find mount namespace of init process") + } + processMntNs, err := os.Readlink(fmt.Sprintf("/proc/%d/ns/mnt", pid)) + if err != nil { + return false, fmt.Errorf("failed to find mount namespace of process %q", pid) + } + return initMntNs == processMntNs, nil +} + +func getPidsForProcess(name string) ([]int, error) { + out, err := exec.Command("pidof", name).Output() + if err != nil { + return []int{}, fmt.Errorf("failed to find pid of %q: %v", name, err) + } + + // The output of pidof is a list of pids. + pids := []int{} + for _, pidStr := range strings.Split(strings.TrimSpace(string(out)), " ") { + pid, err := strconv.Atoi(pidStr) + if err != nil { + continue + } + pids = append(pids, pid) + } + return pids, nil +} + +// Ensures that the Docker daemon is in the desired container. +func ensureDockerInContainer(cadvisor cadvisor.Interface, oomScoreAdj int, manager *fs.Manager) error { + pids, err := getPidsForProcess("docker") + if err != nil { + return err + } + // Move if the pid is not already in the desired container. + errs := []error{} + for _, pid := range pids { + if runningInHost, err := isProcessRunningInHost(pid); err != nil { + errs = append(errs, err) + // Err on the side of caution. Avoid moving the docker daemon unless we are able to identify its context. + continue + } else if !runningInHost { + // Docker daemon is running inside a container. Don't touch that. + continue + } + + cont, err := getContainer(pid) + if err != nil { + errs = append(errs, fmt.Errorf("failed to find container of PID %d: %v", pid, err)) + } + + if cont != manager.Cgroups.Name { + err = manager.Apply(pid) + if err != nil { + errs = append(errs, fmt.Errorf("failed to move PID %d (in %q) to %q", pid, cont, manager.Cgroups.Name)) + } + } + + // Also apply oom-score-adj to processes + oomAdjuster := oom.NewOOMAdjuster() + if err := oomAdjuster.ApplyOOMScoreAdj(pid, oomScoreAdj); err != nil { + errs = append(errs, fmt.Errorf("failed to apply oom score %d to PID %d", oomScoreAdj, pid)) + } + } + + return utilerrors.NewAggregate(errs) +} + +// Gets the (CPU) container the specified pid is in. +func getContainer(pid int) (string, error) { + cgs, err := cgroups.ParseCgroupFile(fmt.Sprintf("/proc/%d/cgroup", pid)) + if err != nil { + return "", err + } + + cg, ok := cgs["cpu"] + if ok { + return cg, nil + } + + return "", cgroups.NewNotFoundError("cpu") +} + +// Ensures the system container is created and all non-kernel threads and process 1 +// without a container are moved to it. +// +// The reason of leaving kernel threads at root cgroup is that we don't want to tie the +// execution of these threads with to-be defined /system quota and create priority inversions. +// +func ensureSystemCgroups(rootContainer *fs.Manager, manager *fs.Manager) error { + // Move non-kernel PIDs to the system container. + attemptsRemaining := 10 + var errs []error + for attemptsRemaining >= 0 { + // Only keep errors on latest attempt. + errs = []error{} + attemptsRemaining-- + + allPids, err := rootContainer.GetPids() + if err != nil { + errs = append(errs, fmt.Errorf("failed to list PIDs for root: %v", err)) + continue + } + + // Remove kernel pids and other protected PIDs (pid 1, PIDs already in system & kubelet containers) + pids := make([]int, 0, len(allPids)) + for _, pid := range allPids { + if pid == 1 || isKernelPid(pid) { + continue + } + + pids = append(pids, pid) + } + glog.Infof("Found %d PIDs in root, %d of them are not to be moved", len(allPids), len(allPids)-len(pids)) + + // Check if we have moved all the non-kernel PIDs. + if len(pids) == 0 { + break + } + + glog.Infof("Moving non-kernel processes: %v", pids) + for _, pid := range pids { + err := manager.Apply(pid) + if err != nil { + errs = append(errs, fmt.Errorf("failed to move PID %d into the system container %q: %v", pid, manager.Cgroups.Name, err)) + } + } + + } + if attemptsRemaining < 0 { + errs = append(errs, fmt.Errorf("ran out of attempts to create system containers %q", manager.Cgroups.Name)) + } + + return utilerrors.NewAggregate(errs) +} + +// Determines whether the specified PID is a kernel PID. +func isKernelPid(pid int) bool { + // Kernel threads have no associated executable. + _, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", pid)) + return err != nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_linux_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_linux_test.go new file mode 100644 index 000000000..34e91c83b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_linux_test.go @@ -0,0 +1,164 @@ +// +build linux + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 cm + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "k8s.io/kubernetes/pkg/util/mount" +) + +type fakeMountInterface struct { + mountPoints []mount.MountPoint +} + +func (mi *fakeMountInterface) Mount(source string, target string, fstype string, options []string) error { + return fmt.Errorf("unsupported") +} + +func (mi *fakeMountInterface) Unmount(target string) error { + return fmt.Errorf("unsupported") +} + +func (mi *fakeMountInterface) List() ([]mount.MountPoint, error) { + return mi.mountPoints, nil +} + +func (mi *fakeMountInterface) IsLikelyNotMountPoint(file string) (bool, error) { + return false, fmt.Errorf("unsupported") +} + +func fakeContainerMgrMountInt() mount.Interface { + return &fakeMountInterface{ + []mount.MountPoint{ + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpuset"}, + }, + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpu"}, + }, + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpuacct"}, + }, + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "memory"}, + }, + }, + } +} + +func TestCgroupMountValidationSuccess(t *testing.T) { + f, err := validateSystemRequirements(fakeContainerMgrMountInt()) + assert.Nil(t, err) + assert.False(t, f.cpuHardcapping, "cpu hardcapping is expected to be disabled") +} + +func TestCgroupMountValidationMemoryMissing(t *testing.T) { + mountInt := &fakeMountInterface{ + []mount.MountPoint{ + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpuset"}, + }, + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpu"}, + }, + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpuacct"}, + }, + }, + } + _, err := validateSystemRequirements(mountInt) + assert.Error(t, err) +} + +func TestCgroupMountValidationMultipleSubsytem(t *testing.T) { + mountInt := &fakeMountInterface{ + []mount.MountPoint{ + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpuset", "memory"}, + }, + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpu"}, + }, + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpuacct"}, + }, + }, + } + _, err := validateSystemRequirements(mountInt) + assert.Nil(t, err) +} + +func TestSoftRequirementsValidationSuccess(t *testing.T) { + req := require.New(t) + tempDir, err := ioutil.TempDir("", "") + req.NoError(err) + req.NoError(ioutil.WriteFile(path.Join(tempDir, "cpu.cfs_period_us"), []byte("0"), os.ModePerm)) + req.NoError(ioutil.WriteFile(path.Join(tempDir, "cpu.cfs_quota_us"), []byte("0"), os.ModePerm)) + mountInt := &fakeMountInterface{ + []mount.MountPoint{ + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpuset"}, + }, + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpu"}, + Path: tempDir, + }, + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpuacct", "memory"}, + }, + }, + } + f, err := validateSystemRequirements(mountInt) + assert.NoError(t, err) + assert.True(t, f.cpuHardcapping, "cpu hardcapping is expected to be enabled") +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_stub.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_stub.go new file mode 100644 index 000000000..4bca506c2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_stub.go @@ -0,0 +1,47 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 cm + +import ( + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" +) + +type containerManagerStub struct{} + +var _ ContainerManager = &containerManagerStub{} + +func (cm *containerManagerStub) Start() error { + glog.V(2).Infof("Starting stub container manager") + return nil +} + +func (cm *containerManagerStub) SystemCgroupsLimit() api.ResourceList { + return api.ResourceList{} +} + +func (cm *containerManagerStub) GetNodeConfig() NodeConfig { + return NodeConfig{} +} + +func (cm *containerManagerStub) Status() Status { + return Status{} +} + +func NewStubContainerManager() ContainerManager { + return &containerManagerStub{} +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_unsupported.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_unsupported.go new file mode 100644 index 000000000..426c95ca4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_unsupported.go @@ -0,0 +1,52 @@ +// +build !linux + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 cm + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/kubelet/cadvisor" + "k8s.io/kubernetes/pkg/util/mount" +) + +type unsupportedContainerManager struct { +} + +var _ ContainerManager = &unsupportedContainerManager{} + +func (unsupportedContainerManager) Start() error { + return fmt.Errorf("Container Manager is unsupported in this build") +} + +func (unsupportedContainerManager) SystemCgroupsLimit() api.ResourceList { + return api.ResourceList{} +} + +func (unsupportedContainerManager) GetNodeConfig() NodeConfig { + return NodeConfig{} +} + +func (cm *unsupportedContainerManager) Status() Status { + return Status{} +} + +func NewContainerManager(_ mount.Interface, _ cadvisor.Interface, _ NodeConfig) (ContainerManager, error) { + return &unsupportedContainerManager{}, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_unsupported_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_unsupported_test.go new file mode 100644 index 000000000..48a4f04fd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/cm/container_manager_unsupported_test.go @@ -0,0 +1,72 @@ +// +build !linux + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 cm + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/util/mount" +) + +type fakeMountInterface struct { + mountPoints []mount.MountPoint +} + +func (mi *fakeMountInterface) Mount(source string, target string, fstype string, options []string) error { + return fmt.Errorf("unsupported") +} + +func (mi *fakeMountInterface) Unmount(target string) error { + return fmt.Errorf("unsupported") +} + +func (mi *fakeMountInterface) List() ([]mount.MountPoint, error) { + return mi.mountPoints, nil +} + +func (mi *fakeMountInterface) IsLikelyNotMountPoint(file string) (bool, error) { + return false, fmt.Errorf("unsupported") +} + +func fakeContainerMgrMountInt() mount.Interface { + return &fakeMountInterface{ + []mount.MountPoint{ + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpuset"}, + }, + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpu"}, + }, + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "cpuacct"}, + }, + { + Device: "cgroup", + Type: "cgroup", + Opts: []string{"rw", "relatime", "memory"}, + }, + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/config/apiserver.go b/vendor/k8s.io/kubernetes/pkg/kubelet/config/apiserver.go new file mode 100644 index 000000000..c3baed8d0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/config/apiserver.go @@ -0,0 +1,44 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// Reads the pod configuration from the Kubernetes apiserver. +package config + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/fields" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" +) + +// NewSourceApiserver creates a config source that watches and pulls from the apiserver. +func NewSourceApiserver(c *clientset.Clientset, nodeName string, updates chan<- interface{}) { + lw := cache.NewListWatchFromClient(c.CoreClient, "pods", api.NamespaceAll, fields.OneTermEqualSelector(api.PodHostField, nodeName)) + newSourceApiserverFromLW(lw, updates) +} + +// newSourceApiserverFromLW holds creates a config source that watches and pulls from the apiserver. +func newSourceApiserverFromLW(lw cache.ListerWatcher, updates chan<- interface{}) { + send := func(objs []interface{}) { + var pods []*api.Pod + for _, o := range objs { + pods = append(pods, o.(*api.Pod)) + } + updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.ApiserverSource} + } + cache.NewReflector(lw, &api.Pod{}, cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc), 0).Run() +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/config/apiserver_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/config/apiserver_test.go new file mode 100644 index 000000000..d7be8a7fe --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/config/apiserver_test.go @@ -0,0 +1,192 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 config + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/cache" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/watch" +) + +type fakePodLW struct { + listResp runtime.Object + watchResp watch.Interface +} + +func (lw fakePodLW) List(options api.ListOptions) (runtime.Object, error) { + return lw.listResp, nil +} + +func (lw fakePodLW) Watch(options api.ListOptions) (watch.Interface, error) { + return lw.watchResp, nil +} + +var _ cache.ListerWatcher = fakePodLW{} + +func TestNewSourceApiserver_UpdatesAndMultiplePods(t *testing.T) { + pod1v1 := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "p"}, + Spec: api.PodSpec{Containers: []api.Container{{Image: "image/one"}}}} + pod1v2 := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "p"}, + Spec: api.PodSpec{Containers: []api.Container{{Image: "image/two"}}}} + pod2 := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "q"}, + Spec: api.PodSpec{Containers: []api.Container{{Image: "image/blah"}}}} + + // Setup fake api client. + fakeWatch := watch.NewFake() + lw := fakePodLW{ + listResp: &api.PodList{Items: []api.Pod{*pod1v1}}, + watchResp: fakeWatch, + } + + ch := make(chan interface{}) + + newSourceApiserverFromLW(lw, ch) + + got, ok := <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + update := got.(kubetypes.PodUpdate) + expected := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v1) + if !api.Semantic.DeepEqual(expected, update) { + t.Errorf("Expected %#v; Got %#v", expected, update) + } + + // Add another pod + fakeWatch.Add(pod2) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + update = got.(kubetypes.PodUpdate) + // Could be sorted either of these two ways: + expectedA := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v1, pod2) + expectedB := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2, pod1v1) + + if !api.Semantic.DeepEqual(expectedA, update) && !api.Semantic.DeepEqual(expectedB, update) { + t.Errorf("Expected %#v or %#v, Got %#v", expectedA, expectedB, update) + } + + // Modify pod1 + fakeWatch.Modify(pod1v2) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + update = got.(kubetypes.PodUpdate) + expectedA = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v2, pod2) + expectedB = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2, pod1v2) + + if !api.Semantic.DeepEqual(expectedA, update) && !api.Semantic.DeepEqual(expectedB, update) { + t.Errorf("Expected %#v or %#v, Got %#v", expectedA, expectedB, update) + } + + // Delete pod1 + fakeWatch.Delete(pod1v2) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + update = got.(kubetypes.PodUpdate) + expected = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2) + if !api.Semantic.DeepEqual(expected, update) { + t.Errorf("Expected %#v, Got %#v", expected, update) + } + + // Delete pod2 + fakeWatch.Delete(pod2) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + update = got.(kubetypes.PodUpdate) + expected = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource) + if !api.Semantic.DeepEqual(expected, update) { + t.Errorf("Expected %#v, Got %#v", expected, update) + } +} + +func TestNewSourceApiserver_TwoNamespacesSameName(t *testing.T) { + pod1 := api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "p", Namespace: "one"}, + Spec: api.PodSpec{Containers: []api.Container{{Image: "image/one"}}}} + pod2 := api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "p", Namespace: "two"}, + Spec: api.PodSpec{Containers: []api.Container{{Image: "image/blah"}}}} + + // Setup fake api client. + fakeWatch := watch.NewFake() + lw := fakePodLW{ + listResp: &api.PodList{Items: []api.Pod{pod1, pod2}}, + watchResp: fakeWatch, + } + + ch := make(chan interface{}) + + newSourceApiserverFromLW(lw, ch) + + got, ok := <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + update := got.(kubetypes.PodUpdate) + // Make sure that we get both pods. Catches bug #2294. + if !(len(update.Pods) == 2) { + t.Errorf("Expected %d, Got %d", 2, len(update.Pods)) + } + + // Delete pod1 + fakeWatch.Delete(&pod1) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + update = got.(kubetypes.PodUpdate) + if !(len(update.Pods) == 1) { + t.Errorf("Expected %d, Got %d", 1, len(update.Pods)) + } +} + +func TestNewSourceApiserverInitialEmptySendsEmptyPodUpdate(t *testing.T) { + // Setup fake api client. + fakeWatch := watch.NewFake() + lw := fakePodLW{ + listResp: &api.PodList{Items: []api.Pod{}}, + watchResp: fakeWatch, + } + + ch := make(chan interface{}) + + newSourceApiserverFromLW(lw, ch) + + got, ok := <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + update := got.(kubetypes.PodUpdate) + expected := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource) + if !api.Semantic.DeepEqual(expected, update) { + t.Errorf("Expected %#v; Got %#v", expected, update) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/config/common.go b/vendor/k8s.io/kubernetes/pkg/kubelet/config/common.go new file mode 100644 index 000000000..0838699ec --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/config/common.go @@ -0,0 +1,141 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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. +*/ + +// Common logic used by both http and file channels. +package config + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/apimachinery/registered" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/hash" + utilyaml "k8s.io/kubernetes/pkg/util/yaml" + + "github.com/golang/glog" +) + +// Generate a pod name that is unique among nodes by appending the nodeName. +func generatePodName(name, nodeName string) string { + return fmt.Sprintf("%s-%s", name, nodeName) +} + +func applyDefaults(pod *api.Pod, source string, isFile bool, nodeName string) error { + if len(pod.UID) == 0 { + hasher := md5.New() + if isFile { + fmt.Fprintf(hasher, "host:%s", nodeName) + fmt.Fprintf(hasher, "file:%s", source) + } else { + fmt.Fprintf(hasher, "url:%s", source) + } + hash.DeepHashObject(hasher, pod) + pod.UID = types.UID(hex.EncodeToString(hasher.Sum(nil)[0:])) + glog.V(5).Infof("Generated UID %q pod %q from %s", pod.UID, pod.Name, source) + } + + pod.Name = generatePodName(pod.Name, nodeName) + glog.V(5).Infof("Generated Name %q for UID %q from URL %s", pod.Name, pod.UID, source) + + if pod.Namespace == "" { + pod.Namespace = kubetypes.NamespaceDefault + } + glog.V(5).Infof("Using namespace %q for pod %q from %s", pod.Namespace, pod.Name, source) + + // Set the Host field to indicate this pod is scheduled on the current node. + pod.Spec.NodeName = nodeName + + pod.ObjectMeta.SelfLink = getSelfLink(pod.Name, pod.Namespace) + + if pod.Annotations == nil { + pod.Annotations = make(map[string]string) + } + // The generated UID is the hash of the file. + pod.Annotations[kubetypes.ConfigHashAnnotationKey] = string(pod.UID) + + // Set the default status to pending. + pod.Status.Phase = api.PodPending + return nil +} + +func getSelfLink(name, namespace string) string { + var selfLink string + if len(namespace) == 0 { + namespace = api.NamespaceDefault + } + selfLink = fmt.Sprintf("/api/"+registered.GroupOrDie(api.GroupName).GroupVersion.Version+"/pods/namespaces/%s/%s", name, namespace) + return selfLink +} + +type defaultFunc func(pod *api.Pod) error + +func tryDecodeSinglePod(data []byte, defaultFn defaultFunc) (parsed bool, pod *api.Pod, err error) { + // JSON is valid YAML, so this should work for everything. + json, err := utilyaml.ToJSON(data) + if err != nil { + return false, nil, err + } + obj, err := runtime.Decode(api.Codecs.UniversalDecoder(), json) + if err != nil { + return false, pod, err + } + // Check whether the object could be converted to single pod. + if _, ok := obj.(*api.Pod); !ok { + err = fmt.Errorf("invalid pod: %+v", obj) + return false, pod, err + } + newPod := obj.(*api.Pod) + // Apply default values and validate the pod. + if err = defaultFn(newPod); err != nil { + return true, pod, err + } + if errs := validation.ValidatePod(newPod); len(errs) > 0 { + err = fmt.Errorf("invalid pod: %v", errs) + return true, pod, err + } + return true, newPod, nil +} + +func tryDecodePodList(data []byte, defaultFn defaultFunc) (parsed bool, pods api.PodList, err error) { + obj, err := runtime.Decode(api.Codecs.UniversalDecoder(), data) + if err != nil { + return false, pods, err + } + // Check whether the object could be converted to list of pods. + if _, ok := obj.(*api.PodList); !ok { + err = fmt.Errorf("invalid pods list: %#v", obj) + return false, pods, err + } + newPods := obj.(*api.PodList) + // Apply default values and validate pods. + for i := range newPods.Items { + newPod := &newPods.Items[i] + if err = defaultFn(newPod); err != nil { + return true, pods, err + } + if errs := validation.ValidatePod(newPod); len(errs) > 0 { + err = fmt.Errorf("invalid pod: %v", errs) + return true, pods, err + } + } + return true, *newPods, err +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/config/common_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/config/common_test.go new file mode 100644 index 000000000..80eab2f4e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/config/common_test.go @@ -0,0 +1,157 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 config + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/securitycontext" +) + +func noDefault(*api.Pod) error { return nil } + +func TestDecodeSinglePod(t *testing.T) { + grace := int64(30) + pod := &api.Pod{ + TypeMeta: unversioned.TypeMeta{ + APIVersion: "", + }, + ObjectMeta: api.ObjectMeta{ + Name: "test", + UID: "12345", + Namespace: "mynamespace", + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + TerminationGracePeriodSeconds: &grace, + Containers: []api.Container{{ + Name: "image", + Image: "test/image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePath: "/dev/termination-log", + SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), + }}, + SecurityContext: &api.PodSecurityContext{}, + }, + } + json, err := runtime.Encode(testapi.Default.Codec(), pod) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + parsed, podOut, err := tryDecodeSinglePod(json, noDefault) + if !parsed { + t.Errorf("expected to have parsed file: (%s)", string(json)) + } + if err != nil { + t.Errorf("unexpected error: %v (%s)", err, string(json)) + } + if !reflect.DeepEqual(pod, podOut) { + t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", pod, podOut, string(json)) + } + + for _, gv := range registered.EnabledVersionsForGroup(api.GroupName) { + s, _ := api.Codecs.SerializerForFileExtension("yaml") + encoder := api.Codecs.EncoderForVersion(s, gv) + yaml, err := runtime.Encode(encoder, pod) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + parsed, podOut, err = tryDecodeSinglePod(yaml, noDefault) + if !parsed { + t.Errorf("expected to have parsed file: (%s)", string(yaml)) + } + if err != nil { + t.Errorf("unexpected error: %v (%s)", err, string(yaml)) + } + if !reflect.DeepEqual(pod, podOut) { + t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", pod, podOut, string(yaml)) + } + } +} + +func TestDecodePodList(t *testing.T) { + grace := int64(30) + pod := &api.Pod{ + TypeMeta: unversioned.TypeMeta{ + APIVersion: "", + }, + ObjectMeta: api.ObjectMeta{ + Name: "test", + UID: "12345", + Namespace: "mynamespace", + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + TerminationGracePeriodSeconds: &grace, + Containers: []api.Container{{ + Name: "image", + Image: "test/image", + ImagePullPolicy: "IfNotPresent", + TerminationMessagePath: "/dev/termination-log", + SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), + }}, + SecurityContext: &api.PodSecurityContext{}, + }, + } + podList := &api.PodList{ + Items: []api.Pod{*pod}, + } + json, err := runtime.Encode(testapi.Default.Codec(), podList) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + parsed, podListOut, err := tryDecodePodList(json, noDefault) + if !parsed { + t.Errorf("expected to have parsed file: (%s)", string(json)) + } + if err != nil { + t.Errorf("unexpected error: %v (%s)", err, string(json)) + } + if !reflect.DeepEqual(podList, &podListOut) { + t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", podList, &podListOut, string(json)) + } + + for _, gv := range registered.EnabledVersionsForGroup(api.GroupName) { + s, _ := api.Codecs.SerializerForFileExtension("yaml") + encoder := api.Codecs.EncoderForVersion(s, gv) + yaml, err := runtime.Encode(encoder, podList) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + parsed, podListOut, err = tryDecodePodList(yaml, noDefault) + if !parsed { + t.Errorf("expected to have parsed file: (%s): %v", string(yaml), err) + continue + } + if err != nil { + t.Errorf("unexpected error: %v (%s)", err, string(yaml)) + continue + } + if !reflect.DeepEqual(podList, &podListOut) { + t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", pod, &podListOut, string(yaml)) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/config/config.go b/vendor/k8s.io/kubernetes/pkg/kubelet/config/config.go new file mode 100644 index 000000000..0d190e01e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/config/config.go @@ -0,0 +1,497 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 config + +import ( + "fmt" + "reflect" + "sync" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/client/record" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/kubelet/util/format" + "k8s.io/kubernetes/pkg/util/config" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// PodConfigNotificationMode describes how changes are sent to the update channel. +type PodConfigNotificationMode int + +const ( + // PodConfigNotificationUnknown is the default value for + // PodConfigNotificationMode when uninitialized. + PodConfigNotificationUnknown = iota + // PodConfigNotificationSnapshot delivers the full configuration as a SET whenever + // any change occurs. + PodConfigNotificationSnapshot + // PodConfigNotificationSnapshotAndUpdates delivers an UPDATE message whenever pods are + // changed, and a SET message if there are any additions or removals. + PodConfigNotificationSnapshotAndUpdates + // PodConfigNotificationIncremental delivers ADD, UPDATE, REMOVE, RECONCILE to the update channel. + PodConfigNotificationIncremental +) + +// PodConfig is a configuration mux that merges many sources of pod configuration into a single +// consistent structure, and then delivers incremental change notifications to listeners +// in order. +type PodConfig struct { + pods *podStorage + mux *config.Mux + + // the channel of denormalized changes passed to listeners + updates chan kubetypes.PodUpdate + + // contains the list of all configured sources + sourcesLock sync.Mutex + sources sets.String +} + +// NewPodConfig creates an object that can merge many configuration sources into a stream +// of normalized updates to a pod configuration. +func NewPodConfig(mode PodConfigNotificationMode, recorder record.EventRecorder) *PodConfig { + updates := make(chan kubetypes.PodUpdate, 50) + storage := newPodStorage(updates, mode, recorder) + podConfig := &PodConfig{ + pods: storage, + mux: config.NewMux(storage), + updates: updates, + sources: sets.String{}, + } + return podConfig +} + +// Channel creates or returns a config source channel. The channel +// only accepts PodUpdates +func (c *PodConfig) Channel(source string) chan<- interface{} { + c.sourcesLock.Lock() + defer c.sourcesLock.Unlock() + c.sources.Insert(source) + return c.mux.Channel(source) +} + +// SeenAllSources returns true if seenSources contains all sources in the +// config, and also this config has received a SET message from each source. +func (c *PodConfig) SeenAllSources(seenSources sets.String) bool { + if c.pods == nil { + return false + } + glog.V(6).Infof("Looking for %v, have seen %v", c.sources.List(), seenSources) + return seenSources.HasAll(c.sources.List()...) && c.pods.seenSources(c.sources.List()...) +} + +// Updates returns a channel of updates to the configuration, properly denormalized. +func (c *PodConfig) Updates() <-chan kubetypes.PodUpdate { + return c.updates +} + +// Sync requests the full configuration be delivered to the update channel. +func (c *PodConfig) Sync() { + c.pods.Sync() +} + +// podStorage manages the current pod state at any point in time and ensures updates +// to the channel are delivered in order. Note that this object is an in-memory source of +// "truth" and on creation contains zero entries. Once all previously read sources are +// available, then this object should be considered authoritative. +type podStorage struct { + podLock sync.RWMutex + // map of source name to pod name to pod reference + pods map[string]map[string]*api.Pod + mode PodConfigNotificationMode + + // ensures that updates are delivered in strict order + // on the updates channel + updateLock sync.Mutex + updates chan<- kubetypes.PodUpdate + + // contains the set of all sources that have sent at least one SET + sourcesSeenLock sync.Mutex + sourcesSeen sets.String + + // the EventRecorder to use + recorder record.EventRecorder +} + +// TODO: PodConfigNotificationMode could be handled by a listener to the updates channel +// in the future, especially with multiple listeners. +// TODO: allow initialization of the current state of the store with snapshotted version. +func newPodStorage(updates chan<- kubetypes.PodUpdate, mode PodConfigNotificationMode, recorder record.EventRecorder) *podStorage { + return &podStorage{ + pods: make(map[string]map[string]*api.Pod), + mode: mode, + updates: updates, + sourcesSeen: sets.String{}, + recorder: recorder, + } +} + +// Merge normalizes a set of incoming changes from different sources into a map of all Pods +// and ensures that redundant changes are filtered out, and then pushes zero or more minimal +// updates onto the update channel. Ensures that updates are delivered in order. +func (s *podStorage) Merge(source string, change interface{}) error { + s.updateLock.Lock() + defer s.updateLock.Unlock() + + seenBefore := s.sourcesSeen.Has(source) + adds, updates, deletes, reconciles := s.merge(source, change) + firstSet := !seenBefore && s.sourcesSeen.Has(source) + + // deliver update notifications + switch s.mode { + case PodConfigNotificationIncremental: + if len(deletes.Pods) > 0 { + s.updates <- *deletes + } + if len(adds.Pods) > 0 { + s.updates <- *adds + } + if len(updates.Pods) > 0 { + s.updates <- *updates + } + if firstSet && len(adds.Pods) == 0 && len(updates.Pods) == 0 { + // Send an empty update when first seeing the source and there are + // no ADD or UPDATE pods from the source. This signals kubelet that + // the source is ready. + s.updates <- *adds + } + // Only add reconcile support here, because kubelet doesn't support Snapshot update now. + if len(reconciles.Pods) > 0 { + s.updates <- *reconciles + } + + case PodConfigNotificationSnapshotAndUpdates: + if len(deletes.Pods) > 0 || len(adds.Pods) > 0 || firstSet { + s.updates <- kubetypes.PodUpdate{Pods: s.MergedState().([]*api.Pod), Op: kubetypes.SET, Source: source} + } + if len(updates.Pods) > 0 { + s.updates <- *updates + } + + case PodConfigNotificationSnapshot: + if len(updates.Pods) > 0 || len(deletes.Pods) > 0 || len(adds.Pods) > 0 || firstSet { + s.updates <- kubetypes.PodUpdate{Pods: s.MergedState().([]*api.Pod), Op: kubetypes.SET, Source: source} + } + + case PodConfigNotificationUnknown: + fallthrough + default: + panic(fmt.Sprintf("unsupported PodConfigNotificationMode: %#v", s.mode)) + } + + return nil +} + +func (s *podStorage) merge(source string, change interface{}) (adds, updates, deletes, reconciles *kubetypes.PodUpdate) { + s.podLock.Lock() + defer s.podLock.Unlock() + + addPods := []*api.Pod{} + updatePods := []*api.Pod{} + deletePods := []*api.Pod{} + reconcilePods := []*api.Pod{} + + pods := s.pods[source] + if pods == nil { + pods = make(map[string]*api.Pod) + } + + // updatePodFunc is the local function which updates the pod cache *oldPods* with new pods *newPods*. + // After updated, new pod will be stored in the pod cache *pods*. + // Notice that *pods* and *oldPods* could be the same cache. + updatePodsFunc := func(newPods []*api.Pod, oldPods, pods map[string]*api.Pod) { + filtered := filterInvalidPods(newPods, source, s.recorder) + for _, ref := range filtered { + name := kubecontainer.GetPodFullName(ref) + // Annotate the pod with the source before any comparison. + if ref.Annotations == nil { + ref.Annotations = make(map[string]string) + } + ref.Annotations[kubetypes.ConfigSourceAnnotationKey] = source + if existing, found := oldPods[name]; found { + pods[name] = existing + needUpdate, needReconcile := checkAndUpdatePod(existing, ref) + if needUpdate { + updatePods = append(updatePods, existing) + } else if needReconcile { + reconcilePods = append(reconcilePods, existing) + } + continue + } + recordFirstSeenTime(ref) + pods[name] = ref + addPods = append(addPods, ref) + } + } + + update := change.(kubetypes.PodUpdate) + switch update.Op { + case kubetypes.ADD, kubetypes.UPDATE: + if update.Op == kubetypes.ADD { + glog.V(4).Infof("Adding new pods from source %s : %v", source, update.Pods) + } else { + glog.V(4).Infof("Updating pods from source %s : %v", source, update.Pods) + } + updatePodsFunc(update.Pods, pods, pods) + + case kubetypes.REMOVE: + glog.V(4).Infof("Removing a pod %v", update) + for _, value := range update.Pods { + name := kubecontainer.GetPodFullName(value) + if existing, found := pods[name]; found { + // this is a delete + delete(pods, name) + deletePods = append(deletePods, existing) + continue + } + // this is a no-op + } + + case kubetypes.SET: + glog.V(4).Infof("Setting pods for source %s", source) + s.markSourceSet(source) + // Clear the old map entries by just creating a new map + oldPods := pods + pods = make(map[string]*api.Pod) + updatePodsFunc(update.Pods, oldPods, pods) + for name, existing := range oldPods { + if _, found := pods[name]; !found { + // this is a delete + deletePods = append(deletePods, existing) + } + } + + default: + glog.Warningf("Received invalid update type: %v", update) + + } + + s.pods[source] = pods + + adds = &kubetypes.PodUpdate{Op: kubetypes.ADD, Pods: copyPods(addPods), Source: source} + updates = &kubetypes.PodUpdate{Op: kubetypes.UPDATE, Pods: copyPods(updatePods), Source: source} + deletes = &kubetypes.PodUpdate{Op: kubetypes.REMOVE, Pods: copyPods(deletePods), Source: source} + reconciles = &kubetypes.PodUpdate{Op: kubetypes.RECONCILE, Pods: copyPods(reconcilePods), Source: source} + + return adds, updates, deletes, reconciles +} + +func (s *podStorage) markSourceSet(source string) { + s.sourcesSeenLock.Lock() + defer s.sourcesSeenLock.Unlock() + s.sourcesSeen.Insert(source) +} + +func (s *podStorage) seenSources(sources ...string) bool { + s.sourcesSeenLock.Lock() + defer s.sourcesSeenLock.Unlock() + return s.sourcesSeen.HasAll(sources...) +} + +func filterInvalidPods(pods []*api.Pod, source string, recorder record.EventRecorder) (filtered []*api.Pod) { + names := sets.String{} + for i, pod := range pods { + var errlist field.ErrorList + if errs := validation.ValidatePod(pod); len(errs) != 0 { + errlist = append(errlist, errs...) + // If validation fails, don't trust it any further - + // even Name could be bad. + } else { + name := kubecontainer.GetPodFullName(pod) + if names.Has(name) { + // TODO: when validation becomes versioned, this gets a bit + // more complicated. + errlist = append(errlist, field.Duplicate(field.NewPath("metadata", "name"), pod.Name)) + } else { + names.Insert(name) + } + } + if len(errlist) > 0 { + name := bestPodIdentString(pod) + err := errlist.ToAggregate() + glog.Warningf("Pod[%d] (%s) from %s failed validation, ignoring: %v", i+1, name, source, err) + recorder.Eventf(pod, api.EventTypeWarning, kubecontainer.FailedValidation, "Error validating pod %s from %s, ignoring: %v", name, source, err) + continue + } + filtered = append(filtered, pod) + } + return +} + +// Annotations that the kubelet adds to the pod. +var localAnnotations = []string{ + kubetypes.ConfigSourceAnnotationKey, + kubetypes.ConfigMirrorAnnotationKey, + kubetypes.ConfigFirstSeenAnnotationKey, +} + +func isLocalAnnotationKey(key string) bool { + for _, localKey := range localAnnotations { + if key == localKey { + return true + } + } + return false +} + +// isAnnotationMapEqual returns true if the existing annotation Map is equal to candidate except +// for local annotations. +func isAnnotationMapEqual(existingMap, candidateMap map[string]string) bool { + if candidateMap == nil { + candidateMap = make(map[string]string) + } + for k, v := range candidateMap { + if isLocalAnnotationKey(k) { + continue + } + if existingValue, ok := existingMap[k]; ok && existingValue == v { + continue + } + return false + } + for k := range existingMap { + if isLocalAnnotationKey(k) { + continue + } + // stale entry in existing map. + if _, exists := candidateMap[k]; !exists { + return false + } + } + return true +} + +// recordFirstSeenTime records the first seen time of this pod. +func recordFirstSeenTime(pod *api.Pod) { + glog.V(4).Infof("Receiving a new pod %q", format.Pod(pod)) + pod.Annotations[kubetypes.ConfigFirstSeenAnnotationKey] = kubetypes.NewTimestamp().GetString() +} + +// updateAnnotations returns an Annotation map containing the api annotation map plus +// locally managed annotations +func updateAnnotations(existing, ref *api.Pod) { + annotations := make(map[string]string, len(ref.Annotations)+len(localAnnotations)) + for k, v := range ref.Annotations { + annotations[k] = v + } + for _, k := range localAnnotations { + if v, ok := existing.Annotations[k]; ok { + annotations[k] = v + } + } + existing.Annotations = annotations +} + +func podsDifferSemantically(existing, ref *api.Pod) bool { + if reflect.DeepEqual(existing.Spec, ref.Spec) && + reflect.DeepEqual(existing.Labels, ref.Labels) && + reflect.DeepEqual(existing.DeletionTimestamp, ref.DeletionTimestamp) && + reflect.DeepEqual(existing.DeletionGracePeriodSeconds, ref.DeletionGracePeriodSeconds) && + isAnnotationMapEqual(existing.Annotations, ref.Annotations) { + return false + } + return true +} + +// checkAndUpdatePod updates existing, and: +// * if ref makes a meaningful change, returns needUpdate=true +// * if ref makes no meaningful change, but changes the pod status, returns needReconcile=true +// * else return both false +// Now, needUpdate and needReconcile should never be both true +func checkAndUpdatePod(existing, ref *api.Pod) (needUpdate, needReconcile bool) { + // TODO: it would be better to update the whole object and only preserve certain things + // like the source annotation or the UID (to ensure safety) + if !podsDifferSemantically(existing, ref) { + // this is not an update + // Only check reconcile when it is not an update, because if the pod is going to + // be updated, an extra reconcile is unnecessary + if !reflect.DeepEqual(existing.Status, ref.Status) { + // Pod with changed pod status needs reconcile, because kubelet should + // be the source of truth of pod status. + existing.Status = ref.Status + needReconcile = true + } + return + } + // this is an update + + // Overwrite the first-seen time with the existing one. This is our own + // internal annotation, there is no need to update. + ref.Annotations[kubetypes.ConfigFirstSeenAnnotationKey] = existing.Annotations[kubetypes.ConfigFirstSeenAnnotationKey] + + existing.Spec = ref.Spec + existing.Labels = ref.Labels + existing.DeletionTimestamp = ref.DeletionTimestamp + existing.DeletionGracePeriodSeconds = ref.DeletionGracePeriodSeconds + existing.Status = ref.Status + updateAnnotations(existing, ref) + needUpdate = true + return +} + +// Sync sends a copy of the current state through the update channel. +func (s *podStorage) Sync() { + s.updateLock.Lock() + defer s.updateLock.Unlock() + s.updates <- kubetypes.PodUpdate{Pods: s.MergedState().([]*api.Pod), Op: kubetypes.SET, Source: kubetypes.AllSource} +} + +// Object implements config.Accessor +func (s *podStorage) MergedState() interface{} { + s.podLock.RLock() + defer s.podLock.RUnlock() + pods := make([]*api.Pod, 0) + for _, sourcePods := range s.pods { + for _, podRef := range sourcePods { + pod, err := api.Scheme.Copy(podRef) + if err != nil { + glog.Errorf("unable to copy pod: %v", err) + } + pods = append(pods, pod.(*api.Pod)) + } + } + return pods +} + +func bestPodIdentString(pod *api.Pod) string { + namespace := pod.Namespace + if namespace == "" { + namespace = "" + } + name := pod.Name + if name == "" { + name = "" + } + return fmt.Sprintf("%s.%s", name, namespace) +} + +func copyPods(sourcePods []*api.Pod) []*api.Pod { + pods := []*api.Pod{} + for _, source := range sourcePods { + // Use a deep copy here just in case + pod, err := api.Scheme.Copy(source) + if err != nil { + glog.Errorf("unable to copy pod: %v", err) + } + pods = append(pods, pod.(*api.Pod)) + } + return pods +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/config/config_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/config/config_test.go new file mode 100644 index 000000000..5bc6a114a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/config/config_test.go @@ -0,0 +1,396 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 config + +import ( + "math/rand" + "reflect" + "sort" + "strconv" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/conversion" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/securitycontext" + "k8s.io/kubernetes/pkg/types" +) + +const ( + TestSource = "test" +) + +func expectEmptyChannel(t *testing.T, ch <-chan interface{}) { + select { + case update := <-ch: + t.Errorf("Expected no update in channel, Got %v", update) + default: + } +} + +type sortedPods []*api.Pod + +func (s sortedPods) Len() int { + return len(s) +} +func (s sortedPods) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} +func (s sortedPods) Less(i, j int) bool { + return s[i].Namespace < s[j].Namespace +} + +func CreateValidPod(name, namespace string) *api.Pod { + return &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: types.UID(name), // for the purpose of testing, this is unique enough + Name: name, + Namespace: namespace, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{ + { + Name: "ctr", + Image: "image", + ImagePullPolicy: "IfNotPresent", + SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), + }, + }, + }, + } +} + +func CreatePodUpdate(op kubetypes.PodOperation, source string, pods ...*api.Pod) kubetypes.PodUpdate { + return kubetypes.PodUpdate{Pods: pods, Op: op, Source: source} +} + +func createPodConfigTester(mode PodConfigNotificationMode) (chan<- interface{}, <-chan kubetypes.PodUpdate, *PodConfig) { + eventBroadcaster := record.NewBroadcaster() + config := NewPodConfig(mode, eventBroadcaster.NewRecorder(api.EventSource{Component: "kubelet"})) + channel := config.Channel(TestSource) + ch := config.Updates() + return channel, ch, config +} + +func expectPodUpdate(t *testing.T, ch <-chan kubetypes.PodUpdate, expected ...kubetypes.PodUpdate) { + for i := range expected { + update := <-ch + sort.Sort(sortedPods(update.Pods)) + sort.Sort(sortedPods(expected[i].Pods)) + // Make copies of the expected/actual update to compare all fields + // except for "Pods", which are compared separately below. + expectedCopy, updateCopy := expected[i], update + expectedCopy.Pods, updateCopy.Pods = nil, nil + if !api.Semantic.DeepEqual(expectedCopy, updateCopy) { + t.Fatalf("Expected %#v, Got %#v", expectedCopy, updateCopy) + } + + if len(expected[i].Pods) != len(update.Pods) { + t.Fatalf("Expected %#v, Got %#v", expected[i], update) + } + // Compare pods one by one. This is necessary because we don't want to + // compare local annotations. + for j := range expected[i].Pods { + if podsDifferSemantically(expected[i].Pods[j], update.Pods[j]) || !reflect.DeepEqual(expected[i].Pods[j].Status, update.Pods[j].Status) { + t.Fatalf("Expected %#v, Got %#v", expected[i].Pods[j], update.Pods[j]) + } + } + } + expectNoPodUpdate(t, ch) +} + +func expectNoPodUpdate(t *testing.T, ch <-chan kubetypes.PodUpdate) { + select { + case update := <-ch: + t.Errorf("Expected no update in channel, Got %#v", update) + default: + } +} + +func TestNewPodAdded(t *testing.T) { + channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental) + + // see an update + podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))) + + config.Sync() + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new"))) +} + +func TestNewPodAddedInvalidNamespace(t *testing.T) { + channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental) + + // see an update + podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "")) + channel <- podUpdate + config.Sync() + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource)) +} + +func TestNewPodAddedDefaultNamespace(t *testing.T) { + channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental) + + // see an update + podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default"))) + + config.Sync() + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "default"))) +} + +func TestNewPodAddedDifferentNamespaces(t *testing.T) { + channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental) + + // see an update + podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default"))) + + // see an update in another namespace + podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))) + + config.Sync() + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "default"), CreateValidPod("foo", "new"))) +} + +func TestInvalidPodFiltered(t *testing.T) { + channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental) + + // see an update + podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))) + + // add an invalid update + podUpdate = CreatePodUpdate(kubetypes.UPDATE, TestSource, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}) + channel <- podUpdate + expectNoPodUpdate(t, ch) +} + +func TestNewPodAddedSnapshotAndUpdates(t *testing.T) { + channel, ch, config := createPodConfigTester(PodConfigNotificationSnapshotAndUpdates) + + // see an set + podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo", "new"))) + + config.Sync() + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new"))) + + // container updates are separated as UPDATE + pod := *podUpdate.Pods[0] + pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test", ImagePullPolicy: api.PullIfNotPresent}} + channel <- CreatePodUpdate(kubetypes.ADD, TestSource, &pod) + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, &pod)) +} + +func TestNewPodAddedSnapshot(t *testing.T) { + channel, ch, config := createPodConfigTester(PodConfigNotificationSnapshot) + + // see an set + podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo", "new"))) + + config.Sync() + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new"))) + + // container updates are separated as UPDATE + pod := *podUpdate.Pods[0] + pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test", ImagePullPolicy: api.PullIfNotPresent}} + channel <- CreatePodUpdate(kubetypes.ADD, TestSource, &pod) + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, &pod)) +} + +func TestNewPodAddedUpdatedRemoved(t *testing.T) { + channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental) + + // should register an add + podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))) + + // should ignore ADDs that are identical + expectNoPodUpdate(t, ch) + + // an kubetypes.ADD should be converted to kubetypes.UPDATE + pod := CreateValidPod("foo", "new") + pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test", ImagePullPolicy: api.PullIfNotPresent}} + podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, pod) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod)) + + podUpdate = CreatePodUpdate(kubetypes.REMOVE, TestSource, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "new"}}) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.REMOVE, TestSource, pod)) +} + +func TestNewPodAddedUpdatedSet(t *testing.T) { + channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental) + + // should register an add + podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"), CreateValidPod("foo2", "new"), CreateValidPod("foo3", "new")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"), CreateValidPod("foo2", "new"), CreateValidPod("foo3", "new"))) + + // should ignore ADDs that are identical + expectNoPodUpdate(t, ch) + + // should be converted to an kubetypes.ADD, kubetypes.REMOVE, and kubetypes.UPDATE + pod := CreateValidPod("foo2", "new") + pod.Spec.Containers = []api.Container{{Name: "bar", Image: "test", ImagePullPolicy: api.PullIfNotPresent}} + podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, pod, CreateValidPod("foo3", "new"), CreateValidPod("foo4", "new")) + channel <- podUpdate + expectPodUpdate(t, ch, + CreatePodUpdate(kubetypes.REMOVE, TestSource, CreateValidPod("foo", "new")), + CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo4", "new")), + CreatePodUpdate(kubetypes.UPDATE, TestSource, pod)) +} + +func TestNewPodAddedSetReconciled(t *testing.T) { + // Create and touch new test pods, return the new pods and touched pod. We should create new pod list + // before touching to avoid data race. + newTestPods := func(touchStatus, touchSpec bool) ([]*api.Pod, *api.Pod) { + pods := []*api.Pod{ + CreateValidPod("changable-pod-0", "new"), + CreateValidPod("constant-pod-1", "new"), + CreateValidPod("constant-pod-2", "new"), + } + if touchStatus { + pods[0].Status = api.PodStatus{Message: strconv.Itoa(rand.Int())} + } + if touchSpec { + pods[0].Spec.Containers[0].Name = strconv.Itoa(rand.Int()) + } + return pods, pods[0] + } + for _, op := range []kubetypes.PodOperation{ + kubetypes.ADD, + kubetypes.SET, + } { + var podWithStatusChange *api.Pod + pods, _ := newTestPods(false, false) + channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental) + + // Use SET to initialize the config, especially initialize the source set + channel <- CreatePodUpdate(kubetypes.SET, TestSource, pods...) + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, pods...)) + + // If status is not changed, no reconcile should be triggered + channel <- CreatePodUpdate(op, TestSource, pods...) + expectNoPodUpdate(t, ch) + + // If the pod status is changed and not updated, a reconcile should be triggered + pods, podWithStatusChange = newTestPods(true, false) + channel <- CreatePodUpdate(op, TestSource, pods...) + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.RECONCILE, TestSource, podWithStatusChange)) + + // If the pod status is changed, but the pod is also updated, no reconcile should be triggered + pods, podWithStatusChange = newTestPods(true, true) + channel <- CreatePodUpdate(op, TestSource, pods...) + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, podWithStatusChange)) + } +} + +func TestInitialEmptySet(t *testing.T) { + for _, test := range []struct { + mode PodConfigNotificationMode + op kubetypes.PodOperation + }{ + {PodConfigNotificationIncremental, kubetypes.ADD}, + {PodConfigNotificationSnapshot, kubetypes.SET}, + {PodConfigNotificationSnapshotAndUpdates, kubetypes.SET}, + } { + channel, ch, _ := createPodConfigTester(test.mode) + + // should register an empty PodUpdate operation + podUpdate := CreatePodUpdate(kubetypes.SET, TestSource) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(test.op, TestSource)) + + // should ignore following empty sets + podUpdate = CreatePodUpdate(kubetypes.SET, TestSource) + channel <- podUpdate + podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(test.op, TestSource, CreateValidPod("foo", "new"))) + } +} + +func TestPodUpdateAnnotations(t *testing.T) { + channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental) + + pod := CreateValidPod("foo2", "new") + pod.Annotations = make(map[string]string, 0) + pod.Annotations["kubernetes.io/blah"] = "blah" + + clone, err := conversion.NewCloner().DeepCopy(pod) + if err != nil { + t.Fatalf("%v", err) + } + + podUpdate := CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), clone.(*api.Pod), CreateValidPod("foo3", "new")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new"))) + + pod.Annotations["kubenetes.io/blah"] = "superblah" + podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod)) + + pod.Annotations["kubernetes.io/otherblah"] = "doh" + podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod)) + + delete(pod.Annotations, "kubernetes.io/blah") + podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new")) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod)) +} + +func TestPodUpdateLabels(t *testing.T) { + channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental) + + pod := CreateValidPod("foo2", "new") + pod.Labels = make(map[string]string, 0) + pod.Labels["key"] = "value" + + clone, err := conversion.NewCloner().DeepCopy(pod) + if err != nil { + t.Fatalf("%v", err) + } + + podUpdate := CreatePodUpdate(kubetypes.SET, TestSource, clone.(*api.Pod)) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, pod)) + + pod.Labels["key"] = "newValue" + podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, pod) + channel <- podUpdate + expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod)) + +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/config/doc.go b/vendor/k8s.io/kubernetes/pkg/kubelet/config/doc.go new file mode 100644 index 000000000..511d05522 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/config/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 config implements the pod configuration readers. +package config diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/config/file.go b/vendor/k8s.io/kubernetes/pkg/kubelet/config/file.go new file mode 100644 index 000000000..da5cd7400 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/config/file.go @@ -0,0 +1,161 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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. +*/ + +// Reads the pod configuration from file or a directory of files. +package config + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "time" + + "k8s.io/kubernetes/pkg/api" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/util/wait" + + "github.com/golang/glog" +) + +type sourceFile struct { + path string + nodeName string + updates chan<- interface{} +} + +func NewSourceFile(path string, nodeName string, period time.Duration, updates chan<- interface{}) { + config := &sourceFile{ + path: path, + nodeName: nodeName, + updates: updates, + } + glog.V(1).Infof("Watching path %q", path) + go wait.Until(config.run, period, wait.NeverStop) +} + +func (s *sourceFile) run() { + if err := s.extractFromPath(); err != nil { + glog.Errorf("Unable to read config path %q: %v", s.path, err) + } +} + +func (s *sourceFile) applyDefaults(pod *api.Pod, source string) error { + return applyDefaults(pod, source, true, s.nodeName) +} + +func (s *sourceFile) extractFromPath() error { + path := s.path + statInfo, err := os.Stat(path) + if err != nil { + if !os.IsNotExist(err) { + return err + } + // Emit an update with an empty PodList to allow FileSource to be marked as seen + s.updates <- kubetypes.PodUpdate{Pods: []*api.Pod{}, Op: kubetypes.SET, Source: kubetypes.FileSource} + return fmt.Errorf("path does not exist, ignoring") + } + + switch { + case statInfo.Mode().IsDir(): + pods, err := s.extractFromDir(path) + if err != nil { + return err + } + s.updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.FileSource} + + case statInfo.Mode().IsRegular(): + pod, err := s.extractFromFile(path) + if err != nil { + return err + } + s.updates <- kubetypes.PodUpdate{Pods: []*api.Pod{pod}, Op: kubetypes.SET, Source: kubetypes.FileSource} + + default: + return fmt.Errorf("path is not a directory or file") + } + + return nil +} + +// Get as many pod configs as we can from a directory. Return an error if and only if something +// prevented us from reading anything at all. Do not return an error if only some files +// were problematic. +func (s *sourceFile) extractFromDir(name string) ([]*api.Pod, error) { + dirents, err := filepath.Glob(filepath.Join(name, "[^.]*")) + if err != nil { + return nil, fmt.Errorf("glob failed: %v", err) + } + + pods := make([]*api.Pod, 0) + if len(dirents) == 0 { + return pods, nil + } + + sort.Strings(dirents) + for _, path := range dirents { + statInfo, err := os.Stat(path) + if err != nil { + glog.V(1).Infof("Can't get metadata for %q: %v", path, err) + continue + } + + switch { + case statInfo.Mode().IsDir(): + glog.V(1).Infof("Not recursing into config path %q", path) + case statInfo.Mode().IsRegular(): + pod, err := s.extractFromFile(path) + if err != nil { + glog.V(1).Infof("Can't process config file %q: %v", path, err) + } else { + pods = append(pods, pod) + } + default: + glog.V(1).Infof("Config path %q is not a directory or file: %v", path, statInfo.Mode()) + } + } + return pods, nil +} + +func (s *sourceFile) extractFromFile(filename string) (pod *api.Pod, err error) { + glog.V(3).Infof("Reading config file %q", filename) + file, err := os.Open(filename) + if err != nil { + return pod, err + } + defer file.Close() + + data, err := ioutil.ReadAll(file) + if err != nil { + return pod, err + } + + defaultFn := func(pod *api.Pod) error { + return s.applyDefaults(pod, filename) + } + + parsed, pod, podErr := tryDecodeSinglePod(data, defaultFn) + if parsed { + if podErr != nil { + return pod, podErr + } + return pod, nil + } + + return pod, fmt.Errorf("%v: read '%v', but couldn't parse as pod(%v).\n", + filename, string(data), podErr) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/config/file_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/config/file_test.go new file mode 100644 index 000000000..fad1f227b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/config/file_test.go @@ -0,0 +1,196 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 config + +import ( + "io/ioutil" + "os" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/validation" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/securitycontext" + utiltesting "k8s.io/kubernetes/pkg/util/testing" + "k8s.io/kubernetes/pkg/util/wait" +) + +func TestExtractFromNonExistentFile(t *testing.T) { + ch := make(chan interface{}, 1) + c := sourceFile{"/some/fake/file", "localhost", ch} + err := c.extractFromPath() + if err == nil { + t.Errorf("Expected error") + } +} + +func TestUpdateOnNonExistentFile(t *testing.T) { + ch := make(chan interface{}) + NewSourceFile("random_non_existent_path", "localhost", time.Millisecond, ch) + select { + case got := <-ch: + update := got.(kubetypes.PodUpdate) + expected := CreatePodUpdate(kubetypes.SET, kubetypes.FileSource) + if !api.Semantic.DeepDerivative(expected, update) { + t.Fatalf("Expected %#v, Got %#v", expected, update) + } + + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Expected update, timeout instead") + } +} + +func writeTestFile(t *testing.T, dir, name string, contents string) *os.File { + file, err := ioutil.TempFile(os.TempDir(), "test_pod_config") + if err != nil { + t.Fatalf("Unable to create test file %#v", err) + } + file.Close() + if err := ioutil.WriteFile(file.Name(), []byte(contents), 0555); err != nil { + t.Fatalf("Unable to write test file %#v", err) + } + return file +} + +func TestReadPodsFromFile(t *testing.T) { + hostname := "random-test-hostname" + grace := int64(30) + var testCases = []struct { + desc string + pod runtime.Object + expected kubetypes.PodUpdate + }{ + { + desc: "Simple pod", + pod: &api.Pod{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Pod", + APIVersion: "", + }, + ObjectMeta: api.ObjectMeta{ + Name: "test", + UID: "12345", + Namespace: "mynamespace", + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Name: "image", Image: "test/image", SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}}, + SecurityContext: &api.PodSecurityContext{}, + }, + Status: api.PodStatus{ + Phase: api.PodPending, + }, + }, + expected: CreatePodUpdate(kubetypes.SET, kubetypes.FileSource, &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "test-" + hostname, + UID: "12345", + Namespace: "mynamespace", + Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "12345"}, + SelfLink: getSelfLink("test-"+hostname, "mynamespace"), + }, + Spec: api.PodSpec{ + NodeName: hostname, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + TerminationGracePeriodSeconds: &grace, + Containers: []api.Container{{ + Name: "image", + Image: "test/image", + TerminationMessagePath: "/dev/termination-log", + ImagePullPolicy: "Always", + SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}}, + SecurityContext: &api.PodSecurityContext{}, + }, + Status: api.PodStatus{ + Phase: api.PodPending, + }, + }), + }, + } + + for _, testCase := range testCases { + func() { + var versionedPod runtime.Object + err := testapi.Default.Converter().Convert(&testCase.pod, &versionedPod) + if err != nil { + t.Fatalf("%s: error in versioning the pod: %v", testCase.desc, err) + } + fileContents, err := runtime.Encode(testapi.Default.Codec(), versionedPod) + if err != nil { + t.Fatalf("%s: error in encoding the pod: %v", testCase.desc, err) + } + + file := writeTestFile(t, os.TempDir(), "test_pod_config", string(fileContents)) + defer os.Remove(file.Name()) + + ch := make(chan interface{}) + NewSourceFile(file.Name(), hostname, time.Millisecond, ch) + select { + case got := <-ch: + update := got.(kubetypes.PodUpdate) + for _, pod := range update.Pods { + if errs := validation.ValidatePod(pod); len(errs) > 0 { + t.Errorf("%s: Invalid pod %#v, %#v", testCase.desc, pod, errs) + } + } + if !api.Semantic.DeepEqual(testCase.expected, update) { + t.Errorf("%s: Expected %#v, Got %#v", testCase.desc, testCase.expected, update) + } + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("%s: Expected update, timeout instead", testCase.desc) + } + }() + } +} + +func TestExtractFromBadDataFile(t *testing.T) { + file := writeTestFile(t, os.TempDir(), "test_pod_config", string([]byte{1, 2, 3})) + defer os.Remove(file.Name()) + + ch := make(chan interface{}, 1) + c := sourceFile{file.Name(), "localhost", ch} + err := c.extractFromPath() + if err == nil { + t.Fatalf("Expected error") + } + expectEmptyChannel(t, ch) +} + +func TestExtractFromEmptyDir(t *testing.T) { + dirName, err := utiltesting.MkTmpdir("file-test") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer os.RemoveAll(dirName) + + ch := make(chan interface{}, 1) + c := sourceFile{dirName, "localhost", ch} + err = c.extractFromPath() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + update := (<-ch).(kubetypes.PodUpdate) + expected := CreatePodUpdate(kubetypes.SET, kubetypes.FileSource) + if !api.Semantic.DeepEqual(expected, update) { + t.Errorf("Expected %#v, Got %#v", expected, update) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/config/http.go b/vendor/k8s.io/kubernetes/pkg/kubelet/config/http.go new file mode 100644 index 000000000..0752e5fa3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/config/http.go @@ -0,0 +1,141 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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. +*/ + +// Reads the pod configuration from an HTTP GET response. +package config + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "time" + + "k8s.io/kubernetes/pkg/api" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/util/wait" + + "github.com/golang/glog" +) + +type sourceURL struct { + url string + header http.Header + nodeName string + updates chan<- interface{} + data []byte + failureLogs int + client *http.Client +} + +func NewSourceURL(url string, header http.Header, nodeName string, period time.Duration, updates chan<- interface{}) { + config := &sourceURL{ + url: url, + header: header, + nodeName: nodeName, + updates: updates, + data: nil, + // Timing out requests leads to retries. This client is only used to + // read the the manifest URL passed to kubelet. + client: &http.Client{Timeout: 10 * time.Second}, + } + glog.V(1).Infof("Watching URL %s", url) + go wait.Until(config.run, period, wait.NeverStop) +} + +func (s *sourceURL) run() { + if err := s.extractFromURL(); err != nil { + // Don't log this multiple times per minute. The first few entries should be + // enough to get the point across. + if s.failureLogs < 3 { + glog.Warningf("Failed to read pods from URL: %v", err) + } else if s.failureLogs == 3 { + glog.Warningf("Failed to read pods from URL. Dropping verbosity of this message to V(4): %v", err) + } else { + glog.V(4).Infof("Failed to read pods from URL: %v", err) + } + s.failureLogs++ + } else { + if s.failureLogs > 0 { + glog.Info("Successfully read pods from URL.") + s.failureLogs = 0 + } + } +} + +func (s *sourceURL) applyDefaults(pod *api.Pod) error { + return applyDefaults(pod, s.url, false, s.nodeName) +} + +func (s *sourceURL) extractFromURL() error { + req, err := http.NewRequest("GET", s.url, nil) + if err != nil { + return err + } + req.Header = s.header + resp, err := s.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + if resp.StatusCode != 200 { + return fmt.Errorf("%v: %v", s.url, resp.Status) + } + if len(data) == 0 { + // Emit an update with an empty PodList to allow HTTPSource to be marked as seen + s.updates <- kubetypes.PodUpdate{Pods: []*api.Pod{}, Op: kubetypes.SET, Source: kubetypes.HTTPSource} + return fmt.Errorf("zero-length data received from %v", s.url) + } + // Short circuit if the data has not changed since the last time it was read. + if bytes.Compare(data, s.data) == 0 { + return nil + } + s.data = data + + // First try as it is a single pod. + parsed, pod, singlePodErr := tryDecodeSinglePod(data, s.applyDefaults) + if parsed { + if singlePodErr != nil { + // It parsed but could not be used. + return singlePodErr + } + s.updates <- kubetypes.PodUpdate{Pods: []*api.Pod{pod}, Op: kubetypes.SET, Source: kubetypes.HTTPSource} + return nil + } + + // That didn't work, so try a list of pods. + parsed, podList, multiPodErr := tryDecodePodList(data, s.applyDefaults) + if parsed { + if multiPodErr != nil { + // It parsed but could not be used. + return multiPodErr + } + pods := make([]*api.Pod, 0) + for i := range podList.Items { + pods = append(pods, &podList.Items[i]) + } + s.updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.HTTPSource} + return nil + } + + return fmt.Errorf("%v: received '%v', but couldn't parse as "+ + "single (%v) or multiple pods (%v).\n", + s.url, string(data), singlePodErr, multiPodErr) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/config/http_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/config/http_test.go new file mode 100644 index 000000000..a2d9359bd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/config/http_test.go @@ -0,0 +1,357 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 config + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/validation" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/runtime" + utiltesting "k8s.io/kubernetes/pkg/util/testing" +) + +func TestURLErrorNotExistNoUpdate(t *testing.T) { + ch := make(chan interface{}) + NewSourceURL("http://localhost:49575/_not_found_", http.Header{}, "localhost", time.Millisecond, ch) + select { + case got := <-ch: + t.Errorf("Expected no update, Got %#v", got) + case <-time.After(2 * time.Millisecond): + } +} + +func TestExtractFromHttpBadness(t *testing.T) { + ch := make(chan interface{}, 1) + c := sourceURL{"http://localhost:49575/_not_found_", http.Header{}, "other", ch, nil, 0, http.DefaultClient} + if err := c.extractFromURL(); err == nil { + t.Errorf("Expected error") + } + expectEmptyChannel(t, ch) +} + +func TestExtractInvalidPods(t *testing.T) { + var testCases = []struct { + desc string + pod *api.Pod + }{ + { + desc: "No version", + pod: &api.Pod{TypeMeta: unversioned.TypeMeta{APIVersion: ""}}, + }, + { + desc: "Invalid version", + pod: &api.Pod{TypeMeta: unversioned.TypeMeta{APIVersion: "v1betta2"}}, + }, + { + desc: "Invalid volume name", + pod: &api.Pod{ + TypeMeta: unversioned.TypeMeta{APIVersion: testapi.Default.GroupVersion().String()}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "_INVALID_"}}, + }, + }, + }, + { + desc: "Duplicate volume names", + pod: &api.Pod{ + TypeMeta: unversioned.TypeMeta{APIVersion: testapi.Default.GroupVersion().String()}, + Spec: api.PodSpec{ + Volumes: []api.Volume{{Name: "repeated"}, {Name: "repeated"}}, + }, + }, + }, + { + desc: "Unspecified container name", + pod: &api.Pod{ + TypeMeta: unversioned.TypeMeta{APIVersion: testapi.Default.GroupVersion().String()}, + Spec: api.PodSpec{ + Containers: []api.Container{{Name: ""}}, + }, + }, + }, + { + desc: "Invalid container name", + pod: &api.Pod{ + TypeMeta: unversioned.TypeMeta{APIVersion: testapi.Default.GroupVersion().String()}, + Spec: api.PodSpec{ + Containers: []api.Container{{Name: "_INVALID_"}}, + }, + }, + }, + } + for _, testCase := range testCases { + data, err := json.Marshal(testCase.pod) + if err != nil { + t.Fatalf("%s: Some weird json problem: %v", testCase.desc, err) + } + fakeHandler := utiltesting.FakeHandler{ + StatusCode: 200, + ResponseBody: string(data), + } + testServer := httptest.NewServer(&fakeHandler) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + ch := make(chan interface{}, 1) + c := sourceURL{testServer.URL, http.Header{}, "localhost", ch, nil, 0, http.DefaultClient} + if err := c.extractFromURL(); err == nil { + t.Errorf("%s: Expected error", testCase.desc) + } + } +} + +func TestExtractPodsFromHTTP(t *testing.T) { + hostname := "different-value" + + grace := int64(30) + var testCases = []struct { + desc string + pods runtime.Object + expected kubetypes.PodUpdate + }{ + { + desc: "Single pod", + pods: &api.Pod{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Pod", + APIVersion: "", + }, + ObjectMeta: api.ObjectMeta{ + Name: "foo", + UID: "111", + Namespace: "mynamespace", + }, + Spec: api.PodSpec{ + NodeName: hostname, + Containers: []api.Container{{Name: "1", Image: "foo", ImagePullPolicy: api.PullAlways}}, + SecurityContext: &api.PodSecurityContext{}, + }, + Status: api.PodStatus{ + Phase: api.PodPending, + }, + }, + expected: CreatePodUpdate(kubetypes.SET, + kubetypes.HTTPSource, + &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "111", + Name: "foo" + "-" + hostname, + Namespace: "mynamespace", + Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "111"}, + SelfLink: getSelfLink("foo-"+hostname, "mynamespace"), + }, + Spec: api.PodSpec{ + NodeName: hostname, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + SecurityContext: &api.PodSecurityContext{}, + TerminationGracePeriodSeconds: &grace, + + Containers: []api.Container{{ + Name: "1", + Image: "foo", + TerminationMessagePath: "/dev/termination-log", + ImagePullPolicy: "Always", + }}, + }, + Status: api.PodStatus{ + Phase: api.PodPending, + }, + }), + }, + { + desc: "Multiple pods", + pods: &api.PodList{ + TypeMeta: unversioned.TypeMeta{ + Kind: "PodList", + APIVersion: "", + }, + Items: []api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + Name: "foo", + UID: "111", + }, + Spec: api.PodSpec{ + NodeName: hostname, + Containers: []api.Container{{Name: "1", Image: "foo", ImagePullPolicy: api.PullAlways}}, + SecurityContext: &api.PodSecurityContext{}, + }, + Status: api.PodStatus{ + Phase: api.PodPending, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + Name: "bar", + UID: "222", + }, + Spec: api.PodSpec{ + NodeName: hostname, + Containers: []api.Container{{Name: "2", Image: "bar:bartag", ImagePullPolicy: ""}}, + SecurityContext: &api.PodSecurityContext{}, + }, + Status: api.PodStatus{ + Phase: api.PodPending, + }, + }, + }, + }, + expected: CreatePodUpdate(kubetypes.SET, + kubetypes.HTTPSource, + &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "111", + Name: "foo" + "-" + hostname, + Namespace: "default", + Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "111"}, + SelfLink: getSelfLink("foo-"+hostname, kubetypes.NamespaceDefault), + }, + Spec: api.PodSpec{ + NodeName: hostname, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + TerminationGracePeriodSeconds: &grace, + SecurityContext: &api.PodSecurityContext{}, + + Containers: []api.Container{{ + Name: "1", + Image: "foo", + TerminationMessagePath: "/dev/termination-log", + ImagePullPolicy: "Always", + }}, + }, + Status: api.PodStatus{ + Phase: api.PodPending, + }, + }, + &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "222", + Name: "bar" + "-" + hostname, + Namespace: "default", + Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "222"}, + SelfLink: getSelfLink("bar-"+hostname, kubetypes.NamespaceDefault), + }, + Spec: api.PodSpec{ + NodeName: hostname, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + TerminationGracePeriodSeconds: &grace, + SecurityContext: &api.PodSecurityContext{}, + + Containers: []api.Container{{ + Name: "2", + Image: "bar:bartag", + TerminationMessagePath: "/dev/termination-log", + ImagePullPolicy: "IfNotPresent", + }}, + }, + Status: api.PodStatus{ + Phase: api.PodPending, + }, + }), + }, + } + + for _, testCase := range testCases { + var versionedPods runtime.Object + err := testapi.Default.Converter().Convert(&testCase.pods, &versionedPods) + if err != nil { + t.Fatalf("%s: error in versioning the pods: %s", testCase.desc, err) + } + data, err := runtime.Encode(testapi.Default.Codec(), versionedPods) + if err != nil { + t.Fatalf("%s: error in encoding the pod: %v", testCase.desc, err) + } + fakeHandler := utiltesting.FakeHandler{ + StatusCode: 200, + ResponseBody: string(data), + } + testServer := httptest.NewServer(&fakeHandler) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + ch := make(chan interface{}, 1) + c := sourceURL{testServer.URL, http.Header{}, hostname, ch, nil, 0, http.DefaultClient} + if err := c.extractFromURL(); err != nil { + t.Errorf("%s: Unexpected error: %v", testCase.desc, err) + continue + } + update := (<-ch).(kubetypes.PodUpdate) + + if !api.Semantic.DeepEqual(testCase.expected, update) { + t.Errorf("%s: Expected: %#v, Got: %#v", testCase.desc, testCase.expected, update) + } + for _, pod := range update.Pods { + if errs := validation.ValidatePod(pod); len(errs) != 0 { + t.Errorf("%s: Expected no validation errors on %#v, Got %v", testCase.desc, pod, errs.ToAggregate()) + } + } + } +} + +func TestURLWithHeader(t *testing.T) { + pod := &api.Pod{ + TypeMeta: unversioned.TypeMeta{ + APIVersion: testapi.Default.GroupVersion().String(), + Kind: "Pod", + }, + ObjectMeta: api.ObjectMeta{ + Name: "foo", + UID: "111", + Namespace: "mynamespace", + }, + Spec: api.PodSpec{ + NodeName: "localhost", + Containers: []api.Container{{Name: "1", Image: "foo", ImagePullPolicy: api.PullAlways}}, + }, + } + data, err := json.Marshal(pod) + if err != nil { + t.Fatalf("Unexpected json marshalling error: %v", err) + } + fakeHandler := utiltesting.FakeHandler{ + StatusCode: 200, + ResponseBody: string(data), + } + testServer := httptest.NewServer(&fakeHandler) + // TODO: Uncomment when fix #19254 + // defer testServer.Close() + ch := make(chan interface{}, 1) + header := make(http.Header) + header.Set("Metadata-Flavor", "Google") + c := sourceURL{testServer.URL, header, "localhost", ch, nil, 0, http.DefaultClient} + if err := c.extractFromURL(); err != nil { + t.Fatalf("Unexpected error extracting from URL: %v", err) + } + update := (<-ch).(kubetypes.PodUpdate) + + headerVal := fakeHandler.RequestReceived.Header["Metadata-Flavor"] + if len(headerVal) != 1 || headerVal[0] != "Google" { + t.Errorf("Header missing expected entry %v. Got %v", header, fakeHandler.RequestReceived.Header) + } + if len(update.Pods) != 1 { + t.Errorf("Received wrong number of pods, expected one: %v", update.Pods) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/cache.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/cache.go new file mode 100644 index 000000000..219ad49f3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/cache.go @@ -0,0 +1,199 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "sync" + "time" + + "k8s.io/kubernetes/pkg/types" +) + +// Cache stores the PodStatus for the pods. It represents *all* the visible +// pods/containers in the container runtime. All cache entries are at least as +// new or newer than the global timestamp (set by UpdateTime()), while +// individual entries may be slightly newer than the global timestamp. If a pod +// has no states known by the runtime, Cache returns an empty PodStatus object +// with ID populated. +// +// Cache provides two methods to retrive the PodStatus: the non-blocking Get() +// and the blocking GetNewerThan() method. The component responsible for +// populating the cache is expected to call Delete() to explicitly free the +// cache entries. +type Cache interface { + Get(types.UID) (*PodStatus, error) + Set(types.UID, *PodStatus, error, time.Time) + // GetNewerThan is a blocking call that only returns the status + // when it is newer than the given time. + GetNewerThan(types.UID, time.Time) (*PodStatus, error) + Delete(types.UID) + UpdateTime(time.Time) +} + +type data struct { + // Status of the pod. + status *PodStatus + // Error got when trying to inspect the pod. + err error + // Time when the data was last modfied. + modified time.Time +} + +type subRecord struct { + time time.Time + ch chan *data +} + +// cache implements Cache. +type cache struct { + // Lock which guards all internal data structures. + lock sync.RWMutex + // Map that stores the pod statuses. + pods map[types.UID]*data + // A global timestamp represents how fresh the cached data is. All + // cache content is at the least newer than this timestamp. Note that the + // timestamp is nil after initialization, and will only become non-nil when + // it is ready to serve the cached statuses. + timestamp *time.Time + // Map that stores the subscriber records. + subscribers map[types.UID][]*subRecord +} + +// NewCache creates a pod cache. +func NewCache() Cache { + return &cache{pods: map[types.UID]*data{}, subscribers: map[types.UID][]*subRecord{}} +} + +// Get returns the PodStatus for the pod; callers are expected not to +// modify the objects returned. +func (c *cache) Get(id types.UID) (*PodStatus, error) { + c.lock.RLock() + defer c.lock.RUnlock() + d := c.get(id) + return d.status, d.err +} + +func (c *cache) GetNewerThan(id types.UID, minTime time.Time) (*PodStatus, error) { + ch := c.subscribe(id, minTime) + d := <-ch + return d.status, d.err +} + +// Set sets the PodStatus for the pod. +func (c *cache) Set(id types.UID, status *PodStatus, err error, timestamp time.Time) { + c.lock.Lock() + defer c.lock.Unlock() + defer c.notify(id, timestamp) + c.pods[id] = &data{status: status, err: err, modified: timestamp} +} + +// Delete removes the entry of the pod. +func (c *cache) Delete(id types.UID) { + c.lock.Lock() + defer c.lock.Unlock() + delete(c.pods, id) +} + +// UpdateTime modifies the global timestamp of the cache and notify +// subscribers if needed. +func (c *cache) UpdateTime(timestamp time.Time) { + c.lock.Lock() + defer c.lock.Unlock() + c.timestamp = ×tamp + // Notify all the subscribers if the condition is met. + for id := range c.subscribers { + c.notify(id, *c.timestamp) + } +} + +func makeDefaultData(id types.UID) *data { + return &data{status: &PodStatus{ID: id}, err: nil} +} + +func (c *cache) get(id types.UID) *data { + d, ok := c.pods[id] + if !ok { + // Cache should store *all* pod/container information known by the + // container runtime. A cache miss indicates that there are no states + // regarding the pod last time we queried the container runtime. + // What this *really* means is that there are no visible pod/containers + // associated with this pod. Simply return an default (mostly empty) + // PodStatus to reflect this. + return makeDefaultData(id) + } + return d +} + +// getIfNewerThan returns the data it is newer than the given time. +// Otherwise, it returns nil. The caller should acquire the lock. +func (c *cache) getIfNewerThan(id types.UID, minTime time.Time) *data { + d, ok := c.pods[id] + globalTimestampIsNewer := (c.timestamp != nil && c.timestamp.After(minTime)) + if !ok && globalTimestampIsNewer { + // Status is not cached, but the global timestamp is newer than + // minTime, return the default status. + return makeDefaultData(id) + } + if ok && (d.modified.After(minTime) || globalTimestampIsNewer) { + // Status is cached, return status if either of the following is true. + // * status was modified after minTime + // * the global timestamp of the cache is newer than minTime. + return d + } + // The pod status is not ready. + return nil +} + +// notify sends notifications for pod with the given id, if the requirements +// are met. Note that the caller should acquire the lock. +func (c *cache) notify(id types.UID, timestamp time.Time) { + list, ok := c.subscribers[id] + if !ok { + // No one to notify. + return + } + newList := []*subRecord{} + for i, r := range list { + if timestamp.Before(r.time) { + // Doesn't meet the time requirement; keep the record. + newList = append(newList, list[i]) + continue + } + r.ch <- c.get(id) + close(r.ch) + } + if len(newList) == 0 { + delete(c.subscribers, id) + } else { + c.subscribers[id] = newList + } +} + +func (c *cache) subscribe(id types.UID, timestamp time.Time) chan *data { + ch := make(chan *data, 1) + c.lock.Lock() + defer c.lock.Unlock() + d := c.getIfNewerThan(id, timestamp) + if d != nil { + // If the cache entry is ready, send the data and return immediately. + ch <- d + return ch + } + // Add the subscription record. + c.subscribers[id] = append(c.subscribers[id], &subRecord{time: timestamp, ch: ch}) + return ch +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/cache_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/cache_test.go new file mode 100644 index 000000000..5755005d8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/cache_test.go @@ -0,0 +1,210 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "fmt" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "k8s.io/kubernetes/pkg/types" +) + +func newTestCache() *cache { + c := NewCache() + return c.(*cache) +} + +func TestCacheNotInitialized(t *testing.T) { + cache := newTestCache() + // If the global timestamp is not set, always return nil. + d := cache.getIfNewerThan(types.UID("1234"), time.Time{}) + assert.True(t, d == nil, "should return nil since cache is not initialized") +} + +func getTestPodIDAndStatus(numContainers int) (types.UID, *PodStatus) { + id := types.UID(strconv.FormatInt(time.Now().UnixNano(), 10)) + name := fmt.Sprintf("cache-foo-%s", string(id)) + namespace := "ns" + var status *PodStatus + if numContainers > 0 { + status = &PodStatus{ID: id, Name: name, Namespace: namespace} + } else { + status = &PodStatus{ID: id} + } + for i := 0; i < numContainers; i++ { + status.ContainerStatuses = append(status.ContainerStatuses, &ContainerStatus{Name: string(i)}) + } + return id, status +} + +func TestGetIfNewerThanWhenPodExists(t *testing.T) { + cache := newTestCache() + timestamp := time.Now() + + cases := []struct { + cacheTime time.Time + modified time.Time + expected bool + }{ + { + // Both the global cache timestamp and the modified time are newer + // than the timestamp. + cacheTime: timestamp.Add(time.Second), + modified: timestamp, + expected: true, + }, + { + // Global cache timestamp is newer, but the pod entry modified + // time is older than the given timestamp. This means that the + // entry is up-to-date even though it hasn't changed for a while. + cacheTime: timestamp.Add(time.Second), + modified: timestamp.Add(-time.Second * 10), + expected: true, + }, + { + // Global cache timestamp is older, but the pod entry modified + // time is newer than the given timestamp. This means that the + // entry is up-to-date but the rest of the cache are still being + // updated. + cacheTime: timestamp.Add(-time.Second), + modified: timestamp.Add(time.Second * 3), + expected: true, + }, + { + // Both the global cache timestamp and the modified time are older + // than the given timestamp. + cacheTime: timestamp.Add(-time.Second), + modified: timestamp.Add(-time.Second), + expected: false, + }, + } + for i, c := range cases { + podID, status := getTestPodIDAndStatus(2) + cache.UpdateTime(c.cacheTime) + cache.Set(podID, status, nil, c.modified) + d := cache.getIfNewerThan(podID, timestamp) + assert.Equal(t, c.expected, d != nil, "test[%d]", i) + } +} + +func TestGetPodNewerThanWhenPodDoesNotExist(t *testing.T) { + cache := newTestCache() + cacheTime := time.Now() + cache.UpdateTime(cacheTime) + podID := types.UID("1234") + + cases := []struct { + timestamp time.Time + expected bool + }{ + { + timestamp: cacheTime.Add(-time.Second), + expected: true, + }, + { + timestamp: cacheTime.Add(time.Second), + expected: false, + }, + } + for i, c := range cases { + d := cache.getIfNewerThan(podID, c.timestamp) + assert.Equal(t, c.expected, d != nil, "test[%d]", i) + } +} + +func TestCacheSetAndGet(t *testing.T) { + cache := NewCache() + cases := []struct { + numContainers int + error error + }{ + {numContainers: 3, error: nil}, + {numContainers: 2, error: fmt.Errorf("unable to get status")}, + {numContainers: 0, error: nil}, + } + for i, c := range cases { + podID, status := getTestPodIDAndStatus(c.numContainers) + cache.Set(podID, status, c.error, time.Time{}) + // Read back the status and error stored in cache and make sure they + // match the original ones. + actualStatus, actualErr := cache.Get(podID) + assert.Equal(t, status, actualStatus, "test[%d]", i) + assert.Equal(t, c.error, actualErr, "test[%d]", i) + } +} + +func TestCacheGetPodDoesNotExist(t *testing.T) { + cache := NewCache() + podID, status := getTestPodIDAndStatus(0) + // If the pod does not exist in cache, cache should return an status + // object with id filled. + actualStatus, actualErr := cache.Get(podID) + assert.Equal(t, status, actualStatus) + assert.Equal(t, nil, actualErr) +} + +func TestDelete(t *testing.T) { + cache := &cache{pods: map[types.UID]*data{}} + // Write a new pod status into the cache. + podID, status := getTestPodIDAndStatus(3) + cache.Set(podID, status, nil, time.Time{}) + actualStatus, actualErr := cache.Get(podID) + assert.Equal(t, status, actualStatus) + assert.Equal(t, nil, actualErr) + // Delete the pod from cache, and verify that we get an empty status. + cache.Delete(podID) + expectedStatus := &PodStatus{ID: podID} + actualStatus, actualErr = cache.Get(podID) + assert.Equal(t, expectedStatus, actualStatus) + assert.Equal(t, nil, actualErr) +} + +func verifyNotification(t *testing.T, ch chan *data, expectNotification bool) { + if expectNotification { + assert.True(t, len(ch) > 0, "Did not receive notification") + } else { + assert.True(t, len(ch) < 1, "Should not have triggered the notification") + } + // Drain the channel. + for i := 0; i < len(ch); i++ { + <-ch + } +} + +func TestRegisterNotification(t *testing.T) { + cache := newTestCache() + cacheTime := time.Now() + cache.UpdateTime(cacheTime) + + podID, status := getTestPodIDAndStatus(1) + ch := cache.subscribe(podID, cacheTime.Add(time.Second)) + verifyNotification(t, ch, false) + cache.Set(podID, status, nil, cacheTime.Add(time.Second)) + // The Set operation should've triggered the notification. + verifyNotification(t, ch, true) + + podID, _ = getTestPodIDAndStatus(1) + + ch = cache.subscribe(podID, cacheTime.Add(time.Second)) + verifyNotification(t, ch, false) + cache.UpdateTime(cacheTime.Add(time.Second * 2)) + // The advance of cache timestamp should've triggered the notification. + verifyNotification(t, ch, true) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/container_gc.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/container_gc.go new file mode 100644 index 000000000..cd69c1ab4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/container_gc.go @@ -0,0 +1,68 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "fmt" + "time" +) + +// Specified a policy for garbage collecting containers. +type ContainerGCPolicy struct { + // Minimum age at which a container can be garbage collected, zero for no limit. + MinAge time.Duration + + // Max number of dead containers any single pod (UID, container name) pair is + // allowed to have, less than zero for no limit. + MaxPerPodContainer int + + // Max number of total dead containers, less than zero for no limit. + MaxContainers int +} + +// Manages garbage collection of dead containers. +// +// Implementation is thread-compatible. +type ContainerGC interface { + // Garbage collect containers. + GarbageCollect() error +} + +// TODO(vmarmol): Preferentially remove pod infra containers. +type realContainerGC struct { + // Container runtime + runtime Runtime + + // Policy for garbage collection. + policy ContainerGCPolicy +} + +// New ContainerGC instance with the specified policy. +func NewContainerGC(runtime Runtime, policy ContainerGCPolicy) (ContainerGC, error) { + if policy.MinAge < 0 { + return nil, fmt.Errorf("invalid minimum garbage collection age: %v", policy.MinAge) + } + + return &realContainerGC{ + runtime: runtime, + policy: policy, + }, nil +} + +func (cgc *realContainerGC) GarbageCollect() error { + return cgc.runtime.GarbageCollect(cgc.policy) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/container_reference_manager.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/container_reference_manager.go new file mode 100644 index 000000000..1f44389c7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/container_reference_manager.go @@ -0,0 +1,60 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "sync" + + "k8s.io/kubernetes/pkg/api" +) + +// RefManager manages the references for the containers. +// The references are used for reporting events such as creation, +// failure, etc. This manager is thread-safe, no locks are necessary +// for the caller. +type RefManager struct { + sync.RWMutex + containerIDToRef map[ContainerID]*api.ObjectReference +} + +// NewRefManager creates and returns a container reference manager +// with empty contents. +func NewRefManager() *RefManager { + return &RefManager{containerIDToRef: make(map[ContainerID]*api.ObjectReference)} +} + +// SetRef stores a reference to a pod's container, associating it with the given container ID. +func (c *RefManager) SetRef(id ContainerID, ref *api.ObjectReference) { + c.Lock() + defer c.Unlock() + c.containerIDToRef[id] = ref +} + +// ClearRef forgets the given container id and its associated container reference. +func (c *RefManager) ClearRef(id ContainerID) { + c.Lock() + defer c.Unlock() + delete(c.containerIDToRef, id) +} + +// GetRef returns the container reference of the given ID, or (nil, false) if none is stored. +func (c *RefManager) GetRef(id ContainerID) (ref *api.ObjectReference, ok bool) { + c.RLock() + defer c.RUnlock() + ref, ok = c.containerIDToRef[id] + return ref, ok +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/event.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/event.go new file mode 100644 index 000000000..6694239e9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/event.go @@ -0,0 +1,65 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 container + +const ( + // Container event reason list + CreatedContainer = "Created" + StartedContainer = "Started" + FailedToCreateContainer = "Failed" + FailedToStartContainer = "Failed" + KillingContainer = "Killing" + BackOffStartContainer = "BackOff" + + // Image event reason list + PullingImage = "Pulling" + PulledImage = "Pulled" + FailedToPullImage = "Failed" + FailedToInspectImage = "InspectFailed" + ErrImageNeverPullPolicy = "ErrImageNeverPull" + BackOffPullImage = "BackOff" + + // kubelet event reason list + NodeReady = "NodeReady" + NodeNotReady = "NodeNotReady" + NodeSchedulable = "NodeSchedulable" + NodeNotSchedulable = "NodeNotSchedulable" + StartingKubelet = "Starting" + KubeletSetupFailed = "KubeletSetupFailed" + FailedMountVolume = "FailedMount" + HostPortConflict = "HostPortConflict" + NodeSelectorMismatching = "NodeSelectorMismatching" + InsufficientFreeCPU = "InsufficientFreeCPU" + InsufficientFreeMemory = "InsufficientFreeMemory" + OutOfDisk = "OutOfDisk" + HostNetworkNotSupported = "HostNetworkNotSupported" + UndefinedShaper = "NilShaper" + NodeRebooted = "Rebooted" + + // Image manager event reason list + InvalidDiskCapacity = "InvalidDiskCapacity" + FreeDiskSpaceFailed = "FreeDiskSpaceFailed" + + // Probe event reason list + ContainerUnhealthy = "Unhealthy" + + // Pod worker event reason list + FailedSync = "FailedSync" + + // Config event reason list + FailedValidation = "FailedValidation" +) diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/helpers.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/helpers.go new file mode 100644 index 000000000..b62232fc1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/helpers.go @@ -0,0 +1,181 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "hash/adler32" + "strings" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/kubelet/util/format" + "k8s.io/kubernetes/pkg/runtime" + hashutil "k8s.io/kubernetes/pkg/util/hash" + "k8s.io/kubernetes/third_party/golang/expansion" + + "github.com/golang/glog" +) + +// HandlerRunner runs a lifecycle handler for a container. +type HandlerRunner interface { + Run(containerID ContainerID, pod *api.Pod, container *api.Container, handler *api.Handler) error +} + +// RuntimeHelper wraps kubelet to make container runtime +// able to get necessary informations like the RunContainerOptions, DNS settings. +type RuntimeHelper interface { + GenerateRunContainerOptions(pod *api.Pod, container *api.Container, podIP string) (*RunContainerOptions, error) + GetClusterDNS(pod *api.Pod) (dnsServers []string, dnsSearches []string, err error) + GeneratePodHostNameAndDomain(pod *api.Pod) (hostname string, hostDomain string) +} + +// ShouldContainerBeRestarted checks whether a container needs to be restarted. +// TODO(yifan): Think about how to refactor this. +func ShouldContainerBeRestarted(container *api.Container, pod *api.Pod, podStatus *PodStatus) bool { + // Get latest container status. + status := podStatus.FindContainerStatusByName(container.Name) + // If the container was never started before, we should start it. + // NOTE(random-liu): If all historical containers were GC'd, we'll also return true here. + if status == nil { + return true + } + // Check whether container is running + if status.State == ContainerStateRunning { + return false + } + // Always restart container in unknown state now + if status.State == ContainerStateUnknown { + return true + } + // Check RestartPolicy for dead container + if pod.Spec.RestartPolicy == api.RestartPolicyNever { + glog.V(4).Infof("Already ran container %q of pod %q, do nothing", container.Name, format.Pod(pod)) + return false + } + if pod.Spec.RestartPolicy == api.RestartPolicyOnFailure { + // Check the exit code. + if status.ExitCode == 0 { + glog.V(4).Infof("Already successfully ran container %q of pod %q, do nothing", container.Name, format.Pod(pod)) + return false + } + } + return true +} + +// TODO(random-liu): Convert PodStatus to running Pod, should be deprecated soon +func ConvertPodStatusToRunningPod(podStatus *PodStatus) Pod { + runningPod := Pod{ + ID: podStatus.ID, + Name: podStatus.Name, + Namespace: podStatus.Namespace, + } + for _, containerStatus := range podStatus.ContainerStatuses { + if containerStatus.State != ContainerStateRunning { + continue + } + container := &Container{ + ID: containerStatus.ID, + Name: containerStatus.Name, + Image: containerStatus.Image, + Hash: containerStatus.Hash, + Created: containerStatus.CreatedAt.Unix(), + State: containerStatus.State, + } + runningPod.Containers = append(runningPod.Containers, container) + } + return runningPod +} + +// HashContainer returns the hash of the container. It is used to compare +// the running container with its desired spec. +func HashContainer(container *api.Container) uint64 { + hash := adler32.New() + hashutil.DeepHashObject(hash, *container) + return uint64(hash.Sum32()) +} + +// EnvVarsToMap constructs a map of environment name to value from a slice +// of env vars. +func EnvVarsToMap(envs []EnvVar) map[string]string { + result := map[string]string{} + for _, env := range envs { + result[env.Name] = env.Value + } + + return result +} + +func ExpandContainerCommandAndArgs(container *api.Container, envs []EnvVar) (command []string, args []string) { + mapping := expansion.MappingFuncFor(EnvVarsToMap(envs)) + + if len(container.Command) != 0 { + for _, cmd := range container.Command { + command = append(command, expansion.Expand(cmd, mapping)) + } + } + + if len(container.Args) != 0 { + for _, arg := range container.Args { + args = append(args, expansion.Expand(arg, mapping)) + } + } + + return command, args +} + +// Create an event recorder to record object's event except implicitly required container's, like infra container. +func FilterEventRecorder(recorder record.EventRecorder) record.EventRecorder { + return &innerEventRecorder{ + recorder: recorder, + } +} + +type innerEventRecorder struct { + recorder record.EventRecorder +} + +func (irecorder *innerEventRecorder) shouldRecordEvent(object runtime.Object) (*api.ObjectReference, bool) { + if object == nil { + return nil, false + } + if ref, ok := object.(*api.ObjectReference); ok { + if !strings.HasPrefix(ref.FieldPath, ImplicitContainerPrefix) { + return ref, true + } + } + return nil, false +} + +func (irecorder *innerEventRecorder) Event(object runtime.Object, eventtype, reason, message string) { + if ref, ok := irecorder.shouldRecordEvent(object); ok { + irecorder.recorder.Event(ref, eventtype, reason, message) + } +} + +func (irecorder *innerEventRecorder) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) { + if ref, ok := irecorder.shouldRecordEvent(object); ok { + irecorder.recorder.Eventf(ref, eventtype, reason, messageFmt, args...) + } + +} + +func (irecorder *innerEventRecorder) PastEventf(object runtime.Object, timestamp unversioned.Time, eventtype, reason, messageFmt string, args ...interface{}) { + if ref, ok := irecorder.shouldRecordEvent(object); ok { + irecorder.recorder.PastEventf(ref, timestamp, eventtype, reason, messageFmt, args...) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/helpers_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/helpers_test.go new file mode 100644 index 000000000..435790c95 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/helpers_test.go @@ -0,0 +1,213 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" +) + +func TestEnvVarsToMap(t *testing.T) { + vars := []EnvVar{ + { + Name: "foo", + Value: "bar", + }, + { + Name: "zoo", + Value: "baz", + }, + } + + varMap := EnvVarsToMap(vars) + + if e, a := len(vars), len(varMap); e != a { + t.Errorf("Unexpected map length; expected: %d, got %d", e, a) + } + + if a := varMap["foo"]; a != "bar" { + t.Errorf("Unexpected value of key 'foo': %v", a) + } + + if a := varMap["zoo"]; a != "baz" { + t.Errorf("Unexpected value of key 'zoo': %v", a) + } +} + +func TestExpandCommandAndArgs(t *testing.T) { + cases := []struct { + name string + container *api.Container + envs []EnvVar + expectedCommand []string + expectedArgs []string + }{ + { + name: "none", + container: &api.Container{}, + }, + { + name: "command expanded", + container: &api.Container{ + Command: []string{"foo", "$(VAR_TEST)", "$(VAR_TEST2)"}, + }, + envs: []EnvVar{ + { + Name: "VAR_TEST", + Value: "zoo", + }, + { + Name: "VAR_TEST2", + Value: "boo", + }, + }, + expectedCommand: []string{"foo", "zoo", "boo"}, + }, + { + name: "args expanded", + container: &api.Container{ + Args: []string{"zap", "$(VAR_TEST)", "$(VAR_TEST2)"}, + }, + envs: []EnvVar{ + { + Name: "VAR_TEST", + Value: "hap", + }, + { + Name: "VAR_TEST2", + Value: "trap", + }, + }, + expectedArgs: []string{"zap", "hap", "trap"}, + }, + { + name: "both expanded", + container: &api.Container{ + Command: []string{"$(VAR_TEST2)--$(VAR_TEST)", "foo", "$(VAR_TEST3)"}, + Args: []string{"foo", "$(VAR_TEST)", "$(VAR_TEST2)"}, + }, + envs: []EnvVar{ + { + Name: "VAR_TEST", + Value: "zoo", + }, + { + Name: "VAR_TEST2", + Value: "boo", + }, + { + Name: "VAR_TEST3", + Value: "roo", + }, + }, + expectedCommand: []string{"boo--zoo", "foo", "roo"}, + expectedArgs: []string{"foo", "zoo", "boo"}, + }, + } + + for _, tc := range cases { + actualCommand, actualArgs := ExpandContainerCommandAndArgs(tc.container, tc.envs) + + if e, a := tc.expectedCommand, actualCommand; !reflect.DeepEqual(e, a) { + t.Errorf("%v: unexpected command; expected %v, got %v", tc.name, e, a) + } + + if e, a := tc.expectedArgs, actualArgs; !reflect.DeepEqual(e, a) { + t.Errorf("%v: unexpected args; expected %v, got %v", tc.name, e, a) + } + + } +} + +func TestShouldContainerBeRestarted(t *testing.T) { + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "no-history"}, + {Name: "alive"}, + {Name: "succeed"}, + {Name: "failed"}, + {Name: "unknown"}, + }, + }, + } + podStatus := &PodStatus{ + ID: pod.UID, + Name: pod.Name, + Namespace: pod.Namespace, + ContainerStatuses: []*ContainerStatus{ + { + Name: "alive", + State: ContainerStateRunning, + }, + { + Name: "succeed", + State: ContainerStateExited, + ExitCode: 0, + }, + { + Name: "failed", + State: ContainerStateExited, + ExitCode: 1, + }, + { + Name: "alive", + State: ContainerStateExited, + ExitCode: 2, + }, + { + Name: "unknown", + State: ContainerStateUnknown, + }, + { + Name: "failed", + State: ContainerStateExited, + ExitCode: 3, + }, + }, + } + policies := []api.RestartPolicy{ + api.RestartPolicyNever, + api.RestartPolicyOnFailure, + api.RestartPolicyAlways, + } + expected := map[string][]bool{ + "no-history": {true, true, true}, + "alive": {false, false, false}, + "succeed": {false, false, true}, + "failed": {false, true, true}, + "unknown": {true, true, true}, + } + for _, c := range pod.Spec.Containers { + for i, policy := range policies { + pod.Spec.RestartPolicy = policy + e := expected[c.Name][i] + r := ShouldContainerBeRestarted(&c, pod, podStatus) + if r != e { + t.Errorf("Restart for container %q with restart policy %q expected %t, got %t", + c.Name, policy, e, r) + } + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/image_puller.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/image_puller.go new file mode 100644 index 000000000..23ed9fa7b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/image_puller.go @@ -0,0 +1,123 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "fmt" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/util/flowcontrol" +) + +// imagePuller pulls the image using Runtime.PullImage(). +// It will check the presence of the image, and report the 'image pulling', +// 'image pulled' events correspondingly. +type imagePuller struct { + recorder record.EventRecorder + runtime Runtime + backOff *flowcontrol.Backoff +} + +// enforce compatibility. +var _ ImagePuller = &imagePuller{} + +// NewImagePuller takes an event recorder and container runtime to create a +// image puller that wraps the container runtime's PullImage interface. +func NewImagePuller(recorder record.EventRecorder, runtime Runtime, imageBackOff *flowcontrol.Backoff) ImagePuller { + return &imagePuller{ + recorder: recorder, + runtime: runtime, + backOff: imageBackOff, + } +} + +// shouldPullImage returns whether we should pull an image according to +// the presence and pull policy of the image. +func shouldPullImage(container *api.Container, imagePresent bool) bool { + if container.ImagePullPolicy == api.PullNever { + return false + } + + if container.ImagePullPolicy == api.PullAlways || + (container.ImagePullPolicy == api.PullIfNotPresent && (!imagePresent)) { + return true + } + + return false +} + +// records an event using ref, event msg. log to glog using prefix, msg, logFn +func (puller *imagePuller) logIt(ref *api.ObjectReference, eventtype, event, prefix, msg string, logFn func(args ...interface{})) { + if ref != nil { + puller.recorder.Event(ref, eventtype, event, msg) + } else { + logFn(fmt.Sprint(prefix, " ", msg)) + } +} + +// PullImage pulls the image for the specified pod and container. +func (puller *imagePuller) PullImage(pod *api.Pod, container *api.Container, pullSecrets []api.Secret) (error, string) { + logPrefix := fmt.Sprintf("%s/%s", pod.Name, container.Image) + ref, err := GenerateContainerRef(pod, container) + if err != nil { + glog.Errorf("Couldn't make a ref to pod %v, container %v: '%v'", pod.Name, container.Name, err) + } + + spec := ImageSpec{container.Image} + present, err := puller.runtime.IsImagePresent(spec) + if err != nil { + msg := fmt.Sprintf("Failed to inspect image %q: %v", container.Image, err) + puller.logIt(ref, api.EventTypeWarning, FailedToInspectImage, logPrefix, msg, glog.Warning) + return ErrImageInspect, msg + } + + if !shouldPullImage(container, present) { + if present { + msg := fmt.Sprintf("Container image %q already present on machine", container.Image) + puller.logIt(ref, api.EventTypeNormal, "Pulled", logPrefix, msg, glog.Info) + return nil, "" + } else { + msg := fmt.Sprintf("Container image %q is not present with pull policy of Never", container.Image) + puller.logIt(ref, api.EventTypeWarning, ErrImageNeverPullPolicy, logPrefix, msg, glog.Warning) + return ErrImageNeverPull, msg + } + } + + backOffKey := fmt.Sprintf("%s_%s", pod.UID, container.Image) + if puller.backOff.IsInBackOffSinceUpdate(backOffKey, puller.backOff.Clock.Now()) { + msg := fmt.Sprintf("Back-off pulling image %q", container.Image) + puller.logIt(ref, api.EventTypeNormal, BackOffPullImage, logPrefix, msg, glog.Info) + return ErrImagePullBackOff, msg + } + puller.logIt(ref, api.EventTypeNormal, "Pulling", logPrefix, fmt.Sprintf("pulling image %q", container.Image), glog.Info) + if err := puller.runtime.PullImage(spec, pullSecrets); err != nil { + puller.logIt(ref, api.EventTypeWarning, "Failed", logPrefix, fmt.Sprintf("Failed to pull image %q: %v", container.Image, err), glog.Warning) + puller.backOff.Next(backOffKey, puller.backOff.Clock.Now()) + if err == RegistryUnavailable { + msg := fmt.Sprintf("image pull failed for %s because the registry is unavailable.", container.Image) + return err, msg + } else { + return ErrImagePull, err.Error() + } + } + puller.logIt(ref, api.EventTypeNormal, "Pulled", logPrefix, fmt.Sprintf("Successfully pulled image %q", container.Image), glog.Info) + puller.backOff.DeleteEntry(backOffKey) + puller.backOff.GC() + return nil, "" +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/image_puller_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/image_puller_test.go new file mode 100644 index 000000000..0bde69b22 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/image_puller_test.go @@ -0,0 +1,123 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container_test + +import ( + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + . "k8s.io/kubernetes/pkg/kubelet/container" + ctest "k8s.io/kubernetes/pkg/kubelet/container/testing" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/flowcontrol" +) + +func TestPuller(t *testing.T) { + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "test_pod", + Namespace: "test-ns", + UID: "bar", + ResourceVersion: "42", + SelfLink: "/api/v1/pods/foo", + }} + + cases := []struct { + containerImage string + policy api.PullPolicy + calledFunctions []string + inspectErr error + pullerErr error + expectedErr []error + }{ + { // pull missing image + containerImage: "missing_image", + policy: api.PullIfNotPresent, + calledFunctions: []string{"IsImagePresent", "PullImage"}, + inspectErr: nil, + pullerErr: nil, + expectedErr: []error{nil}}, + + { // image present, dont pull + containerImage: "present_image", + policy: api.PullIfNotPresent, + calledFunctions: []string{"IsImagePresent"}, + inspectErr: nil, + pullerErr: nil, + expectedErr: []error{nil, nil, nil}}, + // image present, pull it + {containerImage: "present_image", + policy: api.PullAlways, + calledFunctions: []string{"IsImagePresent", "PullImage"}, + inspectErr: nil, + pullerErr: nil, + expectedErr: []error{nil, nil, nil}}, + // missing image, error PullNever + {containerImage: "missing_image", + policy: api.PullNever, + calledFunctions: []string{"IsImagePresent"}, + inspectErr: nil, + pullerErr: nil, + expectedErr: []error{ErrImageNeverPull, ErrImageNeverPull, ErrImageNeverPull}}, + // missing image, unable to inspect + {containerImage: "missing_image", + policy: api.PullIfNotPresent, + calledFunctions: []string{"IsImagePresent"}, + inspectErr: errors.New("unknown inspectError"), + pullerErr: nil, + expectedErr: []error{ErrImageInspect, ErrImageInspect, ErrImageInspect}}, + // missing image, unable to fetch + {containerImage: "typo_image", + policy: api.PullIfNotPresent, + calledFunctions: []string{"IsImagePresent", "PullImage"}, + inspectErr: nil, + pullerErr: errors.New("404"), + expectedErr: []error{ErrImagePull, ErrImagePull, ErrImagePullBackOff, ErrImagePull, ErrImagePullBackOff, ErrImagePullBackOff}}, + } + + for i, c := range cases { + container := &api.Container{ + Name: "container_name", + Image: c.containerImage, + ImagePullPolicy: c.policy, + } + + backOff := flowcontrol.NewBackOff(time.Second, time.Minute) + fakeClock := util.NewFakeClock(time.Now()) + backOff.Clock = fakeClock + + fakeRuntime := &ctest.FakeRuntime{} + fakeRecorder := &record.FakeRecorder{} + puller := NewImagePuller(fakeRecorder, fakeRuntime, backOff) + + fakeRuntime.ImageList = []Image{{"present_image", nil, 0}} + fakeRuntime.Err = c.pullerErr + fakeRuntime.InspectErr = c.inspectErr + + for tick, expected := range c.expectedErr { + fakeClock.Step(time.Second) + err, _ := puller.PullImage(pod, container, nil) + fakeRuntime.AssertCalls(c.calledFunctions) + assert.Equal(t, expected, err, "in test %d tick=%d", i, tick) + } + + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/os.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/os.go new file mode 100644 index 000000000..37a1abeac --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/os.go @@ -0,0 +1,41 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "os" +) + +// OSInterface collects system level operations that need to be mocked out +// during tests. +type OSInterface interface { + Mkdir(path string, perm os.FileMode) error + Symlink(oldname string, newname string) error +} + +// RealOS is used to dispatch the real system level operaitons. +type RealOS struct{} + +// MkDir will will call os.Mkdir to create a directory. +func (RealOS) Mkdir(path string, perm os.FileMode) error { + return os.Mkdir(path, perm) +} + +// Symlink will call os.Symlink to create a symbolic link. +func (RealOS) Symlink(oldname string, newname string) error { + return os.Symlink(oldname, newname) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/pty_linux.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/pty_linux.go new file mode 100644 index 000000000..cbc36f6d3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/pty_linux.go @@ -0,0 +1,30 @@ +// +build linux + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "os" + "os/exec" + + "github.com/kr/pty" +) + +func StartPty(c *exec.Cmd) (*os.File, error) { + return pty.Start(c) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/pty_unsupported.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/pty_unsupported.go new file mode 100644 index 000000000..b48a999b0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/pty_unsupported.go @@ -0,0 +1,28 @@ +// +build !linux + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "os" + "os/exec" +) + +func StartPty(c *exec.Cmd) (pty *os.File, err error) { + return nil, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/ref.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/ref.go new file mode 100644 index 000000000..55e4d5465 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/ref.go @@ -0,0 +1,61 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" +) + +var ImplicitContainerPrefix string = "implicitly required container " + +// GenerateContainerRef returns an *api.ObjectReference which references the given container +// within the given pod. Returns an error if the reference can't be constructed or the +// container doesn't actually belong to the pod. +// +// This function will return an error if the provided Pod does not have a selfLink, +// but we expect selfLink to be populated at all call sites for the function. +func GenerateContainerRef(pod *api.Pod, container *api.Container) (*api.ObjectReference, error) { + fieldPath, err := fieldPath(pod, container) + if err != nil { + // TODO: figure out intelligent way to refer to containers that we implicitly + // start (like the pod infra container). This is not a good way, ugh. + fieldPath = ImplicitContainerPrefix + container.Name + } + ref, err := api.GetPartialReference(pod, fieldPath) + if err != nil { + return nil, err + } + return ref, nil +} + +// fieldPath returns a fieldPath locating container within pod. +// Returns an error if the container isn't part of the pod. +func fieldPath(pod *api.Pod, container *api.Container) (string, error) { + for i := range pod.Spec.Containers { + here := &pod.Spec.Containers[i] + if here.Name == container.Name { + if here.Name == "" { + return fmt.Sprintf("spec.containers[%d]", i), nil + } else { + return fmt.Sprintf("spec.containers{%s}", here.Name), nil + } + } + } + return "", fmt.Errorf("container %#v not found in pod %#v", container, pod) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/ref_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/ref_test.go new file mode 100644 index 000000000..18cc6672e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/ref_test.go @@ -0,0 +1,212 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" +) + +func TestFieldPath(t *testing.T) { + pod := &api.Pod{Spec: api.PodSpec{Containers: []api.Container{ + {Name: "foo"}, + {Name: "bar"}, + {Name: ""}, + {Name: "baz"}, + }}} + table := map[string]struct { + pod *api.Pod + container *api.Container + path string + success bool + }{ + "basic": {pod, &api.Container{Name: "foo"}, "spec.containers{foo}", true}, + "basic2": {pod, &api.Container{Name: "baz"}, "spec.containers{baz}", true}, + "emptyName": {pod, &api.Container{Name: ""}, "spec.containers[2]", true}, + "basicSamePointer": {pod, &pod.Spec.Containers[0], "spec.containers{foo}", true}, + "missing": {pod, &api.Container{Name: "qux"}, "", false}, + } + + for name, item := range table { + res, err := fieldPath(item.pod, item.container) + if item.success == false { + if err == nil { + t.Errorf("%v: unexpected non-error", name) + } + continue + } + if err != nil { + t.Errorf("%v: unexpected error: %v", name, err) + continue + } + if e, a := item.path, res; e != a { + t.Errorf("%v: wanted %v, got %v", name, e, a) + } + } +} + +func TestGenerateContainerRef(t *testing.T) { + var ( + okPod = api.Pod{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Pod", + APIVersion: testapi.Default.GroupVersion().String(), + }, + ObjectMeta: api.ObjectMeta{ + Name: "ok", + Namespace: "test-ns", + UID: "bar", + ResourceVersion: "42", + SelfLink: "/api/" + testapi.Default.GroupVersion().String() + "/pods/foo", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "by-name", + }, + {}, + }, + }, + } + noSelfLinkPod = okPod + defaultedSelfLinkPod = okPod + ) + noSelfLinkPod.Kind = "" + noSelfLinkPod.APIVersion = "" + noSelfLinkPod.ObjectMeta.SelfLink = "" + defaultedSelfLinkPod.ObjectMeta.SelfLink = "/api/" + testapi.Default.GroupVersion().String() + "/pods/ok" + + cases := []struct { + name string + pod *api.Pod + container *api.Container + expected *api.ObjectReference + success bool + }{ + { + name: "by-name", + pod: &okPod, + container: &api.Container{ + Name: "by-name", + }, + expected: &api.ObjectReference{ + Kind: "Pod", + APIVersion: testapi.Default.GroupVersion().String(), + Name: "ok", + Namespace: "test-ns", + UID: "bar", + ResourceVersion: "42", + FieldPath: ".spec.containers{by-name}", + }, + success: true, + }, + { + name: "no-name", + pod: &okPod, + container: &api.Container{}, + expected: &api.ObjectReference{ + Kind: "Pod", + APIVersion: testapi.Default.GroupVersion().String(), + Name: "ok", + Namespace: "test-ns", + UID: "bar", + ResourceVersion: "42", + FieldPath: ".spec.containers[1]", + }, + success: true, + }, + { + name: "no-selflink", + pod: &noSelfLinkPod, + container: &api.Container{}, + expected: nil, + success: false, + }, + { + name: "defaulted-selflink", + pod: &defaultedSelfLinkPod, + container: &api.Container{ + Name: "by-name", + }, + expected: &api.ObjectReference{ + Kind: "Pod", + APIVersion: testapi.Default.GroupVersion().String(), + Name: "ok", + Namespace: "test-ns", + UID: "bar", + ResourceVersion: "42", + FieldPath: ".spec.containers{by-name}", + }, + success: true, + }, + { + name: "implicitly-required", + pod: &okPod, + container: &api.Container{ + Name: "net", + }, + expected: &api.ObjectReference{ + Kind: "Pod", + APIVersion: testapi.Default.GroupVersion().String(), + Name: "ok", + Namespace: "test-ns", + UID: "bar", + ResourceVersion: "42", + FieldPath: "implicitly required container net", + }, + success: true, + }, + } + + for _, tc := range cases { + actual, err := GenerateContainerRef(tc.pod, tc.container) + if err != nil { + if tc.success { + t.Errorf("%v: unexpected error: %v", tc.name, err) + } + + continue + } + + if !tc.success { + t.Errorf("%v: unexpected success", tc.name) + continue + } + + if e, a := tc.expected.Kind, actual.Kind; e != a { + t.Errorf("%v: kind: expected %v, got %v", tc.name, e, a) + } + if e, a := tc.expected.APIVersion, actual.APIVersion; e != a { + t.Errorf("%v: apiVersion: expected %v, got %v", tc.name, e, a) + } + if e, a := tc.expected.Name, actual.Name; e != a { + t.Errorf("%v: name: expected %v, got %v", tc.name, e, a) + } + if e, a := tc.expected.Namespace, actual.Namespace; e != a { + t.Errorf("%v: namespace: expected %v, got %v", tc.name, e, a) + } + if e, a := tc.expected.UID, actual.UID; e != a { + t.Errorf("%v: uid: expected %v, got %v", tc.name, e, a) + } + if e, a := tc.expected.ResourceVersion, actual.ResourceVersion; e != a { + t.Errorf("%v: kind: expected %v, got %v", tc.name, e, a) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/runtime.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/runtime.go new file mode 100644 index 000000000..9fda09993 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/runtime.go @@ -0,0 +1,488 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "fmt" + "io" + "reflect" + "strings" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/flowcontrol" + "k8s.io/kubernetes/pkg/volume" +) + +type Version interface { + // Compare compares two versions of the runtime. On success it returns -1 + // if the version is less than the other, 1 if it is greater than the other, + // or 0 if they are equal. + Compare(other string) (int, error) + // String returns a string that represents the version. + String() string +} + +// ImageSpec is an internal representation of an image. Currently, it wraps the +// value of a Container's Image field, but in the future it will include more detailed +// information about the different image types. +type ImageSpec struct { + Image string +} + +// Runtime interface defines the interfaces that should be implemented +// by a container runtime. +// Thread safety is required from implementations of this interface. +type Runtime interface { + // Type returns the type of the container runtime. + Type() string + + // Version returns the version information of the container runtime. + Version() (Version, error) + // APIVersion returns the API version information of the container + // runtime. This may be different from the runtime engine's version. + // TODO(random-liu): We should fold this into Version() + APIVersion() (Version, error) + // Status returns error if the runtime is unhealthy; nil otherwise. + Status() error + // GetPods returns a list containers group by pods. The boolean parameter + // specifies whether the runtime returns all containers including those already + // exited and dead containers (used for garbage collection). + GetPods(all bool) ([]*Pod, error) + // GarbageCollect removes dead containers using the specified container gc policy + GarbageCollect(gcPolicy ContainerGCPolicy) error + // Syncs the running pod into the desired pod. + SyncPod(pod *api.Pod, apiPodStatus api.PodStatus, podStatus *PodStatus, pullSecrets []api.Secret, backOff *flowcontrol.Backoff) PodSyncResult + // KillPod kills all the containers of a pod. Pod may be nil, running pod must not be. + // TODO(random-liu): Return PodSyncResult in KillPod. + KillPod(pod *api.Pod, runningPod Pod) error + // GetPodStatus retrieves the status of the pod, including the + // information of all containers in the pod that are visble in Runtime. + GetPodStatus(uid types.UID, name, namespace string) (*PodStatus, error) + // PullImage pulls an image from the network to local storage using the supplied + // secrets if necessary. + PullImage(image ImageSpec, pullSecrets []api.Secret) error + // IsImagePresent checks whether the container image is already in the local storage. + IsImagePresent(image ImageSpec) (bool, error) + // Gets all images currently on the machine. + ListImages() ([]Image, error) + // Removes the specified image. + RemoveImage(image ImageSpec) error + // TODO(vmarmol): Unify pod and containerID args. + // GetContainerLogs returns logs of a specific container. By + // default, it returns a snapshot of the container log. Set 'follow' to true to + // stream the log. Set 'follow' to false and specify the number of lines (e.g. + // "100" or "all") to tail the log. + GetContainerLogs(pod *api.Pod, containerID ContainerID, logOptions *api.PodLogOptions, stdout, stderr io.Writer) (err error) + // ContainerCommandRunner encapsulates the command runner interfaces for testability. + ContainerCommandRunner + // ContainerAttach encapsulates the attaching to containers for testability + ContainerAttacher +} + +type ContainerAttacher interface { + AttachContainer(id ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) (err error) +} + +// CommandRunner encapsulates the command runner interfaces for testability. +type ContainerCommandRunner interface { + // TODO(vmarmol): Merge RunInContainer and ExecInContainer. + // Runs the command in the container of the specified pod. + RunInContainer(containerID ContainerID, cmd []string) ([]byte, error) + // Runs the command in the container of the specified pod using nsenter. + // Attaches the processes stdin, stdout, and stderr. Optionally uses a + // tty. + ExecInContainer(containerID ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error + // Forward the specified port from the specified pod to the stream. + PortForward(pod *Pod, port uint16, stream io.ReadWriteCloser) error +} + +// ImagePuller wraps Runtime.PullImage() to pull a container image. +// It will check the presence of the image, and report the 'image pulling', +// 'image pulled' events correspondingly. +type ImagePuller interface { + PullImage(pod *api.Pod, container *api.Container, pullSecrets []api.Secret) (error, string) +} + +// Pod is a group of containers. +type Pod struct { + // The ID of the pod, which can be used to retrieve a particular pod + // from the pod list returned by GetPods(). + ID types.UID + // The name and namespace of the pod, which is readable by human. + Name string + Namespace string + // List of containers that belongs to this pod. It may contain only + // running containers, or mixed with dead ones (when GetPods(true)). + Containers []*Container +} + +// PodPair contains both runtime#Pod and api#Pod +type PodPair struct { + // APIPod is the api.Pod + APIPod *api.Pod + // RunningPod is the pod defined defined in pkg/kubelet/container/runtime#Pod + RunningPod *Pod +} + +// ContainerID is a type that identifies a container. +type ContainerID struct { + // The type of the container runtime. e.g. 'docker', 'rkt'. + Type string + // The identification of the container, this is comsumable by + // the underlying container runtime. (Note that the container + // runtime interface still takes the whole struct as input). + ID string +} + +func BuildContainerID(typ, ID string) ContainerID { + return ContainerID{Type: typ, ID: ID} +} + +// Convenience method for creating a ContainerID from an ID string. +func ParseContainerID(containerID string) ContainerID { + var id ContainerID + if err := id.ParseString(containerID); err != nil { + glog.Error(err) + } + return id +} + +func (c *ContainerID) ParseString(data string) error { + // Trim the quotes and split the type and ID. + parts := strings.Split(strings.Trim(data, "\""), "://") + if len(parts) != 2 { + return fmt.Errorf("invalid container ID: %q", data) + } + c.Type, c.ID = parts[0], parts[1] + return nil +} + +func (c *ContainerID) String() string { + return fmt.Sprintf("%s://%s", c.Type, c.ID) +} + +func (c *ContainerID) IsEmpty() bool { + return *c == ContainerID{} +} + +func (c *ContainerID) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("%q", c.String())), nil +} + +func (c *ContainerID) UnmarshalJSON(data []byte) error { + return c.ParseString(string(data)) +} + +// DockerID is an ID of docker container. It is a type to make it clear when we're working with docker container Ids +type DockerID string + +func (id DockerID) ContainerID() ContainerID { + return ContainerID{ + Type: "docker", + ID: string(id), + } +} + +type ContainerState string + +const ( + ContainerStateRunning ContainerState = "running" + ContainerStateExited ContainerState = "exited" + // This unknown encompasses all the states that we currently don't care. + ContainerStateUnknown ContainerState = "unknown" +) + +// Container provides the runtime information for a container, such as ID, hash, +// state of the container. +type Container struct { + // The ID of the container, used by the container runtime to identify + // a container. + ID ContainerID + // The name of the container, which should be the same as specified by + // api.Container. + Name string + // The image name of the container, this also includes the tag of the image, + // the expected form is "NAME:TAG". + Image string + // Hash of the container, used for comparison. Optional for containers + // not managed by kubelet. + Hash uint64 + // The timestamp of the creation time of the container. + // TODO(yifan): Consider to move it to api.ContainerStatus. + Created int64 + // State is the state of the container. + State ContainerState +} + +// PodStatus represents the status of the pod and its containers. +// api.PodStatus can be derived from examining PodStatus and api.Pod. +type PodStatus struct { + // ID of the pod. + ID types.UID + // Name of the pod. + Name string + // Namspace of the pod. + Namespace string + // IP of the pod. + IP string + // Status of containers in the pod. + ContainerStatuses []*ContainerStatus +} + +// ContainerStatus represents the status of a container. +type ContainerStatus struct { + // ID of the container. + ID ContainerID + // Name of the container. + Name string + // Status of the container. + State ContainerState + // Creation time of the container. + CreatedAt time.Time + // Start time of the container. + StartedAt time.Time + // Finish time of the container. + FinishedAt time.Time + // Exit code of the container. + ExitCode int + // Name of the image, this also includes the tag of the image, + // the expected form is "NAME:TAG". + Image string + // ID of the image. + ImageID string + // Hash of the container, used for comparison. + Hash uint64 + // Number of times that the container has been restarted. + RestartCount int + // A string explains why container is in such a status. + Reason string + // Message written by the container before exiting (stored in + // TerminationMessagePath). + Message string +} + +// FindContainerStatusByName returns container status in the pod status with the given name. +// When there are multiple containers' statuses with the same name, the first match will be returned. +func (podStatus *PodStatus) FindContainerStatusByName(containerName string) *ContainerStatus { + for _, containerStatus := range podStatus.ContainerStatuses { + if containerStatus.Name == containerName { + return containerStatus + } + } + return nil +} + +// Get container status of all the running containers in a pod +func (podStatus *PodStatus) GetRunningContainerStatuses() []*ContainerStatus { + runnningContainerStatues := []*ContainerStatus{} + for _, containerStatus := range podStatus.ContainerStatuses { + if containerStatus.State == ContainerStateRunning { + runnningContainerStatues = append(runnningContainerStatues, containerStatus) + } + } + return runnningContainerStatues +} + +// Basic information about a container image. +type Image struct { + // ID of the image. + ID string + // Other names by which this image is known. + RepoTags []string + // The size of the image in bytes. + Size int64 +} + +type EnvVar struct { + Name string + Value string +} + +type Mount struct { + // Name of the volume mount. + Name string + // Path of the mount within the container. + ContainerPath string + // Path of the mount on the host. + HostPath string + // Whether the mount is read-only. + ReadOnly bool + // Whether the mount needs SELinux relabeling + SELinuxRelabel bool +} + +type PortMapping struct { + // Name of the port mapping + Name string + // Protocol of the port mapping. + Protocol api.Protocol + // The port number within the container. + ContainerPort int + // The port number on the host. + HostPort int + // The host IP. + HostIP string +} + +// RunContainerOptions specify the options which are necessary for running containers +type RunContainerOptions struct { + // The environment variables list. + Envs []EnvVar + // The mounts for the containers. + Mounts []Mount + // The port mappings for the containers. + PortMappings []PortMapping + // If the container has specified the TerminationMessagePath, then + // this directory will be used to create and mount the log file to + // container.TerminationMessagePath + PodContainerDir string + // The list of DNS servers for the container to use. + DNS []string + // The list of DNS search domains. + DNSSearch []string + // The parent cgroup to pass to Docker + CgroupParent string + // The type of container rootfs + ReadOnly bool + // hostname for pod containers + Hostname string +} + +// VolumeInfo contains information about the volume. +type VolumeInfo struct { + // Mounter is the volume's mounter + Mounter volume.Mounter + // SELinuxLabeled indicates whether this volume has had the + // pod's SELinux label applied to it or not + SELinuxLabeled bool +} + +type VolumeMap map[string]VolumeInfo + +type Pods []*Pod + +// FindPodByID finds and returns a pod in the pod list by UID. It will return an empty pod +// if not found. +func (p Pods) FindPodByID(podUID types.UID) Pod { + for i := range p { + if p[i].ID == podUID { + return *p[i] + } + } + return Pod{} +} + +// FindPodByFullName finds and returns a pod in the pod list by the full name. +// It will return an empty pod if not found. +func (p Pods) FindPodByFullName(podFullName string) Pod { + for i := range p { + if BuildPodFullName(p[i].Name, p[i].Namespace) == podFullName { + return *p[i] + } + } + return Pod{} +} + +// FindPod combines FindPodByID and FindPodByFullName, it finds and returns a pod in the +// pod list either by the full name or the pod ID. It will return an empty pod +// if not found. +func (p Pods) FindPod(podFullName string, podUID types.UID) Pod { + if len(podFullName) > 0 { + return p.FindPodByFullName(podFullName) + } + return p.FindPodByID(podUID) +} + +// FindContainerByName returns a container in the pod with the given name. +// When there are multiple containers with the same name, the first match will +// be returned. +func (p *Pod) FindContainerByName(containerName string) *Container { + for _, c := range p.Containers { + if c.Name == containerName { + return c + } + } + return nil +} + +func (p *Pod) FindContainerByID(id ContainerID) *Container { + for _, c := range p.Containers { + if c.ID == id { + return c + } + } + return nil +} + +// ToAPIPod converts Pod to api.Pod. Note that if a field in api.Pod has no +// corresponding field in Pod, the field would not be populated. +func (p *Pod) ToAPIPod() *api.Pod { + var pod api.Pod + pod.UID = p.ID + pod.Name = p.Name + pod.Namespace = p.Namespace + + for _, c := range p.Containers { + var container api.Container + container.Name = c.Name + container.Image = c.Image + pod.Spec.Containers = append(pod.Spec.Containers, container) + } + return &pod +} + +// IsEmpty returns true if the pod is empty. +func (p *Pod) IsEmpty() bool { + return reflect.DeepEqual(p, &Pod{}) +} + +// GetPodFullName returns a name that uniquely identifies a pod. +func GetPodFullName(pod *api.Pod) string { + // Use underscore as the delimiter because it is not allowed in pod name + // (DNS subdomain format), while allowed in the container name format. + return pod.Name + "_" + pod.Namespace +} + +// Build the pod full name from pod name and namespace. +func BuildPodFullName(name, namespace string) string { + return name + "_" + namespace +} + +// Parse the pod full name. +func ParsePodFullName(podFullName string) (string, string, error) { + parts := strings.Split(podFullName, "_") + if len(parts) != 2 { + return "", "", fmt.Errorf("failed to parse the pod full name %q", podFullName) + } + return parts[0], parts[1], nil +} + +// Option is a functional option type for Runtime, useful for +// completely optional settings. +type Option func(Runtime) + +// Sort the container statuses by creation time. +type SortContainerStatusesByCreationTime []*ContainerStatus + +func (s SortContainerStatusesByCreationTime) Len() int { return len(s) } +func (s SortContainerStatusesByCreationTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s SortContainerStatusesByCreationTime) Less(i, j int) bool { + return s[i].CreatedAt.Before(s[j].CreatedAt) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/runtime_cache.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/runtime_cache.go new file mode 100644 index 000000000..0926107da --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/runtime_cache.go @@ -0,0 +1,96 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "sync" + "time" +) + +var ( + // TODO(yifan): Maybe set the them as parameters for NewCache(). + defaultCachePeriod = time.Second * 2 +) + +type RuntimeCache interface { + GetPods() ([]*Pod, error) + ForceUpdateIfOlder(time.Time) error +} + +type podsGetter interface { + GetPods(bool) ([]*Pod, error) +} + +// NewRuntimeCache creates a container runtime cache. +func NewRuntimeCache(getter podsGetter) (RuntimeCache, error) { + return &runtimeCache{ + getter: getter, + }, nil +} + +// runtimeCache caches a list of pods. It records a timestamp (cacheTime) right +// before updating the pods, so the timestamp is at most as new as the pods +// (and can be slightly older). The timestamp always moves forward. Callers are +// expected not to modify the pods returned from GetPods. +type runtimeCache struct { + sync.Mutex + // The underlying container runtime used to update the cache. + getter podsGetter + // Last time when cache was updated. + cacheTime time.Time + // The content of the cache. + pods []*Pod +} + +// GetPods returns the cached pods if they are not outdated; otherwise, it +// retrieves the latest pods and return them. +func (r *runtimeCache) GetPods() ([]*Pod, error) { + r.Lock() + defer r.Unlock() + if time.Since(r.cacheTime) > defaultCachePeriod { + if err := r.updateCache(); err != nil { + return nil, err + } + } + return r.pods, nil +} + +func (r *runtimeCache) ForceUpdateIfOlder(minExpectedCacheTime time.Time) error { + r.Lock() + defer r.Unlock() + if r.cacheTime.Before(minExpectedCacheTime) { + return r.updateCache() + } + return nil +} + +func (r *runtimeCache) updateCache() error { + pods, timestamp, err := r.getPodsWithTimestamp() + if err != nil { + return err + } + r.pods, r.cacheTime = pods, timestamp + return nil +} + +// getPodsWithTimestamp records a timestamp and retrieves pods from the getter. +func (r *runtimeCache) getPodsWithTimestamp() ([]*Pod, time.Time, error) { + // Always record the timestamp before getting the pods to avoid stale pods. + timestamp := time.Now() + pods, err := r.getter.GetPods(false) + return pods, timestamp, err +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/runtime_cache_fake.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/runtime_cache_fake.go new file mode 100644 index 000000000..0b6e7d868 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/runtime_cache_fake.go @@ -0,0 +1,42 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +// TestRunTimeCache embeds runtimeCache with some additional methods for testing. +// It must be declared in the container package to have visibility to runtimeCache. +// It cannot be in a "..._test.go" file in order for runtime_cache_test.go to have cross-package visibility to it. +// (cross-package declarations in test files cannot be used from dot imports if this package is vendored) +type TestRuntimeCache struct { + runtimeCache +} + +func (r *TestRuntimeCache) UpdateCacheWithLock() error { + r.Lock() + defer r.Unlock() + return r.updateCache() +} + +func (r *TestRuntimeCache) GetCachedPods() []*Pod { + r.Lock() + defer r.Unlock() + return r.pods +} + +func NewTestRuntimeCache(getter podsGetter) *TestRuntimeCache { + c, _ := NewRuntimeCache(getter) + return &TestRuntimeCache{*c.(*runtimeCache)} +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/runtime_cache_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/runtime_cache_test.go new file mode 100644 index 000000000..d66e07bfc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/runtime_cache_test.go @@ -0,0 +1,68 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container_test + +import ( + "reflect" + "testing" + "time" + + . "k8s.io/kubernetes/pkg/kubelet/container" + ctest "k8s.io/kubernetes/pkg/kubelet/container/testing" +) + +func TestGetPods(t *testing.T) { + runtime := &ctest.FakeRuntime{} + expected := []*Pod{{ID: "1111"}, {ID: "2222"}, {ID: "3333"}} + runtime.PodList = expected + cache := NewTestRuntimeCache(runtime) + actual, err := cache.GetPods() + if err != nil { + t.Errorf("unexpected error %v", err) + } + if !reflect.DeepEqual(expected, actual) { + t.Errorf("expected %#v, got %#v", expected, actual) + } +} + +func TestForceUpdateIfOlder(t *testing.T) { + runtime := &ctest.FakeRuntime{} + cache := NewTestRuntimeCache(runtime) + + // Cache old pods. + oldpods := []*Pod{{ID: "1111"}} + runtime.PodList = oldpods + cache.UpdateCacheWithLock() + + // Update the runtime to new pods. + newpods := []*Pod{{ID: "1111"}, {ID: "2222"}, {ID: "3333"}} + runtime.PodList = newpods + + // An older timestamp should not force an update. + cache.ForceUpdateIfOlder(time.Now().Add(-20 * time.Minute)) + actual := cache.GetCachedPods() + if !reflect.DeepEqual(oldpods, actual) { + t.Errorf("expected %#v, got %#v", oldpods, actual) + } + + // A newer timestamp should force an update. + cache.ForceUpdateIfOlder(time.Now().Add(20 * time.Second)) + actual = cache.GetCachedPods() + if !reflect.DeepEqual(newpods, actual) { + t.Errorf("expected %#v, got %#v", newpods, actual) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/serialized_image_puller.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/serialized_image_puller.go new file mode 100644 index 000000000..3b5c4689f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/serialized_image_puller.go @@ -0,0 +1,141 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "fmt" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/util/flowcontrol" + "k8s.io/kubernetes/pkg/util/wait" +) + +type imagePullRequest struct { + spec ImageSpec + container *api.Container + pullSecrets []api.Secret + logPrefix string + ref *api.ObjectReference + returnChan chan<- error +} + +// serializedImagePuller pulls the image using Runtime.PullImage(). +// It will check the presence of the image, and report the 'image pulling', +// 'image pulled' events correspondingly. +type serializedImagePuller struct { + recorder record.EventRecorder + runtime Runtime + backOff *flowcontrol.Backoff + pullRequests chan *imagePullRequest +} + +// enforce compatibility. +var _ ImagePuller = &serializedImagePuller{} + +// NewSerializedImagePuller takes an event recorder and container runtime to create a +// image puller that wraps the container runtime's PullImage interface. +// Pulls one image at a time. +// Issue #10959 has the rationale behind serializing image pulls. +func NewSerializedImagePuller(recorder record.EventRecorder, runtime Runtime, imageBackOff *flowcontrol.Backoff) ImagePuller { + imagePuller := &serializedImagePuller{ + recorder: recorder, + runtime: runtime, + backOff: imageBackOff, + pullRequests: make(chan *imagePullRequest, 10), + } + go wait.Until(imagePuller.pullImages, time.Second, wait.NeverStop) + return imagePuller +} + +// records an event using ref, event msg. log to glog using prefix, msg, logFn +func (puller *serializedImagePuller) logIt(ref *api.ObjectReference, eventtype, event, prefix, msg string, logFn func(args ...interface{})) { + if ref != nil { + puller.recorder.Event(ref, eventtype, event, msg) + } else { + logFn(fmt.Sprint(prefix, " ", msg)) + } +} + +// PullImage pulls the image for the specified pod and container. +func (puller *serializedImagePuller) PullImage(pod *api.Pod, container *api.Container, pullSecrets []api.Secret) (error, string) { + logPrefix := fmt.Sprintf("%s/%s", pod.Name, container.Image) + ref, err := GenerateContainerRef(pod, container) + if err != nil { + glog.Errorf("Couldn't make a ref to pod %v, container %v: '%v'", pod.Name, container.Name, err) + } + + spec := ImageSpec{container.Image} + present, err := puller.runtime.IsImagePresent(spec) + if err != nil { + msg := fmt.Sprintf("Failed to inspect image %q: %v", container.Image, err) + puller.logIt(ref, api.EventTypeWarning, FailedToInspectImage, logPrefix, msg, glog.Warning) + return ErrImageInspect, msg + } + + if !shouldPullImage(container, present) { + if present { + msg := fmt.Sprintf("Container image %q already present on machine", container.Image) + puller.logIt(ref, api.EventTypeNormal, PulledImage, logPrefix, msg, glog.Info) + return nil, "" + } else { + msg := fmt.Sprintf("Container image %q is not present with pull policy of Never", container.Image) + puller.logIt(ref, api.EventTypeWarning, ErrImageNeverPullPolicy, logPrefix, msg, glog.Warning) + return ErrImageNeverPull, msg + } + } + + backOffKey := fmt.Sprintf("%s_%s", pod.Name, container.Image) + if puller.backOff.IsInBackOffSinceUpdate(backOffKey, puller.backOff.Clock.Now()) { + msg := fmt.Sprintf("Back-off pulling image %q", container.Image) + puller.logIt(ref, api.EventTypeNormal, BackOffPullImage, logPrefix, msg, glog.Info) + return ErrImagePullBackOff, msg + } + + // enqueue image pull request and wait for response. + returnChan := make(chan error) + puller.pullRequests <- &imagePullRequest{ + spec: spec, + container: container, + pullSecrets: pullSecrets, + logPrefix: logPrefix, + ref: ref, + returnChan: returnChan, + } + if err = <-returnChan; err != nil { + puller.logIt(ref, api.EventTypeWarning, FailedToPullImage, logPrefix, fmt.Sprintf("Failed to pull image %q: %v", container.Image, err), glog.Warning) + puller.backOff.Next(backOffKey, puller.backOff.Clock.Now()) + if err == RegistryUnavailable { + msg := fmt.Sprintf("image pull failed for %s because the registry is unavailable.", container.Image) + return err, msg + } else { + return ErrImagePull, err.Error() + } + } + puller.logIt(ref, api.EventTypeNormal, PulledImage, logPrefix, fmt.Sprintf("Successfully pulled image %q", container.Image), glog.Info) + puller.backOff.GC() + return nil, "" +} + +func (puller *serializedImagePuller) pullImages() { + for pullRequest := range puller.pullRequests { + puller.logIt(pullRequest.ref, api.EventTypeNormal, PullingImage, pullRequest.logPrefix, fmt.Sprintf("pulling image %q", pullRequest.container.Image), glog.Info) + pullRequest.returnChan <- puller.runtime.PullImage(pullRequest.spec, pullRequest.pullSecrets) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/serialized_image_puller_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/serialized_image_puller_test.go new file mode 100644 index 000000000..f4ea8f8e1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/serialized_image_puller_test.go @@ -0,0 +1,123 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container_test + +import ( + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + . "k8s.io/kubernetes/pkg/kubelet/container" + ctest "k8s.io/kubernetes/pkg/kubelet/container/testing" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/flowcontrol" +) + +func TestSerializedPuller(t *testing.T) { + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "test_pod", + Namespace: "test-ns", + UID: "bar", + ResourceVersion: "42", + SelfLink: "/api/v1/pods/foo", + }} + + cases := []struct { + containerImage string + policy api.PullPolicy + calledFunctions []string + inspectErr error + pullerErr error + expectedErr []error + }{ + { // pull missing image + containerImage: "missing_image", + policy: api.PullIfNotPresent, + calledFunctions: []string{"IsImagePresent", "PullImage"}, + inspectErr: nil, + pullerErr: nil, + expectedErr: []error{nil}}, + + { // image present, dont pull + containerImage: "present_image", + policy: api.PullIfNotPresent, + calledFunctions: []string{"IsImagePresent"}, + inspectErr: nil, + pullerErr: nil, + expectedErr: []error{nil, nil, nil}}, + // image present, pull it + {containerImage: "present_image", + policy: api.PullAlways, + calledFunctions: []string{"IsImagePresent", "PullImage"}, + inspectErr: nil, + pullerErr: nil, + expectedErr: []error{nil, nil, nil}}, + // missing image, error PullNever + {containerImage: "missing_image", + policy: api.PullNever, + calledFunctions: []string{"IsImagePresent"}, + inspectErr: nil, + pullerErr: nil, + expectedErr: []error{ErrImageNeverPull, ErrImageNeverPull, ErrImageNeverPull}}, + // missing image, unable to inspect + {containerImage: "missing_image", + policy: api.PullIfNotPresent, + calledFunctions: []string{"IsImagePresent"}, + inspectErr: errors.New("unknown inspectError"), + pullerErr: nil, + expectedErr: []error{ErrImageInspect, ErrImageInspect, ErrImageInspect}}, + // missing image, unable to fetch + {containerImage: "typo_image", + policy: api.PullIfNotPresent, + calledFunctions: []string{"IsImagePresent", "PullImage"}, + inspectErr: nil, + pullerErr: errors.New("404"), + expectedErr: []error{ErrImagePull, ErrImagePull, ErrImagePullBackOff, ErrImagePull, ErrImagePullBackOff, ErrImagePullBackOff}}, + } + + for i, c := range cases { + container := &api.Container{ + Name: "container_name", + Image: c.containerImage, + ImagePullPolicy: c.policy, + } + + backOff := flowcontrol.NewBackOff(time.Second, time.Minute) + fakeClock := util.NewFakeClock(time.Now()) + backOff.Clock = fakeClock + + fakeRuntime := &ctest.FakeRuntime{} + fakeRecorder := &record.FakeRecorder{} + puller := NewSerializedImagePuller(fakeRecorder, fakeRuntime, backOff) + + fakeRuntime.ImageList = []Image{{"present_image", nil, 0}} + fakeRuntime.Err = c.pullerErr + fakeRuntime.InspectErr = c.inspectErr + + for tick, expected := range c.expectedErr { + fakeClock.Step(time.Second) + err, _ := puller.PullImage(pod, container, nil) + fakeRuntime.AssertCalls(c.calledFunctions) + assert.Equal(t, expected, err, "in test %d tick=%d", i, tick) + } + + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/sync_result.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/sync_result.go new file mode 100644 index 000000000..1c3aa9eea --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/sync_result.go @@ -0,0 +1,135 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "errors" + "fmt" + + utilerrors "k8s.io/kubernetes/pkg/util/errors" +) + +// TODO(random-liu): We need to better organize runtime errors for introspection. + +// Container Terminated and Kubelet is backing off the restart +var ErrCrashLoopBackOff = errors.New("CrashLoopBackOff") + +var ( + // Container image pull failed, kubelet is backing off image pull + ErrImagePullBackOff = errors.New("ImagePullBackOff") + + // Unable to inspect image + ErrImageInspect = errors.New("ImageInspectError") + + // General image pull error + ErrImagePull = errors.New("ErrImagePull") + + // Required Image is absent on host and PullPolicy is NeverPullImage + ErrImageNeverPull = errors.New("ErrImageNeverPull") + + // ErrContainerNotFound returned when a container in the given pod with the + // given container name was not found, amongst those managed by the kubelet. + ErrContainerNotFound = errors.New("no matching container") + + // Get http error when pulling image from registry + RegistryUnavailable = errors.New("RegistryUnavailable") +) + +var ( + ErrRunContainer = errors.New("RunContainerError") + ErrKillContainer = errors.New("KillContainerError") + ErrVerifyNonRoot = errors.New("VerifyNonRootError") +) + +var ( + ErrSetupNetwork = errors.New("SetupNetworkError") + ErrTeardownNetwork = errors.New("TeardownNetworkError") +) + +// SyncAction indicates different kind of actions in SyncPod() and KillPod(). Now there are only actions +// about start/kill container and setup/teardown network. +type SyncAction string + +const ( + StartContainer SyncAction = "StartContainer" + KillContainer SyncAction = "KillContainer" + SetupNetwork SyncAction = "SetupNetwork" + TeardownNetwork SyncAction = "TeardownNetwork" +) + +// SyncResult is the result of sync action. +type SyncResult struct { + // The associated action of the result + Action SyncAction + // The target of the action, now the target can only be: + // * Container: Target should be container name + // * Network: Target is useless now, we just set it as pod full name now + Target interface{} + // Brief error reason + Error error + // Human readable error reason + Message string +} + +// NewSyncResult generates new SyncResult with specific Action and Target +func NewSyncResult(action SyncAction, target interface{}) *SyncResult { + return &SyncResult{Action: action, Target: target} +} + +// Fail fails the SyncResult with specific error and message +func (r *SyncResult) Fail(err error, msg string) { + r.Error, r.Message = err, msg +} + +// PodSyncResult is the summary result of SyncPod() and KillPod() +type PodSyncResult struct { + // Result of different sync actions + SyncResults []*SyncResult + // Error encountered in SyncPod() and KillPod() that is not already included in SyncResults + SyncError error +} + +// AddSyncResult adds multiple SyncResult to current PodSyncResult +func (p *PodSyncResult) AddSyncResult(result ...*SyncResult) { + p.SyncResults = append(p.SyncResults, result...) +} + +// AddPodSyncResult merges a PodSyncResult to current one +func (p *PodSyncResult) AddPodSyncResult(result PodSyncResult) { + p.AddSyncResult(result.SyncResults...) + p.SyncError = result.SyncError +} + +// Fail fails the PodSyncResult with an error occurred in SyncPod() and KillPod() itself +func (p *PodSyncResult) Fail(err error) { + p.SyncError = err +} + +// Error returns an error summarizing all the errors in PodSyncResult +func (p *PodSyncResult) Error() error { + errlist := []error{} + if p.SyncError != nil { + errlist = append(errlist, fmt.Errorf("failed to SyncPod: %v\n", p.SyncError)) + } + for _, result := range p.SyncResults { + if result.Error != nil { + errlist = append(errlist, fmt.Errorf("failed to %q for %q with %v: %q\n", result.Action, result.Target, + result.Error, result.Message)) + } + } + return utilerrors.NewAggregate(errlist) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/sync_result_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/sync_result_test.go new file mode 100644 index 000000000..a510d8a92 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/sync_result_test.go @@ -0,0 +1,68 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 container + +import ( + "errors" + "testing" +) + +func TestPodSyncResult(t *testing.T) { + okResults := []*SyncResult{ + NewSyncResult(StartContainer, "container_0"), + NewSyncResult(SetupNetwork, "pod"), + } + errResults := []*SyncResult{ + NewSyncResult(KillContainer, "container_1"), + NewSyncResult(TeardownNetwork, "pod"), + } + errResults[0].Fail(errors.New("error_0"), "message_0") + errResults[1].Fail(errors.New("error_1"), "message_1") + + // If the PodSyncResult doesn't contain error result, it should not be error + result := PodSyncResult{} + result.AddSyncResult(okResults...) + if result.Error() != nil { + t.Errorf("PodSyncResult should not be error: %v", result) + } + + // If the PodSyncResult contains error result, it should be error + result = PodSyncResult{} + result.AddSyncResult(okResults...) + result.AddSyncResult(errResults...) + if result.Error() == nil { + t.Errorf("PodSyncResult should be error: %q", result) + } + + // If the PodSyncResult is failed, it should be error + result = PodSyncResult{} + result.AddSyncResult(okResults...) + result.Fail(errors.New("error")) + if result.Error() == nil { + t.Errorf("PodSyncResult should be error: %q", result) + } + + // If the PodSyncResult is added an error PodSyncResult, it should be error + errResult := PodSyncResult{} + errResult.AddSyncResult(errResults...) + result = PodSyncResult{} + result.AddSyncResult(okResults...) + result.AddPodSyncResult(errResult) + if result.Error() == nil { + t.Errorf("PodSyncResult should be error: %q", result) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/testing/fake_cache.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/testing/fake_cache.go new file mode 100644 index 000000000..db7a82e5a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/testing/fake_cache.go @@ -0,0 +1,49 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 testing + +import ( + "time" + + "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/types" +) + +type fakeCache struct { + runtime container.Runtime +} + +func NewFakeCache(runtime container.Runtime) container.Cache { + return &fakeCache{runtime: runtime} +} + +func (c *fakeCache) Get(id types.UID) (*container.PodStatus, error) { + return c.runtime.GetPodStatus(id, "", "") +} + +func (c *fakeCache) GetNewerThan(id types.UID, minTime time.Time) (*container.PodStatus, error) { + return c.Get(id) +} + +func (c *fakeCache) Set(id types.UID, status *container.PodStatus, err error, timestamp time.Time) { +} + +func (c *fakeCache) Delete(id types.UID) { +} + +func (c *fakeCache) UpdateTime(_ time.Time) { +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/testing/fake_runtime.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/testing/fake_runtime.go new file mode 100644 index 000000000..1bc56d182 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/testing/fake_runtime.go @@ -0,0 +1,355 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 testing + +import ( + "fmt" + "io" + "reflect" + "sync" + "time" + + "k8s.io/kubernetes/pkg/api" + . "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/flowcontrol" + "k8s.io/kubernetes/pkg/volume" +) + +// FakeRuntime is a fake container runtime for testing. +type FakeRuntime struct { + sync.Mutex + CalledFunctions []string + PodList []*Pod + AllPodList []*Pod + ImageList []Image + APIPodStatus api.PodStatus + PodStatus PodStatus + StartedPods []string + KilledPods []string + StartedContainers []string + KilledContainers []string + VersionInfo string + APIVersionInfo string + RuntimeType string + Err error + InspectErr error + StatusErr error +} + +// FakeRuntime should implement Runtime. +var _ Runtime = &FakeRuntime{} + +type FakeVersion struct { + Version string +} + +func (fv *FakeVersion) String() string { + return fv.Version +} + +func (fv *FakeVersion) Compare(other string) (int, error) { + result := 0 + if fv.Version > other { + result = 1 + } else if fv.Version < other { + result = -1 + } + return result, nil +} + +type podsGetter interface { + GetPods(bool) ([]*Pod, error) +} + +type FakeRuntimeCache struct { + getter podsGetter +} + +func NewFakeRuntimeCache(getter podsGetter) RuntimeCache { + return &FakeRuntimeCache{getter} +} + +func (f *FakeRuntimeCache) GetPods() ([]*Pod, error) { + return f.getter.GetPods(false) +} + +func (f *FakeRuntimeCache) ForceUpdateIfOlder(time.Time) error { + return nil +} + +// ClearCalls resets the FakeRuntime to the initial state. +func (f *FakeRuntime) ClearCalls() { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = []string{} + f.PodList = []*Pod{} + f.AllPodList = []*Pod{} + f.APIPodStatus = api.PodStatus{} + f.StartedPods = []string{} + f.KilledPods = []string{} + f.StartedContainers = []string{} + f.KilledContainers = []string{} + f.VersionInfo = "" + f.RuntimeType = "" + f.Err = nil + f.InspectErr = nil + f.StatusErr = nil +} + +func (f *FakeRuntime) assertList(expect []string, test []string) error { + if !reflect.DeepEqual(expect, test) { + return fmt.Errorf("expected %#v, got %#v", expect, test) + } + return nil +} + +// AssertCalls test if the invoked functions are as expected. +func (f *FakeRuntime) AssertCalls(calls []string) error { + f.Lock() + defer f.Unlock() + return f.assertList(calls, f.CalledFunctions) +} + +func (f *FakeRuntime) AssertStartedPods(pods []string) error { + f.Lock() + defer f.Unlock() + return f.assertList(pods, f.StartedPods) +} + +func (f *FakeRuntime) AssertKilledPods(pods []string) error { + f.Lock() + defer f.Unlock() + return f.assertList(pods, f.KilledPods) +} + +func (f *FakeRuntime) AssertStartedContainers(containers []string) error { + f.Lock() + defer f.Unlock() + return f.assertList(containers, f.StartedContainers) +} + +func (f *FakeRuntime) AssertKilledContainers(containers []string) error { + f.Lock() + defer f.Unlock() + return f.assertList(containers, f.KilledContainers) +} + +func (f *FakeRuntime) Type() string { + return f.RuntimeType +} + +func (f *FakeRuntime) Version() (Version, error) { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "Version") + return &FakeVersion{Version: f.VersionInfo}, f.Err +} + +func (f *FakeRuntime) APIVersion() (Version, error) { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "APIVersion") + return &FakeVersion{Version: f.APIVersionInfo}, f.Err +} + +func (f *FakeRuntime) Status() error { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "Status") + return f.StatusErr +} + +func (f *FakeRuntime) GetPods(all bool) ([]*Pod, error) { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "GetPods") + if all { + return f.AllPodList, f.Err + } + return f.PodList, f.Err +} + +func (f *FakeRuntime) SyncPod(pod *api.Pod, _ api.PodStatus, _ *PodStatus, _ []api.Secret, backOff *flowcontrol.Backoff) (result PodSyncResult) { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "SyncPod") + f.StartedPods = append(f.StartedPods, string(pod.UID)) + for _, c := range pod.Spec.Containers { + f.StartedContainers = append(f.StartedContainers, c.Name) + } + // TODO(random-liu): Add SyncResult for starting and killing containers + if f.Err != nil { + result.Fail(f.Err) + } + return +} + +func (f *FakeRuntime) KillPod(pod *api.Pod, runningPod Pod) error { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "KillPod") + f.KilledPods = append(f.KilledPods, string(runningPod.ID)) + for _, c := range runningPod.Containers { + f.KilledContainers = append(f.KilledContainers, c.Name) + } + return f.Err +} + +func (f *FakeRuntime) RunContainerInPod(container api.Container, pod *api.Pod, volumeMap map[string]volume.VolumePlugin) error { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "RunContainerInPod") + f.StartedContainers = append(f.StartedContainers, container.Name) + + pod.Spec.Containers = append(pod.Spec.Containers, container) + for _, c := range pod.Spec.Containers { + if c.Name == container.Name { // Container already in the pod. + return f.Err + } + } + pod.Spec.Containers = append(pod.Spec.Containers, container) + return f.Err +} + +func (f *FakeRuntime) KillContainerInPod(container api.Container, pod *api.Pod) error { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "KillContainerInPod") + f.KilledContainers = append(f.KilledContainers, container.Name) + + var containers []api.Container + for _, c := range pod.Spec.Containers { + if c.Name == container.Name { + continue + } + containers = append(containers, c) + } + return f.Err +} + +func (f *FakeRuntime) GetPodStatus(uid types.UID, name, namespace string) (*PodStatus, error) { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "GetPodStatus") + status := f.PodStatus + return &status, f.Err +} + +func (f *FakeRuntime) ExecInContainer(containerID ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "ExecInContainer") + return f.Err +} + +func (f *FakeRuntime) AttachContainer(containerID ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "AttachContainer") + return f.Err +} + +func (f *FakeRuntime) RunInContainer(containerID ContainerID, cmd []string) ([]byte, error) { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "RunInContainer") + return []byte{}, f.Err +} + +func (f *FakeRuntime) GetContainerLogs(pod *api.Pod, containerID ContainerID, logOptions *api.PodLogOptions, stdout, stderr io.Writer) (err error) { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "GetContainerLogs") + return f.Err +} + +func (f *FakeRuntime) PullImage(image ImageSpec, pullSecrets []api.Secret) error { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "PullImage") + return f.Err +} + +func (f *FakeRuntime) IsImagePresent(image ImageSpec) (bool, error) { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "IsImagePresent") + for _, i := range f.ImageList { + if i.ID == image.Image { + return true, nil + } + } + return false, f.InspectErr +} + +func (f *FakeRuntime) ListImages() ([]Image, error) { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "ListImages") + return f.ImageList, f.Err +} + +func (f *FakeRuntime) RemoveImage(image ImageSpec) error { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "RemoveImage") + index := 0 + for i := range f.ImageList { + if f.ImageList[i].ID == image.Image { + index = i + break + } + } + f.ImageList = append(f.ImageList[:index], f.ImageList[index+1:]...) + + return f.Err +} + +func (f *FakeRuntime) PortForward(pod *Pod, port uint16, stream io.ReadWriteCloser) error { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "PortForward") + return f.Err +} + +func (f *FakeRuntime) GarbageCollect(gcPolicy ContainerGCPolicy) error { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "GarbageCollect") + return f.Err +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/testing/os.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/testing/os.go new file mode 100644 index 000000000..fd379c2e2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/testing/os.go @@ -0,0 +1,35 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 testing + +import ( + "os" +) + +// FakeOS mocks out certain OS calls to avoid perturbing the filesystem +// on the test machine. +type FakeOS struct{} + +// Mkdir is a fake call that just returns nil. +func (FakeOS) Mkdir(path string, perm os.FileMode) error { + return nil +} + +// Symlink is a fake call that just returns nil. +func (FakeOS) Symlink(oldname string, newname string) error { + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container/testing/runtime_mock.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container/testing/runtime_mock.go new file mode 100644 index 000000000..d1f799835 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container/testing/runtime_mock.go @@ -0,0 +1,139 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 testing + +import ( + "io" + + "github.com/stretchr/testify/mock" + "k8s.io/kubernetes/pkg/api" + . "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/flowcontrol" + "k8s.io/kubernetes/pkg/volume" +) + +type Mock struct { + mock.Mock +} + +var _ Runtime = new(Mock) + +func (r *Mock) Start() error { + args := r.Called() + return args.Error(0) +} + +func (r *Mock) Type() string { + args := r.Called() + return args.Get(0).(string) +} + +func (r *Mock) Version() (Version, error) { + args := r.Called() + return args.Get(0).(Version), args.Error(1) +} + +func (r *Mock) APIVersion() (Version, error) { + args := r.Called() + return args.Get(0).(Version), args.Error(1) +} + +func (r *Mock) Status() error { + args := r.Called() + return args.Error(0) +} + +func (r *Mock) GetPods(all bool) ([]*Pod, error) { + args := r.Called(all) + return args.Get(0).([]*Pod), args.Error(1) +} + +func (r *Mock) SyncPod(pod *api.Pod, apiStatus api.PodStatus, status *PodStatus, secrets []api.Secret, backOff *flowcontrol.Backoff) PodSyncResult { + args := r.Called(pod, apiStatus, status, secrets, backOff) + return args.Get(0).(PodSyncResult) +} + +func (r *Mock) KillPod(pod *api.Pod, runningPod Pod) error { + args := r.Called(pod, runningPod) + return args.Error(0) +} + +func (r *Mock) RunContainerInPod(container api.Container, pod *api.Pod, volumeMap map[string]volume.VolumePlugin) error { + args := r.Called(pod, pod, volumeMap) + return args.Error(0) +} + +func (r *Mock) KillContainerInPod(container api.Container, pod *api.Pod) error { + args := r.Called(pod, pod) + return args.Error(0) +} + +func (r *Mock) GetPodStatus(uid types.UID, name, namespace string) (*PodStatus, error) { + args := r.Called(uid, name, namespace) + return args.Get(0).(*PodStatus), args.Error(1) +} + +func (r *Mock) ExecInContainer(containerID ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error { + args := r.Called(containerID, cmd, stdin, stdout, stderr, tty) + return args.Error(0) +} + +func (r *Mock) AttachContainer(containerID ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error { + args := r.Called(containerID, stdin, stdout, stderr, tty) + return args.Error(0) +} + +func (r *Mock) RunInContainer(containerID ContainerID, cmd []string) ([]byte, error) { + args := r.Called(containerID, cmd) + return args.Get(0).([]byte), args.Error(1) +} + +func (r *Mock) GetContainerLogs(pod *api.Pod, containerID ContainerID, logOptions *api.PodLogOptions, stdout, stderr io.Writer) (err error) { + args := r.Called(pod, containerID, logOptions, stdout, stderr) + return args.Error(0) +} + +func (r *Mock) PullImage(image ImageSpec, pullSecrets []api.Secret) error { + args := r.Called(image, pullSecrets) + return args.Error(0) +} + +func (r *Mock) IsImagePresent(image ImageSpec) (bool, error) { + args := r.Called(image) + return args.Get(0).(bool), args.Error(1) +} + +func (r *Mock) ListImages() ([]Image, error) { + args := r.Called() + return args.Get(0).([]Image), args.Error(1) +} + +func (r *Mock) RemoveImage(image ImageSpec) error { + args := r.Called(image) + return args.Error(0) +} + +func (r *Mock) PortForward(pod *Pod, port uint16, stream io.ReadWriteCloser) error { + args := r.Called(pod, port, stream) + return args.Error(0) +} + +func (r *Mock) GarbageCollect(gcPolicy ContainerGCPolicy) error { + args := r.Called(gcPolicy) + return args.Error(0) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/container_bridge.go b/vendor/k8s.io/kubernetes/pkg/kubelet/container_bridge.go new file mode 100644 index 000000000..e151dc709 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/container_bridge.go @@ -0,0 +1,167 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "bytes" + "net" + "os" + "os/exec" + "regexp" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/util" +) + +var cidrRegexp = regexp.MustCompile(`inet ([0-9a-fA-F.:]*/[0-9]*)`) + +func createCBR0(wantCIDR *net.IPNet, babysitDaemons bool) error { + // recreate cbr0 with wantCIDR + if err := exec.Command("brctl", "addbr", "cbr0").Run(); err != nil { + glog.Error(err) + return err + } + if err := exec.Command("ip", "addr", "add", wantCIDR.String(), "dev", "cbr0").Run(); err != nil { + glog.Error(err) + return err + } + if err := exec.Command("ip", "link", "set", "dev", "cbr0", "mtu", "1460", "up").Run(); err != nil { + glog.Error(err) + return err + } + // Stop docker so that babysitter process can restart it again with proper configurations and + // checkpoint file (https://github.com/docker/docker/issues/18283). It is safe to kill docker + // process here since CIDR can be changed only once for a given node object, and node is marked + // as NotReady until the docker daemon is restarted with the newly configured custom bridge. + // TODO (dawnchen): Remove this once corrupted checkpoint issue is fixed. + // + // For now just log the error. The containerRuntime check will catch docker failures. + // TODO (dawnchen) figure out what we should do for rkt here. + if babysitDaemons { + if err := exec.Command("pkill", "-KILL", "docker").Run(); err != nil { + glog.Error(err) + } + } else if util.UsingSystemdInitSystem() { + if err := exec.Command("systemctl", "restart", "docker").Run(); err != nil { + glog.Error(err) + } + } else { + if err := exec.Command("service", "docker", "restart").Run(); err != nil { + glog.Error(err) + } + } + glog.V(2).Info("Recreated cbr0 and restarted docker") + return nil +} + +func ensureCbr0(wantCIDR *net.IPNet, promiscuous, babysitDaemons bool) error { + exists, err := cbr0Exists() + if err != nil { + return err + } + if !exists { + glog.V(2).Infof("CBR0 doesn't exist, attempting to create it with range: %s", wantCIDR) + return createCBR0(wantCIDR, babysitDaemons) + } + if !cbr0CidrCorrect(wantCIDR) { + glog.V(2).Infof("Attempting to recreate cbr0 with address range: %s", wantCIDR) + + // delete cbr0 + if err := exec.Command("ip", "link", "set", "dev", "cbr0", "down").Run(); err != nil { + glog.Error(err) + return err + } + if err := exec.Command("brctl", "delbr", "cbr0").Run(); err != nil { + glog.Error(err) + return err + } + if err := createCBR0(wantCIDR, babysitDaemons); err != nil { + glog.Error(err) + return err + } + } + // Put the container bridge into promiscuous mode to force it to accept hairpin packets. + // TODO: Remove this once the kernel bug (#20096) is fixed. + if promiscuous { + // Checking if the bridge is in promiscuous mode is as expensive and more brittle than + // simply setting the flag every time. + if err := exec.Command("ip", "link", "set", "cbr0", "promisc", "on").Run(); err != nil { + glog.Error(err) + return err + } + } + return nil +} + +// Check if cbr0 network interface is configured or not, and take action +// when the configuration is missing on the node, and propagate the rest +// error to kubelet to handle. +func cbr0Exists() (bool, error) { + if _, err := os.Stat("/sys/class/net/cbr0"); err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + return true, nil +} + +func cbr0CidrCorrect(wantCIDR *net.IPNet) bool { + output, err := exec.Command("ip", "addr", "show", "cbr0").Output() + if err != nil { + return false + } + match := cidrRegexp.FindSubmatch(output) + if len(match) < 2 { + return false + } + cbr0IP, cbr0CIDR, err := net.ParseCIDR(string(match[1])) + if err != nil { + glog.Errorf("Couldn't parse CIDR: %q", match[1]) + return false + } + cbr0CIDR.IP = cbr0IP + + glog.V(5).Infof("Want cbr0 CIDR: %s, have cbr0 CIDR: %s", wantCIDR, cbr0CIDR) + return wantCIDR.IP.Equal(cbr0IP) && bytes.Equal(wantCIDR.Mask, cbr0CIDR.Mask) +} + +// TODO(dawnchen): Using pkg/util/iptables +// nonMasqueradeCIDR is the CIDR for our internal IP range; traffic to IPs outside this range will use IP masquerade. +func ensureIPTablesMasqRule(nonMasqueradeCIDR string) error { + // Check if the MASQUERADE rule exist or not + if err := exec.Command("iptables", + "-t", "nat", + "-C", "POSTROUTING", + "!", "-d", nonMasqueradeCIDR, + "-m", "addrtype", "!", "--dst-type", "LOCAL", + "-j", "MASQUERADE").Run(); err == nil { + // The MASQUERADE rule exists + return nil + } + + glog.Infof("MASQUERADE rule doesn't exist, recreate it (with nonMasqueradeCIDR %s)", nonMasqueradeCIDR) + if err := exec.Command("iptables", + "-t", "nat", + "-A", "POSTROUTING", + "!", "-d", nonMasqueradeCIDR, + "-m", "addrtype", "!", "--dst-type", "LOCAL", + "-j", "MASQUERADE").Run(); err != nil { + return err + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/custommetrics/custom_metrics.go b/vendor/k8s.io/kubernetes/pkg/kubelet/custommetrics/custom_metrics.go new file mode 100644 index 000000000..05a628a44 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/custommetrics/custom_metrics.go @@ -0,0 +1,48 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 custommetrics contains support for instrumenting cAdvisor to gather custom metrics from pods. +package custommetrics + +import ( + "path" + + "k8s.io/kubernetes/pkg/api" +) + +const ( + CustomMetricsDefinitionContainerFile = "definition.json" + + CustomMetricsDefinitionDir = "/etc/custom-metrics" +) + +// Alpha implementation. +// Returns a path to a cAdvisor-specific custom metrics configuration. +func GetCAdvisorCustomMetricsDefinitionPath(container *api.Container) (*string, error) { + // Assuemes that the container has Custom Metrics enabled if it has "/etc/custom-metrics" directory + // mounted as a volume. Custom Metrics definition is expected to be in "definition.json". + if container.VolumeMounts != nil { + for _, volumeMount := range container.VolumeMounts { + if path.Clean(volumeMount.MountPath) == path.Clean(CustomMetricsDefinitionDir) { + // TODO: add definition file validation. + definitionPath := path.Clean(path.Join(volumeMount.MountPath, CustomMetricsDefinitionContainerFile)) + return &definitionPath, nil + } + } + } + // No Custom Metrics definition available. + return nil, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/custommetrics/custom_metrics_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/custommetrics/custom_metrics_test.go new file mode 100644 index 000000000..54892ad1b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/custommetrics/custom_metrics_test.go @@ -0,0 +1,48 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 custommetrics + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/kubernetes/pkg/api" +) + +func TestGetCAdvisorCustomMetricsDefinitionPath(t *testing.T) { + + regularContainer := &api.Container{ + Name: "test_container", + } + + cmContainer := &api.Container{ + Name: "test_container", + VolumeMounts: []api.VolumeMount{ + { + Name: "cm", + MountPath: CustomMetricsDefinitionDir, + }, + }, + } + path, err := GetCAdvisorCustomMetricsDefinitionPath(regularContainer) + assert.Nil(t, path) + assert.NoError(t, err) + + path, err = GetCAdvisorCustomMetricsDefinitionPath(cmContainer) + assert.NotEmpty(t, *path) + assert.NoError(t, err) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/disk_manager.go b/vendor/k8s.io/kubernetes/pkg/kubelet/disk_manager.go new file mode 100644 index 000000000..ad110cef4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/disk_manager.go @@ -0,0 +1,137 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "fmt" + "sync" + "time" + + "github.com/golang/glog" + cadvisorapi "github.com/google/cadvisor/info/v2" + "k8s.io/kubernetes/pkg/kubelet/cadvisor" +) + +// Manages policy for diskspace management for disks holding docker images and root fs. + +// mb is used to easily convert an int to an mb +const mb = 1024 * 1024 + +// Implementation is thread-safe. +type diskSpaceManager interface { + // Checks the available disk space + IsRootDiskSpaceAvailable() (bool, error) + IsDockerDiskSpaceAvailable() (bool, error) +} + +type DiskSpacePolicy struct { + // free disk space threshold for filesystem holding docker images. + DockerFreeDiskMB int + // free disk space threshold for root filesystem. Host volumes are created on root fs. + RootFreeDiskMB int +} + +type fsInfo struct { + Usage int64 + Capacity int64 + Available int64 + Timestamp time.Time +} + +type realDiskSpaceManager struct { + cadvisor cadvisor.Interface + cachedInfo map[string]fsInfo // cache of filesystem info. + lock sync.Mutex // protecting cachedInfo. + policy DiskSpacePolicy // thresholds. Set at creation time. +} + +func (dm *realDiskSpaceManager) getFsInfo(fsType string, f func() (cadvisorapi.FsInfo, error)) (fsInfo, error) { + dm.lock.Lock() + defer dm.lock.Unlock() + fsi := fsInfo{} + if info, ok := dm.cachedInfo[fsType]; ok { + timeLimit := time.Now().Add(-2 * time.Second) + if info.Timestamp.After(timeLimit) { + fsi = info + } + } + if fsi.Timestamp.IsZero() { + fs, err := f() + if err != nil { + return fsInfo{}, err + } + fsi.Timestamp = time.Now() + fsi.Usage = int64(fs.Usage) + fsi.Capacity = int64(fs.Capacity) + fsi.Available = int64(fs.Available) + dm.cachedInfo[fsType] = fsi + } + return fsi, nil +} + +func (dm *realDiskSpaceManager) IsDockerDiskSpaceAvailable() (bool, error) { + return dm.isSpaceAvailable("docker", dm.policy.DockerFreeDiskMB, dm.cadvisor.DockerImagesFsInfo) +} + +func (dm *realDiskSpaceManager) IsRootDiskSpaceAvailable() (bool, error) { + return dm.isSpaceAvailable("root", dm.policy.RootFreeDiskMB, dm.cadvisor.RootFsInfo) +} + +func (dm *realDiskSpaceManager) isSpaceAvailable(fsType string, threshold int, f func() (cadvisorapi.FsInfo, error)) (bool, error) { + fsInfo, err := dm.getFsInfo(fsType, f) + if err != nil { + return true, fmt.Errorf("failed to get fs info for %q: %v", fsType, err) + } + if fsInfo.Capacity == 0 { + return true, fmt.Errorf("could not determine capacity for %q fs. Info: %+v", fsType, fsInfo) + } + if fsInfo.Available < 0 { + return true, fmt.Errorf("wrong available space for %q: %+v", fsType, fsInfo) + } + + if fsInfo.Available < int64(threshold)*mb { + glog.Infof("Running out of space on disk for %q: available %d MB, threshold %d MB", fsType, fsInfo.Available/mb, threshold) + return false, nil + } + return true, nil +} + +func validatePolicy(policy DiskSpacePolicy) error { + if policy.DockerFreeDiskMB < 0 { + return fmt.Errorf("free disk space should be non-negative. Invalid value %d for docker disk space threshold.", policy.DockerFreeDiskMB) + } + if policy.RootFreeDiskMB < 0 { + return fmt.Errorf("free disk space should be non-negative. Invalid value %d for root disk space threshold.", policy.RootFreeDiskMB) + } + return nil +} + +func newDiskSpaceManager(cadvisorInterface cadvisor.Interface, policy DiskSpacePolicy) (diskSpaceManager, error) { + // validate policy + err := validatePolicy(policy) + if err != nil { + return nil, err + } + + dm := &realDiskSpaceManager{ + cadvisor: cadvisorInterface, + policy: policy, + cachedInfo: map[string]fsInfo{}, + } + + return dm, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/disk_manager_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/disk_manager_test.go new file mode 100644 index 000000000..378c3ab3f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/disk_manager_test.go @@ -0,0 +1,295 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "fmt" + "testing" + + cadvisorapi "github.com/google/cadvisor/info/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing" +) + +func testPolicy() DiskSpacePolicy { + return DiskSpacePolicy{ + DockerFreeDiskMB: 250, + RootFreeDiskMB: 250, + } +} + +func setUp(t *testing.T) (*assert.Assertions, DiskSpacePolicy, *cadvisortest.Mock) { + assert := assert.New(t) + policy := testPolicy() + c := new(cadvisortest.Mock) + return assert, policy, c +} + +func TestValidPolicy(t *testing.T) { + assert, policy, c := setUp(t) + _, err := newDiskSpaceManager(c, policy) + assert.NoError(err) + + policy = testPolicy() + policy.DockerFreeDiskMB = -1 + _, err = newDiskSpaceManager(c, policy) + assert.Error(err) + + policy = testPolicy() + policy.RootFreeDiskMB = -1 + _, err = newDiskSpaceManager(c, policy) + assert.Error(err) +} + +func TestSpaceAvailable(t *testing.T) { + assert, policy, mockCadvisor := setUp(t) + dm, err := newDiskSpaceManager(mockCadvisor, policy) + assert.NoError(err) + + mockCadvisor.On("DockerImagesFsInfo").Return(cadvisorapi.FsInfo{ + Usage: 400 * mb, + Capacity: 1000 * mb, + Available: 600 * mb, + }, nil) + mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{ + Usage: 9 * mb, + Capacity: 10 * mb, + }, nil) + + ok, err := dm.IsDockerDiskSpaceAvailable() + assert.NoError(err) + assert.True(ok) + + ok, err = dm.IsRootDiskSpaceAvailable() + assert.NoError(err) + assert.False(ok) +} + +// TestIsDockerDiskSpaceAvailableWithSpace verifies IsDockerDiskSpaceAvailable results when +// space is available. +func TestIsDockerDiskSpaceAvailableWithSpace(t *testing.T) { + assert, policy, mockCadvisor := setUp(t) + dm, err := newDiskSpaceManager(mockCadvisor, policy) + require.NoError(t, err) + + // 500MB available + mockCadvisor.On("DockerImagesFsInfo").Return(cadvisorapi.FsInfo{ + Usage: 9500 * mb, + Capacity: 10000 * mb, + Available: 500 * mb, + }, nil) + + ok, err := dm.IsDockerDiskSpaceAvailable() + assert.NoError(err) + assert.True(ok) +} + +// TestIsDockerDiskSpaceAvailableWithoutSpace verifies IsDockerDiskSpaceAvailable results when +// space is not available. +func TestIsDockerDiskSpaceAvailableWithoutSpace(t *testing.T) { + // 1MB available + assert, policy, mockCadvisor := setUp(t) + mockCadvisor.On("DockerImagesFsInfo").Return(cadvisorapi.FsInfo{ + Usage: 999 * mb, + Capacity: 1000 * mb, + Available: 1 * mb, + }, nil) + + dm, err := newDiskSpaceManager(mockCadvisor, policy) + require.NoError(t, err) + + ok, err := dm.IsDockerDiskSpaceAvailable() + assert.NoError(err) + assert.False(ok) +} + +// TestIsRootDiskSpaceAvailableWithSpace verifies IsRootDiskSpaceAvailable results when +// space is available. +func TestIsRootDiskSpaceAvailableWithSpace(t *testing.T) { + assert, policy, mockCadvisor := setUp(t) + policy.RootFreeDiskMB = 10 + dm, err := newDiskSpaceManager(mockCadvisor, policy) + assert.NoError(err) + + // 999MB available + mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{ + Usage: 1 * mb, + Capacity: 1000 * mb, + Available: 999 * mb, + }, nil) + + ok, err := dm.IsRootDiskSpaceAvailable() + assert.NoError(err) + assert.True(ok) +} + +// TestIsRootDiskSpaceAvailableWithoutSpace verifies IsRootDiskSpaceAvailable results when +// space is not available. +func TestIsRootDiskSpaceAvailableWithoutSpace(t *testing.T) { + assert, policy, mockCadvisor := setUp(t) + policy.RootFreeDiskMB = 10 + dm, err := newDiskSpaceManager(mockCadvisor, policy) + assert.NoError(err) + + // 9MB available + mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{ + Usage: 990 * mb, + Capacity: 1000 * mb, + Available: 9 * mb, + }, nil) + + ok, err := dm.IsRootDiskSpaceAvailable() + assert.NoError(err) + assert.False(ok) +} + +// TestCache verifies that caching works properly with DiskSpaceAvailable calls +func TestCache(t *testing.T) { + assert, policy, mockCadvisor := setUp(t) + dm, err := newDiskSpaceManager(mockCadvisor, policy) + assert.NoError(err) + + mockCadvisor.On("DockerImagesFsInfo").Return(cadvisorapi.FsInfo{ + Usage: 400 * mb, + Capacity: 1000 * mb, + Available: 300 * mb, + }, nil).Once() + mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{ + Usage: 500 * mb, + Capacity: 1000 * mb, + Available: 500 * mb, + }, nil).Once() + + // Initial calls which should be recorded in mockCadvisor + ok, err := dm.IsDockerDiskSpaceAvailable() + assert.NoError(err) + assert.True(ok) + + ok, err = dm.IsRootDiskSpaceAvailable() + assert.NoError(err) + assert.True(ok) + + // Get the current count of calls to mockCadvisor + cadvisorCallCount := len(mockCadvisor.Calls) + + // Checking for space again shouldn't need to mock as cache would serve it. + ok, err = dm.IsDockerDiskSpaceAvailable() + assert.NoError(err) + assert.True(ok) + + ok, err = dm.IsRootDiskSpaceAvailable() + assert.NoError(err) + assert.True(ok) + + // Ensure no more calls to the mockCadvisor occurred + assert.Equal(cadvisorCallCount, len(mockCadvisor.Calls)) +} + +// TestFsInfoError verifies errors are returned by DiskSpaceAvailable calls +// when FsInfo calls return an error +func TestFsInfoError(t *testing.T) { + assert, policy, mockCadvisor := setUp(t) + policy.RootFreeDiskMB = 10 + dm, err := newDiskSpaceManager(mockCadvisor, policy) + assert.NoError(err) + + mockCadvisor.On("DockerImagesFsInfo").Return(cadvisorapi.FsInfo{}, fmt.Errorf("can't find fs")) + mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{}, fmt.Errorf("EBUSY")) + ok, err := dm.IsDockerDiskSpaceAvailable() + assert.Error(err) + assert.True(ok) + ok, err = dm.IsRootDiskSpaceAvailable() + assert.Error(err) + assert.True(ok) +} + +// Test_getFSInfo verifies multiple possible cases for getFsInfo. +func Test_getFsInfo(t *testing.T) { + assert, policy, mockCadvisor := setUp(t) + + // Sunny day case + mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{ + Usage: 10 * mb, + Capacity: 100 * mb, + Available: 90 * mb, + }, nil).Once() + + dm := &realDiskSpaceManager{ + cadvisor: mockCadvisor, + policy: policy, + cachedInfo: map[string]fsInfo{}, + } + + available, err := dm.isSpaceAvailable("root", 10, dm.cadvisor.RootFsInfo) + assert.True(available) + assert.NoError(err) + + // Threshold case + mockCadvisor = new(cadvisortest.Mock) + mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{ + Usage: 9 * mb, + Capacity: 100 * mb, + Available: 9 * mb, + }, nil).Once() + + dm = &realDiskSpaceManager{ + cadvisor: mockCadvisor, + policy: policy, + cachedInfo: map[string]fsInfo{}, + } + available, err = dm.isSpaceAvailable("root", 10, dm.cadvisor.RootFsInfo) + assert.False(available) + assert.NoError(err) + + // Frozen case + mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{ + Usage: 9 * mb, + Capacity: 10 * mb, + Available: 500 * mb, + }, nil).Once() + + dm = &realDiskSpaceManager{ + cadvisor: mockCadvisor, + policy: policy, + cachedInfo: map[string]fsInfo{}, + } + available, err = dm.isSpaceAvailable("root", 10, dm.cadvisor.RootFsInfo) + assert.True(available) + assert.NoError(err) + + // Capacity error case + mockCadvisor = new(cadvisortest.Mock) + mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{ + Usage: 9 * mb, + Capacity: 0, + Available: 500 * mb, + }, nil).Once() + + dm = &realDiskSpaceManager{ + cadvisor: mockCadvisor, + policy: policy, + cachedInfo: map[string]fsInfo{}, + } + available, err = dm.isSpaceAvailable("root", 10, dm.cadvisor.RootFsInfo) + assert.True(available) + assert.Error(err) + assert.Contains(fmt.Sprintf("%s", err), "could not determine capacity") + + // Available error case skipped as v2.FSInfo uses uint64 and this + // can not be less than 0 +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/doc.go b/vendor/k8s.io/kubernetes/pkg/kubelet/doc.go new file mode 100644 index 000000000..8fd7b3b3d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 kubelet is the package that contains the libraries that drive the Kubelet binary. +// The kubelet is responsible for node level pod management. It runs on each worker in the cluster. +package kubelet diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/container_gc.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/container_gc.go new file mode 100644 index 000000000..fae2194b0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/container_gc.go @@ -0,0 +1,251 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "fmt" + "os" + "path" + "path/filepath" + "sort" + "time" + + docker "github.com/fsouza/go-dockerclient" + "github.com/golang/glog" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/types" +) + +type containerGC struct { + client DockerInterface + podGetter podGetter + containerLogsDir string +} + +func NewContainerGC(client DockerInterface, podGetter podGetter, containerLogsDir string) *containerGC { + return &containerGC{ + client: client, + podGetter: podGetter, + containerLogsDir: containerLogsDir, + } +} + +// Internal information kept for containers being considered for GC. +type containerGCInfo struct { + // Docker ID of the container. + id string + + // Docker name of the container. + name string + + // Creation time for the container. + createTime time.Time + + // Full pod name, including namespace in the format `namespace_podName`. + // This comes from dockertools.ParseDockerName(...) + podNameWithNamespace string + + // Container name in pod + containerName string +} + +// Containers are considered for eviction as units of (UID, container name) pair. +type evictUnit struct { + // UID of the pod. + uid types.UID + + // Name of the container in the pod. + name string +} + +type containersByEvictUnit map[evictUnit][]containerGCInfo + +// Returns the number of containers in this map. +func (cu containersByEvictUnit) NumContainers() int { + num := 0 + for key := range cu { + num += len(cu[key]) + } + + return num +} + +// Returns the number of pod in this map. +func (cu containersByEvictUnit) NumEvictUnits() int { + return len(cu) +} + +// Newest first. +type byCreated []containerGCInfo + +func (a byCreated) Len() int { return len(a) } +func (a byCreated) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byCreated) Less(i, j int) bool { return a[i].createTime.After(a[j].createTime) } + +func (cgc *containerGC) enforceMaxContainersPerEvictUnit(evictUnits containersByEvictUnit, MaxContainers int) { + for uid := range evictUnits { + toRemove := len(evictUnits[uid]) - MaxContainers + + if toRemove > 0 { + evictUnits[uid] = cgc.removeOldestN(evictUnits[uid], toRemove) + } + } +} + +// Removes the oldest toRemove containers and returns the resulting slice. +func (cgc *containerGC) removeOldestN(containers []containerGCInfo, toRemove int) []containerGCInfo { + // Remove from oldest to newest (last to first). + numToKeep := len(containers) - toRemove + for i := numToKeep; i < len(containers); i++ { + err := cgc.client.RemoveContainer(docker.RemoveContainerOptions{ID: containers[i].id, RemoveVolumes: true}) + if err != nil { + glog.Warningf("Failed to remove dead container %q: %v", containers[i].name, err) + } + symlinkPath := LogSymlink(cgc.containerLogsDir, containers[i].podNameWithNamespace, containers[i].containerName, containers[i].id) + err = os.Remove(symlinkPath) + if err != nil && !os.IsNotExist(err) { + glog.Warningf("Failed to remove container %q log symlink %q: %v", containers[i].name, symlinkPath, err) + } + } + + // Assume we removed the containers so that we're not too aggressive. + return containers[:numToKeep] +} + +// Get all containers that are evictable. Evictable containers are: not running +// and created more than MinAge ago. +func (cgc *containerGC) evictableContainers(minAge time.Duration) (containersByEvictUnit, []containerGCInfo, error) { + containers, err := GetKubeletDockerContainers(cgc.client, true) + if err != nil { + return containersByEvictUnit{}, []containerGCInfo{}, err + } + + unidentifiedContainers := make([]containerGCInfo, 0) + evictUnits := make(containersByEvictUnit) + newestGCTime := time.Now().Add(-minAge) + for _, container := range containers { + // Prune out running containers. + data, err := cgc.client.InspectContainer(container.ID) + if err != nil { + // Container may have been removed already, skip. + continue + } else if data.State.Running { + continue + } else if newestGCTime.Before(data.Created) { + continue + } + + containerInfo := containerGCInfo{ + id: container.ID, + name: container.Names[0], + createTime: data.Created, + } + + containerName, _, err := ParseDockerName(container.Names[0]) + + if err != nil { + unidentifiedContainers = append(unidentifiedContainers, containerInfo) + } else { + key := evictUnit{ + uid: containerName.PodUID, + name: containerName.ContainerName, + } + containerInfo.podNameWithNamespace = containerName.PodFullName + containerInfo.containerName = containerName.ContainerName + evictUnits[key] = append(evictUnits[key], containerInfo) + } + } + + // Sort the containers by age. + for uid := range evictUnits { + sort.Sort(byCreated(evictUnits[uid])) + } + + return evictUnits, unidentifiedContainers, nil +} + +// GarbageCollect removes dead containers using the specified container gc policy +func (cgc *containerGC) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy) error { + // Separate containers by evict units. + evictUnits, unidentifiedContainers, err := cgc.evictableContainers(gcPolicy.MinAge) + if err != nil { + return err + } + + // Remove unidentified containers. + for _, container := range unidentifiedContainers { + glog.Infof("Removing unidentified dead container %q with ID %q", container.name, container.id) + err = cgc.client.RemoveContainer(docker.RemoveContainerOptions{ID: container.id, RemoveVolumes: true}) + if err != nil { + glog.Warningf("Failed to remove unidentified dead container %q: %v", container.name, err) + } + } + + // Remove deleted pod containers. + for key, unit := range evictUnits { + if cgc.isPodDeleted(key.uid) { + cgc.removeOldestN(unit, len(unit)) // Remove all. + delete(evictUnits, key) + } + } + + // Enforce max containers per evict unit. + if gcPolicy.MaxPerPodContainer >= 0 { + cgc.enforceMaxContainersPerEvictUnit(evictUnits, gcPolicy.MaxPerPodContainer) + } + + // Enforce max total number of containers. + if gcPolicy.MaxContainers >= 0 && evictUnits.NumContainers() > gcPolicy.MaxContainers { + // Leave an equal number of containers per evict unit (min: 1). + numContainersPerEvictUnit := gcPolicy.MaxContainers / evictUnits.NumEvictUnits() + if numContainersPerEvictUnit < 1 { + numContainersPerEvictUnit = 1 + } + cgc.enforceMaxContainersPerEvictUnit(evictUnits, numContainersPerEvictUnit) + + // If we still need to evict, evict oldest first. + numContainers := evictUnits.NumContainers() + if numContainers > gcPolicy.MaxContainers { + flattened := make([]containerGCInfo, 0, numContainers) + for uid := range evictUnits { + flattened = append(flattened, evictUnits[uid]...) + } + sort.Sort(byCreated(flattened)) + + cgc.removeOldestN(flattened, numContainers-gcPolicy.MaxContainers) + } + } + + // Remove dead symlinks - should only happen on upgrade + // from a k8s version without proper log symlink cleanup + logSymlinks, _ := filepath.Glob(path.Join(cgc.containerLogsDir, fmt.Sprintf("*.%s", LogSuffix))) + for _, logSymlink := range logSymlinks { + if _, err = os.Stat(logSymlink); os.IsNotExist(err) { + err = os.Remove(logSymlink) + if err != nil { + glog.Warningf("Failed to remove container log dead symlink %q: %v", logSymlink, err) + } + } + } + + return nil +} + +func (cgc *containerGC) isPodDeleted(podUID types.UID) bool { + _, found := cgc.podGetter.GetPodByUID(podUID) + return !found +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/container_gc_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/container_gc_test.go new file mode 100644 index 000000000..d6038e26a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/container_gc_test.go @@ -0,0 +1,251 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "fmt" + "reflect" + "sort" + "testing" + "time" + + docker "github.com/fsouza/go-dockerclient" + "github.com/stretchr/testify/assert" + "k8s.io/kubernetes/pkg/api" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/types" +) + +func newTestContainerGC(t *testing.T) (*containerGC, *FakeDockerClient) { + fakeDocker := new(FakeDockerClient) + fakePodGetter := newFakePodGetter() + gc := NewContainerGC(fakeDocker, fakePodGetter, "") + return gc, fakeDocker +} + +// Makes a stable time object, lower id is earlier time. +func makeTime(id int) time.Time { + var zero time.Time + return zero.Add(time.Duration(id) * time.Second) +} + +// Makes a container with the specified properties. +func makeContainer(id, uid, name string, running bool, created time.Time) *docker.Container { + return &docker.Container{ + Name: fmt.Sprintf("/k8s_%s_bar_new_%s_42", name, uid), + State: docker.State{ + Running: running, + }, + ID: id, + Created: created, + } +} + +// Makes a container with unidentified name and specified properties. +func makeUndefinedContainer(id string, running bool, created time.Time) *docker.Container { + return &docker.Container{ + Name: "/k8s_unidentified", + State: docker.State{ + Running: running, + }, + ID: id, + Created: created, + } +} + +func addPods(podGetter podGetter, podUIDs ...types.UID) { + fakePodGetter := podGetter.(*fakePodGetter) + for _, uid := range podUIDs { + fakePodGetter.pods[uid] = &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod" + string(uid), + Namespace: "test", + UID: uid, + }, + } + } +} + +func verifyStringArrayEqualsAnyOrder(t *testing.T, actual, expected []string) { + act := make([]string, len(actual)) + exp := make([]string, len(expected)) + copy(act, actual) + copy(exp, expected) + + sort.StringSlice(act).Sort() + sort.StringSlice(exp).Sort() + + if !reflect.DeepEqual(exp, act) { + t.Errorf("Expected(sorted): %#v, Actual(sorted): %#v", exp, act) + } +} + +func TestGarbageCollectZeroMaxContainers(t *testing.T) { + gc, fakeDocker := newTestContainerGC(t) + fakeDocker.SetFakeContainers([]*docker.Container{ + makeContainer("1876", "foo", "POD", false, makeTime(0)), + }) + addPods(gc.podGetter, "foo") + + assert.Nil(t, gc.GarbageCollect(kubecontainer.ContainerGCPolicy{MinAge: time.Minute, MaxPerPodContainer: 1, MaxContainers: 0})) + assert.Len(t, fakeDocker.Removed, 1) +} + +func TestGarbageCollectNoMaxPerPodContainerLimit(t *testing.T) { + gc, fakeDocker := newTestContainerGC(t) + fakeDocker.SetFakeContainers([]*docker.Container{ + makeContainer("1876", "foo", "POD", false, makeTime(0)), + makeContainer("2876", "foo1", "POD", false, makeTime(1)), + makeContainer("3876", "foo2", "POD", false, makeTime(2)), + makeContainer("4876", "foo3", "POD", false, makeTime(3)), + makeContainer("5876", "foo4", "POD", false, makeTime(4)), + }) + addPods(gc.podGetter, "foo", "foo1", "foo2", "foo3", "foo4") + + assert.Nil(t, gc.GarbageCollect(kubecontainer.ContainerGCPolicy{MinAge: time.Minute, MaxPerPodContainer: -1, MaxContainers: 4})) + assert.Len(t, fakeDocker.Removed, 1) +} + +func TestGarbageCollectNoMaxLimit(t *testing.T) { + gc, fakeDocker := newTestContainerGC(t) + fakeDocker.SetFakeContainers([]*docker.Container{ + makeContainer("1876", "foo", "POD", false, makeTime(0)), + makeContainer("2876", "foo1", "POD", false, makeTime(0)), + makeContainer("3876", "foo2", "POD", false, makeTime(0)), + makeContainer("4876", "foo3", "POD", false, makeTime(0)), + makeContainer("5876", "foo4", "POD", false, makeTime(0)), + }) + addPods(gc.podGetter, "foo", "foo1", "foo2", "foo3", "foo4") + + assert.Len(t, fakeDocker.Removed, 0) +} + +func TestGarbageCollect(t *testing.T) { + tests := []struct { + containers []*docker.Container + expectedRemoved []string + }{ + // Don't remove containers started recently. + { + containers: []*docker.Container{ + makeContainer("1876", "foo", "POD", false, time.Now()), + makeContainer("2876", "foo", "POD", false, time.Now()), + makeContainer("3876", "foo", "POD", false, time.Now()), + }, + }, + // Remove oldest containers. + { + containers: []*docker.Container{ + makeContainer("1876", "foo", "POD", false, makeTime(0)), + makeContainer("2876", "foo", "POD", false, makeTime(1)), + makeContainer("3876", "foo", "POD", false, makeTime(2)), + }, + expectedRemoved: []string{"1876"}, + }, + // Only remove non-running containers. + { + containers: []*docker.Container{ + makeContainer("1876", "foo", "POD", true, makeTime(0)), + makeContainer("2876", "foo", "POD", false, makeTime(1)), + makeContainer("3876", "foo", "POD", false, makeTime(2)), + makeContainer("4876", "foo", "POD", false, makeTime(3)), + }, + expectedRemoved: []string{"2876"}, + }, + // Less than maxContainerCount doesn't delete any. + { + containers: []*docker.Container{ + makeContainer("1876", "foo", "POD", false, makeTime(0)), + }, + }, + // maxContainerCount applies per (UID,container) pair. + { + containers: []*docker.Container{ + makeContainer("1876", "foo", "POD", false, makeTime(0)), + makeContainer("2876", "foo", "POD", false, makeTime(1)), + makeContainer("3876", "foo", "POD", false, makeTime(2)), + makeContainer("1076", "foo", "bar", false, makeTime(0)), + makeContainer("2076", "foo", "bar", false, makeTime(1)), + makeContainer("3076", "foo", "bar", false, makeTime(2)), + makeContainer("1176", "foo2", "POD", false, makeTime(0)), + makeContainer("2176", "foo2", "POD", false, makeTime(1)), + makeContainer("3176", "foo2", "POD", false, makeTime(2)), + }, + expectedRemoved: []string{"1076", "1176", "1876"}, + }, + // Remove non-running unidentified Kubernetes containers. + { + containers: []*docker.Container{ + makeUndefinedContainer("1876", true, makeTime(0)), + makeUndefinedContainer("2876", false, makeTime(0)), + makeContainer("3876", "foo", "POD", false, makeTime(0)), + }, + expectedRemoved: []string{"2876"}, + }, + // Max limit applied and tries to keep from every pod. + { + containers: []*docker.Container{ + makeContainer("1876", "foo", "POD", false, makeTime(0)), + makeContainer("2876", "foo", "POD", false, makeTime(1)), + makeContainer("3876", "foo1", "POD", false, makeTime(0)), + makeContainer("4876", "foo1", "POD", false, makeTime(1)), + makeContainer("5876", "foo2", "POD", false, makeTime(0)), + makeContainer("6876", "foo2", "POD", false, makeTime(1)), + makeContainer("7876", "foo3", "POD", false, makeTime(0)), + makeContainer("8876", "foo3", "POD", false, makeTime(1)), + makeContainer("9876", "foo4", "POD", false, makeTime(0)), + makeContainer("10876", "foo4", "POD", false, makeTime(1)), + }, + expectedRemoved: []string{"1876", "3876", "5876", "7876", "9876"}, + }, + // If more pods than limit allows, evicts oldest pod. + { + containers: []*docker.Container{ + makeContainer("1876", "foo", "POD", false, makeTime(1)), + makeContainer("2876", "foo", "POD", false, makeTime(2)), + makeContainer("3876", "foo1", "POD", false, makeTime(1)), + makeContainer("4876", "foo1", "POD", false, makeTime(2)), + makeContainer("5876", "foo2", "POD", false, makeTime(0)), + makeContainer("6876", "foo3", "POD", false, makeTime(1)), + makeContainer("7876", "foo4", "POD", false, makeTime(0)), + makeContainer("8876", "foo5", "POD", false, makeTime(1)), + makeContainer("9876", "foo6", "POD", false, makeTime(2)), + makeContainer("10876", "foo7", "POD", false, makeTime(1)), + }, + expectedRemoved: []string{"1876", "3876", "5876", "7876"}, + }, + // Containers for deleted pods should be GC'd. + { + containers: []*docker.Container{ + makeContainer("1876", "foo", "POD", false, makeTime(1)), + makeContainer("2876", "foo", "POD", false, makeTime(2)), + makeContainer("3876", "deleted", "POD", false, makeTime(1)), + makeContainer("4876", "deleted", "POD", false, makeTime(2)), + makeContainer("5876", "deleted", "POD", false, time.Now()), // Deleted pods still respect MinAge. + }, + expectedRemoved: []string{"3876", "4876"}, + }, + } + for i, test := range tests { + t.Logf("Running test case with index %d", i) + gc, fakeDocker := newTestContainerGC(t) + fakeDocker.SetFakeContainers(test.containers) + addPods(gc.podGetter, "foo", "foo1", "foo2", "foo3", "foo4", "foo5", "foo6", "foo7") + assert.Nil(t, gc.GarbageCollect(kubecontainer.ContainerGCPolicy{MinAge: time.Hour, MaxPerPodContainer: 2, MaxContainers: 6})) + verifyStringArrayEqualsAnyOrder(t, fakeDocker.Removed, test.expectedRemoved) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/convert.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/convert.go new file mode 100644 index 000000000..5de700283 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/convert.go @@ -0,0 +1,83 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "fmt" + "strings" + + docker "github.com/fsouza/go-dockerclient" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" +) + +// This file contains helper functions to convert docker API types to runtime +// (kubecontainer) types. +const ( + statusRunningPrefix = "Up" + statusExitedPrefix = "Exited" +) + +func mapState(state string) kubecontainer.ContainerState { + // Parse the state string in docker.APIContainers. This could break when + // we upgrade docker. + switch { + case strings.HasPrefix(state, statusRunningPrefix): + return kubecontainer.ContainerStateRunning + case strings.HasPrefix(state, statusExitedPrefix): + return kubecontainer.ContainerStateExited + default: + return kubecontainer.ContainerStateUnknown + } +} + +// Converts docker.APIContainers to kubecontainer.Container. +func toRuntimeContainer(c *docker.APIContainers) (*kubecontainer.Container, error) { + if c == nil { + return nil, fmt.Errorf("unable to convert a nil pointer to a runtime container") + } + + dockerName, hash, err := getDockerContainerNameInfo(c) + if err != nil { + return nil, err + } + + return &kubecontainer.Container{ + ID: kubecontainer.DockerID(c.ID).ContainerID(), + Name: dockerName.ContainerName, + Image: c.Image, + Hash: hash, + Created: c.Created, + // (random-liu) docker uses status to indicate whether a container is running or exited. + // However, in kubernetes we usually use state to indicate whether a container is running or exited, + // while use status to indicate the comprehensive status of the container. So we have different naming + // norm here. + State: mapState(c.Status), + }, nil +} + +// Converts docker.APIImages to kubecontainer.Image. +func toRuntimeImage(image *docker.APIImages) (*kubecontainer.Image, error) { + if image == nil { + return nil, fmt.Errorf("unable to convert a nil pointer to a runtime image") + } + + return &kubecontainer.Image{ + ID: image.ID, + RepoTags: image.RepoTags, + Size: image.VirtualSize, + }, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/convert_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/convert_test.go new file mode 100644 index 000000000..2e18ba77a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/convert_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "reflect" + "testing" + + docker "github.com/fsouza/go-dockerclient" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" +) + +func TestMapState(t *testing.T) { + testCases := []struct { + input string + expected kubecontainer.ContainerState + }{ + {input: "Up 5 hours", expected: kubecontainer.ContainerStateRunning}, + {input: "Exited (0) 2 hours ago", expected: kubecontainer.ContainerStateExited}, + {input: "Created", expected: kubecontainer.ContainerStateUnknown}, + {input: "Random string", expected: kubecontainer.ContainerStateUnknown}, + } + + for i, test := range testCases { + if actual := mapState(test.input); actual != test.expected { + t.Errorf("Test[%d]: expected %q, got %q", i, test.expected, actual) + } + } +} + +func TestToRuntimeContainer(t *testing.T) { + original := &docker.APIContainers{ + ID: "ab2cdf", + Image: "bar_image", + Created: 12345, + Names: []string{"/k8s_bar.5678_foo_ns_1234_42"}, + Status: "Up 5 hours", + } + expected := &kubecontainer.Container{ + ID: kubecontainer.ContainerID{Type: "docker", ID: "ab2cdf"}, + Name: "bar", + Image: "bar_image", + Hash: 0x5678, + Created: 12345, + State: kubecontainer.ContainerStateRunning, + } + + actual, err := toRuntimeContainer(original) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + if !reflect.DeepEqual(expected, actual) { + t.Errorf("expected %#v, got %#v", expected, actual) + } +} + +func TestToRuntimeImage(t *testing.T) { + original := &docker.APIImages{ + ID: "aeeea", + RepoTags: []string{"abc", "def"}, + VirtualSize: 1234, + } + expected := &kubecontainer.Image{ + ID: "aeeea", + RepoTags: []string{"abc", "def"}, + Size: 1234, + } + + actual, err := toRuntimeImage(original) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + if !reflect.DeepEqual(expected, actual) { + t.Errorf("expected %#v, got %#v", expected, actual) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/docker.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/docker.go new file mode 100644 index 000000000..9ae1ed5e4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/docker.go @@ -0,0 +1,373 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "fmt" + "math/rand" + "net/http" + "path" + "strconv" + "strings" + + "github.com/docker/docker/pkg/jsonmessage" + dockerapi "github.com/docker/engine-api/client" + docker "github.com/fsouza/go-dockerclient" + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/credentialprovider" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/leaky" + "k8s.io/kubernetes/pkg/types" + utilerrors "k8s.io/kubernetes/pkg/util/errors" + "k8s.io/kubernetes/pkg/util/flowcontrol" + "k8s.io/kubernetes/pkg/util/parsers" +) + +const ( + PodInfraContainerName = leaky.PodInfraContainerName + DockerPrefix = "docker://" + LogSuffix = "log" +) + +const ( + // Taken from lmctfy https://github.com/google/lmctfy/blob/master/lmctfy/controllers/cpu_controller.cc + minShares = 2 + sharesPerCPU = 1024 + milliCPUToCPU = 1000 + + // 100000 is equivalent to 100ms + quotaPeriod = 100000 + minQuotaPerod = 1000 +) + +// DockerInterface is an abstract interface for testability. It abstracts the interface of docker.Client. +type DockerInterface interface { + ListContainers(options docker.ListContainersOptions) ([]docker.APIContainers, error) + InspectContainer(id string) (*docker.Container, error) + CreateContainer(docker.CreateContainerOptions) (*docker.Container, error) + StartContainer(id string, hostConfig *docker.HostConfig) error + StopContainer(id string, timeout uint) error + RemoveContainer(opts docker.RemoveContainerOptions) error + InspectImage(image string) (*docker.Image, error) + ListImages(opts docker.ListImagesOptions) ([]docker.APIImages, error) + PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error + RemoveImage(image string) error + Logs(opts docker.LogsOptions) error + Version() (*docker.Env, error) + Info() (*docker.Env, error) + CreateExec(docker.CreateExecOptions) (*docker.Exec, error) + StartExec(string, docker.StartExecOptions) error + InspectExec(id string) (*docker.ExecInspect, error) + AttachToContainer(opts docker.AttachToContainerOptions) error +} + +// KubeletContainerName encapsulates a pod name and a Kubernetes container name. +type KubeletContainerName struct { + PodFullName string + PodUID types.UID + ContainerName string +} + +// containerNamePrefix is used to identify the containers on the node managed by this +// process. +var containerNamePrefix = "k8s" + +// SetContainerNamePrefix allows the container prefix name for this process to be changed. +// This is intended to support testing and bootstrapping experimentation. It cannot be +// changed once the Kubelet starts. +func SetContainerNamePrefix(prefix string) { + containerNamePrefix = prefix +} + +// DockerPuller is an abstract interface for testability. It abstracts image pull operations. +type DockerPuller interface { + Pull(image string, secrets []api.Secret) error + IsImagePresent(image string) (bool, error) +} + +// dockerPuller is the default implementation of DockerPuller. +type dockerPuller struct { + client DockerInterface + keyring credentialprovider.DockerKeyring +} + +type throttledDockerPuller struct { + puller dockerPuller + limiter flowcontrol.RateLimiter +} + +// newDockerPuller creates a new instance of the default implementation of DockerPuller. +func newDockerPuller(client DockerInterface, qps float32, burst int) DockerPuller { + dp := dockerPuller{ + client: client, + keyring: credentialprovider.NewDockerKeyring(), + } + + if qps == 0.0 { + return dp + } + return &throttledDockerPuller{ + puller: dp, + limiter: flowcontrol.NewTokenBucketRateLimiter(qps, burst), + } +} + +func filterHTTPError(err error, image string) error { + // docker/docker/pull/11314 prints detailed error info for docker pull. + // When it hits 502, it returns a verbose html output including an inline svg, + // which makes the output of kubectl get pods much harder to parse. + // Here converts such verbose output to a concise one. + jerr, ok := err.(*jsonmessage.JSONError) + if ok && (jerr.Code == http.StatusBadGateway || + jerr.Code == http.StatusServiceUnavailable || + jerr.Code == http.StatusGatewayTimeout) { + glog.V(2).Infof("Pulling image %q failed: %v", image, err) + return kubecontainer.RegistryUnavailable + } else { + return err + } +} + +func (p dockerPuller) Pull(image string, secrets []api.Secret) error { + // If no tag was specified, use the default "latest". + repoToPull, tag := parsers.ParseImageName(image) + + opts := docker.PullImageOptions{ + Repository: repoToPull, + Tag: tag, + } + + keyring, err := credentialprovider.MakeDockerKeyring(secrets, p.keyring) + if err != nil { + return err + } + + creds, haveCredentials := keyring.Lookup(repoToPull) + if !haveCredentials { + glog.V(1).Infof("Pulling image %s without credentials", image) + + err := p.client.PullImage(opts, docker.AuthConfiguration{}) + if err == nil { + // Sometimes PullImage failed with no error returned. + exist, ierr := p.IsImagePresent(image) + if ierr != nil { + glog.Warningf("Failed to inspect image %s: %v", image, ierr) + } + if !exist { + return fmt.Errorf("image pull failed for unknown error") + } + return nil + } + + // Image spec: [/]/[: 1 { + hash, err = strconv.ParseUint(nameParts[1], 16, 32) + if err != nil { + glog.Warningf("invalid container hash %q in container %q", nameParts[1], name) + } + } + + podFullName := parts[2] + "_" + parts[3] + podUID := types.UID(parts[4]) + + return &KubeletContainerName{podFullName, podUID, containerName}, hash, nil +} + +func LogSymlink(containerLogsDir, podFullName, containerName, dockerId string) string { + return path.Join(containerLogsDir, fmt.Sprintf("%s_%s-%s.%s", podFullName, containerName, dockerId, LogSuffix)) +} + +// Get a *dockerapi.Client, either using the endpoint passed in, or using +// DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT path per their spec +func getDockerClient(dockerEndpoint string) (*dockerapi.Client, error) { + if len(dockerEndpoint) > 0 { + glog.Infof("Connecting to docker on %s", dockerEndpoint) + return dockerapi.NewClient(dockerEndpoint, "", nil, nil) + } + return dockerapi.NewEnvClient() +} + +// ConnectToDockerOrDie creates docker client connecting to docker daemon. +// If the endpoint passed in is "fake://", a fake docker client +// will be returned. The program exits if error occurs. +func ConnectToDockerOrDie(dockerEndpoint string) DockerInterface { + if dockerEndpoint == "fake://" { + return &FakeDockerClient{ + VersionInfo: docker.Env{"ApiVersion=1.18", "Version=1.6.0"}, + } + } + client, err := getDockerClient(dockerEndpoint) + if err != nil { + glog.Fatalf("Couldn't connect to docker: %v", err) + } + return newKubeDockerClient(client) +} + +// milliCPUToQuota converts milliCPU to CFS quota and period values +func milliCPUToQuota(milliCPU int64) (quota int64, period int64) { + // CFS quota is measured in two values: + // - cfs_period_us=100ms (the amount of time to measure usage across) + // - cfs_quota=20ms (the amount of cpu time allowed to be used across a period) + // so in the above example, you are limited to 20% of a single CPU + // for multi-cpu environments, you just scale equivalent amounts + + if milliCPU == 0 { + // take the default behavior from docker + return + } + + // we set the period to 100ms by default + period = quotaPeriod + + // we then convert your milliCPU to a value normalized over a period + quota = (milliCPU * quotaPeriod) / milliCPUToCPU + + // quota needs to be a minimum of 1ms. + if quota < minQuotaPerod { + quota = minQuotaPerod + } + + return +} + +func milliCPUToShares(milliCPU int64) int64 { + if milliCPU == 0 { + // Docker converts zero milliCPU to unset, which maps to kernel default + // for unset: 1024. Return 2 here to really match kernel default for + // zero milliCPU. + return minShares + } + // Conceptually (milliCPU / milliCPUToCPU) * sharesPerCPU, but factored to improve rounding. + shares := (milliCPU * sharesPerCPU) / milliCPUToCPU + if shares < minShares { + return minShares + } + return shares +} + +// GetKubeletDockerContainers lists all container or just the running ones. +// Returns a list of docker containers that we manage +// TODO: Move this function with dockerCache to DockerManager. +func GetKubeletDockerContainers(client DockerInterface, allContainers bool) ([]*docker.APIContainers, error) { + result := []*docker.APIContainers{} + containers, err := client.ListContainers(docker.ListContainersOptions{All: allContainers}) + if err != nil { + return nil, err + } + for i := range containers { + container := &containers[i] + if len(container.Names) == 0 { + continue + } + // Skip containers that we didn't create to allow users to manually + // spin up their own containers if they want. + // TODO(dchen1107): Remove the old separator "--" by end of Oct + if !strings.HasPrefix(container.Names[0], "/"+containerNamePrefix+"_") && + !strings.HasPrefix(container.Names[0], "/"+containerNamePrefix+"--") { + glog.V(3).Infof("Docker Container: %s is not managed by kubelet.", container.Names[0]) + continue + } + result = append(result, container) + } + return result, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/docker_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/docker_test.go new file mode 100644 index 000000000..4c4f3caaf --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/docker_test.go @@ -0,0 +1,851 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "encoding/json" + "fmt" + "hash/adler32" + "reflect" + "sort" + "strconv" + "strings" + "testing" + + "github.com/docker/docker/pkg/jsonmessage" + docker "github.com/fsouza/go-dockerclient" + cadvisorapi "github.com/google/cadvisor/info/v1" + "k8s.io/kubernetes/cmd/kubelet/app/options" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/credentialprovider" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" + "k8s.io/kubernetes/pkg/kubelet/network" + nettest "k8s.io/kubernetes/pkg/kubelet/network/testing" + "k8s.io/kubernetes/pkg/types" + hashutil "k8s.io/kubernetes/pkg/util/hash" + "k8s.io/kubernetes/pkg/util/parsers" +) + +func verifyCalls(t *testing.T, fakeDocker *FakeDockerClient, calls []string) { + fakeDocker.Lock() + defer fakeDocker.Unlock() + verifyStringArrayEquals(t, fakeDocker.called, calls) +} + +func verifyStringArrayEquals(t *testing.T, actual, expected []string) { + invalid := len(actual) != len(expected) + if !invalid { + for ix, value := range actual { + if expected[ix] != value { + invalid = true + } + } + } + if invalid { + t.Errorf("Expected: %#v, Actual: %#v", expected, actual) + } +} + +func findPodContainer(dockerContainers []*docker.APIContainers, podFullName string, uid types.UID, containerName string) (*docker.APIContainers, bool, uint64) { + for _, dockerContainer := range dockerContainers { + if len(dockerContainer.Names) == 0 { + continue + } + dockerName, hash, err := ParseDockerName(dockerContainer.Names[0]) + if err != nil { + continue + } + if dockerName.PodFullName == podFullName && + (uid == "" || dockerName.PodUID == uid) && + dockerName.ContainerName == containerName { + return dockerContainer, true, hash + } + } + return nil, false, 0 +} + +func TestGetContainerID(t *testing.T) { + fakeDocker := &FakeDockerClient{} + fakeDocker.SetFakeRunningContainers([]*docker.Container{ + { + ID: "foobar", + Name: "/k8s_foo_qux_ns_1234_42", + }, + { + ID: "barbar", + Name: "/k8s_bar_qux_ns_2565_42", + }, + }) + + dockerContainers, err := GetKubeletDockerContainers(fakeDocker, false) + if err != nil { + t.Errorf("Expected no error, Got %#v", err) + } + if len(dockerContainers) != 2 { + t.Errorf("Expected %#v, Got %#v", fakeDocker.ContainerList, dockerContainers) + } + verifyCalls(t, fakeDocker, []string{"list"}) + + dockerContainer, found, _ := findPodContainer(dockerContainers, "qux_ns", "", "foo") + if dockerContainer == nil || !found { + t.Errorf("Failed to find container %#v", dockerContainer) + } + + fakeDocker.ClearCalls() + dockerContainer, found, _ = findPodContainer(dockerContainers, "foobar", "", "foo") + verifyCalls(t, fakeDocker, []string{}) + if dockerContainer != nil || found { + t.Errorf("Should not have found container %#v", dockerContainer) + } +} + +func verifyPackUnpack(t *testing.T, podNamespace, podUID, podName, containerName string) { + container := &api.Container{Name: containerName} + hasher := adler32.New() + hashutil.DeepHashObject(hasher, *container) + computedHash := uint64(hasher.Sum32()) + podFullName := fmt.Sprintf("%s_%s", podName, podNamespace) + _, name, _ := BuildDockerName(KubeletContainerName{podFullName, types.UID(podUID), container.Name}, container) + returned, hash, err := ParseDockerName(name) + if err != nil { + t.Errorf("Failed to parse Docker container name %q: %v", name, err) + } + if podFullName != returned.PodFullName || podUID != string(returned.PodUID) || containerName != returned.ContainerName || computedHash != hash { + t.Errorf("For (%s, %s, %s, %d), unpacked (%s, %s, %s, %d)", podFullName, podUID, containerName, computedHash, returned.PodFullName, returned.PodUID, returned.ContainerName, hash) + } +} + +func TestContainerNaming(t *testing.T) { + podUID := "12345678" + verifyPackUnpack(t, "file", podUID, "name", "container") + verifyPackUnpack(t, "file", podUID, "name-with-dashes", "container") + // UID is same as pod name + verifyPackUnpack(t, "file", podUID, podUID, "container") + // No Container name + verifyPackUnpack(t, "other", podUID, "name", "") + + container := &api.Container{Name: "container"} + podName := "foo" + podNamespace := "test" + name := fmt.Sprintf("k8s_%s_%s_%s_%s_42", container.Name, podName, podNamespace, podUID) + podFullName := fmt.Sprintf("%s_%s", podName, podNamespace) + + returned, hash, err := ParseDockerName(name) + if err != nil { + t.Errorf("Failed to parse Docker container name %q: %v", name, err) + } + if returned.PodFullName != podFullName || string(returned.PodUID) != podUID || returned.ContainerName != container.Name || hash != 0 { + t.Errorf("unexpected parse: %s %s %s %d", returned.PodFullName, returned.PodUID, returned.ContainerName, hash) + } +} + +func TestVersion(t *testing.T) { + fakeDocker := &FakeDockerClient{VersionInfo: docker.Env{"Version=1.1.3", "ApiVersion=1.15"}} + manager := &DockerManager{client: fakeDocker} + version, err := manager.Version() + if err != nil { + t.Errorf("got error while getting docker server version - %s", err) + } + expectedVersion, _ := docker.NewAPIVersion("1.1.3") + if e, a := expectedVersion.String(), version.String(); e != a { + t.Errorf("invalid docker server version. expected: %v, got: %v", e, a) + } + + version, err = manager.APIVersion() + if err != nil { + t.Errorf("got error while getting docker server version - %s", err) + } + expectedVersion, _ = docker.NewAPIVersion("1.15") + if e, a := expectedVersion.String(), version.String(); e != a { + t.Errorf("invalid docker server version. expected: %v, got: %v", e, a) + } +} + +func TestParseImageName(t *testing.T) { + tests := []struct { + imageName string + name string + tag string + }{ + {"ubuntu", "ubuntu", "latest"}, + {"ubuntu:2342", "ubuntu", "2342"}, + {"ubuntu:latest", "ubuntu", "latest"}, + {"foo/bar:445566", "foo/bar", "445566"}, + {"registry.example.com:5000/foobar", "registry.example.com:5000/foobar", "latest"}, + {"registry.example.com:5000/foobar:5342", "registry.example.com:5000/foobar", "5342"}, + {"registry.example.com:5000/foobar:latest", "registry.example.com:5000/foobar", "latest"}, + } + for _, test := range tests { + name, tag := parsers.ParseImageName(test.imageName) + if name != test.name || tag != test.tag { + t.Errorf("Expected name/tag: %s/%s, got %s/%s", test.name, test.tag, name, tag) + } + } +} + +func TestPullWithNoSecrets(t *testing.T) { + tests := []struct { + imageName string + expectedImage string + }{ + {"ubuntu", "ubuntu:latest using {}"}, + {"ubuntu:2342", "ubuntu:2342 using {}"}, + {"ubuntu:latest", "ubuntu:latest using {}"}, + {"foo/bar:445566", "foo/bar:445566 using {}"}, + {"registry.example.com:5000/foobar", "registry.example.com:5000/foobar:latest using {}"}, + {"registry.example.com:5000/foobar:5342", "registry.example.com:5000/foobar:5342 using {}"}, + {"registry.example.com:5000/foobar:latest", "registry.example.com:5000/foobar:latest using {}"}, + } + for _, test := range tests { + fakeKeyring := &credentialprovider.FakeKeyring{} + fakeClient := &FakeDockerClient{} + + dp := dockerPuller{ + client: fakeClient, + keyring: fakeKeyring, + } + + err := dp.Pull(test.imageName, []api.Secret{}) + if err != nil { + t.Errorf("unexpected non-nil err: %s", err) + continue + } + + if e, a := 1, len(fakeClient.pulled); e != a { + t.Errorf("%s: expected 1 pulled image, got %d: %v", test.imageName, a, fakeClient.pulled) + continue + } + + if e, a := test.expectedImage, fakeClient.pulled[0]; e != a { + t.Errorf("%s: expected pull of %q, but got %q", test.imageName, e, a) + } + } +} + +func TestPullWithJSONError(t *testing.T) { + tests := map[string]struct { + imageName string + err error + expectedError string + }{ + "Json error": { + "ubuntu", + &jsonmessage.JSONError{Code: 50, Message: "Json error"}, + "Json error", + }, + "Bad gateway": { + "ubuntu", + &jsonmessage.JSONError{Code: 502, Message: "\n\n \n \n \n

Oops, there was an error!

\n

We have been contacted of this error, feel free to check out status.docker.com\n to see if there is a bigger issue.

\n\n \n"}, + kubecontainer.RegistryUnavailable.Error(), + }, + } + for i, test := range tests { + fakeKeyring := &credentialprovider.FakeKeyring{} + fakeClient := &FakeDockerClient{ + Errors: map[string]error{"pull": test.err}, + } + puller := &dockerPuller{ + client: fakeClient, + keyring: fakeKeyring, + } + err := puller.Pull(test.imageName, []api.Secret{}) + if err == nil || !strings.Contains(err.Error(), test.expectedError) { + t.Errorf("%s: expect error %s, got : %s", i, test.expectedError, err) + continue + } + } +} + +func TestPullWithSecrets(t *testing.T) { + // auth value is equivalent to: "username":"passed-user","password":"passed-password" + dockerCfg := map[string]map[string]string{"index.docker.io/v1/": {"email": "passed-email", "auth": "cGFzc2VkLXVzZXI6cGFzc2VkLXBhc3N3b3Jk"}} + dockercfgContent, err := json.Marshal(dockerCfg) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + dockerConfigJson := map[string]map[string]map[string]string{"auths": dockerCfg} + dockerConfigJsonContent, err := json.Marshal(dockerConfigJson) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + tests := map[string]struct { + imageName string + passedSecrets []api.Secret + builtInDockerConfig credentialprovider.DockerConfig + expectedPulls []string + }{ + "no matching secrets": { + "ubuntu", + []api.Secret{}, + credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{}), + []string{"ubuntu:latest using {}"}, + }, + "default keyring secrets": { + "ubuntu", + []api.Secret{}, + credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{"index.docker.io/v1/": {"built-in", "password", "email"}}), + []string{`ubuntu:latest using {"username":"built-in","password":"password","email":"email"}`}, + }, + "default keyring secrets unused": { + "ubuntu", + []api.Secret{}, + credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{"extraneous": {"built-in", "password", "email"}}), + []string{`ubuntu:latest using {}`}, + }, + "builtin keyring secrets, but use passed": { + "ubuntu", + []api.Secret{{Type: api.SecretTypeDockercfg, Data: map[string][]byte{api.DockerConfigKey: dockercfgContent}}}, + credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{"index.docker.io/v1/": {"built-in", "password", "email"}}), + []string{`ubuntu:latest using {"username":"passed-user","password":"passed-password","email":"passed-email"}`}, + }, + "builtin keyring secrets, but use passed with new docker config": { + "ubuntu", + []api.Secret{{Type: api.SecretTypeDockerConfigJson, Data: map[string][]byte{api.DockerConfigJsonKey: dockerConfigJsonContent}}}, + credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{"index.docker.io/v1/": {"built-in", "password", "email"}}), + []string{`ubuntu:latest using {"username":"passed-user","password":"passed-password","email":"passed-email"}`}, + }, + } + for i, test := range tests { + builtInKeyRing := &credentialprovider.BasicDockerKeyring{} + builtInKeyRing.Add(test.builtInDockerConfig) + + fakeClient := &FakeDockerClient{} + + dp := dockerPuller{ + client: fakeClient, + keyring: builtInKeyRing, + } + + err := dp.Pull(test.imageName, test.passedSecrets) + if err != nil { + t.Errorf("%s: unexpected non-nil err: %s", i, err) + continue + } + + if e, a := 1, len(fakeClient.pulled); e != a { + t.Errorf("%s: expected 1 pulled image, got %d: %v", i, a, fakeClient.pulled) + continue + } + + if e, a := test.expectedPulls, fakeClient.pulled; !reflect.DeepEqual(e, a) { + t.Errorf("%s: expected pull of %v, but got %v", i, e, a) + } + } +} + +func TestDockerKeyringLookupFails(t *testing.T) { + fakeKeyring := &credentialprovider.FakeKeyring{} + fakeClient := &FakeDockerClient{ + Errors: map[string]error{"pull": fmt.Errorf("test error")}, + } + + dp := dockerPuller{ + client: fakeClient, + keyring: fakeKeyring, + } + + err := dp.Pull("host/repository/image:version", []api.Secret{}) + if err == nil { + t.Errorf("unexpected non-error") + } + msg := "image pull failed for host/repository/image:version, this may be because there are no credentials on this request. details: (test error)" + if err.Error() != msg { + t.Errorf("expected: %s, saw: %s", msg, err.Error()) + } +} + +func TestDockerKeyringLookup(t *testing.T) { + + ada := docker.AuthConfiguration{ + Username: "ada", + Password: "smash", + Email: "ada@example.com", + } + + grace := docker.AuthConfiguration{ + Username: "grace", + Password: "squash", + Email: "grace@example.com", + } + + dk := &credentialprovider.BasicDockerKeyring{} + dk.Add(credentialprovider.DockerConfig{ + "bar.example.com/pong": credentialprovider.DockerConfigEntry{ + Username: grace.Username, + Password: grace.Password, + Email: grace.Email, + }, + "bar.example.com": credentialprovider.DockerConfigEntry{ + Username: ada.Username, + Password: ada.Password, + Email: ada.Email, + }, + }) + + tests := []struct { + image string + match []docker.AuthConfiguration + ok bool + }{ + // direct match + {"bar.example.com", []docker.AuthConfiguration{ada}, true}, + + // direct match deeper than other possible matches + {"bar.example.com/pong", []docker.AuthConfiguration{grace, ada}, true}, + + // no direct match, deeper path ignored + {"bar.example.com/ping", []docker.AuthConfiguration{ada}, true}, + + // match first part of path token + {"bar.example.com/pongz", []docker.AuthConfiguration{grace, ada}, true}, + + // match regardless of sub-path + {"bar.example.com/pong/pang", []docker.AuthConfiguration{grace, ada}, true}, + + // no host match + {"example.com", []docker.AuthConfiguration{}, false}, + {"foo.example.com", []docker.AuthConfiguration{}, false}, + } + + for i, tt := range tests { + match, ok := dk.Lookup(tt.image) + if tt.ok != ok { + t.Errorf("case %d: expected ok=%t, got %t", i, tt.ok, ok) + } + + if !reflect.DeepEqual(tt.match, match) { + t.Errorf("case %d: expected match=%#v, got %#v", i, tt.match, match) + } + } +} + +// This validates that dockercfg entries with a scheme and url path are properly matched +// by images that only match the hostname. +// NOTE: the above covers the case of a more specific match trumping just hostname. +func TestIssue3797(t *testing.T) { + rex := docker.AuthConfiguration{ + Username: "rex", + Password: "tiny arms", + Email: "rex@example.com", + } + + dk := &credentialprovider.BasicDockerKeyring{} + dk.Add(credentialprovider.DockerConfig{ + "https://quay.io/v1/": credentialprovider.DockerConfigEntry{ + Username: rex.Username, + Password: rex.Password, + Email: rex.Email, + }, + }) + + tests := []struct { + image string + match []docker.AuthConfiguration + ok bool + }{ + // direct match + {"quay.io", []docker.AuthConfiguration{rex}, true}, + + // partial matches + {"quay.io/foo", []docker.AuthConfiguration{rex}, true}, + {"quay.io/foo/bar", []docker.AuthConfiguration{rex}, true}, + } + + for i, tt := range tests { + match, ok := dk.Lookup(tt.image) + if tt.ok != ok { + t.Errorf("case %d: expected ok=%t, got %t", i, tt.ok, ok) + } + + if !reflect.DeepEqual(tt.match, match) { + t.Errorf("case %d: expected match=%#v, got %#v", i, tt.match, match) + } + } +} + +type imageTrackingDockerClient struct { + *FakeDockerClient + imageName string +} + +func (f *imageTrackingDockerClient) InspectImage(name string) (image *docker.Image, err error) { + image, err = f.FakeDockerClient.InspectImage(name) + f.imageName = name + return +} + +func TestIsImagePresent(t *testing.T) { + cl := &imageTrackingDockerClient{&FakeDockerClient{}, ""} + puller := &dockerPuller{ + client: cl, + } + _, _ = puller.IsImagePresent("abc:123") + if cl.imageName != "abc:123" { + t.Errorf("expected inspection of image abc:123, instead inspected image %v", cl.imageName) + } +} + +type podsByID []*kubecontainer.Pod + +func (b podsByID) Len() int { return len(b) } +func (b podsByID) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b podsByID) Less(i, j int) bool { return b[i].ID < b[j].ID } + +type containersByID []*kubecontainer.Container + +func (b containersByID) Len() int { return len(b) } +func (b containersByID) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b containersByID) Less(i, j int) bool { return b[i].ID.ID < b[j].ID.ID } + +func TestFindContainersByPod(t *testing.T) { + tests := []struct { + containerList []docker.APIContainers + exitedContainerList []docker.APIContainers + all bool + expectedPods []*kubecontainer.Pod + }{ + + { + []docker.APIContainers{ + { + ID: "foobar", + Names: []string{"/k8s_foobar.1234_qux_ns_1234_42"}, + }, + { + ID: "barbar", + Names: []string{"/k8s_barbar.1234_qux_ns_2343_42"}, + }, + { + ID: "baz", + Names: []string{"/k8s_baz.1234_qux_ns_1234_42"}, + }, + }, + []docker.APIContainers{ + { + ID: "barfoo", + Names: []string{"/k8s_barfoo.1234_qux_ns_1234_42"}, + }, + { + ID: "bazbaz", + Names: []string{"/k8s_bazbaz.1234_qux_ns_5678_42"}, + }, + }, + false, + []*kubecontainer.Pod{ + { + ID: "1234", + Name: "qux", + Namespace: "ns", + Containers: []*kubecontainer.Container{ + { + ID: kubecontainer.DockerID("foobar").ContainerID(), + Name: "foobar", + Hash: 0x1234, + State: kubecontainer.ContainerStateUnknown, + }, + { + ID: kubecontainer.DockerID("baz").ContainerID(), + Name: "baz", + Hash: 0x1234, + State: kubecontainer.ContainerStateUnknown, + }, + }, + }, + { + ID: "2343", + Name: "qux", + Namespace: "ns", + Containers: []*kubecontainer.Container{ + { + ID: kubecontainer.DockerID("barbar").ContainerID(), + Name: "barbar", + Hash: 0x1234, + State: kubecontainer.ContainerStateUnknown, + }, + }, + }, + }, + }, + { + []docker.APIContainers{ + { + ID: "foobar", + Names: []string{"/k8s_foobar.1234_qux_ns_1234_42"}, + }, + { + ID: "barbar", + Names: []string{"/k8s_barbar.1234_qux_ns_2343_42"}, + }, + { + ID: "baz", + Names: []string{"/k8s_baz.1234_qux_ns_1234_42"}, + }, + }, + []docker.APIContainers{ + { + ID: "barfoo", + Names: []string{"/k8s_barfoo.1234_qux_ns_1234_42"}, + }, + { + ID: "bazbaz", + Names: []string{"/k8s_bazbaz.1234_qux_ns_5678_42"}, + }, + }, + true, + []*kubecontainer.Pod{ + { + ID: "1234", + Name: "qux", + Namespace: "ns", + Containers: []*kubecontainer.Container{ + { + ID: kubecontainer.DockerID("foobar").ContainerID(), + Name: "foobar", + Hash: 0x1234, + State: kubecontainer.ContainerStateUnknown, + }, + { + ID: kubecontainer.DockerID("barfoo").ContainerID(), + Name: "barfoo", + Hash: 0x1234, + State: kubecontainer.ContainerStateUnknown, + }, + { + ID: kubecontainer.DockerID("baz").ContainerID(), + Name: "baz", + Hash: 0x1234, + State: kubecontainer.ContainerStateUnknown, + }, + }, + }, + { + ID: "2343", + Name: "qux", + Namespace: "ns", + Containers: []*kubecontainer.Container{ + { + ID: kubecontainer.DockerID("barbar").ContainerID(), + Name: "barbar", + Hash: 0x1234, + State: kubecontainer.ContainerStateUnknown, + }, + }, + }, + { + ID: "5678", + Name: "qux", + Namespace: "ns", + Containers: []*kubecontainer.Container{ + { + ID: kubecontainer.DockerID("bazbaz").ContainerID(), + Name: "bazbaz", + Hash: 0x1234, + State: kubecontainer.ContainerStateUnknown, + }, + }, + }, + }, + }, + { + []docker.APIContainers{}, + []docker.APIContainers{}, + true, + nil, + }, + } + fakeClient := &FakeDockerClient{} + np, _ := network.InitNetworkPlugin([]network.NetworkPlugin{}, "", nettest.NewFakeHost(nil)) + // image back-off is set to nil, this test should not pull images + containerManager := NewFakeDockerManager(fakeClient, &record.FakeRecorder{}, nil, nil, &cadvisorapi.MachineInfo{}, options.GetDefaultPodInfraContainerImage(), 0, 0, "", containertest.FakeOS{}, np, nil, nil, nil) + for i, test := range tests { + fakeClient.ContainerList = test.containerList + fakeClient.ExitedContainerList = test.exitedContainerList + + result, _ := containerManager.GetPods(test.all) + for i := range result { + sort.Sort(containersByID(result[i].Containers)) + } + for i := range test.expectedPods { + sort.Sort(containersByID(test.expectedPods[i].Containers)) + } + sort.Sort(podsByID(result)) + sort.Sort(podsByID(test.expectedPods)) + if !reflect.DeepEqual(test.expectedPods, result) { + t.Errorf("%d: expected: %#v, saw: %#v", i, test.expectedPods, result) + } + } +} + +func TestMakePortsAndBindings(t *testing.T) { + ports := []kubecontainer.PortMapping{ + { + ContainerPort: 80, + HostPort: 8080, + HostIP: "127.0.0.1", + }, + { + ContainerPort: 443, + HostPort: 443, + Protocol: "tcp", + }, + { + ContainerPort: 444, + HostPort: 444, + Protocol: "udp", + }, + { + ContainerPort: 445, + HostPort: 445, + Protocol: "foobar", + }, + { + ContainerPort: 443, + HostPort: 446, + Protocol: "tcp", + }, + { + ContainerPort: 443, + HostPort: 446, + Protocol: "udp", + }, + } + + exposedPorts, bindings := makePortsAndBindings(ports) + + // Count the expected exposed ports and bindings + expectedExposedPorts := map[string]struct{}{} + + for _, binding := range ports { + dockerKey := strconv.Itoa(binding.ContainerPort) + "/" + string(binding.Protocol) + expectedExposedPorts[dockerKey] = struct{}{} + } + + // Should expose right ports in docker + if len(expectedExposedPorts) != len(exposedPorts) { + t.Errorf("Unexpected ports and bindings, %#v %#v %#v", ports, exposedPorts, bindings) + } + + // Construct expected bindings + expectPortBindings := map[string][]docker.PortBinding{ + "80/tcp": { + docker.PortBinding{ + HostPort: "8080", + HostIP: "127.0.0.1", + }, + }, + "443/tcp": { + docker.PortBinding{ + HostPort: "443", + HostIP: "", + }, + docker.PortBinding{ + HostPort: "446", + HostIP: "", + }, + }, + "443/udp": { + docker.PortBinding{ + HostPort: "446", + HostIP: "", + }, + }, + "444/udp": { + docker.PortBinding{ + HostPort: "444", + HostIP: "", + }, + }, + "445/tcp": { + docker.PortBinding{ + HostPort: "445", + HostIP: "", + }, + }, + } + + // interate the bindings by dockerPort, and check its portBindings + for dockerPort, portBindings := range bindings { + switch dockerPort { + case "80/tcp", "443/tcp", "443/udp", "444/udp", "445/tcp": + if !reflect.DeepEqual(expectPortBindings[string(dockerPort)], portBindings) { + t.Errorf("Unexpected portbindings for %#v, expected: %#v, but got: %#v", + dockerPort, expectPortBindings[string(dockerPort)], portBindings) + } + default: + t.Errorf("Unexpected docker port: %#v with portbindings: %#v", dockerPort, portBindings) + } + } +} + +func TestMilliCPUToQuota(t *testing.T) { + testCases := []struct { + input int64 + quota int64 + period int64 + }{ + { + input: int64(0), + quota: int64(0), + period: int64(0), + }, + { + input: int64(5), + quota: int64(1000), + period: int64(100000), + }, + { + input: int64(9), + quota: int64(1000), + period: int64(100000), + }, + { + input: int64(10), + quota: int64(1000), + period: int64(100000), + }, + { + input: int64(200), + quota: int64(20000), + period: int64(100000), + }, + { + input: int64(500), + quota: int64(50000), + period: int64(100000), + }, + { + input: int64(1000), + quota: int64(100000), + period: int64(100000), + }, + { + input: int64(1500), + quota: int64(150000), + period: int64(100000), + }, + } + for _, testCase := range testCases { + quota, period := milliCPUToQuota(testCase.input) + if quota != testCase.quota || period != testCase.period { + t.Errorf("Input %v, expected quota %v period %v, but got quota %v period %v", testCase.input, testCase.quota, testCase.period, quota, period) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/exec.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/exec.go new file mode 100644 index 000000000..426b5fb2e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/exec.go @@ -0,0 +1,151 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "fmt" + "io" + "os" + "os/exec" + "time" + + docker "github.com/fsouza/go-dockerclient" + "github.com/golang/glog" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" +) + +// ExecHandler knows how to execute a command in a running Docker container. +type ExecHandler interface { + ExecInContainer(client DockerInterface, container *docker.Container, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error +} + +// NsenterExecHandler executes commands in Docker containers using nsenter. +type NsenterExecHandler struct{} + +// TODO should we support nsenter in a container, running with elevated privs and --pid=host? +func (*NsenterExecHandler) ExecInContainer(client DockerInterface, container *docker.Container, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error { + nsenter, err := exec.LookPath("nsenter") + if err != nil { + return fmt.Errorf("exec unavailable - unable to locate nsenter") + } + + containerPid := container.State.Pid + + // TODO what if the container doesn't have `env`??? + args := []string{"-t", fmt.Sprintf("%d", containerPid), "-m", "-i", "-u", "-n", "-p", "--", "env", "-i"} + args = append(args, fmt.Sprintf("HOSTNAME=%s", container.Config.Hostname)) + args = append(args, container.Config.Env...) + args = append(args, cmd...) + command := exec.Command(nsenter, args...) + if tty { + p, err := kubecontainer.StartPty(command) + if err != nil { + return err + } + defer p.Close() + + // make sure to close the stdout stream + defer stdout.Close() + + if stdin != nil { + go io.Copy(p, stdin) + } + + if stdout != nil { + go io.Copy(stdout, p) + } + + return command.Wait() + } else { + if stdin != nil { + // Use an os.Pipe here as it returns true *os.File objects. + // This way, if you run 'kubectl exec -i bash' (no tty) and type 'exit', + // the call below to command.Run() can unblock because its Stdin is the read half + // of the pipe. + r, w, err := os.Pipe() + if err != nil { + return err + } + go io.Copy(w, stdin) + + command.Stdin = r + } + if stdout != nil { + command.Stdout = stdout + } + if stderr != nil { + command.Stderr = stderr + } + + return command.Run() + } +} + +// NativeExecHandler executes commands in Docker containers using Docker's exec API. +type NativeExecHandler struct{} + +func (*NativeExecHandler) ExecInContainer(client DockerInterface, container *docker.Container, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error { + createOpts := docker.CreateExecOptions{ + Container: container.ID, + Cmd: cmd, + AttachStdin: stdin != nil, + AttachStdout: stdout != nil, + AttachStderr: stderr != nil, + Tty: tty, + } + execObj, err := client.CreateExec(createOpts) + if err != nil { + return fmt.Errorf("failed to exec in container - Exec setup failed - %v", err) + } + startOpts := docker.StartExecOptions{ + Detach: false, + InputStream: stdin, + OutputStream: stdout, + ErrorStream: stderr, + Tty: tty, + RawTerminal: tty, + } + err = client.StartExec(execObj.ID, startOpts) + if err != nil { + return err + } + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + count := 0 + for { + inspect, err2 := client.InspectExec(execObj.ID) + if err2 != nil { + return err2 + } + if !inspect.Running { + if inspect.ExitCode != 0 { + err = &dockerExitError{inspect} + } + break + } + + count++ + if count == 5 { + glog.Errorf("Exec session %s in container %s terminated but process still running!", execObj.ID, container.ID) + break + } + + <-ticker.C + } + + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/fake_docker_client.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/fake_docker_client.go new file mode 100644 index 000000000..1a905cb90 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/fake_docker_client.go @@ -0,0 +1,496 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "encoding/json" + "fmt" + "math/rand" + "os" + "reflect" + "sort" + "sync" + "time" + + docker "github.com/fsouza/go-dockerclient" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/util/sets" +) + +// FakeDockerClient is a simple fake docker client, so that kubelet can be run for testing without requiring a real docker setup. +type FakeDockerClient struct { + sync.Mutex + ContainerList []docker.APIContainers + ExitedContainerList []docker.APIContainers + ContainerMap map[string]*docker.Container + Image *docker.Image + Images []docker.APIImages + Errors map[string]error + called []string + pulled []string + // Created, Stopped and Removed all container docker ID + Created []string + Stopped []string + Removed []string + RemovedImages sets.String + VersionInfo docker.Env + Information docker.Env + ExecInspect *docker.ExecInspect + execCmd []string + EnableSleep bool +} + +func NewFakeDockerClient() *FakeDockerClient { + return NewFakeDockerClientWithVersion("1.8.1", "1.20") +} + +func NewFakeDockerClientWithVersion(version, apiVersion string) *FakeDockerClient { + return &FakeDockerClient{ + VersionInfo: docker.Env{fmt.Sprintf("Version=%s", version), fmt.Sprintf("ApiVersion=%s", apiVersion)}, + Errors: make(map[string]error), + RemovedImages: sets.String{}, + ContainerMap: make(map[string]*docker.Container), + } +} + +func (f *FakeDockerClient) InjectError(fn string, err error) { + f.Lock() + defer f.Unlock() + f.Errors[fn] = err +} + +func (f *FakeDockerClient) InjectErrors(errs map[string]error) { + f.Lock() + defer f.Unlock() + for fn, err := range errs { + f.Errors[fn] = err + } +} + +func (f *FakeDockerClient) ClearErrors() { + f.Lock() + defer f.Unlock() + f.Errors = map[string]error{} +} + +func (f *FakeDockerClient) ClearCalls() { + f.Lock() + defer f.Unlock() + f.called = []string{} + f.Stopped = []string{} + f.pulled = []string{} + f.Created = []string{} + f.Removed = []string{} +} + +func (f *FakeDockerClient) SetFakeContainers(containers []*docker.Container) { + f.Lock() + defer f.Unlock() + // Reset the lists and the map. + f.ContainerMap = map[string]*docker.Container{} + f.ContainerList = []docker.APIContainers{} + f.ExitedContainerList = []docker.APIContainers{} + + for i := range containers { + c := containers[i] + if c.Config == nil { + c.Config = &docker.Config{} + } + f.ContainerMap[c.ID] = c + apiContainer := docker.APIContainers{ + Names: []string{c.Name}, + ID: c.ID, + } + if c.State.Running { + f.ContainerList = append(f.ContainerList, apiContainer) + } else { + f.ExitedContainerList = append(f.ExitedContainerList, apiContainer) + } + } +} + +func (f *FakeDockerClient) SetFakeRunningContainers(containers []*docker.Container) { + for _, c := range containers { + c.State.Running = true + } + f.SetFakeContainers(containers) +} + +func (f *FakeDockerClient) AssertCalls(calls []string) (err error) { + f.Lock() + defer f.Unlock() + + if !reflect.DeepEqual(calls, f.called) { + err = fmt.Errorf("expected %#v, got %#v", calls, f.called) + } + + return +} + +func (f *FakeDockerClient) AssertCreated(created []string) error { + f.Lock() + defer f.Unlock() + + actualCreated := []string{} + for _, c := range f.Created { + dockerName, _, err := ParseDockerName(c) + if err != nil { + return fmt.Errorf("unexpected error: %v", err) + } + actualCreated = append(actualCreated, dockerName.ContainerName) + } + sort.StringSlice(created).Sort() + sort.StringSlice(actualCreated).Sort() + if !reflect.DeepEqual(created, actualCreated) { + return fmt.Errorf("expected %#v, got %#v", created, actualCreated) + } + return nil +} + +func (f *FakeDockerClient) AssertStopped(stopped []string) error { + f.Lock() + defer f.Unlock() + sort.StringSlice(stopped).Sort() + sort.StringSlice(f.Stopped).Sort() + if !reflect.DeepEqual(stopped, f.Stopped) { + return fmt.Errorf("expected %#v, got %#v", stopped, f.Stopped) + } + return nil +} + +func (f *FakeDockerClient) AssertUnorderedCalls(calls []string) (err error) { + f.Lock() + defer f.Unlock() + + expected := make([]string, len(calls)) + actual := make([]string, len(f.called)) + copy(expected, calls) + copy(actual, f.called) + + sort.StringSlice(expected).Sort() + sort.StringSlice(actual).Sort() + + if !reflect.DeepEqual(actual, expected) { + err = fmt.Errorf("expected(sorted) %#v, got(sorted) %#v", expected, actual) + } + return +} + +func (f *FakeDockerClient) popError(op string) error { + if f.Errors == nil { + return nil + } + err, ok := f.Errors[op] + if ok { + delete(f.Errors, op) + return err + } else { + return nil + } +} + +// ListContainers is a test-spy implementation of DockerInterface.ListContainers. +// It adds an entry "list" to the internal method call record. +func (f *FakeDockerClient) ListContainers(options docker.ListContainersOptions) ([]docker.APIContainers, error) { + f.Lock() + defer f.Unlock() + f.called = append(f.called, "list") + err := f.popError("list") + containerList := append([]docker.APIContainers{}, f.ContainerList...) + if options.All { + // Although the container is not sorted, but the container with the same name should be in order, + // that is enough for us now. + // TODO(random-liu): Is a fully sorted array needed? + containerList = append(containerList, f.ExitedContainerList...) + } + return containerList, err +} + +// InspectContainer is a test-spy implementation of DockerInterface.InspectContainer. +// It adds an entry "inspect" to the internal method call record. +func (f *FakeDockerClient) InspectContainer(id string) (*docker.Container, error) { + f.Lock() + defer f.Unlock() + f.called = append(f.called, "inspect_container") + err := f.popError("inspect_container") + if container, ok := f.ContainerMap[id]; ok { + return container, err + } + return nil, err +} + +// InspectImage is a test-spy implementation of DockerInterface.InspectImage. +// It adds an entry "inspect" to the internal method call record. +func (f *FakeDockerClient) InspectImage(name string) (*docker.Image, error) { + f.Lock() + defer f.Unlock() + f.called = append(f.called, "inspect_image") + err := f.popError("inspect_image") + return f.Image, err +} + +// Sleeps random amount of time with the normal distribution with given mean and stddev +// (in milliseconds), we never sleep less than cutOffMillis +func (f *FakeDockerClient) normalSleep(mean, stdDev, cutOffMillis int) { + if !f.EnableSleep { + return + } + cutoff := (time.Duration)(cutOffMillis) * time.Millisecond + delay := (time.Duration)(rand.NormFloat64()*float64(stdDev)+float64(mean)) * time.Millisecond + if delay < cutoff { + delay = cutoff + } + time.Sleep(delay) +} + +// CreateContainer is a test-spy implementation of DockerInterface.CreateContainer. +// It adds an entry "create" to the internal method call record. +func (f *FakeDockerClient) CreateContainer(c docker.CreateContainerOptions) (*docker.Container, error) { + f.Lock() + defer f.Unlock() + f.called = append(f.called, "create") + if err := f.popError("create"); err != nil { + return nil, err + } + // This is not a very good fake. We'll just add this container's name to the list. + // Docker likes to add a '/', so copy that behavior. + name := "/" + c.Name + f.Created = append(f.Created, name) + // The newest container should be in front, because we assume so in GetPodStatus() + f.ContainerList = append([]docker.APIContainers{ + {ID: name, Names: []string{name}, Image: c.Config.Image, Labels: c.Config.Labels}, + }, f.ContainerList...) + container := docker.Container{ID: name, Name: name, Config: c.Config, HostConfig: c.HostConfig} + containerCopy := container + f.ContainerMap[name] = &containerCopy + f.normalSleep(100, 25, 25) + return &container, nil +} + +// StartContainer is a test-spy implementation of DockerInterface.StartContainer. +// It adds an entry "start" to the internal method call record. +// The HostConfig at StartContainer will be deprecated from docker 1.10. Now in +// docker manager the HostConfig is set when CreateContainer(). +// TODO(random-liu): Remove the HostConfig here when it is completely removed in +// docker 1.12. +func (f *FakeDockerClient) StartContainer(id string, _ *docker.HostConfig) error { + f.Lock() + defer f.Unlock() + f.called = append(f.called, "start") + if err := f.popError("start"); err != nil { + return err + } + container, ok := f.ContainerMap[id] + if !ok { + container = &docker.Container{ID: id, Name: id} + } + container.State = docker.State{ + Running: true, + Pid: os.Getpid(), + StartedAt: time.Now(), + } + container.NetworkSettings = &docker.NetworkSettings{IPAddress: "2.3.4.5"} + f.ContainerMap[id] = container + f.updateContainerStatus(id, statusRunningPrefix) + f.normalSleep(200, 50, 50) + return nil +} + +// StopContainer is a test-spy implementation of DockerInterface.StopContainer. +// It adds an entry "stop" to the internal method call record. +func (f *FakeDockerClient) StopContainer(id string, timeout uint) error { + f.Lock() + defer f.Unlock() + f.called = append(f.called, "stop") + if err := f.popError("stop"); err != nil { + return err + } + f.Stopped = append(f.Stopped, id) + // Container status should be Updated before container moved to ExitedContainerList + f.updateContainerStatus(id, statusExitedPrefix) + var newList []docker.APIContainers + for _, container := range f.ContainerList { + if container.ID == id { + // The newest exited container should be in front. Because we assume so in GetPodStatus() + f.ExitedContainerList = append([]docker.APIContainers{container}, f.ExitedContainerList...) + continue + } + newList = append(newList, container) + } + f.ContainerList = newList + container, ok := f.ContainerMap[id] + if !ok { + container = &docker.Container{ + ID: id, + Name: id, + State: docker.State{ + Running: false, + StartedAt: time.Now().Add(-time.Second), + FinishedAt: time.Now(), + }, + } + } else { + container.State.FinishedAt = time.Now() + container.State.Running = false + } + f.ContainerMap[id] = container + f.normalSleep(200, 50, 50) + return nil +} + +func (f *FakeDockerClient) RemoveContainer(opts docker.RemoveContainerOptions) error { + f.Lock() + defer f.Unlock() + f.called = append(f.called, "remove") + err := f.popError("remove") + if err != nil { + return err + } + for i := range f.ExitedContainerList { + if f.ExitedContainerList[i].ID == opts.ID { + delete(f.ContainerMap, opts.ID) + f.ExitedContainerList = append(f.ExitedContainerList[:i], f.ExitedContainerList[i+1:]...) + f.Removed = append(f.Removed, opts.ID) + return nil + } + + } + // To be a good fake, report error if container is not stopped. + return fmt.Errorf("container not stopped") +} + +// Logs is a test-spy implementation of DockerInterface.Logs. +// It adds an entry "logs" to the internal method call record. +func (f *FakeDockerClient) Logs(opts docker.LogsOptions) error { + f.Lock() + defer f.Unlock() + f.called = append(f.called, "logs") + return f.popError("logs") +} + +// PullImage is a test-spy implementation of DockerInterface.StopContainer. +// It adds an entry "pull" to the internal method call record. +func (f *FakeDockerClient) PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error { + f.Lock() + defer f.Unlock() + f.called = append(f.called, "pull") + err := f.popError("pull") + if err == nil { + registry := opts.Registry + if len(registry) != 0 { + registry = registry + "/" + } + authJson, _ := json.Marshal(auth) + f.pulled = append(f.pulled, fmt.Sprintf("%s%s:%s using %s", registry, opts.Repository, opts.Tag, string(authJson))) + } + return err +} + +func (f *FakeDockerClient) Version() (*docker.Env, error) { + return &f.VersionInfo, f.popError("version") +} + +func (f *FakeDockerClient) Info() (*docker.Env, error) { + return &f.Information, nil +} + +func (f *FakeDockerClient) CreateExec(opts docker.CreateExecOptions) (*docker.Exec, error) { + f.Lock() + defer f.Unlock() + f.execCmd = opts.Cmd + f.called = append(f.called, "create_exec") + return &docker.Exec{ID: "12345678"}, nil +} + +func (f *FakeDockerClient) StartExec(_ string, _ docker.StartExecOptions) error { + f.Lock() + defer f.Unlock() + f.called = append(f.called, "start_exec") + return nil +} + +func (f *FakeDockerClient) AttachToContainer(opts docker.AttachToContainerOptions) error { + f.Lock() + defer f.Unlock() + f.called = append(f.called, "attach") + return nil +} + +func (f *FakeDockerClient) InspectExec(id string) (*docker.ExecInspect, error) { + return f.ExecInspect, f.popError("inspect_exec") +} + +func (f *FakeDockerClient) ListImages(opts docker.ListImagesOptions) ([]docker.APIImages, error) { + err := f.popError("list_images") + return f.Images, err +} + +func (f *FakeDockerClient) RemoveImage(image string) error { + err := f.popError("remove_image") + if err == nil { + f.RemovedImages.Insert(image) + } + return err +} + +func (f *FakeDockerClient) updateContainerStatus(id, status string) { + for i := range f.ContainerList { + if f.ContainerList[i].ID == id { + f.ContainerList[i].Status = status + } + } +} + +// FakeDockerPuller is a stub implementation of DockerPuller. +type FakeDockerPuller struct { + sync.Mutex + + HasImages []string + ImagesPulled []string + + // Every pull will return the first error here, and then reslice + // to remove it. Will give nil errors if this slice is empty. + ErrorsToInject []error +} + +// Pull records the image pull attempt, and optionally injects an error. +func (f *FakeDockerPuller) Pull(image string, secrets []api.Secret) (err error) { + f.Lock() + defer f.Unlock() + f.ImagesPulled = append(f.ImagesPulled, image) + + if len(f.ErrorsToInject) > 0 { + err = f.ErrorsToInject[0] + f.ErrorsToInject = f.ErrorsToInject[1:] + } + return err +} + +func (f *FakeDockerPuller) IsImagePresent(name string) (bool, error) { + f.Lock() + defer f.Unlock() + if f.HasImages == nil { + return true, nil + } + for _, s := range f.HasImages { + if s == name { + return true, nil + } + } + return false, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/fake_manager.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/fake_manager.go new file mode 100644 index 000000000..73984f70d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/fake_manager.go @@ -0,0 +1,69 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + cadvisorapi "github.com/google/cadvisor/info/v1" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/network" + proberesults "k8s.io/kubernetes/pkg/kubelet/prober/results" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/flowcontrol" + "k8s.io/kubernetes/pkg/util/oom" + "k8s.io/kubernetes/pkg/util/procfs" +) + +func NewFakeDockerManager( + client DockerInterface, + recorder record.EventRecorder, + livenessManager proberesults.Manager, + containerRefManager *kubecontainer.RefManager, + machineInfo *cadvisorapi.MachineInfo, + podInfraContainerImage string, + qps float32, + burst int, + containerLogsDir string, + osInterface kubecontainer.OSInterface, + networkPlugin network.NetworkPlugin, + runtimeHelper kubecontainer.RuntimeHelper, + httpClient kubetypes.HttpGetter, imageBackOff *flowcontrol.Backoff) *DockerManager { + + fakeOOMAdjuster := oom.NewFakeOOMAdjuster() + fakeProcFs := procfs.NewFakeProcFS() + fakePodGetter := &fakePodGetter{} + dm := NewDockerManager(client, recorder, livenessManager, containerRefManager, fakePodGetter, machineInfo, podInfraContainerImage, qps, + burst, containerLogsDir, osInterface, networkPlugin, runtimeHelper, httpClient, &NativeExecHandler{}, + fakeOOMAdjuster, fakeProcFs, false, imageBackOff, false, false, true) + dm.dockerPuller = &FakeDockerPuller{} + return dm +} + +type fakePodGetter struct { + pods map[types.UID]*api.Pod +} + +func newFakePodGetter() *fakePodGetter { + return &fakePodGetter{make(map[types.UID]*api.Pod)} +} + +func (f *fakePodGetter) GetPodByUID(uid types.UID) (*api.Pod, bool) { + pod, found := f.pods[uid] + return pod, found +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/instrumented_docker.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/instrumented_docker.go new file mode 100644 index 000000000..e90c3f754 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/instrumented_docker.go @@ -0,0 +1,202 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "time" + + docker "github.com/fsouza/go-dockerclient" + "k8s.io/kubernetes/pkg/kubelet/metrics" +) + +// instrumentedDockerInterface wraps the DockerInterface and records the operations +// and errors metrics. +type instrumentedDockerInterface struct { + client DockerInterface +} + +// Creates an instrumented DockerInterface from an existing DockerInterface. +func newInstrumentedDockerInterface(dockerClient DockerInterface) DockerInterface { + return instrumentedDockerInterface{ + client: dockerClient, + } +} + +// recordOperation records the duration of the operation. +func recordOperation(operation string, start time.Time) { + metrics.DockerOperationsLatency.WithLabelValues(operation).Observe(metrics.SinceInMicroseconds(start)) +} + +// recordError records error for metric if an error occurred. +func recordError(operation string, err error) { + if err != nil { + metrics.DockerErrors.WithLabelValues(operation).Inc() + } +} + +func (in instrumentedDockerInterface) ListContainers(options docker.ListContainersOptions) ([]docker.APIContainers, error) { + const operation = "list_containers" + defer recordOperation(operation, time.Now()) + + out, err := in.client.ListContainers(options) + recordError(operation, err) + return out, err +} + +func (in instrumentedDockerInterface) InspectContainer(id string) (*docker.Container, error) { + const operation = "inspect_container" + defer recordOperation(operation, time.Now()) + + out, err := in.client.InspectContainer(id) + recordError(operation, err) + return out, err +} + +func (in instrumentedDockerInterface) CreateContainer(opts docker.CreateContainerOptions) (*docker.Container, error) { + const operation = "create_container" + defer recordOperation(operation, time.Now()) + + out, err := in.client.CreateContainer(opts) + recordError(operation, err) + return out, err +} + +func (in instrumentedDockerInterface) StartContainer(id string, hostConfig *docker.HostConfig) error { + const operation = "start_container" + defer recordOperation(operation, time.Now()) + + err := in.client.StartContainer(id, hostConfig) + recordError(operation, err) + return err +} + +func (in instrumentedDockerInterface) StopContainer(id string, timeout uint) error { + const operation = "stop_container" + defer recordOperation(operation, time.Now()) + + err := in.client.StopContainer(id, timeout) + recordError(operation, err) + return err +} + +func (in instrumentedDockerInterface) RemoveContainer(opts docker.RemoveContainerOptions) error { + const operation = "remove_container" + defer recordOperation(operation, time.Now()) + + err := in.client.RemoveContainer(opts) + recordError(operation, err) + return err +} + +func (in instrumentedDockerInterface) InspectImage(image string) (*docker.Image, error) { + const operation = "inspect_image" + defer recordOperation(operation, time.Now()) + + out, err := in.client.InspectImage(image) + recordError(operation, err) + return out, err +} + +func (in instrumentedDockerInterface) ListImages(opts docker.ListImagesOptions) ([]docker.APIImages, error) { + const operation = "list_images" + defer recordOperation(operation, time.Now()) + + out, err := in.client.ListImages(opts) + recordError(operation, err) + return out, err +} + +func (in instrumentedDockerInterface) PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error { + const operation = "pull_image" + defer recordOperation(operation, time.Now()) + + err := in.client.PullImage(opts, auth) + recordError(operation, err) + return err +} + +func (in instrumentedDockerInterface) RemoveImage(image string) error { + const operation = "remove_image" + defer recordOperation(operation, time.Now()) + + err := in.client.RemoveImage(image) + recordError(operation, err) + return err +} + +func (in instrumentedDockerInterface) Logs(opts docker.LogsOptions) error { + const operation = "logs" + defer recordOperation(operation, time.Now()) + + err := in.client.Logs(opts) + recordError(operation, err) + return err +} + +func (in instrumentedDockerInterface) Version() (*docker.Env, error) { + const operation = "version" + defer recordOperation(operation, time.Now()) + + out, err := in.client.Version() + recordError(operation, err) + return out, err +} + +func (in instrumentedDockerInterface) Info() (*docker.Env, error) { + const operation = "info" + defer recordOperation(operation, time.Now()) + + out, err := in.client.Info() + recordError(operation, err) + return out, err +} + +func (in instrumentedDockerInterface) CreateExec(opts docker.CreateExecOptions) (*docker.Exec, error) { + const operation = "create_exec" + defer recordOperation(operation, time.Now()) + + out, err := in.client.CreateExec(opts) + recordError(operation, err) + return out, err +} + +func (in instrumentedDockerInterface) StartExec(startExec string, opts docker.StartExecOptions) error { + const operation = "start_exec" + defer recordOperation(operation, time.Now()) + + err := in.client.StartExec(startExec, opts) + recordError(operation, err) + return err +} + +func (in instrumentedDockerInterface) InspectExec(id string) (*docker.ExecInspect, error) { + const operation = "inspect_exec" + defer recordOperation(operation, time.Now()) + + out, err := in.client.InspectExec(id) + recordError(operation, err) + return out, err +} + +func (in instrumentedDockerInterface) AttachToContainer(opts docker.AttachToContainerOptions) error { + const operation = "attach" + defer recordOperation(operation, time.Now()) + + err := in.client.AttachToContainer(opts) + recordError(operation, err) + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/kube_docker_client.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/kube_docker_client.go new file mode 100644 index 000000000..599d5d0ce --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/kube_docker_client.go @@ -0,0 +1,389 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "io" + "io/ioutil" + "strconv" + + "github.com/docker/docker/pkg/stdcopy" + dockerapi "github.com/docker/engine-api/client" + dockertypes "github.com/docker/engine-api/types" + dockercontainer "github.com/docker/engine-api/types/container" + dockerfilters "github.com/docker/engine-api/types/filters" + docker "github.com/fsouza/go-dockerclient" + "golang.org/x/net/context" +) + +// kubeDockerClient is a wrapped layer of docker client for kubelet internal use. This layer is added to: +// 1) Redirect stream for exec and attach operations. +// 2) Wrap the context in this layer to make the DockerInterface cleaner. +// 3) Stabilize the DockerInterface. The engine-api is still under active development, the interface +// is not stabilized yet. However, the DockerInterface is used in many files in Kubernetes, we may +// not want to change the interface frequently. With this layer, we can port the engine api to the +// DockerInterface to avoid changing DockerInterface as much as possible. +// (See +// * https://github.com/docker/engine-api/issues/89 +// * https://github.com/docker/engine-api/issues/137 +// * https://github.com/docker/engine-api/pull/140) +// TODO(random-liu): Swith to new docker interface by refactoring the functions in the old DockerInterface +// one by one. +type kubeDockerClient struct { + client *dockerapi.Client +} + +// Make sure that kubeDockerClient implemented the DockerInterface. +var _ DockerInterface = &kubeDockerClient{} + +// newKubeDockerClient creates an kubeDockerClient from an existing docker client. +func newKubeDockerClient(dockerClient *dockerapi.Client) DockerInterface { + return &kubeDockerClient{ + client: dockerClient, + } +} + +// getDefaultContext returns the default context, now the default context is +// context.Background() +// TODO(random-liu): Add timeout and timeout handling mechanism. +func getDefaultContext() context.Context { + return context.Background() +} + +// convertType converts between different types with the same json format. +func convertType(src interface{}, dst interface{}) error { + data, err := json.Marshal(src) + if err != nil { + return err + } + return json.Unmarshal(data, dst) +} + +// convertFilters converts filters to the filter type in engine-api. +func convertFilters(filters map[string][]string) dockerfilters.Args { + args := dockerfilters.NewArgs() + for name, fields := range filters { + for _, field := range fields { + args.Add(name, field) + } + } + return args +} + +// convertEnv converts data to a go-dockerclient Env +func convertEnv(src interface{}) (*docker.Env, error) { + m := make(map[string]interface{}) + if err := convertType(&src, &m); err != nil { + return nil, err + } + env := &docker.Env{} + for k, v := range m { + env.SetAuto(k, v) + } + return env, nil +} + +func (k *kubeDockerClient) ListContainers(options docker.ListContainersOptions) ([]docker.APIContainers, error) { + containers, err := k.client.ContainerList(getDefaultContext(), dockertypes.ContainerListOptions{ + Size: options.Size, + All: options.All, + Limit: options.Limit, + Since: options.Since, + Before: options.Before, + Filter: convertFilters(options.Filters), + }) + if err != nil { + return nil, err + } + apiContainers := []docker.APIContainers{} + if err := convertType(&containers, &apiContainers); err != nil { + return nil, err + } + return apiContainers, nil +} + +func (d *kubeDockerClient) InspectContainer(id string) (*docker.Container, error) { + containerJSON, err := d.client.ContainerInspect(getDefaultContext(), id) + if err != nil { + // TODO(random-liu): Use IsErrContainerNotFound instead of NoSuchContainer error + if dockerapi.IsErrContainerNotFound(err) { + err = &docker.NoSuchContainer{ID: id, Err: err} + } + return nil, err + } + container := &docker.Container{} + if err := convertType(&containerJSON, container); err != nil { + return nil, err + } + return container, nil +} + +func (d *kubeDockerClient) CreateContainer(opts docker.CreateContainerOptions) (*docker.Container, error) { + config := &dockercontainer.Config{} + if err := convertType(opts.Config, config); err != nil { + return nil, err + } + hostConfig := &dockercontainer.HostConfig{} + if err := convertType(opts.HostConfig, hostConfig); err != nil { + return nil, err + } + resp, err := d.client.ContainerCreate(getDefaultContext(), config, hostConfig, nil, opts.Name) + if err != nil { + return nil, err + } + container := &docker.Container{} + if err := convertType(&resp, container); err != nil { + return nil, err + } + return container, nil +} + +// TODO(random-liu): The HostConfig at container start is deprecated, will remove this in the following refactoring. +func (d *kubeDockerClient) StartContainer(id string, _ *docker.HostConfig) error { + return d.client.ContainerStart(getDefaultContext(), id) +} + +// Stopping an already stopped container will not cause an error in engine-api. +func (d *kubeDockerClient) StopContainer(id string, timeout uint) error { + return d.client.ContainerStop(getDefaultContext(), id, int(timeout)) +} + +func (d *kubeDockerClient) RemoveContainer(opts docker.RemoveContainerOptions) error { + return d.client.ContainerRemove(getDefaultContext(), dockertypes.ContainerRemoveOptions{ + ContainerID: opts.ID, + RemoveVolumes: opts.RemoveVolumes, + Force: opts.Force, + }) +} + +func (d *kubeDockerClient) InspectImage(image string) (*docker.Image, error) { + resp, _, err := d.client.ImageInspectWithRaw(getDefaultContext(), image, true) + if err != nil { + // TODO(random-liu): Use IsErrImageNotFound instead of ErrNoSuchImage + if dockerapi.IsErrImageNotFound(err) { + err = docker.ErrNoSuchImage + } + return nil, err + } + imageInfo := &docker.Image{} + if err := convertType(&resp, imageInfo); err != nil { + return nil, err + } + return imageInfo, nil +} + +func (d *kubeDockerClient) ListImages(opts docker.ListImagesOptions) ([]docker.APIImages, error) { + resp, err := d.client.ImageList(getDefaultContext(), dockertypes.ImageListOptions{ + MatchName: opts.Filter, + All: opts.All, + Filters: convertFilters(opts.Filters), + }) + if err != nil { + return nil, err + } + images := []docker.APIImages{} + if err = convertType(&resp, &images); err != nil { + return nil, err + } + return images, nil +} + +func base64EncodeAuth(auth docker.AuthConfiguration) (string, error) { + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(auth); err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(buf.Bytes()), nil +} + +func (d *kubeDockerClient) PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error { + base64Auth, err := base64EncodeAuth(auth) + if err != nil { + return err + } + resp, err := d.client.ImagePull(getDefaultContext(), dockertypes.ImagePullOptions{ + ImageID: opts.Repository, + Tag: opts.Tag, + RegistryAuth: base64Auth, + }, nil) + if err != nil { + return err + } + defer resp.Close() + // TODO(random-liu): Use the image pulling progress information. + _, err = io.Copy(ioutil.Discard, resp) + return err +} + +func (d *kubeDockerClient) RemoveImage(image string) error { + _, err := d.client.ImageRemove(getDefaultContext(), dockertypes.ImageRemoveOptions{ImageID: image}) + return err +} + +func (d *kubeDockerClient) Logs(opts docker.LogsOptions) error { + resp, err := d.client.ContainerLogs(getDefaultContext(), dockertypes.ContainerLogsOptions{ + ContainerID: opts.Container, + ShowStdout: opts.Stdout, + ShowStderr: opts.Stderr, + Since: strconv.FormatInt(opts.Since, 10), + Timestamps: opts.Timestamps, + Follow: opts.Follow, + Tail: opts.Tail, + }) + if err != nil { + return err + } + defer resp.Close() + return d.redirectResponseToOutputStream(opts.RawTerminal, opts.OutputStream, opts.ErrorStream, resp) +} + +func (d *kubeDockerClient) Version() (*docker.Env, error) { + resp, err := d.client.ServerVersion(getDefaultContext()) + if err != nil { + return nil, err + } + return convertEnv(resp) +} + +func (d *kubeDockerClient) Info() (*docker.Env, error) { + resp, err := d.client.Info(getDefaultContext()) + if err != nil { + return nil, err + } + return convertEnv(resp) +} + +func (d *kubeDockerClient) CreateExec(opts docker.CreateExecOptions) (*docker.Exec, error) { + cfg := dockertypes.ExecConfig{} + if err := convertType(&opts, &cfg); err != nil { + return nil, err + } + resp, err := d.client.ContainerExecCreate(getDefaultContext(), cfg) + if err != nil { + return nil, err + } + exec := &docker.Exec{} + if err := convertType(&resp, exec); err != nil { + return nil, err + } + return exec, nil +} + +func (d *kubeDockerClient) StartExec(startExec string, opts docker.StartExecOptions) error { + if opts.Detach { + return d.client.ContainerExecStart(getDefaultContext(), startExec, dockertypes.ExecStartCheck{ + Detach: opts.Detach, + Tty: opts.Tty, + }) + } + resp, err := d.client.ContainerExecAttach(getDefaultContext(), startExec, dockertypes.ExecConfig{ + Detach: opts.Detach, + Tty: opts.Tty, + }) + if err != nil { + return err + } + defer resp.Close() + if opts.Success != nil { + opts.Success <- struct{}{} + <-opts.Success + } + return d.holdHijackedConnection(opts.RawTerminal || opts.Tty, opts.InputStream, opts.OutputStream, opts.ErrorStream, resp) +} + +func (d *kubeDockerClient) InspectExec(id string) (*docker.ExecInspect, error) { + resp, err := d.client.ContainerExecInspect(getDefaultContext(), id) + if err != nil { + return nil, err + } + exec := &docker.ExecInspect{} + if err := convertType(&resp, exec); err != nil { + return nil, err + } + return exec, nil +} + +func (d *kubeDockerClient) AttachToContainer(opts docker.AttachToContainerOptions) error { + resp, err := d.client.ContainerAttach(getDefaultContext(), dockertypes.ContainerAttachOptions{ + ContainerID: opts.Container, + Stream: opts.Stream, + Stdin: opts.Stdin, + Stdout: opts.Stdout, + Stderr: opts.Stderr, + // TODO: How to deal with the *Logs* here? There is no *Logs* field in the engine-api. + }) + if err != nil { + return err + } + defer resp.Close() + if opts.Success != nil { + opts.Success <- struct{}{} + <-opts.Success + } + return d.holdHijackedConnection(opts.RawTerminal, opts.InputStream, opts.OutputStream, opts.ErrorStream, resp) +} + +// redirectResponseToOutputStream redirect the response stream to stdout and stderr. When tty is true, all stream will +// only be redirected to stdout. +func (d *kubeDockerClient) redirectResponseToOutputStream(tty bool, outputStream, errorStream io.Writer, resp io.Reader) error { + if outputStream == nil { + outputStream = ioutil.Discard + } + if errorStream == nil { + errorStream = ioutil.Discard + } + var err error + if tty { + _, err = io.Copy(outputStream, resp) + } else { + _, err = stdcopy.StdCopy(outputStream, errorStream, resp) + } + return err +} + +// holdHijackedConnection hold the HijackedResponse, redirect the inputStream to the connection, and redirect the response +// stream to stdout and stderr. NOTE: If needed, we could also add context in this function. +func (d *kubeDockerClient) holdHijackedConnection(tty bool, inputStream io.Reader, outputStream, errorStream io.Writer, resp dockertypes.HijackedResponse) error { + receiveStdout := make(chan error) + if outputStream != nil || errorStream != nil { + go func() { + receiveStdout <- d.redirectResponseToOutputStream(tty, outputStream, errorStream, resp.Reader) + }() + } + + stdinDone := make(chan struct{}) + go func() { + if inputStream != nil { + io.Copy(resp.Conn, inputStream) + } + resp.CloseWrite() + close(stdinDone) + }() + + select { + case err := <-receiveStdout: + return err + case <-stdinDone: + if outputStream != nil || errorStream != nil { + return <-receiveStdout + } + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/labels.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/labels.go new file mode 100644 index 000000000..5b501465b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/labels.go @@ -0,0 +1,248 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "encoding/json" + "strconv" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/custommetrics" + "k8s.io/kubernetes/pkg/kubelet/util/format" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" +) + +// This file contains all docker label related constants and functions, including: +// * label setters and getters +// * label filters (maybe in the future) + +const ( + kubernetesPodNameLabel = "io.kubernetes.pod.name" + kubernetesPodNamespaceLabel = "io.kubernetes.pod.namespace" + kubernetesPodUIDLabel = "io.kubernetes.pod.uid" + kubernetesPodDeletionGracePeriodLabel = "io.kubernetes.pod.deletionGracePeriod" + kubernetesPodTerminationGracePeriodLabel = "io.kubernetes.pod.terminationGracePeriod" + + kubernetesContainerNameLabel = "io.kubernetes.container.name" + kubernetesContainerHashLabel = "io.kubernetes.container.hash" + kubernetesContainerRestartCountLabel = "io.kubernetes.container.restartCount" + kubernetesContainerTerminationMessagePathLabel = "io.kubernetes.container.terminationMessagePath" + kubernetesContainerPreStopHandlerLabel = "io.kubernetes.container.preStopHandler" + + // TODO(random-liu): Keep this for old containers, remove this when we drop support for v1.1. + kubernetesPodLabel = "io.kubernetes.pod.data" + + cadvisorPrometheusMetricsLabel = "io.cadvisor.metric.prometheus" +) + +// Container information which has been labelled on each docker container +// TODO(random-liu): The type of Hash should be compliance with kubelet container status. +type labelledContainerInfo struct { + PodName string + PodNamespace string + PodUID types.UID + PodDeletionGracePeriod *int64 + PodTerminationGracePeriod *int64 + Name string + Hash string + RestartCount int + TerminationMessagePath string + PreStopHandler *api.Handler +} + +func GetContainerName(labels map[string]string) string { + return labels[kubernetesContainerNameLabel] +} + +func GetPodName(labels map[string]string) string { + return labels[kubernetesPodNameLabel] +} + +func GetPodUID(labels map[string]string) string { + return labels[kubernetesPodUIDLabel] +} + +func GetPodNamespace(labels map[string]string) string { + return labels[kubernetesPodNamespaceLabel] +} + +func newLabels(container *api.Container, pod *api.Pod, restartCount int, enableCustomMetrics bool) map[string]string { + labels := map[string]string{} + labels[kubernetesPodNameLabel] = pod.Name + labels[kubernetesPodNamespaceLabel] = pod.Namespace + labels[kubernetesPodUIDLabel] = string(pod.UID) + if pod.DeletionGracePeriodSeconds != nil { + labels[kubernetesPodDeletionGracePeriodLabel] = strconv.FormatInt(*pod.DeletionGracePeriodSeconds, 10) + } + if pod.Spec.TerminationGracePeriodSeconds != nil { + labels[kubernetesPodTerminationGracePeriodLabel] = strconv.FormatInt(*pod.Spec.TerminationGracePeriodSeconds, 10) + } + + labels[kubernetesContainerNameLabel] = container.Name + labels[kubernetesContainerHashLabel] = strconv.FormatUint(kubecontainer.HashContainer(container), 16) + labels[kubernetesContainerRestartCountLabel] = strconv.Itoa(restartCount) + labels[kubernetesContainerTerminationMessagePathLabel] = container.TerminationMessagePath + if container.Lifecycle != nil && container.Lifecycle.PreStop != nil { + // Using json enconding so that the PreStop handler object is readable after writing as a label + rawPreStop, err := json.Marshal(container.Lifecycle.PreStop) + if err != nil { + glog.Errorf("Unable to marshal lifecycle PreStop handler for container %q of pod %q: %v", container.Name, format.Pod(pod), err) + } else { + labels[kubernetesContainerPreStopHandlerLabel] = string(rawPreStop) + } + } + + if enableCustomMetrics { + path, err := custommetrics.GetCAdvisorCustomMetricsDefinitionPath(container) + if path != nil && err == nil { + labels[cadvisorPrometheusMetricsLabel] = *path + } + } + + return labels +} + +func getContainerInfoFromLabel(labels map[string]string) *labelledContainerInfo { + var err error + containerInfo := &labelledContainerInfo{ + PodName: getStringValueFromLabel(labels, kubernetesPodNameLabel), + PodNamespace: getStringValueFromLabel(labels, kubernetesPodNamespaceLabel), + PodUID: types.UID(getStringValueFromLabel(labels, kubernetesPodUIDLabel)), + Name: getStringValueFromLabel(labels, kubernetesContainerNameLabel), + Hash: getStringValueFromLabel(labels, kubernetesContainerHashLabel), + TerminationMessagePath: getStringValueFromLabel(labels, kubernetesContainerTerminationMessagePathLabel), + } + if containerInfo.RestartCount, err = getIntValueFromLabel(labels, kubernetesContainerRestartCountLabel); err != nil { + logError(containerInfo, kubernetesContainerRestartCountLabel, err) + } + if containerInfo.PodDeletionGracePeriod, err = getInt64PointerFromLabel(labels, kubernetesPodDeletionGracePeriodLabel); err != nil { + logError(containerInfo, kubernetesPodDeletionGracePeriodLabel, err) + } + if containerInfo.PodTerminationGracePeriod, err = getInt64PointerFromLabel(labels, kubernetesPodTerminationGracePeriodLabel); err != nil { + logError(containerInfo, kubernetesPodTerminationGracePeriodLabel, err) + } + preStopHandler := &api.Handler{} + if found, err := getJsonObjectFromLabel(labels, kubernetesContainerPreStopHandlerLabel, preStopHandler); err != nil { + logError(containerInfo, kubernetesContainerPreStopHandlerLabel, err) + } else if found { + containerInfo.PreStopHandler = preStopHandler + } + supplyContainerInfoWithOldLabel(labels, containerInfo) + return containerInfo +} + +func getStringValueFromLabel(labels map[string]string, label string) string { + if value, found := labels[label]; found { + return value + } + // Do not report error, because there should be many old containers without label now. + glog.V(3).Infof("Container doesn't have label %s, it may be an old or invalid container", label) + // Return empty string "" for these containers, the caller will get value by other ways. + return "" +} + +func getIntValueFromLabel(labels map[string]string, label string) (int, error) { + if strValue, found := labels[label]; found { + intValue, err := strconv.Atoi(strValue) + if err != nil { + // This really should not happen. Just set value to 0 to handle this abnormal case + return 0, err + } + return intValue, nil + } + // Do not report error, because there should be many old containers without label now. + glog.V(3).Infof("Container doesn't have label %s, it may be an old or invalid container", label) + // Just set the value to 0 + return 0, nil +} + +func getInt64PointerFromLabel(labels map[string]string, label string) (*int64, error) { + if strValue, found := labels[label]; found { + int64Value, err := strconv.ParseInt(strValue, 10, 64) + if err != nil { + return nil, err + } + return &int64Value, nil + } + // Because it's normal that a container has no PodDeletionGracePeriod and PodTerminationGracePeriod label, + // don't report any error here. + return nil, nil +} + +// getJsonObjectFromLabel returns a bool value indicating whether an object is found +func getJsonObjectFromLabel(labels map[string]string, label string, value interface{}) (bool, error) { + if strValue, found := labels[label]; found { + err := json.Unmarshal([]byte(strValue), value) + return found, err + } + // Because it's normal that a container has no PreStopHandler label, don't report any error here. + return false, nil +} + +// The label kubernetesPodLabel is added a long time ago (#7421), it serialized the whole api.Pod to a docker label. +// We want to remove this label because it serialized too much useless information. However kubelet may still work +// with old containers which only have this label for a long time until we completely deprecate the old label. +// Before that to ensure correctness we have to supply information with the old labels when newly added labels +// are not available. +// TODO(random-liu): Remove this function when we can completely remove label kubernetesPodLabel, probably after +// dropping support for v1.1. +func supplyContainerInfoWithOldLabel(labels map[string]string, containerInfo *labelledContainerInfo) { + // Get api.Pod from old label + var pod *api.Pod + data, found := labels[kubernetesPodLabel] + if !found { + // Don't report any error here, because it's normal that a container has no pod label, especially + // when we gradually deprecate the old label + return + } + pod = &api.Pod{} + if err := runtime.DecodeInto(api.Codecs.UniversalDecoder(), []byte(data), pod); err != nil { + // If the pod label can't be parsed, we should report an error + logError(containerInfo, kubernetesPodLabel, err) + return + } + if containerInfo.PodDeletionGracePeriod == nil { + containerInfo.PodDeletionGracePeriod = pod.DeletionGracePeriodSeconds + } + if containerInfo.PodTerminationGracePeriod == nil { + containerInfo.PodTerminationGracePeriod = pod.Spec.TerminationGracePeriodSeconds + } + + // Get api.Container from api.Pod + var container *api.Container + for i := range pod.Spec.Containers { + if pod.Spec.Containers[i].Name == containerInfo.Name { + container = &pod.Spec.Containers[i] + break + } + } + if container == nil { + glog.Errorf("Unable to find container %q in pod %q", containerInfo.Name, format.Pod(pod)) + return + } + if containerInfo.PreStopHandler == nil && container.Lifecycle != nil { + containerInfo.PreStopHandler = container.Lifecycle.PreStop + } +} + +func logError(containerInfo *labelledContainerInfo, label string, err error) { + glog.Errorf("Unable to get %q for container %q of pod %q: %v", label, containerInfo.Name, + kubecontainer.BuildPodFullName(containerInfo.PodName, containerInfo.PodNamespace), err) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/labels_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/labels_test.go new file mode 100644 index 000000000..48eaa8059 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/labels_test.go @@ -0,0 +1,125 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "reflect" + "strconv" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/util/format" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/intstr" +) + +func TestLabels(t *testing.T) { + restartCount := 5 + deletionGracePeriod := int64(10) + terminationGracePeriod := int64(10) + lifecycle := &api.Lifecycle{ + // Left PostStart as nil + PreStop: &api.Handler{ + Exec: &api.ExecAction{ + Command: []string{"action1", "action2"}, + }, + HTTPGet: &api.HTTPGetAction{ + Path: "path", + Host: "host", + Port: intstr.FromInt(8080), + Scheme: "scheme", + }, + TCPSocket: &api.TCPSocketAction{ + Port: intstr.FromString("80"), + }, + }, + } + container := &api.Container{ + Name: "test_container", + TerminationMessagePath: "/somepath", + Lifecycle: lifecycle, + } + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "test_pod", + Namespace: "test_pod_namespace", + UID: "test_pod_uid", + DeletionGracePeriodSeconds: &deletionGracePeriod, + }, + Spec: api.PodSpec{ + Containers: []api.Container{*container}, + TerminationGracePeriodSeconds: &terminationGracePeriod, + }, + } + expected := &labelledContainerInfo{ + PodName: pod.Name, + PodNamespace: pod.Namespace, + PodUID: pod.UID, + PodDeletionGracePeriod: pod.DeletionGracePeriodSeconds, + PodTerminationGracePeriod: pod.Spec.TerminationGracePeriodSeconds, + Name: container.Name, + Hash: strconv.FormatUint(kubecontainer.HashContainer(container), 16), + RestartCount: restartCount, + TerminationMessagePath: container.TerminationMessagePath, + PreStopHandler: container.Lifecycle.PreStop, + } + + // Test whether we can get right information from label + labels := newLabels(container, pod, restartCount, false) + containerInfo := getContainerInfoFromLabel(labels) + if !reflect.DeepEqual(containerInfo, expected) { + t.Errorf("expected %v, got %v", expected, containerInfo) + } + + // Test when DeletionGracePeriodSeconds, TerminationGracePeriodSeconds and Lifecycle are nil, + // the information got from label should also be nil + container.Lifecycle = nil + pod.DeletionGracePeriodSeconds = nil + pod.Spec.TerminationGracePeriodSeconds = nil + expected.PodDeletionGracePeriod = nil + expected.PodTerminationGracePeriod = nil + expected.PreStopHandler = nil + // Because container is changed, the Hash should be updated + expected.Hash = strconv.FormatUint(kubecontainer.HashContainer(container), 16) + labels = newLabels(container, pod, restartCount, false) + containerInfo = getContainerInfoFromLabel(labels) + if !reflect.DeepEqual(containerInfo, expected) { + t.Errorf("expected %v, got %v", expected, containerInfo) + } + + // Test when DeletionGracePeriodSeconds, TerminationGracePeriodSeconds and Lifecycle are nil, + // but the old label kubernetesPodLabel is set, the information got from label should also be set + pod.DeletionGracePeriodSeconds = &deletionGracePeriod + pod.Spec.TerminationGracePeriodSeconds = &terminationGracePeriod + container.Lifecycle = lifecycle + data, err := runtime.Encode(testapi.Default.Codec(), pod) + if err != nil { + t.Fatalf("Failed to encode pod %q into string: %v", format.Pod(pod), err) + } + labels[kubernetesPodLabel] = string(data) + expected.PodDeletionGracePeriod = pod.DeletionGracePeriodSeconds + expected.PodTerminationGracePeriod = pod.Spec.TerminationGracePeriodSeconds + expected.PreStopHandler = container.Lifecycle.PreStop + // Do not update expected.Hash here, because we directly use the labels in last test, so we never + // changed the kubernetesContainerHashLabel in this test, the expected.Hash shouldn't be changed. + containerInfo = getContainerInfoFromLabel(labels) + if !reflect.DeepEqual(containerInfo, expected) { + t.Errorf("expected %v, got %v", expected, containerInfo) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/manager.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/manager.go new file mode 100644 index 000000000..36d0e4295 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/manager.go @@ -0,0 +1,2114 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path" + "regexp" + "strconv" + "strings" + "sync" + "time" + + "github.com/coreos/go-semver/semver" + docker "github.com/fsouza/go-dockerclient" + "github.com/golang/glog" + cadvisorapi "github.com/google/cadvisor/info/v1" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/record" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/lifecycle" + "k8s.io/kubernetes/pkg/kubelet/metrics" + "k8s.io/kubernetes/pkg/kubelet/network" + "k8s.io/kubernetes/pkg/kubelet/network/hairpin" + proberesults "k8s.io/kubernetes/pkg/kubelet/prober/results" + "k8s.io/kubernetes/pkg/kubelet/qos" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/kubelet/util/format" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/securitycontext" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/flowcontrol" + "k8s.io/kubernetes/pkg/util/oom" + "k8s.io/kubernetes/pkg/util/procfs" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + utilstrings "k8s.io/kubernetes/pkg/util/strings" +) + +const ( + DockerType = "docker" + + minimumDockerAPIVersion = "1.20" + + // ndots specifies the minimum number of dots that a domain name must contain for the resolver to consider it as FQDN (fully-qualified) + // we want to able to consider SRV lookup names like _dns._udp.kube-dns.default.svc to be considered relative. + // hence, setting ndots to be 5. + ndotsDNSOption = "options ndots:5\n" + // In order to avoid unnecessary SIGKILLs, give every container a minimum grace + // period after SIGTERM. Docker will guarantee the termination, but SIGTERM is + // potentially dangerous. + // TODO: evaluate whether there are scenarios in which SIGKILL is preferable to + // SIGTERM for certain process types, which may justify setting this to 0. + minimumGracePeriodInSeconds = 2 + + DockerNetnsFmt = "/proc/%v/ns/net" + + // String used to detect docker host mode for various namespaces (e.g. + // networking). Must match the value returned by docker inspect -f + // '{{.HostConfig.NetworkMode}}'. + namespaceModeHost = "host" + + // Remote API version for docker daemon version v1.10 + // https://docs.docker.com/engine/reference/api/docker_remote_api/ + dockerV110APIVersion = "1.22" +) + +var ( + // DockerManager implements the Runtime interface. + _ kubecontainer.Runtime = &DockerManager{} + + // TODO: make this a TTL based pull (if image older than X policy, pull) + podInfraContainerImagePullPolicy = api.PullIfNotPresent + + // Default set of security options. Seccomp is disabled by default until + // github issue #20870 is resolved. + defaultSecurityOpt = []string{"seccomp:unconfined"} +) + +type DockerManager struct { + client DockerInterface + recorder record.EventRecorder + containerRefManager *kubecontainer.RefManager + os kubecontainer.OSInterface + machineInfo *cadvisorapi.MachineInfo + + // The image name of the pod infra container. + podInfraContainerImage string + // (Optional) Additional environment variables to be set for the pod infra container. + podInfraContainerEnv []api.EnvVar + + // TODO(yifan): Record the pull failure so we can eliminate the image checking? + // Lower level docker image puller. + dockerPuller DockerPuller + + // wrapped image puller. + imagePuller kubecontainer.ImagePuller + + // Root of the Docker runtime. + dockerRoot string + + // Directory of container logs. + containerLogsDir string + + // Network plugin. + networkPlugin network.NetworkPlugin + + // Health check results. + livenessManager proberesults.Manager + + // RuntimeHelper that wraps kubelet to generate runtime container options. + runtimeHelper kubecontainer.RuntimeHelper + + // Runner of lifecycle events. + runner kubecontainer.HandlerRunner + + // Handler used to execute commands in containers. + execHandler ExecHandler + + // Used to set OOM scores of processes. + oomAdjuster *oom.OOMAdjuster + + // Get information from /proc mount. + procFs procfs.ProcFSInterface + + // If true, enforce container cpu limits with CFS quota support + cpuCFSQuota bool + + // Container GC manager + containerGC *containerGC + + // Support for gathering custom metrics. + enableCustomMetrics bool + + // If true, the "hairpin mode" flag is set on container interfaces. + // A false value means the kubelet just backs off from setting it, + // it might already be true. + configureHairpinMode bool +} + +// A subset of the pod.Manager interface extracted for testing purposes. +type podGetter interface { + GetPodByUID(types.UID) (*api.Pod, bool) +} + +func PodInfraContainerEnv(env map[string]string) kubecontainer.Option { + return func(rt kubecontainer.Runtime) { + dm := rt.(*DockerManager) + for k, v := range env { + dm.podInfraContainerEnv = append(dm.podInfraContainerEnv, api.EnvVar{ + Name: k, + Value: v, + }) + } + } +} + +func NewDockerManager( + client DockerInterface, + recorder record.EventRecorder, + livenessManager proberesults.Manager, + containerRefManager *kubecontainer.RefManager, + podGetter podGetter, + machineInfo *cadvisorapi.MachineInfo, + podInfraContainerImage string, + qps float32, + burst int, + containerLogsDir string, + osInterface kubecontainer.OSInterface, + networkPlugin network.NetworkPlugin, + runtimeHelper kubecontainer.RuntimeHelper, + httpClient kubetypes.HttpGetter, + execHandler ExecHandler, + oomAdjuster *oom.OOMAdjuster, + procFs procfs.ProcFSInterface, + cpuCFSQuota bool, + imageBackOff *flowcontrol.Backoff, + serializeImagePulls bool, + enableCustomMetrics bool, + hairpinMode bool, + options ...kubecontainer.Option) *DockerManager { + // Wrap the docker client with instrumentedDockerInterface + client = newInstrumentedDockerInterface(client) + + // Work out the location of the Docker runtime, defaulting to /var/lib/docker + // if there are any problems. + dockerRoot := "/var/lib/docker" + dockerInfo, err := client.Info() + if err != nil { + glog.Errorf("Failed to execute Info() call to the Docker client: %v", err) + glog.Warningf("Using fallback default of /var/lib/docker for location of Docker runtime") + } else { + dockerRoot = dockerInfo.Get("DockerRootDir") + glog.Infof("Setting dockerRoot to %s", dockerRoot) + } + + dm := &DockerManager{ + client: client, + recorder: recorder, + containerRefManager: containerRefManager, + os: osInterface, + machineInfo: machineInfo, + podInfraContainerImage: podInfraContainerImage, + dockerPuller: newDockerPuller(client, qps, burst), + dockerRoot: dockerRoot, + containerLogsDir: containerLogsDir, + networkPlugin: networkPlugin, + livenessManager: livenessManager, + runtimeHelper: runtimeHelper, + execHandler: execHandler, + oomAdjuster: oomAdjuster, + procFs: procFs, + cpuCFSQuota: cpuCFSQuota, + enableCustomMetrics: enableCustomMetrics, + configureHairpinMode: hairpinMode, + } + dm.runner = lifecycle.NewHandlerRunner(httpClient, dm, dm) + if serializeImagePulls { + dm.imagePuller = kubecontainer.NewSerializedImagePuller(kubecontainer.FilterEventRecorder(recorder), dm, imageBackOff) + } else { + dm.imagePuller = kubecontainer.NewImagePuller(kubecontainer.FilterEventRecorder(recorder), dm, imageBackOff) + } + dm.containerGC = NewContainerGC(client, podGetter, containerLogsDir) + + // apply optional settings.. + for _, optf := range options { + optf(dm) + } + + return dm +} + +// GetContainerLogs returns logs of a specific container. By +// default, it returns a snapshot of the container log. Set 'follow' to true to +// stream the log. Set 'follow' to false and specify the number of lines (e.g. +// "100" or "all") to tail the log. +// TODO: Make 'RawTerminal' option flagable. +func (dm *DockerManager) GetContainerLogs(pod *api.Pod, containerID kubecontainer.ContainerID, logOptions *api.PodLogOptions, stdout, stderr io.Writer) (err error) { + var since int64 + if logOptions.SinceSeconds != nil { + t := unversioned.Now().Add(-time.Duration(*logOptions.SinceSeconds) * time.Second) + since = t.Unix() + } + if logOptions.SinceTime != nil { + since = logOptions.SinceTime.Unix() + } + opts := docker.LogsOptions{ + Container: containerID.ID, + Stdout: true, + Stderr: true, + OutputStream: stdout, + ErrorStream: stderr, + Timestamps: logOptions.Timestamps, + Since: since, + Follow: logOptions.Follow, + RawTerminal: false, + } + + if logOptions.TailLines != nil { + opts.Tail = strconv.FormatInt(*logOptions.TailLines, 10) + } + + err = dm.client.Logs(opts) + return +} + +var ( + // ErrNoContainersInPod is returned when there are no containers for a given pod + ErrNoContainersInPod = errors.New("NoContainersInPod") + + // ErrNoPodInfraContainerInPod is returned when there is no pod infra container for a given pod + ErrNoPodInfraContainerInPod = errors.New("NoPodInfraContainerInPod") + + // ErrContainerCannotRun is returned when a container is created, but cannot run properly + ErrContainerCannotRun = errors.New("ContainerCannotRun") +) + +// determineContainerIP determines the IP address of the given container. It is expected +// that the container passed is the infrastructure container of a pod and the responsibility +// of the caller to ensure that the correct container is passed. +func (dm *DockerManager) determineContainerIP(podNamespace, podName string, container *docker.Container) string { + result := "" + + if container.NetworkSettings != nil { + result = container.NetworkSettings.IPAddress + } + + if dm.networkPlugin.Name() != network.DefaultPluginName { + netStatus, err := dm.networkPlugin.Status(podNamespace, podName, kubecontainer.DockerID(container.ID)) + if err != nil { + glog.Errorf("NetworkPlugin %s failed on the status hook for pod '%s' - %v", dm.networkPlugin.Name(), podName, err) + } else if netStatus != nil { + result = netStatus.IP.String() + } + } + + return result +} + +func (dm *DockerManager) inspectContainer(id string, podName, podNamespace string) (*kubecontainer.ContainerStatus, string, error) { + var ip string + iResult, err := dm.client.InspectContainer(id) + if err != nil { + return nil, ip, err + } + glog.V(4).Infof("Container inspect result: %+v", *iResult) + + // TODO: Get k8s container name by parsing the docker name. This will be + // replaced by checking docker labels eventually. + dockerName, hash, err := ParseDockerName(iResult.Name) + if err != nil { + return nil, ip, fmt.Errorf("Unable to parse docker name %q", iResult.Name) + } + containerName := dockerName.ContainerName + + var containerInfo *labelledContainerInfo + containerInfo = getContainerInfoFromLabel(iResult.Config.Labels) + + status := kubecontainer.ContainerStatus{ + Name: containerName, + RestartCount: containerInfo.RestartCount, + Image: iResult.Config.Image, + ImageID: DockerPrefix + iResult.Image, + ID: kubecontainer.DockerID(id).ContainerID(), + ExitCode: iResult.State.ExitCode, + CreatedAt: iResult.Created, + Hash: hash, + } + if iResult.State.Running { + // Container that are running, restarting and paused + status.State = kubecontainer.ContainerStateRunning + status.StartedAt = iResult.State.StartedAt + if containerName == PodInfraContainerName { + ip = dm.determineContainerIP(podNamespace, podName, iResult) + } + return &status, ip, nil + } + + // Find containers that have exited or failed to start. + if !iResult.State.FinishedAt.IsZero() || iResult.State.ExitCode != 0 { + // Containers that are exited, dead or created (docker failed to start container) + // When a container fails to start State.ExitCode is non-zero, FinishedAt and StartedAt are both zero + reason := "" + message := iResult.State.Error + finishedAt := iResult.State.FinishedAt + startedAt := iResult.State.StartedAt + + // Note: An application might handle OOMKilled gracefully. + // In that case, the container is oom killed, but the exit + // code could be 0. + if iResult.State.OOMKilled { + reason = "OOMKilled" + } else if iResult.State.ExitCode == 0 { + reason = "Completed" + } else if !iResult.State.FinishedAt.IsZero() { + reason = "Error" + } else { + // finishedAt is zero and ExitCode is nonZero occurs when docker fails to start the container + reason = ErrContainerCannotRun.Error() + // Adjust time to the time docker attempted to run the container, otherwise startedAt and finishedAt will be set to epoch, which is misleading + finishedAt = iResult.Created + startedAt = iResult.Created + } + + terminationMessagePath := containerInfo.TerminationMessagePath + if terminationMessagePath != "" { + for _, mount := range iResult.Mounts { + if mount.Destination == terminationMessagePath { + path := mount.Source + if data, err := ioutil.ReadFile(path); err != nil { + message = fmt.Sprintf("Error on reading termination-log %s: %v", path, err) + } else { + message = string(data) + } + } + } + } + status.State = kubecontainer.ContainerStateExited + status.Message = message + status.Reason = reason + status.StartedAt = startedAt + status.FinishedAt = finishedAt + } else { + // Non-running containers that are created (not yet started or kubelet failed before calling + // start container function etc.) Kubelet doesn't handle these scenarios yet. + status.State = kubecontainer.ContainerStateUnknown + } + return &status, "", nil +} + +// makeEnvList converts EnvVar list to a list of strings, in the form of +// '=', which can be understood by docker. +func makeEnvList(envs []kubecontainer.EnvVar) (result []string) { + for _, env := range envs { + result = append(result, fmt.Sprintf("%s=%s", env.Name, env.Value)) + } + return +} + +// makeMountBindings converts the mount list to a list of strings that +// can be understood by docker. +// Each element in the string is in the form of: +// ':', or +// '::ro', if the path is read only, or +// '::Z', if the volume requires SELinux +// relabeling and the pod provides an SELinux label +func makeMountBindings(mounts []kubecontainer.Mount, podHasSELinuxLabel bool) (result []string) { + for _, m := range mounts { + bind := fmt.Sprintf("%s:%s", m.HostPath, m.ContainerPath) + if m.ReadOnly { + bind += ":ro" + } + // Only request relabeling if the pod provides an + // SELinux context. If the pod does not provide an + // SELinux context relabeling will label the volume + // with the container's randomly allocated MCS label. + // This would restrict access to the volume to the + // container which mounts it first. + if m.SELinuxRelabel && podHasSELinuxLabel { + if m.ReadOnly { + bind += ",Z" + } else { + bind += ":Z" + } + + } + result = append(result, bind) + } + return +} + +func makePortsAndBindings(portMappings []kubecontainer.PortMapping) (map[docker.Port]struct{}, map[docker.Port][]docker.PortBinding) { + exposedPorts := map[docker.Port]struct{}{} + portBindings := map[docker.Port][]docker.PortBinding{} + for _, port := range portMappings { + exteriorPort := port.HostPort + if exteriorPort == 0 { + // No need to do port binding when HostPort is not specified + continue + } + interiorPort := port.ContainerPort + // Some of this port stuff is under-documented voodoo. + // See http://stackoverflow.com/questions/20428302/binding-a-port-to-a-host-interface-using-the-rest-api + var protocol string + switch strings.ToUpper(string(port.Protocol)) { + case "UDP": + protocol = "/udp" + case "TCP": + protocol = "/tcp" + default: + glog.Warningf("Unknown protocol %q: defaulting to TCP", port.Protocol) + protocol = "/tcp" + } + + dockerPort := docker.Port(strconv.Itoa(interiorPort) + protocol) + exposedPorts[dockerPort] = struct{}{} + + hostBinding := docker.PortBinding{ + HostPort: strconv.Itoa(exteriorPort), + HostIP: port.HostIP, + } + + // Allow multiple host ports bind to same docker port + if existedBindings, ok := portBindings[dockerPort]; ok { + // If a docker port already map to a host port, just append the host ports + portBindings[dockerPort] = append(existedBindings, hostBinding) + } else { + // Otherwise, it's fresh new port binding + portBindings[dockerPort] = []docker.PortBinding{ + hostBinding, + } + } + } + return exposedPorts, portBindings +} + +func (dm *DockerManager) runContainer( + pod *api.Pod, + container *api.Container, + opts *kubecontainer.RunContainerOptions, + ref *api.ObjectReference, + netMode string, + ipcMode string, + utsMode string, + pidMode string, + restartCount int) (kubecontainer.ContainerID, error) { + + dockerName := KubeletContainerName{ + PodFullName: kubecontainer.GetPodFullName(pod), + PodUID: pod.UID, + ContainerName: container.Name, + } + + securityOpts, err := dm.defaultSecurityOpt() + if err != nil { + return kubecontainer.ContainerID{}, err + } + + // Pod information is recorded on the container as labels to preserve it in the event the pod is deleted + // while the Kubelet is down and there is no information available to recover the pod. + // TODO: keep these labels up to date if the pod changes + labels := newLabels(container, pod, restartCount, dm.enableCustomMetrics) + + // TODO(random-liu): Remove this when we start to use new labels for KillContainerInPod + if container.Lifecycle != nil && container.Lifecycle.PreStop != nil { + // TODO: This is kind of hacky, we should really just encode the bits we need. + // TODO: This is hacky because the Kubelet should be parameterized to encode a specific version + // and needs to be able to migrate this whenever we deprecate v1. Should be a member of DockerManager. + if data, err := runtime.Encode(api.Codecs.LegacyCodec(unversioned.GroupVersion{Group: api.GroupName, Version: "v1"}), pod); err == nil { + labels[kubernetesPodLabel] = string(data) + } else { + glog.Errorf("Failed to encode pod: %s for prestop hook", pod.Name) + } + } + memoryLimit := container.Resources.Limits.Memory().Value() + cpuRequest := container.Resources.Requests.Cpu() + cpuLimit := container.Resources.Limits.Cpu() + var cpuShares int64 + // If request is not specified, but limit is, we want request to default to limit. + // API server does this for new containers, but we repeat this logic in Kubelet + // for containers running on existing Kubernetes clusters. + if cpuRequest.Amount == nil && cpuLimit.Amount != nil { + cpuShares = milliCPUToShares(cpuLimit.MilliValue()) + } else { + // if cpuRequest.Amount is nil, then milliCPUToShares will return the minimal number + // of CPU shares. + cpuShares = milliCPUToShares(cpuRequest.MilliValue()) + } + podHasSELinuxLabel := pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SELinuxOptions != nil + binds := makeMountBindings(opts.Mounts, podHasSELinuxLabel) + // The reason we create and mount the log file in here (not in kubelet) is because + // the file's location depends on the ID of the container, and we need to create and + // mount the file before actually starting the container. + // TODO(yifan): Consider to pull this logic out since we might need to reuse it in + // other container runtime. + _, containerName, cid := BuildDockerName(dockerName, container) + if opts.PodContainerDir != "" && len(container.TerminationMessagePath) != 0 { + // Because the PodContainerDir contains pod uid and container name which is unique enough, + // here we just add an unique container id to make the path unique for different instances + // of the same container. + containerLogPath := path.Join(opts.PodContainerDir, cid) + fs, err := os.Create(containerLogPath) + if err != nil { + // TODO: Clean up the previouly created dir? return the error? + glog.Errorf("Error on creating termination-log file %q: %v", containerLogPath, err) + } else { + fs.Close() // Close immediately; we're just doing a `touch` here + b := fmt.Sprintf("%s:%s", containerLogPath, container.TerminationMessagePath) + binds = append(binds, b) + } + } + + hc := &docker.HostConfig{ + Binds: binds, + NetworkMode: netMode, + IpcMode: ipcMode, + UTSMode: utsMode, + PidMode: pidMode, + ReadonlyRootfs: readOnlyRootFilesystem(container), + // Memory and CPU are set here for newer versions of Docker (1.6+). + Memory: memoryLimit, + MemorySwap: -1, + CPUShares: cpuShares, + SecurityOpt: securityOpts, + } + + if dm.cpuCFSQuota { + // if cpuLimit.Amount is nil, then the appropriate default value is returned to allow full usage of cpu resource. + cpuQuota, cpuPeriod := milliCPUToQuota(cpuLimit.MilliValue()) + + hc.CPUQuota = cpuQuota + hc.CPUPeriod = cpuPeriod + } + + if len(opts.CgroupParent) > 0 { + hc.CgroupParent = opts.CgroupParent + } + + dockerOpts := docker.CreateContainerOptions{ + Name: containerName, + Config: &docker.Config{ + Env: makeEnvList(opts.Envs), + Image: container.Image, + // Memory and CPU are set here for older versions of Docker (pre-1.6). + Memory: memoryLimit, + MemorySwap: -1, + CPUShares: cpuShares, + WorkingDir: container.WorkingDir, + Labels: labels, + // Interactive containers: + OpenStdin: container.Stdin, + StdinOnce: container.StdinOnce, + Tty: container.TTY, + }, + HostConfig: hc, + } + + // Set network configuration for infra-container + if container.Name == PodInfraContainerName { + setInfraContainerNetworkConfig(pod, netMode, opts, dockerOpts) + } + + setEntrypointAndCommand(container, opts, &dockerOpts) + + glog.V(3).Infof("Container %v/%v/%v: setting entrypoint \"%v\" and command \"%v\"", pod.Namespace, pod.Name, container.Name, dockerOpts.Config.Entrypoint, dockerOpts.Config.Cmd) + + securityContextProvider := securitycontext.NewSimpleSecurityContextProvider() + securityContextProvider.ModifyContainerConfig(pod, container, dockerOpts.Config) + securityContextProvider.ModifyHostConfig(pod, container, dockerOpts.HostConfig) + dockerContainer, err := dm.client.CreateContainer(dockerOpts) + if err != nil { + dm.recorder.Eventf(ref, api.EventTypeWarning, kubecontainer.FailedToCreateContainer, "Failed to create docker container with error: %v", err) + return kubecontainer.ContainerID{}, err + } + dm.recorder.Eventf(ref, api.EventTypeNormal, kubecontainer.CreatedContainer, "Created container with docker id %v", utilstrings.ShortenString(dockerContainer.ID, 12)) + + if err = dm.client.StartContainer(dockerContainer.ID, nil); err != nil { + dm.recorder.Eventf(ref, api.EventTypeWarning, kubecontainer.FailedToStartContainer, + "Failed to start container with docker id %v with error: %v", utilstrings.ShortenString(dockerContainer.ID, 12), err) + return kubecontainer.ContainerID{}, err + } + dm.recorder.Eventf(ref, api.EventTypeNormal, kubecontainer.StartedContainer, "Started container with docker id %v", utilstrings.ShortenString(dockerContainer.ID, 12)) + + return kubecontainer.DockerID(dockerContainer.ID).ContainerID(), nil +} + +// setInfraContainerNetworkConfig sets the network configuration for the infra-container. We only set network configuration for infra-container, all +// the user containers will share the same network namespace with infra-container. +func setInfraContainerNetworkConfig(pod *api.Pod, netMode string, opts *kubecontainer.RunContainerOptions, dockerOpts docker.CreateContainerOptions) { + exposedPorts, portBindings := makePortsAndBindings(opts.PortMappings) + dockerOpts.Config.ExposedPorts = exposedPorts + dockerOpts.HostConfig.PortBindings = portBindings + + if netMode != namespaceModeHost { + dockerOpts.Config.Hostname = opts.Hostname + if len(opts.DNS) > 0 { + dockerOpts.HostConfig.DNS = opts.DNS + } + if len(opts.DNSSearch) > 0 { + dockerOpts.HostConfig.DNSSearch = opts.DNSSearch + } + } +} + +func setEntrypointAndCommand(container *api.Container, opts *kubecontainer.RunContainerOptions, dockerOpts *docker.CreateContainerOptions) { + command, args := kubecontainer.ExpandContainerCommandAndArgs(container, opts.Envs) + + dockerOpts.Config.Entrypoint = command + dockerOpts.Config.Cmd = args +} + +// A helper function to get the KubeletContainerName and hash from a docker +// container. +func getDockerContainerNameInfo(c *docker.APIContainers) (*KubeletContainerName, uint64, error) { + if len(c.Names) == 0 { + return nil, 0, fmt.Errorf("cannot parse empty docker container name: %#v", c.Names) + } + dockerName, hash, err := ParseDockerName(c.Names[0]) + if err != nil { + return nil, 0, fmt.Errorf("parse docker container name %q error: %v", c.Names[0], err) + } + return dockerName, hash, nil +} + +// Get pod UID, name, and namespace by examining the container names. +func getPodInfoFromContainer(c *docker.APIContainers) (types.UID, string, string, error) { + dockerName, _, err := getDockerContainerNameInfo(c) + if err != nil { + return types.UID(""), "", "", err + } + name, namespace, err := kubecontainer.ParsePodFullName(dockerName.PodFullName) + if err != nil { + return types.UID(""), "", "", fmt.Errorf("parse pod full name %q error: %v", dockerName.PodFullName, err) + } + return dockerName.PodUID, name, namespace, nil +} + +// GetContainers returns a list of running containers if |all| is false; +// otherwise, it returns all containers. +func (dm *DockerManager) GetContainers(all bool) ([]*kubecontainer.Container, error) { + containers, err := GetKubeletDockerContainers(dm.client, all) + if err != nil { + return nil, err + } + // Convert DockerContainers to []*kubecontainer.Container + result := make([]*kubecontainer.Container, 0, len(containers)) + for _, c := range containers { + converted, err := toRuntimeContainer(c) + if err != nil { + glog.Errorf("Error examining the container: %v", err) + continue + } + result = append(result, converted) + } + return result, nil +} + +func (dm *DockerManager) GetPods(all bool) ([]*kubecontainer.Pod, error) { + start := time.Now() + defer func() { + metrics.ContainerManagerLatency.WithLabelValues("GetPods").Observe(metrics.SinceInMicroseconds(start)) + }() + pods := make(map[types.UID]*kubecontainer.Pod) + var result []*kubecontainer.Pod + + containers, err := GetKubeletDockerContainers(dm.client, all) + if err != nil { + return nil, err + } + + // Group containers by pod. + for _, c := range containers { + converted, err := toRuntimeContainer(c) + if err != nil { + glog.Errorf("Error examining the container: %v", err) + continue + } + + podUID, podName, podNamespace, err := getPodInfoFromContainer(c) + if err != nil { + glog.Errorf("Error examining the container: %v", err) + continue + } + + pod, found := pods[podUID] + if !found { + pod = &kubecontainer.Pod{ + ID: podUID, + Name: podName, + Namespace: podNamespace, + } + pods[podUID] = pod + } + pod.Containers = append(pod.Containers, converted) + } + + // Convert map to list. + for _, c := range pods { + result = append(result, c) + } + return result, nil +} + +// List all images in the local storage. +func (dm *DockerManager) ListImages() ([]kubecontainer.Image, error) { + var images []kubecontainer.Image + + dockerImages, err := dm.client.ListImages(docker.ListImagesOptions{}) + if err != nil { + return images, err + } + + for _, di := range dockerImages { + image, err := toRuntimeImage(&di) + if err != nil { + continue + } + images = append(images, *image) + } + return images, nil +} + +// TODO(vmarmol): Consider unexporting. +// PullImage pulls an image from network to local storage. +func (dm *DockerManager) PullImage(image kubecontainer.ImageSpec, secrets []api.Secret) error { + return dm.dockerPuller.Pull(image.Image, secrets) +} + +// IsImagePresent checks whether the container image is already in the local storage. +func (dm *DockerManager) IsImagePresent(image kubecontainer.ImageSpec) (bool, error) { + return dm.dockerPuller.IsImagePresent(image.Image) +} + +// Removes the specified image. +func (dm *DockerManager) RemoveImage(image kubecontainer.ImageSpec) error { + return dm.client.RemoveImage(image.Image) +} + +// podInfraContainerChanged returns true if the pod infra container has changed. +func (dm *DockerManager) podInfraContainerChanged(pod *api.Pod, podInfraContainerStatus *kubecontainer.ContainerStatus) (bool, error) { + var ports []api.ContainerPort + + // Check network mode. + if usesHostNetwork(pod) { + dockerPodInfraContainer, err := dm.client.InspectContainer(podInfraContainerStatus.ID.ID) + if err != nil { + return false, err + } + + networkMode := getDockerNetworkMode(dockerPodInfraContainer) + if networkMode != namespaceModeHost { + glog.V(4).Infof("host: %v, %v", pod.Spec.SecurityContext.HostNetwork, networkMode) + return true, nil + } + } else if dm.networkPlugin.Name() != "cni" && dm.networkPlugin.Name() != "kubenet" { + // Docker only exports ports from the pod infra container. Let's + // collect all of the relevant ports and export them. + for _, container := range pod.Spec.Containers { + ports = append(ports, container.Ports...) + } + } + expectedPodInfraContainer := &api.Container{ + Name: PodInfraContainerName, + Image: dm.podInfraContainerImage, + Ports: ports, + ImagePullPolicy: podInfraContainerImagePullPolicy, + Env: dm.podInfraContainerEnv, + } + return podInfraContainerStatus.Hash != kubecontainer.HashContainer(expectedPodInfraContainer), nil +} + +// pod must not be nil +func usesHostNetwork(pod *api.Pod) bool { + return pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostNetwork +} + +// determine if the container root should be a read only filesystem. +func readOnlyRootFilesystem(container *api.Container) bool { + return container.SecurityContext != nil && container.SecurityContext.ReadOnlyRootFilesystem != nil && *container.SecurityContext.ReadOnlyRootFilesystem +} + +// container must not be nil +func getDockerNetworkMode(container *docker.Container) string { + if container.HostConfig != nil { + return container.HostConfig.NetworkMode + } + return "" +} + +// dockerVersion implementes kubecontainer.Version interface by implementing +// Compare() and String() (which is implemented by the underlying semver.Version) +// TODO: this code is the same as rktVersion and may make sense to be moved to +// somewhere shared. +type dockerVersion struct { + *semver.Version +} + +// Older versions of Docker could return non-semantically versioned values (distros like Fedora +// included partial values such as 1.8.1.fc21 which is not semver). Force those values to be semver. +var almostSemverRegexp = regexp.MustCompile(`^(\d+\.\d+\.\d+)\.(.*)$`) + +// newDockerVersion returns a semantically versioned docker version value +func newDockerVersion(version string) (dockerVersion, error) { + sem, err := semver.NewVersion(version) + if err != nil { + matches := almostSemverRegexp.FindStringSubmatch(version) + if matches == nil { + return dockerVersion{}, err + } + sem, err = semver.NewVersion(strings.Join(matches[1:], "-")) + } + return dockerVersion{sem}, err +} + +func (r dockerVersion) Compare(other string) (int, error) { + v, err := newDockerVersion(other) + if err != nil { + return -1, err + } + + if r.LessThan(*v.Version) { + return -1, nil + } + if v.Version.LessThan(*r.Version) { + return 1, nil + } + return 0, nil +} + +// dockerVersion implementes kubecontainer.Version interface by implementing +// Compare() and String() on top og go-dockerclient's APIVersion. This version +// string doesn't conform to semantic versioning, as it is only "x.y" +type dockerAPIVersion docker.APIVersion + +func (dv dockerAPIVersion) String() string { + return docker.APIVersion(dv).String() +} + +func (dv dockerAPIVersion) Compare(other string) (int, error) { + a := docker.APIVersion(dv) + b, err := docker.NewAPIVersion(other) + if err != nil { + return 0, err + } + if a.LessThan(b) { + return -1, nil + } + if a.GreaterThan(b) { + return 1, nil + } + return 0, nil +} + +func (dm *DockerManager) Type() string { + return DockerType +} + +func (dm *DockerManager) Version() (kubecontainer.Version, error) { + env, err := dm.client.Version() + if err != nil { + return nil, fmt.Errorf("docker: failed to get docker version: %v", err) + } + + engineVersion := env.Get("Version") + version, err := newDockerVersion(engineVersion) + if err != nil { + glog.Errorf("docker: failed to parse docker server version %q: %v", engineVersion, err) + return nil, fmt.Errorf("docker: failed to parse docker server version %q: %v", engineVersion, err) + } + return version, nil +} + +func (dm *DockerManager) APIVersion() (kubecontainer.Version, error) { + env, err := dm.client.Version() + if err != nil { + return nil, fmt.Errorf("docker: failed to get docker version: %v", err) + } + + apiVersion := env.Get("ApiVersion") + version, err := docker.NewAPIVersion(apiVersion) + if err != nil { + glog.Errorf("docker: failed to parse docker api version %q: %v", apiVersion, err) + return nil, fmt.Errorf("docker: failed to parse docker api version %q: %v", apiVersion, err) + } + return dockerAPIVersion(version), nil +} + +// Status returns error if docker daemon is unhealthy, nil otherwise. +// Now we do this by checking whether: +// 1) `docker version` works +// 2) docker version is compatible with minimum requirement +func (dm *DockerManager) Status() error { + return dm.checkVersionCompatibility() +} + +func (dm *DockerManager) checkVersionCompatibility() error { + version, err := dm.APIVersion() + if err != nil { + return err + } + // Verify the docker version. + result, err := version.Compare(minimumDockerAPIVersion) + if err != nil { + return fmt.Errorf("failed to compare current docker version %v with minimum support Docker version %q - %v", version, minimumDockerAPIVersion, err) + } + if result < 0 { + return fmt.Errorf("container runtime version is older than %s", minimumDockerAPIVersion) + } + return nil +} + +func (dm *DockerManager) defaultSecurityOpt() ([]string, error) { + version, err := dm.APIVersion() + if err != nil { + return nil, err + } + // seccomp is to be disabled on docker versions >= v1.10 + result, err := version.Compare(dockerV110APIVersion) + if err != nil { + return nil, err + } + if result >= 0 { + return defaultSecurityOpt, nil + } + return nil, nil +} + +// RunInContainer run the command inside the container identified by containerID +func (dm *DockerManager) RunInContainer(containerID kubecontainer.ContainerID, cmd []string) ([]byte, error) { + glog.V(2).Infof("Using docker native exec to run cmd %+v inside container %s", cmd, containerID) + createOpts := docker.CreateExecOptions{ + Container: containerID.ID, + Cmd: cmd, + AttachStdin: false, + AttachStdout: true, + AttachStderr: true, + Tty: false, + } + execObj, err := dm.client.CreateExec(createOpts) + if err != nil { + return nil, fmt.Errorf("failed to run in container - Exec setup failed - %v", err) + } + var buf bytes.Buffer + startOpts := docker.StartExecOptions{ + Detach: false, + Tty: false, + OutputStream: &buf, + ErrorStream: &buf, + RawTerminal: false, + } + err = dm.client.StartExec(execObj.ID, startOpts) + if err != nil { + glog.V(2).Infof("StartExec With error: %v", err) + return nil, err + } + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + for { + inspect, err2 := dm.client.InspectExec(execObj.ID) + if err2 != nil { + glog.V(2).Infof("InspectExec %s failed with error: %+v", execObj.ID, err2) + return buf.Bytes(), err2 + } + if !inspect.Running { + if inspect.ExitCode != 0 { + glog.V(2).Infof("InspectExec %s exit with result %+v", execObj.ID, inspect) + err = &dockerExitError{inspect} + } + break + } + <-ticker.C + } + + return buf.Bytes(), err +} + +type dockerExitError struct { + Inspect *docker.ExecInspect +} + +func (d *dockerExitError) String() string { + return d.Error() +} + +func (d *dockerExitError) Error() string { + return fmt.Sprintf("Error executing in Docker Container: %d", d.Inspect.ExitCode) +} + +func (d *dockerExitError) Exited() bool { + return !d.Inspect.Running +} + +func (d *dockerExitError) ExitStatus() int { + return d.Inspect.ExitCode +} + +// ExecInContainer runs the command inside the container identified by containerID. +func (dm *DockerManager) ExecInContainer(containerID kubecontainer.ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error { + if dm.execHandler == nil { + return errors.New("unable to exec without an exec handler") + } + + container, err := dm.client.InspectContainer(containerID.ID) + if err != nil { + return err + } + if !container.State.Running { + return fmt.Errorf("container not running (%s)", container.ID) + } + + return dm.execHandler.ExecInContainer(dm.client, container, cmd, stdin, stdout, stderr, tty) +} + +func (dm *DockerManager) AttachContainer(containerID kubecontainer.ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error { + opts := docker.AttachToContainerOptions{ + Container: containerID.ID, + InputStream: stdin, + OutputStream: stdout, + ErrorStream: stderr, + Stream: true, + Logs: true, + Stdin: stdin != nil, + Stdout: stdout != nil, + Stderr: stderr != nil, + RawTerminal: tty, + } + return dm.client.AttachToContainer(opts) +} + +func noPodInfraContainerError(podName, podNamespace string) error { + return fmt.Errorf("cannot find pod infra container in pod %q", kubecontainer.BuildPodFullName(podName, podNamespace)) +} + +// PortForward executes socat in the pod's network namespace and copies +// data between stream (representing the user's local connection on their +// computer) and the specified port in the container. +// +// TODO: +// - match cgroups of container +// - should we support nsenter + socat on the host? (current impl) +// - should we support nsenter + socat in a container, running with elevated privs and --pid=host? +func (dm *DockerManager) PortForward(pod *kubecontainer.Pod, port uint16, stream io.ReadWriteCloser) error { + podInfraContainer := pod.FindContainerByName(PodInfraContainerName) + if podInfraContainer == nil { + return noPodInfraContainerError(pod.Name, pod.Namespace) + } + container, err := dm.client.InspectContainer(podInfraContainer.ID.ID) + if err != nil { + return err + } + + if !container.State.Running { + return fmt.Errorf("container not running (%s)", container.ID) + } + + containerPid := container.State.Pid + socatPath, lookupErr := exec.LookPath("socat") + if lookupErr != nil { + return fmt.Errorf("unable to do port forwarding: socat not found.") + } + + args := []string{"-t", fmt.Sprintf("%d", containerPid), "-n", socatPath, "-", fmt.Sprintf("TCP4:localhost:%d", port)} + + nsenterPath, lookupErr := exec.LookPath("nsenter") + if lookupErr != nil { + return fmt.Errorf("unable to do port forwarding: nsenter not found.") + } + + commandString := fmt.Sprintf("%s %s", nsenterPath, strings.Join(args, " ")) + glog.V(4).Infof("executing port forwarding command: %s", commandString) + + command := exec.Command(nsenterPath, args...) + command.Stdout = stream + + stderr := new(bytes.Buffer) + command.Stderr = stderr + + // If we use Stdin, command.Run() won't return until the goroutine that's copying + // from stream finishes. Unfortunately, if you have a client like telnet connected + // via port forwarding, as long as the user's telnet client is connected to the user's + // local listener that port forwarding sets up, the telnet session never exits. This + // means that even if socat has finished running, command.Run() won't ever return + // (because the client still has the connection and stream open). + // + // The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe + // when the command (socat) exits. + inPipe, err := command.StdinPipe() + if err != nil { + return fmt.Errorf("unable to do port forwarding: error creating stdin pipe: %v", err) + } + go func() { + io.Copy(inPipe, stream) + inPipe.Close() + }() + + if err := command.Run(); err != nil { + return fmt.Errorf("%v: %s", err, stderr.String()) + } + + return nil +} + +// Get the IP address of a container's interface using nsenter +func (dm *DockerManager) GetContainerIP(containerID, interfaceName string) (string, error) { + _, lookupErr := exec.LookPath("nsenter") + if lookupErr != nil { + return "", fmt.Errorf("Unable to obtain IP address of container: missing nsenter.") + } + container, err := dm.client.InspectContainer(containerID) + if err != nil { + return "", err + } + + if !container.State.Running { + return "", fmt.Errorf("container not running (%s)", container.ID) + } + + containerPid := container.State.Pid + extractIPCmd := fmt.Sprintf("ip -4 addr show %s | grep inet | awk -F\" \" '{print $2}'", interfaceName) + args := []string{"-t", fmt.Sprintf("%d", containerPid), "-n", "--", "bash", "-c", extractIPCmd} + command := exec.Command("nsenter", args...) + out, err := command.CombinedOutput() + if err != nil { + return "", err + } + return string(out), nil +} + +// TODO(random-liu): Change running pod to pod status in the future. We can't do it now, because kubelet also uses this function without pod status. +// We can only deprecate this after refactoring kubelet. +// TODO(random-liu): After using pod status for KillPod(), we can also remove the kubernetesPodLabel, because all the needed information should have +// been extract from new labels and stored in pod status. +func (dm *DockerManager) KillPod(pod *api.Pod, runningPod kubecontainer.Pod) error { + result := dm.killPodWithSyncResult(pod, runningPod) + return result.Error() +} + +// TODO(random-liu): This is just a temporary function, will be removed when we acturally add PodSyncResult +// NOTE(random-liu): The pod passed in could be *nil* when kubelet restarted. +func (dm *DockerManager) killPodWithSyncResult(pod *api.Pod, runningPod kubecontainer.Pod) (result kubecontainer.PodSyncResult) { + // Send the kills in parallel since they may take a long time. + // There may be len(runningPod.Containers) or len(runningPod.Containers)-1 of result in the channel + containerResults := make(chan *kubecontainer.SyncResult, len(runningPod.Containers)) + wg := sync.WaitGroup{} + var ( + networkContainer *kubecontainer.Container + networkSpec *api.Container + ) + wg.Add(len(runningPod.Containers)) + for _, container := range runningPod.Containers { + go func(container *kubecontainer.Container) { + defer utilruntime.HandleCrash() + defer wg.Done() + + var containerSpec *api.Container + if pod != nil { + for i, c := range pod.Spec.Containers { + if c.Name == container.Name { + containerSpec = &pod.Spec.Containers[i] + break + } + } + } + + // TODO: Handle this without signaling the pod infra container to + // adapt to the generic container runtime. + if container.Name == PodInfraContainerName { + // Store the container runtime for later deletion. + // We do this so that PreStop handlers can run in the network namespace. + networkContainer = container + networkSpec = containerSpec + return + } + + killContainerResult := kubecontainer.NewSyncResult(kubecontainer.KillContainer, container.Name) + err := dm.KillContainerInPod(container.ID, containerSpec, pod, "Need to kill pod.") + if err != nil { + killContainerResult.Fail(kubecontainer.ErrKillContainer, err.Error()) + glog.Errorf("Failed to delete container: %v; Skipping pod %q", err, runningPod.ID) + } + containerResults <- killContainerResult + }(container) + } + wg.Wait() + close(containerResults) + for containerResult := range containerResults { + result.AddSyncResult(containerResult) + } + if networkContainer != nil { + ins, err := dm.client.InspectContainer(networkContainer.ID.ID) + if err != nil { + err = fmt.Errorf("Error inspecting container %v: %v", networkContainer.ID.ID, err) + glog.Error(err) + result.Fail(err) + return + } + if getDockerNetworkMode(ins) != namespaceModeHost { + teardownNetworkResult := kubecontainer.NewSyncResult(kubecontainer.TeardownNetwork, kubecontainer.BuildPodFullName(runningPod.Name, runningPod.Namespace)) + result.AddSyncResult(teardownNetworkResult) + if err := dm.networkPlugin.TearDownPod(runningPod.Namespace, runningPod.Name, kubecontainer.DockerID(networkContainer.ID.ID)); err != nil { + message := fmt.Sprintf("Failed to teardown network for pod %q using network plugins %q: %v", runningPod.ID, dm.networkPlugin.Name(), err) + teardownNetworkResult.Fail(kubecontainer.ErrTeardownNetwork, message) + glog.Error(message) + } + } + killContainerResult := kubecontainer.NewSyncResult(kubecontainer.KillContainer, networkContainer.Name) + result.AddSyncResult(killContainerResult) + if err := dm.KillContainerInPod(networkContainer.ID, networkSpec, pod, "Need to kill pod."); err != nil { + killContainerResult.Fail(kubecontainer.ErrKillContainer, err.Error()) + glog.Errorf("Failed to delete container: %v; Skipping pod %q", err, runningPod.ID) + } + } + return +} + +// KillContainerInPod kills a container in the pod. It must be passed either a container ID or a container and pod, +// and will attempt to lookup the other information if missing. +func (dm *DockerManager) KillContainerInPod(containerID kubecontainer.ContainerID, container *api.Container, pod *api.Pod, message string) error { + switch { + case containerID.IsEmpty(): + // Locate the container. + pods, err := dm.GetPods(false) + if err != nil { + return err + } + targetPod := kubecontainer.Pods(pods).FindPod(kubecontainer.GetPodFullName(pod), pod.UID) + targetContainer := targetPod.FindContainerByName(container.Name) + if targetContainer == nil { + return fmt.Errorf("unable to find container %q in pod %q", container.Name, targetPod.Name) + } + containerID = targetContainer.ID + + case container == nil || pod == nil: + // Read information about the container from labels + inspect, err := dm.client.InspectContainer(containerID.ID) + if err != nil { + return err + } + storedPod, storedContainer, cerr := containerAndPodFromLabels(inspect) + if cerr != nil { + glog.Errorf("unable to access pod data from container: %v", err) + } + if container == nil { + container = storedContainer + } + if pod == nil { + pod = storedPod + } + } + return dm.killContainer(containerID, container, pod, message) +} + +// killContainer accepts a containerID and an optional container or pod containing shutdown policies. Invoke +// KillContainerInPod if information must be retrieved first. +func (dm *DockerManager) killContainer(containerID kubecontainer.ContainerID, container *api.Container, pod *api.Pod, reason string) error { + ID := containerID.ID + name := ID + if container != nil { + name = fmt.Sprintf("%s %s", name, container.Name) + } + if pod != nil { + name = fmt.Sprintf("%s %s/%s", name, pod.Namespace, pod.Name) + } + + gracePeriod := int64(minimumGracePeriodInSeconds) + if pod != nil { + switch { + case pod.DeletionGracePeriodSeconds != nil: + gracePeriod = *pod.DeletionGracePeriodSeconds + case pod.Spec.TerminationGracePeriodSeconds != nil: + gracePeriod = *pod.Spec.TerminationGracePeriodSeconds + } + } + glog.V(2).Infof("Killing container %q with %d second grace period", name, gracePeriod) + start := unversioned.Now() + + if pod != nil && container != nil && container.Lifecycle != nil && container.Lifecycle.PreStop != nil { + glog.V(4).Infof("Running preStop hook for container %q", name) + done := make(chan struct{}) + go func() { + defer close(done) + defer utilruntime.HandleCrash() + if err := dm.runner.Run(containerID, pod, container, container.Lifecycle.PreStop); err != nil { + glog.Errorf("preStop hook for container %q failed: %v", name, err) + } + }() + select { + case <-time.After(time.Duration(gracePeriod) * time.Second): + glog.V(2).Infof("preStop hook for container %q did not complete in %d seconds", name, gracePeriod) + case <-done: + glog.V(4).Infof("preStop hook for container %q completed", name) + } + gracePeriod -= int64(unversioned.Now().Sub(start.Time).Seconds()) + } + + // always give containers a minimal shutdown window to avoid unnecessary SIGKILLs + if gracePeriod < minimumGracePeriodInSeconds { + gracePeriod = minimumGracePeriodInSeconds + } + err := dm.client.StopContainer(ID, uint(gracePeriod)) + if err == nil { + glog.V(2).Infof("Container %q exited after %s", name, unversioned.Now().Sub(start.Time)) + } else { + glog.V(2).Infof("Container %q termination failed after %s: %v", name, unversioned.Now().Sub(start.Time), err) + } + ref, ok := dm.containerRefManager.GetRef(containerID) + if !ok { + glog.Warningf("No ref for pod '%q'", name) + } else { + message := fmt.Sprintf("Killing container with docker id %v", utilstrings.ShortenString(ID, 12)) + if reason != "" { + message = fmt.Sprint(message, ": ", reason) + } + dm.recorder.Event(ref, api.EventTypeNormal, kubecontainer.KillingContainer, message) + dm.containerRefManager.ClearRef(containerID) + } + return err +} + +var errNoPodOnContainer = fmt.Errorf("no pod information labels on Docker container") + +// containerAndPodFromLabels tries to load the appropriate container info off of a Docker container's labels +func containerAndPodFromLabels(inspect *docker.Container) (pod *api.Pod, container *api.Container, err error) { + if inspect == nil && inspect.Config == nil && inspect.Config.Labels == nil { + return nil, nil, errNoPodOnContainer + } + labels := inspect.Config.Labels + + // the pod data may not be set + if body, found := labels[kubernetesPodLabel]; found { + pod = &api.Pod{} + if err = runtime.DecodeInto(api.Codecs.UniversalDecoder(), []byte(body), pod); err == nil { + name := labels[kubernetesContainerNameLabel] + for ix := range pod.Spec.Containers { + if pod.Spec.Containers[ix].Name == name { + container = &pod.Spec.Containers[ix] + break + } + } + if container == nil { + err = fmt.Errorf("unable to find container %s in pod %v", name, pod) + } + } else { + pod = nil + } + } + + // attempt to find the default grace period if we didn't commit a pod, but set the generic metadata + // field (the one used by kill) + if pod == nil { + if period, ok := labels[kubernetesPodTerminationGracePeriodLabel]; ok { + if seconds, err := strconv.ParseInt(period, 10, 64); err == nil { + pod = &api.Pod{} + pod.DeletionGracePeriodSeconds = &seconds + } + } + } + + return +} + +func (dm *DockerManager) applyOOMScoreAdj(container *api.Container, containerInfo *docker.Container) error { + cgroupName, err := dm.procFs.GetFullContainerName(containerInfo.State.Pid) + if err != nil { + if err == os.ErrNotExist { + // Container exited. We cannot do anything about it. Ignore this error. + glog.V(2).Infof("Failed to apply OOM score adj on container %q with ID %q. Init process does not exist.", containerInfo.Name, containerInfo.ID) + return nil + } + return err + } + // Set OOM score of the container based on the priority of the container. + // Processes in lower-priority pods should be killed first if the system runs out of memory. + // The main pod infrastructure container is considered high priority, since if it is killed the + // whole pod will die. + // TODO: Cache this value. + var oomScoreAdj int + if containerInfo.Name == PodInfraContainerName { + oomScoreAdj = qos.PodInfraOOMAdj + } else { + oomScoreAdj = qos.GetContainerOOMScoreAdjust(container, int64(dm.machineInfo.MemoryCapacity)) + } + if err = dm.oomAdjuster.ApplyOOMScoreAdjContainer(cgroupName, oomScoreAdj, 5); err != nil { + if err == os.ErrNotExist { + // Container exited. We cannot do anything about it. Ignore this error. + glog.V(2).Infof("Failed to apply OOM score adj on container %q with ID %q. Init process does not exist.", containerInfo.Name, containerInfo.ID) + return nil + } + return err + } + return nil +} + +// Run a single container from a pod. Returns the docker container ID +// If do not need to pass labels, just pass nil. +func (dm *DockerManager) runContainerInPod(pod *api.Pod, container *api.Container, netMode, ipcMode, pidMode, podIP string, restartCount int) (kubecontainer.ContainerID, error) { + start := time.Now() + defer func() { + metrics.ContainerManagerLatency.WithLabelValues("runContainerInPod").Observe(metrics.SinceInMicroseconds(start)) + }() + + ref, err := kubecontainer.GenerateContainerRef(pod, container) + if err != nil { + glog.Errorf("Can't make a ref to pod %v, container %v: '%v'", pod.Name, container.Name, err) + } + + opts, err := dm.runtimeHelper.GenerateRunContainerOptions(pod, container, podIP) + if err != nil { + return kubecontainer.ContainerID{}, fmt.Errorf("GenerateRunContainerOptions: %v", err) + } + + utsMode := "" + if usesHostNetwork(pod) { + utsMode = namespaceModeHost + } + id, err := dm.runContainer(pod, container, opts, ref, netMode, ipcMode, utsMode, pidMode, restartCount) + if err != nil { + return kubecontainer.ContainerID{}, fmt.Errorf("runContainer: %v", err) + } + + // Remember this reference so we can report events about this container + if ref != nil { + dm.containerRefManager.SetRef(id, ref) + } + + if container.Lifecycle != nil && container.Lifecycle.PostStart != nil { + handlerErr := dm.runner.Run(id, pod, container, container.Lifecycle.PostStart) + if handlerErr != nil { + err := fmt.Errorf("PostStart handler: %v", handlerErr) + dm.KillContainerInPod(id, container, pod, err.Error()) + return kubecontainer.ContainerID{}, err + } + } + + // Create a symbolic link to the Docker container log file using a name which captures the + // full pod name, the container name and the Docker container ID. Cluster level logging will + // capture these symbolic filenames which can be used for search terms in Elasticsearch or for + // labels for Cloud Logging. + containerLogFile := path.Join(dm.dockerRoot, "containers", id.ID, fmt.Sprintf("%s-json.log", id.ID)) + symlinkFile := LogSymlink(dm.containerLogsDir, kubecontainer.GetPodFullName(pod), container.Name, id.ID) + if err = dm.os.Symlink(containerLogFile, symlinkFile); err != nil { + glog.Errorf("Failed to create symbolic link to the log file of pod %q container %q: %v", format.Pod(pod), container.Name, err) + } + + // Container information is used in adjusting OOM scores and adding ndots. + containerInfo, err := dm.client.InspectContainer(id.ID) + if err != nil { + return kubecontainer.ContainerID{}, fmt.Errorf("InspectContainer: %v", err) + } + // Ensure the PID actually exists, else we'll move ourselves. + if containerInfo.State.Pid == 0 { + return kubecontainer.ContainerID{}, fmt.Errorf("can't get init PID for container %q", id) + } + + if err := dm.applyOOMScoreAdj(container, containerInfo); err != nil { + return kubecontainer.ContainerID{}, fmt.Errorf("failed to apply oom-score-adj to container %q- %v", err, containerInfo.Name) + } + // The addNDotsOption call appends the ndots option to the resolv.conf file generated by docker. + // This resolv.conf file is shared by all containers of the same pod, and needs to be modified only once per pod. + // we modify it when the pause container is created since it is the first container created in the pod since it holds + // the networking namespace. + if container.Name == PodInfraContainerName && utsMode != namespaceModeHost { + err = addNDotsOption(containerInfo.ResolvConfPath) + if err != nil { + return kubecontainer.ContainerID{}, fmt.Errorf("addNDotsOption: %v", err) + } + } + + return id, err +} + +func addNDotsOption(resolvFilePath string) error { + if len(resolvFilePath) == 0 { + glog.Errorf("ResolvConfPath is empty.") + return nil + } + + if _, err := os.Stat(resolvFilePath); os.IsNotExist(err) { + return fmt.Errorf("ResolvConfPath %q does not exist", resolvFilePath) + } + + glog.V(4).Infof("DNS ResolvConfPath exists: %s. Will attempt to add ndots option: %s", resolvFilePath, ndotsDNSOption) + + if err := appendToFile(resolvFilePath, ndotsDNSOption); err != nil { + glog.Errorf("resolv.conf could not be updated: %v", err) + return err + } + return nil +} + +func appendToFile(filePath, stringToAppend string) error { + f, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + + _, err = f.WriteString(stringToAppend) + return err +} + +// createPodInfraContainer starts the pod infra container for a pod. Returns the docker container ID of the newly created container. +// If any error occurs in this function, it will return a brief error and a detailed error message. +func (dm *DockerManager) createPodInfraContainer(pod *api.Pod) (kubecontainer.DockerID, error, string) { + start := time.Now() + defer func() { + metrics.ContainerManagerLatency.WithLabelValues("createPodInfraContainer").Observe(metrics.SinceInMicroseconds(start)) + }() + // Use host networking if specified. + netNamespace := "" + var ports []api.ContainerPort + + if usesHostNetwork(pod) { + netNamespace = namespaceModeHost + } else if dm.networkPlugin.Name() == "cni" || dm.networkPlugin.Name() == "kubenet" { + netNamespace = "none" + } else { + // Docker only exports ports from the pod infra container. Let's + // collect all of the relevant ports and export them. + for _, container := range pod.Spec.Containers { + ports = append(ports, container.Ports...) + } + } + + container := &api.Container{ + Name: PodInfraContainerName, + Image: dm.podInfraContainerImage, + Ports: ports, + ImagePullPolicy: podInfraContainerImagePullPolicy, + Env: dm.podInfraContainerEnv, + } + + // No pod secrets for the infra container. + // The message isn't needed for the Infra container + if err, msg := dm.imagePuller.PullImage(pod, container, nil); err != nil { + return "", err, msg + } + + // Currently we don't care about restart count of infra container, just set it to 0. + id, err := dm.runContainerInPod(pod, container, netNamespace, getIPCMode(pod), getPidMode(pod), "", 0) + if err != nil { + return "", kubecontainer.ErrRunContainer, err.Error() + } + + return kubecontainer.DockerID(id.ID), nil, "" +} + +// Structure keeping information on changes that need to happen for a pod. The semantics is as follows: +// - startInfraContainer is true if new Infra Containers have to be started and old one (if running) killed. +// Additionally if it is true then containersToKeep have to be empty +// - infraContainerId have to be set if and only if startInfraContainer is false. It stores dockerID of running Infra Container +// - containersToStart keeps indices of Specs of containers that have to be started and reasons why containers will be started. +// - containersToKeep stores mapping from dockerIDs of running containers to indices of their Specs for containers that +// should be kept running. If startInfraContainer is false then it contains an entry for infraContainerId (mapped to -1). +// It shouldn't be the case where containersToStart is empty and containersToKeep contains only infraContainerId. In such case +// Infra Container should be killed, hence it's removed from this map. +// - all running containers which are NOT contained in containersToKeep should be killed. +type podContainerChangesSpec struct { + StartInfraContainer bool + InfraChanged bool + InfraContainerId kubecontainer.DockerID + ContainersToStart map[int]string + ContainersToKeep map[kubecontainer.DockerID]int +} + +func (dm *DockerManager) computePodContainerChanges(pod *api.Pod, podStatus *kubecontainer.PodStatus) (podContainerChangesSpec, error) { + start := time.Now() + defer func() { + metrics.ContainerManagerLatency.WithLabelValues("computePodContainerChanges").Observe(metrics.SinceInMicroseconds(start)) + }() + glog.V(5).Infof("Syncing Pod %q: %+v", format.Pod(pod), pod) + + containersToStart := make(map[int]string) + containersToKeep := make(map[kubecontainer.DockerID]int) + + var err error + var podInfraContainerID kubecontainer.DockerID + var changed bool + podInfraContainerStatus := podStatus.FindContainerStatusByName(PodInfraContainerName) + if podInfraContainerStatus != nil && podInfraContainerStatus.State == kubecontainer.ContainerStateRunning { + glog.V(4).Infof("Found pod infra container for %q", format.Pod(pod)) + changed, err = dm.podInfraContainerChanged(pod, podInfraContainerStatus) + if err != nil { + return podContainerChangesSpec{}, err + } + } + + createPodInfraContainer := true + if podInfraContainerStatus == nil || podInfraContainerStatus.State != kubecontainer.ContainerStateRunning { + glog.V(2).Infof("Need to restart pod infra container for %q because it is not found", format.Pod(pod)) + } else if changed { + glog.V(2).Infof("Need to restart pod infra container for %q because it is changed", format.Pod(pod)) + } else { + glog.V(4).Infof("Pod infra container looks good, keep it %q", format.Pod(pod)) + createPodInfraContainer = false + podInfraContainerID = kubecontainer.DockerID(podInfraContainerStatus.ID.ID) + containersToKeep[podInfraContainerID] = -1 + } + + for index, container := range pod.Spec.Containers { + expectedHash := kubecontainer.HashContainer(&container) + + containerStatus := podStatus.FindContainerStatusByName(container.Name) + if containerStatus == nil || containerStatus.State != kubecontainer.ContainerStateRunning { + if kubecontainer.ShouldContainerBeRestarted(&container, pod, podStatus) { + // If we are here it means that the container is dead and should be restarted, or never existed and should + // be created. We may be inserting this ID again if the container has changed and it has + // RestartPolicy::Always, but it's not a big deal. + message := fmt.Sprintf("Container %+v is dead, but RestartPolicy says that we should restart it.", container) + glog.V(3).Info(message) + containersToStart[index] = message + } + continue + } + + containerID := kubecontainer.DockerID(containerStatus.ID.ID) + hash := containerStatus.Hash + glog.V(3).Infof("pod %q container %q exists as %v", format.Pod(pod), container.Name, containerID) + + if createPodInfraContainer { + // createPodInfraContainer == true and Container exists + // If we're creating infra container everything will be killed anyway + // If RestartPolicy is Always or OnFailure we restart containers that were running before we + // killed them when restarting Infra Container. + if pod.Spec.RestartPolicy != api.RestartPolicyNever { + message := fmt.Sprintf("Infra Container is being recreated. %q will be restarted.", container.Name) + glog.V(1).Info(message) + containersToStart[index] = message + } + continue + } + + // At this point, the container is running and pod infra container is good. + // We will look for changes and check healthiness for the container. + containerChanged := hash != 0 && hash != expectedHash + if containerChanged { + message := fmt.Sprintf("pod %q container %q hash changed (%d vs %d), it will be killed and re-created.", format.Pod(pod), container.Name, hash, expectedHash) + glog.Info(message) + containersToStart[index] = message + continue + } + + liveness, found := dm.livenessManager.Get(containerStatus.ID) + if !found || liveness == proberesults.Success { + containersToKeep[containerID] = index + continue + } + if pod.Spec.RestartPolicy != api.RestartPolicyNever { + message := fmt.Sprintf("pod %q container %q is unhealthy, it will be killed and re-created.", format.Pod(pod), container.Name) + glog.Info(message) + containersToStart[index] = message + } + } + + // After the loop one of the following should be true: + // - createPodInfraContainer is true and containersToKeep is empty. + // (In fact, when createPodInfraContainer is false, containersToKeep will not be touched). + // - createPodInfraContainer is false and containersToKeep contains at least ID of Infra Container + + // If Infra container is the last running one, we don't want to keep it. + if !createPodInfraContainer && len(containersToStart) == 0 && len(containersToKeep) == 1 { + containersToKeep = make(map[kubecontainer.DockerID]int) + } + + return podContainerChangesSpec{ + StartInfraContainer: createPodInfraContainer, + InfraChanged: changed, + InfraContainerId: podInfraContainerID, + ContainersToStart: containersToStart, + ContainersToKeep: containersToKeep, + }, nil +} + +// Sync the running pod to match the specified desired pod. +func (dm *DockerManager) SyncPod(pod *api.Pod, _ api.PodStatus, podStatus *kubecontainer.PodStatus, pullSecrets []api.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult) { + start := time.Now() + defer func() { + metrics.ContainerManagerLatency.WithLabelValues("SyncPod").Observe(metrics.SinceInMicroseconds(start)) + }() + + containerChanges, err := dm.computePodContainerChanges(pod, podStatus) + if err != nil { + result.Fail(err) + return + } + glog.V(3).Infof("Got container changes for pod %q: %+v", format.Pod(pod), containerChanges) + + if containerChanges.InfraChanged { + ref, err := api.GetReference(pod) + if err != nil { + glog.Errorf("Couldn't make a ref to pod %q: '%v'", format.Pod(pod), err) + } + dm.recorder.Eventf(ref, api.EventTypeNormal, "InfraChanged", "Pod infrastructure changed, it will be killed and re-created.") + } + if containerChanges.StartInfraContainer || (len(containerChanges.ContainersToKeep) == 0 && len(containerChanges.ContainersToStart) == 0) { + if len(containerChanges.ContainersToKeep) == 0 && len(containerChanges.ContainersToStart) == 0 { + glog.V(4).Infof("Killing Infra Container for %q because all other containers are dead.", format.Pod(pod)) + } else { + glog.V(4).Infof("Killing Infra Container for %q, will start new one", format.Pod(pod)) + } + + // Killing phase: if we want to start new infra container, or nothing is running kill everything (including infra container) + // TODO(random-liu): We'll use pod status directly in the future + killResult := dm.killPodWithSyncResult(pod, kubecontainer.ConvertPodStatusToRunningPod(podStatus)) + result.AddPodSyncResult(killResult) + if killResult.Error() != nil { + return + } + } else { + // Otherwise kill any running containers in this pod which are not specified as ones to keep. + runningContainerStatues := podStatus.GetRunningContainerStatuses() + for _, containerStatus := range runningContainerStatues { + _, keep := containerChanges.ContainersToKeep[kubecontainer.DockerID(containerStatus.ID.ID)] + if !keep { + glog.V(3).Infof("Killing unwanted container %q(id=%q) for pod %q", containerStatus.Name, containerStatus.ID, format.Pod(pod)) + // attempt to find the appropriate container policy + var podContainer *api.Container + var killMessage string + for i, c := range pod.Spec.Containers { + if c.Name == containerStatus.Name { + podContainer = &pod.Spec.Containers[i] + killMessage = containerChanges.ContainersToStart[i] + break + } + } + killContainerResult := kubecontainer.NewSyncResult(kubecontainer.KillContainer, containerStatus.Name) + result.AddSyncResult(killContainerResult) + if err := dm.KillContainerInPod(containerStatus.ID, podContainer, pod, killMessage); err != nil { + killContainerResult.Fail(kubecontainer.ErrKillContainer, err.Error()) + glog.Errorf("Error killing container %q(id=%q) for pod %q: %v", containerStatus.Name, containerStatus.ID, format.Pod(pod), err) + return + } + } + } + } + + // We pass the value of the podIP down to runContainerInPod, which in turn + // passes it to various other functions, in order to facilitate + // functionality that requires this value (hosts file and downward API) + // and avoid races determining the pod IP in cases where a container + // requires restart but the podIP isn't in the status manager yet. + // + // We default to the IP in the passed-in pod status, and overwrite it if the + // infra container needs to be (re)started. + podIP := "" + if podStatus != nil { + podIP = podStatus.IP + } + + // If we should create infra container then we do it first. + podInfraContainerID := containerChanges.InfraContainerId + if containerChanges.StartInfraContainer && (len(containerChanges.ContainersToStart) > 0) { + glog.V(4).Infof("Creating pod infra container for %q", format.Pod(pod)) + startContainerResult := kubecontainer.NewSyncResult(kubecontainer.StartContainer, PodInfraContainerName) + result.AddSyncResult(startContainerResult) + var msg string + podInfraContainerID, err, msg = dm.createPodInfraContainer(pod) + if err != nil { + startContainerResult.Fail(err, msg) + glog.Errorf("Failed to create pod infra container: %v; Skipping pod %q", err, format.Pod(pod)) + return + } + + setupNetworkResult := kubecontainer.NewSyncResult(kubecontainer.SetupNetwork, kubecontainer.GetPodFullName(pod)) + result.AddSyncResult(setupNetworkResult) + if !usesHostNetwork(pod) { + // Call the networking plugin + err = dm.networkPlugin.SetUpPod(pod.Namespace, pod.Name, podInfraContainerID) + if err != nil { + // TODO: (random-liu) There shouldn't be "Skipping pod" in sync result message + message := fmt.Sprintf("Failed to setup network for pod %q using network plugins %q: %v; Skipping pod", format.Pod(pod), dm.networkPlugin.Name(), err) + setupNetworkResult.Fail(kubecontainer.ErrSetupNetwork, message) + glog.Error(message) + + // Delete infra container + killContainerResult := kubecontainer.NewSyncResult(kubecontainer.KillContainer, PodInfraContainerName) + result.AddSyncResult(killContainerResult) + if delErr := dm.KillContainerInPod(kubecontainer.ContainerID{ + ID: string(podInfraContainerID), + Type: "docker"}, nil, pod, message); delErr != nil { + killContainerResult.Fail(kubecontainer.ErrKillContainer, delErr.Error()) + glog.Warningf("Clear infra container failed for pod %q: %v", format.Pod(pod), delErr) + } + return + } + + // Setup the host interface unless the pod is on the host's network (FIXME: move to networkPlugin when ready) + var podInfraContainer *docker.Container + podInfraContainer, err = dm.client.InspectContainer(string(podInfraContainerID)) + if err != nil { + glog.Errorf("Failed to inspect pod infra container: %v; Skipping pod %q", err, format.Pod(pod)) + result.Fail(err) + return + } + + if dm.configureHairpinMode { + if err = hairpin.SetUpContainer(podInfraContainer.State.Pid, network.DefaultInterfaceName); err != nil { + glog.Warningf("Hairpin setup failed for pod %q: %v", format.Pod(pod), err) + } + } + + // Overwrite the podIP passed in the pod status, since we just started the infra container. + podIP = dm.determineContainerIP(pod.Name, pod.Namespace, podInfraContainer) + } + } + + // Start everything + for idx := range containerChanges.ContainersToStart { + container := &pod.Spec.Containers[idx] + startContainerResult := kubecontainer.NewSyncResult(kubecontainer.StartContainer, container.Name) + result.AddSyncResult(startContainerResult) + + // containerChanges.StartInfraContainer causes the containers to be restarted for config reasons + // ignore backoff + if !containerChanges.StartInfraContainer { + isInBackOff, err, msg := dm.doBackOff(pod, container, podStatus, backOff) + if isInBackOff { + startContainerResult.Fail(err, msg) + glog.V(4).Infof("Backing Off restarting container %+v in pod %v", container, format.Pod(pod)) + continue + } + } + glog.V(4).Infof("Creating container %+v in pod %v", container, format.Pod(pod)) + err, msg := dm.imagePuller.PullImage(pod, container, pullSecrets) + if err != nil { + startContainerResult.Fail(err, msg) + continue + } + + if container.SecurityContext != nil && container.SecurityContext.RunAsNonRoot != nil && *container.SecurityContext.RunAsNonRoot { + err := dm.verifyNonRoot(container) + if err != nil { + startContainerResult.Fail(kubecontainer.ErrVerifyNonRoot, err.Error()) + glog.Errorf("Error running pod %q container %q: %v", format.Pod(pod), container.Name, err) + continue + } + } + // For a new container, the RestartCount should be 0 + restartCount := 0 + containerStatus := podStatus.FindContainerStatusByName(container.Name) + if containerStatus != nil { + restartCount = containerStatus.RestartCount + 1 + } + + // TODO(dawnchen): Check RestartPolicy.DelaySeconds before restart a container + // Note: when configuring the pod's containers anything that can be configured by pointing + // to the namespace of the infra container should use namespaceMode. This includes things like the net namespace + // and IPC namespace. PID mode cannot point to another container right now. + // See createPodInfraContainer for infra container setup. + namespaceMode := fmt.Sprintf("container:%v", podInfraContainerID) + _, err = dm.runContainerInPod(pod, container, namespaceMode, namespaceMode, getPidMode(pod), podIP, restartCount) + if err != nil { + startContainerResult.Fail(kubecontainer.ErrRunContainer, err.Error()) + // TODO(bburns) : Perhaps blacklist a container after N failures? + glog.Errorf("Error running pod %q container %q: %v", format.Pod(pod), container.Name, err) + continue + } + // Successfully started the container; clear the entry in the failure + } + return +} + +// verifyNonRoot returns an error if the container or image will run as the root user. +func (dm *DockerManager) verifyNonRoot(container *api.Container) error { + if securitycontext.HasRunAsUser(container) { + if securitycontext.HasRootRunAsUser(container) { + return fmt.Errorf("container's runAsUser breaks non-root policy") + } + return nil + } + + imgRoot, err := dm.isImageRoot(container.Image) + if err != nil { + return fmt.Errorf("can't tell if image runs as root: %v", err) + } + if imgRoot { + return fmt.Errorf("container has no runAsUser and image will run as root") + } + + return nil +} + +// isImageRoot returns true if the user directive is not set on the image, the user is set to 0 +// or the user is set to root. If there is an error inspecting the image this method will return +// false and return the error. +func (dm *DockerManager) isImageRoot(image string) (bool, error) { + img, err := dm.client.InspectImage(image) + if err != nil { + return false, err + } + if img == nil || img.Config == nil { + return false, fmt.Errorf("unable to inspect image %s, nil Config", image) + } + + user := getUidFromUser(img.Config.User) + // if no user is defined container will run as root + if user == "" { + return true, nil + } + // do not allow non-numeric user directives + uid, err := strconv.Atoi(user) + if err != nil { + return false, fmt.Errorf("non-numeric user (%s) is not allowed", user) + } + // user is numeric, check for 0 + return uid == 0, nil +} + +// getUidFromUser splits the uid out of a uid:gid string. +func getUidFromUser(id string) string { + if id == "" { + return id + } + // split instances where the id may contain uid:gid + if strings.Contains(id, ":") { + return strings.Split(id, ":")[0] + } + // no gid, just return the id + return id +} + +// If all instances of a container are garbage collected, doBackOff will also return false, which means the container may be restarted before the +// backoff deadline. However, because that won't cause error and the chance is really slim, we can just ignore it for now. +// If a container is still in backoff, the function will return a brief backoff error and a detailed error message. +func (dm *DockerManager) doBackOff(pod *api.Pod, container *api.Container, podStatus *kubecontainer.PodStatus, backOff *flowcontrol.Backoff) (bool, error, string) { + var cStatus *kubecontainer.ContainerStatus + // Use the finished time of the latest exited container as the start point to calculate whether to do back-off. + // TODO(random-liu): Better define backoff start point; add unit and e2e test after we finalize this. (See github issue #22240) + for _, c := range podStatus.ContainerStatuses { + if c.Name == container.Name && c.State == kubecontainer.ContainerStateExited { + cStatus = c + break + } + } + if cStatus != nil { + ts := cStatus.FinishedAt + // found a container that requires backoff + dockerName := KubeletContainerName{ + PodFullName: kubecontainer.GetPodFullName(pod), + PodUID: pod.UID, + ContainerName: container.Name, + } + stableName, _, _ := BuildDockerName(dockerName, container) + if backOff.IsInBackOffSince(stableName, ts) { + if ref, err := kubecontainer.GenerateContainerRef(pod, container); err == nil { + dm.recorder.Eventf(ref, api.EventTypeWarning, kubecontainer.BackOffStartContainer, "Back-off restarting failed docker container") + } + err := fmt.Errorf("Back-off %s restarting failed container=%s pod=%s", backOff.Get(stableName), container.Name, format.Pod(pod)) + glog.Infof("%s", err.Error()) + return true, kubecontainer.ErrCrashLoopBackOff, err.Error() + } + backOff.Next(stableName, ts) + } + return false, nil, "" +} + +// getPidMode returns the pid mode to use on the docker container based on pod.Spec.HostPID. +func getPidMode(pod *api.Pod) string { + pidMode := "" + if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostPID { + pidMode = namespaceModeHost + } + return pidMode +} + +// getIPCMode returns the ipc mode to use on the docker container based on pod.Spec.HostIPC. +func getIPCMode(pod *api.Pod) string { + ipcMode := "" + if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostIPC { + ipcMode = namespaceModeHost + } + return ipcMode +} + +// GetNetNS returns the network namespace path for the given container +func (dm *DockerManager) GetNetNS(containerID kubecontainer.ContainerID) (string, error) { + inspectResult, err := dm.client.InspectContainer(containerID.ID) + if err != nil { + glog.Errorf("Error inspecting container: '%v'", err) + return "", err + } + netnsPath := fmt.Sprintf(DockerNetnsFmt, inspectResult.State.Pid) + return netnsPath, nil +} + +// Garbage collection of dead containers +func (dm *DockerManager) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy) error { + return dm.containerGC.GarbageCollect(gcPolicy) +} + +func (dm *DockerManager) GetPodStatus(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) { + podStatus := &kubecontainer.PodStatus{ID: uid, Name: name, Namespace: namespace} + // Now we retain restart count of container as a docker label. Each time a container + // restarts, pod will read the restart count from the registered dead container, increment + // it to get the new restart count, and then add a label with the new restart count on + // the newly started container. + // However, there are some limitations of this method: + // 1. When all dead containers were garbage collected, the container status could + // not get the historical value and would be *inaccurate*. Fortunately, the chance + // is really slim. + // 2. When working with old version containers which have no restart count label, + // we can only assume their restart count is 0. + // Anyhow, we only promised "best-effort" restart count reporting, we can just ignore + // these limitations now. + var containerStatuses []*kubecontainer.ContainerStatus + // We have added labels like pod name and pod namespace, it seems that we can do filtered list here. + // However, there may be some old containers without these labels, so at least now we can't do that. + // TODO(random-liu): Do only one list and pass in the list result in the future + // TODO(random-liu): Add filter when we are sure that all the containers have the labels + containers, err := dm.client.ListContainers(docker.ListContainersOptions{All: true}) + if err != nil { + return podStatus, err + } + // Loop through list of running and exited docker containers to construct + // the statuses. We assume docker returns a list of containers sorted in + // reverse by time. + // TODO: optimization: set maximum number of containers per container name to examine. + for _, c := range containers { + if len(c.Names) == 0 { + continue + } + dockerName, _, err := ParseDockerName(c.Names[0]) + if err != nil { + continue + } + if dockerName.PodUID != uid { + continue + } + result, ip, err := dm.inspectContainer(c.ID, name, namespace) + if err != nil { + if _, ok := err.(*docker.NoSuchContainer); ok { + // https://github.com/kubernetes/kubernetes/issues/22541 + // Sometimes when docker's state is corrupt, a container can be listed + // but couldn't be inspected. We fake a status for this container so + // that we can still return a status for the pod to sync. + result = &kubecontainer.ContainerStatus{ + ID: kubecontainer.DockerID(c.ID).ContainerID(), + Name: dockerName.ContainerName, + State: kubecontainer.ContainerStateUnknown, + } + glog.Errorf("Unable to inspect container %q: %v", c.ID, err) + } else { + return podStatus, err + } + } + containerStatuses = append(containerStatuses, result) + if ip != "" { + podStatus.IP = ip + } + } + + podStatus.ContainerStatuses = containerStatuses + return podStatus, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/manager_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/manager_test.go new file mode 100644 index 000000000..089e60564 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/dockertools/manager_test.go @@ -0,0 +1,1947 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "fmt" + "io/ioutil" + "net/http" + "os" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "testing" + "time" + + docker "github.com/fsouza/go-dockerclient" + cadvisorapi "github.com/google/cadvisor/info/v1" + "github.com/stretchr/testify/assert" + "k8s.io/kubernetes/cmd/kubelet/app/options" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/client/record" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" + "k8s.io/kubernetes/pkg/kubelet/network" + nettest "k8s.io/kubernetes/pkg/kubelet/network/testing" + proberesults "k8s.io/kubernetes/pkg/kubelet/prober/results" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util" + uexec "k8s.io/kubernetes/pkg/util/exec" + "k8s.io/kubernetes/pkg/util/flowcontrol" + "k8s.io/kubernetes/pkg/util/intstr" + "k8s.io/kubernetes/pkg/util/sets" +) + +type fakeHTTP struct { + url string + err error +} + +func (f *fakeHTTP) Get(url string) (*http.Response, error) { + f.url = url + return nil, f.err +} + +// fakeRuntimeHelper implementes kubecontainer.RuntimeHelper inter +// faces for testing purposes. +type fakeRuntimeHelper struct{} + +var _ kubecontainer.RuntimeHelper = &fakeRuntimeHelper{} + +var testPodContainerDir string + +func (f *fakeRuntimeHelper) GenerateRunContainerOptions(pod *api.Pod, container *api.Container, podIP string) (*kubecontainer.RunContainerOptions, error) { + var opts kubecontainer.RunContainerOptions + var err error + if len(container.TerminationMessagePath) != 0 { + testPodContainerDir, err = ioutil.TempDir("", "fooPodContainerDir") + if err != nil { + return nil, err + } + opts.PodContainerDir = testPodContainerDir + } + return &opts, nil +} + +func (f *fakeRuntimeHelper) GetClusterDNS(pod *api.Pod) ([]string, []string, error) { + return nil, nil, fmt.Errorf("not implemented") +} + +// This is not used by docker runtime. +func (f *fakeRuntimeHelper) GeneratePodHostNameAndDomain(pod *api.Pod) (string, string) { + return "", "" +} + +func newTestDockerManagerWithHTTPClientWithVersion(fakeHTTPClient *fakeHTTP, version, apiVersion string) (*DockerManager, *FakeDockerClient) { + fakeDocker := NewFakeDockerClientWithVersion(version, apiVersion) + fakeRecorder := &record.FakeRecorder{} + containerRefManager := kubecontainer.NewRefManager() + networkPlugin, _ := network.InitNetworkPlugin([]network.NetworkPlugin{}, "", nettest.NewFakeHost(nil)) + dockerManager := NewFakeDockerManager( + fakeDocker, + fakeRecorder, + proberesults.NewManager(), + containerRefManager, + &cadvisorapi.MachineInfo{}, + options.GetDefaultPodInfraContainerImage(), + 0, 0, "", + containertest.FakeOS{}, + networkPlugin, + &fakeRuntimeHelper{}, + fakeHTTPClient, + flowcontrol.NewBackOff(time.Second, 300*time.Second)) + + return dockerManager, fakeDocker +} + +func newTestDockerManagerWithHTTPClient(fakeHTTPClient *fakeHTTP) (*DockerManager, *FakeDockerClient) { + return newTestDockerManagerWithHTTPClientWithVersion(fakeHTTPClient, "1.8.1", "1.20") +} + +func newTestDockerManager() (*DockerManager, *FakeDockerClient) { + return newTestDockerManagerWithHTTPClient(&fakeHTTP{}) +} + +func matchString(t *testing.T, pattern, str string) bool { + match, err := regexp.MatchString(pattern, str) + if err != nil { + t.Logf("unexpected error: %v", err) + } + return match +} + +func TestNewDockerVersion(t *testing.T) { + cases := []struct { + value string + out string + err bool + }{ + {value: "1", err: true}, + {value: "1.8", err: true}, + {value: "1.8.1", out: "1.8.1"}, + {value: "1.8.1.fc21", out: "1.8.1-fc21"}, + {value: "1.8.1.fc21.other", out: "1.8.1-fc21.other"}, + {value: "1.8.1-fc21.other", out: "1.8.1-fc21.other"}, + {value: "1.8.1-beta.12", out: "1.8.1-beta.12"}, + } + for _, test := range cases { + v, err := newDockerVersion(test.value) + switch { + case err != nil && test.err: + continue + case (err != nil) != test.err: + t.Errorf("error for %q: expected %t, got %v", test.value, test.err, err) + continue + } + if v.String() != test.out { + t.Errorf("unexpected parsed version %q for %q", v, test.value) + } + } +} + +func TestSetEntrypointAndCommand(t *testing.T) { + cases := []struct { + name string + container *api.Container + envs []kubecontainer.EnvVar + expected *docker.CreateContainerOptions + }{ + { + name: "none", + container: &api.Container{}, + expected: &docker.CreateContainerOptions{ + Config: &docker.Config{}, + }, + }, + { + name: "command", + container: &api.Container{ + Command: []string{"foo", "bar"}, + }, + expected: &docker.CreateContainerOptions{ + Config: &docker.Config{ + Entrypoint: []string{"foo", "bar"}, + }, + }, + }, + { + name: "command expanded", + container: &api.Container{ + Command: []string{"foo", "$(VAR_TEST)", "$(VAR_TEST2)"}, + }, + envs: []kubecontainer.EnvVar{ + { + Name: "VAR_TEST", + Value: "zoo", + }, + { + Name: "VAR_TEST2", + Value: "boo", + }, + }, + expected: &docker.CreateContainerOptions{ + Config: &docker.Config{ + Entrypoint: []string{"foo", "zoo", "boo"}, + }, + }, + }, + { + name: "args", + container: &api.Container{ + Args: []string{"foo", "bar"}, + }, + expected: &docker.CreateContainerOptions{ + Config: &docker.Config{ + Cmd: []string{"foo", "bar"}, + }, + }, + }, + { + name: "args expanded", + container: &api.Container{ + Args: []string{"zap", "$(VAR_TEST)", "$(VAR_TEST2)"}, + }, + envs: []kubecontainer.EnvVar{ + { + Name: "VAR_TEST", + Value: "hap", + }, + { + Name: "VAR_TEST2", + Value: "trap", + }, + }, + expected: &docker.CreateContainerOptions{ + Config: &docker.Config{ + Cmd: []string{"zap", "hap", "trap"}, + }, + }, + }, + { + name: "both", + container: &api.Container{ + Command: []string{"foo"}, + Args: []string{"bar", "baz"}, + }, + expected: &docker.CreateContainerOptions{ + Config: &docker.Config{ + Entrypoint: []string{"foo"}, + Cmd: []string{"bar", "baz"}, + }, + }, + }, + { + name: "both expanded", + container: &api.Container{ + Command: []string{"$(VAR_TEST2)--$(VAR_TEST)", "foo", "$(VAR_TEST3)"}, + Args: []string{"foo", "$(VAR_TEST)", "$(VAR_TEST2)"}, + }, + envs: []kubecontainer.EnvVar{ + { + Name: "VAR_TEST", + Value: "zoo", + }, + { + Name: "VAR_TEST2", + Value: "boo", + }, + { + Name: "VAR_TEST3", + Value: "roo", + }, + }, + expected: &docker.CreateContainerOptions{ + Config: &docker.Config{ + Entrypoint: []string{"boo--zoo", "foo", "roo"}, + Cmd: []string{"foo", "zoo", "boo"}, + }, + }, + }, + } + + for _, tc := range cases { + opts := &kubecontainer.RunContainerOptions{ + Envs: tc.envs, + } + + actualOpts := &docker.CreateContainerOptions{ + Config: &docker.Config{}, + } + setEntrypointAndCommand(tc.container, opts, actualOpts) + + if e, a := tc.expected.Config.Entrypoint, actualOpts.Config.Entrypoint; !api.Semantic.DeepEqual(e, a) { + t.Errorf("%v: unexpected entrypoint: expected %v, got %v", tc.name, e, a) + } + if e, a := tc.expected.Config.Cmd, actualOpts.Config.Cmd; !api.Semantic.DeepEqual(e, a) { + t.Errorf("%v: unexpected command: expected %v, got %v", tc.name, e, a) + } + } +} + +// verifyPods returns true if the two pod slices are equal. +func verifyPods(a, b []*kubecontainer.Pod) bool { + if len(a) != len(b) { + return false + } + + // Sort the containers within a pod. + for i := range a { + sort.Sort(containersByID(a[i].Containers)) + } + for i := range b { + sort.Sort(containersByID(b[i].Containers)) + } + + // Sort the pods by UID. + sort.Sort(podsByID(a)) + sort.Sort(podsByID(b)) + + return reflect.DeepEqual(a, b) +} + +func TestGetPods(t *testing.T) { + manager, fakeDocker := newTestDockerManager() + dockerContainers := []*docker.Container{ + { + ID: "1111", + Name: "/k8s_foo_qux_new_1234_42", + }, + { + ID: "2222", + Name: "/k8s_bar_qux_new_1234_42", + }, + { + ID: "3333", + Name: "/k8s_bar_jlk_wen_5678_42", + }, + } + + // Convert the docker containers. This does not affect the test coverage + // because the conversion is tested separately in convert_test.go + containers := make([]*kubecontainer.Container, len(dockerContainers)) + for i := range containers { + c, err := toRuntimeContainer(&docker.APIContainers{ + ID: dockerContainers[i].ID, + Names: []string{dockerContainers[i].Name}, + }) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + containers[i] = c + } + + expected := []*kubecontainer.Pod{ + { + ID: types.UID("1234"), + Name: "qux", + Namespace: "new", + Containers: []*kubecontainer.Container{containers[0], containers[1]}, + }, + { + ID: types.UID("5678"), + Name: "jlk", + Namespace: "wen", + Containers: []*kubecontainer.Container{containers[2]}, + }, + } + + fakeDocker.SetFakeRunningContainers(dockerContainers) + actual, err := manager.GetPods(false) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + if !verifyPods(expected, actual) { + t.Errorf("expected %#v, got %#v", expected, actual) + } +} + +func TestListImages(t *testing.T) { + manager, fakeDocker := newTestDockerManager() + dockerImages := []docker.APIImages{{ID: "1111"}, {ID: "2222"}, {ID: "3333"}} + expected := sets.NewString([]string{"1111", "2222", "3333"}...) + + fakeDocker.Images = dockerImages + actualImages, err := manager.ListImages() + if err != nil { + t.Fatalf("unexpected error %v", err) + } + actual := sets.NewString() + for _, i := range actualImages { + actual.Insert(i.ID) + } + // We can compare the two sets directly because util.StringSet.List() + // returns a "sorted" list. + if !reflect.DeepEqual(expected.List(), actual.List()) { + t.Errorf("expected %#v, got %#v", expected.List(), actual.List()) + } +} + +func apiContainerToContainer(c docker.APIContainers) kubecontainer.Container { + dockerName, hash, err := ParseDockerName(c.Names[0]) + if err != nil { + return kubecontainer.Container{} + } + return kubecontainer.Container{ + ID: kubecontainer.ContainerID{Type: "docker", ID: c.ID}, + Name: dockerName.ContainerName, + Hash: hash, + } +} + +func dockerContainersToPod(containers []*docker.APIContainers) kubecontainer.Pod { + var pod kubecontainer.Pod + for _, c := range containers { + dockerName, hash, err := ParseDockerName(c.Names[0]) + if err != nil { + continue + } + pod.Containers = append(pod.Containers, &kubecontainer.Container{ + ID: kubecontainer.ContainerID{Type: "docker", ID: c.ID}, + Name: dockerName.ContainerName, + Hash: hash, + Image: c.Image, + }) + // TODO(yifan): Only one evaluation is enough. + pod.ID = dockerName.PodUID + name, namespace, _ := kubecontainer.ParsePodFullName(dockerName.PodFullName) + pod.Name = name + pod.Namespace = namespace + } + return pod +} + +func TestKillContainerInPod(t *testing.T) { + manager, fakeDocker := newTestDockerManager() + + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "qux", + Namespace: "new", + }, + Spec: api.PodSpec{Containers: []api.Container{{Name: "foo"}, {Name: "bar"}}}, + } + containers := []*docker.Container{ + { + ID: "1111", + Name: "/k8s_foo_qux_new_1234_42", + }, + { + ID: "2222", + Name: "/k8s_bar_qux_new_1234_42", + }, + } + containerToKill := containers[0] + containerToSpare := containers[1] + + fakeDocker.SetFakeRunningContainers(containers) + + if err := manager.KillContainerInPod(kubecontainer.ContainerID{}, &pod.Spec.Containers[0], pod, "test kill container in pod."); err != nil { + t.Errorf("unexpected error: %v", err) + } + // Assert the container has been stopped. + if err := fakeDocker.AssertStopped([]string{containerToKill.ID}); err != nil { + t.Errorf("container was not stopped correctly: %v", err) + } + // Assert the container has been spared. + if err := fakeDocker.AssertStopped([]string{containerToSpare.ID}); err == nil { + t.Errorf("container unexpectedly stopped: %v", containerToSpare.ID) + } +} + +func TestKillContainerInPodWithPreStop(t *testing.T) { + manager, fakeDocker := newTestDockerManager() + fakeDocker.ExecInspect = &docker.ExecInspect{ + Running: false, + ExitCode: 0, + } + expectedCmd := []string{"foo.sh", "bar"} + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "qux", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "foo", + Lifecycle: &api.Lifecycle{ + PreStop: &api.Handler{ + Exec: &api.ExecAction{ + Command: expectedCmd, + }, + }, + }, + }, + {Name: "bar"}}}, + } + podString, err := runtime.Encode(testapi.Default.Codec(), pod) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + containers := []*docker.Container{ + { + ID: "1111", + Name: "/k8s_foo_qux_new_1234_42", + Config: &docker.Config{ + Labels: map[string]string{ + kubernetesPodLabel: string(podString), + kubernetesContainerNameLabel: "foo", + }, + }, + }, + { + ID: "2222", + Name: "/k8s_bar_qux_new_1234_42", + }, + } + containerToKill := containers[0] + fakeDocker.SetFakeRunningContainers(containers) + + if err := manager.KillContainerInPod(kubecontainer.ContainerID{}, &pod.Spec.Containers[0], pod, "test kill container with preStop."); err != nil { + t.Errorf("unexpected error: %v", err) + } + // Assert the container has been stopped. + if err := fakeDocker.AssertStopped([]string{containerToKill.ID}); err != nil { + t.Errorf("container was not stopped correctly: %v", err) + } + verifyCalls(t, fakeDocker, []string{"list", "create_exec", "start_exec", "stop"}) + if !reflect.DeepEqual(expectedCmd, fakeDocker.execCmd) { + t.Errorf("expected: %v, got %v", expectedCmd, fakeDocker.execCmd) + } +} + +func TestKillContainerInPodWithError(t *testing.T) { + manager, fakeDocker := newTestDockerManager() + + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "qux", + Namespace: "new", + }, + Spec: api.PodSpec{Containers: []api.Container{{Name: "foo"}, {Name: "bar"}}}, + } + containers := []*docker.Container{ + { + ID: "1111", + Name: "/k8s_foo_qux_new_1234_42", + }, + { + ID: "2222", + Name: "/k8s_bar_qux_new_1234_42", + }, + } + fakeDocker.SetFakeRunningContainers(containers) + fakeDocker.InjectError("stop", fmt.Errorf("sample error")) + + if err := manager.KillContainerInPod(kubecontainer.ContainerID{}, &pod.Spec.Containers[0], pod, "test kill container with error."); err == nil { + t.Errorf("expected error, found nil") + } +} + +func TestIsAExitError(t *testing.T) { + var err error + err = &dockerExitError{nil} + _, ok := err.(uexec.ExitError) + if !ok { + t.Error("couldn't cast dockerExitError to exec.ExitError") + } +} + +func generatePodInfraContainerHash(pod *api.Pod) uint64 { + var ports []api.ContainerPort + if pod.Spec.SecurityContext == nil || !pod.Spec.SecurityContext.HostNetwork { + for _, container := range pod.Spec.Containers { + ports = append(ports, container.Ports...) + } + } + + container := &api.Container{ + Name: PodInfraContainerName, + Image: options.GetDefaultPodInfraContainerImage(), + Ports: ports, + ImagePullPolicy: podInfraContainerImagePullPolicy, + } + return kubecontainer.HashContainer(container) +} + +// runSyncPod is a helper function to retrieve the running pods from the fake +// docker client and runs SyncPod for the given pod. +func runSyncPod(t *testing.T, dm *DockerManager, fakeDocker *FakeDockerClient, pod *api.Pod, backOff *flowcontrol.Backoff, expectErr bool) kubecontainer.PodSyncResult { + podStatus, err := dm.GetPodStatus(pod.UID, pod.Name, pod.Namespace) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + fakeDocker.ClearCalls() + if backOff == nil { + backOff = flowcontrol.NewBackOff(time.Second, time.Minute) + } + // api.PodStatus is not used in SyncPod now, pass in an empty one. + result := dm.SyncPod(pod, api.PodStatus{}, podStatus, []api.Secret{}, backOff) + err = result.Error() + if err != nil && !expectErr { + t.Errorf("unexpected error: %v", err) + } else if err == nil && expectErr { + t.Errorf("expected error didn't occur") + } + return result +} + +func TestSyncPodCreateNetAndContainer(t *testing.T) { + dm, fakeDocker := newTestDockerManager() + dm.podInfraContainerImage = "pod_infra_image" + + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "bar"}, + }, + }, + } + + runSyncPod(t, dm, fakeDocker, pod, nil, false) + verifyCalls(t, fakeDocker, []string{ + // Create pod infra container. + "create", "start", "inspect_container", "inspect_container", + // Create container. + "create", "start", "inspect_container", + }) + fakeDocker.Lock() + + found := false + for _, c := range fakeDocker.ContainerList { + if c.Image == "pod_infra_image" && strings.HasPrefix(c.Names[0], "/k8s_POD") { + found = true + } + } + if !found { + t.Errorf("Custom pod infra container not found: %v", fakeDocker.ContainerList) + } + + if len(fakeDocker.Created) != 2 || + !matchString(t, "/k8s_POD\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) || + !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[1]) { + t.Errorf("unexpected containers created %v", fakeDocker.Created) + } + fakeDocker.Unlock() +} + +func TestSyncPodCreatesNetAndContainerPullsImage(t *testing.T) { + dm, fakeDocker := newTestDockerManager() + dm.podInfraContainerImage = "pod_infra_image" + puller := dm.dockerPuller.(*FakeDockerPuller) + puller.HasImages = []string{} + dm.podInfraContainerImage = "pod_infra_image" + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "bar", Image: "something", ImagePullPolicy: "IfNotPresent"}, + }, + }, + } + + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + verifyCalls(t, fakeDocker, []string{ + // Create pod infra container. + "create", "start", "inspect_container", "inspect_container", + // Create container. + "create", "start", "inspect_container", + }) + + fakeDocker.Lock() + + if !reflect.DeepEqual(puller.ImagesPulled, []string{"pod_infra_image", "something"}) { + t.Errorf("unexpected pulled containers: %v", puller.ImagesPulled) + } + + if len(fakeDocker.Created) != 2 || + !matchString(t, "/k8s_POD\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) || + !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[1]) { + t.Errorf("unexpected containers created %v", fakeDocker.Created) + } + fakeDocker.Unlock() +} + +func TestSyncPodWithPodInfraCreatesContainer(t *testing.T) { + dm, fakeDocker := newTestDockerManager() + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "bar"}, + }, + }, + } + + fakeDocker.SetFakeRunningContainers([]*docker.Container{{ + ID: "9876", + // Pod infra container. + Name: "/k8s_POD." + strconv.FormatUint(generatePodInfraContainerHash(pod), 16) + "_foo_new_12345678_0", + }}) + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + verifyCalls(t, fakeDocker, []string{ + // Create container. + "create", "start", "inspect_container", + }) + + fakeDocker.Lock() + if len(fakeDocker.Created) != 1 || + !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) { + t.Errorf("unexpected containers created %v", fakeDocker.Created) + } + fakeDocker.Unlock() +} + +func TestSyncPodDeletesWithNoPodInfraContainer(t *testing.T) { + dm, fakeDocker := newTestDockerManager() + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo1", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "bar1"}, + }, + }, + } + fakeDocker.SetFakeRunningContainers([]*docker.Container{{ + ID: "1234", + Name: "/k8s_bar1_foo1_new_12345678_0", + }}) + + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + verifyCalls(t, fakeDocker, []string{ + // Kill the container since pod infra container is not running. + "stop", + // Create pod infra container. + "create", "start", "inspect_container", "inspect_container", + // Create container. + "create", "start", "inspect_container", + }) + + // A map iteration is used to delete containers, so must not depend on + // order here. + expectedToStop := map[string]bool{ + "1234": true, + } + fakeDocker.Lock() + if len(fakeDocker.Stopped) != 1 || !expectedToStop[fakeDocker.Stopped[0]] { + t.Errorf("Wrong containers were stopped: %v", fakeDocker.Stopped) + } + fakeDocker.Unlock() +} + +func TestSyncPodDeletesDuplicate(t *testing.T) { + dm, fakeDocker := newTestDockerManager() + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "bar", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "foo"}, + }, + }, + } + + fakeDocker.SetFakeRunningContainers([]*docker.Container{ + { + ID: "1234", + Name: "/k8s_foo_bar_new_12345678_1111", + }, + { + ID: "9876", + Name: "/k8s_POD." + strconv.FormatUint(generatePodInfraContainerHash(pod), 16) + "_bar_new_12345678_2222", + }, + { + ID: "4567", + Name: "/k8s_foo_bar_new_12345678_3333", + }}) + + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + verifyCalls(t, fakeDocker, []string{ + // Kill the duplicated container. + "stop", + }) + // Expect one of the duplicates to be killed. + if len(fakeDocker.Stopped) != 1 || (fakeDocker.Stopped[0] != "1234" && fakeDocker.Stopped[0] != "4567") { + t.Errorf("Wrong containers were stopped: %v", fakeDocker.Stopped) + } +} + +func TestSyncPodBadHash(t *testing.T) { + dm, fakeDocker := newTestDockerManager() + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "bar"}, + }, + }, + } + + fakeDocker.SetFakeRunningContainers([]*docker.Container{ + { + ID: "1234", + Name: "/k8s_bar.1234_foo_new_12345678_42", + }, + { + ID: "9876", + Name: "/k8s_POD." + strconv.FormatUint(generatePodInfraContainerHash(pod), 16) + "_foo_new_12345678_42", + }}) + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + verifyCalls(t, fakeDocker, []string{ + // Kill and restart the bad hash container. + "stop", "create", "start", "inspect_container", + }) + + if err := fakeDocker.AssertStopped([]string{"1234"}); err != nil { + t.Errorf("%v", err) + } +} + +func TestSyncPodsUnhealthy(t *testing.T) { + const ( + unhealthyContainerID = "1234" + infraContainerID = "9876" + ) + dm, fakeDocker := newTestDockerManager() + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Name: "unhealthy"}}, + }, + } + + fakeDocker.SetFakeRunningContainers([]*docker.Container{ + { + ID: unhealthyContainerID, + Name: "/k8s_unhealthy_foo_new_12345678_42", + }, + { + ID: infraContainerID, + Name: "/k8s_POD." + strconv.FormatUint(generatePodInfraContainerHash(pod), 16) + "_foo_new_12345678_42", + }}) + dm.livenessManager.Set(kubecontainer.DockerID(unhealthyContainerID).ContainerID(), proberesults.Failure, pod) + + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + verifyCalls(t, fakeDocker, []string{ + // Kill the unhealthy container. + "stop", + // Restart the unhealthy container. + "create", "start", "inspect_container", + }) + + if err := fakeDocker.AssertStopped([]string{unhealthyContainerID}); err != nil { + t.Errorf("%v", err) + } +} + +func TestSyncPodsDoesNothing(t *testing.T) { + dm, fakeDocker := newTestDockerManager() + container := api.Container{Name: "bar"} + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + container, + }, + }, + } + fakeDocker.SetFakeRunningContainers([]*docker.Container{ + { + ID: "1234", + Name: "/k8s_bar." + strconv.FormatUint(kubecontainer.HashContainer(&container), 16) + "_foo_new_12345678_0", + }, + { + ID: "9876", + Name: "/k8s_POD." + strconv.FormatUint(generatePodInfraContainerHash(pod), 16) + "_foo_new_12345678_0", + }}) + + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + verifyCalls(t, fakeDocker, []string{}) +} + +func TestSyncPodWithRestartPolicy(t *testing.T) { + dm, fakeDocker := newTestDockerManager() + containers := []api.Container{ + {Name: "succeeded"}, + {Name: "failed"}, + } + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: containers, + }, + } + dockerContainers := []*docker.Container{ + { + ID: "9876", + Name: "/k8s_POD." + strconv.FormatUint(generatePodInfraContainerHash(pod), 16) + "_foo_new_12345678_0", + Config: &docker.Config{}, + State: docker.State{ + StartedAt: time.Now(), + Running: true, + }, + }, + { + ID: "1234", + Name: "/k8s_succeeded." + strconv.FormatUint(kubecontainer.HashContainer(&containers[0]), 16) + "_foo_new_12345678_0", + Config: &docker.Config{}, + State: docker.State{ + ExitCode: 0, + StartedAt: time.Now(), + FinishedAt: time.Now(), + }, + }, + { + ID: "5678", + Name: "/k8s_failed." + strconv.FormatUint(kubecontainer.HashContainer(&containers[1]), 16) + "_foo_new_12345678_0", + Config: &docker.Config{}, + State: docker.State{ + ExitCode: 42, + StartedAt: time.Now(), + FinishedAt: time.Now(), + }, + }} + + tests := []struct { + policy api.RestartPolicy + calls []string + created []string + stopped []string + }{ + { + api.RestartPolicyAlways, + []string{ + // Restart both containers. + "create", "start", "inspect_container", "create", "start", "inspect_container", + }, + []string{"succeeded", "failed"}, + []string{}, + }, + { + api.RestartPolicyOnFailure, + []string{ + // Restart the failed container. + "create", "start", "inspect_container", + }, + []string{"failed"}, + []string{}, + }, + { + api.RestartPolicyNever, + []string{ + // Check the pod infra container. + "inspect_container", "inspect_container", + // Stop the last pod infra container. + "stop", + }, + []string{}, + []string{"9876"}, + }, + } + + for i, tt := range tests { + fakeDocker.SetFakeContainers(dockerContainers) + pod.Spec.RestartPolicy = tt.policy + runSyncPod(t, dm, fakeDocker, pod, nil, false) + // 'stop' is because the pod infra container is killed when no container is running. + verifyCalls(t, fakeDocker, tt.calls) + + if err := fakeDocker.AssertCreated(tt.created); err != nil { + t.Errorf("case [%d]: %v", i, err) + } + if err := fakeDocker.AssertStopped(tt.stopped); err != nil { + t.Errorf("case [%d]: %v", i, err) + } + } +} + +func TestSyncPodBackoff(t *testing.T) { + var fakeClock = util.NewFakeClock(time.Now()) + startTime := fakeClock.Now() + + dm, fakeDocker := newTestDockerManager() + containers := []api.Container{ + {Name: "good"}, + {Name: "bad"}, + } + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "podfoo", + Namespace: "nsnew", + }, + Spec: api.PodSpec{ + Containers: containers, + }, + } + + stableId := "k8s_bad." + strconv.FormatUint(kubecontainer.HashContainer(&containers[1]), 16) + "_podfoo_nsnew_12345678" + dockerContainers := []*docker.Container{ + { + ID: "9876", + Name: "/k8s_POD." + strconv.FormatUint(generatePodInfraContainerHash(pod), 16) + "_podfoo_nsnew_12345678_0", + State: docker.State{ + StartedAt: startTime, + Running: true, + }, + }, + { + ID: "1234", + Name: "/k8s_good." + strconv.FormatUint(kubecontainer.HashContainer(&containers[0]), 16) + "_podfoo_nsnew_12345678_0", + State: docker.State{ + StartedAt: startTime, + Running: true, + }, + }, + { + ID: "5678", + Name: "/k8s_bad." + strconv.FormatUint(kubecontainer.HashContainer(&containers[1]), 16) + "_podfoo_nsnew_12345678_0", + State: docker.State{ + ExitCode: 42, + StartedAt: startTime, + FinishedAt: fakeClock.Now(), + }, + }, + } + + startCalls := []string{"create", "start", "inspect_container"} + backOffCalls := []string{} + startResult := &kubecontainer.SyncResult{Action: kubecontainer.StartContainer, Target: "bad", Error: nil, Message: ""} + backoffResult := &kubecontainer.SyncResult{Action: kubecontainer.StartContainer, Target: "bad", Error: kubecontainer.ErrCrashLoopBackOff, Message: ""} + tests := []struct { + tick int + backoff int + killDelay int + result []string + expectErr bool + }{ + {1, 1, 1, startCalls, false}, + {2, 2, 2, startCalls, false}, + {3, 2, 3, backOffCalls, true}, + {4, 4, 4, startCalls, false}, + {5, 4, 5, backOffCalls, true}, + {6, 4, 6, backOffCalls, true}, + {7, 4, 7, backOffCalls, true}, + {8, 8, 129, startCalls, false}, + {130, 1, 0, startCalls, false}, + } + + backOff := flowcontrol.NewBackOff(time.Second, time.Minute) + backOff.Clock = fakeClock + for _, c := range tests { + fakeDocker.SetFakeContainers(dockerContainers) + fakeClock.SetTime(startTime.Add(time.Duration(c.tick) * time.Second)) + + result := runSyncPod(t, dm, fakeDocker, pod, backOff, c.expectErr) + verifyCalls(t, fakeDocker, c.result) + + // Verify whether the correct sync pod result is generated + if c.expectErr { + verifySyncResults(t, []*kubecontainer.SyncResult{backoffResult}, result) + } else { + verifySyncResults(t, []*kubecontainer.SyncResult{startResult}, result) + } + + if backOff.Get(stableId) != time.Duration(c.backoff)*time.Second { + t.Errorf("At tick %s expected backoff=%s got=%s", time.Duration(c.tick)*time.Second, time.Duration(c.backoff)*time.Second, backOff.Get(stableId)) + } + + if len(fakeDocker.Created) > 0 { + // pretend kill the container + fakeDocker.Created = nil + dockerContainers[2].State.FinishedAt = startTime.Add(time.Duration(c.killDelay) * time.Second) + } + } +} + +func TestGetRestartCount(t *testing.T) { + dm, fakeDocker := newTestDockerManager() + containerName := "bar" + pod := api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: containerName}, + }, + RestartPolicy: "Always", + }, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + { + Name: containerName, + RestartCount: 3, + }, + }, + }, + } + + // Helper function for verifying the restart count. + verifyRestartCount := func(pod *api.Pod, expectedCount int) { + runSyncPod(t, dm, fakeDocker, pod, nil, false) + status, err := dm.GetPodStatus(pod.UID, pod.Name, pod.Namespace) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + cs := status.FindContainerStatusByName(containerName) + if cs == nil { + t.Fatalf("Can't find status for container %q", containerName) + } + restartCount := cs.RestartCount + if restartCount != expectedCount { + t.Errorf("expected %d restart count, got %d", expectedCount, restartCount) + } + } + + killOneContainer := func(pod *api.Pod) { + status, err := dm.GetPodStatus(pod.UID, pod.Name, pod.Namespace) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + cs := status.FindContainerStatusByName(containerName) + if cs == nil { + t.Fatalf("Can't find status for container %q", containerName) + } + dm.KillContainerInPod(cs.ID, &pod.Spec.Containers[0], pod, "test container restart count.") + } + // Container "bar" starts the first time. + // TODO: container lists are expected to be sorted reversely by time. + // We should fix FakeDockerClient to sort the list before returning. + // (randome-liu) Just partially sorted now. + verifyRestartCount(&pod, 0) + killOneContainer(&pod) + + // Poor container "bar" has been killed, and should be restarted with restart count 1 + verifyRestartCount(&pod, 1) + killOneContainer(&pod) + + // Poor container "bar" has been killed again, and should be restarted with restart count 2 + verifyRestartCount(&pod, 2) + killOneContainer(&pod) + + // Poor container "bar" has been killed again ang again, and should be restarted with restart count 3 + verifyRestartCount(&pod, 3) + + // The oldest container has been garbage collected + exitedContainers := fakeDocker.ExitedContainerList + fakeDocker.ExitedContainerList = exitedContainers[:len(exitedContainers)-1] + verifyRestartCount(&pod, 3) + + // The last two oldest containers have been garbage collected + fakeDocker.ExitedContainerList = exitedContainers[:len(exitedContainers)-2] + verifyRestartCount(&pod, 3) + + // All exited containers have been garbage collected, restart count should be got from old api pod status + fakeDocker.ExitedContainerList = []docker.APIContainers{} + verifyRestartCount(&pod, 3) + killOneContainer(&pod) + + // Poor container "bar" has been killed again ang again and again, and should be restarted with restart count 4 + verifyRestartCount(&pod, 4) +} + +func TestGetTerminationMessagePath(t *testing.T) { + dm, fakeDocker := newTestDockerManager() + containers := []api.Container{ + { + Name: "bar", + TerminationMessagePath: "/dev/somepath", + }, + } + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: containers, + }, + } + + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + containerList := fakeDocker.ContainerList + if len(containerList) != 2 { + // One for infra container, one for container "bar" + t.Fatalf("unexpected container list length %d", len(containerList)) + } + inspectResult, err := dm.client.InspectContainer(containerList[0].ID) + if err != nil { + t.Fatalf("unexpected inspect error: %v", err) + } + containerInfo := getContainerInfoFromLabel(inspectResult.Config.Labels) + terminationMessagePath := containerInfo.TerminationMessagePath + if terminationMessagePath != containers[0].TerminationMessagePath { + t.Errorf("expected termination message path %s, got %s", containers[0].TerminationMessagePath, terminationMessagePath) + } +} + +func TestSyncPodWithPodInfraCreatesContainerCallsHandler(t *testing.T) { + fakeHTTPClient := &fakeHTTP{} + dm, fakeDocker := newTestDockerManagerWithHTTPClient(fakeHTTPClient) + + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "bar", + Lifecycle: &api.Lifecycle{ + PostStart: &api.Handler{ + HTTPGet: &api.HTTPGetAction{ + Host: "foo", + Port: intstr.FromInt(8080), + Path: "bar", + }, + }, + }, + }, + }, + }, + } + fakeDocker.SetFakeRunningContainers([]*docker.Container{{ + ID: "9876", + Name: "/k8s_POD." + strconv.FormatUint(generatePodInfraContainerHash(pod), 16) + "_foo_new_12345678_0", + }}) + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + verifyCalls(t, fakeDocker, []string{ + // Create container. + "create", "start", "inspect_container", + }) + + fakeDocker.Lock() + if len(fakeDocker.Created) != 1 || + !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) { + t.Errorf("unexpected containers created %v", fakeDocker.Created) + } + fakeDocker.Unlock() + if fakeHTTPClient.url != "http://foo:8080/bar" { + t.Errorf("unexpected handler: %q", fakeHTTPClient.url) + } +} + +func TestSyncPodEventHandlerFails(t *testing.T) { + // Simulate HTTP failure. + fakeHTTPClient := &fakeHTTP{err: fmt.Errorf("test error")} + dm, fakeDocker := newTestDockerManagerWithHTTPClient(fakeHTTPClient) + + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "bar", + Lifecycle: &api.Lifecycle{ + PostStart: &api.Handler{ + HTTPGet: &api.HTTPGetAction{ + Host: "does.no.exist", + Port: intstr.FromInt(8080), + Path: "bar", + }, + }, + }, + }, + }, + }, + } + + fakeDocker.SetFakeRunningContainers([]*docker.Container{{ + ID: "9876", + Name: "/k8s_POD." + strconv.FormatUint(generatePodInfraContainerHash(pod), 16) + "_foo_new_12345678_0", + }}) + runSyncPod(t, dm, fakeDocker, pod, nil, true) + + verifyCalls(t, fakeDocker, []string{ + // Create the container. + "create", "start", + // Kill the container since event handler fails. + "stop", + }) + + // TODO(yifan): Check the stopped container's name. + if len(fakeDocker.Stopped) != 1 { + t.Fatalf("Wrong containers were stopped: %v", fakeDocker.Stopped) + } + dockerName, _, err := ParseDockerName(fakeDocker.Stopped[0]) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if dockerName.ContainerName != "bar" { + t.Errorf("Wrong stopped container, expected: bar, get: %q", dockerName.ContainerName) + } +} + +type fakeReadWriteCloser struct{} + +func (*fakeReadWriteCloser) Read([]byte) (int, error) { return 0, nil } +func (*fakeReadWriteCloser) Write([]byte) (int, error) { return 0, nil } +func (*fakeReadWriteCloser) Close() error { return nil } + +func TestPortForwardNoSuchContainer(t *testing.T) { + dm, _ := newTestDockerManager() + + podName, podNamespace := "podName", "podNamespace" + err := dm.PortForward( + &kubecontainer.Pod{ + ID: "podID", + Name: podName, + Namespace: podNamespace, + Containers: nil, + }, + 5000, + // need a valid io.ReadWriteCloser here + &fakeReadWriteCloser{}, + ) + if err == nil { + t.Fatal("unexpected non-error") + } + expectedErr := noPodInfraContainerError(podName, podNamespace) + if !reflect.DeepEqual(err, expectedErr) { + t.Fatalf("expected %v, but saw %v", expectedErr, err) + } +} + +func TestSyncPodWithTerminationLog(t *testing.T) { + dm, fakeDocker := newTestDockerManager() + container := api.Container{ + Name: "bar", + TerminationMessagePath: "/dev/somepath", + } + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + container, + }, + }, + } + + runSyncPod(t, dm, fakeDocker, pod, nil, false) + verifyCalls(t, fakeDocker, []string{ + // Create pod infra container. + "create", "start", "inspect_container", "inspect_container", + // Create container. + "create", "start", "inspect_container", + }) + + defer os.Remove(testPodContainerDir) + + fakeDocker.Lock() + if len(fakeDocker.Created) != 2 || + !matchString(t, "/k8s_POD\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) || + !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[1]) { + t.Errorf("unexpected containers created %v", fakeDocker.Created) + } + fakeDocker.Unlock() + newContainer, err := fakeDocker.InspectContainer(fakeDocker.Created[1]) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + parts := strings.Split(newContainer.HostConfig.Binds[0], ":") + if !matchString(t, testPodContainerDir+"/[a-f0-9]", parts[0]) { + t.Errorf("unexpected host path: %s", parts[0]) + } + if parts[1] != "/dev/somepath" { + t.Errorf("unexpected container path: %s", parts[1]) + } +} + +func TestSyncPodWithHostNetwork(t *testing.T) { + dm, fakeDocker := newTestDockerManager() + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "bar"}, + }, + SecurityContext: &api.PodSecurityContext{ + HostNetwork: true, + }, + }, + } + + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + verifyCalls(t, fakeDocker, []string{ + // Create pod infra container. + "create", "start", "inspect_container", + // Create container. + "create", "start", "inspect_container", + }) + + fakeDocker.Lock() + if len(fakeDocker.Created) != 2 || + !matchString(t, "/k8s_POD\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) || + !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[1]) { + t.Errorf("unexpected containers created %v", fakeDocker.Created) + } + fakeDocker.Unlock() + + newContainer, err := fakeDocker.InspectContainer(fakeDocker.Created[1]) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + utsMode := newContainer.HostConfig.UTSMode + if utsMode != "host" { + t.Errorf("Pod with host network must have \"host\" utsMode, actual: \"%v\"", utsMode) + } +} + +func TestVerifyNonRoot(t *testing.T) { + dm, fakeDocker := newTestDockerManager() + + // setup test cases. + var rootUid int64 = 0 + var nonRootUid int64 = 1 + + tests := map[string]struct { + container *api.Container + inspectImage *docker.Image + expectedError string + }{ + // success cases + "non-root runAsUser": { + container: &api.Container{ + SecurityContext: &api.SecurityContext{ + RunAsUser: &nonRootUid, + }, + }, + }, + "numeric non-root image user": { + container: &api.Container{}, + inspectImage: &docker.Image{ + Config: &docker.Config{ + User: "1", + }, + }, + }, + "numeric non-root image user with gid": { + container: &api.Container{}, + inspectImage: &docker.Image{ + Config: &docker.Config{ + User: "1:2", + }, + }, + }, + + // failure cases + "root runAsUser": { + container: &api.Container{ + SecurityContext: &api.SecurityContext{ + RunAsUser: &rootUid, + }, + }, + expectedError: "container's runAsUser breaks non-root policy", + }, + "non-numeric image user": { + container: &api.Container{}, + inspectImage: &docker.Image{ + Config: &docker.Config{ + User: "foo", + }, + }, + expectedError: "non-numeric user", + }, + "numeric root image user": { + container: &api.Container{}, + inspectImage: &docker.Image{ + Config: &docker.Config{ + User: "0", + }, + }, + expectedError: "container has no runAsUser and image will run as root", + }, + "numeric root image user with gid": { + container: &api.Container{}, + inspectImage: &docker.Image{ + Config: &docker.Config{ + User: "0:1", + }, + }, + expectedError: "container has no runAsUser and image will run as root", + }, + "nil image in inspect": { + container: &api.Container{}, + expectedError: "unable to inspect image", + }, + "nil config in image inspect": { + container: &api.Container{}, + inspectImage: &docker.Image{}, + expectedError: "unable to inspect image", + }, + } + + for k, v := range tests { + fakeDocker.Image = v.inspectImage + err := dm.verifyNonRoot(v.container) + if v.expectedError == "" && err != nil { + t.Errorf("case[%q]: unexpected error: %v", k, err) + } + if v.expectedError != "" && !strings.Contains(err.Error(), v.expectedError) { + t.Errorf("case[%q]: expected: %q, got: %q", k, v.expectedError, err.Error()) + } + } +} + +func TestGetUidFromUser(t *testing.T) { + tests := map[string]struct { + input string + expect string + }{ + "no gid": { + input: "0", + expect: "0", + }, + "uid/gid": { + input: "0:1", + expect: "0", + }, + "empty input": { + input: "", + expect: "", + }, + "multiple spearators": { + input: "1:2:3", + expect: "1", + }, + } + for k, v := range tests { + actual := getUidFromUser(v.input) + if actual != v.expect { + t.Errorf("%s failed. Expected %s but got %s", k, v.expect, actual) + } + } +} + +func TestGetPidMode(t *testing.T) { + // test false + pod := &api.Pod{} + pidMode := getPidMode(pod) + + if pidMode != "" { + t.Errorf("expected empty pid mode for pod but got %v", pidMode) + } + + // test true + pod.Spec.SecurityContext = &api.PodSecurityContext{} + pod.Spec.SecurityContext.HostPID = true + pidMode = getPidMode(pod) + if pidMode != "host" { + t.Errorf("expected host pid mode for pod but got %v", pidMode) + } +} + +func TestGetIPCMode(t *testing.T) { + // test false + pod := &api.Pod{} + ipcMode := getIPCMode(pod) + + if ipcMode != "" { + t.Errorf("expected empty ipc mode for pod but got %v", ipcMode) + } + + // test true + pod.Spec.SecurityContext = &api.PodSecurityContext{} + pod.Spec.SecurityContext.HostIPC = true + ipcMode = getIPCMode(pod) + if ipcMode != "host" { + t.Errorf("expected host ipc mode for pod but got %v", ipcMode) + } +} + +func TestSyncPodWithPullPolicy(t *testing.T) { + dm, fakeDocker := newTestDockerManager() + puller := dm.dockerPuller.(*FakeDockerPuller) + puller.HasImages = []string{"existing_one", "want:latest"} + dm.podInfraContainerImage = "pod_infra_image" + + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "bar", Image: "pull_always_image", ImagePullPolicy: api.PullAlways}, + {Name: "bar2", Image: "pull_if_not_present_image", ImagePullPolicy: api.PullIfNotPresent}, + {Name: "bar3", Image: "existing_one", ImagePullPolicy: api.PullIfNotPresent}, + {Name: "bar4", Image: "want:latest", ImagePullPolicy: api.PullIfNotPresent}, + {Name: "bar5", Image: "pull_never_image", ImagePullPolicy: api.PullNever}, + }, + }, + } + + expectedResults := []*kubecontainer.SyncResult{ + //Sync result for infra container + {kubecontainer.StartContainer, PodInfraContainerName, nil, ""}, + {kubecontainer.SetupNetwork, kubecontainer.GetPodFullName(pod), nil, ""}, + //Sync result for user containers + {kubecontainer.StartContainer, "bar", nil, ""}, + {kubecontainer.StartContainer, "bar2", nil, ""}, + {kubecontainer.StartContainer, "bar3", nil, ""}, + {kubecontainer.StartContainer, "bar4", nil, ""}, + {kubecontainer.StartContainer, "bar5", kubecontainer.ErrImageNeverPull, + "Container image \"pull_never_image\" is not present with pull policy of Never"}, + } + + result := runSyncPod(t, dm, fakeDocker, pod, nil, true) + verifySyncResults(t, expectedResults, result) + + fakeDocker.Lock() + defer fakeDocker.Unlock() + + pulledImageSorted := puller.ImagesPulled[:] + sort.Strings(pulledImageSorted) + assert.Equal(t, []string{"pod_infra_image", "pull_always_image", "pull_if_not_present_image"}, pulledImageSorted) + + if len(fakeDocker.Created) != 5 { + t.Errorf("unexpected containers created %v", fakeDocker.Created) + } +} + +// This test only covers SyncPod with PullImageFailure, CreateContainerFailure and StartContainerFailure. +// There are still quite a few failure cases not covered. +// TODO(random-liu): Better way to test the SyncPod failures. +func TestSyncPodWithFailure(t *testing.T) { + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + } + tests := map[string]struct { + container api.Container + dockerError map[string]error + pullerError []error + expected []*kubecontainer.SyncResult + }{ + "PullImageFailure": { + api.Container{Name: "bar", Image: "realImage", ImagePullPolicy: api.PullAlways}, + map[string]error{}, + []error{fmt.Errorf("can't pull image")}, + []*kubecontainer.SyncResult{{kubecontainer.StartContainer, "bar", kubecontainer.ErrImagePull, "can't pull image"}}, + }, + "CreateContainerFailure": { + api.Container{Name: "bar", Image: "alreadyPresent"}, + map[string]error{"create": fmt.Errorf("can't create container")}, + []error{}, + []*kubecontainer.SyncResult{{kubecontainer.StartContainer, "bar", kubecontainer.ErrRunContainer, "can't create container"}}, + }, + "StartContainerFailure": { + api.Container{Name: "bar", Image: "alreadyPresent"}, + map[string]error{"start": fmt.Errorf("can't start container")}, + []error{}, + []*kubecontainer.SyncResult{{kubecontainer.StartContainer, "bar", kubecontainer.ErrRunContainer, "can't start container"}}, + }, + } + + for _, test := range tests { + dm, fakeDocker := newTestDockerManager() + puller := dm.dockerPuller.(*FakeDockerPuller) + puller.HasImages = []string{test.container.Image} + // Pretend that the pod infra container has already been created, so that + // we can run the user containers. + fakeDocker.SetFakeRunningContainers([]*docker.Container{{ + ID: "9876", + Name: "/k8s_POD." + strconv.FormatUint(generatePodInfraContainerHash(pod), 16) + "_foo_new_12345678_0", + }}) + fakeDocker.InjectErrors(test.dockerError) + puller.ErrorsToInject = test.pullerError + pod.Spec.Containers = []api.Container{test.container} + result := runSyncPod(t, dm, fakeDocker, pod, nil, true) + verifySyncResults(t, test.expected, result) + } +} + +// Verify whether all the expected results appear exactly only once in real result. +func verifySyncResults(t *testing.T, expectedResults []*kubecontainer.SyncResult, realResult kubecontainer.PodSyncResult) { + if len(expectedResults) != len(realResult.SyncResults) { + t.Errorf("expected sync result number %d, got %d", len(expectedResults), len(realResult.SyncResults)) + for _, r := range expectedResults { + t.Errorf("expected result: %+v", r) + } + for _, r := range realResult.SyncResults { + t.Errorf("real result: %+v", r) + } + return + } + // The container start order is not fixed, because SyncPod() uses a map to store the containers to start. + // Here we should make sure each expected result appears only once in the real result. + for _, expectR := range expectedResults { + found := 0 + for _, realR := range realResult.SyncResults { + // For the same action of the same container, the result should be the same + if realR.Target == expectR.Target && realR.Action == expectR.Action { + // We use Contains() here because the message format may be changed, but at least we should + // make sure that the expected message is contained. + if realR.Error != expectR.Error || !strings.Contains(realR.Message, expectR.Message) { + t.Errorf("expected sync result %+v, got %+v", expectR, realR) + } + found++ + } + } + if found == 0 { + t.Errorf("not found expected result %+v", expectR) + } + if found > 1 { + t.Errorf("got %d duplicate expected result %+v", found, expectR) + } + } +} + +func TestSeccompIsDisabledWithDockerV110(t *testing.T) { + dm, fakeDocker := newTestDockerManagerWithHTTPClientWithVersion(&fakeHTTP{}, "1.10.1", "1.22") + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "bar"}, + }, + }, + } + + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + verifyCalls(t, fakeDocker, []string{ + // Create pod infra container. + "create", "start", "inspect_container", "inspect_container", + // Create container. + "create", "start", "inspect_container", + }) + + fakeDocker.Lock() + if len(fakeDocker.Created) != 2 || + !matchString(t, "/k8s_POD\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) || + !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[1]) { + t.Errorf("unexpected containers created %v", fakeDocker.Created) + } + fakeDocker.Unlock() + + newContainer, err := fakeDocker.InspectContainer(fakeDocker.Created[1]) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + assert.Contains(t, newContainer.HostConfig.SecurityOpt, "seccomp:unconfined", "Pods with Docker versions >= 1.10 must have seccomp disabled.") +} + +func TestSecurityOptsAreNilWithDockerV19(t *testing.T) { + dm, fakeDocker := newTestDockerManagerWithHTTPClientWithVersion(&fakeHTTP{}, "1.9.1", "1.21") + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "bar"}, + }, + }, + } + + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + verifyCalls(t, fakeDocker, []string{ + // Create pod infra container. + "create", "start", "inspect_container", "inspect_container", + // Create container. + "create", "start", "inspect_container", + }) + + fakeDocker.Lock() + if len(fakeDocker.Created) != 2 || + !matchString(t, "/k8s_POD\\.[a-f0-9]+_foo_new_", fakeDocker.Created[0]) || + !matchString(t, "/k8s_bar\\.[a-f0-9]+_foo_new_", fakeDocker.Created[1]) { + t.Errorf("unexpected containers created %v", fakeDocker.Created) + } + fakeDocker.Unlock() + + newContainer, err := fakeDocker.InspectContainer(fakeDocker.Created[1]) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + assert.NotContains(t, newContainer.HostConfig.SecurityOpt, "seccomp:unconfined", "Pods with Docker versions < 1.10 must not have seccomp disabled by default") +} + +func TestCheckVersionCompatibility(t *testing.T) { + apiVersion, err := docker.NewAPIVersion(minimumDockerAPIVersion) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + type test struct { + version string + compatible bool + } + tests := []test{ + // Minimum apiversion + {minimumDockerAPIVersion, true}, + // Invalid apiversion + {"invalid_api_version", false}, + } + for i := range apiVersion { + apiVersion[i]++ + // Newer apiversion + tests = append(tests, test{apiVersion.String(), true}) + apiVersion[i] -= 2 + // Older apiversion + if apiVersion[i] >= 0 { + tests = append(tests, test{apiVersion.String(), false}) + } + apiVersion[i]++ + } + + for i, tt := range tests { + testCase := fmt.Sprintf("test case #%d test version %q", i, tt.version) + dm, fakeDocker := newTestDockerManagerWithHTTPClientWithVersion(&fakeHTTP{}, "", tt.version) + err := dm.checkVersionCompatibility() + assert.Equal(t, tt.compatible, err == nil, testCase) + if tt.compatible == true { + // Get docker version error + fakeDocker.InjectError("version", fmt.Errorf("injected version error")) + err := dm.checkVersionCompatibility() + assert.NotNil(t, err, testCase+" version error check") + } + } +} + +func TestGetPodStatusNoSuchContainer(t *testing.T) { + const ( + noSuchContainerID = "nosuchcontainer" + infraContainerID = "9876" + ) + dm, fakeDocker := newTestDockerManager() + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{{Name: "nosuchcontainer"}}, + }, + } + + fakeDocker.SetFakeContainers([]*docker.Container{ + { + ID: noSuchContainerID, + Name: "/k8s_nosuchcontainer_foo_new_12345678_42", + State: docker.State{ + ExitCode: 0, + StartedAt: time.Now(), + FinishedAt: time.Now(), + Running: false, + }, + }, + { + ID: infraContainerID, + Name: "/k8s_POD." + strconv.FormatUint(generatePodInfraContainerHash(pod), 16) + "_foo_new_12345678_42", + State: docker.State{ + ExitCode: 0, + StartedAt: time.Now(), + FinishedAt: time.Now(), + Running: false, + }, + }}) + + fakeDocker.Errors = map[string]error{"inspect": &docker.NoSuchContainer{}} + runSyncPod(t, dm, fakeDocker, pod, nil, false) + + // Verify that we will try to start new contrainers even if the inspections + // failed. + verifyCalls(t, fakeDocker, []string{ + // Start a new infra container. + "create", "start", "inspect_container", "inspect_container", + // Start a new container. + "create", "start", "inspect_container", + }) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/envvars/doc.go b/vendor/k8s.io/kubernetes/pkg/kubelet/envvars/doc.go new file mode 100644 index 000000000..22f57e80a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/envvars/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 envvars is the package that build the environment variables that kubernetes provides +// to the containers run by it. +package envvars diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/envvars/envvars.go b/vendor/k8s.io/kubernetes/pkg/kubelet/envvars/envvars.go new file mode 100644 index 000000000..5a1de0f35 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/envvars/envvars.go @@ -0,0 +1,108 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 envvars + +import ( + "fmt" + "strconv" + "strings" + + "k8s.io/kubernetes/pkg/api" +) + +// FromServices builds environment variables that a container is started with, +// which tell the container where to find the services it may need, which are +// provided as an argument. +func FromServices(services *api.ServiceList) []api.EnvVar { + var result []api.EnvVar + for i := range services.Items { + service := &services.Items[i] + + // ignore services where ClusterIP is "None" or empty + // the services passed to this method should be pre-filtered + // only services that have the cluster IP set should be included here + if !api.IsServiceIPSet(service) { + continue + } + + // Host + name := makeEnvVariableName(service.Name) + "_SERVICE_HOST" + result = append(result, api.EnvVar{Name: name, Value: service.Spec.ClusterIP}) + // First port - give it the backwards-compatible name + name = makeEnvVariableName(service.Name) + "_SERVICE_PORT" + result = append(result, api.EnvVar{Name: name, Value: strconv.Itoa(service.Spec.Ports[0].Port)}) + // All named ports (only the first may be unnamed, checked in validation) + for i := range service.Spec.Ports { + sp := &service.Spec.Ports[i] + if sp.Name != "" { + pn := name + "_" + makeEnvVariableName(sp.Name) + result = append(result, api.EnvVar{Name: pn, Value: strconv.Itoa(sp.Port)}) + } + } + // Docker-compatible vars. + result = append(result, makeLinkVariables(service)...) + } + return result +} + +func makeEnvVariableName(str string) string { + // TODO: If we simplify to "all names are DNS1123Subdomains" this + // will need two tweaks: + // 1) Handle leading digits + // 2) Handle dots + return strings.ToUpper(strings.Replace(str, "-", "_", -1)) +} + +func makeLinkVariables(service *api.Service) []api.EnvVar { + prefix := makeEnvVariableName(service.Name) + all := []api.EnvVar{} + for i := range service.Spec.Ports { + sp := &service.Spec.Ports[i] + + protocol := string(api.ProtocolTCP) + if sp.Protocol != "" { + protocol = string(sp.Protocol) + } + if i == 0 { + // Docker special-cases the first port. + all = append(all, api.EnvVar{ + Name: prefix + "_PORT", + Value: fmt.Sprintf("%s://%s:%d", strings.ToLower(protocol), service.Spec.ClusterIP, sp.Port), + }) + } + portPrefix := fmt.Sprintf("%s_PORT_%d_%s", prefix, sp.Port, strings.ToUpper(protocol)) + all = append(all, []api.EnvVar{ + { + Name: portPrefix, + Value: fmt.Sprintf("%s://%s:%d", strings.ToLower(protocol), service.Spec.ClusterIP, sp.Port), + }, + { + Name: portPrefix + "_PROTO", + Value: strings.ToLower(protocol), + }, + { + Name: portPrefix + "_PORT", + Value: strconv.Itoa(sp.Port), + }, + { + Name: portPrefix + "_ADDR", + Value: service.Spec.ClusterIP, + }, + }...) + } + return all +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/envvars/envvars_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/envvars/envvars_test.go new file mode 100644 index 000000000..7feaf065d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/envvars/envvars_test.go @@ -0,0 +1,128 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 envvars_test + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/kubelet/envvars" +) + +func TestFromServices(t *testing.T) { + sl := api.ServiceList{ + Items: []api.Service{ + { + ObjectMeta: api.ObjectMeta{Name: "foo-bar"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + ClusterIP: "1.2.3.4", + Ports: []api.ServicePort{ + {Port: 8080, Protocol: "TCP"}, + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "abc-123"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + ClusterIP: "5.6.7.8", + Ports: []api.ServicePort{ + {Name: "u-d-p", Port: 8081, Protocol: "UDP"}, + {Name: "t-c-p", Port: 8081, Protocol: "TCP"}, + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "q-u-u-x"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + ClusterIP: "9.8.7.6", + Ports: []api.ServicePort{ + {Port: 8082, Protocol: "TCP"}, + {Name: "8083", Port: 8083, Protocol: "TCP"}, + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "svrc-clusterip-none"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + ClusterIP: "None", + Ports: []api.ServicePort{ + {Port: 8082, Protocol: "TCP"}, + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "svrc-clusterip-empty"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + ClusterIP: "", + Ports: []api.ServicePort{ + {Port: 8082, Protocol: "TCP"}, + }, + }, + }, + }, + } + vars := envvars.FromServices(&sl) + expected := []api.EnvVar{ + {Name: "FOO_BAR_SERVICE_HOST", Value: "1.2.3.4"}, + {Name: "FOO_BAR_SERVICE_PORT", Value: "8080"}, + {Name: "FOO_BAR_PORT", Value: "tcp://1.2.3.4:8080"}, + {Name: "FOO_BAR_PORT_8080_TCP", Value: "tcp://1.2.3.4:8080"}, + {Name: "FOO_BAR_PORT_8080_TCP_PROTO", Value: "tcp"}, + {Name: "FOO_BAR_PORT_8080_TCP_PORT", Value: "8080"}, + {Name: "FOO_BAR_PORT_8080_TCP_ADDR", Value: "1.2.3.4"}, + {Name: "ABC_123_SERVICE_HOST", Value: "5.6.7.8"}, + {Name: "ABC_123_SERVICE_PORT", Value: "8081"}, + {Name: "ABC_123_SERVICE_PORT_U_D_P", Value: "8081"}, + {Name: "ABC_123_SERVICE_PORT_T_C_P", Value: "8081"}, + {Name: "ABC_123_PORT", Value: "udp://5.6.7.8:8081"}, + {Name: "ABC_123_PORT_8081_UDP", Value: "udp://5.6.7.8:8081"}, + {Name: "ABC_123_PORT_8081_UDP_PROTO", Value: "udp"}, + {Name: "ABC_123_PORT_8081_UDP_PORT", Value: "8081"}, + {Name: "ABC_123_PORT_8081_UDP_ADDR", Value: "5.6.7.8"}, + {Name: "ABC_123_PORT_8081_TCP", Value: "tcp://5.6.7.8:8081"}, + {Name: "ABC_123_PORT_8081_TCP_PROTO", Value: "tcp"}, + {Name: "ABC_123_PORT_8081_TCP_PORT", Value: "8081"}, + {Name: "ABC_123_PORT_8081_TCP_ADDR", Value: "5.6.7.8"}, + {Name: "Q_U_U_X_SERVICE_HOST", Value: "9.8.7.6"}, + {Name: "Q_U_U_X_SERVICE_PORT", Value: "8082"}, + {Name: "Q_U_U_X_SERVICE_PORT_8083", Value: "8083"}, + {Name: "Q_U_U_X_PORT", Value: "tcp://9.8.7.6:8082"}, + {Name: "Q_U_U_X_PORT_8082_TCP", Value: "tcp://9.8.7.6:8082"}, + {Name: "Q_U_U_X_PORT_8082_TCP_PROTO", Value: "tcp"}, + {Name: "Q_U_U_X_PORT_8082_TCP_PORT", Value: "8082"}, + {Name: "Q_U_U_X_PORT_8082_TCP_ADDR", Value: "9.8.7.6"}, + {Name: "Q_U_U_X_PORT_8083_TCP", Value: "tcp://9.8.7.6:8083"}, + {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"}, + } + if len(vars) != len(expected) { + t.Errorf("Expected %d env vars, got: %+v", len(expected), vars) + return + } + for i := range expected { + if !reflect.DeepEqual(vars[i], expected[i]) { + t.Errorf("expected %#v, got %#v", vars[i], expected[i]) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/flannel_helper.go b/vendor/k8s.io/kubernetes/pkg/kubelet/flannel_helper.go new file mode 100644 index 000000000..c81cb594f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/flannel_helper.go @@ -0,0 +1,168 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "fmt" + "io/ioutil" + "os" + "strconv" + "strings" + + utildbus "k8s.io/kubernetes/pkg/util/dbus" + utilexec "k8s.io/kubernetes/pkg/util/exec" + utiliptables "k8s.io/kubernetes/pkg/util/iptables" + + "github.com/golang/glog" +) + +// TODO: Move all this to a network plugin. +const ( + // TODO: The location of default docker options is distro specific, so this + // probably won't work on anything other than debian/ubuntu. This is a + // short-term compromise till we've moved overlay setup into a plugin. + dockerOptsFile = "/etc/default/docker" + flannelSubnetKey = "FLANNEL_SUBNET" + flannelNetworkKey = "FLANNEL_NETWORK" + flannelMtuKey = "FLANNEL_MTU" + dockerOptsKey = "DOCKER_OPTS" + flannelSubnetFile = "/var/run/flannel/subnet.env" +) + +// A Kubelet to flannel bridging helper. +type FlannelHelper struct { + subnetFile string + iptablesHelper utiliptables.Interface +} + +// NewFlannelHelper creates a new flannel helper. +func NewFlannelHelper() *FlannelHelper { + return &FlannelHelper{ + subnetFile: flannelSubnetFile, + iptablesHelper: utiliptables.New(utilexec.New(), utildbus.New(), utiliptables.ProtocolIpv4), + } +} + +// Ensure the required MASQUERADE rules exist for the given network/cidr. +func (f *FlannelHelper) ensureFlannelMasqRule(kubeNetwork, podCIDR string) error { + // TODO: Investigate delegation to flannel via -ip-masq=true once flannel + // issue #374 is resolved. + comment := "Flannel masquerade facilitates pod<->node traffic." + args := []string{ + "-m", "comment", "--comment", comment, + "!", "-d", kubeNetwork, "-s", podCIDR, "-j", "MASQUERADE", + } + _, err := f.iptablesHelper.EnsureRule( + utiliptables.Append, + utiliptables.TableNAT, + utiliptables.ChainPostrouting, + args...) + return err +} + +// Handshake waits for the flannel subnet file and installs a few IPTables +// rules, returning the pod CIDR allocated for this node. +func (f *FlannelHelper) Handshake() (podCIDR string, err error) { + // TODO: Using a file to communicate is brittle + if _, err = os.Stat(f.subnetFile); err != nil { + return "", fmt.Errorf("Waiting for subnet file %v", f.subnetFile) + } + glog.Infof("Found flannel subnet file %v", f.subnetFile) + + config, err := parseKVConfig(f.subnetFile) + if err != nil { + return "", err + } + if err = writeDockerOptsFromFlannelConfig(config); err != nil { + return "", err + } + podCIDR, ok := config[flannelSubnetKey] + if !ok { + return "", fmt.Errorf("No flannel subnet, config %+v", config) + } + kubeNetwork, ok := config[flannelNetworkKey] + if !ok { + return "", fmt.Errorf("No flannel network, config %+v", config) + } + if f.ensureFlannelMasqRule(kubeNetwork, podCIDR); err != nil { + return "", fmt.Errorf("Unable to install flannel masquerade %v", err) + } + return podCIDR, nil +} + +// Take env variables from flannel subnet env and write to /etc/docker/defaults. +func writeDockerOptsFromFlannelConfig(flannelConfig map[string]string) error { + // TODO: Write dockeropts to unit file on systemd machines + // https://github.com/docker/docker/issues/9889 + mtu, ok := flannelConfig[flannelMtuKey] + if !ok { + return fmt.Errorf("No flannel mtu, flannel config %+v", flannelConfig) + } + dockerOpts, err := parseKVConfig(dockerOptsFile) + if err != nil { + return err + } + opts, ok := dockerOpts[dockerOptsKey] + if !ok { + glog.Errorf("Did not find docker opts, writing them") + opts = fmt.Sprintf( + " --bridge=cbr0 --iptables=false --ip-masq=false") + } else { + opts, _ = strconv.Unquote(opts) + } + dockerOpts[dockerOptsKey] = fmt.Sprintf("\"%v --mtu=%v\"", opts, mtu) + if err = writeKVConfig(dockerOptsFile, dockerOpts); err != nil { + return err + } + return nil +} + +// parseKVConfig takes a file with key-value env variables and returns a dictionary mapping the same. +func parseKVConfig(filename string) (map[string]string, error) { + config := map[string]string{} + if _, err := os.Stat(filename); err != nil { + return config, err + } + buff, err := ioutil.ReadFile(filename) + if err != nil { + return config, err + } + str := string(buff) + glog.Infof("Read kv options %+v from %v", str, filename) + for _, line := range strings.Split(str, "\n") { + kv := strings.Split(line, "=") + if len(kv) != 2 { + glog.Warningf("Ignoring non key-value pair %v", kv) + continue + } + config[string(kv[0])] = string(kv[1]) + } + return config, nil +} + +// writeKVConfig writes a kv map as env variables into the given file. +func writeKVConfig(filename string, kv map[string]string) error { + if _, err := os.Stat(filename); err != nil { + return err + } + content := "" + for k, v := range kv { + content += fmt.Sprintf("%v=%v\n", k, v) + } + glog.Warningf("Writing kv options %+v to %v", content, filename) + return ioutil.WriteFile(filename, []byte(content), 0644) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/image_manager.go b/vendor/k8s.io/kubernetes/pkg/kubelet/image_manager.go new file mode 100644 index 000000000..9b0dc0979 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/image_manager.go @@ -0,0 +1,328 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "fmt" + "sort" + "sync" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/kubelet/cadvisor" + "k8s.io/kubernetes/pkg/kubelet/container" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/wait" +) + +// Manages lifecycle of all images. +// +// Implementation is thread-safe. +type imageManager interface { + // Applies the garbage collection policy. Errors include being unable to free + // enough space as per the garbage collection policy. + GarbageCollect() error + + // Start async garbage collection of images. + Start() error + + GetImageList() ([]kubecontainer.Image, error) + + // TODO(vmarmol): Have this subsume pulls as well. +} + +// A policy for garbage collecting images. Policy defines an allowed band in +// which garbage collection will be run. +type ImageGCPolicy struct { + // Any usage above this threshold will always trigger garbage collection. + // This is the highest usage we will allow. + HighThresholdPercent int + + // Any usage below this threshold will never trigger garbage collection. + // This is the lowest threshold we will try to garbage collect to. + LowThresholdPercent int + + // Minimum age at which a image can be garbage collected. + MinAge time.Duration +} + +type realImageManager struct { + // Container runtime + runtime container.Runtime + + // Records of images and their use. + imageRecords map[string]*imageRecord + imageRecordsLock sync.Mutex + + // The image garbage collection policy in use. + policy ImageGCPolicy + + // cAdvisor instance. + cadvisor cadvisor.Interface + + // Recorder for Kubernetes events. + recorder record.EventRecorder + + // Reference to this node. + nodeRef *api.ObjectReference + + // Track initialization + initialized bool +} + +// Information about the images we track. +type imageRecord struct { + // Time when this image was first detected. + firstDetected time.Time + + // Time when we last saw this image being used. + lastUsed time.Time + + // Size of the image in bytes. + size int64 +} + +func newImageManager(runtime container.Runtime, cadvisorInterface cadvisor.Interface, recorder record.EventRecorder, nodeRef *api.ObjectReference, policy ImageGCPolicy) (imageManager, error) { + // Validate policy. + if policy.HighThresholdPercent < 0 || policy.HighThresholdPercent > 100 { + return nil, fmt.Errorf("invalid HighThresholdPercent %d, must be in range [0-100]", policy.HighThresholdPercent) + } + if policy.LowThresholdPercent < 0 || policy.LowThresholdPercent > 100 { + return nil, fmt.Errorf("invalid LowThresholdPercent %d, must be in range [0-100]", policy.LowThresholdPercent) + } + if policy.LowThresholdPercent > policy.HighThresholdPercent { + return nil, fmt.Errorf("LowThresholdPercent %d can not be higher than HighThresholdPercent %d", policy.LowThresholdPercent, policy.HighThresholdPercent) + } + im := &realImageManager{ + runtime: runtime, + policy: policy, + imageRecords: make(map[string]*imageRecord), + cadvisor: cadvisorInterface, + recorder: recorder, + nodeRef: nodeRef, + initialized: false, + } + + return im, nil +} + +func (im *realImageManager) Start() error { + go wait.Until(func() { + // Initial detection make detected time "unknown" in the past. + var ts time.Time + if im.initialized { + ts = time.Now() + } + err := im.detectImages(ts) + if err != nil { + glog.Warningf("[ImageManager] Failed to monitor images: %v", err) + } else { + im.initialized = true + } + }, 5*time.Minute, wait.NeverStop) + + return nil +} + +// Get a list of images on this node +func (im *realImageManager) GetImageList() ([]kubecontainer.Image, error) { + images, err := im.runtime.ListImages() + if err != nil { + return nil, err + } + return images, nil +} + +func (im *realImageManager) detectImages(detectTime time.Time) error { + images, err := im.runtime.ListImages() + if err != nil { + return err + } + pods, err := im.runtime.GetPods(true) + if err != nil { + return err + } + + // Make a set of images in use by containers. + imagesInUse := sets.NewString() + for _, pod := range pods { + for _, container := range pod.Containers { + imagesInUse.Insert(container.Image) + } + } + + // Add new images and record those being used. + now := time.Now() + currentImages := sets.NewString() + im.imageRecordsLock.Lock() + defer im.imageRecordsLock.Unlock() + for _, image := range images { + currentImages.Insert(image.ID) + + // New image, set it as detected now. + if _, ok := im.imageRecords[image.ID]; !ok { + im.imageRecords[image.ID] = &imageRecord{ + firstDetected: detectTime, + } + } + + // Set last used time to now if the image is being used. + if isImageUsed(image, imagesInUse) { + im.imageRecords[image.ID].lastUsed = now + } + + im.imageRecords[image.ID].size = image.Size + } + + // Remove old images from our records. + for image := range im.imageRecords { + if !currentImages.Has(image) { + delete(im.imageRecords, image) + } + } + + return nil +} + +func (im *realImageManager) GarbageCollect() error { + // Get disk usage on disk holding images. + fsInfo, err := im.cadvisor.DockerImagesFsInfo() + if err != nil { + return err + } + usage := int64(fsInfo.Usage) + capacity := int64(fsInfo.Capacity) + + // Check valid capacity. + if capacity == 0 { + err := fmt.Errorf("invalid capacity %d on device %q at mount point %q", capacity, fsInfo.Device, fsInfo.Mountpoint) + im.recorder.Eventf(im.nodeRef, api.EventTypeWarning, container.InvalidDiskCapacity, err.Error()) + return err + } + + // If over the max threshold, free enough to place us at the lower threshold. + usagePercent := int(usage * 100 / capacity) + if usagePercent >= im.policy.HighThresholdPercent { + amountToFree := usage - (int64(im.policy.LowThresholdPercent) * capacity / 100) + glog.Infof("[ImageManager]: Disk usage on %q (%s) is at %d%% which is over the high threshold (%d%%). Trying to free %d bytes", fsInfo.Device, fsInfo.Mountpoint, usagePercent, im.policy.HighThresholdPercent, amountToFree) + freed, err := im.freeSpace(amountToFree, time.Now()) + if err != nil { + return err + } + + if freed < amountToFree { + err := fmt.Errorf("failed to garbage collect required amount of images. Wanted to free %d, but freed %d", amountToFree, freed) + im.recorder.Eventf(im.nodeRef, api.EventTypeWarning, container.FreeDiskSpaceFailed, err.Error()) + return err + } + } + + return nil +} + +// Tries to free bytesToFree worth of images on the disk. +// +// Returns the number of bytes free and an error if any occurred. The number of +// bytes freed is always returned. +// Note that error may be nil and the number of bytes free may be less +// than bytesToFree. +func (im *realImageManager) freeSpace(bytesToFree int64, freeTime time.Time) (int64, error) { + err := im.detectImages(freeTime) + if err != nil { + return 0, err + } + + im.imageRecordsLock.Lock() + defer im.imageRecordsLock.Unlock() + + // Get all images in eviction order. + images := make([]evictionInfo, 0, len(im.imageRecords)) + for image, record := range im.imageRecords { + images = append(images, evictionInfo{ + id: image, + imageRecord: *record, + }) + } + sort.Sort(byLastUsedAndDetected(images)) + + // Delete unused images until we've freed up enough space. + var lastErr error + spaceFreed := int64(0) + for _, image := range images { + // Images that are currently in used were given a newer lastUsed. + if image.lastUsed.After(freeTime) { + break + } + + // Avoid garbage collect the image if the image is not old enough. + // In such a case, the image may have just been pulled down, and will be used by a container right away. + + if freeTime.Sub(image.firstDetected) < im.policy.MinAge { + continue + } + + // Remove image. Continue despite errors. + glog.Infof("[ImageManager]: Removing image %q to free %d bytes", image.id, image.size) + err := im.runtime.RemoveImage(container.ImageSpec{Image: image.id}) + if err != nil { + lastErr = err + continue + } + delete(im.imageRecords, image.id) + spaceFreed += image.size + + if spaceFreed >= bytesToFree { + break + } + } + + return spaceFreed, lastErr +} + +type evictionInfo struct { + id string + imageRecord +} + +type byLastUsedAndDetected []evictionInfo + +func (ev byLastUsedAndDetected) Len() int { return len(ev) } +func (ev byLastUsedAndDetected) Swap(i, j int) { ev[i], ev[j] = ev[j], ev[i] } +func (ev byLastUsedAndDetected) Less(i, j int) bool { + // Sort by last used, break ties by detected. + if ev[i].lastUsed.Equal(ev[j].lastUsed) { + return ev[i].firstDetected.Before(ev[j].firstDetected) + } else { + return ev[i].lastUsed.Before(ev[j].lastUsed) + } +} + +func isImageUsed(image container.Image, imagesInUse sets.String) bool { + // Check the image ID and all the RepoTags. + if _, ok := imagesInUse[image.ID]; ok { + return true + } + for _, tag := range image.RepoTags { + if _, ok := imagesInUse[tag]; ok { + return true + } + } + return false +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/image_manager_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/image_manager_test.go new file mode 100644 index 000000000..2f2c88573 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/image_manager_test.go @@ -0,0 +1,451 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "fmt" + "testing" + "time" + + cadvisorapiv2 "github.com/google/cadvisor/info/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/kubernetes/pkg/client/record" + cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing" + "k8s.io/kubernetes/pkg/kubelet/container" + containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" + "k8s.io/kubernetes/pkg/util" +) + +var zero time.Time + +func newRealImageManager(policy ImageGCPolicy) (*realImageManager, *containertest.FakeRuntime, *cadvisortest.Mock) { + fakeRuntime := &containertest.FakeRuntime{} + mockCadvisor := new(cadvisortest.Mock) + return &realImageManager{ + runtime: fakeRuntime, + policy: policy, + imageRecords: make(map[string]*imageRecord), + cadvisor: mockCadvisor, + recorder: &record.FakeRecorder{}, + }, fakeRuntime, mockCadvisor +} + +// Accessors used for thread-safe testing. +func (im *realImageManager) imageRecordsLen() int { + im.imageRecordsLock.Lock() + defer im.imageRecordsLock.Unlock() + return len(im.imageRecords) +} +func (im *realImageManager) getImageRecord(name string) (*imageRecord, bool) { + im.imageRecordsLock.Lock() + defer im.imageRecordsLock.Unlock() + v, ok := im.imageRecords[name] + vCopy := *v + return &vCopy, ok +} + +// Returns the name of the image with the given ID. +func imageName(id int) string { + return fmt.Sprintf("image-%d", id) +} + +// Make an image with the specified ID. +func makeImage(id int, size int64) container.Image { + return container.Image{ + ID: imageName(id), + Size: size, + } +} + +// Make a container with the specified ID. It will use the image with the same ID. +func makeContainer(id int) *container.Container { + return &container.Container{ + ID: container.ContainerID{Type: "test", ID: fmt.Sprintf("container-%d", id)}, + Image: imageName(id), + } +} + +func TestDetectImagesInitialDetect(t *testing.T) { + manager, fakeRuntime, _ := newRealImageManager(ImageGCPolicy{}) + fakeRuntime.ImageList = []container.Image{ + makeImage(0, 1024), + makeImage(1, 2048), + } + fakeRuntime.AllPodList = []*container.Pod{ + { + Containers: []*container.Container{ + makeContainer(1), + }, + }, + } + + startTime := time.Now().Add(-time.Millisecond) + err := manager.detectImages(zero) + assert := assert.New(t) + require.NoError(t, err) + assert.Equal(manager.imageRecordsLen(), 2) + noContainer, ok := manager.getImageRecord(imageName(0)) + require.True(t, ok) + assert.Equal(zero, noContainer.firstDetected) + assert.Equal(zero, noContainer.lastUsed) + withContainer, ok := manager.getImageRecord(imageName(1)) + require.True(t, ok) + assert.Equal(zero, withContainer.firstDetected) + assert.True(withContainer.lastUsed.After(startTime)) +} + +func TestDetectImagesWithNewImage(t *testing.T) { + // Just one image initially. + manager, fakeRuntime, _ := newRealImageManager(ImageGCPolicy{}) + fakeRuntime.ImageList = []container.Image{ + makeImage(0, 1024), + makeImage(1, 2048), + } + fakeRuntime.AllPodList = []*container.Pod{ + { + Containers: []*container.Container{ + makeContainer(1), + }, + }, + } + + err := manager.detectImages(zero) + assert := assert.New(t) + require.NoError(t, err) + assert.Equal(manager.imageRecordsLen(), 2) + + // Add a new image. + fakeRuntime.ImageList = []container.Image{ + makeImage(0, 1024), + makeImage(1, 1024), + makeImage(2, 1024), + } + + detectedTime := zero.Add(time.Second) + startTime := time.Now().Add(-time.Millisecond) + err = manager.detectImages(detectedTime) + require.NoError(t, err) + assert.Equal(manager.imageRecordsLen(), 3) + noContainer, ok := manager.getImageRecord(imageName(0)) + require.True(t, ok) + assert.Equal(zero, noContainer.firstDetected) + assert.Equal(zero, noContainer.lastUsed) + withContainer, ok := manager.getImageRecord(imageName(1)) + require.True(t, ok) + assert.Equal(zero, withContainer.firstDetected) + assert.True(withContainer.lastUsed.After(startTime)) + newContainer, ok := manager.getImageRecord(imageName(2)) + require.True(t, ok) + assert.Equal(detectedTime, newContainer.firstDetected) + assert.Equal(zero, noContainer.lastUsed) +} + +func TestDetectImagesContainerStopped(t *testing.T) { + manager, fakeRuntime, _ := newRealImageManager(ImageGCPolicy{}) + fakeRuntime.ImageList = []container.Image{ + makeImage(0, 1024), + makeImage(1, 2048), + } + fakeRuntime.AllPodList = []*container.Pod{ + { + Containers: []*container.Container{ + makeContainer(1), + }, + }, + } + + err := manager.detectImages(zero) + assert := assert.New(t) + require.NoError(t, err) + assert.Equal(manager.imageRecordsLen(), 2) + withContainer, ok := manager.getImageRecord(imageName(1)) + require.True(t, ok) + + // Simulate container being stopped. + fakeRuntime.AllPodList = []*container.Pod{} + err = manager.detectImages(time.Now()) + require.NoError(t, err) + assert.Equal(manager.imageRecordsLen(), 2) + container1, ok := manager.getImageRecord(imageName(0)) + require.True(t, ok) + assert.Equal(zero, container1.firstDetected) + assert.Equal(zero, container1.lastUsed) + container2, ok := manager.getImageRecord(imageName(1)) + require.True(t, ok) + assert.Equal(zero, container2.firstDetected) + assert.True(container2.lastUsed.Equal(withContainer.lastUsed)) +} + +func TestDetectImagesWithRemovedImages(t *testing.T) { + manager, fakeRuntime, _ := newRealImageManager(ImageGCPolicy{}) + fakeRuntime.ImageList = []container.Image{ + makeImage(0, 1024), + makeImage(1, 2048), + } + fakeRuntime.AllPodList = []*container.Pod{ + { + Containers: []*container.Container{ + makeContainer(1), + }, + }, + } + + err := manager.detectImages(zero) + assert := assert.New(t) + require.NoError(t, err) + assert.Equal(manager.imageRecordsLen(), 2) + + // Simulate both images being removed. + fakeRuntime.ImageList = []container.Image{} + err = manager.detectImages(time.Now()) + require.NoError(t, err) + assert.Equal(manager.imageRecordsLen(), 0) +} + +func TestFreeSpaceImagesInUseContainersAreIgnored(t *testing.T) { + manager, fakeRuntime, _ := newRealImageManager(ImageGCPolicy{}) + fakeRuntime.ImageList = []container.Image{ + makeImage(0, 1024), + makeImage(1, 2048), + } + fakeRuntime.AllPodList = []*container.Pod{ + { + Containers: []*container.Container{ + makeContainer(1), + }, + }, + } + + spaceFreed, err := manager.freeSpace(2048, time.Now()) + assert := assert.New(t) + require.NoError(t, err) + assert.EqualValues(1024, spaceFreed) + assert.Len(fakeRuntime.ImageList, 1) +} + +func TestFreeSpaceRemoveByLeastRecentlyUsed(t *testing.T) { + manager, fakeRuntime, _ := newRealImageManager(ImageGCPolicy{}) + fakeRuntime.ImageList = []container.Image{ + makeImage(0, 1024), + makeImage(1, 2048), + } + fakeRuntime.AllPodList = []*container.Pod{ + { + Containers: []*container.Container{ + makeContainer(0), + makeContainer(1), + }, + }, + } + + // Make 1 be more recently used than 0. + require.NoError(t, manager.detectImages(zero)) + fakeRuntime.AllPodList = []*container.Pod{ + { + Containers: []*container.Container{ + makeContainer(1), + }, + }, + } + require.NoError(t, manager.detectImages(time.Now())) + fakeRuntime.AllPodList = []*container.Pod{ + { + Containers: []*container.Container{}, + }, + } + require.NoError(t, manager.detectImages(time.Now())) + require.Equal(t, manager.imageRecordsLen(), 2) + + spaceFreed, err := manager.freeSpace(1024, time.Now()) + assert := assert.New(t) + require.NoError(t, err) + assert.EqualValues(1024, spaceFreed) + assert.Len(fakeRuntime.ImageList, 1) +} + +func TestFreeSpaceTiesBrokenByDetectedTime(t *testing.T) { + manager, fakeRuntime, _ := newRealImageManager(ImageGCPolicy{}) + fakeRuntime.ImageList = []container.Image{ + makeImage(0, 1024), + } + fakeRuntime.AllPodList = []*container.Pod{ + { + Containers: []*container.Container{ + makeContainer(0), + }, + }, + } + + // Make 1 more recently detected but used at the same time as 0. + require.NoError(t, manager.detectImages(zero)) + fakeRuntime.ImageList = []container.Image{ + makeImage(0, 1024), + makeImage(1, 2048), + } + require.NoError(t, manager.detectImages(time.Now())) + fakeRuntime.AllPodList = []*container.Pod{} + require.NoError(t, manager.detectImages(time.Now())) + require.Equal(t, manager.imageRecordsLen(), 2) + + spaceFreed, err := manager.freeSpace(1024, time.Now()) + assert := assert.New(t) + require.NoError(t, err) + assert.EqualValues(2048, spaceFreed) + assert.Len(fakeRuntime.ImageList, 1) +} + +func TestFreeSpaceImagesAlsoDoesLookupByRepoTags(t *testing.T) { + manager, fakeRuntime, _ := newRealImageManager(ImageGCPolicy{}) + fakeRuntime.ImageList = []container.Image{ + makeImage(0, 1024), + { + ID: "5678", + RepoTags: []string{"potato", "salad"}, + Size: 2048, + }, + } + fakeRuntime.AllPodList = []*container.Pod{ + { + Containers: []*container.Container{ + { + ID: container.ContainerID{Type: "test", ID: "c5678"}, + Image: "salad", + }, + }, + }, + } + + spaceFreed, err := manager.freeSpace(1024, time.Now()) + assert := assert.New(t) + require.NoError(t, err) + assert.EqualValues(1024, spaceFreed) + assert.Len(fakeRuntime.ImageList, 1) +} + +func TestGarbageCollectBelowLowThreshold(t *testing.T) { + policy := ImageGCPolicy{ + HighThresholdPercent: 90, + LowThresholdPercent: 80, + } + manager, _, mockCadvisor := newRealImageManager(policy) + + // Expect 40% usage. + mockCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{ + Usage: 400, + Capacity: 1000, + }, nil) + + assert.NoError(t, manager.GarbageCollect()) +} + +func TestGarbageCollectCadvisorFailure(t *testing.T) { + policy := ImageGCPolicy{ + HighThresholdPercent: 90, + LowThresholdPercent: 80, + } + manager, _, mockCadvisor := newRealImageManager(policy) + + mockCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{}, fmt.Errorf("error")) + assert.NotNil(t, manager.GarbageCollect()) +} + +func TestGarbageCollectBelowSuccess(t *testing.T) { + policy := ImageGCPolicy{ + HighThresholdPercent: 90, + LowThresholdPercent: 80, + } + manager, fakeRuntime, mockCadvisor := newRealImageManager(policy) + + // Expect 95% usage and most of it gets freed. + mockCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{ + Usage: 950, + Capacity: 1000, + }, nil) + fakeRuntime.ImageList = []container.Image{ + makeImage(0, 450), + } + + assert.NoError(t, manager.GarbageCollect()) +} + +func TestGarbageCollectNotEnoughFreed(t *testing.T) { + policy := ImageGCPolicy{ + HighThresholdPercent: 90, + LowThresholdPercent: 80, + } + manager, fakeRuntime, mockCadvisor := newRealImageManager(policy) + + // Expect 95% usage and little of it gets freed. + mockCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{ + Usage: 950, + Capacity: 1000, + }, nil) + fakeRuntime.ImageList = []container.Image{ + makeImage(0, 50), + } + + assert.NotNil(t, manager.GarbageCollect()) +} + +func TestGarbageCollectImageNotOldEnough(t *testing.T) { + policy := ImageGCPolicy{ + HighThresholdPercent: 90, + LowThresholdPercent: 80, + MinAge: time.Minute * 1, + } + fakeRuntime := &containertest.FakeRuntime{} + mockCadvisor := new(cadvisortest.Mock) + manager := &realImageManager{ + runtime: fakeRuntime, + policy: policy, + imageRecords: make(map[string]*imageRecord), + cadvisor: mockCadvisor, + recorder: &record.FakeRecorder{}, + } + + fakeRuntime.ImageList = []container.Image{ + makeImage(0, 1024), + makeImage(1, 2048), + } + // 1 image is in use, and another one is not old enough + fakeRuntime.AllPodList = []*container.Pod{ + { + Containers: []*container.Container{ + makeContainer(1), + }, + }, + } + + fakeClock := util.NewFakeClock(time.Now()) + t.Log(fakeClock.Now()) + require.NoError(t, manager.detectImages(fakeClock.Now())) + require.Equal(t, manager.imageRecordsLen(), 2) + // no space freed since one image is in used, and another one is not old enough + spaceFreed, err := manager.freeSpace(1024, fakeClock.Now()) + assert := assert.New(t) + require.NoError(t, err) + assert.EqualValues(0, spaceFreed) + assert.Len(fakeRuntime.ImageList, 2) + + // move clock by minAge duration, then 1 image will be garbage collected + fakeClock.Step(policy.MinAge) + spaceFreed, err = manager.freeSpace(1024, fakeClock.Now()) + require.NoError(t, err) + assert.EqualValues(1024, spaceFreed) + assert.Len(fakeRuntime.ImageList, 1) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/kubelet.go b/vendor/k8s.io/kubernetes/pkg/kubelet/kubelet.go new file mode 100644 index 000000000..54099d7e4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/kubelet.go @@ -0,0 +1,3682 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "os" + "path" + "path/filepath" + "sort" + "strings" + "sync" + "time" + + "github.com/golang/glog" + cadvisorapi "github.com/google/cadvisor/info/v1" + cadvisorapiv2 "github.com/google/cadvisor/info/v2" + "k8s.io/kubernetes/pkg/api" + apierrors "k8s.io/kubernetes/pkg/api/errors" + utilpod "k8s.io/kubernetes/pkg/api/pod" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/apis/componentconfig" + "k8s.io/kubernetes/pkg/client/cache" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/cloudprovider" + "k8s.io/kubernetes/pkg/fieldpath" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/kubelet/cadvisor" + "k8s.io/kubernetes/pkg/kubelet/cm" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/dockertools" + "k8s.io/kubernetes/pkg/kubelet/envvars" + "k8s.io/kubernetes/pkg/kubelet/metrics" + "k8s.io/kubernetes/pkg/kubelet/network" + "k8s.io/kubernetes/pkg/kubelet/pleg" + kubepod "k8s.io/kubernetes/pkg/kubelet/pod" + "k8s.io/kubernetes/pkg/kubelet/prober" + proberesults "k8s.io/kubernetes/pkg/kubelet/prober/results" + "k8s.io/kubernetes/pkg/kubelet/rkt" + "k8s.io/kubernetes/pkg/kubelet/server" + "k8s.io/kubernetes/pkg/kubelet/server/stats" + "k8s.io/kubernetes/pkg/kubelet/status" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/kubelet/util/format" + "k8s.io/kubernetes/pkg/kubelet/util/queue" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/securitycontext" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/atomic" + "k8s.io/kubernetes/pkg/util/bandwidth" + utilerrors "k8s.io/kubernetes/pkg/util/errors" + "k8s.io/kubernetes/pkg/util/flowcontrol" + kubeio "k8s.io/kubernetes/pkg/util/io" + "k8s.io/kubernetes/pkg/util/mount" + utilnet "k8s.io/kubernetes/pkg/util/net" + nodeutil "k8s.io/kubernetes/pkg/util/node" + "k8s.io/kubernetes/pkg/util/oom" + "k8s.io/kubernetes/pkg/util/procfs" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/selinux" + "k8s.io/kubernetes/pkg/util/sets" + utilvalidation "k8s.io/kubernetes/pkg/util/validation" + "k8s.io/kubernetes/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/version" + "k8s.io/kubernetes/pkg/volume" + "k8s.io/kubernetes/pkg/watch" + "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm/predicates" + "k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache" + "k8s.io/kubernetes/third_party/golang/expansion" +) + +const ( + // Max amount of time to wait for the container runtime to come up. + maxWaitForContainerRuntime = 5 * time.Minute + + // nodeStatusUpdateRetry specifies how many times kubelet retries when posting node status failed. + nodeStatusUpdateRetry = 5 + + // Location of container logs. + containerLogsDir = "/var/log/containers" + + // max backoff period, exported for the e2e test + MaxContainerBackOff = 300 * time.Second + + // Capacity of the channel for storing pods to kill. A small number should + // suffice because a goroutine is dedicated to check the channel and does + // not block on anything else. + podKillingChannelCapacity = 50 + + // Period for performing global cleanup tasks. + housekeepingPeriod = time.Second * 2 + + etcHostsPath = "/etc/hosts" + + // Capacity of the channel for receiving pod lifecycle events. This number + // is a bit arbitrary and may be adjusted in the future. + plegChannelCapacity = 1000 + + // Generic PLEG relies on relisting for discovering container events. + // A longer period means that kubelet will take longer to detect container + // changes and to update pod status. On the other hand, a shorter period + // will cause more frequent relisting (e.g., container runtime operations), + // leading to higher cpu usage. + // Note that even though we set the period to 1s, the relisting itself can + // take more than 1s to finish if the container runtime responds slowly + // and/or when there are many container changes in one cycle. + plegRelistPeriod = time.Second * 1 + + // backOffPeriod is the period to back off when pod syncing resulting in an + // error. It is also used as the base period for the exponential backoff + // container restarts and image pulls. + backOffPeriod = time.Second * 10 + + // Period for performing container garbage collection. + ContainerGCPeriod = time.Minute + // Period for performing image garbage collection. + ImageGCPeriod = 5 * time.Minute +) + +// SyncHandler is an interface implemented by Kubelet, for testability +type SyncHandler interface { + HandlePodAdditions(pods []*api.Pod) + HandlePodUpdates(pods []*api.Pod) + HandlePodDeletions(pods []*api.Pod) + HandlePodReconcile(pods []*api.Pod) + HandlePodSyncs(pods []*api.Pod) + HandlePodCleanups() error +} + +type SourcesReadyFn func(sourcesSeen sets.String) bool + +// Option is a functional option type for Kubelet +type Option func(*Kubelet) + +// New instantiates a new Kubelet object along with all the required internal modules. +// No initialization of Kubelet and its modules should happen here. +func NewMainKubelet( + hostname string, + nodeName string, + dockerClient dockertools.DockerInterface, + kubeClient clientset.Interface, + rootDirectory string, + podInfraContainerImage string, + resyncInterval time.Duration, + pullQPS float32, + pullBurst int, + eventQPS float32, + eventBurst int, + containerGCPolicy kubecontainer.ContainerGCPolicy, + sourcesReady SourcesReadyFn, + registerNode bool, + registerSchedulable bool, + standaloneMode bool, + clusterDomain string, + clusterDNS net.IP, + masterServiceNamespace string, + volumePlugins []volume.VolumePlugin, + networkPlugins []network.NetworkPlugin, + networkPluginName string, + streamingConnectionIdleTimeout time.Duration, + recorder record.EventRecorder, + cadvisorInterface cadvisor.Interface, + imageGCPolicy ImageGCPolicy, + diskSpacePolicy DiskSpacePolicy, + cloud cloudprovider.Interface, + nodeLabels map[string]string, + nodeStatusUpdateFrequency time.Duration, + osInterface kubecontainer.OSInterface, + cgroupRoot string, + containerRuntime string, + rktPath string, + rktAPIEndpoint string, + rktStage1Image string, + mounter mount.Interface, + writer kubeio.Writer, + configureCBR0 bool, + nonMasqueradeCIDR string, + podCIDR string, + reconcileCIDR bool, + maxPods int, + dockerExecHandler dockertools.ExecHandler, + resolverConfig string, + cpuCFSQuota bool, + daemonEndpoints *api.NodeDaemonEndpoints, + oomAdjuster *oom.OOMAdjuster, + serializeImagePulls bool, + containerManager cm.ContainerManager, + outOfDiskTransitionFrequency time.Duration, + flannelExperimentalOverlay bool, + nodeIP net.IP, + reservation kubetypes.Reservation, + enableCustomMetrics bool, + volumeStatsAggPeriod time.Duration, + containerRuntimeOptions []kubecontainer.Option, + hairpinMode string, + babysitDaemons bool, + kubeOptions []Option, +) (*Kubelet, error) { + if rootDirectory == "" { + return nil, fmt.Errorf("invalid root directory %q", rootDirectory) + } + if resyncInterval <= 0 { + return nil, fmt.Errorf("invalid sync frequency %d", resyncInterval) + } + + serviceStore := cache.NewStore(cache.MetaNamespaceKeyFunc) + if kubeClient != nil { + // TODO: cache.NewListWatchFromClient is limited as it takes a client implementation rather + // than an interface. There is no way to construct a list+watcher using resource name. + listWatch := &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + return kubeClient.Core().Services(api.NamespaceAll).List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + return kubeClient.Core().Services(api.NamespaceAll).Watch(options) + }, + } + cache.NewReflector(listWatch, &api.Service{}, serviceStore, 0).Run() + } + serviceLister := &cache.StoreToServiceLister{Store: serviceStore} + + nodeStore := cache.NewStore(cache.MetaNamespaceKeyFunc) + if kubeClient != nil { + // TODO: cache.NewListWatchFromClient is limited as it takes a client implementation rather + // than an interface. There is no way to construct a list+watcher using resource name. + fieldSelector := fields.Set{api.ObjectNameField: nodeName}.AsSelector() + listWatch := &cache.ListWatch{ + ListFunc: func(options api.ListOptions) (runtime.Object, error) { + options.FieldSelector = fieldSelector + return kubeClient.Core().Nodes().List(options) + }, + WatchFunc: func(options api.ListOptions) (watch.Interface, error) { + options.FieldSelector = fieldSelector + return kubeClient.Core().Nodes().Watch(options) + }, + } + cache.NewReflector(listWatch, &api.Node{}, nodeStore, 0).Run() + } + nodeLister := &cache.StoreToNodeLister{Store: nodeStore} + nodeInfo := &predicates.CachedNodeInfo{StoreToNodeLister: nodeLister} + + // TODO: get the real node object of ourself, + // and use the real node name and UID. + // TODO: what is namespace for node? + nodeRef := &api.ObjectReference{ + Kind: "Node", + Name: nodeName, + UID: types.UID(nodeName), + Namespace: "", + } + + diskSpaceManager, err := newDiskSpaceManager(cadvisorInterface, diskSpacePolicy) + if err != nil { + return nil, fmt.Errorf("failed to initialize disk manager: %v", err) + } + containerRefManager := kubecontainer.NewRefManager() + + volumeManager := newVolumeManager() + + oomWatcher := NewOOMWatcher(cadvisorInterface, recorder) + + // TODO: remove when internal cbr0 implementation gets removed in favor + // of the kubenet network plugin + if networkPluginName == "kubenet" { + configureCBR0 = false + flannelExperimentalOverlay = false + } + + klet := &Kubelet{ + hostname: hostname, + nodeName: nodeName, + dockerClient: dockerClient, + kubeClient: kubeClient, + rootDirectory: rootDirectory, + resyncInterval: resyncInterval, + containerRefManager: containerRefManager, + httpClient: &http.Client{}, + sourcesReady: sourcesReady, + registerNode: registerNode, + registerSchedulable: registerSchedulable, + standaloneMode: standaloneMode, + clusterDomain: clusterDomain, + clusterDNS: clusterDNS, + serviceLister: serviceLister, + nodeLister: nodeLister, + nodeInfo: nodeInfo, + masterServiceNamespace: masterServiceNamespace, + streamingConnectionIdleTimeout: streamingConnectionIdleTimeout, + recorder: recorder, + cadvisor: cadvisorInterface, + diskSpaceManager: diskSpaceManager, + volumeManager: volumeManager, + cloud: cloud, + nodeRef: nodeRef, + nodeLabels: nodeLabels, + nodeStatusUpdateFrequency: nodeStatusUpdateFrequency, + os: osInterface, + oomWatcher: oomWatcher, + cgroupRoot: cgroupRoot, + mounter: mounter, + writer: writer, + configureCBR0: configureCBR0, + nonMasqueradeCIDR: nonMasqueradeCIDR, + reconcileCIDR: reconcileCIDR, + maxPods: maxPods, + syncLoopMonitor: atomic.Value{}, + resolverConfig: resolverConfig, + cpuCFSQuota: cpuCFSQuota, + daemonEndpoints: daemonEndpoints, + containerManager: containerManager, + flannelExperimentalOverlay: flannelExperimentalOverlay, + flannelHelper: NewFlannelHelper(), + nodeIP: nodeIP, + clock: util.RealClock{}, + outOfDiskTransitionFrequency: outOfDiskTransitionFrequency, + reservation: reservation, + enableCustomMetrics: enableCustomMetrics, + babysitDaemons: babysitDaemons, + } + // TODO: Factor out "StatsProvider" from Kubelet so we don't have a cyclic dependency + klet.resourceAnalyzer = stats.NewResourceAnalyzer(klet, volumeStatsAggPeriod) + + if klet.flannelExperimentalOverlay { + glog.Infof("Flannel is in charge of podCIDR and overlay networking.") + } + if klet.nodeIP != nil { + if err := klet.validateNodeIP(); err != nil { + return nil, err + } + glog.Infof("Using node IP: %q", klet.nodeIP.String()) + } + if plug, err := network.InitNetworkPlugin(networkPlugins, networkPluginName, &networkHost{klet}); err != nil { + return nil, err + } else { + klet.networkPlugin = plug + } + + machineInfo, err := klet.GetCachedMachineInfo() + if err != nil { + return nil, err + } + + procFs := procfs.NewProcFS() + imageBackOff := flowcontrol.NewBackOff(backOffPeriod, MaxContainerBackOff) + + klet.livenessManager = proberesults.NewManager() + + klet.podCache = kubecontainer.NewCache() + klet.podManager = kubepod.NewBasicPodManager(kubepod.NewBasicMirrorClient(klet.kubeClient)) + + if mode, err := effectiveHairpinMode(componentconfig.HairpinMode(hairpinMode), containerRuntime, configureCBR0); err != nil { + // This is a non-recoverable error. Returning it up the callstack will just + // lead to retries of the same failure, so just fail hard. + glog.Fatalf("Invalid hairpin mode: %v", err) + } else { + klet.hairpinMode = mode + } + glog.Infof("Hairpin mode set to %q", klet.hairpinMode) + + // Initialize the runtime. + switch containerRuntime { + case "docker": + // Only supported one for now, continue. + klet.containerRuntime = dockertools.NewDockerManager( + dockerClient, + kubecontainer.FilterEventRecorder(recorder), + klet.livenessManager, + containerRefManager, + klet.podManager, + machineInfo, + podInfraContainerImage, + pullQPS, + pullBurst, + containerLogsDir, + osInterface, + klet.networkPlugin, + klet, + klet.httpClient, + dockerExecHandler, + oomAdjuster, + procFs, + klet.cpuCFSQuota, + imageBackOff, + serializeImagePulls, + enableCustomMetrics, + klet.hairpinMode == componentconfig.HairpinVeth, + containerRuntimeOptions..., + ) + case "rkt": + // TODO: Include hairpin mode settings in rkt? + conf := &rkt.Config{ + Path: rktPath, + Stage1Image: rktStage1Image, + InsecureOptions: "image,ondisk", + } + rktRuntime, err := rkt.New( + rktAPIEndpoint, + conf, + klet, + recorder, + containerRefManager, + klet.livenessManager, + klet.volumeManager, + imageBackOff, + serializeImagePulls, + ) + if err != nil { + return nil, err + } + klet.containerRuntime = rktRuntime + default: + return nil, fmt.Errorf("unsupported container runtime %q specified", containerRuntime) + } + + klet.pleg = pleg.NewGenericPLEG(klet.containerRuntime, plegChannelCapacity, plegRelistPeriod, klet.podCache, util.RealClock{}) + klet.runtimeState = newRuntimeState(maxWaitForContainerRuntime, configureCBR0) + klet.updatePodCIDR(podCIDR) + + // setup containerGC + containerGC, err := kubecontainer.NewContainerGC(klet.containerRuntime, containerGCPolicy) + if err != nil { + return nil, err + } + klet.containerGC = containerGC + + // setup imageManager + imageManager, err := newImageManager(klet.containerRuntime, cadvisorInterface, recorder, nodeRef, imageGCPolicy) + if err != nil { + return nil, fmt.Errorf("failed to initialize image manager: %v", err) + } + klet.imageManager = imageManager + + klet.runner = klet.containerRuntime + klet.statusManager = status.NewManager(kubeClient, klet.podManager) + + klet.probeManager = prober.NewManager( + klet.statusManager, + klet.livenessManager, + klet.runner, + containerRefManager, + recorder) + + if err := klet.volumePluginMgr.InitPlugins(volumePlugins, &volumeHost{klet}); err != nil { + return nil, err + } + + runtimeCache, err := kubecontainer.NewRuntimeCache(klet.containerRuntime) + if err != nil { + return nil, err + } + klet.runtimeCache = runtimeCache + klet.reasonCache = NewReasonCache() + klet.workQueue = queue.NewBasicWorkQueue() + klet.podWorkers = newPodWorkers(klet.syncPod, recorder, klet.workQueue, klet.resyncInterval, backOffPeriod, klet.podCache) + + klet.backOff = flowcontrol.NewBackOff(backOffPeriod, MaxContainerBackOff) + klet.podKillingCh = make(chan *kubecontainer.PodPair, podKillingChannelCapacity) + klet.sourcesSeen = sets.NewString() + klet.setNodeStatusFuncs = klet.defaultNodeStatusFuncs() + + // apply functional Option's + for _, opt := range kubeOptions { + opt(klet) + } + return klet, nil +} + +func effectiveHairpinMode(hairpinMode componentconfig.HairpinMode, containerRuntime string, configureCBR0 bool) (componentconfig.HairpinMode, error) { + // The hairpin mode setting doesn't matter if: + // - We're not using a bridge network. This is hard to check because we might + // be using a plugin. It matters if --configure-cbr0=true, and we currently + // don't pipe it down to any plugins. + // - It's set to hairpin-veth for a container runtime that doesn't know how + // to set the hairpin flag on the veth's of containers. Currently the + // docker runtime is the only one that understands this. + // - It's set to "none". + if hairpinMode == componentconfig.PromiscuousBridge || hairpinMode == componentconfig.HairpinVeth { + // Only on docker. + if containerRuntime != "docker" { + glog.Warningf("Hairpin mode set to %q but container runtime is %q, ignoring", hairpinMode, containerRuntime) + return componentconfig.HairpinNone, nil + } + if hairpinMode == componentconfig.PromiscuousBridge && !configureCBR0 { + // This is not a valid combination. Users might be using the + // default values (from before the hairpin-mode flag existed) and we + // should keep the old behavior. + glog.Warningf("Hairpin mode set to %q but configureCBR0 is false, falling back to %q", hairpinMode, componentconfig.HairpinVeth) + return componentconfig.HairpinVeth, nil + } + } else if hairpinMode == componentconfig.HairpinNone { + if configureCBR0 { + glog.Warningf("Hairpin mode set to %q and configureCBR0 is true, this might result in loss of hairpin packets", hairpinMode) + } + } else { + return "", fmt.Errorf("unknown value: %q", hairpinMode) + } + return hairpinMode, nil +} + +type serviceLister interface { + List() (api.ServiceList, error) +} + +type nodeLister interface { + List() (machines api.NodeList, err error) +} + +// Kubelet is the main kubelet implementation. +type Kubelet struct { + hostname string + nodeName string + dockerClient dockertools.DockerInterface + runtimeCache kubecontainer.RuntimeCache + kubeClient clientset.Interface + rootDirectory string + podWorkers PodWorkers + + resyncInterval time.Duration + sourcesReady SourcesReadyFn + // sourcesSeen records the sources seen by kubelet. This set is not thread + // safe and should only be access by the main kubelet syncloop goroutine. + sourcesSeen sets.String + + podManager kubepod.Manager + + // Needed to report events for containers belonging to deleted/modified pods. + // Tracks references for reporting events + containerRefManager *kubecontainer.RefManager + + // Optional, defaults to /logs/ from /var/log + logServer http.Handler + // Optional, defaults to simple Docker implementation + runner kubecontainer.ContainerCommandRunner + // Optional, client for http requests, defaults to empty client + httpClient kubetypes.HttpGetter + + // cAdvisor used for container information. + cadvisor cadvisor.Interface + + // Set to true to have the node register itself with the apiserver. + registerNode bool + // Set to true to have the node register itself as schedulable. + registerSchedulable bool + // for internal book keeping; access only from within registerWithApiserver + registrationCompleted bool + + // Set to true if the kubelet is in standalone mode (i.e. setup without an apiserver) + standaloneMode bool + + // If non-empty, use this for container DNS search. + clusterDomain string + + // If non-nil, use this for container DNS server. + clusterDNS net.IP + + masterServiceNamespace string + serviceLister serviceLister + nodeLister nodeLister + nodeInfo predicates.NodeInfo + + // a list of node labels to register + nodeLabels map[string]string + + // Last timestamp when runtime responded on ping. + // Mutex is used to protect this value. + runtimeState *runtimeState + + // Volume plugins. + volumePluginMgr volume.VolumePluginMgr + + // Network plugin. + networkPlugin network.NetworkPlugin + + // Handles container probing. + probeManager prober.Manager + // Manages container health check results. + livenessManager proberesults.Manager + + // How long to keep idle streaming command execution/port forwarding + // connections open before terminating them + streamingConnectionIdleTimeout time.Duration + + // The EventRecorder to use + recorder record.EventRecorder + + // Policy for handling garbage collection of dead containers. + containerGC kubecontainer.ContainerGC + + // Manager for images. + imageManager imageManager + + // Diskspace manager. + diskSpaceManager diskSpaceManager + + // Cached MachineInfo returned by cadvisor. + machineInfo *cadvisorapi.MachineInfo + + // Syncs pods statuses with apiserver; also used as a cache of statuses. + statusManager status.Manager + + // Manager for the volume maps for the pods. + volumeManager *volumeManager + + //Cloud provider interface + cloud cloudprovider.Interface + + // Reference to this node. + nodeRef *api.ObjectReference + + // Container runtime. + containerRuntime kubecontainer.Runtime + + // reasonCache caches the failure reason of the last creation of all containers, which is + // used for generating ContainerStatus. + reasonCache *ReasonCache + + // nodeStatusUpdateFrequency specifies how often kubelet posts node status to master. + // Note: be cautious when changing the constant, it must work with nodeMonitorGracePeriod + // in nodecontroller. There are several constraints: + // 1. nodeMonitorGracePeriod must be N times more than nodeStatusUpdateFrequency, where + // N means number of retries allowed for kubelet to post node status. It is pointless + // to make nodeMonitorGracePeriod be less than nodeStatusUpdateFrequency, since there + // will only be fresh values from Kubelet at an interval of nodeStatusUpdateFrequency. + // The constant must be less than podEvictionTimeout. + // 2. nodeStatusUpdateFrequency needs to be large enough for kubelet to generate node + // status. Kubelet may fail to update node status reliably if the value is too small, + // as it takes time to gather all necessary node information. + nodeStatusUpdateFrequency time.Duration + + // Generates pod events. + pleg pleg.PodLifecycleEventGenerator + + // Store kubecontainer.PodStatus for all pods. + podCache kubecontainer.Cache + + os kubecontainer.OSInterface + + // Watcher of out of memory events. + oomWatcher OOMWatcher + + // Monitor resource usage + resourceAnalyzer stats.ResourceAnalyzer + + // If non-empty, pass this to the container runtime as the root cgroup. + cgroupRoot string + + // Mounter to use for volumes. + mounter mount.Interface + + // Writer interface to use for volumes. + writer kubeio.Writer + + // Manager of non-Runtime containers. + containerManager cm.ContainerManager + nodeConfig cm.NodeConfig + + // Whether or not kubelet should take responsibility for keeping cbr0 in + // the correct state. + configureCBR0 bool + reconcileCIDR bool + + // Traffic to IPs outside this range will use IP masquerade. + nonMasqueradeCIDR string + + // Maximum Number of Pods which can be run by this Kubelet + maxPods int + + // Monitor Kubelet's sync loop + syncLoopMonitor atomic.Value + + // Container restart Backoff + backOff *flowcontrol.Backoff + + // Channel for sending pods to kill. + podKillingCh chan *kubecontainer.PodPair + + // The configuration file used as the base to generate the container's + // DNS resolver configuration file. This can be used in conjunction with + // clusterDomain and clusterDNS. + resolverConfig string + + // Optionally shape the bandwidth of a pod + // TODO: remove when kubenet plugin is ready + shaper bandwidth.BandwidthShaper + + // True if container cpu limits should be enforced via cgroup CFS quota + cpuCFSQuota bool + + // Information about the ports which are opened by daemons on Node running this Kubelet server. + daemonEndpoints *api.NodeDaemonEndpoints + + // A queue used to trigger pod workers. + workQueue queue.WorkQueue + + // oneTimeInitializer is used to initialize modules that are dependent on the runtime to be up. + oneTimeInitializer sync.Once + + flannelExperimentalOverlay bool + + // TODO: Flannelhelper doesn't store any state, we can instantiate it + // on the fly if we're confident the dbus connetions it opens doesn't + // put the system under duress. + flannelHelper *FlannelHelper + + // If non-nil, use this IP address for the node + nodeIP net.IP + + // clock is an interface that provides time related functionality in a way that makes it + // easy to test the code. + clock util.Clock + + // outOfDiskTransitionFrequency specifies the amount of time the kubelet has to be actually + // not out of disk before it can transition the node condition status from out-of-disk to + // not-out-of-disk. This prevents a pod that causes out-of-disk condition from repeatedly + // getting rescheduled onto the node. + outOfDiskTransitionFrequency time.Duration + + // reservation specifies resources which are reserved for non-pod usage, including kubernetes and + // non-kubernetes system processes. + reservation kubetypes.Reservation + + // support gathering custom metrics. + enableCustomMetrics bool + + // How the Kubelet should setup hairpin NAT. Can take the values: "promiscuous-bridge" + // (make cbr0 promiscuous), "hairpin-veth" (set the hairpin flag on veth interfaces) + // or "none" (do nothing). + hairpinMode componentconfig.HairpinMode + + // The node has babysitter process monitoring docker and kubelet + babysitDaemons bool + + // handlers called during the tryUpdateNodeStatus cycle + setNodeStatusFuncs []func(*api.Node) error +} + +// Validate given node IP belongs to the current host +func (kl *Kubelet) validateNodeIP() error { + if kl.nodeIP == nil { + return nil + } + + // Honor IP limitations set in setNodeStatus() + if kl.nodeIP.IsLoopback() { + return fmt.Errorf("nodeIP can't be loopback address") + } + if kl.nodeIP.To4() == nil { + return fmt.Errorf("nodeIP must be IPv4 address") + } + + addrs, err := net.InterfaceAddrs() + if err != nil { + return err + } + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if ip != nil && ip.Equal(kl.nodeIP) { + return nil + } + } + return fmt.Errorf("Node IP: %q not found in the host's network interfaces", kl.nodeIP.String()) +} + +func (kl *Kubelet) allSourcesReady() bool { + // Make a copy of the sourcesSeen list because it's not thread-safe. + return kl.sourcesReady(sets.NewString(kl.sourcesSeen.List()...)) +} + +func (kl *Kubelet) addSource(source string) { + kl.sourcesSeen.Insert(source) +} + +// getRootDir returns the full path to the directory under which kubelet can +// store data. These functions are useful to pass interfaces to other modules +// that may need to know where to write data without getting a whole kubelet +// instance. +func (kl *Kubelet) getRootDir() string { + return kl.rootDirectory +} + +// getPodsDir returns the full path to the directory under which pod +// directories are created. +func (kl *Kubelet) getPodsDir() string { + return path.Join(kl.getRootDir(), "pods") +} + +// getPluginsDir returns the full path to the directory under which plugin +// directories are created. Plugins can use these directories for data that +// they need to persist. Plugins should create subdirectories under this named +// after their own names. +func (kl *Kubelet) getPluginsDir() string { + return path.Join(kl.getRootDir(), "plugins") +} + +// getPluginDir returns a data directory name for a given plugin name. +// Plugins can use these directories to store data that they need to persist. +// For per-pod plugin data, see getPodPluginDir. +func (kl *Kubelet) getPluginDir(pluginName string) string { + return path.Join(kl.getPluginsDir(), pluginName) +} + +// getPodDir returns the full path to the per-pod data directory for the +// specified pod. This directory may not exist if the pod does not exist. +func (kl *Kubelet) getPodDir(podUID types.UID) string { + // Backwards compat. The "old" stuff should be removed before 1.0 + // release. The thinking here is this: + // !old && !new = use new + // !old && new = use new + // old && !new = use old + // old && new = use new (but warn) + oldPath := path.Join(kl.getRootDir(), string(podUID)) + oldExists := dirExists(oldPath) + newPath := path.Join(kl.getPodsDir(), string(podUID)) + newExists := dirExists(newPath) + if oldExists && !newExists { + return oldPath + } + if oldExists { + glog.Warningf("Data dir for pod %q exists in both old and new form, using new", podUID) + } + return newPath +} + +// getPodVolumesDir returns the full path to the per-pod data directory under +// which volumes are created for the specified pod. This directory may not +// exist if the pod does not exist. +func (kl *Kubelet) getPodVolumesDir(podUID types.UID) string { + return path.Join(kl.getPodDir(podUID), "volumes") +} + +// getPodVolumeDir returns the full path to the directory which represents the +// named volume under the named plugin for specified pod. This directory may not +// exist if the pod does not exist. +func (kl *Kubelet) getPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string { + return path.Join(kl.getPodVolumesDir(podUID), pluginName, volumeName) +} + +// getPodPluginsDir returns the full path to the per-pod data directory under +// which plugins may store data for the specified pod. This directory may not +// exist if the pod does not exist. +func (kl *Kubelet) getPodPluginsDir(podUID types.UID) string { + return path.Join(kl.getPodDir(podUID), "plugins") +} + +// getPodPluginDir returns a data directory name for a given plugin name for a +// given pod UID. Plugins can use these directories to store data that they +// need to persist. For non-per-pod plugin data, see getPluginDir. +func (kl *Kubelet) getPodPluginDir(podUID types.UID, pluginName string) string { + return path.Join(kl.getPodPluginsDir(podUID), pluginName) +} + +// getPodContainerDir returns the full path to the per-pod data directory under +// which container data is held for the specified pod. This directory may not +// exist if the pod or container does not exist. +func (kl *Kubelet) getPodContainerDir(podUID types.UID, ctrName string) string { + // Backwards compat. The "old" stuff should be removed before 1.0 + // release. The thinking here is this: + // !old && !new = use new + // !old && new = use new + // old && !new = use old + // old && new = use new (but warn) + oldPath := path.Join(kl.getPodDir(podUID), ctrName) + oldExists := dirExists(oldPath) + newPath := path.Join(kl.getPodDir(podUID), "containers", ctrName) + newExists := dirExists(newPath) + if oldExists && !newExists { + return oldPath + } + if oldExists { + glog.Warningf("Data dir for pod %q, container %q exists in both old and new form, using new", podUID, ctrName) + } + return newPath +} + +func dirExists(path string) bool { + s, err := os.Stat(path) + if err != nil { + return false + } + return s.IsDir() +} + +func (kl *Kubelet) setupDataDirs() error { + kl.rootDirectory = path.Clean(kl.rootDirectory) + if err := os.MkdirAll(kl.getRootDir(), 0750); err != nil { + return fmt.Errorf("error creating root directory: %v", err) + } + if err := os.MkdirAll(kl.getPodsDir(), 0750); err != nil { + return fmt.Errorf("error creating pods directory: %v", err) + } + if err := os.MkdirAll(kl.getPluginsDir(), 0750); err != nil { + return fmt.Errorf("error creating plugins directory: %v", err) + } + return nil +} + +// Get a list of pods that have data directories. +func (kl *Kubelet) listPodsFromDisk() ([]types.UID, error) { + podInfos, err := ioutil.ReadDir(kl.getPodsDir()) + if err != nil { + return nil, err + } + pods := []types.UID{} + for i := range podInfos { + if podInfos[i].IsDir() { + pods = append(pods, types.UID(podInfos[i].Name())) + } + } + return pods, nil +} + +func (kl *Kubelet) GetNode() (*api.Node, error) { + if kl.standaloneMode { + return kl.initialNodeStatus() + } + return kl.nodeInfo.GetNodeInfo(kl.nodeName) +} + +// Starts garbage collection threads. +func (kl *Kubelet) StartGarbageCollection() { + go wait.Until(func() { + if err := kl.containerGC.GarbageCollect(); err != nil { + glog.Errorf("Container garbage collection failed: %v", err) + } + }, ContainerGCPeriod, wait.NeverStop) + + go wait.Until(func() { + if err := kl.imageManager.GarbageCollect(); err != nil { + glog.Errorf("Image garbage collection failed: %v", err) + } + }, ImageGCPeriod, wait.NeverStop) +} + +// initializeModules will initialize internal modules that do not require the container runtime to be up. +// Note that the modules here must not depend on modules that are not initialized here. +func (kl *Kubelet) initializeModules() error { + // Step 1: Promethues metrics. + metrics.Register(kl.runtimeCache) + + // Step 2: Setup filesystem directories. + if err := kl.setupDataDirs(); err != nil { + return err + } + + // Step 3: If the container logs directory does not exist, create it. + if _, err := os.Stat(containerLogsDir); err != nil { + if err := kl.os.Mkdir(containerLogsDir, 0755); err != nil { + glog.Errorf("Failed to create directory %q: %v", containerLogsDir, err) + } + } + + // Step 4: Start the image manager. + if err := kl.imageManager.Start(); err != nil { + return fmt.Errorf("Failed to start ImageManager, images may not be garbage collected: %v", err) + } + + // Step 5: Start container manager. + if err := kl.containerManager.Start(); err != nil { + return fmt.Errorf("Failed to start ContainerManager %v", err) + } + + // Step 6: Start out of memory watcher. + if err := kl.oomWatcher.Start(kl.nodeRef); err != nil { + return fmt.Errorf("Failed to start OOM watcher %v", err) + } + + // Step 7: Start resource analyzer + kl.resourceAnalyzer.Start() + return nil +} + +// initializeRuntimeDependentModules will initialize internal modules that require the container runtime to be up. +func (kl *Kubelet) initializeRuntimeDependentModules() { + if err := kl.cadvisor.Start(); err != nil { + kl.runtimeState.setInternalError(fmt.Errorf("Failed to start cAdvisor %v", err)) + } +} + +// Run starts the kubelet reacting to config updates +func (kl *Kubelet) Run(updates <-chan kubetypes.PodUpdate) { + if kl.logServer == nil { + kl.logServer = http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log/"))) + } + if kl.kubeClient == nil { + glog.Warning("No api server defined - no node status update will be sent.") + } + if err := kl.initializeModules(); err != nil { + kl.recorder.Eventf(kl.nodeRef, api.EventTypeWarning, kubecontainer.KubeletSetupFailed, err.Error()) + glog.Error(err) + kl.runtimeState.setInitError(err) + } + + if kl.kubeClient != nil { + // Start syncing node status immediately, this may set up things the runtime needs to run. + go wait.Until(kl.syncNodeStatus, kl.nodeStatusUpdateFrequency, wait.NeverStop) + } + go wait.Until(kl.syncNetworkStatus, 30*time.Second, wait.NeverStop) + go wait.Until(kl.updateRuntimeUp, 5*time.Second, wait.NeverStop) + + // Start a goroutine responsible for killing pods (that are not properly + // handled by pod workers). + go wait.Until(kl.podKiller, 1*time.Second, wait.NeverStop) + + // Start component sync loops. + kl.statusManager.Start() + kl.probeManager.Start() + // Start the pod lifecycle event generator. + kl.pleg.Start() + kl.syncLoop(updates, kl) +} + +func (kl *Kubelet) initialNodeStatus() (*api.Node, error) { + node := &api.Node{ + ObjectMeta: api.ObjectMeta{ + Name: kl.nodeName, + Labels: map[string]string{unversioned.LabelHostname: kl.hostname}, + }, + Spec: api.NodeSpec{ + Unschedulable: !kl.registerSchedulable, + }, + } + + // @question: should this be place after the call to the cloud provider? which also applies labels + for k, v := range kl.nodeLabels { + if cv, found := node.ObjectMeta.Labels[k]; found { + glog.Warningf("the node label %s=%s will overwrite default setting %s", k, v, cv) + } + node.ObjectMeta.Labels[k] = v + } + + if kl.cloud != nil { + instances, ok := kl.cloud.Instances() + if !ok { + return nil, fmt.Errorf("failed to get instances from cloud provider") + } + + // TODO(roberthbailey): Can we do this without having credentials to talk + // to the cloud provider? + // TODO: ExternalID is deprecated, we'll have to drop this code + externalID, err := instances.ExternalID(kl.nodeName) + if err != nil { + return nil, fmt.Errorf("failed to get external ID from cloud provider: %v", err) + } + node.Spec.ExternalID = externalID + + // TODO: We can't assume that the node has credentials to talk to the + // cloudprovider from arbitrary nodes. At most, we should talk to a + // local metadata server here. + node.Spec.ProviderID, err = cloudprovider.GetInstanceProviderID(kl.cloud, kl.nodeName) + if err != nil { + return nil, err + } + + instanceType, err := instances.InstanceType(kl.nodeName) + if err != nil { + return nil, err + } + if instanceType != "" { + glog.Infof("Adding node label from cloud provider: %s=%s", unversioned.LabelInstanceType, instanceType) + node.ObjectMeta.Labels[unversioned.LabelInstanceType] = instanceType + } + // If the cloud has zone information, label the node with the zone information + zones, ok := kl.cloud.Zones() + if ok { + zone, err := zones.GetZone() + if err != nil { + return nil, fmt.Errorf("failed to get zone from cloud provider: %v", err) + } + if zone.FailureDomain != "" { + glog.Infof("Adding node label from cloud provider: %s=%s", unversioned.LabelZoneFailureDomain, zone.FailureDomain) + node.ObjectMeta.Labels[unversioned.LabelZoneFailureDomain] = zone.FailureDomain + } + if zone.Region != "" { + glog.Infof("Adding node label from cloud provider: %s=%s", unversioned.LabelZoneRegion, zone.Region) + node.ObjectMeta.Labels[unversioned.LabelZoneRegion] = zone.Region + } + } + } else { + node.Spec.ExternalID = kl.hostname + } + if err := kl.setNodeStatus(node); err != nil { + return nil, err + } + return node, nil +} + +// registerWithApiserver registers the node with the cluster master. It is safe +// to call multiple times, but not concurrently (kl.registrationCompleted is +// not locked). +func (kl *Kubelet) registerWithApiserver() { + if kl.registrationCompleted { + return + } + step := 100 * time.Millisecond + for { + time.Sleep(step) + step = step * 2 + if step >= 7*time.Second { + step = 7 * time.Second + } + + node, err := kl.initialNodeStatus() + if err != nil { + glog.Errorf("Unable to construct api.Node object for kubelet: %v", err) + continue + } + glog.V(2).Infof("Attempting to register node %s", node.Name) + if _, err := kl.kubeClient.Core().Nodes().Create(node); err != nil { + if !apierrors.IsAlreadyExists(err) { + glog.V(2).Infof("Unable to register %s with the apiserver: %v", node.Name, err) + continue + } + currentNode, err := kl.kubeClient.Core().Nodes().Get(kl.nodeName) + if err != nil { + glog.Errorf("error getting node %q: %v", kl.nodeName, err) + continue + } + if currentNode == nil { + glog.Errorf("no node instance returned for %q", kl.nodeName) + continue + } + if currentNode.Spec.ExternalID == node.Spec.ExternalID { + glog.Infof("Node %s was previously registered", node.Name) + kl.registrationCompleted = true + return + } + glog.Errorf( + "Previously %q had externalID %q; now it is %q; will delete and recreate.", + kl.nodeName, node.Spec.ExternalID, currentNode.Spec.ExternalID, + ) + if err := kl.kubeClient.Core().Nodes().Delete(node.Name, nil); err != nil { + glog.Errorf("Unable to delete old node: %v", err) + } else { + glog.Errorf("Deleted old node object %q", kl.nodeName) + } + continue + } + glog.Infof("Successfully registered node %s", node.Name) + kl.registrationCompleted = true + return + } +} + +// syncNodeStatus should be called periodically from a goroutine. +// It synchronizes node status to master, registering the kubelet first if +// necessary. +func (kl *Kubelet) syncNodeStatus() { + if kl.kubeClient == nil { + return + } + if kl.registerNode { + // This will exit immediately if it doesn't need to do anything. + kl.registerWithApiserver() + } + if err := kl.updateNodeStatus(); err != nil { + glog.Errorf("Unable to update node status: %v", err) + } +} + +// relabelVolumes relabels SELinux volumes to match the pod's +// SELinuxOptions specification. This is only needed if the pod uses +// hostPID or hostIPC. Otherwise relabeling is delegated to docker. +func (kl *Kubelet) relabelVolumes(pod *api.Pod, volumes kubecontainer.VolumeMap) error { + if pod.Spec.SecurityContext.SELinuxOptions == nil { + return nil + } + + rootDirContext, err := kl.getRootDirContext() + if err != nil { + return err + } + + chconRunner := selinux.NewChconRunner() + // Apply the pod's Level to the rootDirContext + rootDirSELinuxOptions, err := securitycontext.ParseSELinuxOptions(rootDirContext) + if err != nil { + return err + } + + rootDirSELinuxOptions.Level = pod.Spec.SecurityContext.SELinuxOptions.Level + volumeContext := fmt.Sprintf("%s:%s:%s:%s", rootDirSELinuxOptions.User, rootDirSELinuxOptions.Role, rootDirSELinuxOptions.Type, rootDirSELinuxOptions.Level) + + for _, vol := range volumes { + if vol.Mounter.GetAttributes().Managed && vol.Mounter.GetAttributes().SupportsSELinux { + // Relabel the volume and its content to match the 'Level' of the pod + err := filepath.Walk(vol.Mounter.GetPath(), func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + return chconRunner.SetContext(path, volumeContext) + }) + if err != nil { + return err + } + vol.SELinuxLabeled = true + } + } + return nil +} + +func makeMounts(pod *api.Pod, podDir string, container *api.Container, hostName, hostDomain, podIP string, podVolumes kubecontainer.VolumeMap) ([]kubecontainer.Mount, error) { + // Kubernetes only mounts on /etc/hosts if : + // - container does not use hostNetwork and + // - container is not a infrastructure(pause) container + // - container is not already mounting on /etc/hosts + // When the pause container is being created, its IP is still unknown. Hence, PodIP will not have been set. + mountEtcHostsFile := (pod.Spec.SecurityContext == nil || !pod.Spec.SecurityContext.HostNetwork) && len(podIP) > 0 + glog.V(3).Infof("container: %v/%v/%v podIP: %q creating hosts mount: %v", pod.Namespace, pod.Name, container.Name, podIP, mountEtcHostsFile) + mounts := []kubecontainer.Mount{} + for _, mount := range container.VolumeMounts { + mountEtcHostsFile = mountEtcHostsFile && (mount.MountPath != etcHostsPath) + vol, ok := podVolumes[mount.Name] + if !ok { + glog.Warningf("Mount cannot be satisified for container %q, because the volume is missing: %q", container.Name, mount) + continue + } + + relabelVolume := false + // If the volume supports SELinux and it has not been + // relabeled already and it is not a read-only volume, + // relabel it and mark it as labeled + if vol.Mounter.GetAttributes().Managed && vol.Mounter.GetAttributes().SupportsSELinux && !vol.SELinuxLabeled { + vol.SELinuxLabeled = true + relabelVolume = true + } + mounts = append(mounts, kubecontainer.Mount{ + Name: mount.Name, + ContainerPath: mount.MountPath, + HostPath: vol.Mounter.GetPath(), + ReadOnly: mount.ReadOnly, + SELinuxRelabel: relabelVolume, + }) + } + if mountEtcHostsFile { + hostsMount, err := makeHostsMount(podDir, podIP, hostName, hostDomain) + if err != nil { + return nil, err + } + mounts = append(mounts, *hostsMount) + } + return mounts, nil +} + +func makeHostsMount(podDir, podIP, hostName, hostDomainName string) (*kubecontainer.Mount, error) { + hostsFilePath := path.Join(podDir, "etc-hosts") + if err := ensureHostsFile(hostsFilePath, podIP, hostName, hostDomainName); err != nil { + return nil, err + } + return &kubecontainer.Mount{ + Name: "k8s-managed-etc-hosts", + ContainerPath: etcHostsPath, + HostPath: hostsFilePath, + ReadOnly: false, + }, nil +} + +func ensureHostsFile(fileName, hostIP, hostName, hostDomainName string) error { + if _, err := os.Stat(fileName); os.IsExist(err) { + glog.V(4).Infof("kubernetes-managed etc-hosts file exits. Will not be recreated: %q", fileName) + return nil + } + var buffer bytes.Buffer + buffer.WriteString("# Kubernetes-managed hosts file.\n") + buffer.WriteString("127.0.0.1\tlocalhost\n") // ipv4 localhost + buffer.WriteString("::1\tlocalhost ip6-localhost ip6-loopback\n") // ipv6 localhost + buffer.WriteString("fe00::0\tip6-localnet\n") + buffer.WriteString("fe00::0\tip6-mcastprefix\n") + buffer.WriteString("fe00::1\tip6-allnodes\n") + buffer.WriteString("fe00::2\tip6-allrouters\n") + if len(hostDomainName) > 0 { + buffer.WriteString(fmt.Sprintf("%s\t%s.%s\t%s\n", hostIP, hostName, hostDomainName, hostName)) + } else { + buffer.WriteString(fmt.Sprintf("%s\t%s\n", hostIP, hostName)) + } + return ioutil.WriteFile(fileName, buffer.Bytes(), 0644) +} + +func makePortMappings(container *api.Container) (ports []kubecontainer.PortMapping) { + names := make(map[string]struct{}) + for _, p := range container.Ports { + pm := kubecontainer.PortMapping{ + HostPort: p.HostPort, + ContainerPort: p.ContainerPort, + Protocol: p.Protocol, + HostIP: p.HostIP, + } + + // We need to create some default port name if it's not specified, since + // this is necessary for rkt. + // http://issue.k8s.io/7710 + if p.Name == "" { + pm.Name = fmt.Sprintf("%s-%s:%d", container.Name, p.Protocol, p.ContainerPort) + } else { + pm.Name = fmt.Sprintf("%s-%s", container.Name, p.Name) + } + + // Protect against exposing the same protocol-port more than once in a container. + if _, ok := names[pm.Name]; ok { + glog.Warningf("Port name conflicted, %q is defined more than once", pm.Name) + continue + } + ports = append(ports, pm) + names[pm.Name] = struct{}{} + } + return +} + +func (kl *Kubelet) GeneratePodHostNameAndDomain(pod *api.Pod) (string, string) { + // TODO(vmarmol): Handle better. + // Cap hostname at 63 chars (specification is 64bytes which is 63 chars and the null terminating char). + clusterDomain := kl.clusterDomain + const hostnameMaxLen = 63 + podAnnotations := pod.Annotations + if podAnnotations == nil { + podAnnotations = make(map[string]string) + } + hostname := pod.Name + hostnameCandidate := podAnnotations[utilpod.PodHostnameAnnotation] + if utilvalidation.IsDNS1123Label(hostnameCandidate) { + // use hostname annotation, if specified. + hostname = hostnameCandidate + } + if len(hostname) > hostnameMaxLen { + hostname = hostname[:hostnameMaxLen] + glog.Errorf("hostname for pod:%q was longer than %d. Truncated hostname to :%q", pod.Name, hostnameMaxLen, hostname) + } + + hostDomain := "" + subdomainCandidate := pod.Annotations[utilpod.PodSubdomainAnnotation] + if utilvalidation.IsDNS1123Label(subdomainCandidate) { + hostDomain = fmt.Sprintf("%s.%s.svc.%s", subdomainCandidate, pod.Namespace, clusterDomain) + } + return hostname, hostDomain +} + +// GenerateRunContainerOptions generates the RunContainerOptions, which can be used by +// the container runtime to set parameters for launching a container. +func (kl *Kubelet) GenerateRunContainerOptions(pod *api.Pod, container *api.Container, podIP string) (*kubecontainer.RunContainerOptions, error) { + var err error + opts := &kubecontainer.RunContainerOptions{CgroupParent: kl.cgroupRoot} + hostname, hostDomainName := kl.GeneratePodHostNameAndDomain(pod) + opts.Hostname = hostname + vol, ok := kl.volumeManager.GetVolumes(pod.UID) + if !ok { + return nil, fmt.Errorf("impossible: cannot find the mounted volumes for pod %q", format.Pod(pod)) + } + + opts.PortMappings = makePortMappings(container) + // Docker does not relabel volumes if the container is running + // in the host pid or ipc namespaces so the kubelet must + // relabel the volumes + if pod.Spec.SecurityContext != nil && (pod.Spec.SecurityContext.HostIPC || pod.Spec.SecurityContext.HostPID) { + err = kl.relabelVolumes(pod, vol) + if err != nil { + return nil, err + } + } + + opts.Mounts, err = makeMounts(pod, kl.getPodDir(pod.UID), container, hostname, hostDomainName, podIP, vol) + if err != nil { + return nil, err + } + opts.Envs, err = kl.makeEnvironmentVariables(pod, container, podIP) + if err != nil { + return nil, err + } + + if len(container.TerminationMessagePath) != 0 { + p := kl.getPodContainerDir(pod.UID, container.Name) + if err := os.MkdirAll(p, 0750); err != nil { + glog.Errorf("Error on creating %q: %v", p, err) + } else { + opts.PodContainerDir = p + } + } + + opts.DNS, opts.DNSSearch, err = kl.GetClusterDNS(pod) + if err != nil { + return nil, err + } + + return opts, nil +} + +var masterServices = sets.NewString("kubernetes") + +// getServiceEnvVarMap makes a map[string]string of env vars for services a pod in namespace ns should see +func (kl *Kubelet) getServiceEnvVarMap(ns string) (map[string]string, error) { + var ( + serviceMap = make(map[string]api.Service) + m = make(map[string]string) + ) + + // Get all service resources from the master (via a cache), + // and populate them into service environment variables. + if kl.serviceLister == nil { + // Kubelets without masters (e.g. plain GCE ContainerVM) don't set env vars. + return m, nil + } + services, err := kl.serviceLister.List() + if err != nil { + return m, fmt.Errorf("failed to list services when setting up env vars.") + } + + // project the services in namespace ns onto the master services + for _, service := range services.Items { + // ignore services where ClusterIP is "None" or empty + if !api.IsServiceIPSet(&service) { + continue + } + serviceName := service.Name + + switch service.Namespace { + // for the case whether the master service namespace is the namespace the pod + // is in, the pod should receive all the services in the namespace. + // + // ordering of the case clauses below enforces this + case ns: + serviceMap[serviceName] = service + case kl.masterServiceNamespace: + if masterServices.Has(serviceName) { + if _, exists := serviceMap[serviceName]; !exists { + serviceMap[serviceName] = service + } + } + } + } + services.Items = []api.Service{} + for _, service := range serviceMap { + services.Items = append(services.Items, service) + } + + for _, e := range envvars.FromServices(&services) { + m[e.Name] = e.Value + } + return m, nil +} + +// Make the service environment variables for a pod in the given namespace. +func (kl *Kubelet) makeEnvironmentVariables(pod *api.Pod, container *api.Container, podIP string) ([]kubecontainer.EnvVar, error) { + var result []kubecontainer.EnvVar + // Note: These are added to the docker.Config, but are not included in the checksum computed + // by dockertools.BuildDockerName(...). That way, we can still determine whether an + // api.Container is already running by its hash. (We don't want to restart a container just + // because some service changed.) + // + // Note that there is a race between Kubelet seeing the pod and kubelet seeing the service. + // To avoid this users can: (1) wait between starting a service and starting; or (2) detect + // missing service env var and exit and be restarted; or (3) use DNS instead of env vars + // and keep trying to resolve the DNS name of the service (recommended). + serviceEnv, err := kl.getServiceEnvVarMap(pod.Namespace) + if err != nil { + return result, err + } + + // Determine the final values of variables: + // + // 1. Determine the final value of each variable: + // a. If the variable's Value is set, expand the `$(var)` references to other + // variables in the .Value field; the sources of variables are the declared + // variables of the container and the service environment variables + // b. If a source is defined for an environment variable, resolve the source + // 2. Create the container's environment in the order variables are declared + // 3. Add remaining service environment vars + var ( + tmpEnv = make(map[string]string) + configMaps = make(map[string]*api.ConfigMap) + secrets = make(map[string]*api.Secret) + mappingFunc = expansion.MappingFuncFor(tmpEnv, serviceEnv) + ) + for _, envVar := range container.Env { + // Accesses apiserver+Pods. + // So, the master may set service env vars, or kubelet may. In case both are doing + // it, we delete the key from the kubelet-generated ones so we don't have duplicate + // env vars. + // TODO: remove this net line once all platforms use apiserver+Pods. + delete(serviceEnv, envVar.Name) + + runtimeVal := envVar.Value + if runtimeVal != "" { + // Step 1a: expand variable references + runtimeVal = expansion.Expand(runtimeVal, mappingFunc) + } else if envVar.ValueFrom != nil { + // Step 1b: resolve alternate env var sources + switch { + case envVar.ValueFrom.FieldRef != nil: + runtimeVal, err = kl.podFieldSelectorRuntimeValue(envVar.ValueFrom.FieldRef, pod, podIP) + if err != nil { + return result, err + } + case envVar.ValueFrom.ConfigMapKeyRef != nil: + name := envVar.ValueFrom.ConfigMapKeyRef.Name + key := envVar.ValueFrom.ConfigMapKeyRef.Key + configMap, ok := configMaps[name] + if !ok { + configMap, err = kl.kubeClient.Core().ConfigMaps(pod.Namespace).Get(name) + if err != nil { + return result, err + } + } + runtimeVal, ok = configMap.Data[key] + if !ok { + return result, fmt.Errorf("Couldn't find key %v in ConfigMap %v/%v", key, pod.Namespace, name) + } + case envVar.ValueFrom.SecretKeyRef != nil: + name := envVar.ValueFrom.SecretKeyRef.Name + key := envVar.ValueFrom.SecretKeyRef.Key + secret, ok := secrets[name] + if !ok { + secret, err = kl.kubeClient.Core().Secrets(pod.Namespace).Get(name) + if err != nil { + return result, err + } + } + runtimeValBytes, ok := secret.Data[key] + if !ok { + return result, fmt.Errorf("Couldn't find key %v in Secret %v/%v", key, pod.Namespace, name) + } + runtimeVal = string(runtimeValBytes) + } + } + + tmpEnv[envVar.Name] = runtimeVal + result = append(result, kubecontainer.EnvVar{Name: envVar.Name, Value: tmpEnv[envVar.Name]}) + } + + // Append remaining service env vars. + for k, v := range serviceEnv { + result = append(result, kubecontainer.EnvVar{Name: k, Value: v}) + } + return result, nil +} + +func (kl *Kubelet) podFieldSelectorRuntimeValue(fs *api.ObjectFieldSelector, pod *api.Pod, podIP string) (string, error) { + internalFieldPath, _, err := api.Scheme.ConvertFieldLabel(fs.APIVersion, "Pod", fs.FieldPath, "") + if err != nil { + return "", err + } + switch internalFieldPath { + case "status.podIP": + return podIP, nil + } + return fieldpath.ExtractFieldPathAsString(pod, internalFieldPath) +} + +// GetClusterDNS returns a list of the DNS servers and a list of the DNS search +// domains of the cluster. +func (kl *Kubelet) GetClusterDNS(pod *api.Pod) ([]string, []string, error) { + var hostDNS, hostSearch []string + // Get host DNS settings + if kl.resolverConfig != "" { + f, err := os.Open(kl.resolverConfig) + if err != nil { + return nil, nil, err + } + defer f.Close() + + hostDNS, hostSearch, err = kl.parseResolvConf(f) + if err != nil { + return nil, nil, err + } + } + useClusterFirstPolicy := pod.Spec.DNSPolicy == api.DNSClusterFirst + if useClusterFirstPolicy && kl.clusterDNS == nil { + // clusterDNS is not known. + // pod with ClusterDNSFirst Policy cannot be created + kl.recorder.Eventf(pod, api.EventTypeWarning, "MissingClusterDNS", "kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. Falling back to DNSDefault policy.", pod.Spec.DNSPolicy) + log := fmt.Sprintf("kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. pod: %q. Falling back to DNSDefault policy.", pod.Spec.DNSPolicy, format.Pod(pod)) + kl.recorder.Eventf(kl.nodeRef, api.EventTypeWarning, "MissingClusterDNS", log) + + // fallback to DNSDefault + useClusterFirstPolicy = false + } + + if !useClusterFirstPolicy { + // When the kubelet --resolv-conf flag is set to the empty string, use + // DNS settings that override the docker default (which is to use + // /etc/resolv.conf) and effectivly disable DNS lookups. According to + // the bind documentation, the behavior of the DNS client library when + // "nameservers" are not specified is to "use the nameserver on the + // local machine". A nameserver setting of localhost is equivalent to + // this documented behavior. + if kl.resolverConfig == "" { + hostDNS = []string{"127.0.0.1"} + hostSearch = []string{"."} + } + return hostDNS, hostSearch, nil + } + + // for a pod with DNSClusterFirst policy, the cluster DNS server is the only nameserver configured for + // the pod. The cluster DNS server itself will forward queries to other nameservers that is configured to use, + // in case the cluster DNS server cannot resolve the DNS query itself + dns := []string{kl.clusterDNS.String()} + + var dnsSearch []string + if kl.clusterDomain != "" { + nsSvcDomain := fmt.Sprintf("%s.svc.%s", pod.Namespace, kl.clusterDomain) + svcDomain := fmt.Sprintf("svc.%s", kl.clusterDomain) + dnsSearch = append([]string{nsSvcDomain, svcDomain, kl.clusterDomain}, hostSearch...) + } else { + dnsSearch = hostSearch + } + return dns, dnsSearch, nil +} + +// Returns the list of DNS servers and DNS search domains. +func (kl *Kubelet) parseResolvConf(reader io.Reader) (nameservers []string, searches []string, err error) { + var scrubber dnsScrubber + if kl.cloud != nil { + scrubber = kl.cloud + } + return parseResolvConf(reader, scrubber) +} + +// A helper for testing. +type dnsScrubber interface { + ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) +} + +func parseResolvConf(reader io.Reader, dnsScrubber dnsScrubber) (nameservers []string, searches []string, err error) { + file, err := ioutil.ReadAll(reader) + if err != nil { + return nil, nil, err + } + + // Lines of the form "nameserver 1.2.3.4" accumulate. + nameservers = []string{} + + // Lines of the form "search example.com" overrule - last one wins. + searches = []string{} + + lines := strings.Split(string(file), "\n") + for l := range lines { + trimmed := strings.TrimSpace(lines[l]) + if strings.HasPrefix(trimmed, "#") { + continue + } + fields := strings.Fields(trimmed) + if len(fields) == 0 { + continue + } + if fields[0] == "nameserver" { + nameservers = append(nameservers, fields[1:]...) + } + if fields[0] == "search" { + searches = fields[1:] + } + } + + // Give the cloud-provider a chance to post-process DNS settings. + if dnsScrubber != nil { + nameservers, searches = dnsScrubber.ScrubDNS(nameservers, searches) + } + return nameservers, searches, nil +} + +// One of the following aruguements must be non-nil: runningPod, status. +// TODO: Modify containerRuntime.KillPod() to accept the right arguments. +func (kl *Kubelet) killPod(pod *api.Pod, runningPod *kubecontainer.Pod, status *kubecontainer.PodStatus) error { + var p kubecontainer.Pod + if runningPod != nil { + p = *runningPod + } else if status != nil { + p = kubecontainer.ConvertPodStatusToRunningPod(status) + } + return kl.containerRuntime.KillPod(pod, p) +} + +type empty struct{} + +// makePodDataDirs creates the dirs for the pod datas. +func (kl *Kubelet) makePodDataDirs(pod *api.Pod) error { + uid := pod.UID + if err := os.Mkdir(kl.getPodDir(uid), 0750); err != nil && !os.IsExist(err) { + return err + } + if err := os.Mkdir(kl.getPodVolumesDir(uid), 0750); err != nil && !os.IsExist(err) { + return err + } + if err := os.Mkdir(kl.getPodPluginsDir(uid), 0750); err != nil && !os.IsExist(err) { + return err + } + return nil +} + +func (kl *Kubelet) syncPod(pod *api.Pod, mirrorPod *api.Pod, podStatus *kubecontainer.PodStatus, updateType kubetypes.SyncPodType) error { + var firstSeenTime time.Time + if firstSeenTimeStr, ok := pod.Annotations[kubetypes.ConfigFirstSeenAnnotationKey]; ok { + firstSeenTime = kubetypes.ConvertToTimestamp(firstSeenTimeStr).Get() + } + + if updateType == kubetypes.SyncPodCreate { + if !firstSeenTime.IsZero() { + // This is the first time we are syncing the pod. Record the latency + // since kubelet first saw the pod if firstSeenTime is set. + metrics.PodWorkerStartLatency.Observe(metrics.SinceInMicroseconds(firstSeenTime)) + } else { + glog.V(3).Infof("First seen time not recorded for pod %q", pod.UID) + } + } + + apiPodStatus := kl.generatePodStatus(pod, podStatus) + // Record the time it takes for the pod to become running. + existingStatus, ok := kl.statusManager.GetPodStatus(pod.UID) + if !ok || existingStatus.Phase == api.PodPending && apiPodStatus.Phase == api.PodRunning && + !firstSeenTime.IsZero() { + metrics.PodStartLatency.Observe(metrics.SinceInMicroseconds(firstSeenTime)) + } + kl.statusManager.SetPodStatus(pod, apiPodStatus) + + // Kill pods we can't run. + if err := canRunPod(pod); err != nil || pod.DeletionTimestamp != nil { + if err := kl.killPod(pod, nil, podStatus); err != nil { + utilruntime.HandleError(err) + } + return err + } + + // Create Mirror Pod for Static Pod if it doesn't already exist + if kubepod.IsStaticPod(pod) { + podFullName := kubecontainer.GetPodFullName(pod) + deleted := false + if mirrorPod != nil { + if mirrorPod.DeletionTimestamp != nil || !kl.podManager.IsMirrorPodOf(mirrorPod, pod) { + // The mirror pod is semantically different from the static pod. Remove + // it. The mirror pod will get recreated later. + glog.Errorf("Deleting mirror pod %q because it is outdated", format.Pod(mirrorPod)) + if err := kl.podManager.DeleteMirrorPod(podFullName); err != nil { + glog.Errorf("Failed deleting mirror pod %q: %v", format.Pod(mirrorPod), err) + } else { + deleted = true + } + } + } + if mirrorPod == nil || deleted { + glog.V(3).Infof("Creating a mirror pod for static pod %q", format.Pod(pod)) + if err := kl.podManager.CreateMirrorPod(pod); err != nil { + glog.Errorf("Failed creating a mirror pod for %q: %v", format.Pod(pod), err) + } + } + } + + if err := kl.makePodDataDirs(pod); err != nil { + glog.Errorf("Unable to make pod data directories for pod %q: %v", format.Pod(pod), err) + return err + } + + // Mount volumes. + podVolumes, err := kl.mountExternalVolumes(pod) + if err != nil { + ref, errGetRef := api.GetReference(pod) + if errGetRef == nil && ref != nil { + kl.recorder.Eventf(ref, api.EventTypeWarning, kubecontainer.FailedMountVolume, "Unable to mount volumes for pod %q: %v", format.Pod(pod), err) + glog.Errorf("Unable to mount volumes for pod %q: %v; skipping pod", format.Pod(pod), err) + return err + } + } + kl.volumeManager.SetVolumes(pod.UID, podVolumes) + + pullSecrets, err := kl.getPullSecretsForPod(pod) + if err != nil { + glog.Errorf("Unable to get pull secrets for pod %q: %v", format.Pod(pod), err) + return err + } + + result := kl.containerRuntime.SyncPod(pod, apiPodStatus, podStatus, pullSecrets, kl.backOff) + kl.reasonCache.Update(pod.UID, result) + if err = result.Error(); err != nil { + return err + } + + if !kl.shapingEnabled() { + return nil + } + ingress, egress, err := extractBandwidthResources(pod) + if err != nil { + return err + } + if egress != nil || ingress != nil { + if podUsesHostNetwork(pod) { + kl.recorder.Event(pod, api.EventTypeWarning, kubecontainer.HostNetworkNotSupported, "Bandwidth shaping is not currently supported on the host network") + } else if kl.shaper != nil { + if len(apiPodStatus.PodIP) > 0 { + err = kl.shaper.ReconcileCIDR(fmt.Sprintf("%s/32", apiPodStatus.PodIP), egress, ingress) + } + } else { + kl.recorder.Event(pod, api.EventTypeWarning, kubecontainer.UndefinedShaper, "Pod requests bandwidth shaping, but the shaper is undefined") + } + } + + return nil +} + +func podUsesHostNetwork(pod *api.Pod) bool { + return pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostNetwork +} + +// getPullSecretsForPod inspects the Pod and retrieves the referenced pull secrets +// TODO duplicate secrets are being retrieved multiple times and there is no cache. Creating and using a secret manager interface will make this easier to address. +func (kl *Kubelet) getPullSecretsForPod(pod *api.Pod) ([]api.Secret, error) { + pullSecrets := []api.Secret{} + + for _, secretRef := range pod.Spec.ImagePullSecrets { + secret, err := kl.kubeClient.Core().Secrets(pod.Namespace).Get(secretRef.Name) + if err != nil { + glog.Warningf("Unable to retrieve pull secret %s/%s for %s/%s due to %v. The image pull may not succeed.", pod.Namespace, secretRef.Name, pod.Namespace, pod.Name, err) + continue + } + + pullSecrets = append(pullSecrets, *secret) + } + + return pullSecrets, nil +} + +// Return name of a volume. When the volume is a PersistentVolumeClaim, +// it returns name of the real PersistentVolume bound to the claim. +// It returns errror when the clam is not bound yet. +func (kl *Kubelet) resolveVolumeName(pod *api.Pod, volume *api.Volume) (string, error) { + claimSource := volume.VolumeSource.PersistentVolumeClaim + if claimSource != nil { + // resolve real volume behind the claim + claim, err := kl.kubeClient.Core().PersistentVolumeClaims(pod.Namespace).Get(claimSource.ClaimName) + if err != nil { + return "", fmt.Errorf("Cannot find claim %s/%s for volume %s", pod.Namespace, claimSource.ClaimName, volume.Name) + } + if claim.Status.Phase != api.ClaimBound { + return "", fmt.Errorf("Claim for volume %s/%s is not bound yet", pod.Namespace, claimSource.ClaimName) + } + // Use the real bound volume instead of PersistentVolume.Name + return claim.Spec.VolumeName, nil + } + return volume.Name, nil +} + +// Stores all volumes defined by the set of pods into a map. +// It stores real volumes there, i.e. persistent volume claims are resolved +// to volumes that are bound to them. +// Keys for each entry are in the format (POD_ID)/(VOLUME_NAME) +func (kl *Kubelet) getDesiredVolumes(pods []*api.Pod) map[string]api.Volume { + desiredVolumes := make(map[string]api.Volume) + for _, pod := range pods { + for _, volume := range pod.Spec.Volumes { + volumeName, err := kl.resolveVolumeName(pod, &volume) + if err != nil { + glog.V(3).Infof("%v", err) + // Ignore the error and hope it's resolved next time + continue + } + identifier := path.Join(string(pod.UID), volumeName) + desiredVolumes[identifier] = volume + } + } + return desiredVolumes +} + +// cleanupOrphanedPodDirs removes a pod directory if the pod is not in the +// desired set of pods and there is no running containers in the pod. +func (kl *Kubelet) cleanupOrphanedPodDirs(pods []*api.Pod, runningPods []*kubecontainer.Pod) error { + active := sets.NewString() + for _, pod := range pods { + active.Insert(string(pod.UID)) + } + for _, pod := range runningPods { + active.Insert(string(pod.ID)) + } + + found, err := kl.listPodsFromDisk() + if err != nil { + return err + } + errlist := []error{} + for _, uid := range found { + if active.Has(string(uid)) { + continue + } + if volumes, err := kl.getPodVolumes(uid); err != nil || len(volumes) != 0 { + glog.V(3).Infof("Orphaned pod %q found, but volumes are not cleaned up; err: %v, volumes: %v ", uid, err, volumes) + continue + } + + glog.V(3).Infof("Orphaned pod %q found, removing", uid) + if err := os.RemoveAll(kl.getPodDir(uid)); err != nil { + errlist = append(errlist, err) + } + } + return utilerrors.NewAggregate(errlist) +} + +func (kl *Kubelet) cleanupBandwidthLimits(allPods []*api.Pod) error { + if kl.shaper == nil { + return nil + } + currentCIDRs, err := kl.shaper.GetCIDRs() + if err != nil { + return err + } + possibleCIDRs := sets.String{} + for ix := range allPods { + pod := allPods[ix] + ingress, egress, err := extractBandwidthResources(pod) + if err != nil { + return err + } + if ingress == nil && egress == nil { + glog.V(8).Infof("Not a bandwidth limited container...") + continue + } + status, found := kl.statusManager.GetPodStatus(pod.UID) + if !found { + // TODO(random-liu): Cleanup status get functions. (issue #20477) + s, err := kl.containerRuntime.GetPodStatus(pod.UID, pod.Name, pod.Namespace) + if err != nil { + return err + } + status = kl.generatePodStatus(pod, s) + } + if status.Phase == api.PodRunning { + possibleCIDRs.Insert(fmt.Sprintf("%s/32", status.PodIP)) + } + } + for _, cidr := range currentCIDRs { + if !possibleCIDRs.Has(cidr) { + glog.V(2).Infof("Removing CIDR: %s (%v)", cidr, possibleCIDRs) + if err := kl.shaper.Reset(cidr); err != nil { + return err + } + } + } + return nil +} + +// Compares the map of current volumes to the map of desired volumes. +// If an active volume does not have a respective desired volume, clean it up. +// This method is blocking: +// 1) it talks to API server to find volumes bound to persistent volume claims +// 2) it talks to cloud to detach volumes +func (kl *Kubelet) cleanupOrphanedVolumes(pods []*api.Pod, runningPods []*kubecontainer.Pod) error { + desiredVolumes := kl.getDesiredVolumes(pods) + currentVolumes := kl.getPodVolumesFromDisk() + + runningSet := sets.String{} + for _, pod := range runningPods { + runningSet.Insert(string(pod.ID)) + } + + for name, cleanerTuple := range currentVolumes { + if _, ok := desiredVolumes[name]; !ok { + parts := strings.Split(name, "/") + if runningSet.Has(parts[0]) { + glog.Infof("volume %q, still has a container running %q, skipping teardown", name, parts[0]) + continue + } + //TODO (jonesdl) We should somehow differentiate between volumes that are supposed + //to be deleted and volumes that are leftover after a crash. + glog.Warningf("Orphaned volume %q found, tearing down volume", name) + // TODO(yifan): Refactor this hacky string manipulation. + kl.volumeManager.DeleteVolumes(types.UID(parts[0])) + // Get path reference count + refs, err := mount.GetMountRefs(kl.mounter, cleanerTuple.Unmounter.GetPath()) + if err != nil { + return fmt.Errorf("Could not get mount path references %v", err) + } + //TODO (jonesdl) This should not block other kubelet synchronization procedures + err = cleanerTuple.Unmounter.TearDown() + if err != nil { + glog.Errorf("Could not tear down volume %q: %v", name, err) + } + + // volume is unmounted. some volumes also require detachment from the node. + if cleanerTuple.Detacher != nil && len(refs) == 1 { + detacher := *cleanerTuple.Detacher + err = detacher.Detach() + if err != nil { + glog.Errorf("Could not detach volume %q: %v", name, err) + } + } + } + } + return nil +} + +// pastActiveDeadline returns true if the pod has been active for more than +// ActiveDeadlineSeconds. +func (kl *Kubelet) pastActiveDeadline(pod *api.Pod) bool { + if pod.Spec.ActiveDeadlineSeconds != nil { + podStatus, ok := kl.statusManager.GetPodStatus(pod.UID) + if !ok { + podStatus = pod.Status + } + if !podStatus.StartTime.IsZero() { + startTime := podStatus.StartTime.Time + duration := kl.clock.Since(startTime) + allowedDuration := time.Duration(*pod.Spec.ActiveDeadlineSeconds) * time.Second + if duration >= allowedDuration { + return true + } + } + } + return false +} + +// Get pods which should be resynchronized. Currently, the following pod should be resynchronized: +// * pod whose work is ready. +// * pod past the active deadline. +func (kl *Kubelet) getPodsToSync() []*api.Pod { + allPods := kl.podManager.GetPods() + podUIDs := kl.workQueue.GetWork() + podUIDSet := sets.NewString() + for _, podUID := range podUIDs { + podUIDSet.Insert(string(podUID)) + } + var podsToSync []*api.Pod + for _, pod := range allPods { + if kl.pastActiveDeadline(pod) { + // The pod has passed the active deadline + podsToSync = append(podsToSync, pod) + continue + } + if podUIDSet.Has(string(pod.UID)) { + // The work of the pod is ready + podsToSync = append(podsToSync, pod) + } + } + return podsToSync +} + +// Returns true if pod is in the terminated state ("Failed" or "Succeeded"). +func (kl *Kubelet) podIsTerminated(pod *api.Pod) bool { + var status api.PodStatus + // Check the cached pod status which was set after the last sync. + status, ok := kl.statusManager.GetPodStatus(pod.UID) + if !ok { + // If there is no cached status, use the status from the + // apiserver. This is useful if kubelet has recently been + // restarted. + status = pod.Status + } + if status.Phase == api.PodFailed || status.Phase == api.PodSucceeded { + return true + } + + return false +} + +func (kl *Kubelet) filterOutTerminatedPods(pods []*api.Pod) []*api.Pod { + var filteredPods []*api.Pod + for _, p := range pods { + if kl.podIsTerminated(p) { + continue + } + filteredPods = append(filteredPods, p) + } + return filteredPods +} + +// removeOrphanedPodStatuses removes obsolete entries in podStatus where +// the pod is no longer considered bound to this node. +func (kl *Kubelet) removeOrphanedPodStatuses(pods []*api.Pod, mirrorPods []*api.Pod) { + podUIDs := make(map[types.UID]bool) + for _, pod := range pods { + podUIDs[pod.UID] = true + } + for _, pod := range mirrorPods { + podUIDs[pod.UID] = true + } + kl.statusManager.RemoveOrphanedStatuses(podUIDs) +} + +func (kl *Kubelet) deletePod(pod *api.Pod) error { + if pod == nil { + return fmt.Errorf("deletePod does not allow nil pod") + } + if !kl.allSourcesReady() { + // If the sources aren't ready, skip deletion, as we may accidentally delete pods + // for sources that haven't reported yet. + return fmt.Errorf("skipping delete because sources aren't ready yet") + } + kl.podWorkers.ForgetWorker(pod.UID) + + // Runtime cache may not have been updated to with the pod, but it's okay + // because the periodic cleanup routine will attempt to delete again later. + runningPods, err := kl.runtimeCache.GetPods() + if err != nil { + return fmt.Errorf("error listing containers: %v", err) + } + runningPod := kubecontainer.Pods(runningPods).FindPod("", pod.UID) + if runningPod.IsEmpty() { + return fmt.Errorf("pod not found") + } + podPair := kubecontainer.PodPair{APIPod: pod, RunningPod: &runningPod} + + kl.podKillingCh <- &podPair + // TODO: delete the mirror pod here? + + // We leave the volume/directory cleanup to the periodic cleanup routine. + return nil +} + +// HandlePodCleanups performs a series of cleanup work, including terminating +// pod workers, killing unwanted pods, and removing orphaned volumes/pod +// directories. +// TODO(yujuhong): This function is executed by the main sync loop, so it +// should not contain any blocking calls. Re-examine the function and decide +// whether or not we should move it into a separte goroutine. +func (kl *Kubelet) HandlePodCleanups() error { + allPods, mirrorPods := kl.podManager.GetPodsAndMirrorPods() + // Pod phase progresses monotonically. Once a pod has reached a final state, + // it should never leave regardless of the restart policy. The statuses + // of such pods should not be changed, and there is no need to sync them. + // TODO: the logic here does not handle two cases: + // 1. If the containers were removed immediately after they died, kubelet + // may fail to generate correct statuses, let alone filtering correctly. + // 2. If kubelet restarted before writing the terminated status for a pod + // to the apiserver, it could still restart the terminated pod (even + // though the pod was not considered terminated by the apiserver). + // These two conditions could be alleviated by checkpointing kubelet. + activePods := kl.filterOutTerminatedPods(allPods) + + desiredPods := make(map[types.UID]empty) + for _, pod := range activePods { + desiredPods[pod.UID] = empty{} + } + // Stop the workers for no-longer existing pods. + // TODO: is here the best place to forget pod workers? + kl.podWorkers.ForgetNonExistingPodWorkers(desiredPods) + kl.probeManager.CleanupPods(activePods) + + runningPods, err := kl.runtimeCache.GetPods() + if err != nil { + glog.Errorf("Error listing containers: %#v", err) + return err + } + for _, pod := range runningPods { + if _, found := desiredPods[pod.ID]; !found { + kl.podKillingCh <- &kubecontainer.PodPair{APIPod: nil, RunningPod: pod} + } + } + + kl.removeOrphanedPodStatuses(allPods, mirrorPods) + // Note that we just killed the unwanted pods. This may not have reflected + // in the cache. We need to bypass the cache to get the latest set of + // running pods to clean up the volumes. + // TODO: Evaluate the performance impact of bypassing the runtime cache. + runningPods, err = kl.containerRuntime.GetPods(false) + if err != nil { + glog.Errorf("Error listing containers: %#v", err) + return err + } + + // Remove any orphaned volumes. + // Note that we pass all pods (including terminated pods) to the function, + // so that we don't remove volumes associated with terminated but not yet + // deleted pods. + err = kl.cleanupOrphanedVolumes(allPods, runningPods) + if err != nil { + glog.Errorf("Failed cleaning up orphaned volumes: %v", err) + return err + } + + // Remove any orphaned pod directories. + // Note that we pass all pods (including terminated pods) to the function, + // so that we don't remove directories associated with terminated but not yet + // deleted pods. + err = kl.cleanupOrphanedPodDirs(allPods, runningPods) + if err != nil { + glog.Errorf("Failed cleaning up orphaned pod directories: %v", err) + return err + } + + // Remove any orphaned mirror pods. + kl.podManager.DeleteOrphanedMirrorPods() + + // Clear out any old bandwidth rules + if err = kl.cleanupBandwidthLimits(allPods); err != nil { + return err + } + + kl.backOff.GC() + return err +} + +// podKiller launches a goroutine to kill a pod received from the channel if +// another goroutine isn't already in action. +func (kl *Kubelet) podKiller() { + killing := sets.NewString() + resultCh := make(chan types.UID) + defer close(resultCh) + for { + select { + case podPair, ok := <-kl.podKillingCh: + runningPod := podPair.RunningPod + apiPod := podPair.APIPod + if !ok { + return + } + if killing.Has(string(runningPod.ID)) { + // The pod is already being killed. + break + } + killing.Insert(string(runningPod.ID)) + go func(apiPod *api.Pod, runningPod *kubecontainer.Pod, ch chan types.UID) { + defer func() { + ch <- runningPod.ID + }() + glog.V(2).Infof("Killing unwanted pod %q", runningPod.Name) + err := kl.killPod(apiPod, runningPod, nil) + if err != nil { + glog.Errorf("Failed killing the pod %q: %v", runningPod.Name, err) + } + }(apiPod, runningPod, resultCh) + + case podID := <-resultCh: + killing.Delete(string(podID)) + } + } +} + +type podsByCreationTime []*api.Pod + +func (s podsByCreationTime) Len() int { + return len(s) +} + +func (s podsByCreationTime) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s podsByCreationTime) Less(i, j int) bool { + return s[i].CreationTimestamp.Before(s[j].CreationTimestamp) +} + +// checkHostPortConflicts detects pods with conflicted host ports. +func hasHostPortConflicts(pods []*api.Pod) bool { + ports := sets.String{} + for _, pod := range pods { + if errs := validation.AccumulateUniqueHostPorts(pod.Spec.Containers, &ports, field.NewPath("spec", "containers")); len(errs) > 0 { + glog.Errorf("Pod %q: HostPort is already allocated, ignoring: %v", format.Pod(pod), errs) + return true + } + } + return false +} + +// hasInsufficientfFreeResources detects pods that exceeds node's cpu and memory resource. +func (kl *Kubelet) hasInsufficientfFreeResources(pods []*api.Pod) (bool, bool) { + info, err := kl.GetCachedMachineInfo() + if err != nil { + glog.Errorf("error getting machine info: %v", err) + // TODO: Should we admit the pod when machine info is unavailable? + return false, false + } + capacity := cadvisor.CapacityFromMachineInfo(info) + _, notFittingCPU, notFittingMemory := predicates.CheckPodsExceedingFreeResources(pods, capacity) + return len(notFittingCPU) > 0, len(notFittingMemory) > 0 +} + +// handleOutOfDisk detects if pods can't fit due to lack of disk space. +func (kl *Kubelet) isOutOfDisk() bool { + outOfDockerDisk := false + outOfRootDisk := false + // Check disk space once globally and reject or accept all new pods. + withinBounds, err := kl.diskSpaceManager.IsDockerDiskSpaceAvailable() + // Assume enough space in case of errors. + if err == nil && !withinBounds { + outOfDockerDisk = true + } + + withinBounds, err = kl.diskSpaceManager.IsRootDiskSpaceAvailable() + // Assume enough space in case of errors. + if err == nil && !withinBounds { + outOfRootDisk = true + } + return outOfDockerDisk || outOfRootDisk +} + +// matchesNodeSelector returns true if pod matches node's labels. +func (kl *Kubelet) matchesNodeSelector(pod *api.Pod) bool { + if kl.standaloneMode { + return true + } + node, err := kl.GetNode() + if err != nil { + glog.Errorf("error getting node: %v", err) + return true + } + return predicates.PodMatchesNodeLabels(pod, node) +} + +func (kl *Kubelet) rejectPod(pod *api.Pod, reason, message string) { + kl.recorder.Eventf(pod, api.EventTypeWarning, reason, message) + kl.statusManager.SetPodStatus(pod, api.PodStatus{ + Phase: api.PodFailed, + Reason: reason, + Message: "Pod " + message}) +} + +// getNodeAnyWay() must return a *api.Node which is required by RunGeneralPredicates(). +// The *api.Node is obtained as follows: +// Return kubelet's nodeInfo for this node, except on error or if in standalone mode, +// in which case return a manufactured nodeInfo representing a node with no pods, +// zero capacity, and the default labels. +func (kl *Kubelet) getNodeAnyWay() (*api.Node, error) { + if !kl.standaloneMode { + if n, err := kl.nodeInfo.GetNodeInfo(kl.nodeName); err == nil { + return n, nil + } + } + return kl.initialNodeStatus() +} + +// canAdmitPod determines if a pod can be admitted, and gives a reason if it +// cannot. "pod" is new pod, while "pods" include all admitted pods plus the +// new pod. The function returns a boolean value indicating whether the pod +// can be admitted, a brief single-word reason and a message explaining why +// the pod cannot be admitted. +func (kl *Kubelet) canAdmitPod(pods []*api.Pod, pod *api.Pod) (bool, string, string) { + node, err := kl.getNodeAnyWay() + if err != nil { + glog.Errorf("Cannot get Node info: %v", err) + return false, "InvalidNodeInfo", "Kubelet cannot get node info." + } + otherPods := []*api.Pod{} + for _, p := range pods { + if p != pod { + otherPods = append(otherPods, p) + } + } + nodeInfo := schedulercache.CreateNodeNameToInfoMap(otherPods)[kl.nodeName] + fit, err := predicates.RunGeneralPredicates(pod, kl.nodeName, nodeInfo, node) + if !fit { + if re, ok := err.(*predicates.PredicateFailureError); ok { + reason := re.PredicateName + message := re.Error() + glog.V(2).Infof("Predicate failed on Pod: %v, for reason: %v", format.Pod(pod), message) + return fit, reason, message + } + if re, ok := err.(*predicates.InsufficientResourceError); ok { + reason := fmt.Sprintf("OutOf%s", re.ResourceName) + message := re.Error() + glog.V(2).Infof("Predicate failed on Pod: %v, for reason: %v", format.Pod(pod), message) + return fit, reason, message + } + reason := "UnexpectedPredicateFailureType" + message := fmt.Sprintf("GeneralPredicates failed due to %v, which is unexpected.", err) + glog.Warningf("Failed to admit pod %v - %s", format.Pod(pod), message) + return fit, reason, message + } + // TODO: When disk space scheduling is implemented (#11976), remove the out-of-disk check here and + // add the disk space predicate to predicates.GeneralPredicates. + if kl.isOutOfDisk() { + glog.Warningf("Failed to admit pod %v - %s", format.Pod(pod), "predicate fails due to isOutOfDisk") + return false, "OutOfDisk", "cannot be started due to lack of disk space." + } + return true, "", "" +} + +// syncLoop is the main loop for processing changes. It watches for changes from +// three channels (file, apiserver, and http) and creates a union of them. For +// any new change seen, will run a sync against desired state and running state. If +// no changes are seen to the configuration, will synchronize the last known desired +// state every sync-frequency seconds. Never returns. +func (kl *Kubelet) syncLoop(updates <-chan kubetypes.PodUpdate, handler SyncHandler) { + glog.Info("Starting kubelet main sync loop.") + // The resyncTicker wakes up kubelet to checks if there are any pod workers + // that need to be sync'd. A one-second period is sufficient because the + // sync interval is defaulted to 10s. + syncTicker := time.NewTicker(time.Second) + housekeepingTicker := time.NewTicker(housekeepingPeriod) + plegCh := kl.pleg.Watch() + for { + if rs := kl.runtimeState.errors(); len(rs) != 0 { + glog.Infof("skipping pod synchronization - %v", rs) + time.Sleep(5 * time.Second) + continue + } + if !kl.syncLoopIteration(updates, handler, syncTicker.C, housekeepingTicker.C, plegCh) { + break + } + } +} + +func (kl *Kubelet) syncLoopIteration(updates <-chan kubetypes.PodUpdate, handler SyncHandler, + syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool { + kl.syncLoopMonitor.Store(kl.clock.Now()) + select { + case u, open := <-updates: + if !open { + glog.Errorf("Update channel is closed. Exiting the sync loop.") + return false + } + kl.addSource(u.Source) + + switch u.Op { + case kubetypes.ADD: + glog.V(2).Infof("SyncLoop (ADD, %q): %q", u.Source, format.Pods(u.Pods)) + // After restarting, kubelet will get all existing pods through + // ADD as if they are new pods. These pods will then go through the + // admission process and *may* be rejcted. This can be resolved + // once we have checkpointing. + handler.HandlePodAdditions(u.Pods) + case kubetypes.UPDATE: + glog.V(2).Infof("SyncLoop (UPDATE, %q): %q", u.Source, format.Pods(u.Pods)) + handler.HandlePodUpdates(u.Pods) + case kubetypes.REMOVE: + glog.V(2).Infof("SyncLoop (REMOVE, %q): %q", u.Source, format.Pods(u.Pods)) + handler.HandlePodDeletions(u.Pods) + case kubetypes.RECONCILE: + glog.V(4).Infof("SyncLoop (RECONCILE, %q): %q", u.Source, format.Pods(u.Pods)) + handler.HandlePodReconcile(u.Pods) + case kubetypes.SET: + // TODO: Do we want to support this? + glog.Errorf("Kubelet does not support snapshot update") + } + case e := <-plegCh: + pod, ok := kl.podManager.GetPodByUID(e.ID) + if !ok { + // If the pod no longer exists, ignore the event. + glog.V(4).Infof("SyncLoop (PLEG): ignore irrelevant event: %#v", e) + break + } + glog.V(2).Infof("SyncLoop (PLEG): %q, event: %#v", format.Pod(pod), e) + // Force the container runtime cache to update. + if err := kl.runtimeCache.ForceUpdateIfOlder(kl.clock.Now()); err != nil { + glog.Errorf("SyncLoop: unable to update runtime cache") + // TODO (yujuhong): should we delay the sync until container + // runtime can be updated? + } + handler.HandlePodSyncs([]*api.Pod{pod}) + case <-syncCh: + podsToSync := kl.getPodsToSync() + if len(podsToSync) == 0 { + break + } + glog.V(4).Infof("SyncLoop (SYNC): %d pods; %s", len(podsToSync), format.Pods(podsToSync)) + kl.HandlePodSyncs(podsToSync) + case update := <-kl.livenessManager.Updates(): + // We only care about failures (signalling container death) here. + if update.Result == proberesults.Failure { + // We should not use the pod from livenessManager, because it is never updated after + // initialization. + pod, ok := kl.podManager.GetPodByUID(update.PodUID) + if !ok { + // If the pod no longer exists, ignore the update. + glog.V(4).Infof("SyncLoop (container unhealthy): ignore irrelevant update: %#v", update) + break + } + glog.V(1).Infof("SyncLoop (container unhealthy): %q", format.Pod(pod)) + handler.HandlePodSyncs([]*api.Pod{pod}) + } + case <-housekeepingCh: + if !kl.allSourcesReady() { + // If the sources aren't ready, skip housekeeping, as we may + // accidentally delete pods from unready sources. + glog.V(4).Infof("SyncLoop (housekeeping, skipped): sources aren't ready yet.") + } else { + glog.V(4).Infof("SyncLoop (housekeeping)") + if err := handler.HandlePodCleanups(); err != nil { + glog.Errorf("Failed cleaning pods: %v", err) + } + } + } + kl.syncLoopMonitor.Store(kl.clock.Now()) + return true +} + +func (kl *Kubelet) dispatchWork(pod *api.Pod, syncType kubetypes.SyncPodType, mirrorPod *api.Pod, start time.Time) { + if kl.podIsTerminated(pod) { + if pod.DeletionTimestamp != nil { + // If the pod is in a termianted state, there is no pod worker to + // handle the work item. Check if the DeletionTimestamp has been + // set, and force a status update to trigger a pod deletion request + // to the apiserver. + kl.statusManager.TerminatePod(pod) + } + return + } + // Run the sync in an async worker. + kl.podWorkers.UpdatePod(pod, mirrorPod, syncType, func() { + metrics.PodWorkerLatency.WithLabelValues(syncType.String()).Observe(metrics.SinceInMicroseconds(start)) + }) + // Note the number of containers for new pods. + if syncType == kubetypes.SyncPodCreate { + metrics.ContainersPerPodCount.Observe(float64(len(pod.Spec.Containers))) + } +} + +// TODO: Consider handling all mirror pods updates in a separate component. +func (kl *Kubelet) handleMirrorPod(mirrorPod *api.Pod, start time.Time) { + // Mirror pod ADD/UPDATE/DELETE operations are considered an UPDATE to the + // corresponding static pod. Send update to the pod worker if the static + // pod exists. + if pod, ok := kl.podManager.GetPodByMirrorPod(mirrorPod); ok { + kl.dispatchWork(pod, kubetypes.SyncPodUpdate, mirrorPod, start) + } +} + +func (kl *Kubelet) HandlePodAdditions(pods []*api.Pod) { + start := kl.clock.Now() + sort.Sort(podsByCreationTime(pods)) + for _, pod := range pods { + kl.podManager.AddPod(pod) + if kubepod.IsMirrorPod(pod) { + kl.handleMirrorPod(pod, start) + continue + } + // Note that allPods includes the new pod since we added at the + // beginning of the loop. + allPods := kl.podManager.GetPods() + // We failed pods that we rejected, so activePods include all admitted + // pods that are alive and the new pod. + activePods := kl.filterOutTerminatedPods(allPods) + // Check if we can admit the pod; if not, reject it. + if ok, reason, message := kl.canAdmitPod(activePods, pod); !ok { + kl.rejectPod(pod, reason, message) + continue + } + mirrorPod, _ := kl.podManager.GetMirrorPodByPod(pod) + kl.dispatchWork(pod, kubetypes.SyncPodCreate, mirrorPod, start) + kl.probeManager.AddPod(pod) + } +} + +func (kl *Kubelet) HandlePodUpdates(pods []*api.Pod) { + start := kl.clock.Now() + for _, pod := range pods { + kl.podManager.UpdatePod(pod) + if kubepod.IsMirrorPod(pod) { + kl.handleMirrorPod(pod, start) + continue + } + // TODO: Evaluate if we need to validate and reject updates. + + mirrorPod, _ := kl.podManager.GetMirrorPodByPod(pod) + kl.dispatchWork(pod, kubetypes.SyncPodUpdate, mirrorPod, start) + } +} + +func (kl *Kubelet) HandlePodDeletions(pods []*api.Pod) { + start := kl.clock.Now() + for _, pod := range pods { + kl.podManager.DeletePod(pod) + if kubepod.IsMirrorPod(pod) { + kl.handleMirrorPod(pod, start) + continue + } + // Deletion is allowed to fail because the periodic cleanup routine + // will trigger deletion again. + if err := kl.deletePod(pod); err != nil { + glog.V(2).Infof("Failed to delete pod %q, err: %v", format.Pod(pod), err) + } + kl.probeManager.RemovePod(pod) + } +} + +func (kl *Kubelet) HandlePodReconcile(pods []*api.Pod) { + for _, pod := range pods { + // Update the pod in pod manager, status manager will do periodically reconcile according + // to the pod manager. + kl.podManager.UpdatePod(pod) + } +} + +func (kl *Kubelet) HandlePodSyncs(pods []*api.Pod) { + start := kl.clock.Now() + for _, pod := range pods { + mirrorPod, _ := kl.podManager.GetMirrorPodByPod(pod) + kl.dispatchWork(pod, kubetypes.SyncPodSync, mirrorPod, start) + } +} + +func (kl *Kubelet) LatestLoopEntryTime() time.Time { + val := kl.syncLoopMonitor.Load() + if val == nil { + return time.Time{} + } + return val.(time.Time) +} + +func (kl *Kubelet) PLEGHealthCheck() (bool, error) { + return kl.pleg.Healthy() +} + +// validateContainerLogStatus returns the container ID for the desired container to retrieve logs for, based on the state +// of the container. The previous flag will only return the logs for the the last terminated container, otherwise, the current +// running container is preferred over a previous termination. If info about the container is not available then a specific +// error is returned to the end user. +func (kl *Kubelet) validateContainerLogStatus(podName string, podStatus *api.PodStatus, containerName string, previous bool) (containerID kubecontainer.ContainerID, err error) { + var cID string + + cStatus, found := api.GetContainerStatus(podStatus.ContainerStatuses, containerName) + if !found { + return kubecontainer.ContainerID{}, fmt.Errorf("container %q in pod %q is not available", containerName, podName) + } + lastState := cStatus.LastTerminationState + waiting, running, terminated := cStatus.State.Waiting, cStatus.State.Running, cStatus.State.Terminated + + switch { + case previous: + if lastState.Terminated == nil { + return kubecontainer.ContainerID{}, fmt.Errorf("previous terminated container %q in pod %q not found", containerName, podName) + } + cID = lastState.Terminated.ContainerID + + case running != nil: + cID = cStatus.ContainerID + + case terminated != nil: + cID = terminated.ContainerID + + case lastState.Terminated != nil: + cID = lastState.Terminated.ContainerID + + case waiting != nil: + // output some info for the most common pending failures + switch reason := waiting.Reason; reason { + case kubecontainer.ErrImagePull.Error(): + return kubecontainer.ContainerID{}, fmt.Errorf("container %q in pod %q is waiting to start: image can't be pulled", containerName, podName) + case kubecontainer.ErrImagePullBackOff.Error(): + return kubecontainer.ContainerID{}, fmt.Errorf("container %q in pod %q is waiting to start: trying and failing to pull image", containerName, podName) + default: + return kubecontainer.ContainerID{}, fmt.Errorf("container %q in pod %q is waiting to start: %v", containerName, podName, reason) + } + default: + // unrecognized state + return kubecontainer.ContainerID{}, fmt.Errorf("container %q in pod %q is waiting to start - no logs yet", containerName, podName) + } + + return kubecontainer.ParseContainerID(cID), nil +} + +// GetKubeletContainerLogs returns logs from the container +// TODO: this method is returning logs of random container attempts, when it should be returning the most recent attempt +// or all of them. +func (kl *Kubelet) GetKubeletContainerLogs(podFullName, containerName string, logOptions *api.PodLogOptions, stdout, stderr io.Writer) error { + // TODO(vmarmol): Refactor to not need the pod status and verification. + // Pod workers periodically write status to statusManager. If status is not + // cached there, something is wrong (or kubelet just restarted and hasn't + // caught up yet). Just assume the pod is not ready yet. + name, namespace, err := kubecontainer.ParsePodFullName(podFullName) + if err != nil { + return fmt.Errorf("unable to parse pod full name %q: %v", podFullName, err) + } + + pod, ok := kl.GetPodByName(namespace, name) + if !ok { + return fmt.Errorf("pod %q cannot be found - no logs available", name) + } + + podUID := pod.UID + if mirrorPod, ok := kl.podManager.GetMirrorPodByPod(pod); ok { + podUID = mirrorPod.UID + } + podStatus, found := kl.statusManager.GetPodStatus(podUID) + if !found { + // If there is no cached status, use the status from the + // apiserver. This is useful if kubelet has recently been + // restarted. + podStatus = pod.Status + } + + containerID, err := kl.validateContainerLogStatus(pod.Name, &podStatus, containerName, logOptions.Previous) + if err != nil { + return err + } + return kl.containerRuntime.GetContainerLogs(pod, containerID, logOptions, stdout, stderr) +} + +// GetHostname Returns the hostname as the kubelet sees it. +func (kl *Kubelet) GetHostname() string { + return kl.hostname +} + +// Returns host IP or nil in case of error. +func (kl *Kubelet) GetHostIP() (net.IP, error) { + node, err := kl.GetNode() + if err != nil { + return nil, fmt.Errorf("cannot get node: %v", err) + } + return nodeutil.GetNodeHostIP(node) +} + +// GetPods returns all pods bound to the kubelet and their spec, and the mirror +// pods. +func (kl *Kubelet) GetPods() []*api.Pod { + return kl.podManager.GetPods() +} + +// GetRunningPods returns all pods running on kubelet from looking at the +// container runtime cache. This function converts kubecontainer.Pod to +// api.Pod, so only the fields that exist in both kubecontainer.Pod and +// api.Pod are considered meaningful. +func (kl *Kubelet) GetRunningPods() ([]*api.Pod, error) { + pods, err := kl.runtimeCache.GetPods() + if err != nil { + return nil, err + } + + apiPods := make([]*api.Pod, 0, len(pods)) + for _, pod := range pods { + apiPods = append(apiPods, pod.ToAPIPod()) + } + return apiPods, nil +} + +func (kl *Kubelet) GetPodByFullName(podFullName string) (*api.Pod, bool) { + return kl.podManager.GetPodByFullName(podFullName) +} + +// GetPodByName provides the first pod that matches namespace and name, as well +// as whether the pod was found. +func (kl *Kubelet) GetPodByName(namespace, name string) (*api.Pod, bool) { + return kl.podManager.GetPodByName(namespace, name) +} + +func (kl *Kubelet) updateRuntimeUp() { + if err := kl.containerRuntime.Status(); err != nil { + glog.Errorf("Container runtime sanity check failed: %v", err) + return + } + kl.oneTimeInitializer.Do(kl.initializeRuntimeDependentModules) + kl.runtimeState.setRuntimeSync(kl.clock.Now()) +} + +// TODO: remove when kubenet plugin is ready +// NOTE!!! if you make changes here, also make them to kubenet +func (kl *Kubelet) reconcileCBR0(podCIDR string) error { + if podCIDR == "" { + glog.V(5).Info("PodCIDR not set. Will not configure cbr0.") + return nil + } + glog.V(5).Infof("PodCIDR is set to %q", podCIDR) + _, cidr, err := net.ParseCIDR(podCIDR) + if err != nil { + return err + } + // Set cbr0 interface address to first address in IPNet + cidr.IP.To4()[3] += 1 + if err := ensureCbr0(cidr, kl.hairpinMode == componentconfig.PromiscuousBridge, kl.babysitDaemons); err != nil { + return err + } + if kl.shapingEnabled() { + if kl.shaper == nil { + glog.V(5).Info("Shaper is nil, creating") + kl.shaper = bandwidth.NewTCShaper("cbr0") + } + return kl.shaper.ReconcileInterface() + } + return nil +} + +// updateNodeStatus updates node status to master with retries. +func (kl *Kubelet) updateNodeStatus() error { + for i := 0; i < nodeStatusUpdateRetry; i++ { + if err := kl.tryUpdateNodeStatus(); err != nil { + glog.Errorf("Error updating node status, will retry: %v", err) + } else { + return nil + } + } + return fmt.Errorf("update node status exceeds retry count") +} + +func (kl *Kubelet) recordNodeStatusEvent(eventtype, event string) { + glog.V(2).Infof("Recording %s event message for node %s", event, kl.nodeName) + // TODO: This requires a transaction, either both node status is updated + // and event is recorded or neither should happen, see issue #6055. + kl.recorder.Eventf(kl.nodeRef, eventtype, event, "Node %s status is now: %s", kl.nodeName, event) +} + +func (kl *Kubelet) syncNetworkStatus() { + var err error + if kl.configureCBR0 { + if kl.flannelExperimentalOverlay { + podCIDR, err := kl.flannelHelper.Handshake() + if err != nil { + glog.Infof("Flannel server handshake failed %v", err) + return + } + kl.updatePodCIDR(podCIDR) + } + if err := ensureIPTablesMasqRule(kl.nonMasqueradeCIDR); err != nil { + err = fmt.Errorf("Error on adding ip table rules: %v", err) + glog.Error(err) + kl.runtimeState.setNetworkState(err) + return + } + podCIDR := kl.runtimeState.podCIDR() + if len(podCIDR) == 0 { + err = fmt.Errorf("ConfigureCBR0 requested, but PodCIDR not set. Will not configure CBR0 right now") + glog.Warning(err) + } else if err = kl.reconcileCBR0(podCIDR); err != nil { + err = fmt.Errorf("Error configuring cbr0: %v", err) + glog.Error(err) + } + } + kl.runtimeState.setNetworkState(err) +} + +// Set addresses for the node. +func (kl *Kubelet) setNodeAddress(node *api.Node) error { + // Set addresses for the node. + if kl.cloud != nil { + instances, ok := kl.cloud.Instances() + if !ok { + return fmt.Errorf("failed to get instances from cloud provider") + } + // TODO(roberthbailey): Can we do this without having credentials to talk + // to the cloud provider? + // TODO(justinsb): We can if CurrentNodeName() was actually CurrentNode() and returned an interface + nodeAddresses, err := instances.NodeAddresses(kl.nodeName) + if err != nil { + return fmt.Errorf("failed to get node address from cloud provider: %v", err) + } + node.Status.Addresses = nodeAddresses + } else { + if kl.nodeIP != nil { + node.Status.Addresses = []api.NodeAddress{ + {Type: api.NodeLegacyHostIP, Address: kl.nodeIP.String()}, + {Type: api.NodeInternalIP, Address: kl.nodeIP.String()}, + } + } else if addr := net.ParseIP(kl.hostname); addr != nil { + node.Status.Addresses = []api.NodeAddress{ + {Type: api.NodeLegacyHostIP, Address: addr.String()}, + {Type: api.NodeInternalIP, Address: addr.String()}, + } + } else { + addrs, err := net.LookupIP(node.Name) + if err != nil { + return fmt.Errorf("can't get ip address of node %s: %v", node.Name, err) + } else if len(addrs) == 0 { + return fmt.Errorf("no ip address for node %v", node.Name) + } else { + // check all ip addresses for this node.Name and try to find the first non-loopback IPv4 address. + // If no match is found, it uses the IP of the interface with gateway on it. + for _, ip := range addrs { + if ip.IsLoopback() { + continue + } + + if ip.To4() != nil { + node.Status.Addresses = []api.NodeAddress{ + {Type: api.NodeLegacyHostIP, Address: ip.String()}, + {Type: api.NodeInternalIP, Address: ip.String()}, + } + break + } + } + + if len(node.Status.Addresses) == 0 { + ip, err := utilnet.ChooseHostInterface() + if err != nil { + return err + } + + node.Status.Addresses = []api.NodeAddress{ + {Type: api.NodeLegacyHostIP, Address: ip.String()}, + {Type: api.NodeInternalIP, Address: ip.String()}, + } + } + } + } + } + return nil +} + +func (kl *Kubelet) setNodeStatusMachineInfo(node *api.Node) { + // TODO: Post NotReady if we cannot get MachineInfo from cAdvisor. This needs to start + // cAdvisor locally, e.g. for test-cmd.sh, and in integration test. + info, err := kl.GetCachedMachineInfo() + if err != nil { + // TODO(roberthbailey): This is required for test-cmd.sh to pass. + // See if the test should be updated instead. + node.Status.Capacity = api.ResourceList{ + api.ResourceCPU: *resource.NewMilliQuantity(0, resource.DecimalSI), + api.ResourceMemory: resource.MustParse("0Gi"), + api.ResourcePods: *resource.NewQuantity(int64(kl.maxPods), resource.DecimalSI), + } + glog.Errorf("Error getting machine info: %v", err) + } else { + node.Status.NodeInfo.MachineID = info.MachineID + node.Status.NodeInfo.SystemUUID = info.SystemUUID + node.Status.Capacity = cadvisor.CapacityFromMachineInfo(info) + node.Status.Capacity[api.ResourcePods] = *resource.NewQuantity( + int64(kl.maxPods), resource.DecimalSI) + if node.Status.NodeInfo.BootID != "" && + node.Status.NodeInfo.BootID != info.BootID { + // TODO: This requires a transaction, either both node status is updated + // and event is recorded or neither should happen, see issue #6055. + kl.recorder.Eventf(kl.nodeRef, api.EventTypeWarning, kubecontainer.NodeRebooted, + "Node %s has been rebooted, boot id: %s", kl.nodeName, info.BootID) + } + node.Status.NodeInfo.BootID = info.BootID + } + + // Set Allocatable. + node.Status.Allocatable = make(api.ResourceList) + for k, v := range node.Status.Capacity { + value := *(v.Copy()) + if kl.reservation.System != nil { + value.Sub(kl.reservation.System[k]) + } + if kl.reservation.Kubernetes != nil { + value.Sub(kl.reservation.Kubernetes[k]) + } + if value.Amount != nil && value.Amount.Sign() < 0 { + // Negative Allocatable resources don't make sense. + value.Set(0) + } + node.Status.Allocatable[k] = value + } +} + +// Set versioninfo for the node. +func (kl *Kubelet) setNodeStatusVersionInfo(node *api.Node) { + verinfo, err := kl.cadvisor.VersionInfo() + if err != nil { + glog.Errorf("Error getting version info: %v", err) + } else { + node.Status.NodeInfo.KernelVersion = verinfo.KernelVersion + node.Status.NodeInfo.OSImage = verinfo.ContainerOsVersion + + runtimeVersion := "Unknown" + if runtimeVer, err := kl.containerRuntime.Version(); err == nil { + runtimeVersion = runtimeVer.String() + } + node.Status.NodeInfo.ContainerRuntimeVersion = fmt.Sprintf("%s://%s", kl.containerRuntime.Type(), runtimeVersion) + + node.Status.NodeInfo.KubeletVersion = version.Get().String() + // TODO: kube-proxy might be different version from kubelet in the future + node.Status.NodeInfo.KubeProxyVersion = version.Get().String() + } + +} + +// Set daemonEndpoints for the node. +func (kl *Kubelet) setNodeStatusDaemonEndpoints(node *api.Node) { + node.Status.DaemonEndpoints = *kl.daemonEndpoints +} + +// Set images list fot this node +func (kl *Kubelet) setNodeStatusImages(node *api.Node) { + // Update image list of this node + var imagesOnNode []api.ContainerImage + containerImages, err := kl.imageManager.GetImageList() + if err != nil { + glog.Errorf("Error getting image list: %v", err) + } else { + for _, image := range containerImages { + imagesOnNode = append(imagesOnNode, api.ContainerImage{ + Names: image.RepoTags, + SizeBytes: image.Size, + }) + } + } + node.Status.Images = imagesOnNode +} + +// Set status for the node. +func (kl *Kubelet) setNodeStatusInfo(node *api.Node) { + kl.setNodeStatusMachineInfo(node) + kl.setNodeStatusVersionInfo(node) + kl.setNodeStatusDaemonEndpoints(node) + kl.setNodeStatusImages(node) +} + +// Set Readycondition for the node. +func (kl *Kubelet) setNodeReadyCondition(node *api.Node) { + // NOTE(aaronlevy): NodeReady condition needs to be the last in the list of node conditions. + // This is due to an issue with version skewed kubelet and master components. + // ref: https://github.com/kubernetes/kubernetes/issues/16961 + currentTime := unversioned.NewTime(kl.clock.Now()) + var newNodeReadyCondition api.NodeCondition + if rs := kl.runtimeState.errors(); len(rs) == 0 { + newNodeReadyCondition = api.NodeCondition{ + Type: api.NodeReady, + Status: api.ConditionTrue, + Reason: "KubeletReady", + Message: "kubelet is posting ready status", + LastHeartbeatTime: currentTime, + } + } else { + newNodeReadyCondition = api.NodeCondition{ + Type: api.NodeReady, + Status: api.ConditionFalse, + Reason: "KubeletNotReady", + Message: strings.Join(rs, ","), + LastHeartbeatTime: currentTime, + } + } + + // Record any soft requirements that were not met in the container manager. + status := kl.containerManager.Status() + if status.SoftRequirements != nil { + newNodeReadyCondition.Message = fmt.Sprintf("%s. WARNING: %s", newNodeReadyCondition.Message, status.SoftRequirements.Error()) + } + + readyConditionUpdated := false + needToRecordEvent := false + for i := range node.Status.Conditions { + if node.Status.Conditions[i].Type == api.NodeReady { + if node.Status.Conditions[i].Status == newNodeReadyCondition.Status { + newNodeReadyCondition.LastTransitionTime = node.Status.Conditions[i].LastTransitionTime + } else { + newNodeReadyCondition.LastTransitionTime = currentTime + needToRecordEvent = true + } + node.Status.Conditions[i] = newNodeReadyCondition + readyConditionUpdated = true + break + } + } + if !readyConditionUpdated { + newNodeReadyCondition.LastTransitionTime = currentTime + node.Status.Conditions = append(node.Status.Conditions, newNodeReadyCondition) + } + if needToRecordEvent { + if newNodeReadyCondition.Status == api.ConditionTrue { + kl.recordNodeStatusEvent(api.EventTypeNormal, kubecontainer.NodeReady) + } else { + kl.recordNodeStatusEvent(api.EventTypeNormal, kubecontainer.NodeNotReady) + } + } +} + +// Set OODcondition for the node. +func (kl *Kubelet) setNodeOODCondition(node *api.Node) { + currentTime := unversioned.NewTime(kl.clock.Now()) + var nodeOODCondition *api.NodeCondition + + // Check if NodeOutOfDisk condition already exists and if it does, just pick it up for update. + for i := range node.Status.Conditions { + if node.Status.Conditions[i].Type == api.NodeOutOfDisk { + nodeOODCondition = &node.Status.Conditions[i] + } + } + + newOODCondition := false + // If the NodeOutOfDisk condition doesn't exist, create one. + if nodeOODCondition == nil { + nodeOODCondition = &api.NodeCondition{ + Type: api.NodeOutOfDisk, + Status: api.ConditionUnknown, + } + // nodeOODCondition cannot be appended to node.Status.Conditions here because it gets + // copied to the slice. So if we append nodeOODCondition to the slice here none of the + // updates we make to nodeOODCondition below are reflected in the slice. + newOODCondition = true + } + + // Update the heartbeat time irrespective of all the conditions. + nodeOODCondition.LastHeartbeatTime = currentTime + + // Note: The conditions below take care of the case when a new NodeOutOfDisk condition is + // created and as well as the case when the condition already exists. When a new condition + // is created its status is set to api.ConditionUnknown which matches either + // nodeOODCondition.Status != api.ConditionTrue or + // nodeOODCondition.Status != api.ConditionFalse in the conditions below depending on whether + // the kubelet is out of disk or not. + if kl.isOutOfDisk() { + if nodeOODCondition.Status != api.ConditionTrue { + nodeOODCondition.Status = api.ConditionTrue + nodeOODCondition.Reason = "KubeletOutOfDisk" + nodeOODCondition.Message = "out of disk space" + nodeOODCondition.LastTransitionTime = currentTime + kl.recordNodeStatusEvent(api.EventTypeNormal, "NodeOutOfDisk") + } + } else { + if nodeOODCondition.Status != api.ConditionFalse { + // Update the out of disk condition when the condition status is unknown even if we + // are within the outOfDiskTransitionFrequency duration. We do this to set the + // condition status correctly at kubelet startup. + if nodeOODCondition.Status == api.ConditionUnknown || kl.clock.Since(nodeOODCondition.LastTransitionTime.Time) >= kl.outOfDiskTransitionFrequency { + nodeOODCondition.Status = api.ConditionFalse + nodeOODCondition.Reason = "KubeletHasSufficientDisk" + nodeOODCondition.Message = "kubelet has sufficient disk space available" + nodeOODCondition.LastTransitionTime = currentTime + kl.recordNodeStatusEvent(api.EventTypeNormal, "NodeHasSufficientDisk") + } else { + glog.Infof("Node condition status for OutOfDisk is false, but last transition time is less than %s", kl.outOfDiskTransitionFrequency) + } + } + } + + if newOODCondition { + node.Status.Conditions = append(node.Status.Conditions, *nodeOODCondition) + } +} + +// Maintains Node.Spec.Unschedulable value from previous run of tryUpdateNodeStatus() +var oldNodeUnschedulable bool + +// record if node schedulable change. +func (kl *Kubelet) recordNodeSchdulableEvent(node *api.Node) { + if oldNodeUnschedulable != node.Spec.Unschedulable { + if node.Spec.Unschedulable { + kl.recordNodeStatusEvent(api.EventTypeNormal, kubecontainer.NodeNotSchedulable) + } else { + kl.recordNodeStatusEvent(api.EventTypeNormal, kubecontainer.NodeSchedulable) + } + oldNodeUnschedulable = node.Spec.Unschedulable + } +} + +// setNodeStatus fills in the Status fields of the given Node, overwriting +// any fields that are currently set. +// TODO(madhusudancs): Simplify the logic for setting node conditions and +// refactor the node status condtion code out to a different file. +func (kl *Kubelet) setNodeStatus(node *api.Node) error { + for _, f := range kl.setNodeStatusFuncs { + if err := f(node); err != nil { + return err + } + } + return nil +} + +// defaultNodeStatusFuncs is a factory that generates the default set of setNodeStatus funcs +func (kl *Kubelet) defaultNodeStatusFuncs() []func(*api.Node) error { + // initial set of node status update handlers, can be modified by Option's + withoutError := func(f func(*api.Node)) func(*api.Node) error { + return func(n *api.Node) error { + f(n) + return nil + } + } + return []func(*api.Node) error{ + kl.setNodeAddress, + withoutError(kl.setNodeStatusInfo), + withoutError(kl.setNodeOODCondition), + withoutError(kl.setNodeReadyCondition), + withoutError(kl.recordNodeSchdulableEvent), + } +} + +// SetNodeStatus returns a functional Option that adds the given node status update handler to the Kubelet +func SetNodeStatus(f func(*api.Node) error) Option { + return func(k *Kubelet) { + k.setNodeStatusFuncs = append(k.setNodeStatusFuncs, f) + } +} + +// tryUpdateNodeStatus tries to update node status to master. If ReconcileCBR0 +// is set, this function will also confirm that cbr0 is configured correctly. +func (kl *Kubelet) tryUpdateNodeStatus() error { + node, err := kl.kubeClient.Core().Nodes().Get(kl.nodeName) + if err != nil { + return fmt.Errorf("error getting node %q: %v", kl.nodeName, err) + } + if node == nil { + return fmt.Errorf("no node instance returned for %q", kl.nodeName) + } + // Flannel is the authoritative source of pod CIDR, if it's running. + // This is a short term compromise till we get flannel working in + // reservation mode. + if kl.flannelExperimentalOverlay { + flannelPodCIDR := kl.runtimeState.podCIDR() + if node.Spec.PodCIDR != flannelPodCIDR { + node.Spec.PodCIDR = flannelPodCIDR + glog.Infof("Updating podcidr to %v", node.Spec.PodCIDR) + if updatedNode, err := kl.kubeClient.Core().Nodes().Update(node); err != nil { + glog.Warningf("Failed to update podCIDR: %v", err) + } else { + // Update the node resourceVersion so the status update doesn't fail. + node = updatedNode + } + } + } else if kl.reconcileCIDR { + kl.updatePodCIDR(node.Spec.PodCIDR) + } + + if err := kl.setNodeStatus(node); err != nil { + return err + } + // Update the current status on the API server + _, err = kl.kubeClient.Core().Nodes().UpdateStatus(node) + return err +} + +// GetPhase returns the phase of a pod given its container info. +// This func is exported to simplify integration with 3rd party kubelet +// integrations like kubernetes-mesos. +func GetPhase(spec *api.PodSpec, info []api.ContainerStatus) api.PodPhase { + running := 0 + waiting := 0 + stopped := 0 + failed := 0 + succeeded := 0 + unknown := 0 + for _, container := range spec.Containers { + containerStatus, ok := api.GetContainerStatus(info, container.Name) + if !ok { + unknown++ + continue + } + + switch { + case containerStatus.State.Running != nil: + running++ + case containerStatus.State.Terminated != nil: + stopped++ + if containerStatus.State.Terminated.ExitCode == 0 { + succeeded++ + } else { + failed++ + } + case containerStatus.State.Waiting != nil: + if containerStatus.LastTerminationState.Terminated != nil { + stopped++ + } else { + waiting++ + } + default: + unknown++ + } + } + + switch { + case waiting > 0: + glog.V(5).Infof("pod waiting > 0, pending") + // One or more containers has not been started + return api.PodPending + case running > 0 && unknown == 0: + // All containers have been started, and at least + // one container is running + return api.PodRunning + case running == 0 && stopped > 0 && unknown == 0: + // All containers are terminated + if spec.RestartPolicy == api.RestartPolicyAlways { + // All containers are in the process of restarting + return api.PodRunning + } + if stopped == succeeded { + // RestartPolicy is not Always, and all + // containers are terminated in success + return api.PodSucceeded + } + if spec.RestartPolicy == api.RestartPolicyNever { + // RestartPolicy is Never, and all containers are + // terminated with at least one in failure + return api.PodFailed + } + // RestartPolicy is OnFailure, and at least one in failure + // and in the process of restarting + return api.PodRunning + default: + glog.V(5).Infof("pod default case, pending") + return api.PodPending + } +} + +func (kl *Kubelet) generatePodStatus(pod *api.Pod, podStatus *kubecontainer.PodStatus) api.PodStatus { + glog.V(3).Infof("Generating status for %q", format.Pod(pod)) + // TODO: Consider include the container information. + if kl.pastActiveDeadline(pod) { + reason := "DeadlineExceeded" + kl.recorder.Eventf(pod, api.EventTypeNormal, reason, "Pod was active on the node longer than specified deadline") + return api.PodStatus{ + Phase: api.PodFailed, + Reason: reason, + Message: "Pod was active on the node longer than specified deadline"} + } + + s := kl.convertStatusToAPIStatus(pod, podStatus) + + // Assume info is ready to process + spec := &pod.Spec + s.Phase = GetPhase(spec, s.ContainerStatuses) + kl.probeManager.UpdatePodStatus(pod.UID, s) + s.Conditions = append(s.Conditions, status.GeneratePodReadyCondition(spec, s.ContainerStatuses, s.Phase)) + + if !kl.standaloneMode { + hostIP, err := kl.GetHostIP() + if err != nil { + glog.V(4).Infof("Cannot get host IP: %v", err) + } else { + s.HostIP = hostIP.String() + if podUsesHostNetwork(pod) && s.PodIP == "" { + s.PodIP = hostIP.String() + } + } + } + + return *s +} + +// TODO(random-liu): Move this to some better place. +// TODO(random-liu): Add test for convertStatusToAPIStatus() +func (kl *Kubelet) convertStatusToAPIStatus(pod *api.Pod, podStatus *kubecontainer.PodStatus) *api.PodStatus { + var apiPodStatus api.PodStatus + uid := pod.UID + + convertContainerStatus := func(cs *kubecontainer.ContainerStatus) *api.ContainerStatus { + cid := cs.ID.String() + status := &api.ContainerStatus{ + Name: cs.Name, + RestartCount: cs.RestartCount, + Image: cs.Image, + ImageID: cs.ImageID, + ContainerID: cid, + } + switch cs.State { + case kubecontainer.ContainerStateRunning: + status.State.Running = &api.ContainerStateRunning{StartedAt: unversioned.NewTime(cs.StartedAt)} + case kubecontainer.ContainerStateExited: + status.State.Terminated = &api.ContainerStateTerminated{ + ExitCode: cs.ExitCode, + Reason: cs.Reason, + Message: cs.Message, + StartedAt: unversioned.NewTime(cs.StartedAt), + FinishedAt: unversioned.NewTime(cs.FinishedAt), + ContainerID: cid, + } + default: + status.State.Waiting = &api.ContainerStateWaiting{} + } + return status + } + + // Make the latest container status comes first. + sort.Sort(sort.Reverse(kubecontainer.SortContainerStatusesByCreationTime(podStatus.ContainerStatuses))) + + statuses := make(map[string]*api.ContainerStatus, len(pod.Spec.Containers)) + // Create a map of expected containers based on the pod spec. + expectedContainers := make(map[string]api.Container) + for _, container := range pod.Spec.Containers { + expectedContainers[container.Name] = container + } + + containerDone := sets.NewString() + apiPodStatus.PodIP = podStatus.IP + for _, containerStatus := range podStatus.ContainerStatuses { + cName := containerStatus.Name + if _, ok := expectedContainers[cName]; !ok { + // This would also ignore the infra container. + continue + } + if containerDone.Has(cName) { + continue + } + status := convertContainerStatus(containerStatus) + if existing, found := statuses[cName]; found { + existing.LastTerminationState = status.State + containerDone.Insert(cName) + } else { + statuses[cName] = status + } + } + + // Handle the containers for which we cannot find any associated active or dead containers or are in restart backoff + // Fetch old containers statuses from old pod status. + // TODO(random-liu) Maybe it's better to get status from status manager, because it takes the newest status and there is not + // status in api.Pod of static pod. + oldStatuses := make(map[string]api.ContainerStatus, len(pod.Spec.Containers)) + for _, status := range pod.Status.ContainerStatuses { + oldStatuses[status.Name] = status + } + for _, container := range pod.Spec.Containers { + // TODO(random-liu): We should define "Waiting" state better. And cleanup the following code. + if containerStatus, found := statuses[container.Name]; found { + reason, message, ok := kl.reasonCache.Get(uid, container.Name) + if ok && reason == kubecontainer.ErrCrashLoopBackOff { + containerStatus.LastTerminationState = containerStatus.State + containerStatus.State = api.ContainerState{ + Waiting: &api.ContainerStateWaiting{ + Reason: reason.Error(), + Message: message, + }, + } + } + continue + } + var containerStatus api.ContainerStatus + containerStatus.Name = container.Name + containerStatus.Image = container.Image + if oldStatus, found := oldStatuses[container.Name]; found { + // Some states may be lost due to GC; apply the last observed + // values if possible. + containerStatus.RestartCount = oldStatus.RestartCount + containerStatus.LastTerminationState = oldStatus.LastTerminationState + } + reason, _, ok := kl.reasonCache.Get(uid, container.Name) + + if !ok { + // default position for a container + // At this point there are no active or dead containers, the reasonCache is empty (no entry or the entry has expired) + // its reasonable to say the container is being created till a more accurate reason is logged + containerStatus.State = api.ContainerState{ + Waiting: &api.ContainerStateWaiting{ + Reason: fmt.Sprintf("ContainerCreating"), + Message: fmt.Sprintf("Image: %s is ready, container is creating", container.Image), + }, + } + } else if reason == kubecontainer.ErrImagePullBackOff || + reason == kubecontainer.ErrImageInspect || + reason == kubecontainer.ErrImagePull || + reason == kubecontainer.ErrImageNeverPull || + reason == kubecontainer.RegistryUnavailable { + // mark it as waiting, reason will be filled bellow + containerStatus.State = api.ContainerState{Waiting: &api.ContainerStateWaiting{}} + } else if reason == kubecontainer.ErrRunContainer { + // mark it as waiting, reason will be filled bellow + containerStatus.State = api.ContainerState{Waiting: &api.ContainerStateWaiting{}} + } + statuses[container.Name] = &containerStatus + } + + apiPodStatus.ContainerStatuses = make([]api.ContainerStatus, 0) + for containerName, status := range statuses { + if status.State.Waiting != nil { + status.State.Running = nil + // For containers in the waiting state, fill in a specific reason if it is recorded. + if reason, message, ok := kl.reasonCache.Get(uid, containerName); ok { + status.State.Waiting.Reason = reason.Error() + status.State.Waiting.Message = message + } + } + apiPodStatus.ContainerStatuses = append(apiPodStatus.ContainerStatuses, *status) + } + + // Sort the container statuses since clients of this interface expect the list + // of containers in a pod has a deterministic order. + sort.Sort(kubetypes.SortedContainerStatuses(apiPodStatus.ContainerStatuses)) + return &apiPodStatus +} + +// Returns logs of current machine. +func (kl *Kubelet) ServeLogs(w http.ResponseWriter, req *http.Request) { + // TODO: whitelist logs we are willing to serve + kl.logServer.ServeHTTP(w, req) +} + +// findContainer finds and returns the container with the given pod ID, full name, and container name. +// It returns nil if not found. +func (kl *Kubelet) findContainer(podFullName string, podUID types.UID, containerName string) (*kubecontainer.Container, error) { + pods, err := kl.containerRuntime.GetPods(false) + if err != nil { + return nil, err + } + pod := kubecontainer.Pods(pods).FindPod(podFullName, podUID) + return pod.FindContainerByName(containerName), nil +} + +// Run a command in a container, returns the combined stdout, stderr as an array of bytes +func (kl *Kubelet) RunInContainer(podFullName string, podUID types.UID, containerName string, cmd []string) ([]byte, error) { + podUID = kl.podManager.TranslatePodUID(podUID) + + container, err := kl.findContainer(podFullName, podUID, containerName) + if err != nil { + return nil, err + } + if container == nil { + return nil, fmt.Errorf("container not found (%q)", containerName) + } + return kl.runner.RunInContainer(container.ID, cmd) +} + +// ExecInContainer executes a command in a container, connecting the supplied +// stdin/stdout/stderr to the command's IO streams. +func (kl *Kubelet) ExecInContainer(podFullName string, podUID types.UID, containerName string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error { + podUID = kl.podManager.TranslatePodUID(podUID) + + container, err := kl.findContainer(podFullName, podUID, containerName) + if err != nil { + return err + } + if container == nil { + return fmt.Errorf("container not found (%q)", containerName) + } + return kl.runner.ExecInContainer(container.ID, cmd, stdin, stdout, stderr, tty) +} + +func (kl *Kubelet) AttachContainer(podFullName string, podUID types.UID, containerName string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error { + podUID = kl.podManager.TranslatePodUID(podUID) + + container, err := kl.findContainer(podFullName, podUID, containerName) + if err != nil { + return err + } + if container == nil { + return fmt.Errorf("container not found (%q)", containerName) + } + return kl.containerRuntime.AttachContainer(container.ID, stdin, stdout, stderr, tty) +} + +// PortForward connects to the pod's port and copies data between the port +// and the stream. +func (kl *Kubelet) PortForward(podFullName string, podUID types.UID, port uint16, stream io.ReadWriteCloser) error { + podUID = kl.podManager.TranslatePodUID(podUID) + + pods, err := kl.containerRuntime.GetPods(false) + if err != nil { + return err + } + pod := kubecontainer.Pods(pods).FindPod(podFullName, podUID) + if pod.IsEmpty() { + return fmt.Errorf("pod not found (%q)", podFullName) + } + return kl.runner.PortForward(&pod, port, stream) +} + +// BirthCry sends an event that the kubelet has started up. +func (kl *Kubelet) BirthCry() { + // Make an event that kubelet restarted. + kl.recorder.Eventf(kl.nodeRef, api.EventTypeNormal, kubecontainer.StartingKubelet, "Starting kubelet.") +} + +func (kl *Kubelet) StreamingConnectionIdleTimeout() time.Duration { + return kl.streamingConnectionIdleTimeout +} + +func (kl *Kubelet) ResyncInterval() time.Duration { + return kl.resyncInterval +} + +// GetContainerInfo returns stats (from Cadvisor) for a container. +func (kl *Kubelet) GetContainerInfo(podFullName string, podUID types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) { + + podUID = kl.podManager.TranslatePodUID(podUID) + + pods, err := kl.runtimeCache.GetPods() + if err != nil { + return nil, err + } + pod := kubecontainer.Pods(pods).FindPod(podFullName, podUID) + container := pod.FindContainerByName(containerName) + if container == nil { + return nil, kubecontainer.ErrContainerNotFound + } + + ci, err := kl.cadvisor.DockerContainer(container.ID.ID, req) + if err != nil { + return nil, err + } + return &ci, nil +} + +// GetContainerInfoV2 returns stats (from Cadvisor) for containers. +func (kl *Kubelet) GetContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) { + return kl.cadvisor.ContainerInfoV2(name, options) +} + +func (kl *Kubelet) DockerImagesFsInfo() (cadvisorapiv2.FsInfo, error) { + return kl.cadvisor.DockerImagesFsInfo() +} + +func (kl *Kubelet) RootFsInfo() (cadvisorapiv2.FsInfo, error) { + return kl.cadvisor.RootFsInfo() +} + +// Returns stats (from Cadvisor) for a non-Kubernetes container. +func (kl *Kubelet) GetRawContainerInfo(containerName string, req *cadvisorapi.ContainerInfoRequest, subcontainers bool) (map[string]*cadvisorapi.ContainerInfo, error) { + if subcontainers { + return kl.cadvisor.SubcontainerInfo(containerName, req) + } else { + containerInfo, err := kl.cadvisor.ContainerInfo(containerName, req) + if err != nil { + return nil, err + } + return map[string]*cadvisorapi.ContainerInfo{ + containerInfo.Name: containerInfo, + }, nil + } +} + +// GetCachedMachineInfo assumes that the machine info can't change without a reboot +func (kl *Kubelet) GetCachedMachineInfo() (*cadvisorapi.MachineInfo, error) { + if kl.machineInfo == nil { + info, err := kl.cadvisor.MachineInfo() + if err != nil { + return nil, err + } + kl.machineInfo = info + } + return kl.machineInfo, nil +} + +func (kl *Kubelet) ListenAndServe(address net.IP, port uint, tlsOptions *server.TLSOptions, auth server.AuthInterface, enableDebuggingHandlers bool) { + server.ListenAndServeKubeletServer(kl, kl.resourceAnalyzer, address, port, tlsOptions, auth, enableDebuggingHandlers) +} + +func (kl *Kubelet) ListenAndServeReadOnly(address net.IP, port uint) { + server.ListenAndServeKubeletReadOnlyServer(kl, kl.resourceAnalyzer, address, port) +} + +// GetRuntime returns the current Runtime implementation in use by the kubelet. This func +// is exported to simplify integration with third party kubelet extensions (e.g. kubernetes-mesos). +func (kl *Kubelet) GetRuntime() kubecontainer.Runtime { + return kl.containerRuntime +} + +func (kl *Kubelet) updatePodCIDR(cidr string) { + if kl.runtimeState.podCIDR() == cidr { + return + } + + glog.Infof("Setting Pod CIDR: %v -> %v", kl.runtimeState.podCIDR(), cidr) + kl.runtimeState.setPodCIDR(cidr) + + if kl.networkPlugin != nil { + details := make(map[string]interface{}) + details[network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE_DETAIL_CIDR] = cidr + kl.networkPlugin.Event(network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE, details) + } +} + +func (kl *Kubelet) shapingEnabled() bool { + // Disable shaping if a network plugin is defined and supports shaping + if kl.networkPlugin != nil && kl.networkPlugin.Capabilities().Has(network.NET_PLUGIN_CAPABILITY_SHAPING) { + return false + } + return true +} + +func (kl *Kubelet) GetNodeConfig() cm.NodeConfig { + return kl.containerManager.GetNodeConfig() +} + +var minRsrc = resource.MustParse("1k") +var maxRsrc = resource.MustParse("1P") + +func validateBandwidthIsReasonable(rsrc *resource.Quantity) error { + if rsrc.Value() < minRsrc.Value() { + return fmt.Errorf("resource is unreasonably small (< 1kbit)") + } + if rsrc.Value() > maxRsrc.Value() { + return fmt.Errorf("resoruce is unreasonably large (> 1Pbit)") + } + return nil +} + +func extractBandwidthResources(pod *api.Pod) (ingress, egress *resource.Quantity, err error) { + str, found := pod.Annotations["kubernetes.io/ingress-bandwidth"] + if found { + if ingress, err = resource.ParseQuantity(str); err != nil { + return nil, nil, err + } + if err := validateBandwidthIsReasonable(ingress); err != nil { + return nil, nil, err + } + } + str, found = pod.Annotations["kubernetes.io/egress-bandwidth"] + if found { + if egress, err = resource.ParseQuantity(str); err != nil { + return nil, nil, err + } + if err := validateBandwidthIsReasonable(egress); err != nil { + return nil, nil, err + } + } + return ingress, egress, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/kubelet_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/kubelet_test.go new file mode 100644 index 000000000..51dcd0a6b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/kubelet_test.go @@ -0,0 +1,4430 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "os" + "path" + "reflect" + "sort" + "strings" + "testing" + "time" + + cadvisorapi "github.com/google/cadvisor/info/v1" + cadvisorapiv2 "github.com/google/cadvisor/info/v2" + "k8s.io/kubernetes/pkg/api" + apierrors "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/capabilities" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/client/testing/core" + "k8s.io/kubernetes/pkg/client/unversioned/testclient" + cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing" + "k8s.io/kubernetes/pkg/kubelet/cm" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" + "k8s.io/kubernetes/pkg/kubelet/network" + nettest "k8s.io/kubernetes/pkg/kubelet/network/testing" + "k8s.io/kubernetes/pkg/kubelet/pleg" + kubepod "k8s.io/kubernetes/pkg/kubelet/pod" + podtest "k8s.io/kubernetes/pkg/kubelet/pod/testing" + proberesults "k8s.io/kubernetes/pkg/kubelet/prober/results" + probetest "k8s.io/kubernetes/pkg/kubelet/prober/testing" + "k8s.io/kubernetes/pkg/kubelet/status" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/kubelet/util/queue" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/bandwidth" + "k8s.io/kubernetes/pkg/util/diff" + "k8s.io/kubernetes/pkg/util/flowcontrol" + "k8s.io/kubernetes/pkg/util/mount" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/version" + "k8s.io/kubernetes/pkg/volume" + _ "k8s.io/kubernetes/pkg/volume/host_path" + volumetest "k8s.io/kubernetes/pkg/volume/testing" +) + +func init() { + utilruntime.ReallyCrash = true +} + +const testKubeletHostname = "127.0.0.1" + +const testReservationCPU = "200m" +const testReservationMemory = "100M" + +type fakeHTTP struct { + url string + err error +} + +func (f *fakeHTTP) Get(url string) (*http.Response, error) { + f.url = url + return nil, f.err +} + +type TestKubelet struct { + kubelet *Kubelet + fakeRuntime *containertest.FakeRuntime + fakeCadvisor *cadvisortest.Mock + fakeKubeClient *fake.Clientset + fakeMirrorClient *podtest.FakeMirrorClient + fakeClock *util.FakeClock + mounter mount.Interface +} + +func newTestKubelet(t *testing.T) *TestKubelet { + fakeRuntime := &containertest.FakeRuntime{} + fakeRuntime.RuntimeType = "test" + fakeRuntime.VersionInfo = "1.5.0" + fakeRuntime.ImageList = []kubecontainer.Image{ + { + ID: "abc", + RepoTags: []string{"gcr.io/google_containers:v1", "gcr.io/google_containers:v2"}, + Size: 123, + }, + { + ID: "efg", + RepoTags: []string{"gcr.io/google_containers:v3", "gcr.io/google_containers:v4"}, + Size: 456, + }, + } + fakeRecorder := &record.FakeRecorder{} + fakeKubeClient := &fake.Clientset{} + kubelet := &Kubelet{} + kubelet.kubeClient = fakeKubeClient + kubelet.os = containertest.FakeOS{} + + kubelet.hostname = testKubeletHostname + kubelet.nodeName = testKubeletHostname + kubelet.runtimeState = newRuntimeState(maxWaitForContainerRuntime, false) + kubelet.networkPlugin, _ = network.InitNetworkPlugin([]network.NetworkPlugin{}, "", nettest.NewFakeHost(nil)) + if tempDir, err := ioutil.TempDir("/tmp", "kubelet_test."); err != nil { + t.Fatalf("can't make a temp rootdir: %v", err) + } else { + kubelet.rootDirectory = tempDir + } + if err := os.MkdirAll(kubelet.rootDirectory, 0750); err != nil { + t.Fatalf("can't mkdir(%q): %v", kubelet.rootDirectory, err) + } + kubelet.sourcesReady = func(_ sets.String) bool { return true } + kubelet.masterServiceNamespace = api.NamespaceDefault + kubelet.serviceLister = testServiceLister{} + kubelet.nodeLister = testNodeLister{} + kubelet.nodeInfo = testNodeInfo{} + kubelet.recorder = fakeRecorder + if err := kubelet.setupDataDirs(); err != nil { + t.Fatalf("can't initialize kubelet data dirs: %v", err) + } + kubelet.daemonEndpoints = &api.NodeDaemonEndpoints{} + mockCadvisor := &cadvisortest.Mock{} + kubelet.cadvisor = mockCadvisor + fakeMirrorClient := podtest.NewFakeMirrorClient() + kubelet.podManager = kubepod.NewBasicPodManager(fakeMirrorClient) + kubelet.statusManager = status.NewManager(fakeKubeClient, kubelet.podManager) + kubelet.containerRefManager = kubecontainer.NewRefManager() + diskSpaceManager, err := newDiskSpaceManager(mockCadvisor, DiskSpacePolicy{}) + if err != nil { + t.Fatalf("can't initialize disk space manager: %v", err) + } + kubelet.diskSpaceManager = diskSpaceManager + + kubelet.containerRuntime = fakeRuntime + kubelet.runtimeCache = containertest.NewFakeRuntimeCache(kubelet.containerRuntime) + kubelet.reasonCache = NewReasonCache() + kubelet.podCache = containertest.NewFakeCache(kubelet.containerRuntime) + kubelet.podWorkers = &fakePodWorkers{ + syncPodFn: kubelet.syncPod, + cache: kubelet.podCache, + t: t, + } + + kubelet.probeManager = probetest.FakeManager{} + kubelet.livenessManager = proberesults.NewManager() + + kubelet.volumeManager = newVolumeManager() + kubelet.containerManager = cm.NewStubContainerManager() + fakeNodeRef := &api.ObjectReference{ + Kind: "Node", + Name: testKubeletHostname, + UID: types.UID(testKubeletHostname), + Namespace: "", + } + fakeImageGCPolicy := ImageGCPolicy{ + HighThresholdPercent: 90, + LowThresholdPercent: 80, + } + kubelet.imageManager, err = newImageManager(fakeRuntime, mockCadvisor, fakeRecorder, fakeNodeRef, fakeImageGCPolicy) + fakeClock := util.NewFakeClock(time.Now()) + kubelet.backOff = flowcontrol.NewBackOff(time.Second, time.Minute) + kubelet.backOff.Clock = fakeClock + kubelet.podKillingCh = make(chan *kubecontainer.PodPair, 20) + kubelet.resyncInterval = 10 * time.Second + kubelet.reservation = kubetypes.Reservation{ + Kubernetes: api.ResourceList{ + api.ResourceCPU: resource.MustParse(testReservationCPU), + api.ResourceMemory: resource.MustParse(testReservationMemory), + }, + } + kubelet.workQueue = queue.NewBasicWorkQueue() + // Relist period does not affect the tests. + kubelet.pleg = pleg.NewGenericPLEG(fakeRuntime, 100, time.Hour, nil, util.RealClock{}) + kubelet.clock = fakeClock + kubelet.setNodeStatusFuncs = kubelet.defaultNodeStatusFuncs() + return &TestKubelet{kubelet, fakeRuntime, mockCadvisor, fakeKubeClient, fakeMirrorClient, fakeClock, nil} +} + +func newTestPods(count int) []*api.Pod { + pods := make([]*api.Pod, count) + for i := 0; i < count; i++ { + pods[i] = &api.Pod{ + Spec: api.PodSpec{ + SecurityContext: &api.PodSecurityContext{ + HostNetwork: true, + }, + }, + ObjectMeta: api.ObjectMeta{ + UID: types.UID(10000 + i), + Name: fmt.Sprintf("pod%d", i), + }, + } + } + return pods +} + +func TestKubeletDirs(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + root := kubelet.rootDirectory + + var exp, got string + + got = kubelet.getPodsDir() + exp = path.Join(root, "pods") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + got = kubelet.getPluginsDir() + exp = path.Join(root, "plugins") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + got = kubelet.getPluginDir("foobar") + exp = path.Join(root, "plugins/foobar") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + got = kubelet.getPodDir("abc123") + exp = path.Join(root, "pods/abc123") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + got = kubelet.getPodVolumesDir("abc123") + exp = path.Join(root, "pods/abc123/volumes") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + got = kubelet.getPodVolumeDir("abc123", "plugin", "foobar") + exp = path.Join(root, "pods/abc123/volumes/plugin/foobar") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + got = kubelet.getPodPluginsDir("abc123") + exp = path.Join(root, "pods/abc123/plugins") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + got = kubelet.getPodPluginDir("abc123", "foobar") + exp = path.Join(root, "pods/abc123/plugins/foobar") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + got = kubelet.getPodContainerDir("abc123", "def456") + exp = path.Join(root, "pods/abc123/containers/def456") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } +} + +func TestKubeletDirsCompat(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + root := kubelet.rootDirectory + if err := os.MkdirAll(root, 0750); err != nil { + t.Fatalf("can't mkdir(%q): %s", root, err) + } + + var exp, got string + + // Old-style pod dir. + if err := os.MkdirAll(fmt.Sprintf("%s/oldpod", root), 0750); err != nil { + t.Fatalf("can't mkdir(%q): %s", root, err) + } + // New-style pod dir. + if err := os.MkdirAll(fmt.Sprintf("%s/pods/newpod", root), 0750); err != nil { + t.Fatalf("can't mkdir(%q): %s", root, err) + } + // Both-style pod dir. + if err := os.MkdirAll(fmt.Sprintf("%s/bothpod", root), 0750); err != nil { + t.Fatalf("can't mkdir(%q): %s", root, err) + } + if err := os.MkdirAll(fmt.Sprintf("%s/pods/bothpod", root), 0750); err != nil { + t.Fatalf("can't mkdir(%q): %s", root, err) + } + + got = kubelet.getPodDir("oldpod") + exp = path.Join(root, "oldpod") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + got = kubelet.getPodDir("newpod") + exp = path.Join(root, "pods/newpod") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + got = kubelet.getPodDir("bothpod") + exp = path.Join(root, "pods/bothpod") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + got = kubelet.getPodDir("neitherpod") + exp = path.Join(root, "pods/neitherpod") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + root = kubelet.getPodDir("newpod") + + // Old-style container dir. + if err := os.MkdirAll(fmt.Sprintf("%s/oldctr", root), 0750); err != nil { + t.Fatalf("can't mkdir(%q): %s", root, err) + } + // New-style container dir. + if err := os.MkdirAll(fmt.Sprintf("%s/containers/newctr", root), 0750); err != nil { + t.Fatalf("can't mkdir(%q): %s", root, err) + } + // Both-style container dir. + if err := os.MkdirAll(fmt.Sprintf("%s/bothctr", root), 0750); err != nil { + t.Fatalf("can't mkdir(%q): %s", root, err) + } + if err := os.MkdirAll(fmt.Sprintf("%s/containers/bothctr", root), 0750); err != nil { + t.Fatalf("can't mkdir(%q): %s", root, err) + } + + got = kubelet.getPodContainerDir("newpod", "oldctr") + exp = path.Join(root, "oldctr") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + got = kubelet.getPodContainerDir("newpod", "newctr") + exp = path.Join(root, "containers/newctr") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + got = kubelet.getPodContainerDir("newpod", "bothctr") + exp = path.Join(root, "containers/bothctr") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } + + got = kubelet.getPodContainerDir("newpod", "neitherctr") + exp = path.Join(root, "containers/neitherctr") + if got != exp { + t.Errorf("expected %q', got %q", exp, got) + } +} + +var emptyPodUIDs map[types.UID]kubetypes.SyncPodType + +func TestSyncLoopTimeUpdate(t *testing.T) { + testKubelet := newTestKubelet(t) + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + kubelet := testKubelet.kubelet + + loopTime1 := kubelet.LatestLoopEntryTime() + if !loopTime1.IsZero() { + t.Errorf("Unexpected sync loop time: %s, expected 0", loopTime1) + } + + // Start sync ticker. + syncCh := make(chan time.Time, 1) + housekeepingCh := make(chan time.Time, 1) + plegCh := make(chan *pleg.PodLifecycleEvent) + syncCh <- time.Now() + kubelet.syncLoopIteration(make(chan kubetypes.PodUpdate), kubelet, syncCh, housekeepingCh, plegCh) + loopTime2 := kubelet.LatestLoopEntryTime() + if loopTime2.IsZero() { + t.Errorf("Unexpected sync loop time: 0, expected non-zero value.") + } + + syncCh <- time.Now() + kubelet.syncLoopIteration(make(chan kubetypes.PodUpdate), kubelet, syncCh, housekeepingCh, plegCh) + loopTime3 := kubelet.LatestLoopEntryTime() + if !loopTime3.After(loopTime1) { + t.Errorf("Sync Loop Time was not updated correctly. Second update timestamp should be greater than first update timestamp") + } +} + +func TestSyncLoopAbort(t *testing.T) { + testKubelet := newTestKubelet(t) + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + kubelet := testKubelet.kubelet + kubelet.runtimeState.setRuntimeSync(time.Now()) + // The syncLoop waits on time.After(resyncInterval), set it really big so that we don't race for + // the channel close + kubelet.resyncInterval = time.Second * 30 + + ch := make(chan kubetypes.PodUpdate) + close(ch) + + // sanity check (also prevent this test from hanging in the next step) + ok := kubelet.syncLoopIteration(ch, kubelet, make(chan time.Time), make(chan time.Time), make(chan *pleg.PodLifecycleEvent, 1)) + if ok { + t.Fatalf("expected syncLoopIteration to return !ok since update chan was closed") + } + + // this should terminate immediately; if it hangs then the syncLoopIteration isn't aborting properly + kubelet.syncLoop(ch, kubelet) +} + +func TestSyncPodsStartPod(t *testing.T) { + testKubelet := newTestKubelet(t) + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + testKubelet.fakeCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + testKubelet.fakeCadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + kubelet := testKubelet.kubelet + fakeRuntime := testKubelet.fakeRuntime + pods := []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "bar"}, + }, + }, + }, + } + kubelet.podManager.SetPods(pods) + kubelet.HandlePodSyncs(pods) + fakeRuntime.AssertStartedPods([]string{string(pods[0].UID)}) +} + +func TestSyncPodsDeletesWhenSourcesAreReady(t *testing.T) { + ready := false + + testKubelet := newTestKubelet(t) + fakeRuntime := testKubelet.fakeRuntime + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + testKubelet.fakeCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + testKubelet.fakeCadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + kubelet := testKubelet.kubelet + kubelet.sourcesReady = func(_ sets.String) bool { return ready } + + fakeRuntime.PodList = []*kubecontainer.Pod{ + { + ID: "12345678", + Name: "foo", Namespace: "new", + Containers: []*kubecontainer.Container{ + {Name: "bar"}, + }, + }, + } + kubelet.HandlePodCleanups() + // Sources are not ready yet. Don't remove any pods. + fakeRuntime.AssertKilledPods([]string{}) + + ready = true + kubelet.HandlePodCleanups() + + // Sources are ready. Remove unwanted pods. + fakeRuntime.AssertKilledPods([]string{"12345678"}) +} + +func TestMountExternalVolumes(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + plug := &volumetest.FakeVolumePlugin{PluginName: "fake", Host: nil} + kubelet.volumePluginMgr.InitPlugins([]volume.VolumePlugin{plug}, &volumeHost{kubelet}) + + pod := api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "test", + }, + Spec: api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "vol1", + VolumeSource: api.VolumeSource{}, + }, + }, + }, + } + podVolumes, err := kubelet.mountExternalVolumes(&pod) + if err != nil { + t.Errorf("Expected success: %v", err) + } + expectedPodVolumes := []string{"vol1"} + if len(expectedPodVolumes) != len(podVolumes) { + t.Errorf("Unexpected volumes. Expected %#v got %#v. Manifest was: %#v", expectedPodVolumes, podVolumes, pod) + } + for _, name := range expectedPodVolumes { + if _, ok := podVolumes[name]; !ok { + t.Errorf("api.Pod volumes map is missing key: %s. %#v", name, podVolumes) + } + } + if plug.NewAttacherCallCount != 1 { + t.Errorf("Expected plugin NewAttacher to be called %d times but got %d", 1, plug.NewAttacherCallCount) + } +} + +func TestGetPodVolumesFromDisk(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + plug := &volumetest.FakeVolumePlugin{PluginName: "fake", Host: nil} + kubelet.volumePluginMgr.InitPlugins([]volume.VolumePlugin{plug}, &volumeHost{kubelet}) + + volsOnDisk := []struct { + podUID types.UID + volName string + }{ + {"pod1", "vol1"}, + {"pod1", "vol2"}, + {"pod2", "vol1"}, + } + + expectedPaths := []string{} + for i := range volsOnDisk { + fv := volumetest.FakeVolume{PodUID: volsOnDisk[i].podUID, VolName: volsOnDisk[i].volName, Plugin: plug} + fv.SetUp(nil) + expectedPaths = append(expectedPaths, fv.GetPath()) + } + + volumesFound := kubelet.getPodVolumesFromDisk() + if len(volumesFound) != len(expectedPaths) { + t.Errorf("Expected to find %d unmounters, got %d", len(expectedPaths), len(volumesFound)) + } + for _, ep := range expectedPaths { + found := false + for _, cl := range volumesFound { + if ep == cl.Unmounter.GetPath() { + found = true + break + } + } + if !found { + t.Errorf("Could not find a volume with path %s", ep) + } + } + if plug.NewDetacherCallCount != len(volsOnDisk) { + t.Errorf("Expected plugin NewDetacher to be called %d times but got %d", len(volsOnDisk), plug.NewDetacherCallCount) + } +} + +// Test for https://github.com/kubernetes/kubernetes/pull/19600 +func TestCleanupOrphanedVolumes(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + kubelet.mounter = &mount.FakeMounter{} + kubeClient := testKubelet.fakeKubeClient + plug := &volumetest.FakeVolumePlugin{PluginName: "fake", Host: nil} + kubelet.volumePluginMgr.InitPlugins([]volume.VolumePlugin{plug}, &volumeHost{kubelet}) + + // create a volume "on disk" + volsOnDisk := []struct { + podUID types.UID + volName string + }{ + {"podUID", "myrealvol"}, + } + + pathsOnDisk := []string{} + for i := range volsOnDisk { + fv := volumetest.FakeVolume{PodUID: volsOnDisk[i].podUID, VolName: volsOnDisk[i].volName, Plugin: plug} + fv.SetUp(nil) + pathsOnDisk = append(pathsOnDisk, fv.GetPath()) + } + + // store the claim in fake kubelet database + claim := api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "myclaim", + Namespace: "test", + }, + Spec: api.PersistentVolumeClaimSpec{ + VolumeName: "myrealvol", + }, + Status: api.PersistentVolumeClaimStatus{ + Phase: api.ClaimBound, + }, + } + kubeClient.ReactionChain = fake.NewSimpleClientset(&api.PersistentVolumeClaimList{Items: []api.PersistentVolumeClaim{ + claim, + }}).ReactionChain + + // Create a pod referencing the volume via a PersistentVolumeClaim + pod := api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "podUID", + Name: "pod", + Namespace: "test", + }, + Spec: api.PodSpec{ + Volumes: []api.Volume{ + { + Name: "myvolumeclaim", + VolumeSource: api.VolumeSource{ + PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ + ClaimName: "myclaim", + }, + }, + }, + }, + }, + } + + // The pod is pending and not running yet. Test that cleanupOrphanedVolumes + // won't remove the volume from disk if the volume is referenced only + // indirectly by a claim. + err := kubelet.cleanupOrphanedVolumes([]*api.Pod{&pod}, []*kubecontainer.Pod{}) + if err != nil { + t.Errorf("cleanupOrphanedVolumes failed: %v", err) + } + + volumesFound := kubelet.getPodVolumesFromDisk() + if len(volumesFound) != len(pathsOnDisk) { + t.Errorf("Expected to find %d unmounters, got %d", len(pathsOnDisk), len(volumesFound)) + } + for _, ep := range pathsOnDisk { + found := false + for _, cl := range volumesFound { + if ep == cl.Unmounter.GetPath() { + found = true + break + } + } + if !found { + t.Errorf("Could not find a volume with path %s", ep) + } + } + + // The pod is deleted -> kubelet should delete the volume + err = kubelet.cleanupOrphanedVolumes([]*api.Pod{}, []*kubecontainer.Pod{}) + if err != nil { + t.Errorf("cleanupOrphanedVolumes failed: %v", err) + } + volumesFound = kubelet.getPodVolumesFromDisk() + if len(volumesFound) != 0 { + t.Errorf("Expected to find 0 unmounters, got %d", len(volumesFound)) + } + for _, cl := range volumesFound { + t.Errorf("Found unexpected volume %s", cl.Unmounter.GetPath()) + } +} + +type stubVolume struct { + path string + volume.MetricsNil +} + +func (f *stubVolume) GetPath() string { + return f.path +} + +func (f *stubVolume) GetAttributes() volume.Attributes { + return volume.Attributes{} +} + +func (f *stubVolume) SetUp(fsGroup *int64) error { + return nil +} + +func (f *stubVolume) SetUpAt(dir string, fsGroup *int64) error { + return nil +} + +func TestMakeVolumeMounts(t *testing.T) { + container := api.Container{ + VolumeMounts: []api.VolumeMount{ + { + MountPath: "/etc/hosts", + Name: "disk", + ReadOnly: false, + }, + { + MountPath: "/mnt/path3", + Name: "disk", + ReadOnly: true, + }, + { + MountPath: "/mnt/path4", + Name: "disk4", + ReadOnly: false, + }, + { + MountPath: "/mnt/path5", + Name: "disk5", + ReadOnly: false, + }, + }, + } + + podVolumes := kubecontainer.VolumeMap{ + "disk": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/disk"}}, + "disk4": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/mnt/host"}}, + "disk5": kubecontainer.VolumeInfo{Mounter: &stubVolume{path: "/var/lib/kubelet/podID/volumes/empty/disk5"}}, + } + + pod := api.Pod{ + Spec: api.PodSpec{ + SecurityContext: &api.PodSecurityContext{ + HostNetwork: true, + }, + }, + } + + mounts, _ := makeMounts(&pod, "/pod", &container, "fakepodname", "", "", podVolumes) + + expectedMounts := []kubecontainer.Mount{ + { + "disk", + "/etc/hosts", + "/mnt/disk", + false, + false, + }, + { + "disk", + "/mnt/path3", + "/mnt/disk", + true, + false, + }, + { + "disk4", + "/mnt/path4", + "/mnt/host", + false, + false, + }, + { + "disk5", + "/mnt/path5", + "/var/lib/kubelet/podID/volumes/empty/disk5", + false, + false, + }, + } + if !reflect.DeepEqual(mounts, expectedMounts) { + t.Errorf("Unexpected mounts: Expected %#v got %#v. Container was: %#v", expectedMounts, mounts, container) + } +} + +func TestGetContainerInfo(t *testing.T) { + containerID := "ab2cdf" + containerPath := fmt.Sprintf("/docker/%v", containerID) + containerInfo := cadvisorapi.ContainerInfo{ + ContainerReference: cadvisorapi.ContainerReference{ + Name: containerPath, + }, + } + + testKubelet := newTestKubelet(t) + fakeRuntime := testKubelet.fakeRuntime + kubelet := testKubelet.kubelet + cadvisorReq := &cadvisorapi.ContainerInfoRequest{} + mockCadvisor := testKubelet.fakeCadvisor + mockCadvisor.On("DockerContainer", containerID, cadvisorReq).Return(containerInfo, nil) + fakeRuntime.PodList = []*kubecontainer.Pod{ + { + ID: "12345678", + Name: "qux", + Namespace: "ns", + Containers: []*kubecontainer.Container{ + { + Name: "foo", + ID: kubecontainer.ContainerID{Type: "test", ID: containerID}, + }, + }, + }, + } + stats, err := kubelet.GetContainerInfo("qux_ns", "", "foo", cadvisorReq) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if stats == nil { + t.Fatalf("stats should not be nil") + } + mockCadvisor.AssertExpectations(t) +} + +func TestGetRawContainerInfoRoot(t *testing.T) { + containerPath := "/" + containerInfo := &cadvisorapi.ContainerInfo{ + ContainerReference: cadvisorapi.ContainerReference{ + Name: containerPath, + }, + } + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + mockCadvisor := testKubelet.fakeCadvisor + cadvisorReq := &cadvisorapi.ContainerInfoRequest{} + mockCadvisor.On("ContainerInfo", containerPath, cadvisorReq).Return(containerInfo, nil) + + _, err := kubelet.GetRawContainerInfo(containerPath, cadvisorReq, false) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + mockCadvisor.AssertExpectations(t) +} + +func TestGetRawContainerInfoSubcontainers(t *testing.T) { + containerPath := "/kubelet" + containerInfo := map[string]*cadvisorapi.ContainerInfo{ + containerPath: { + ContainerReference: cadvisorapi.ContainerReference{ + Name: containerPath, + }, + }, + "/kubelet/sub": { + ContainerReference: cadvisorapi.ContainerReference{ + Name: "/kubelet/sub", + }, + }, + } + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + mockCadvisor := testKubelet.fakeCadvisor + cadvisorReq := &cadvisorapi.ContainerInfoRequest{} + mockCadvisor.On("SubcontainerInfo", containerPath, cadvisorReq).Return(containerInfo, nil) + + result, err := kubelet.GetRawContainerInfo(containerPath, cadvisorReq, true) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if len(result) != 2 { + t.Errorf("Expected 2 elements, received: %+v", result) + } + mockCadvisor.AssertExpectations(t) +} + +func TestGetContainerInfoWhenCadvisorFailed(t *testing.T) { + containerID := "ab2cdf" + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + mockCadvisor := testKubelet.fakeCadvisor + fakeRuntime := testKubelet.fakeRuntime + cadvisorApiFailure := fmt.Errorf("cAdvisor failure") + containerInfo := cadvisorapi.ContainerInfo{} + cadvisorReq := &cadvisorapi.ContainerInfoRequest{} + mockCadvisor.On("DockerContainer", containerID, cadvisorReq).Return(containerInfo, cadvisorApiFailure) + fakeRuntime.PodList = []*kubecontainer.Pod{ + { + ID: "uuid", + Name: "qux", + Namespace: "ns", + Containers: []*kubecontainer.Container{ + {Name: "foo", + ID: kubecontainer.ContainerID{Type: "test", ID: containerID}, + }, + }, + }, + } + stats, err := kubelet.GetContainerInfo("qux_ns", "uuid", "foo", cadvisorReq) + if stats != nil { + t.Errorf("non-nil stats on error") + } + if err == nil { + t.Errorf("expect error but received nil error") + return + } + if err.Error() != cadvisorApiFailure.Error() { + t.Errorf("wrong error message. expect %v, got %v", cadvisorApiFailure, err) + } + mockCadvisor.AssertExpectations(t) +} + +func TestGetContainerInfoOnNonExistContainer(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + mockCadvisor := testKubelet.fakeCadvisor + fakeRuntime := testKubelet.fakeRuntime + fakeRuntime.PodList = []*kubecontainer.Pod{} + + stats, _ := kubelet.GetContainerInfo("qux", "", "foo", nil) + if stats != nil { + t.Errorf("non-nil stats on non exist container") + } + mockCadvisor.AssertExpectations(t) +} + +func TestGetContainerInfoWhenContainerRuntimeFailed(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + mockCadvisor := testKubelet.fakeCadvisor + fakeRuntime := testKubelet.fakeRuntime + expectedErr := fmt.Errorf("List containers error") + fakeRuntime.Err = expectedErr + + stats, err := kubelet.GetContainerInfo("qux", "", "foo", nil) + if err == nil { + t.Errorf("expected error from dockertools, got none") + } + if err.Error() != expectedErr.Error() { + t.Errorf("expected error %v got %v", expectedErr.Error(), err.Error()) + } + if stats != nil { + t.Errorf("non-nil stats when dockertools failed") + } + mockCadvisor.AssertExpectations(t) +} + +func TestGetContainerInfoWithNoContainers(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + mockCadvisor := testKubelet.fakeCadvisor + + stats, err := kubelet.GetContainerInfo("qux_ns", "", "foo", nil) + if err == nil { + t.Errorf("expected error from cadvisor client, got none") + } + if err != kubecontainer.ErrContainerNotFound { + t.Errorf("expected error %v, got %v", kubecontainer.ErrContainerNotFound.Error(), err.Error()) + } + if stats != nil { + t.Errorf("non-nil stats when dockertools returned no containers") + } + mockCadvisor.AssertExpectations(t) +} + +func TestNodeIPParam(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + tests := []struct { + nodeIP string + success bool + testName string + }{ + { + nodeIP: "", + success: true, + testName: "IP not set", + }, + { + nodeIP: "127.0.0.1", + success: false, + testName: "loopback address", + }, + { + nodeIP: "FE80::0202:B3FF:FE1E:8329", + success: false, + testName: "IPv6 address", + }, + { + nodeIP: "1.2.3.4", + success: false, + testName: "IPv4 address that doesn't belong to host", + }, + } + for _, test := range tests { + kubelet.nodeIP = net.ParseIP(test.nodeIP) + err := kubelet.validateNodeIP() + if err != nil && test.success { + t.Errorf("Test: %s, expected no error but got: %v", test.testName, err) + } else if err == nil && !test.success { + t.Errorf("Test: %s, expected an error", test.testName) + } + } +} + +func TestGetContainerInfoWithNoMatchingContainers(t *testing.T) { + testKubelet := newTestKubelet(t) + fakeRuntime := testKubelet.fakeRuntime + kubelet := testKubelet.kubelet + mockCadvisor := testKubelet.fakeCadvisor + fakeRuntime.PodList = []*kubecontainer.Pod{ + { + ID: "12345678", + Name: "qux", + Namespace: "ns", + Containers: []*kubecontainer.Container{ + {Name: "bar", + ID: kubecontainer.ContainerID{Type: "test", ID: "fakeID"}, + }, + }}, + } + + stats, err := kubelet.GetContainerInfo("qux_ns", "", "foo", nil) + if err == nil { + t.Errorf("Expected error from cadvisor client, got none") + } + if err != kubecontainer.ErrContainerNotFound { + t.Errorf("Expected error %v, got %v", kubecontainer.ErrContainerNotFound.Error(), err.Error()) + } + if stats != nil { + t.Errorf("non-nil stats when dockertools returned no containers") + } + mockCadvisor.AssertExpectations(t) +} + +type fakeContainerCommandRunner struct { + Cmd []string + ID kubecontainer.ContainerID + PodID types.UID + E error + Stdin io.Reader + Stdout io.WriteCloser + Stderr io.WriteCloser + TTY bool + Port uint16 + Stream io.ReadWriteCloser +} + +func (f *fakeContainerCommandRunner) RunInContainer(id kubecontainer.ContainerID, cmd []string) ([]byte, error) { + f.Cmd = cmd + f.ID = id + return []byte{}, f.E +} + +func (f *fakeContainerCommandRunner) ExecInContainer(id kubecontainer.ContainerID, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error { + f.Cmd = cmd + f.ID = id + f.Stdin = in + f.Stdout = out + f.Stderr = err + f.TTY = tty + return f.E +} + +func (f *fakeContainerCommandRunner) PortForward(pod *kubecontainer.Pod, port uint16, stream io.ReadWriteCloser) error { + f.PodID = pod.ID + f.Port = port + f.Stream = stream + return nil +} + +func TestRunInContainerNoSuchPod(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + fakeRuntime := testKubelet.fakeRuntime + fakeRuntime.PodList = []*kubecontainer.Pod{} + + podName := "podFoo" + podNamespace := "nsFoo" + containerName := "containerFoo" + output, err := kubelet.RunInContainer( + kubecontainer.GetPodFullName(&api.Pod{ObjectMeta: api.ObjectMeta{Name: podName, Namespace: podNamespace}}), + "", + containerName, + []string{"ls"}) + if output != nil { + t.Errorf("unexpected non-nil command: %v", output) + } + if err == nil { + t.Error("unexpected non-error") + } +} + +func TestRunInContainer(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + fakeRuntime := testKubelet.fakeRuntime + fakeCommandRunner := fakeContainerCommandRunner{} + kubelet.runner = &fakeCommandRunner + + containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"} + fakeRuntime.PodList = []*kubecontainer.Pod{ + { + ID: "12345678", + Name: "podFoo", + Namespace: "nsFoo", + Containers: []*kubecontainer.Container{ + {Name: "containerFoo", + ID: containerID, + }, + }, + }, + } + cmd := []string{"ls"} + _, err := kubelet.RunInContainer("podFoo_nsFoo", "", "containerFoo", cmd) + if fakeCommandRunner.ID != containerID { + t.Errorf("unexpected Name: %s", fakeCommandRunner.ID) + } + if !reflect.DeepEqual(fakeCommandRunner.Cmd, cmd) { + t.Errorf("unexpected command: %s", fakeCommandRunner.Cmd) + } + if err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +type countingDNSScrubber struct { + counter *int +} + +func (cds countingDNSScrubber) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) { + (*cds.counter)++ + return nameservers, searches +} + +func TestParseResolvConf(t *testing.T) { + testCases := []struct { + data string + nameservers []string + searches []string + }{ + {"", []string{}, []string{}}, + {" ", []string{}, []string{}}, + {"\n", []string{}, []string{}}, + {"\t\n\t", []string{}, []string{}}, + {"#comment\n", []string{}, []string{}}, + {" #comment\n", []string{}, []string{}}, + {"#comment\n#comment", []string{}, []string{}}, + {"#comment\nnameserver", []string{}, []string{}}, + {"#comment\nnameserver\nsearch", []string{}, []string{}}, + {"nameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}}, + {" nameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}}, + {"\tnameserver 1.2.3.4", []string{"1.2.3.4"}, []string{}}, + {"nameserver\t1.2.3.4", []string{"1.2.3.4"}, []string{}}, + {"nameserver \t 1.2.3.4", []string{"1.2.3.4"}, []string{}}, + {"nameserver 1.2.3.4\nnameserver 5.6.7.8", []string{"1.2.3.4", "5.6.7.8"}, []string{}}, + {"search foo", []string{}, []string{"foo"}}, + {"search foo bar", []string{}, []string{"foo", "bar"}}, + {"search foo bar bat\n", []string{}, []string{"foo", "bar", "bat"}}, + {"search foo\nsearch bar", []string{}, []string{"bar"}}, + {"nameserver 1.2.3.4\nsearch foo bar", []string{"1.2.3.4"}, []string{"foo", "bar"}}, + {"nameserver 1.2.3.4\nsearch foo\nnameserver 5.6.7.8\nsearch bar", []string{"1.2.3.4", "5.6.7.8"}, []string{"bar"}}, + {"#comment\nnameserver 1.2.3.4\n#comment\nsearch foo\ncomment", []string{"1.2.3.4"}, []string{"foo"}}, + } + for i, tc := range testCases { + ns, srch, err := parseResolvConf(strings.NewReader(tc.data), nil) + if err != nil { + t.Errorf("expected success, got %v", err) + continue + } + if !reflect.DeepEqual(ns, tc.nameservers) { + t.Errorf("[%d] expected nameservers %#v, got %#v", i, tc.nameservers, ns) + } + if !reflect.DeepEqual(srch, tc.searches) { + t.Errorf("[%d] expected searches %#v, got %#v", i, tc.searches, srch) + } + + counter := 0 + cds := countingDNSScrubber{&counter} + ns, srch, err = parseResolvConf(strings.NewReader(tc.data), cds) + if err != nil { + t.Errorf("expected success, got %v", err) + continue + } + if !reflect.DeepEqual(ns, tc.nameservers) { + t.Errorf("[%d] expected nameservers %#v, got %#v", i, tc.nameservers, ns) + } + if !reflect.DeepEqual(srch, tc.searches) { + t.Errorf("[%d] expected searches %#v, got %#v", i, tc.searches, srch) + } + if counter != 1 { + t.Errorf("[%d] expected dnsScrubber to have been called: got %d", i, counter) + } + } +} + +func TestDNSConfigurationParams(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + + clusterNS := "203.0.113.1" + kubelet.clusterDomain = "kubernetes.io" + kubelet.clusterDNS = net.ParseIP(clusterNS) + + pods := newTestPods(2) + pods[0].Spec.DNSPolicy = api.DNSClusterFirst + pods[1].Spec.DNSPolicy = api.DNSDefault + + options := make([]*kubecontainer.RunContainerOptions, 2) + for i, pod := range pods { + var err error + kubelet.volumeManager.SetVolumes(pod.UID, make(kubecontainer.VolumeMap, 0)) + options[i], err = kubelet.GenerateRunContainerOptions(pod, &api.Container{}, "") + if err != nil { + t.Fatalf("failed to generate container options: %v", err) + } + } + if len(options[0].DNS) != 1 || options[0].DNS[0] != clusterNS { + t.Errorf("expected nameserver %s, got %+v", clusterNS, options[0].DNS) + } + if len(options[0].DNSSearch) == 0 || options[0].DNSSearch[0] != ".svc."+kubelet.clusterDomain { + t.Errorf("expected search %s, got %+v", ".svc."+kubelet.clusterDomain, options[0].DNSSearch) + } + if len(options[1].DNS) != 1 || options[1].DNS[0] != "127.0.0.1" { + t.Errorf("expected nameserver 127.0.0.1, got %+v", options[1].DNS) + } + if len(options[1].DNSSearch) != 1 || options[1].DNSSearch[0] != "." { + t.Errorf("expected search \".\", got %+v", options[1].DNSSearch) + } + + kubelet.resolverConfig = "/etc/resolv.conf" + for i, pod := range pods { + var err error + options[i], err = kubelet.GenerateRunContainerOptions(pod, &api.Container{}, "") + if err != nil { + t.Fatalf("failed to generate container options: %v", err) + } + } + t.Logf("nameservers %+v", options[1].DNS) + if len(options[0].DNS) != 1 { + t.Errorf("expected cluster nameserver only, got %+v", options[0].DNS) + } else if options[0].DNS[0] != clusterNS { + t.Errorf("expected nameserver %s, got %v", clusterNS, options[0].DNS[0]) + } + if len(options[0].DNSSearch) != len(options[1].DNSSearch)+3 { + t.Errorf("expected prepend of cluster domain, got %+v", options[0].DNSSearch) + } else if options[0].DNSSearch[0] != ".svc."+kubelet.clusterDomain { + t.Errorf("expected domain %s, got %s", ".svc."+kubelet.clusterDomain, options[0].DNSSearch) + } +} + +type testServiceLister struct { + services []api.Service +} + +func (ls testServiceLister) List() (api.ServiceList, error) { + return api.ServiceList{ + Items: ls.services, + }, nil +} + +type testNodeLister struct { + nodes []api.Node +} + +type testNodeInfo struct { + nodes []api.Node +} + +func (ls testNodeInfo) GetNodeInfo(id string) (*api.Node, error) { + for _, node := range ls.nodes { + if node.Name == id { + return &node, nil + } + } + return nil, fmt.Errorf("Node with name: %s does not exist", id) +} + +func (ls testNodeLister) List() (api.NodeList, error) { + return api.NodeList{ + Items: ls.nodes, + }, nil +} + +type envs []kubecontainer.EnvVar + +func (e envs) Len() int { + return len(e) +} + +func (e envs) Swap(i, j int) { e[i], e[j] = e[j], e[i] } + +func (e envs) Less(i, j int) bool { return e[i].Name < e[j].Name } + +func TestMakeEnvironmentVariables(t *testing.T) { + services := []api.Service{ + { + ObjectMeta: api.ObjectMeta{Name: "kubernetes", Namespace: api.NamespaceDefault}, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{{ + Protocol: "TCP", + Port: 8081, + }}, + ClusterIP: "1.2.3.1", + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "test", Namespace: "test1"}, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{{ + Protocol: "TCP", + Port: 8083, + }}, + ClusterIP: "1.2.3.3", + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "kubernetes", Namespace: "test2"}, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{{ + Protocol: "TCP", + Port: 8084, + }}, + ClusterIP: "1.2.3.4", + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "test", Namespace: "test2"}, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{{ + Protocol: "TCP", + Port: 8085, + }}, + ClusterIP: "1.2.3.5", + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "test", Namespace: "test2"}, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{{ + Protocol: "TCP", + Port: 8085, + }}, + ClusterIP: "None", + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "test", Namespace: "test2"}, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{{ + Protocol: "TCP", + Port: 8085, + }}, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "kubernetes", Namespace: "kubernetes"}, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{{ + Protocol: "TCP", + Port: 8086, + }}, + ClusterIP: "1.2.3.6", + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "not-special", Namespace: "kubernetes"}, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{{ + Protocol: "TCP", + Port: 8088, + }}, + ClusterIP: "1.2.3.8", + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "not-special", Namespace: "kubernetes"}, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{{ + Protocol: "TCP", + Port: 8088, + }}, + ClusterIP: "None", + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "not-special", Namespace: "kubernetes"}, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{{ + Protocol: "TCP", + Port: 8088, + }}, + ClusterIP: "", + }, + }, + } + + testCases := []struct { + name string // the name of the test case + ns string // the namespace to generate environment for + container *api.Container // the container to use + masterServiceNs string // the namespace to read master service info from + nilLister bool // whether the lister should be nil + expectedEnvs []kubecontainer.EnvVar // a set of expected environment vars + }{ + { + name: "api server = Y, kubelet = Y", + ns: "test1", + container: &api.Container{ + Env: []api.EnvVar{ + {Name: "FOO", Value: "BAR"}, + {Name: "TEST_SERVICE_HOST", Value: "1.2.3.3"}, + {Name: "TEST_SERVICE_PORT", Value: "8083"}, + {Name: "TEST_PORT", Value: "tcp://1.2.3.3:8083"}, + {Name: "TEST_PORT_8083_TCP", Value: "tcp://1.2.3.3:8083"}, + {Name: "TEST_PORT_8083_TCP_PROTO", Value: "tcp"}, + {Name: "TEST_PORT_8083_TCP_PORT", Value: "8083"}, + {Name: "TEST_PORT_8083_TCP_ADDR", Value: "1.2.3.3"}, + }, + }, + masterServiceNs: api.NamespaceDefault, + nilLister: false, + expectedEnvs: []kubecontainer.EnvVar{ + {Name: "FOO", Value: "BAR"}, + {Name: "TEST_SERVICE_HOST", Value: "1.2.3.3"}, + {Name: "TEST_SERVICE_PORT", Value: "8083"}, + {Name: "TEST_PORT", Value: "tcp://1.2.3.3:8083"}, + {Name: "TEST_PORT_8083_TCP", Value: "tcp://1.2.3.3:8083"}, + {Name: "TEST_PORT_8083_TCP_PROTO", Value: "tcp"}, + {Name: "TEST_PORT_8083_TCP_PORT", Value: "8083"}, + {Name: "TEST_PORT_8083_TCP_ADDR", Value: "1.2.3.3"}, + {Name: "KUBERNETES_SERVICE_PORT", Value: "8081"}, + {Name: "KUBERNETES_SERVICE_HOST", Value: "1.2.3.1"}, + {Name: "KUBERNETES_PORT", Value: "tcp://1.2.3.1:8081"}, + {Name: "KUBERNETES_PORT_8081_TCP", Value: "tcp://1.2.3.1:8081"}, + {Name: "KUBERNETES_PORT_8081_TCP_PROTO", Value: "tcp"}, + {Name: "KUBERNETES_PORT_8081_TCP_PORT", Value: "8081"}, + {Name: "KUBERNETES_PORT_8081_TCP_ADDR", Value: "1.2.3.1"}, + }, + }, + { + name: "api server = Y, kubelet = N", + ns: "test1", + container: &api.Container{ + Env: []api.EnvVar{ + {Name: "FOO", Value: "BAR"}, + {Name: "TEST_SERVICE_HOST", Value: "1.2.3.3"}, + {Name: "TEST_SERVICE_PORT", Value: "8083"}, + {Name: "TEST_PORT", Value: "tcp://1.2.3.3:8083"}, + {Name: "TEST_PORT_8083_TCP", Value: "tcp://1.2.3.3:8083"}, + {Name: "TEST_PORT_8083_TCP_PROTO", Value: "tcp"}, + {Name: "TEST_PORT_8083_TCP_PORT", Value: "8083"}, + {Name: "TEST_PORT_8083_TCP_ADDR", Value: "1.2.3.3"}, + }, + }, + masterServiceNs: api.NamespaceDefault, + nilLister: true, + expectedEnvs: []kubecontainer.EnvVar{ + {Name: "FOO", Value: "BAR"}, + {Name: "TEST_SERVICE_HOST", Value: "1.2.3.3"}, + {Name: "TEST_SERVICE_PORT", Value: "8083"}, + {Name: "TEST_PORT", Value: "tcp://1.2.3.3:8083"}, + {Name: "TEST_PORT_8083_TCP", Value: "tcp://1.2.3.3:8083"}, + {Name: "TEST_PORT_8083_TCP_PROTO", Value: "tcp"}, + {Name: "TEST_PORT_8083_TCP_PORT", Value: "8083"}, + {Name: "TEST_PORT_8083_TCP_ADDR", Value: "1.2.3.3"}, + }, + }, + { + name: "api server = N; kubelet = Y", + ns: "test1", + container: &api.Container{ + Env: []api.EnvVar{ + {Name: "FOO", Value: "BAZ"}, + }, + }, + masterServiceNs: api.NamespaceDefault, + nilLister: false, + expectedEnvs: []kubecontainer.EnvVar{ + {Name: "FOO", Value: "BAZ"}, + {Name: "TEST_SERVICE_HOST", Value: "1.2.3.3"}, + {Name: "TEST_SERVICE_PORT", Value: "8083"}, + {Name: "TEST_PORT", Value: "tcp://1.2.3.3:8083"}, + {Name: "TEST_PORT_8083_TCP", Value: "tcp://1.2.3.3:8083"}, + {Name: "TEST_PORT_8083_TCP_PROTO", Value: "tcp"}, + {Name: "TEST_PORT_8083_TCP_PORT", Value: "8083"}, + {Name: "TEST_PORT_8083_TCP_ADDR", Value: "1.2.3.3"}, + {Name: "KUBERNETES_SERVICE_HOST", Value: "1.2.3.1"}, + {Name: "KUBERNETES_SERVICE_PORT", Value: "8081"}, + {Name: "KUBERNETES_PORT", Value: "tcp://1.2.3.1:8081"}, + {Name: "KUBERNETES_PORT_8081_TCP", Value: "tcp://1.2.3.1:8081"}, + {Name: "KUBERNETES_PORT_8081_TCP_PROTO", Value: "tcp"}, + {Name: "KUBERNETES_PORT_8081_TCP_PORT", Value: "8081"}, + {Name: "KUBERNETES_PORT_8081_TCP_ADDR", Value: "1.2.3.1"}, + }, + }, + { + name: "master service in pod ns", + ns: "test2", + container: &api.Container{ + Env: []api.EnvVar{ + {Name: "FOO", Value: "ZAP"}, + }, + }, + masterServiceNs: "kubernetes", + nilLister: false, + expectedEnvs: []kubecontainer.EnvVar{ + {Name: "FOO", Value: "ZAP"}, + {Name: "TEST_SERVICE_HOST", Value: "1.2.3.5"}, + {Name: "TEST_SERVICE_PORT", Value: "8085"}, + {Name: "TEST_PORT", Value: "tcp://1.2.3.5:8085"}, + {Name: "TEST_PORT_8085_TCP", Value: "tcp://1.2.3.5:8085"}, + {Name: "TEST_PORT_8085_TCP_PROTO", Value: "tcp"}, + {Name: "TEST_PORT_8085_TCP_PORT", Value: "8085"}, + {Name: "TEST_PORT_8085_TCP_ADDR", Value: "1.2.3.5"}, + {Name: "KUBERNETES_SERVICE_HOST", Value: "1.2.3.4"}, + {Name: "KUBERNETES_SERVICE_PORT", Value: "8084"}, + {Name: "KUBERNETES_PORT", Value: "tcp://1.2.3.4:8084"}, + {Name: "KUBERNETES_PORT_8084_TCP", Value: "tcp://1.2.3.4:8084"}, + {Name: "KUBERNETES_PORT_8084_TCP_PROTO", Value: "tcp"}, + {Name: "KUBERNETES_PORT_8084_TCP_PORT", Value: "8084"}, + {Name: "KUBERNETES_PORT_8084_TCP_ADDR", Value: "1.2.3.4"}, + }, + }, + { + name: "pod in master service ns", + ns: "kubernetes", + container: &api.Container{}, + masterServiceNs: "kubernetes", + nilLister: false, + expectedEnvs: []kubecontainer.EnvVar{ + {Name: "NOT_SPECIAL_SERVICE_HOST", Value: "1.2.3.8"}, + {Name: "NOT_SPECIAL_SERVICE_PORT", Value: "8088"}, + {Name: "NOT_SPECIAL_PORT", Value: "tcp://1.2.3.8:8088"}, + {Name: "NOT_SPECIAL_PORT_8088_TCP", Value: "tcp://1.2.3.8:8088"}, + {Name: "NOT_SPECIAL_PORT_8088_TCP_PROTO", Value: "tcp"}, + {Name: "NOT_SPECIAL_PORT_8088_TCP_PORT", Value: "8088"}, + {Name: "NOT_SPECIAL_PORT_8088_TCP_ADDR", Value: "1.2.3.8"}, + {Name: "KUBERNETES_SERVICE_HOST", Value: "1.2.3.6"}, + {Name: "KUBERNETES_SERVICE_PORT", Value: "8086"}, + {Name: "KUBERNETES_PORT", Value: "tcp://1.2.3.6:8086"}, + {Name: "KUBERNETES_PORT_8086_TCP", Value: "tcp://1.2.3.6:8086"}, + {Name: "KUBERNETES_PORT_8086_TCP_PROTO", Value: "tcp"}, + {Name: "KUBERNETES_PORT_8086_TCP_PORT", Value: "8086"}, + {Name: "KUBERNETES_PORT_8086_TCP_ADDR", Value: "1.2.3.6"}, + }, + }, + { + name: "downward api pod", + ns: "downward-api", + container: &api.Container{ + Env: []api.EnvVar{ + { + Name: "POD_NAME", + ValueFrom: &api.EnvVarSource{ + FieldRef: &api.ObjectFieldSelector{ + APIVersion: testapi.Default.GroupVersion().String(), + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "POD_NAMESPACE", + ValueFrom: &api.EnvVarSource{ + FieldRef: &api.ObjectFieldSelector{ + APIVersion: testapi.Default.GroupVersion().String(), + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "POD_IP", + ValueFrom: &api.EnvVarSource{ + FieldRef: &api.ObjectFieldSelector{ + APIVersion: testapi.Default.GroupVersion().String(), + FieldPath: "status.podIP", + }, + }, + }, + }, + }, + masterServiceNs: "nothing", + nilLister: true, + expectedEnvs: []kubecontainer.EnvVar{ + {Name: "POD_NAME", Value: "dapi-test-pod-name"}, + {Name: "POD_NAMESPACE", Value: "downward-api"}, + {Name: "POD_IP", Value: "1.2.3.4"}, + }, + }, + { + name: "env expansion", + ns: "test1", + container: &api.Container{ + Env: []api.EnvVar{ + { + Name: "TEST_LITERAL", + Value: "test-test-test", + }, + { + Name: "POD_NAME", + ValueFrom: &api.EnvVarSource{ + FieldRef: &api.ObjectFieldSelector{ + APIVersion: testapi.Default.GroupVersion().String(), + FieldPath: "metadata.name", + }, + }, + }, + { + Name: "OUT_OF_ORDER_TEST", + Value: "$(OUT_OF_ORDER_TARGET)", + }, + { + Name: "OUT_OF_ORDER_TARGET", + Value: "FOO", + }, + { + Name: "EMPTY_VAR", + }, + { + Name: "EMPTY_TEST", + Value: "foo-$(EMPTY_VAR)", + }, + { + Name: "POD_NAME_TEST2", + Value: "test2-$(POD_NAME)", + }, + { + Name: "POD_NAME_TEST3", + Value: "$(POD_NAME_TEST2)-3", + }, + { + Name: "LITERAL_TEST", + Value: "literal-$(TEST_LITERAL)", + }, + { + Name: "SERVICE_VAR_TEST", + Value: "$(TEST_SERVICE_HOST):$(TEST_SERVICE_PORT)", + }, + { + Name: "TEST_UNDEFINED", + Value: "$(UNDEFINED_VAR)", + }, + }, + }, + masterServiceNs: "nothing", + nilLister: false, + expectedEnvs: []kubecontainer.EnvVar{ + { + Name: "TEST_LITERAL", + Value: "test-test-test", + }, + { + Name: "POD_NAME", + Value: "dapi-test-pod-name", + }, + { + Name: "POD_NAME_TEST2", + Value: "test2-dapi-test-pod-name", + }, + { + Name: "POD_NAME_TEST3", + Value: "test2-dapi-test-pod-name-3", + }, + { + Name: "LITERAL_TEST", + Value: "literal-test-test-test", + }, + { + Name: "TEST_SERVICE_HOST", + Value: "1.2.3.3", + }, + { + Name: "TEST_SERVICE_PORT", + Value: "8083", + }, + { + Name: "TEST_PORT", + Value: "tcp://1.2.3.3:8083", + }, + { + Name: "TEST_PORT_8083_TCP", + Value: "tcp://1.2.3.3:8083", + }, + { + Name: "TEST_PORT_8083_TCP_PROTO", + Value: "tcp", + }, + { + Name: "TEST_PORT_8083_TCP_PORT", + Value: "8083", + }, + { + Name: "TEST_PORT_8083_TCP_ADDR", + Value: "1.2.3.3", + }, + { + Name: "SERVICE_VAR_TEST", + Value: "1.2.3.3:8083", + }, + { + Name: "OUT_OF_ORDER_TEST", + Value: "$(OUT_OF_ORDER_TARGET)", + }, + { + Name: "OUT_OF_ORDER_TARGET", + Value: "FOO", + }, + { + Name: "TEST_UNDEFINED", + Value: "$(UNDEFINED_VAR)", + }, + { + Name: "EMPTY_VAR", + }, + { + Name: "EMPTY_TEST", + Value: "foo-", + }, + }, + }, + } + + for i, tc := range testCases { + testKubelet := newTestKubelet(t) + kl := testKubelet.kubelet + kl.masterServiceNamespace = tc.masterServiceNs + if tc.nilLister { + kl.serviceLister = nil + } else { + kl.serviceLister = testServiceLister{services} + } + + testPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Namespace: tc.ns, + Name: "dapi-test-pod-name", + }, + } + podIP := "1.2.3.4" + + result, err := kl.makeEnvironmentVariables(testPod, tc.container, podIP) + if err != nil { + t.Errorf("[%v] Unexpected error: %v", tc.name, err) + } + + sort.Sort(envs(result)) + sort.Sort(envs(tc.expectedEnvs)) + + if !reflect.DeepEqual(result, tc.expectedEnvs) { + t.Errorf("%d: [%v] Unexpected env entries; expected {%v}, got {%v}", i, tc.name, tc.expectedEnvs, result) + } + } +} + +func waitingState(cName string) api.ContainerStatus { + return api.ContainerStatus{ + Name: cName, + State: api.ContainerState{ + Waiting: &api.ContainerStateWaiting{}, + }, + } +} +func waitingStateWithLastTermination(cName string) api.ContainerStatus { + return api.ContainerStatus{ + Name: cName, + State: api.ContainerState{ + Waiting: &api.ContainerStateWaiting{}, + }, + LastTerminationState: api.ContainerState{ + Terminated: &api.ContainerStateTerminated{ + ExitCode: 0, + }, + }, + } +} +func runningState(cName string) api.ContainerStatus { + return api.ContainerStatus{ + Name: cName, + State: api.ContainerState{ + Running: &api.ContainerStateRunning{}, + }, + } +} +func stoppedState(cName string) api.ContainerStatus { + return api.ContainerStatus{ + Name: cName, + State: api.ContainerState{ + Terminated: &api.ContainerStateTerminated{}, + }, + } +} +func succeededState(cName string) api.ContainerStatus { + return api.ContainerStatus{ + Name: cName, + State: api.ContainerState{ + Terminated: &api.ContainerStateTerminated{ + ExitCode: 0, + }, + }, + } +} +func failedState(cName string) api.ContainerStatus { + return api.ContainerStatus{ + Name: cName, + State: api.ContainerState{ + Terminated: &api.ContainerStateTerminated{ + ExitCode: -1, + }, + }, + } +} + +func TestPodPhaseWithRestartAlways(t *testing.T) { + desiredState := api.PodSpec{ + NodeName: "machine", + Containers: []api.Container{ + {Name: "containerA"}, + {Name: "containerB"}, + }, + RestartPolicy: api.RestartPolicyAlways, + } + + tests := []struct { + pod *api.Pod + status api.PodPhase + test string + }{ + {&api.Pod{Spec: desiredState, Status: api.PodStatus{}}, api.PodPending, "waiting"}, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + runningState("containerA"), + runningState("containerB"), + }, + }, + }, + api.PodRunning, + "all running", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + stoppedState("containerA"), + stoppedState("containerB"), + }, + }, + }, + api.PodRunning, + "all stopped with restart always", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + runningState("containerA"), + stoppedState("containerB"), + }, + }, + }, + api.PodRunning, + "mixed state #1 with restart always", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + runningState("containerA"), + }, + }, + }, + api.PodPending, + "mixed state #2 with restart always", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + runningState("containerA"), + waitingState("containerB"), + }, + }, + }, + api.PodPending, + "mixed state #3 with restart always", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + runningState("containerA"), + waitingStateWithLastTermination("containerB"), + }, + }, + }, + api.PodRunning, + "backoff crashloop container with restart always", + }, + } + for _, test := range tests { + if status := GetPhase(&test.pod.Spec, test.pod.Status.ContainerStatuses); status != test.status { + t.Errorf("In test %s, expected %v, got %v", test.test, test.status, status) + } + } +} + +func TestPodPhaseWithRestartNever(t *testing.T) { + desiredState := api.PodSpec{ + NodeName: "machine", + Containers: []api.Container{ + {Name: "containerA"}, + {Name: "containerB"}, + }, + RestartPolicy: api.RestartPolicyNever, + } + + tests := []struct { + pod *api.Pod + status api.PodPhase + test string + }{ + {&api.Pod{Spec: desiredState, Status: api.PodStatus{}}, api.PodPending, "waiting"}, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + runningState("containerA"), + runningState("containerB"), + }, + }, + }, + api.PodRunning, + "all running with restart never", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + succeededState("containerA"), + succeededState("containerB"), + }, + }, + }, + api.PodSucceeded, + "all succeeded with restart never", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + failedState("containerA"), + failedState("containerB"), + }, + }, + }, + api.PodFailed, + "all failed with restart never", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + runningState("containerA"), + succeededState("containerB"), + }, + }, + }, + api.PodRunning, + "mixed state #1 with restart never", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + runningState("containerA"), + }, + }, + }, + api.PodPending, + "mixed state #2 with restart never", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + runningState("containerA"), + waitingState("containerB"), + }, + }, + }, + api.PodPending, + "mixed state #3 with restart never", + }, + } + for _, test := range tests { + if status := GetPhase(&test.pod.Spec, test.pod.Status.ContainerStatuses); status != test.status { + t.Errorf("In test %s, expected %v, got %v", test.test, test.status, status) + } + } +} + +func TestPodPhaseWithRestartOnFailure(t *testing.T) { + desiredState := api.PodSpec{ + NodeName: "machine", + Containers: []api.Container{ + {Name: "containerA"}, + {Name: "containerB"}, + }, + RestartPolicy: api.RestartPolicyOnFailure, + } + + tests := []struct { + pod *api.Pod + status api.PodPhase + test string + }{ + {&api.Pod{Spec: desiredState, Status: api.PodStatus{}}, api.PodPending, "waiting"}, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + runningState("containerA"), + runningState("containerB"), + }, + }, + }, + api.PodRunning, + "all running with restart onfailure", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + succeededState("containerA"), + succeededState("containerB"), + }, + }, + }, + api.PodSucceeded, + "all succeeded with restart onfailure", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + failedState("containerA"), + failedState("containerB"), + }, + }, + }, + api.PodRunning, + "all failed with restart never", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + runningState("containerA"), + succeededState("containerB"), + }, + }, + }, + api.PodRunning, + "mixed state #1 with restart onfailure", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + runningState("containerA"), + }, + }, + }, + api.PodPending, + "mixed state #2 with restart onfailure", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + runningState("containerA"), + waitingState("containerB"), + }, + }, + }, + api.PodPending, + "mixed state #3 with restart onfailure", + }, + { + &api.Pod{ + Spec: desiredState, + Status: api.PodStatus{ + ContainerStatuses: []api.ContainerStatus{ + runningState("containerA"), + waitingStateWithLastTermination("containerB"), + }, + }, + }, + api.PodRunning, + "backoff crashloop container with restart onfailure", + }, + } + for _, test := range tests { + if status := GetPhase(&test.pod.Spec, test.pod.Status.ContainerStatuses); status != test.status { + t.Errorf("In test %s, expected %v, got %v", test.test, test.status, status) + } + } +} + +func TestExecInContainerNoSuchPod(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + fakeRuntime := testKubelet.fakeRuntime + fakeCommandRunner := fakeContainerCommandRunner{} + kubelet.runner = &fakeCommandRunner + fakeRuntime.PodList = []*kubecontainer.Pod{} + + podName := "podFoo" + podNamespace := "nsFoo" + containerID := "containerFoo" + err := kubelet.ExecInContainer( + kubecontainer.GetPodFullName(&api.Pod{ObjectMeta: api.ObjectMeta{Name: podName, Namespace: podNamespace}}), + "", + containerID, + []string{"ls"}, + nil, + nil, + nil, + false, + ) + if err == nil { + t.Fatal("unexpected non-error") + } + if !fakeCommandRunner.ID.IsEmpty() { + t.Fatal("unexpected invocation of runner.ExecInContainer") + } +} + +func TestExecInContainerNoSuchContainer(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + fakeRuntime := testKubelet.fakeRuntime + fakeCommandRunner := fakeContainerCommandRunner{} + kubelet.runner = &fakeCommandRunner + + podName := "podFoo" + podNamespace := "nsFoo" + containerID := "containerFoo" + fakeRuntime.PodList = []*kubecontainer.Pod{ + { + ID: "12345678", + Name: podName, + Namespace: podNamespace, + Containers: []*kubecontainer.Container{ + {Name: "bar", + ID: kubecontainer.ContainerID{Type: "test", ID: "barID"}}, + }, + }, + } + + err := kubelet.ExecInContainer( + kubecontainer.GetPodFullName(&api.Pod{ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: podName, + Namespace: podNamespace, + }}), + "", + containerID, + []string{"ls"}, + nil, + nil, + nil, + false, + ) + if err == nil { + t.Fatal("unexpected non-error") + } + if !fakeCommandRunner.ID.IsEmpty() { + t.Fatal("unexpected invocation of runner.ExecInContainer") + } +} + +type fakeReadWriteCloser struct{} + +func (f *fakeReadWriteCloser) Write(data []byte) (int, error) { + return 0, nil +} + +func (f *fakeReadWriteCloser) Read(data []byte) (int, error) { + return 0, nil +} + +func (f *fakeReadWriteCloser) Close() error { + return nil +} + +func TestExecInContainer(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + fakeRuntime := testKubelet.fakeRuntime + fakeCommandRunner := fakeContainerCommandRunner{} + kubelet.runner = &fakeCommandRunner + + podName := "podFoo" + podNamespace := "nsFoo" + containerID := "containerFoo" + command := []string{"ls"} + stdin := &bytes.Buffer{} + stdout := &fakeReadWriteCloser{} + stderr := &fakeReadWriteCloser{} + tty := true + fakeRuntime.PodList = []*kubecontainer.Pod{ + { + ID: "12345678", + Name: podName, + Namespace: podNamespace, + Containers: []*kubecontainer.Container{ + {Name: containerID, + ID: kubecontainer.ContainerID{Type: "test", ID: containerID}, + }, + }, + }, + } + + err := kubelet.ExecInContainer( + kubecontainer.GetPodFullName(&api.Pod{ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: podName, + Namespace: podNamespace, + }}), + "", + containerID, + []string{"ls"}, + stdin, + stdout, + stderr, + tty, + ) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if e, a := containerID, fakeCommandRunner.ID.ID; e != a { + t.Fatalf("container name: expected %q, got %q", e, a) + } + if e, a := command, fakeCommandRunner.Cmd; !reflect.DeepEqual(e, a) { + t.Fatalf("command: expected '%v', got '%v'", e, a) + } + if e, a := stdin, fakeCommandRunner.Stdin; e != a { + t.Fatalf("stdin: expected %#v, got %#v", e, a) + } + if e, a := stdout, fakeCommandRunner.Stdout; e != a { + t.Fatalf("stdout: expected %#v, got %#v", e, a) + } + if e, a := stderr, fakeCommandRunner.Stderr; e != a { + t.Fatalf("stderr: expected %#v, got %#v", e, a) + } + if e, a := tty, fakeCommandRunner.TTY; e != a { + t.Fatalf("tty: expected %t, got %t", e, a) + } +} + +func TestPortForwardNoSuchPod(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + fakeRuntime := testKubelet.fakeRuntime + fakeRuntime.PodList = []*kubecontainer.Pod{} + fakeCommandRunner := fakeContainerCommandRunner{} + kubelet.runner = &fakeCommandRunner + + podName := "podFoo" + podNamespace := "nsFoo" + var port uint16 = 5000 + + err := kubelet.PortForward( + kubecontainer.GetPodFullName(&api.Pod{ObjectMeta: api.ObjectMeta{Name: podName, Namespace: podNamespace}}), + "", + port, + nil, + ) + if err == nil { + t.Fatal("unexpected non-error") + } + if !fakeCommandRunner.ID.IsEmpty() { + t.Fatal("unexpected invocation of runner.PortForward") + } +} + +func TestPortForward(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + fakeRuntime := testKubelet.fakeRuntime + + podName := "podFoo" + podNamespace := "nsFoo" + podID := types.UID("12345678") + fakeRuntime.PodList = []*kubecontainer.Pod{ + { + ID: podID, + Name: podName, + Namespace: podNamespace, + Containers: []*kubecontainer.Container{ + { + Name: "foo", + ID: kubecontainer.ContainerID{Type: "test", ID: "containerFoo"}, + }, + }, + }, + } + fakeCommandRunner := fakeContainerCommandRunner{} + kubelet.runner = &fakeCommandRunner + + var port uint16 = 5000 + stream := &fakeReadWriteCloser{} + err := kubelet.PortForward( + kubecontainer.GetPodFullName(&api.Pod{ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: podName, + Namespace: podNamespace, + }}), + "", + port, + stream, + ) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if e, a := podID, fakeCommandRunner.PodID; e != a { + t.Fatalf("container id: expected %q, got %q", e, a) + } + if e, a := port, fakeCommandRunner.Port; e != a { + t.Fatalf("port: expected %v, got %v", e, a) + } + if e, a := stream, fakeCommandRunner.Stream; e != a { + t.Fatalf("stream: expected %v, got %v", e, a) + } +} + +// Tests that identify the host port conflicts are detected correctly. +func TestGetHostPortConflicts(t *testing.T) { + pods := []*api.Pod{ + {Spec: api.PodSpec{Containers: []api.Container{{Ports: []api.ContainerPort{{HostPort: 80}}}}}}, + {Spec: api.PodSpec{Containers: []api.Container{{Ports: []api.ContainerPort{{HostPort: 81}}}}}}, + {Spec: api.PodSpec{Containers: []api.Container{{Ports: []api.ContainerPort{{HostPort: 82}}}}}}, + {Spec: api.PodSpec{Containers: []api.Container{{Ports: []api.ContainerPort{{HostPort: 83}}}}}}, + } + // Pods should not cause any conflict. + if hasHostPortConflicts(pods) { + t.Errorf("expected no conflicts, Got conflicts") + } + + expected := &api.Pod{ + Spec: api.PodSpec{Containers: []api.Container{{Ports: []api.ContainerPort{{HostPort: 81}}}}}, + } + // The new pod should cause conflict and be reported. + pods = append(pods, expected) + if !hasHostPortConflicts(pods) { + t.Errorf("expected conflict, Got no conflicts") + } +} + +// Tests that we handle port conflicts correctly by setting the failed status in status map. +func TestHandlePortConflicts(t *testing.T) { + testKubelet := newTestKubelet(t) + kl := testKubelet.kubelet + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + testKubelet.fakeCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + testKubelet.fakeCadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + + kl.nodeLister = testNodeLister{nodes: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: kl.nodeName}}, + }} + kl.nodeInfo = testNodeInfo{nodes: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: kl.nodeName}}, + }} + + spec := api.PodSpec{NodeName: kl.nodeName, Containers: []api.Container{{Ports: []api.ContainerPort{{HostPort: 80}}}}} + pods := []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + UID: "123456789", + Name: "newpod", + Namespace: "foo", + }, + Spec: spec, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "987654321", + Name: "oldpod", + Namespace: "foo", + }, + Spec: spec, + }, + } + // Make sure the Pods are in the reverse order of creation time. + pods[1].CreationTimestamp = unversioned.NewTime(time.Now()) + pods[0].CreationTimestamp = unversioned.NewTime(time.Now().Add(1 * time.Second)) + // The newer pod should be rejected. + notfittingPod := pods[0] + fittingPod := pods[1] + + kl.HandlePodAdditions(pods) + // Check pod status stored in the status map. + // notfittingPod should be Failed + status, found := kl.statusManager.GetPodStatus(notfittingPod.UID) + if !found { + t.Fatalf("status of pod %q is not found in the status map", notfittingPod.UID) + } + if status.Phase != api.PodFailed { + t.Fatalf("expected pod status %q. Got %q.", api.PodFailed, status.Phase) + } + // fittingPod should be Pending + status, found = kl.statusManager.GetPodStatus(fittingPod.UID) + if !found { + t.Fatalf("status of pod %q is not found in the status map", fittingPod.UID) + } + if status.Phase != api.PodPending { + t.Fatalf("expected pod status %q. Got %q.", api.PodPending, status.Phase) + } +} + +// Tests that we handle host name conflicts correctly by setting the failed status in status map. +func TestHandleHostNameConflicts(t *testing.T) { + testKubelet := newTestKubelet(t) + kl := testKubelet.kubelet + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + testKubelet.fakeCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + testKubelet.fakeCadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + + kl.nodeLister = testNodeLister{nodes: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: "127.0.0.1"}}, + }} + kl.nodeInfo = testNodeInfo{nodes: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: "127.0.0.1"}}, + }} + + pods := []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + UID: "123456789", + Name: "notfittingpod", + Namespace: "foo", + }, + Spec: api.PodSpec{ + // default NodeName in test is 127.0.0.1 + NodeName: "127.0.0.2", + }, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "987654321", + Name: "fittingpod", + Namespace: "foo", + }, + Spec: api.PodSpec{ + // default NodeName in test is 127.0.0.1 + NodeName: "127.0.0.1", + }, + }, + } + + notfittingPod := pods[0] + fittingPod := pods[1] + + kl.HandlePodAdditions(pods) + // Check pod status stored in the status map. + // notfittingPod should be Failed + status, found := kl.statusManager.GetPodStatus(notfittingPod.UID) + if !found { + t.Fatalf("status of pod %q is not found in the status map", notfittingPod.UID) + } + if status.Phase != api.PodFailed { + t.Fatalf("expected pod status %q. Got %q.", api.PodFailed, status.Phase) + } + // fittingPod should be Pending + status, found = kl.statusManager.GetPodStatus(fittingPod.UID) + if !found { + t.Fatalf("status of pod %q is not found in the status map", fittingPod.UID) + } + if status.Phase != api.PodPending { + t.Fatalf("expected pod status %q. Got %q.", api.PodPending, status.Phase) + } +} + +// Tests that we handle not matching labels selector correctly by setting the failed status in status map. +func TestHandleNodeSelector(t *testing.T) { + testKubelet := newTestKubelet(t) + kl := testKubelet.kubelet + kl.nodeLister = testNodeLister{nodes: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: testKubeletHostname, Labels: map[string]string{"key": "B"}}}, + }} + kl.nodeInfo = testNodeInfo{nodes: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: testKubeletHostname, Labels: map[string]string{"key": "B"}}}, + }} + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + testKubelet.fakeCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + testKubelet.fakeCadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + pods := []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + UID: "123456789", + Name: "podA", + Namespace: "foo", + }, + Spec: api.PodSpec{NodeSelector: map[string]string{"key": "A"}}, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "987654321", + Name: "podB", + Namespace: "foo", + }, + Spec: api.PodSpec{NodeSelector: map[string]string{"key": "B"}}, + }, + } + // The first pod should be rejected. + notfittingPod := pods[0] + fittingPod := pods[1] + + kl.HandlePodAdditions(pods) + // Check pod status stored in the status map. + // notfittingPod should be Failed + status, found := kl.statusManager.GetPodStatus(notfittingPod.UID) + if !found { + t.Fatalf("status of pod %q is not found in the status map", notfittingPod.UID) + } + if status.Phase != api.PodFailed { + t.Fatalf("expected pod status %q. Got %q.", api.PodFailed, status.Phase) + } + // fittingPod should be Pending + status, found = kl.statusManager.GetPodStatus(fittingPod.UID) + if !found { + t.Fatalf("status of pod %q is not found in the status map", fittingPod.UID) + } + if status.Phase != api.PodPending { + t.Fatalf("expected pod status %q. Got %q.", api.PodPending, status.Phase) + } +} + +// Tests that we handle exceeded resources correctly by setting the failed status in status map. +func TestHandleMemExceeded(t *testing.T) { + testKubelet := newTestKubelet(t) + kl := testKubelet.kubelet + kl.nodeLister = testNodeLister{nodes: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}, + Status: api.NodeStatus{Capacity: api.ResourceList{}, Allocatable: api.ResourceList{ + api.ResourceCPU: *resource.NewMilliQuantity(10, resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(100, resource.BinarySI), + api.ResourcePods: *resource.NewQuantity(40, resource.DecimalSI), + }}}, + }} + kl.nodeInfo = testNodeInfo{nodes: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}, + Status: api.NodeStatus{Capacity: api.ResourceList{}, Allocatable: api.ResourceList{ + api.ResourceCPU: *resource.NewMilliQuantity(10, resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(100, resource.BinarySI), + api.ResourcePods: *resource.NewQuantity(40, resource.DecimalSI), + }}}, + }} + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + testKubelet.fakeCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + testKubelet.fakeCadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + + spec := api.PodSpec{NodeName: kl.nodeName, + Containers: []api.Container{{Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + "memory": resource.MustParse("90"), + }, + }}}} + pods := []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + UID: "123456789", + Name: "newpod", + Namespace: "foo", + }, + Spec: spec, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "987654321", + Name: "oldpod", + Namespace: "foo", + }, + Spec: spec, + }, + } + // Make sure the Pods are in the reverse order of creation time. + pods[1].CreationTimestamp = unversioned.NewTime(time.Now()) + pods[0].CreationTimestamp = unversioned.NewTime(time.Now().Add(1 * time.Second)) + // The newer pod should be rejected. + notfittingPod := pods[0] + fittingPod := pods[1] + + kl.HandlePodAdditions(pods) + // Check pod status stored in the status map. + // notfittingPod should be Failed + status, found := kl.statusManager.GetPodStatus(notfittingPod.UID) + if !found { + t.Fatalf("status of pod %q is not found in the status map", notfittingPod.UID) + } + if status.Phase != api.PodFailed { + t.Fatalf("expected pod status %q. Got %q.", api.PodFailed, status.Phase) + } + // fittingPod should be Pending + status, found = kl.statusManager.GetPodStatus(fittingPod.UID) + if !found { + t.Fatalf("status of pod %q is not found in the status map", fittingPod.UID) + } + if status.Phase != api.PodPending { + t.Fatalf("expected pod status %q. Got %q.", api.PodPending, status.Phase) + } +} + +// TODO(filipg): This test should be removed once StatusSyncer can do garbage collection without external signal. +func TestPurgingObsoleteStatusMapEntries(t *testing.T) { + testKubelet := newTestKubelet(t) + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + testKubelet.fakeCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + testKubelet.fakeCadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + versionInfo := &cadvisorapi.VersionInfo{ + KernelVersion: "3.16.0-0.bpo.4-amd64", + ContainerOsVersion: "Debian GNU/Linux 7 (wheezy)", + DockerVersion: "1.5.0", + } + testKubelet.fakeCadvisor.On("VersionInfo").Return(versionInfo, nil) + + kl := testKubelet.kubelet + pods := []*api.Pod{ + {ObjectMeta: api.ObjectMeta{Name: "pod1", UID: "1234"}, Spec: api.PodSpec{Containers: []api.Container{{Ports: []api.ContainerPort{{HostPort: 80}}}}}}, + {ObjectMeta: api.ObjectMeta{Name: "pod2", UID: "4567"}, Spec: api.PodSpec{Containers: []api.Container{{Ports: []api.ContainerPort{{HostPort: 80}}}}}}, + } + podToTest := pods[1] + // Run once to populate the status map. + kl.HandlePodAdditions(pods) + if _, found := kl.statusManager.GetPodStatus(podToTest.UID); !found { + t.Fatalf("expected to have status cached for pod2") + } + // Sync with empty pods so that the entry in status map will be removed. + kl.podManager.SetPods([]*api.Pod{}) + kl.HandlePodCleanups() + if _, found := kl.statusManager.GetPodStatus(podToTest.UID); found { + t.Fatalf("expected to not have status cached for pod2") + } +} + +func TestValidateContainerLogStatus(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + containerName := "x" + testCases := []struct { + statuses []api.ContainerStatus + success bool + }{ + { + statuses: []api.ContainerStatus{ + { + Name: containerName, + State: api.ContainerState{ + Running: &api.ContainerStateRunning{}, + }, + LastTerminationState: api.ContainerState{ + Terminated: &api.ContainerStateTerminated{}, + }, + }, + }, + success: true, + }, + { + statuses: []api.ContainerStatus{ + { + Name: containerName, + State: api.ContainerState{ + Running: &api.ContainerStateRunning{}, + }, + }, + }, + success: true, + }, + { + statuses: []api.ContainerStatus{ + { + Name: containerName, + State: api.ContainerState{ + Terminated: &api.ContainerStateTerminated{}, + }, + }, + }, + success: true, + }, + { + statuses: []api.ContainerStatus{ + { + Name: containerName, + State: api.ContainerState{ + Waiting: &api.ContainerStateWaiting{}, + }, + }, + }, + success: false, + }, + { + statuses: []api.ContainerStatus{ + { + Name: containerName, + State: api.ContainerState{Waiting: &api.ContainerStateWaiting{Reason: "ErrImagePull"}}, + }, + }, + success: false, + }, + { + statuses: []api.ContainerStatus{ + { + Name: containerName, + State: api.ContainerState{Waiting: &api.ContainerStateWaiting{Reason: "ErrImagePullBackOff"}}, + }, + }, + success: false, + }, + } + + for i, tc := range testCases { + _, err := kubelet.validateContainerLogStatus("podName", &api.PodStatus{ + ContainerStatuses: tc.statuses, + }, containerName, false) + if tc.success { + if err != nil { + t.Errorf("[case %d]: unexpected failure - %v", i, err) + } + } else if err == nil { + t.Errorf("[case %d]: unexpected success", i) + } + } + if _, err := kubelet.validateContainerLogStatus("podName", &api.PodStatus{ + ContainerStatuses: testCases[0].statuses, + }, "blah", false); err == nil { + t.Errorf("expected error with invalid container name") + } + if _, err := kubelet.validateContainerLogStatus("podName", &api.PodStatus{ + ContainerStatuses: testCases[0].statuses, + }, containerName, true); err != nil { + t.Errorf("unexpected error with for previous terminated container - %v", err) + } + if _, err := kubelet.validateContainerLogStatus("podName", &api.PodStatus{ + ContainerStatuses: testCases[0].statuses, + }, containerName, false); err != nil { + t.Errorf("unexpected error with for most recent container - %v", err) + } + if _, err := kubelet.validateContainerLogStatus("podName", &api.PodStatus{ + ContainerStatuses: testCases[1].statuses, + }, containerName, true); err == nil { + t.Errorf("expected error with for previous terminated container") + } + if _, err := kubelet.validateContainerLogStatus("podName", &api.PodStatus{ + ContainerStatuses: testCases[1].statuses, + }, containerName, false); err != nil { + t.Errorf("unexpected error with for most recent container") + } +} + +// updateDiskSpacePolicy creates a new DiskSpaceManager with a new policy. This new manager along +// with the mock FsInfo values added to Cadvisor should make the kubelet report that it has +// sufficient disk space or it is out of disk, depending on the capacity, availability and +// threshold values. +func updateDiskSpacePolicy(kubelet *Kubelet, mockCadvisor *cadvisortest.Mock, rootCap, dockerCap, rootAvail, dockerAvail uint64, rootThreshold, dockerThreshold int) error { + dockerimagesFsInfo := cadvisorapiv2.FsInfo{Capacity: rootCap * mb, Available: rootAvail * mb} + rootFsInfo := cadvisorapiv2.FsInfo{Capacity: dockerCap * mb, Available: dockerAvail * mb} + mockCadvisor.On("DockerImagesFsInfo").Return(dockerimagesFsInfo, nil) + mockCadvisor.On("RootFsInfo").Return(rootFsInfo, nil) + + dsp := DiskSpacePolicy{DockerFreeDiskMB: rootThreshold, RootFreeDiskMB: dockerThreshold} + diskSpaceManager, err := newDiskSpaceManager(mockCadvisor, dsp) + if err != nil { + return err + } + kubelet.diskSpaceManager = diskSpaceManager + return nil +} + +func TestUpdateNewNodeStatus(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + kubeClient := testKubelet.fakeKubeClient + kubeClient.ReactionChain = fake.NewSimpleClientset(&api.NodeList{Items: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}}, + }}).ReactionChain + machineInfo := &cadvisorapi.MachineInfo{ + MachineID: "123", + SystemUUID: "abc", + BootID: "1b3", + NumCores: 2, + MemoryCapacity: 10E9, // 10G + } + mockCadvisor := testKubelet.fakeCadvisor + mockCadvisor.On("Start").Return(nil) + mockCadvisor.On("MachineInfo").Return(machineInfo, nil) + versionInfo := &cadvisorapi.VersionInfo{ + KernelVersion: "3.16.0-0.bpo.4-amd64", + ContainerOsVersion: "Debian GNU/Linux 7 (wheezy)", + } + mockCadvisor.On("VersionInfo").Return(versionInfo, nil) + + // Make kubelet report that it has sufficient disk space. + if err := updateDiskSpacePolicy(kubelet, mockCadvisor, 500, 500, 200, 200, 100, 100); err != nil { + t.Fatalf("can't update disk space manager: %v", err) + } + + expectedNode := &api.Node{ + ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}, + Spec: api.NodeSpec{}, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeOutOfDisk, + Status: api.ConditionFalse, + Reason: "KubeletHasSufficientDisk", + Message: fmt.Sprintf("kubelet has sufficient disk space available"), + LastHeartbeatTime: unversioned.Time{}, + LastTransitionTime: unversioned.Time{}, + }, + { + Type: api.NodeReady, + Status: api.ConditionTrue, + Reason: "KubeletReady", + Message: fmt.Sprintf("kubelet is posting ready status"), + LastHeartbeatTime: unversioned.Time{}, + LastTransitionTime: unversioned.Time{}, + }, + }, + NodeInfo: api.NodeSystemInfo{ + MachineID: "123", + SystemUUID: "abc", + BootID: "1b3", + KernelVersion: "3.16.0-0.bpo.4-amd64", + OSImage: "Debian GNU/Linux 7 (wheezy)", + ContainerRuntimeVersion: "test://1.5.0", + KubeletVersion: version.Get().String(), + KubeProxyVersion: version.Get().String(), + }, + Capacity: api.ResourceList{ + api.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(10E9, resource.BinarySI), + api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI), + }, + Allocatable: api.ResourceList{ + api.ResourceCPU: *resource.NewMilliQuantity(1800, resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(9900E6, resource.BinarySI), + api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI), + }, + Addresses: []api.NodeAddress{ + {Type: api.NodeLegacyHostIP, Address: "127.0.0.1"}, + {Type: api.NodeInternalIP, Address: "127.0.0.1"}, + }, + Images: []api.ContainerImage{ + { + Names: []string{"gcr.io/google_containers:v1", "gcr.io/google_containers:v2"}, + SizeBytes: 123, + }, + { + Names: []string{"gcr.io/google_containers:v3", "gcr.io/google_containers:v4"}, + SizeBytes: 456, + }, + }, + }, + } + + kubelet.updateRuntimeUp() + if err := kubelet.updateNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + actions := kubeClient.Actions() + if len(actions) != 2 { + t.Fatalf("unexpected actions: %v", actions) + } + if !actions[1].Matches("update", "nodes") || actions[1].GetSubresource() != "status" { + t.Fatalf("unexpected actions: %v", actions) + } + updatedNode, ok := actions[1].(testclient.UpdateAction).GetObject().(*api.Node) + if !ok { + t.Errorf("unexpected object type") + } + for i, cond := range updatedNode.Status.Conditions { + if cond.LastHeartbeatTime.IsZero() { + t.Errorf("unexpected zero last probe timestamp for %v condition", cond.Type) + } + if cond.LastTransitionTime.IsZero() { + t.Errorf("unexpected zero last transition timestamp for %v condition", cond.Type) + } + updatedNode.Status.Conditions[i].LastHeartbeatTime = unversioned.Time{} + updatedNode.Status.Conditions[i].LastTransitionTime = unversioned.Time{} + } + + // Version skew workaround. See: https://github.com/kubernetes/kubernetes/issues/16961 + if updatedNode.Status.Conditions[len(updatedNode.Status.Conditions)-1].Type != api.NodeReady { + t.Errorf("unexpected node condition order. NodeReady should be last.") + } + + if !api.Semantic.DeepEqual(expectedNode, updatedNode) { + t.Errorf("unexpected objects: %s", diff.ObjectDiff(expectedNode, updatedNode)) + } +} + +func TestUpdateNewNodeOutOfDiskStatusWithTransitionFrequency(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + kubeClient := testKubelet.fakeKubeClient + kubeClient.ReactionChain = fake.NewSimpleClientset(&api.NodeList{Items: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}}, + }}).ReactionChain + machineInfo := &cadvisorapi.MachineInfo{ + MachineID: "123", + SystemUUID: "abc", + BootID: "1b3", + NumCores: 2, + MemoryCapacity: 1024, + } + mockCadvisor := testKubelet.fakeCadvisor + mockCadvisor.On("Start").Return(nil) + mockCadvisor.On("MachineInfo").Return(machineInfo, nil) + versionInfo := &cadvisorapi.VersionInfo{ + KernelVersion: "3.16.0-0.bpo.4-amd64", + ContainerOsVersion: "Debian GNU/Linux 7 (wheezy)", + } + mockCadvisor.On("VersionInfo").Return(versionInfo, nil) + + // Make Kubelet report that it has sufficient disk space. + if err := updateDiskSpacePolicy(kubelet, mockCadvisor, 500, 500, 200, 200, 100, 100); err != nil { + t.Fatalf("can't update disk space manager: %v", err) + } + + kubelet.outOfDiskTransitionFrequency = 10 * time.Second + + expectedNodeOutOfDiskCondition := api.NodeCondition{ + Type: api.NodeOutOfDisk, + Status: api.ConditionFalse, + Reason: "KubeletHasSufficientDisk", + Message: fmt.Sprintf("kubelet has sufficient disk space available"), + LastHeartbeatTime: unversioned.Time{}, + LastTransitionTime: unversioned.Time{}, + } + + kubelet.updateRuntimeUp() + if err := kubelet.updateNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + actions := kubeClient.Actions() + if len(actions) != 2 { + t.Fatalf("unexpected actions: %v", actions) + } + if !actions[1].Matches("update", "nodes") || actions[1].GetSubresource() != "status" { + t.Fatalf("unexpected actions: %v", actions) + } + updatedNode, ok := actions[1].(testclient.UpdateAction).GetObject().(*api.Node) + if !ok { + t.Errorf("unexpected object type") + } + + var oodCondition api.NodeCondition + for i, cond := range updatedNode.Status.Conditions { + if cond.LastHeartbeatTime.IsZero() { + t.Errorf("unexpected zero last probe timestamp for %v condition", cond.Type) + } + if cond.LastTransitionTime.IsZero() { + t.Errorf("unexpected zero last transition timestamp for %v condition", cond.Type) + } + updatedNode.Status.Conditions[i].LastHeartbeatTime = unversioned.Time{} + updatedNode.Status.Conditions[i].LastTransitionTime = unversioned.Time{} + if cond.Type == api.NodeOutOfDisk { + oodCondition = updatedNode.Status.Conditions[i] + } + } + + if !reflect.DeepEqual(expectedNodeOutOfDiskCondition, oodCondition) { + t.Errorf("unexpected objects: %s", diff.ObjectDiff(expectedNodeOutOfDiskCondition, oodCondition)) + } +} + +func TestUpdateExistingNodeStatus(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + kubeClient := testKubelet.fakeKubeClient + kubeClient.ReactionChain = fake.NewSimpleClientset(&api.NodeList{Items: []api.Node{ + { + ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}, + Spec: api.NodeSpec{}, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeOutOfDisk, + Status: api.ConditionTrue, + Reason: "KubeletOutOfDisk", + Message: "out of disk space", + LastHeartbeatTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + { + Type: api.NodeReady, + Status: api.ConditionTrue, + Reason: "KubeletReady", + Message: fmt.Sprintf("kubelet is posting ready status"), + LastHeartbeatTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + LastTransitionTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC), + }, + }, + Capacity: api.ResourceList{ + api.ResourceCPU: *resource.NewMilliQuantity(3000, resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(20E9, resource.BinarySI), + api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI), + }, + Allocatable: api.ResourceList{ + api.ResourceCPU: *resource.NewMilliQuantity(2800, resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(19900E6, resource.BinarySI), + api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI), + }, + }, + }, + }}).ReactionChain + mockCadvisor := testKubelet.fakeCadvisor + mockCadvisor.On("Start").Return(nil) + machineInfo := &cadvisorapi.MachineInfo{ + MachineID: "123", + SystemUUID: "abc", + BootID: "1b3", + NumCores: 2, + MemoryCapacity: 20E9, + } + mockCadvisor.On("MachineInfo").Return(machineInfo, nil) + versionInfo := &cadvisorapi.VersionInfo{ + KernelVersion: "3.16.0-0.bpo.4-amd64", + ContainerOsVersion: "Debian GNU/Linux 7 (wheezy)", + } + mockCadvisor.On("VersionInfo").Return(versionInfo, nil) + + // Make kubelet report that it is out of disk space. + if err := updateDiskSpacePolicy(kubelet, mockCadvisor, 500, 500, 50, 50, 100, 100); err != nil { + t.Fatalf("can't update disk space manager: %v", err) + } + + expectedNode := &api.Node{ + ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}, + Spec: api.NodeSpec{}, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeOutOfDisk, + Status: api.ConditionTrue, + Reason: "KubeletOutOfDisk", + Message: "out of disk space", + LastHeartbeatTime: unversioned.Time{}, // placeholder + LastTransitionTime: unversioned.Time{}, // placeholder + }, + { + Type: api.NodeReady, + Status: api.ConditionTrue, + Reason: "KubeletReady", + Message: fmt.Sprintf("kubelet is posting ready status"), + LastHeartbeatTime: unversioned.Time{}, // placeholder + LastTransitionTime: unversioned.Time{}, // placeholder + }, + }, + NodeInfo: api.NodeSystemInfo{ + MachineID: "123", + SystemUUID: "abc", + BootID: "1b3", + KernelVersion: "3.16.0-0.bpo.4-amd64", + OSImage: "Debian GNU/Linux 7 (wheezy)", + ContainerRuntimeVersion: "test://1.5.0", + KubeletVersion: version.Get().String(), + KubeProxyVersion: version.Get().String(), + }, + Capacity: api.ResourceList{ + api.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(20E9, resource.BinarySI), + api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI), + }, + Allocatable: api.ResourceList{ + api.ResourceCPU: *resource.NewMilliQuantity(1800, resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(19900E6, resource.BinarySI), + api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI), + }, + Addresses: []api.NodeAddress{ + {Type: api.NodeLegacyHostIP, Address: "127.0.0.1"}, + {Type: api.NodeInternalIP, Address: "127.0.0.1"}, + }, + Images: []api.ContainerImage{ + { + Names: []string{"gcr.io/google_containers:v1", "gcr.io/google_containers:v2"}, + SizeBytes: 123, + }, + { + Names: []string{"gcr.io/google_containers:v3", "gcr.io/google_containers:v4"}, + SizeBytes: 456, + }, + }, + }, + } + + kubelet.updateRuntimeUp() + if err := kubelet.updateNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + actions := kubeClient.Actions() + if len(actions) != 2 { + t.Errorf("unexpected actions: %v", actions) + } + updateAction, ok := actions[1].(testclient.UpdateAction) + if !ok { + t.Errorf("unexpected action type. expected UpdateAction, got %#v", actions[1]) + } + updatedNode, ok := updateAction.GetObject().(*api.Node) + if !ok { + t.Errorf("unexpected object type") + } + for i, cond := range updatedNode.Status.Conditions { + // Expect LastProbeTime to be updated to Now, while LastTransitionTime to be the same. + if old := unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC).Time; reflect.DeepEqual(cond.LastHeartbeatTime.Rfc3339Copy().UTC(), old) { + t.Errorf("Condition %v LastProbeTime: expected \n%v\n, got \n%v", cond.Type, unversioned.Now(), old) + } + if got, want := cond.LastTransitionTime.Rfc3339Copy().UTC(), unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC).Time; !reflect.DeepEqual(got, want) { + t.Errorf("Condition %v LastTransitionTime: expected \n%#v\n, got \n%#v", cond.Type, want, got) + } + updatedNode.Status.Conditions[i].LastHeartbeatTime = unversioned.Time{} + updatedNode.Status.Conditions[i].LastTransitionTime = unversioned.Time{} + } + + // Version skew workaround. See: https://github.com/kubernetes/kubernetes/issues/16961 + if updatedNode.Status.Conditions[len(updatedNode.Status.Conditions)-1].Type != api.NodeReady { + t.Errorf("unexpected node condition order. NodeReady should be last.") + } + + if !api.Semantic.DeepEqual(expectedNode, updatedNode) { + t.Errorf("expected \n%v\n, got \n%v", expectedNode, updatedNode) + } +} + +func TestUpdateExistingNodeOutOfDiskStatusWithTransitionFrequency(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + clock := testKubelet.fakeClock + kubeClient := testKubelet.fakeKubeClient + kubeClient.ReactionChain = fake.NewSimpleClientset(&api.NodeList{Items: []api.Node{ + { + ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}, + Spec: api.NodeSpec{}, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeReady, + Status: api.ConditionTrue, + Reason: "KubeletReady", + Message: fmt.Sprintf("kubelet is posting ready status"), + LastHeartbeatTime: unversioned.NewTime(clock.Now()), + LastTransitionTime: unversioned.NewTime(clock.Now()), + }, + { + + Type: api.NodeOutOfDisk, + Status: api.ConditionTrue, + Reason: "KubeletOutOfDisk", + Message: "out of disk space", + LastHeartbeatTime: unversioned.NewTime(clock.Now()), + LastTransitionTime: unversioned.NewTime(clock.Now()), + }, + }, + }, + }, + }}).ReactionChain + mockCadvisor := testKubelet.fakeCadvisor + machineInfo := &cadvisorapi.MachineInfo{ + MachineID: "123", + SystemUUID: "abc", + BootID: "1b3", + NumCores: 2, + MemoryCapacity: 1024, + } + mockCadvisor.On("Start").Return(nil) + mockCadvisor.On("MachineInfo").Return(machineInfo, nil) + versionInfo := &cadvisorapi.VersionInfo{ + KernelVersion: "3.16.0-0.bpo.4-amd64", + ContainerOsVersion: "Debian GNU/Linux 7 (wheezy)", + DockerVersion: "1.5.0", + } + mockCadvisor.On("VersionInfo").Return(versionInfo, nil) + + kubelet.outOfDiskTransitionFrequency = 5 * time.Second + + ood := api.NodeCondition{ + Type: api.NodeOutOfDisk, + Status: api.ConditionTrue, + Reason: "KubeletOutOfDisk", + Message: "out of disk space", + LastHeartbeatTime: unversioned.NewTime(clock.Now()), // placeholder + LastTransitionTime: unversioned.NewTime(clock.Now()), // placeholder + } + noOod := api.NodeCondition{ + Type: api.NodeOutOfDisk, + Status: api.ConditionFalse, + Reason: "KubeletHasSufficientDisk", + Message: fmt.Sprintf("kubelet has sufficient disk space available"), + LastHeartbeatTime: unversioned.NewTime(clock.Now()), // placeholder + LastTransitionTime: unversioned.NewTime(clock.Now()), // placeholder + } + + testCases := []struct { + rootFsAvail uint64 + dockerFsAvail uint64 + expected api.NodeCondition + }{ + { + // NodeOutOfDisk==false + rootFsAvail: 200, + dockerFsAvail: 200, + expected: ood, + }, + { + // NodeOutOfDisk==true + rootFsAvail: 50, + dockerFsAvail: 200, + expected: ood, + }, + { + // NodeOutOfDisk==false + rootFsAvail: 200, + dockerFsAvail: 200, + expected: ood, + }, + { + // NodeOutOfDisk==true + rootFsAvail: 200, + dockerFsAvail: 50, + expected: ood, + }, + { + // NodeOutOfDisk==false + rootFsAvail: 200, + dockerFsAvail: 200, + expected: noOod, + }, + } + + kubelet.updateRuntimeUp() + for tcIdx, tc := range testCases { + // Step by a second + clock.Step(1 * time.Second) + + // Setup expected times. + tc.expected.LastHeartbeatTime = unversioned.NewTime(clock.Now()) + // In the last case, there should be a status transition for NodeOutOfDisk + if tcIdx == len(testCases)-1 { + tc.expected.LastTransitionTime = unversioned.NewTime(clock.Now()) + } + + // Make kubelet report that it has sufficient disk space + if err := updateDiskSpacePolicy(kubelet, mockCadvisor, 500, 500, tc.rootFsAvail, tc.dockerFsAvail, 100, 100); err != nil { + t.Fatalf("can't update disk space manager: %v", err) + } + + if err := kubelet.updateNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + actions := kubeClient.Actions() + if len(actions) != 2 { + t.Errorf("%d. unexpected actions: %v", tcIdx, actions) + } + updateAction, ok := actions[1].(testclient.UpdateAction) + if !ok { + t.Errorf("%d. unexpected action type. expected UpdateAction, got %#v", tcIdx, actions[1]) + } + updatedNode, ok := updateAction.GetObject().(*api.Node) + if !ok { + t.Errorf("%d. unexpected object type", tcIdx) + } + kubeClient.ClearActions() + + var oodCondition api.NodeCondition + for i, cond := range updatedNode.Status.Conditions { + if cond.Type == api.NodeOutOfDisk { + oodCondition = updatedNode.Status.Conditions[i] + } + } + + if !reflect.DeepEqual(tc.expected, oodCondition) { + t.Errorf("%d.\nwant \n%v\n, got \n%v", tcIdx, tc.expected, oodCondition) + } + } +} + +func TestUpdateNodeStatusWithRuntimeStateError(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + clock := testKubelet.fakeClock + kubeClient := testKubelet.fakeKubeClient + kubeClient.ReactionChain = fake.NewSimpleClientset(&api.NodeList{Items: []api.Node{ + {ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}}, + }}).ReactionChain + mockCadvisor := testKubelet.fakeCadvisor + mockCadvisor.On("Start").Return(nil) + machineInfo := &cadvisorapi.MachineInfo{ + MachineID: "123", + SystemUUID: "abc", + BootID: "1b3", + NumCores: 2, + MemoryCapacity: 10E9, + } + mockCadvisor.On("MachineInfo").Return(machineInfo, nil) + versionInfo := &cadvisorapi.VersionInfo{ + KernelVersion: "3.16.0-0.bpo.4-amd64", + ContainerOsVersion: "Debian GNU/Linux 7 (wheezy)", + } + mockCadvisor.On("VersionInfo").Return(versionInfo, nil) + + // Make kubelet report that it has sufficient disk space. + if err := updateDiskSpacePolicy(kubelet, mockCadvisor, 500, 500, 200, 200, 100, 100); err != nil { + t.Fatalf("can't update disk space manager: %v", err) + } + + expectedNode := &api.Node{ + ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}, + Spec: api.NodeSpec{}, + Status: api.NodeStatus{ + Conditions: []api.NodeCondition{ + { + Type: api.NodeOutOfDisk, + Status: api.ConditionFalse, + Reason: "KubeletHasSufficientDisk", + Message: "kubelet has sufficient disk space available", + LastHeartbeatTime: unversioned.Time{}, + LastTransitionTime: unversioned.Time{}, + }, + {}, //placeholder + }, + NodeInfo: api.NodeSystemInfo{ + MachineID: "123", + SystemUUID: "abc", + BootID: "1b3", + KernelVersion: "3.16.0-0.bpo.4-amd64", + OSImage: "Debian GNU/Linux 7 (wheezy)", + ContainerRuntimeVersion: "test://1.5.0", + KubeletVersion: version.Get().String(), + KubeProxyVersion: version.Get().String(), + }, + Capacity: api.ResourceList{ + api.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(10E9, resource.BinarySI), + api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI), + }, + Allocatable: api.ResourceList{ + api.ResourceCPU: *resource.NewMilliQuantity(1800, resource.DecimalSI), + api.ResourceMemory: *resource.NewQuantity(9900E6, resource.BinarySI), + api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI), + }, + Addresses: []api.NodeAddress{ + {Type: api.NodeLegacyHostIP, Address: "127.0.0.1"}, + {Type: api.NodeInternalIP, Address: "127.0.0.1"}, + }, + Images: []api.ContainerImage{ + { + Names: []string{"gcr.io/google_containers:v1", "gcr.io/google_containers:v2"}, + SizeBytes: 123, + }, + { + Names: []string{"gcr.io/google_containers:v3", "gcr.io/google_containers:v4"}, + SizeBytes: 456, + }, + }, + }, + } + + checkNodeStatus := func(status api.ConditionStatus, reason, message string) { + kubeClient.ClearActions() + if err := kubelet.updateNodeStatus(); err != nil { + t.Errorf("unexpected error: %v", err) + } + actions := kubeClient.Actions() + if len(actions) != 2 { + t.Fatalf("unexpected actions: %v", actions) + } + if !actions[1].Matches("update", "nodes") || actions[1].GetSubresource() != "status" { + t.Fatalf("unexpected actions: %v", actions) + } + updatedNode, ok := actions[1].(testclient.UpdateAction).GetObject().(*api.Node) + if !ok { + t.Errorf("unexpected action type. expected UpdateAction, got %#v", actions[1]) + } + + for i, cond := range updatedNode.Status.Conditions { + if cond.LastHeartbeatTime.IsZero() { + t.Errorf("unexpected zero last probe timestamp") + } + if cond.LastTransitionTime.IsZero() { + t.Errorf("unexpected zero last transition timestamp") + } + updatedNode.Status.Conditions[i].LastHeartbeatTime = unversioned.Time{} + updatedNode.Status.Conditions[i].LastTransitionTime = unversioned.Time{} + } + + // Version skew workaround. See: https://github.com/kubernetes/kubernetes/issues/16961 + if updatedNode.Status.Conditions[len(updatedNode.Status.Conditions)-1].Type != api.NodeReady { + t.Errorf("unexpected node condition order. NodeReady should be last.") + } + expectedNode.Status.Conditions[1] = api.NodeCondition{ + Type: api.NodeReady, + Status: status, + Reason: reason, + Message: message, + LastHeartbeatTime: unversioned.Time{}, + LastTransitionTime: unversioned.Time{}, + } + if !api.Semantic.DeepEqual(expectedNode, updatedNode) { + t.Errorf("unexpected objects: %s", diff.ObjectDiff(expectedNode, updatedNode)) + } + } + + readyMessage := "kubelet is posting ready status" + downMessage := "container runtime is down" + + // Should report kubelet not ready if the runtime check is out of date + clock.SetTime(time.Now().Add(-maxWaitForContainerRuntime)) + kubelet.updateRuntimeUp() + checkNodeStatus(api.ConditionFalse, "KubeletNotReady", downMessage) + + // Should report kubelet ready if the runtime check is updated + clock.SetTime(time.Now()) + kubelet.updateRuntimeUp() + checkNodeStatus(api.ConditionTrue, "KubeletReady", readyMessage) + + // Should report kubelet not ready if the runtime check is out of date + clock.SetTime(time.Now().Add(-maxWaitForContainerRuntime)) + kubelet.updateRuntimeUp() + checkNodeStatus(api.ConditionFalse, "KubeletNotReady", downMessage) + + // Should report kubelet not ready if the runtime check failed + fakeRuntime := testKubelet.fakeRuntime + // Inject error into fake runtime status check, node should be NotReady + fakeRuntime.StatusErr = fmt.Errorf("injected runtime status error") + clock.SetTime(time.Now()) + kubelet.updateRuntimeUp() + checkNodeStatus(api.ConditionFalse, "KubeletNotReady", downMessage) +} + +func TestUpdateNodeStatusError(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + // No matching node for the kubelet + testKubelet.fakeKubeClient.ReactionChain = fake.NewSimpleClientset(&api.NodeList{Items: []api.Node{}}).ReactionChain + + if err := kubelet.updateNodeStatus(); err == nil { + t.Errorf("unexpected non error: %v", err) + } + if len(testKubelet.fakeKubeClient.Actions()) != nodeStatusUpdateRetry { + t.Errorf("unexpected actions: %v", testKubelet.fakeKubeClient.Actions()) + } +} + +func TestCreateMirrorPod(t *testing.T) { + for _, updateType := range []kubetypes.SyncPodType{kubetypes.SyncPodCreate, kubetypes.SyncPodUpdate} { + testKubelet := newTestKubelet(t) + kl := testKubelet.kubelet + manager := testKubelet.fakeMirrorClient + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "bar", + Namespace: "foo", + Annotations: map[string]string{ + kubetypes.ConfigSourceAnnotationKey: "file", + }, + }, + } + pods := []*api.Pod{pod} + kl.podManager.SetPods(pods) + err := kl.syncPod(pod, nil, &kubecontainer.PodStatus{}, updateType) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + podFullName := kubecontainer.GetPodFullName(pod) + if !manager.HasPod(podFullName) { + t.Errorf("expected mirror pod %q to be created", podFullName) + } + if manager.NumOfPods() != 1 || !manager.HasPod(podFullName) { + t.Errorf("expected one mirror pod %q, got %v", podFullName, manager.GetPods()) + } + } +} + +func TestDeleteOutdatedMirrorPod(t *testing.T) { + testKubelet := newTestKubelet(t) + testKubelet.fakeCadvisor.On("Start").Return(nil) + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + testKubelet.fakeCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + testKubelet.fakeCadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + + kl := testKubelet.kubelet + manager := testKubelet.fakeMirrorClient + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "ns", + Annotations: map[string]string{ + kubetypes.ConfigSourceAnnotationKey: "file", + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "1234", Image: "foo"}, + }, + }, + } + // Mirror pod has an outdated spec. + mirrorPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "11111111", + Name: "foo", + Namespace: "ns", + Annotations: map[string]string{ + kubetypes.ConfigSourceAnnotationKey: "api", + kubetypes.ConfigMirrorAnnotationKey: "mirror", + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "1234", Image: "bar"}, + }, + }, + } + + pods := []*api.Pod{pod, mirrorPod} + kl.podManager.SetPods(pods) + err := kl.syncPod(pod, mirrorPod, &kubecontainer.PodStatus{}, kubetypes.SyncPodUpdate) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + name := kubecontainer.GetPodFullName(pod) + creates, deletes := manager.GetCounts(name) + if creates != 1 || deletes != 1 { + t.Errorf("expected 1 creation and 1 deletion of %q, got %d, %d", name, creates, deletes) + } +} + +func TestDeleteOrphanedMirrorPods(t *testing.T) { + testKubelet := newTestKubelet(t) + testKubelet.fakeCadvisor.On("Start").Return(nil) + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + testKubelet.fakeCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + testKubelet.fakeCadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + + kl := testKubelet.kubelet + manager := testKubelet.fakeMirrorClient + orphanPods := []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "pod1", + Namespace: "ns", + Annotations: map[string]string{ + kubetypes.ConfigSourceAnnotationKey: "api", + kubetypes.ConfigMirrorAnnotationKey: "mirror", + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "12345679", + Name: "pod2", + Namespace: "ns", + Annotations: map[string]string{ + kubetypes.ConfigSourceAnnotationKey: "api", + kubetypes.ConfigMirrorAnnotationKey: "mirror", + }, + }, + }, + } + + kl.podManager.SetPods(orphanPods) + // Sync with an empty pod list to delete all mirror pods. + kl.HandlePodCleanups() + if manager.NumOfPods() != 0 { + t.Errorf("expected zero mirror pods, got %v", manager.GetPods()) + } + for _, pod := range orphanPods { + name := kubecontainer.GetPodFullName(pod) + creates, deletes := manager.GetCounts(name) + if creates != 0 || deletes != 1 { + t.Errorf("expected 0 creation and one deletion of %q, got %d, %d", name, creates, deletes) + } + } +} + +func TestGetContainerInfoForMirrorPods(t *testing.T) { + // pods contain one static and one mirror pod with the same name but + // different UIDs. + pods := []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + UID: "1234", + Name: "qux", + Namespace: "ns", + Annotations: map[string]string{ + kubetypes.ConfigSourceAnnotationKey: "file", + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "foo"}, + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "5678", + Name: "qux", + Namespace: "ns", + Annotations: map[string]string{ + kubetypes.ConfigSourceAnnotationKey: "api", + kubetypes.ConfigMirrorAnnotationKey: "mirror", + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "foo"}, + }, + }, + }, + } + + containerID := "ab2cdf" + containerPath := fmt.Sprintf("/docker/%v", containerID) + containerInfo := cadvisorapi.ContainerInfo{ + ContainerReference: cadvisorapi.ContainerReference{ + Name: containerPath, + }, + } + + testKubelet := newTestKubelet(t) + fakeRuntime := testKubelet.fakeRuntime + mockCadvisor := testKubelet.fakeCadvisor + cadvisorReq := &cadvisorapi.ContainerInfoRequest{} + mockCadvisor.On("DockerContainer", containerID, cadvisorReq).Return(containerInfo, nil) + kubelet := testKubelet.kubelet + + fakeRuntime.PodList = []*kubecontainer.Pod{ + { + ID: "1234", + Name: "qux", + Namespace: "ns", + Containers: []*kubecontainer.Container{ + { + Name: "foo", + ID: kubecontainer.ContainerID{Type: "test", ID: containerID}, + }, + }, + }, + } + + kubelet.podManager.SetPods(pods) + // Use the mirror pod UID to retrieve the stats. + stats, err := kubelet.GetContainerInfo("qux_ns", "5678", "foo", cadvisorReq) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if stats == nil { + t.Fatalf("stats should not be nil") + } + mockCadvisor.AssertExpectations(t) +} + +func TestHostNetworkAllowed(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + + capabilities.SetForTests(capabilities.Capabilities{ + PrivilegedSources: capabilities.PrivilegedSources{ + HostNetworkSources: []string{kubetypes.ApiserverSource, kubetypes.FileSource}, + }, + }) + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + Annotations: map[string]string{ + kubetypes.ConfigSourceAnnotationKey: kubetypes.FileSource, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "foo"}, + }, + SecurityContext: &api.PodSecurityContext{ + HostNetwork: true, + }, + }, + } + kubelet.podManager.SetPods([]*api.Pod{pod}) + err := kubelet.syncPod(pod, nil, &kubecontainer.PodStatus{}, kubetypes.SyncPodUpdate) + if err != nil { + t.Errorf("expected pod infra creation to succeed: %v", err) + } +} + +func TestHostNetworkDisallowed(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + + capabilities.SetForTests(capabilities.Capabilities{ + PrivilegedSources: capabilities.PrivilegedSources{ + HostNetworkSources: []string{}, + }, + }) + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + Annotations: map[string]string{ + kubetypes.ConfigSourceAnnotationKey: kubetypes.FileSource, + }, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "foo"}, + }, + SecurityContext: &api.PodSecurityContext{ + HostNetwork: true, + }, + }, + } + err := kubelet.syncPod(pod, nil, &kubecontainer.PodStatus{}, kubetypes.SyncPodUpdate) + if err == nil { + t.Errorf("expected pod infra creation to fail") + } +} + +func TestPrivilegeContainerAllowed(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + + capabilities.SetForTests(capabilities.Capabilities{ + AllowPrivileged: true, + }) + privileged := true + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "foo", SecurityContext: &api.SecurityContext{Privileged: &privileged}}, + }, + }, + } + kubelet.podManager.SetPods([]*api.Pod{pod}) + err := kubelet.syncPod(pod, nil, &kubecontainer.PodStatus{}, kubetypes.SyncPodUpdate) + if err != nil { + t.Errorf("expected pod infra creation to succeed: %v", err) + } +} + +func TestPrivilegeContainerDisallowed(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + + capabilities.SetForTests(capabilities.Capabilities{ + AllowPrivileged: false, + }) + privileged := true + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "foo", SecurityContext: &api.SecurityContext{Privileged: &privileged}}, + }, + }, + } + err := kubelet.syncPod(pod, nil, &kubecontainer.PodStatus{}, kubetypes.SyncPodUpdate) + if err == nil { + t.Errorf("expected pod infra creation to fail") + } +} + +func TestFilterOutTerminatedPods(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + pods := newTestPods(5) + pods[0].Status.Phase = api.PodFailed + pods[1].Status.Phase = api.PodSucceeded + pods[2].Status.Phase = api.PodRunning + pods[3].Status.Phase = api.PodPending + + expected := []*api.Pod{pods[2], pods[3], pods[4]} + kubelet.podManager.SetPods(pods) + actual := kubelet.filterOutTerminatedPods(pods) + if !reflect.DeepEqual(expected, actual) { + t.Errorf("expected %#v, got %#v", expected, actual) + } +} + +func TestRegisterExistingNodeWithApiserver(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + kubeClient := testKubelet.fakeKubeClient + kubeClient.AddReactor("create", "nodes", func(action core.Action) (bool, runtime.Object, error) { + // Return an error on create. + return true, &api.Node{}, &apierrors.StatusError{ + ErrStatus: unversioned.Status{Reason: unversioned.StatusReasonAlreadyExists}, + } + }) + kubeClient.AddReactor("get", "nodes", func(action core.Action) (bool, runtime.Object, error) { + // Return an existing (matching) node on get. + return true, &api.Node{ + ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}, + Spec: api.NodeSpec{ExternalID: testKubeletHostname}, + }, nil + }) + kubeClient.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf("no reaction implemented for %s", action) + }) + machineInfo := &cadvisorapi.MachineInfo{ + MachineID: "123", + SystemUUID: "abc", + BootID: "1b3", + NumCores: 2, + MemoryCapacity: 1024, + } + mockCadvisor := testKubelet.fakeCadvisor + mockCadvisor.On("MachineInfo").Return(machineInfo, nil) + versionInfo := &cadvisorapi.VersionInfo{ + KernelVersion: "3.16.0-0.bpo.4-amd64", + ContainerOsVersion: "Debian GNU/Linux 7 (wheezy)", + DockerVersion: "1.5.0", + } + mockCadvisor.On("VersionInfo").Return(versionInfo, nil) + mockCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{ + Usage: 400 * mb, + Capacity: 1000 * mb, + Available: 600 * mb, + }, nil) + mockCadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{ + Usage: 9 * mb, + Capacity: 10 * mb, + }, nil) + + done := make(chan struct{}) + go func() { + kubelet.registerWithApiserver() + done <- struct{}{} + }() + select { + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("timed out waiting for registration") + case <-done: + return + } +} + +func TestMakePortMappings(t *testing.T) { + tests := []struct { + container *api.Container + expectedPortMappings []kubecontainer.PortMapping + }{ + { + &api.Container{ + Name: "fooContainer", + Ports: []api.ContainerPort{ + { + Protocol: api.ProtocolTCP, + ContainerPort: 80, + HostPort: 8080, + HostIP: "127.0.0.1", + }, + { + Protocol: api.ProtocolTCP, + ContainerPort: 443, + HostPort: 4343, + HostIP: "192.168.0.1", + }, + { + Name: "foo", + Protocol: api.ProtocolUDP, + ContainerPort: 555, + HostPort: 5555, + }, + { + Name: "foo", // Duplicated, should be ignored. + Protocol: api.ProtocolUDP, + ContainerPort: 888, + HostPort: 8888, + }, + { + Protocol: api.ProtocolTCP, // Duplicated, should be ignored. + ContainerPort: 80, + HostPort: 8888, + }, + }, + }, + []kubecontainer.PortMapping{ + { + Name: "fooContainer-TCP:80", + Protocol: api.ProtocolTCP, + ContainerPort: 80, + HostPort: 8080, + HostIP: "127.0.0.1", + }, + { + Name: "fooContainer-TCP:443", + Protocol: api.ProtocolTCP, + ContainerPort: 443, + HostPort: 4343, + HostIP: "192.168.0.1", + }, + { + Name: "fooContainer-foo", + Protocol: api.ProtocolUDP, + ContainerPort: 555, + HostPort: 5555, + HostIP: "", + }, + }, + }, + } + + for i, tt := range tests { + actual := makePortMappings(tt.container) + if !reflect.DeepEqual(tt.expectedPortMappings, actual) { + t.Errorf("%d: Expected: %#v, saw: %#v", i, tt.expectedPortMappings, actual) + } + } +} + +func TestIsPodPastActiveDeadline(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + pods := newTestPods(5) + + exceededActiveDeadlineSeconds := int64(30) + notYetActiveDeadlineSeconds := int64(120) + now := unversioned.Now() + startTime := unversioned.NewTime(now.Time.Add(-1 * time.Minute)) + pods[0].Status.StartTime = &startTime + pods[0].Spec.ActiveDeadlineSeconds = &exceededActiveDeadlineSeconds + pods[1].Status.StartTime = &startTime + pods[1].Spec.ActiveDeadlineSeconds = ¬YetActiveDeadlineSeconds + tests := []struct { + pod *api.Pod + expected bool + }{{pods[0], true}, {pods[1], false}, {pods[2], false}, {pods[3], false}, {pods[4], false}} + + kubelet.podManager.SetPods(pods) + for i, tt := range tests { + actual := kubelet.pastActiveDeadline(tt.pod) + if actual != tt.expected { + t.Errorf("[%d] expected %#v, got %#v", i, tt.expected, actual) + } + } +} + +func TestSyncPodsSetStatusToFailedForPodsThatRunTooLong(t *testing.T) { + testKubelet := newTestKubelet(t) + fakeRuntime := testKubelet.fakeRuntime + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + kubelet := testKubelet.kubelet + + now := unversioned.Now() + startTime := unversioned.NewTime(now.Time.Add(-1 * time.Minute)) + exceededActiveDeadlineSeconds := int64(30) + + pods := []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "bar", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "foo"}, + }, + ActiveDeadlineSeconds: &exceededActiveDeadlineSeconds, + }, + Status: api.PodStatus{ + StartTime: &startTime, + }, + }, + } + + fakeRuntime.PodList = []*kubecontainer.Pod{ + { + ID: "12345678", + Name: "bar", + Namespace: "new", + Containers: []*kubecontainer.Container{ + {Name: "foo"}, + }, + }, + } + + // Let the pod worker sets the status to fail after this sync. + kubelet.HandlePodUpdates(pods) + status, found := kubelet.statusManager.GetPodStatus(pods[0].UID) + if !found { + t.Errorf("expected to found status for pod %q", pods[0].UID) + } + if status.Phase != api.PodFailed { + t.Fatalf("expected pod status %q, ot %q.", api.PodFailed, status.Phase) + } +} + +func TestSyncPodsDoesNotSetPodsThatDidNotRunTooLongToFailed(t *testing.T) { + testKubelet := newTestKubelet(t) + fakeRuntime := testKubelet.fakeRuntime + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + kubelet := testKubelet.kubelet + + now := unversioned.Now() + startTime := unversioned.NewTime(now.Time.Add(-1 * time.Minute)) + exceededActiveDeadlineSeconds := int64(300) + + pods := []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "bar", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "foo"}, + }, + ActiveDeadlineSeconds: &exceededActiveDeadlineSeconds, + }, + Status: api.PodStatus{ + StartTime: &startTime, + }, + }, + } + + fakeRuntime.PodList = []*kubecontainer.Pod{ + { + ID: "12345678", + Name: "bar", + Namespace: "new", + Containers: []*kubecontainer.Container{ + {Name: "foo"}, + }, + }, + } + + kubelet.podManager.SetPods(pods) + kubelet.HandlePodUpdates(pods) + status, found := kubelet.statusManager.GetPodStatus(pods[0].UID) + if !found { + t.Errorf("expected to found status for pod %q", pods[0].UID) + } + if status.Phase == api.PodFailed { + t.Fatalf("expected pod status to not be %q", status.Phase) + } +} + +func TestDeletePodDirsForDeletedPods(t *testing.T) { + testKubelet := newTestKubelet(t) + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + testKubelet.fakeCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + testKubelet.fakeCadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + kl := testKubelet.kubelet + pods := []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "pod1", + Namespace: "ns", + }, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "12345679", + Name: "pod2", + Namespace: "ns", + }, + }, + } + + kl.podManager.SetPods(pods) + // Sync to create pod directories. + kl.HandlePodSyncs(kl.podManager.GetPods()) + for i := range pods { + if !dirExists(kl.getPodDir(pods[i].UID)) { + t.Errorf("expected directory to exist for pod %d", i) + } + } + + // Pod 1 has been deleted and no longer exists. + kl.podManager.SetPods([]*api.Pod{pods[0]}) + kl.HandlePodCleanups() + if !dirExists(kl.getPodDir(pods[0].UID)) { + t.Errorf("expected directory to exist for pod 0") + } + if dirExists(kl.getPodDir(pods[1].UID)) { + t.Errorf("expected directory to be deleted for pod 1") + } +} + +func syncAndVerifyPodDir(t *testing.T, testKubelet *TestKubelet, pods []*api.Pod, podsToCheck []*api.Pod, shouldExist bool) { + kl := testKubelet.kubelet + + kl.podManager.SetPods(pods) + kl.HandlePodSyncs(pods) + kl.HandlePodCleanups() + for i, pod := range podsToCheck { + exist := dirExists(kl.getPodDir(pod.UID)) + if shouldExist && !exist { + t.Errorf("expected directory to exist for pod %d", i) + } else if !shouldExist && exist { + t.Errorf("expected directory to be removed for pod %d", i) + } + } +} + +func TestDoesNotDeletePodDirsForTerminatedPods(t *testing.T) { + testKubelet := newTestKubelet(t) + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + testKubelet.fakeCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + testKubelet.fakeCadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + kl := testKubelet.kubelet + pods := []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "pod1", + Namespace: "ns", + }, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "12345679", + Name: "pod2", + Namespace: "ns", + }, + }, + { + ObjectMeta: api.ObjectMeta{ + UID: "12345680", + Name: "pod3", + Namespace: "ns", + }, + }, + } + + syncAndVerifyPodDir(t, testKubelet, pods, pods, true) + // Pod 1 failed, and pod 2 succeeded. None of the pod directories should be + // deleted. + kl.statusManager.SetPodStatus(pods[1], api.PodStatus{Phase: api.PodFailed}) + kl.statusManager.SetPodStatus(pods[2], api.PodStatus{Phase: api.PodSucceeded}) + syncAndVerifyPodDir(t, testKubelet, pods, pods, true) +} + +func TestDoesNotDeletePodDirsIfContainerIsRunning(t *testing.T) { + testKubelet := newTestKubelet(t) + testKubelet.fakeCadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + testKubelet.fakeCadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + testKubelet.fakeCadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{}, nil) + runningPod := &kubecontainer.Pod{ + ID: "12345678", + Name: "pod1", + Namespace: "ns", + } + apiPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: runningPod.ID, + Name: runningPod.Name, + Namespace: runningPod.Namespace, + }, + } + // Sync once to create pod directory; confirm that the pod directory has + // already been created. + pods := []*api.Pod{apiPod} + syncAndVerifyPodDir(t, testKubelet, pods, []*api.Pod{apiPod}, true) + + // Pretend the pod is deleted from apiserver, but is still active on the node. + // The pod directory should not be removed. + pods = []*api.Pod{} + testKubelet.fakeRuntime.PodList = []*kubecontainer.Pod{runningPod} + syncAndVerifyPodDir(t, testKubelet, pods, []*api.Pod{apiPod}, true) + + // The pod is deleted and also not active on the node. The pod directory + // should be removed. + pods = []*api.Pod{} + testKubelet.fakeRuntime.PodList = []*kubecontainer.Pod{} + syncAndVerifyPodDir(t, testKubelet, pods, []*api.Pod{apiPod}, false) +} + +func TestCleanupBandwidthLimits(t *testing.T) { + // TODO(random-liu): We removed the test case for pod status not cached here. We should add a higher + // layer status getter function and test that function instead. + tests := []struct { + status *api.PodStatus + pods []*api.Pod + inputCIDRs []string + expectResetCIDRs []string + name string + }{ + { + status: &api.PodStatus{ + PodIP: "1.2.3.4", + Phase: api.PodRunning, + }, + pods: []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Annotations: map[string]string{ + "kubernetes.io/ingress-bandwidth": "10M", + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + Name: "bar", + }, + }, + }, + inputCIDRs: []string{"1.2.3.4/32", "2.3.4.5/32", "5.6.7.8/32"}, + expectResetCIDRs: []string{"2.3.4.5/32", "5.6.7.8/32"}, + name: "pod running", + }, + { + status: &api.PodStatus{ + PodIP: "1.2.3.4", + Phase: api.PodFailed, + }, + pods: []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Annotations: map[string]string{ + "kubernetes.io/ingress-bandwidth": "10M", + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{ + Name: "bar", + }, + }, + }, + inputCIDRs: []string{"1.2.3.4/32", "2.3.4.5/32", "5.6.7.8/32"}, + expectResetCIDRs: []string{"1.2.3.4/32", "2.3.4.5/32", "5.6.7.8/32"}, + name: "pod not running", + }, + { + status: &api.PodStatus{ + PodIP: "1.2.3.4", + Phase: api.PodFailed, + }, + pods: []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + Name: "foo", + }, + }, + { + ObjectMeta: api.ObjectMeta{ + Name: "bar", + }, + }, + }, + inputCIDRs: []string{"1.2.3.4/32", "2.3.4.5/32", "5.6.7.8/32"}, + expectResetCIDRs: []string{"1.2.3.4/32", "2.3.4.5/32", "5.6.7.8/32"}, + name: "no bandwidth limits", + }, + } + for _, test := range tests { + shaper := &bandwidth.FakeShaper{ + CIDRs: test.inputCIDRs, + } + + testKube := newTestKubelet(t) + testKube.kubelet.shaper = shaper + + for _, pod := range test.pods { + testKube.kubelet.statusManager.SetPodStatus(pod, *test.status) + } + + err := testKube.kubelet.cleanupBandwidthLimits(test.pods) + if err != nil { + t.Errorf("unexpected error: %v (%s)", test.name, err) + } + if !reflect.DeepEqual(shaper.ResetCIDRs, test.expectResetCIDRs) { + t.Errorf("[%s]\nexpected: %v, saw: %v", test.name, test.expectResetCIDRs, shaper.ResetCIDRs) + } + } +} + +func TestExtractBandwidthResources(t *testing.T) { + four, _ := resource.ParseQuantity("4M") + ten, _ := resource.ParseQuantity("10M") + twenty, _ := resource.ParseQuantity("20M") + tests := []struct { + pod *api.Pod + expectedIngress *resource.Quantity + expectedEgress *resource.Quantity + expectError bool + }{ + { + pod: &api.Pod{}, + }, + { + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{ + "kubernetes.io/ingress-bandwidth": "10M", + }, + }, + }, + expectedIngress: ten, + }, + { + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{ + "kubernetes.io/egress-bandwidth": "10M", + }, + }, + }, + expectedEgress: ten, + }, + { + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{ + "kubernetes.io/ingress-bandwidth": "4M", + "kubernetes.io/egress-bandwidth": "20M", + }, + }, + }, + expectedIngress: four, + expectedEgress: twenty, + }, + { + pod: &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Annotations: map[string]string{ + "kubernetes.io/ingress-bandwidth": "foo", + }, + }, + }, + expectError: true, + }, + } + for _, test := range tests { + ingress, egress, err := extractBandwidthResources(test.pod) + if test.expectError { + if err == nil { + t.Errorf("unexpected non-error") + } + continue + } + if err != nil { + t.Errorf("unexpected error: %v", err) + continue + } + if !reflect.DeepEqual(ingress, test.expectedIngress) { + t.Errorf("expected: %v, saw: %v", ingress, test.expectedIngress) + } + if !reflect.DeepEqual(egress, test.expectedEgress) { + t.Errorf("expected: %v, saw: %v", egress, test.expectedEgress) + } + } +} + +func TestGetPodsToSync(t *testing.T) { + testKubelet := newTestKubelet(t) + kubelet := testKubelet.kubelet + pods := newTestPods(5) + podUIDs := []types.UID{} + for _, pod := range pods { + podUIDs = append(podUIDs, pod.UID) + } + + exceededActiveDeadlineSeconds := int64(30) + notYetActiveDeadlineSeconds := int64(120) + now := unversioned.Now() + startTime := unversioned.NewTime(now.Time.Add(-1 * time.Minute)) + pods[0].Status.StartTime = &startTime + pods[0].Spec.ActiveDeadlineSeconds = &exceededActiveDeadlineSeconds + pods[1].Status.StartTime = &startTime + pods[1].Spec.ActiveDeadlineSeconds = ¬YetActiveDeadlineSeconds + pods[2].Status.StartTime = &startTime + pods[2].Spec.ActiveDeadlineSeconds = &exceededActiveDeadlineSeconds + + kubelet.podManager.SetPods(pods) + kubelet.workQueue.Enqueue(pods[2].UID, 0) + kubelet.workQueue.Enqueue(pods[3].UID, 0) + kubelet.workQueue.Enqueue(pods[4].UID, time.Hour) + + expectedPodsUID := []types.UID{pods[0].UID, pods[2].UID, pods[3].UID} + + podsToSync := kubelet.getPodsToSync() + + if len(podsToSync) == len(expectedPodsUID) { + var rightNum int + for _, podUID := range expectedPodsUID { + for _, podToSync := range podsToSync { + if podToSync.UID == podUID { + rightNum++ + break + } + } + } + if rightNum != len(expectedPodsUID) { + // Just for report error + podsToSyncUID := []types.UID{} + for _, podToSync := range podsToSync { + podsToSyncUID = append(podsToSyncUID, podToSync.UID) + } + t.Errorf("expected pods %v to sync, got %v", expectedPodsUID, podsToSyncUID) + } + + } else { + t.Errorf("expected %d pods to sync, got %d", 3, len(podsToSync)) + } +} + +// TODO(random-liu): Add unit test for convertStatusToAPIStatus (issue #20478) diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/leaky/leaky.go b/vendor/k8s.io/kubernetes/pkg/kubelet/leaky/leaky.go new file mode 100644 index 000000000..dd4e6efb0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/leaky/leaky.go @@ -0,0 +1,25 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 leaky holds bits of kubelet that should be internal but have leaked +// out through bad abstractions. TODO: delete all of this. +package leaky + +const ( + // This is used in a few places outside of Kubelet, such as indexing + // into the container info. + PodInfraContainerName = "POD" +) diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/lifecycle/doc.go b/vendor/k8s.io/kubernetes/pkg/kubelet/lifecycle/doc.go new file mode 100644 index 000000000..d10db1bca --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/lifecycle/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// Handlers for pod lifecycle events. +package lifecycle diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/lifecycle/handlers.go b/vendor/k8s.io/kubernetes/pkg/kubelet/lifecycle/handlers.go new file mode 100644 index 000000000..de7a76526 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/lifecycle/handlers.go @@ -0,0 +1,113 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 lifecycle + +import ( + "fmt" + "net" + "strconv" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/intstr" +) + +type HandlerRunner struct { + httpGetter kubetypes.HttpGetter + commandRunner kubecontainer.ContainerCommandRunner + containerManager podStatusProvider +} + +type podStatusProvider interface { + GetPodStatus(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) +} + +func NewHandlerRunner(httpGetter kubetypes.HttpGetter, commandRunner kubecontainer.ContainerCommandRunner, containerManager podStatusProvider) kubecontainer.HandlerRunner { + return &HandlerRunner{ + httpGetter: httpGetter, + commandRunner: commandRunner, + containerManager: containerManager, + } +} + +func (hr *HandlerRunner) Run(containerID kubecontainer.ContainerID, pod *api.Pod, container *api.Container, handler *api.Handler) error { + switch { + case handler.Exec != nil: + _, err := hr.commandRunner.RunInContainer(containerID, handler.Exec.Command) + return err + case handler.HTTPGet != nil: + return hr.runHTTPHandler(pod, container, handler) + default: + err := fmt.Errorf("Invalid handler: %v", handler) + glog.Errorf("Cannot run handler: %v", err) + return err + } +} + +// resolvePort attempts to turn a IntOrString port reference into a concrete port number. +// If portReference has an int value, it is treated as a literal, and simply returns that value. +// If portReference is a string, an attempt is first made to parse it as an integer. If that fails, +// an attempt is made to find a port with the same name in the container spec. +// If a port with the same name is found, it's ContainerPort value is returned. If no matching +// port is found, an error is returned. +func resolvePort(portReference intstr.IntOrString, container *api.Container) (int, error) { + if portReference.Type == intstr.Int { + return portReference.IntValue(), nil + } + portName := portReference.StrVal + port, err := strconv.Atoi(portName) + if err == nil { + return port, nil + } + for _, portSpec := range container.Ports { + if portSpec.Name == portName { + return portSpec.ContainerPort, nil + } + } + return -1, fmt.Errorf("couldn't find port: %v in %v", portReference, container) +} + +func (hr *HandlerRunner) runHTTPHandler(pod *api.Pod, container *api.Container, handler *api.Handler) error { + host := handler.HTTPGet.Host + if len(host) == 0 { + status, err := hr.containerManager.GetPodStatus(pod.UID, pod.Name, pod.Namespace) + if err != nil { + glog.Errorf("Unable to get pod info, event handlers may be invalid.") + return err + } + if status.IP == "" { + return fmt.Errorf("failed to find networking container: %v", status) + } + host = status.IP + } + var port int + if handler.HTTPGet.Port.Type == intstr.String && len(handler.HTTPGet.Port.StrVal) == 0 { + port = 80 + } else { + var err error + port, err = resolvePort(handler.HTTPGet.Port, container) + if err != nil { + return err + } + } + url := fmt.Sprintf("http://%s/%s", net.JoinHostPort(host, strconv.Itoa(port)), handler.HTTPGet.Path) + _, err := hr.httpGetter.Get(url) + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/lifecycle/handlers_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/lifecycle/handlers_test.go new file mode 100644 index 000000000..4797db2f3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/lifecycle/handlers_test.go @@ -0,0 +1,190 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 lifecycle + +import ( + "io" + "net/http" + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/util/intstr" +) + +func TestResolvePortInt(t *testing.T) { + expected := 80 + port, err := resolvePort(intstr.FromInt(expected), &api.Container{}) + if port != expected { + t.Errorf("expected: %d, saw: %d", expected, port) + } + if err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func TestResolvePortString(t *testing.T) { + expected := 80 + name := "foo" + container := &api.Container{ + Ports: []api.ContainerPort{ + {Name: name, ContainerPort: expected}, + }, + } + port, err := resolvePort(intstr.FromString(name), container) + if port != expected { + t.Errorf("expected: %d, saw: %d", expected, port) + } + if err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func TestResolvePortStringUnknown(t *testing.T) { + expected := 80 + name := "foo" + container := &api.Container{ + Ports: []api.ContainerPort{ + {Name: "bar", ContainerPort: expected}, + }, + } + port, err := resolvePort(intstr.FromString(name), container) + if port != -1 { + t.Errorf("expected: -1, saw: %d", port) + } + if err == nil { + t.Error("unexpected non-error") + } +} + +type fakeContainerCommandRunner struct { + Cmd []string + ID kubecontainer.ContainerID +} + +func (f *fakeContainerCommandRunner) RunInContainer(id kubecontainer.ContainerID, cmd []string) ([]byte, error) { + f.Cmd = cmd + f.ID = id + return []byte{}, nil +} + +func (f *fakeContainerCommandRunner) ExecInContainer(id kubecontainer.ContainerID, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error { + return nil +} + +func (f *fakeContainerCommandRunner) PortForward(pod *kubecontainer.Pod, port uint16, stream io.ReadWriteCloser) error { + return nil +} + +func TestRunHandlerExec(t *testing.T) { + fakeCommandRunner := fakeContainerCommandRunner{} + handlerRunner := NewHandlerRunner(&fakeHTTP{}, &fakeCommandRunner, nil) + + containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"} + containerName := "containerFoo" + + container := api.Container{ + Name: containerName, + Lifecycle: &api.Lifecycle{ + PostStart: &api.Handler{ + Exec: &api.ExecAction{ + Command: []string{"ls", "-a"}, + }, + }, + }, + } + + pod := api.Pod{} + pod.ObjectMeta.Name = "podFoo" + pod.ObjectMeta.Namespace = "nsFoo" + pod.Spec.Containers = []api.Container{container} + err := handlerRunner.Run(containerID, &pod, &container, container.Lifecycle.PostStart) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if fakeCommandRunner.ID != containerID || + !reflect.DeepEqual(container.Lifecycle.PostStart.Exec.Command, fakeCommandRunner.Cmd) { + t.Errorf("unexpected commands: %v", fakeCommandRunner) + } +} + +type fakeHTTP struct { + url string + err error +} + +func (f *fakeHTTP) Get(url string) (*http.Response, error) { + f.url = url + return nil, f.err +} + +func TestRunHandlerHttp(t *testing.T) { + fakeHttp := fakeHTTP{} + handlerRunner := NewHandlerRunner(&fakeHttp, &fakeContainerCommandRunner{}, nil) + + containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"} + containerName := "containerFoo" + + container := api.Container{ + Name: containerName, + Lifecycle: &api.Lifecycle{ + PostStart: &api.Handler{ + HTTPGet: &api.HTTPGetAction{ + Host: "foo", + Port: intstr.FromInt(8080), + Path: "bar", + }, + }, + }, + } + pod := api.Pod{} + pod.ObjectMeta.Name = "podFoo" + pod.ObjectMeta.Namespace = "nsFoo" + pod.Spec.Containers = []api.Container{container} + err := handlerRunner.Run(containerID, &pod, &container, container.Lifecycle.PostStart) + + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if fakeHttp.url != "http://foo:8080/bar" { + t.Errorf("unexpected url: %s", fakeHttp.url) + } +} + +func TestRunHandlerNil(t *testing.T) { + handlerRunner := NewHandlerRunner(&fakeHTTP{}, &fakeContainerCommandRunner{}, nil) + containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"} + podName := "podFoo" + podNamespace := "nsFoo" + containerName := "containerFoo" + + container := api.Container{ + Name: containerName, + Lifecycle: &api.Lifecycle{ + PostStart: &api.Handler{}, + }, + } + pod := api.Pod{} + pod.ObjectMeta.Name = podName + pod.ObjectMeta.Namespace = podNamespace + pod.Spec.Containers = []api.Container{container} + err := handlerRunner.Run(containerID, &pod, &container, container.Lifecycle.PostStart) + if err == nil { + t.Errorf("expect error, but got nil") + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/metrics/metrics.go b/vendor/k8s.io/kubernetes/pkg/kubelet/metrics/metrics.go new file mode 100644 index 000000000..4c8a52159 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/metrics/metrics.go @@ -0,0 +1,200 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 metrics + +import ( + "sync" + "time" + + "github.com/golang/glog" + "github.com/prometheus/client_golang/prometheus" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" +) + +const ( + KubeletSubsystem = "kubelet" + PodWorkerLatencyKey = "pod_worker_latency_microseconds" + SyncPodsLatencyKey = "sync_pods_latency_microseconds" + PodStartLatencyKey = "pod_start_latency_microseconds" + PodStatusLatencyKey = "generate_pod_status_latency_microseconds" + ContainerManagerOperationsKey = "container_manager_latency_microseconds" + DockerOperationsKey = "docker_operations_latency_microseconds" + DockerErrorsKey = "docker_errors" + PodWorkerStartLatencyKey = "pod_worker_start_latency_microseconds" + PLEGRelistLatencyKey = "pleg_relist_latency_microseconds" + PLEGRelistIntervalKey = "pleg_relist_interval_microseconds" +) + +var ( + ContainersPerPodCount = prometheus.NewSummary( + prometheus.SummaryOpts{ + Subsystem: KubeletSubsystem, + Name: "containers_per_pod_count", + Help: "The number of containers per pod.", + }, + ) + PodWorkerLatency = prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Subsystem: KubeletSubsystem, + Name: PodWorkerLatencyKey, + Help: "Latency in microseconds to sync a single pod. Broken down by operation type: create, update, or sync", + }, + []string{"operation_type"}, + ) + SyncPodsLatency = prometheus.NewSummary( + prometheus.SummaryOpts{ + Subsystem: KubeletSubsystem, + Name: SyncPodsLatencyKey, + Help: "Latency in microseconds to sync all pods.", + }, + ) + PodStartLatency = prometheus.NewSummary( + prometheus.SummaryOpts{ + Subsystem: KubeletSubsystem, + Name: PodStartLatencyKey, + Help: "Latency in microseconds for a single pod to go from pending to running. Broken down by podname.", + }, + ) + PodStatusLatency = prometheus.NewSummary( + prometheus.SummaryOpts{ + Subsystem: KubeletSubsystem, + Name: PodStatusLatencyKey, + Help: "Latency in microseconds to generate status for a single pod.", + }, + ) + ContainerManagerLatency = prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Subsystem: KubeletSubsystem, + Name: ContainerManagerOperationsKey, + Help: "Latency in microseconds for container manager operations. Broken down by method.", + }, + []string{"operation_type"}, + ) + PodWorkerStartLatency = prometheus.NewSummary( + prometheus.SummaryOpts{ + Subsystem: KubeletSubsystem, + Name: PodWorkerStartLatencyKey, + Help: "Latency in microseconds from seeing a pod to starting a worker.", + }, + ) + DockerOperationsLatency = prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Subsystem: KubeletSubsystem, + Name: DockerOperationsKey, + Help: "Latency in microseconds of Docker operations. Broken down by operation type.", + }, + []string{"operation_type"}, + ) + DockerErrors = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: KubeletSubsystem, + Name: DockerErrorsKey, + Help: "Cumulative number of Docker errors by operation type.", + }, + []string{"operation_type"}, + ) + PLEGRelistLatency = prometheus.NewSummary( + prometheus.SummaryOpts{ + Subsystem: KubeletSubsystem, + Name: PLEGRelistLatencyKey, + Help: "Latency in microseconds for relisting pods in PLEG.", + }, + ) + PLEGRelistInterval = prometheus.NewSummary( + prometheus.SummaryOpts{ + Subsystem: KubeletSubsystem, + Name: PLEGRelistIntervalKey, + Help: "Interval in microseconds between relisting in PLEG.", + }, + ) +) + +var registerMetrics sync.Once + +// Register all metrics. +func Register(containerCache kubecontainer.RuntimeCache) { + // Register the metrics. + registerMetrics.Do(func() { + prometheus.MustRegister(PodWorkerLatency) + prometheus.MustRegister(PodStartLatency) + prometheus.MustRegister(PodStatusLatency) + prometheus.MustRegister(DockerOperationsLatency) + prometheus.MustRegister(ContainerManagerLatency) + prometheus.MustRegister(SyncPodsLatency) + prometheus.MustRegister(PodWorkerStartLatency) + prometheus.MustRegister(ContainersPerPodCount) + prometheus.MustRegister(DockerErrors) + prometheus.MustRegister(newPodAndContainerCollector(containerCache)) + prometheus.MustRegister(PLEGRelistLatency) + prometheus.MustRegister(PLEGRelistInterval) + }) +} + +// Gets the time since the specified start in microseconds. +func SinceInMicroseconds(start time.Time) float64 { + return float64(time.Since(start).Nanoseconds() / time.Microsecond.Nanoseconds()) +} + +func newPodAndContainerCollector(containerCache kubecontainer.RuntimeCache) *podAndContainerCollector { + return &podAndContainerCollector{ + containerCache: containerCache, + } +} + +// Custom collector for current pod and container counts. +type podAndContainerCollector struct { + // Cache for accessing information about running containers. + containerCache kubecontainer.RuntimeCache +} + +// TODO(vmarmol): Split by source? +var ( + runningPodCountDesc = prometheus.NewDesc( + prometheus.BuildFQName("", KubeletSubsystem, "running_pod_count"), + "Number of pods currently running", + nil, nil) + runningContainerCountDesc = prometheus.NewDesc( + prometheus.BuildFQName("", KubeletSubsystem, "running_container_count"), + "Number of containers currently running", + nil, nil) +) + +func (pc *podAndContainerCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- runningPodCountDesc + ch <- runningContainerCountDesc +} + +func (pc *podAndContainerCollector) Collect(ch chan<- prometheus.Metric) { + runningPods, err := pc.containerCache.GetPods() + if err != nil { + glog.Warningf("Failed to get running container information while collecting metrics: %v", err) + return + } + + runningContainers := 0 + for _, p := range runningPods { + runningContainers += len(p.Containers) + } + ch <- prometheus.MustNewConstMetric( + runningPodCountDesc, + prometheus.GaugeValue, + float64(len(runningPods))) + ch <- prometheus.MustNewConstMetric( + runningContainerCountDesc, + prometheus.GaugeValue, + float64(runningContainers)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/network/cni/cni.go b/vendor/k8s.io/kubernetes/pkg/kubelet/network/cni/cni.go new file mode 100644 index 000000000..03edbbaf6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/network/cni/cni.go @@ -0,0 +1,206 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 cni + +import ( + "fmt" + "net" + "sort" + "strings" + + "github.com/appc/cni/libcni" + cnitypes "github.com/appc/cni/pkg/types" + "github.com/golang/glog" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/dockertools" + "k8s.io/kubernetes/pkg/kubelet/network" +) + +const ( + CNIPluginName = "cni" + DefaultNetDir = "/etc/cni/net.d" + DefaultCNIDir = "/opt/cni/bin" + VendorCNIDirTemplate = "%s/opt/%s/bin" +) + +type cniNetworkPlugin struct { + network.NoopNetworkPlugin + + defaultNetwork *cniNetwork + host network.Host +} + +type cniNetwork struct { + name string + NetworkConfig *libcni.NetworkConfig + CNIConfig *libcni.CNIConfig +} + +func probeNetworkPluginsWithVendorCNIDirPrefix(pluginDir, vendorCNIDirPrefix string) []network.NetworkPlugin { + configList := make([]network.NetworkPlugin, 0) + network, err := getDefaultCNINetwork(pluginDir, vendorCNIDirPrefix) + if err != nil { + return configList + } + return append(configList, &cniNetworkPlugin{defaultNetwork: network}) +} + +func ProbeNetworkPlugins(pluginDir string) []network.NetworkPlugin { + return probeNetworkPluginsWithVendorCNIDirPrefix(pluginDir, "") +} + +func getDefaultCNINetwork(pluginDir, vendorCNIDirPrefix string) (*cniNetwork, error) { + if pluginDir == "" { + pluginDir = DefaultNetDir + } + files, err := libcni.ConfFiles(pluginDir) + switch { + case err != nil: + return nil, err + case len(files) == 0: + return nil, fmt.Errorf("No networks found in %s", pluginDir) + } + + sort.Strings(files) + for _, confFile := range files { + conf, err := libcni.ConfFromFile(confFile) + if err != nil { + glog.Warningf("Error loading CNI config file %s: %v", confFile, err) + continue + } + // Search for vendor-specific plugins as well as default plugins in the CNI codebase. + vendorCNIDir := fmt.Sprintf(VendorCNIDirTemplate, vendorCNIDirPrefix, conf.Network.Type) + cninet := &libcni.CNIConfig{ + Path: []string{DefaultCNIDir, vendorCNIDir}, + } + network := &cniNetwork{name: conf.Network.Name, NetworkConfig: conf, CNIConfig: cninet} + return network, nil + } + return nil, fmt.Errorf("No valid networks found in %s", pluginDir) +} + +func (plugin *cniNetworkPlugin) Init(host network.Host) error { + plugin.host = host + return nil +} + +func (plugin *cniNetworkPlugin) Name() string { + return CNIPluginName +} + +func (plugin *cniNetworkPlugin) SetUpPod(namespace string, name string, id kubecontainer.DockerID) error { + runtime, ok := plugin.host.GetRuntime().(*dockertools.DockerManager) + if !ok { + return fmt.Errorf("CNI execution called on non-docker runtime") + } + netns, err := runtime.GetNetNS(id.ContainerID()) + if err != nil { + return err + } + + _, err = plugin.defaultNetwork.addToNetwork(name, namespace, id.ContainerID(), netns) + if err != nil { + glog.Errorf("Error while adding to cni network: %s", err) + return err + } + + return err +} + +func (plugin *cniNetworkPlugin) TearDownPod(namespace string, name string, id kubecontainer.DockerID) error { + runtime, ok := plugin.host.GetRuntime().(*dockertools.DockerManager) + if !ok { + return fmt.Errorf("CNI execution called on non-docker runtime") + } + netns, err := runtime.GetNetNS(id.ContainerID()) + if err != nil { + return err + } + + return plugin.defaultNetwork.deleteFromNetwork(name, namespace, id.ContainerID(), netns) +} + +// TODO: Use the addToNetwork function to obtain the IP of the Pod. That will assume idempotent ADD call to the plugin. +// Also fix the runtime's call to Status function to be done only in the case that the IP is lost, no need to do periodic calls +func (plugin *cniNetworkPlugin) Status(namespace string, name string, id kubecontainer.DockerID) (*network.PodNetworkStatus, error) { + runtime, ok := plugin.host.GetRuntime().(*dockertools.DockerManager) + if !ok { + return nil, fmt.Errorf("CNI execution called on non-docker runtime") + } + ipStr, err := runtime.GetContainerIP(string(id), network.DefaultInterfaceName) + if err != nil { + return nil, err + } + ip, _, err := net.ParseCIDR(strings.Trim(ipStr, "\n")) + if err != nil { + return nil, err + } + return &network.PodNetworkStatus{IP: ip}, nil +} + +func (network *cniNetwork) addToNetwork(podName string, podNamespace string, podInfraContainerID kubecontainer.ContainerID, podNetnsPath string) (*cnitypes.Result, error) { + rt, err := buildCNIRuntimeConf(podName, podNamespace, podInfraContainerID, podNetnsPath) + if err != nil { + glog.Errorf("Error adding network: %v", err) + return nil, err + } + + netconf, cninet := network.NetworkConfig, network.CNIConfig + glog.V(4).Infof("About to run with conf.Network.Type=%v, c.Path=%v", netconf.Network.Type, cninet.Path) + res, err := cninet.AddNetwork(netconf, rt) + if err != nil { + glog.Errorf("Error adding network: %v", err) + return nil, err + } + + return res, nil +} + +func (network *cniNetwork) deleteFromNetwork(podName string, podNamespace string, podInfraContainerID kubecontainer.ContainerID, podNetnsPath string) error { + rt, err := buildCNIRuntimeConf(podName, podNamespace, podInfraContainerID, podNetnsPath) + if err != nil { + glog.Errorf("Error deleting network: %v", err) + return err + } + + netconf, cninet := network.NetworkConfig, network.CNIConfig + glog.V(4).Infof("About to run with conf.Network.Type=%v, c.Path=%v", netconf.Network.Type, cninet.Path) + err = cninet.DelNetwork(netconf, rt) + if err != nil { + glog.Errorf("Error deleting network: %v", err) + return err + } + return nil +} + +func buildCNIRuntimeConf(podName string, podNs string, podInfraContainerID kubecontainer.ContainerID, podNetnsPath string) (*libcni.RuntimeConf, error) { + glog.V(4).Infof("Got netns path %v", podNetnsPath) + glog.V(4).Infof("Using netns path %v", podNs) + + rt := &libcni.RuntimeConf{ + ContainerID: podInfraContainerID.ID, + NetNS: podNetnsPath, + IfName: network.DefaultInterfaceName, + Args: [][2]string{ + {"K8S_POD_NAMESPACE", podNs}, + {"K8S_POD_NAME", podName}, + {"K8S_POD_INFRA_CONTAINER_ID", podInfraContainerID.ID}, + }, + } + + return rt, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/network/cni/cni_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/network/cni/cni_test.go new file mode 100644 index 000000000..3d2179a0d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/network/cni/cni_test.go @@ -0,0 +1,207 @@ +// +build linux + +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 cni + +import ( + "bytes" + "fmt" + "io/ioutil" + "math/rand" + "os" + "path" + "testing" + "text/template" + + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + + docker "github.com/fsouza/go-dockerclient" + cadvisorapi "github.com/google/cadvisor/info/v1" + + "k8s.io/kubernetes/cmd/kubelet/app/options" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" + "k8s.io/kubernetes/pkg/kubelet/dockertools" + "k8s.io/kubernetes/pkg/kubelet/network" + nettest "k8s.io/kubernetes/pkg/kubelet/network/testing" + proberesults "k8s.io/kubernetes/pkg/kubelet/prober/results" + utiltesting "k8s.io/kubernetes/pkg/util/testing" +) + +func installPluginUnderTest(t *testing.T, testVendorCNIDirPrefix, testNetworkConfigPath, vendorName string, plugName string) { + pluginDir := path.Join(testNetworkConfigPath, plugName) + err := os.MkdirAll(pluginDir, 0777) + if err != nil { + t.Fatalf("Failed to create plugin config dir: %v", err) + } + pluginConfig := path.Join(pluginDir, plugName+".conf") + f, err := os.Create(pluginConfig) + if err != nil { + t.Fatalf("Failed to install plugin") + } + networkConfig := fmt.Sprintf("{ \"name\": \"%s\", \"type\": \"%s\" }", plugName, vendorName) + + _, err = f.WriteString(networkConfig) + if err != nil { + t.Fatalf("Failed to write network config file (%v)", err) + } + f.Close() + + vendorCNIDir := fmt.Sprintf(VendorCNIDirTemplate, testVendorCNIDirPrefix, vendorName) + err = os.MkdirAll(vendorCNIDir, 0777) + if err != nil { + t.Fatalf("Failed to create plugin dir: %v", err) + } + pluginExec := path.Join(vendorCNIDir, vendorName) + f, err = os.Create(pluginExec) + + const execScriptTempl = `#!/bin/bash +read ignore +env > {{.OutputEnv}} +echo "%@" >> {{.OutputEnv}} +export $(echo ${CNI_ARGS} | sed 's/;/ /g') &> /dev/null +mkdir -p {{.OutputDir}} &> /dev/null +echo -n "$CNI_COMMAND $CNI_NETNS $K8S_POD_NAMESPACE $K8S_POD_NAME $K8S_POD_INFRA_CONTAINER_ID" >& {{.OutputFile}} +echo -n "{ \"ip4\": { \"ip\": \"10.1.0.23/24\" } }" +` + execTemplateData := &map[string]interface{}{ + "OutputFile": path.Join(pluginDir, plugName+".out"), + "OutputEnv": path.Join(pluginDir, plugName+".env"), + "OutputDir": pluginDir, + } + + tObj := template.Must(template.New("test").Parse(execScriptTempl)) + buf := &bytes.Buffer{} + if err := tObj.Execute(buf, *execTemplateData); err != nil { + t.Fatalf("Error in executing script template - %v", err) + } + execScript := buf.String() + _, err = f.WriteString(execScript) + if err != nil { + t.Fatalf("Failed to write plugin exec - %v", err) + } + + err = f.Chmod(0777) + if err != nil { + t.Fatalf("Failed to set exec perms on plugin") + } + + f.Close() +} + +func tearDownPlugin(tmpDir string) { + err := os.RemoveAll(tmpDir) + if err != nil { + fmt.Printf("Error in cleaning up test: %v", err) + } +} + +type fakeNetworkHost struct { + kubeClient clientset.Interface +} + +func NewFakeHost(kubeClient clientset.Interface) *fakeNetworkHost { + host := &fakeNetworkHost{kubeClient: kubeClient} + return host +} + +func (fnh *fakeNetworkHost) GetPodByName(name, namespace string) (*api.Pod, bool) { + return nil, false +} + +func (fnh *fakeNetworkHost) GetKubeClient() clientset.Interface { + return nil +} + +func (nh *fakeNetworkHost) GetRuntime() kubecontainer.Runtime { + dm, fakeDockerClient := newTestDockerManager() + fakeDockerClient.SetFakeRunningContainers([]*docker.Container{ + { + ID: "test_infra_container", + State: docker.State{Pid: 12345}, + }, + }) + return dm +} + +func newTestDockerManager() (*dockertools.DockerManager, *dockertools.FakeDockerClient) { + fakeDocker := dockertools.NewFakeDockerClient() + fakeRecorder := &record.FakeRecorder{} + containerRefManager := kubecontainer.NewRefManager() + networkPlugin, _ := network.InitNetworkPlugin([]network.NetworkPlugin{}, "", nettest.NewFakeHost(nil)) + dockerManager := dockertools.NewFakeDockerManager( + fakeDocker, + fakeRecorder, + proberesults.NewManager(), + containerRefManager, + &cadvisorapi.MachineInfo{}, + options.GetDefaultPodInfraContainerImage(), + 0, 0, "", + containertest.FakeOS{}, + networkPlugin, + nil, + nil, + nil) + + return dockerManager, fakeDocker +} + +func TestCNIPlugin(t *testing.T) { + // install some random plugin + pluginName := fmt.Sprintf("test%d", rand.Intn(1000)) + vendorName := fmt.Sprintf("test_vendor%d", rand.Intn(1000)) + + tmpDir := utiltesting.MkTmpdirOrDie("cni-test") + testNetworkConfigPath := path.Join(tmpDir, "plugins", "net", "cni") + testVendorCNIDirPrefix := tmpDir + defer tearDownPlugin(tmpDir) + installPluginUnderTest(t, testVendorCNIDirPrefix, testNetworkConfigPath, vendorName, pluginName) + + np := probeNetworkPluginsWithVendorCNIDirPrefix(path.Join(testNetworkConfigPath, pluginName), testVendorCNIDirPrefix) + plug, err := network.InitNetworkPlugin(np, "cni", NewFakeHost(nil)) + if err != nil { + t.Fatalf("Failed to select the desired plugin: %v", err) + } + + err = plug.SetUpPod("podNamespace", "podName", "test_infra_container") + if err != nil { + t.Errorf("Expected nil: %v", err) + } + outputEnv := path.Join(testNetworkConfigPath, pluginName, pluginName+".env") + eo, eerr := ioutil.ReadFile(outputEnv) + outputFile := path.Join(testNetworkConfigPath, pluginName, pluginName+".out") + output, err := ioutil.ReadFile(outputFile) + if err != nil { + t.Errorf("Failed to read output file %s: %v (env %s err %v)", outputFile, err, eo, eerr) + } + expectedOutput := "ADD /proc/12345/ns/net podNamespace podName test_infra_container" + if string(output) != expectedOutput { + t.Errorf("Mismatch in expected output for setup hook. Expected '%s', got '%s'", expectedOutput, string(output)) + } + err = plug.TearDownPod("podNamespace", "podName", "test_infra_container") + if err != nil { + t.Errorf("Expected nil: %v", err) + } + output, err = ioutil.ReadFile(path.Join(testNetworkConfigPath, pluginName, pluginName+".out")) + expectedOutput = "DEL /proc/12345/ns/net podNamespace podName test_infra_container" + if string(output) != expectedOutput { + t.Errorf("Mismatch in expected output for setup hook. Expected '%s', got '%s'", expectedOutput, string(output)) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/network/exec/exec.go b/vendor/k8s.io/kubernetes/pkg/kubelet/network/exec/exec.go new file mode 100644 index 000000000..24e847840 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/network/exec/exec.go @@ -0,0 +1,181 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 exec scans and loads networking plugins that are installed +// under /usr/libexec/kubernetes/kubelet-plugins/net/exec/ +// The layout convention for a plugin is: +// plugin-name/ (plugins have to be directories first) +// plugin-name/plugin-name (executable that will be called out, see Vendoring Note for more nuances) +// plugin-name/ +// where, 'executable' has the following requirements: +// - should have exec permissions +// - should give non-zero exit code on failure, and zero on success +// - the arguments will be +// whereupon, will be one of: +// - init, called when the kubelet loads the plugin +// - setup, called after the infra container of a pod is +// created, but before other containers of the pod are created +// - teardown, called before the pod infra container is killed +// - status, called at regular intervals and is supposed to return a json +// formatted output indicating the pod's IPAddress(v4/v6). An empty string value or an erroneous output +// will mean the container runtime (docker) will be asked for the PodIP +// e.g. { +// "apiVersion" : "v1beta1", +// "kind" : "PodNetworkStatus", +// "ip" : "10.20.30.40" +// } +// The fields "apiVersion" and "kind" are optional in version v1beta1 +// As the executables are called, the file-descriptors stdin, stdout, stderr +// remain open. The combined output of stdout/stderr is captured and logged. +// +// Note: If the pod infra container self-terminates (e.g. crashes or is killed), +// the entire pod lifecycle will be restarted, but teardown will not be called. +// +// Vendoring Note: +// Plugin Names can be vendored also. Use '~' as the escaped name for plugin directories. +// And expect command line argument to call vendored plugins as 'vendor/pluginName' +// e.g. pluginName = mysdn +// vendorname = mycompany +// then, plugin layout should be +// mycompany~mysdn/ +// mycompany~mysdn/mysdn (this becomes the executable) +// mycompany~mysdn/ +// and, call the kubelet with '--network-plugin=mycompany/mysdn' +package exec + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "path" + "strings" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api/unversioned" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/network" + utilexec "k8s.io/kubernetes/pkg/util/exec" +) + +type execNetworkPlugin struct { + network.NoopNetworkPlugin + + execName string + execPath string + host network.Host +} + +const ( + initCmd = "init" + setUpCmd = "setup" + tearDownCmd = "teardown" + statusCmd = "status" +) + +func ProbeNetworkPlugins(pluginDir string) []network.NetworkPlugin { + execPlugins := []network.NetworkPlugin{} + + files, _ := ioutil.ReadDir(pluginDir) + for _, f := range files { + // only directories are counted as plugins + // and pluginDir/dirname/dirname should be an executable + // unless dirname contains '~' for escaping namespace + // e.g. dirname = vendor~ipvlan + // then, executable will be pluginDir/dirname/ipvlan + if f.IsDir() { + execPath := path.Join(pluginDir, f.Name()) + execPlugins = append(execPlugins, &execNetworkPlugin{execName: network.UnescapePluginName(f.Name()), execPath: execPath}) + } + } + return execPlugins +} + +func (plugin *execNetworkPlugin) Init(host network.Host) error { + err := plugin.validate() + if err != nil { + return err + } + plugin.host = host + // call the init script + out, err := utilexec.New().Command(plugin.getExecutable(), initCmd).CombinedOutput() + glog.V(5).Infof("Init 'exec' network plugin output: %s, %v", string(out), err) + return err +} + +func (plugin *execNetworkPlugin) getExecutable() string { + parts := strings.Split(plugin.execName, "/") + execName := parts[len(parts)-1] + return path.Join(plugin.execPath, execName) +} + +func (plugin *execNetworkPlugin) Name() string { + return plugin.execName +} + +func (plugin *execNetworkPlugin) validate() error { + if !isExecutable(plugin.getExecutable()) { + errStr := fmt.Sprintf("Invalid exec plugin. Executable '%s' does not have correct permissions.", plugin.execName) + return errors.New(errStr) + } + return nil +} + +func (plugin *execNetworkPlugin) SetUpPod(namespace string, name string, id kubecontainer.DockerID) error { + out, err := utilexec.New().Command(plugin.getExecutable(), setUpCmd, namespace, name, string(id)).CombinedOutput() + glog.V(5).Infof("SetUpPod 'exec' network plugin output: %s, %v", string(out), err) + return err +} + +func (plugin *execNetworkPlugin) TearDownPod(namespace string, name string, id kubecontainer.DockerID) error { + out, err := utilexec.New().Command(plugin.getExecutable(), tearDownCmd, namespace, name, string(id)).CombinedOutput() + glog.V(5).Infof("TearDownPod 'exec' network plugin output: %s, %v", string(out), err) + return err +} + +func (plugin *execNetworkPlugin) Status(namespace string, name string, id kubecontainer.DockerID) (*network.PodNetworkStatus, error) { + out, err := utilexec.New().Command(plugin.getExecutable(), statusCmd, namespace, name, string(id)).CombinedOutput() + glog.V(5).Infof("Status 'exec' network plugin output: %s, %v", string(out), err) + if err != nil { + return nil, err + } + if string(out) == "" { + return nil, nil + } + findVersion := struct { + unversioned.TypeMeta `json:",inline"` + }{} + err = json.Unmarshal(out, &findVersion) + if err != nil { + return nil, err + } + + // check kind and version + if findVersion.Kind != "" && findVersion.Kind != "PodNetworkStatus" { + errStr := fmt.Sprintf("Invalid 'kind' returned in network status for pod '%s'. Valid value is 'PodNetworkStatus', got '%s'.", name, findVersion.Kind) + return nil, errors.New(errStr) + } + switch findVersion.APIVersion { + case "": + fallthrough + case "v1beta1": + networkStatus := &network.PodNetworkStatus{} + err = json.Unmarshal(out, networkStatus) + return networkStatus, err + } + errStr := fmt.Sprintf("Unknown version '%s' in network status for pod '%s'.", findVersion.APIVersion, name) + return nil, errors.New(errStr) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/network/exec/exec_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/network/exec/exec_test.go new file mode 100644 index 000000000..d96f06bd8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/network/exec/exec_test.go @@ -0,0 +1,339 @@ +// +build linux + +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 exec + +import ( + "bytes" + "fmt" + "io/ioutil" + "math/rand" + "os" + "path" + "sync" + "testing" + "text/template" + + "k8s.io/kubernetes/pkg/kubelet/network" + nettest "k8s.io/kubernetes/pkg/kubelet/network/testing" + "k8s.io/kubernetes/pkg/util/sets" + utiltesting "k8s.io/kubernetes/pkg/util/testing" +) + +func tmpDirOrDie() string { + dir, err := utiltesting.MkTmpdir("exec-test") + if err != nil { + panic(fmt.Sprintf("error creating tmp dir: %v", err)) + } + return path.Join(dir, "fake", "plugins", "net") +} + +var lock sync.Mutex +var namesInUse = sets.NewString() + +func selectName() string { + lock.Lock() + defer lock.Unlock() + for { + pluginName := fmt.Sprintf("test%d", rand.Intn(1000)) + if !namesInUse.Has(pluginName) { + namesInUse.Insert(pluginName) + return pluginName + } + } +} + +func releaseName(name string) { + lock.Lock() + defer lock.Unlock() + namesInUse.Delete(name) +} + +func installPluginUnderTest(t *testing.T, vendorName, testPluginPath, plugName string, execTemplateData *map[string]interface{}) { + vendoredName := plugName + if vendorName != "" { + vendoredName = fmt.Sprintf("%s~%s", vendorName, plugName) + } + pluginDir := path.Join(testPluginPath, vendoredName) + err := os.MkdirAll(pluginDir, 0777) + if err != nil { + t.Errorf("Failed to create plugin dir %q: %v", pluginDir, err) + } + pluginExec := path.Join(pluginDir, plugName) + f, err := os.Create(pluginExec) + if err != nil { + t.Errorf("Failed to install plugin %q: %v", pluginExec, err) + } + defer f.Close() + err = f.Chmod(0777) + if err != nil { + t.Errorf("Failed to set exec perms on plugin %q: %v", pluginExec, err) + } + const execScriptTempl = `#!/bin/bash + +# If status hook is called print the expected json to stdout +if [ "$1" == "status" ]; then + echo -n '{ + "ip" : "{{.IPAddress}}" +}' +fi + +# Direct the arguments to a file to be tested against later +echo -n "$@" &> {{.OutputFile}} +` + if execTemplateData == nil { + execTemplateData = &map[string]interface{}{ + "IPAddress": "10.20.30.40", + "OutputFile": path.Join(pluginDir, plugName+".out"), + } + } + + tObj := template.Must(template.New("test").Parse(execScriptTempl)) + buf := &bytes.Buffer{} + if err := tObj.Execute(buf, *execTemplateData); err != nil { + t.Errorf("Error in executing script template: %v", err) + } + execScript := buf.String() + _, err = f.WriteString(execScript) + if err != nil { + t.Errorf("Failed to write plugin %q: %v", pluginExec, err) + } +} + +func tearDownPlugin(testPluginPath string) { + err := os.RemoveAll(testPluginPath) + if err != nil { + fmt.Printf("Error in cleaning up test: %v", err) + } +} + +func TestSelectPlugin(t *testing.T) { + // The temp dir where test plugins will be stored. + testPluginPath := tmpDirOrDie() + + // install some random plugin under testPluginPath + pluginName := selectName() + defer tearDownPlugin(testPluginPath) + defer releaseName(pluginName) + + installPluginUnderTest(t, "", testPluginPath, pluginName, nil) + + plug, err := network.InitNetworkPlugin(ProbeNetworkPlugins(testPluginPath), pluginName, nettest.NewFakeHost(nil)) + if err != nil { + t.Errorf("Failed to select the desired plugin: %v", err) + } + if plug.Name() != pluginName { + t.Errorf("Wrong plugin selected, chose %s, got %s\n", pluginName, plug.Name()) + } +} + +func TestSelectVendoredPlugin(t *testing.T) { + // The temp dir where test plugins will be stored. + testPluginPath := tmpDirOrDie() + + // install some random plugin under testPluginPath + pluginName := selectName() + defer tearDownPlugin(testPluginPath) + defer releaseName(pluginName) + + vendor := "mycompany" + installPluginUnderTest(t, vendor, testPluginPath, pluginName, nil) + + vendoredPluginName := fmt.Sprintf("%s/%s", vendor, pluginName) + plug, err := network.InitNetworkPlugin(ProbeNetworkPlugins(testPluginPath), vendoredPluginName, nettest.NewFakeHost(nil)) + if err != nil { + t.Errorf("Failed to select the desired plugin: %v", err) + } + if plug.Name() != vendoredPluginName { + t.Errorf("Wrong plugin selected, chose %s, got %s\n", vendoredPluginName, plug.Name()) + } +} + +func TestSelectWrongPlugin(t *testing.T) { + // The temp dir where test plugins will be stored. + testPluginPath := tmpDirOrDie() + + // install some random plugin under testPluginPath + pluginName := selectName() + defer tearDownPlugin(testPluginPath) + defer releaseName(pluginName) + + installPluginUnderTest(t, "", testPluginPath, pluginName, nil) + + wrongPlugin := "abcd" + plug, err := network.InitNetworkPlugin(ProbeNetworkPlugins(testPluginPath), wrongPlugin, nettest.NewFakeHost(nil)) + if plug != nil || err == nil { + t.Errorf("Expected to see an error. Wrong plugin selected.") + } +} + +func TestPluginValidation(t *testing.T) { + // The temp dir where test plugins will be stored. + testPluginPath := tmpDirOrDie() + + // install some random plugin under testPluginPath + pluginName := selectName() + defer tearDownPlugin(testPluginPath) + defer releaseName(pluginName) + + installPluginUnderTest(t, "", testPluginPath, pluginName, nil) + + // modify the perms of the pluginExecutable + f, err := os.Open(path.Join(testPluginPath, pluginName, pluginName)) + if err != nil { + t.Errorf("Nil value expected.") + } + err = f.Chmod(0444) + if err != nil { + t.Errorf("Failed to set perms on plugin exec") + } + f.Close() + + _, err = network.InitNetworkPlugin(ProbeNetworkPlugins(testPluginPath), pluginName, nettest.NewFakeHost(nil)) + if err == nil { + // we expected an error here because validation would have failed + t.Errorf("Expected non-nil value.") + } +} + +func TestPluginSetupHook(t *testing.T) { + // The temp dir where test plugins will be stored. + testPluginPath := tmpDirOrDie() + + // install some random plugin under testPluginPath + pluginName := selectName() + defer tearDownPlugin(testPluginPath) + defer releaseName(pluginName) + + installPluginUnderTest(t, "", testPluginPath, pluginName, nil) + + plug, err := network.InitNetworkPlugin(ProbeNetworkPlugins(testPluginPath), pluginName, nettest.NewFakeHost(nil)) + + err = plug.SetUpPod("podNamespace", "podName", "dockerid2345") + if err != nil { + t.Errorf("Expected nil: %v", err) + } + // check output of setup hook + output, err := ioutil.ReadFile(path.Join(testPluginPath, pluginName, pluginName+".out")) + if err != nil { + t.Errorf("Expected nil") + } + expectedOutput := "setup podNamespace podName dockerid2345" + if string(output) != expectedOutput { + t.Errorf("Mismatch in expected output for setup hook. Expected '%s', got '%s'", expectedOutput, string(output)) + } +} + +func TestPluginTearDownHook(t *testing.T) { + // The temp dir where test plugins will be stored. + testPluginPath := tmpDirOrDie() + + // install some random plugin under testPluginPath + pluginName := selectName() + defer tearDownPlugin(testPluginPath) + defer releaseName(pluginName) + + installPluginUnderTest(t, "", testPluginPath, pluginName, nil) + + plug, err := network.InitNetworkPlugin(ProbeNetworkPlugins(testPluginPath), pluginName, nettest.NewFakeHost(nil)) + + err = plug.TearDownPod("podNamespace", "podName", "dockerid2345") + if err != nil { + t.Errorf("Expected nil") + } + // check output of setup hook + output, err := ioutil.ReadFile(path.Join(testPluginPath, pluginName, pluginName+".out")) + if err != nil { + t.Errorf("Expected nil") + } + expectedOutput := "teardown podNamespace podName dockerid2345" + if string(output) != expectedOutput { + t.Errorf("Mismatch in expected output for teardown hook. Expected '%s', got '%s'", expectedOutput, string(output)) + } +} + +func TestPluginStatusHook(t *testing.T) { + // The temp dir where test plugins will be stored. + testPluginPath := tmpDirOrDie() + + // install some random plugin under testPluginPath + pluginName := selectName() + defer tearDownPlugin(testPluginPath) + defer releaseName(pluginName) + + installPluginUnderTest(t, "", testPluginPath, pluginName, nil) + + plug, err := network.InitNetworkPlugin(ProbeNetworkPlugins(testPluginPath), pluginName, nettest.NewFakeHost(nil)) + + ip, err := plug.Status("namespace", "name", "dockerid2345") + if err != nil { + t.Errorf("Expected nil got %v", err) + } + // check output of status hook + output, err := ioutil.ReadFile(path.Join(testPluginPath, pluginName, pluginName+".out")) + if err != nil { + t.Errorf("Expected nil") + } + expectedOutput := "status namespace name dockerid2345" + if string(output) != expectedOutput { + t.Errorf("Mismatch in expected output for status hook. Expected '%s', got '%s'", expectedOutput, string(output)) + } + if ip.IP.String() != "10.20.30.40" { + t.Errorf("Mismatch in expected output for status hook. Expected '10.20.30.40', got '%s'", ip.IP.String()) + } +} + +func TestPluginStatusHookIPv6(t *testing.T) { + // The temp dir where test plugins will be stored. + testPluginPath := tmpDirOrDie() + + // install some random plugin under testPluginPath + pluginName := selectName() + defer tearDownPlugin(testPluginPath) + defer releaseName(pluginName) + + pluginDir := path.Join(testPluginPath, pluginName) + execTemplate := &map[string]interface{}{ + "IPAddress": "fe80::e2cb:4eff:fef9:6710", + "OutputFile": path.Join(pluginDir, pluginName+".out"), + } + installPluginUnderTest(t, "", testPluginPath, pluginName, execTemplate) + + plug, err := network.InitNetworkPlugin(ProbeNetworkPlugins(testPluginPath), pluginName, nettest.NewFakeHost(nil)) + if err != nil { + t.Errorf("InitNetworkPlugin() failed: %v", err) + } + + ip, err := plug.Status("namespace", "name", "dockerid2345") + if err != nil { + t.Errorf("Status() failed: %v", err) + } + // check output of status hook + outPath := path.Join(testPluginPath, pluginName, pluginName+".out") + output, err := ioutil.ReadFile(outPath) + if err != nil { + t.Errorf("ReadFile(%q) failed: %v", outPath, err) + } + expectedOutput := "status namespace name dockerid2345" + if string(output) != expectedOutput { + t.Errorf("Mismatch in expected output for status hook. Expected %q, got %q", expectedOutput, string(output)) + } + if ip.IP.String() != "fe80::e2cb:4eff:fef9:6710" { + t.Errorf("Mismatch in expected output for status hook. Expected 'fe80::e2cb:4eff:fef9:6710', got '%s'", ip.IP.String()) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/network/exec/exec_unix.go b/vendor/k8s.io/kubernetes/pkg/kubelet/network/exec/exec_unix.go new file mode 100644 index 000000000..26847fe76 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/network/exec/exec_unix.go @@ -0,0 +1,27 @@ +// +build !windows + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 exec + +import "syscall" + +const X_OK = 0x1 + +func isExecutable(path string) bool { + return syscall.Access(path, X_OK) == nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/network/exec/exec_unsupported.go b/vendor/k8s.io/kubernetes/pkg/kubelet/network/exec/exec_unsupported.go new file mode 100644 index 000000000..e2d4969f7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/network/exec/exec_unsupported.go @@ -0,0 +1,23 @@ +// +build windows + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 exec + +func isExecutable(path string) bool { + return false +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/network/hairpin/hairpin.go b/vendor/k8s.io/kubernetes/pkg/kubelet/network/hairpin/hairpin.go new file mode 100644 index 000000000..cf5d29375 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/network/hairpin/hairpin.go @@ -0,0 +1,111 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 hairpin + +import ( + "fmt" + "io/ioutil" + "net" + "os" + "path" + "regexp" + "strconv" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/util/exec" +) + +const ( + sysfsNetPath = "/sys/devices/virtual/net" + brportRelativePath = "brport" + hairpinModeRelativePath = "hairpin_mode" + hairpinEnable = "1" +) + +var ( + ethtoolOutputRegex = regexp.MustCompile("peer_ifindex: (\\d+)") +) + +func SetUpContainer(containerPid int, containerInterfaceName string) error { + e := exec.New() + return setUpContainerInternal(e, containerPid, containerInterfaceName) +} + +func setUpContainerInternal(e exec.Interface, containerPid int, containerInterfaceName string) error { + hostIfName, err := findPairInterfaceOfContainerInterface(e, containerPid, containerInterfaceName) + if err != nil { + glog.Infof("Unable to find pair interface, setting up all interfaces: %v", err) + return setUpAllInterfaces() + } + return setUpInterface(hostIfName) +} + +func findPairInterfaceOfContainerInterface(e exec.Interface, containerPid int, containerInterfaceName string) (string, error) { + nsenterPath, err := e.LookPath("nsenter") + if err != nil { + return "", err + } + ethtoolPath, err := e.LookPath("ethtool") + if err != nil { + return "", err + } + // Get container's interface index + output, err := e.Command(nsenterPath, "-t", fmt.Sprintf("%d", containerPid), "-n", "-F", "--", ethtoolPath, "--statistics", containerInterfaceName).CombinedOutput() + if err != nil { + return "", fmt.Errorf("Unable to query interface %s of container %d: %v: %s", containerInterfaceName, containerPid, err, string(output)) + } + // look for peer_ifindex + match := ethtoolOutputRegex.FindSubmatch(output) + if match == nil { + return "", fmt.Errorf("No peer_ifindex in interface statistics for %s of container %d", containerInterfaceName, containerPid) + } + peerIfIndex, err := strconv.Atoi(string(match[1])) + if err != nil { // seems impossible (\d+ not numeric) + return "", fmt.Errorf("peer_ifindex wasn't numeric: %s: %v", match[1], err) + } + iface, err := net.InterfaceByIndex(peerIfIndex) + if err != nil { + return "", err + } + return iface.Name, nil +} + +func setUpAllInterfaces() error { + interfaces, err := net.Interfaces() + if err != nil { + return err + } + for _, netIf := range interfaces { + setUpInterface(netIf.Name) // ignore errors + } + return nil +} + +func setUpInterface(ifName string) error { + glog.V(3).Infof("Enabling hairpin on interface %s", ifName) + ifPath := path.Join(sysfsNetPath, ifName) + if _, err := os.Stat(ifPath); err != nil { + return err + } + brportPath := path.Join(ifPath, brportRelativePath) + if _, err := os.Stat(brportPath); err != nil && os.IsNotExist(err) { + // Device is not on a bridge, so doesn't need hairpin mode + return nil + } + hairpinModeFile := path.Join(brportPath, hairpinModeRelativePath) + return ioutil.WriteFile(hairpinModeFile, []byte(hairpinEnable), 0644) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/network/hairpin/hairpin_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/network/hairpin/hairpin_test.go new file mode 100644 index 000000000..63bc9ef5b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/network/hairpin/hairpin_test.go @@ -0,0 +1,107 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 hairpin + +import ( + "errors" + "fmt" + "net" + "os" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/util/exec" +) + +func TestFindPairInterfaceOfContainerInterface(t *testing.T) { + // there should be at least "lo" on any system + interfaces, _ := net.Interfaces() + validOutput := fmt.Sprintf("garbage\n peer_ifindex: %d", interfaces[0].Index) + invalidOutput := fmt.Sprintf("garbage\n unknown: %d", interfaces[0].Index) + + tests := []struct { + output string + err error + expectedName string + expectErr bool + }{ + { + output: validOutput, + expectedName: interfaces[0].Name, + }, + { + output: invalidOutput, + expectErr: true, + }, + { + output: validOutput, + err: errors.New("error"), + expectErr: true, + }, + } + for _, test := range tests { + fcmd := exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ + func() ([]byte, error) { return []byte(test.output), test.err }, + }, + } + fexec := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { + return exec.InitFakeCmd(&fcmd, cmd, args...) + }, + }, + LookPathFunc: func(file string) (string, error) { + return fmt.Sprintf("/fake-bin/%s", file), nil + }, + } + name, err := findPairInterfaceOfContainerInterface(&fexec, 123, "eth0") + if test.expectErr { + if err == nil { + t.Errorf("unexpected non-error") + } + } else { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + } + if name != test.expectedName { + t.Errorf("unexpected name: %s (expected: %s)", name, test.expectedName) + } + } +} + +func TestSetUpInterfaceNonExistent(t *testing.T) { + err := setUpInterface("non-existent") + if err == nil { + t.Errorf("unexpected non-error") + } + deviceDir := fmt.Sprintf("%s/%s", sysfsNetPath, "non-existent") + if !strings.Contains(fmt.Sprintf("%v", err), deviceDir) { + t.Errorf("should have tried to open %s", deviceDir) + } +} + +func TestSetUpInterfaceNotBridged(t *testing.T) { + err := setUpInterface("lo") + if err != nil { + if os.IsNotExist(err) { + t.Skipf("'lo' device does not exist??? (%v)", err) + } + t.Errorf("unexpected error: %v", err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/network/kubenet/kubenet_linux.go b/vendor/k8s.io/kubernetes/pkg/kubelet/network/kubenet/kubenet_linux.go new file mode 100644 index 000000000..c73c543e7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/network/kubenet/kubenet_linux.go @@ -0,0 +1,291 @@ +// +build linux + +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 kubenet + +import ( + "fmt" + "net" + "strings" + "syscall" + + "github.com/vishvananda/netlink" + + "github.com/appc/cni/libcni" + "github.com/golang/glog" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/dockertools" + "k8s.io/kubernetes/pkg/kubelet/network" + "k8s.io/kubernetes/pkg/util/bandwidth" +) + +const ( + KubenetPluginName = "kubenet" + BridgeName = "cbr0" + DefaultCNIDir = "/opt/cni/bin" +) + +type kubenetNetworkPlugin struct { + network.NoopNetworkPlugin + + host network.Host + netConfig *libcni.NetworkConfig + cniConfig *libcni.CNIConfig + shaper bandwidth.BandwidthShaper + + podCIDRs map[kubecontainer.DockerID]string + MTU int +} + +func NewPlugin() network.NetworkPlugin { + return &kubenetNetworkPlugin{ + podCIDRs: make(map[kubecontainer.DockerID]string), + MTU: 1460, + } +} + +func (plugin *kubenetNetworkPlugin) Init(host network.Host) error { + plugin.host = host + plugin.cniConfig = &libcni.CNIConfig{ + Path: []string{DefaultCNIDir}, + } + + if link, err := findMinMTU(); err == nil { + plugin.MTU = link.MTU + glog.V(5).Infof("Using interface %s MTU %d as bridge MTU", link.Name, link.MTU) + } else { + glog.Warningf("Failed to find default bridge MTU: %v", err) + } + + return nil +} + +func findMinMTU() (*net.Interface, error) { + intfs, err := net.Interfaces() + if err != nil { + return nil, err + } + + mtu := 999999 + defIntfIndex := -1 + for i, intf := range intfs { + if ((intf.Flags & net.FlagUp) != 0) && (intf.Flags&(net.FlagLoopback|net.FlagPointToPoint) == 0) { + if intf.MTU < mtu { + mtu = intf.MTU + defIntfIndex = i + } + } + } + + if mtu >= 999999 || mtu < 576 || defIntfIndex < 0 { + return nil, fmt.Errorf("no suitable interface: %v", BridgeName) + } + + return &intfs[defIntfIndex], nil +} + +const NET_CONFIG_TEMPLATE = `{ + "cniVersion": "0.1.0", + "name": "kubenet", + "type": "bridge", + "bridge": "%s", + "mtu": %d, + "addIf": "%s", + "isGateway": true, + "ipMasq": true, + "ipam": { + "type": "host-local", + "subnet": "%s", + "gateway": "%s", + "routes": [ + { "dst": "0.0.0.0/0" } + ] + } +}` + +func (plugin *kubenetNetworkPlugin) Event(name string, details map[string]interface{}) { + if name != network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE { + return + } + + podCIDR, ok := details[network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE_DETAIL_CIDR].(string) + if !ok { + glog.Warningf("%s event didn't contain pod CIDR", network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE) + return + } + + if plugin.netConfig != nil { + glog.V(5).Infof("Ignoring subsequent pod CIDR update to %s", podCIDR) + return + } + + glog.V(5).Infof("PodCIDR is set to %q", podCIDR) + _, cidr, err := net.ParseCIDR(podCIDR) + if err == nil { + // Set bridge address to first address in IPNet + cidr.IP.To4()[3] += 1 + + json := fmt.Sprintf(NET_CONFIG_TEMPLATE, BridgeName, plugin.MTU, network.DefaultInterfaceName, podCIDR, cidr.IP.String()) + glog.V(2).Infof("CNI network config set to %v", json) + plugin.netConfig, err = libcni.ConfFromBytes([]byte(json)) + if err == nil { + glog.V(5).Infof("CNI network config:\n%s", json) + + // Ensure cbr0 has no conflicting addresses; CNI's 'bridge' + // plugin will bail out if the bridge has an unexpected one + plugin.clearBridgeAddressesExcept(cidr.IP.String()) + } + } + + if err != nil { + glog.Warningf("Failed to generate CNI network config: %v", err) + } +} + +func (plugin *kubenetNetworkPlugin) clearBridgeAddressesExcept(keep string) { + bridge, err := netlink.LinkByName(BridgeName) + if err != nil { + return + } + + addrs, err := netlink.AddrList(bridge, syscall.AF_INET) + if err != nil { + return + } + + for _, addr := range addrs { + if addr.IPNet.String() != keep { + glog.V(5).Infof("Removing old address %s from %s", addr.IPNet.String(), BridgeName) + netlink.AddrDel(bridge, &addr) + } + } +} + +func (plugin *kubenetNetworkPlugin) Name() string { + return KubenetPluginName +} + +func (plugin *kubenetNetworkPlugin) SetUpPod(namespace string, name string, id kubecontainer.DockerID) error { + // Can't set up pods if we don't have a PodCIDR yet + if plugin.netConfig == nil { + return fmt.Errorf("Kubenet needs a PodCIDR to set up pods") + } + + runtime, ok := plugin.host.GetRuntime().(*dockertools.DockerManager) + if !ok { + return fmt.Errorf("Kubenet execution called on non-docker runtime") + } + netnsPath, err := runtime.GetNetNS(id.ContainerID()) + if err != nil { + return err + } + + rt := buildCNIRuntimeConf(name, namespace, id.ContainerID(), netnsPath) + if err != nil { + return fmt.Errorf("Error building CNI config: %v", err) + } + + glog.V(3).Infof("Calling cni plugins to add container to network with cni runtime: %+v", rt) + res, err := plugin.cniConfig.AddNetwork(plugin.netConfig, rt) + if err != nil { + return fmt.Errorf("Error adding container to network: %v", err) + } + if res.IP4 == nil { + return fmt.Errorf("CNI plugin reported no IPv4 address for container %v.", id) + } + + plugin.podCIDRs[id] = res.IP4.IP.String() + + // The first SetUpPod call creates the bridge; ensure shaping is enabled + if plugin.shaper == nil { + plugin.shaper = bandwidth.NewTCShaper(BridgeName) + if plugin.shaper == nil { + return fmt.Errorf("Failed to create bandwidth shaper!") + } + plugin.shaper.ReconcileInterface() + } + + // TODO: get ingress/egress from Pod.Spec and add pod CIDR to shaper + + return nil +} + +func (plugin *kubenetNetworkPlugin) TearDownPod(namespace string, name string, id kubecontainer.DockerID) error { + if plugin.netConfig == nil { + return fmt.Errorf("Kubenet needs a PodCIDR to tear down pods") + } + + runtime, ok := plugin.host.GetRuntime().(*dockertools.DockerManager) + if !ok { + return fmt.Errorf("Kubenet execution called on non-docker runtime") + } + netnsPath, err := runtime.GetNetNS(id.ContainerID()) + if err != nil { + return err + } + + rt := buildCNIRuntimeConf(name, namespace, id.ContainerID(), netnsPath) + if err != nil { + return fmt.Errorf("Error building CNI config: %v", err) + } + + // no cached CIDR is Ok during teardown + if cidr, ok := plugin.podCIDRs[id]; ok { + glog.V(5).Infof("Removing pod CIDR %s from shaper", cidr) + // shaper wants /32 + if addr, _, err := net.ParseCIDR(cidr); err != nil { + if err = plugin.shaper.Reset(fmt.Sprintf("%s/32", addr.String())); err != nil { + glog.Warningf("Failed to remove pod CIDR %s from shaper: %v", cidr, err) + } + } + } + delete(plugin.podCIDRs, id) + + glog.V(3).Infof("Calling cni plugins to remove container from network with cni runtime: %+v", rt) + if err := plugin.cniConfig.DelNetwork(plugin.netConfig, rt); err != nil { + return fmt.Errorf("Error removing container from network: %v", err) + } + + return nil +} + +// TODO: Use the addToNetwork function to obtain the IP of the Pod. That will assume idempotent ADD call to the plugin. +// Also fix the runtime's call to Status function to be done only in the case that the IP is lost, no need to do periodic calls +func (plugin *kubenetNetworkPlugin) Status(namespace string, name string, id kubecontainer.DockerID) (*network.PodNetworkStatus, error) { + cidr, ok := plugin.podCIDRs[id] + if !ok { + return nil, fmt.Errorf("No IP address found for pod %v", id) + } + + ip, _, err := net.ParseCIDR(strings.Trim(cidr, "\n")) + if err != nil { + return nil, err + } + return &network.PodNetworkStatus{IP: ip}, nil +} + +func buildCNIRuntimeConf(podName string, podNs string, podInfraContainerID kubecontainer.ContainerID, podNetnsPath string) *libcni.RuntimeConf { + glog.V(4).Infof("Kubenet: using netns path %v", podNetnsPath) + glog.V(4).Infof("Kubenet: using podns path %v", podNs) + + return &libcni.RuntimeConf{ + ContainerID: podInfraContainerID.ID, + NetNS: podNetnsPath, + IfName: network.DefaultInterfaceName, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/network/kubenet/kubenet_unsupported.go b/vendor/k8s.io/kubernetes/pkg/kubelet/network/kubenet/kubenet_unsupported.go new file mode 100644 index 000000000..05ef445e0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/network/kubenet/kubenet_unsupported.go @@ -0,0 +1,54 @@ +// +build !linux + +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 kubenet + +import ( + "fmt" + + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/network" +) + +type kubenetNetworkPlugin struct { + network.NoopNetworkPlugin +} + +func NewPlugin() network.NetworkPlugin { + return &kubenetNetworkPlugin{} +} + +func (plugin *kubenetNetworkPlugin) Init(host network.Host) error { + return fmt.Errorf("Kubenet is not supported in this build") +} + +func (plugin *kubenetNetworkPlugin) Name() string { + return "kubenet" +} + +func (plugin *kubenetNetworkPlugin) SetUpPod(namespace string, name string, id kubecontainer.DockerID) error { + return fmt.Errorf("Kubenet is not supported in this build") +} + +func (plugin *kubenetNetworkPlugin) TearDownPod(namespace string, name string, id kubecontainer.DockerID) error { + return fmt.Errorf("Kubenet is not supported in this build") +} + +func (plugin *kubenetNetworkPlugin) Status(namespace string, name string, id kubecontainer.DockerID) (*network.PodNetworkStatus, error) { + return nil, fmt.Errorf("Kubenet is not supported in this build") +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/network/network.go b/vendor/k8s.io/kubernetes/pkg/kubelet/network/network.go new file mode 100644 index 000000000..1396d4155 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/network/network.go @@ -0,0 +1,20 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 network + +// TODO: Consider making this value configurable. +const DefaultInterfaceName = "eth0" diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/network/plugins.go b/vendor/k8s.io/kubernetes/pkg/kubelet/network/plugins.go new file mode 100644 index 000000000..64cfd03c1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/network/plugins.go @@ -0,0 +1,193 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 network + +import ( + "fmt" + "net" + "strings" + + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + utilerrors "k8s.io/kubernetes/pkg/util/errors" + utilexec "k8s.io/kubernetes/pkg/util/exec" + utilsets "k8s.io/kubernetes/pkg/util/sets" + utilsysctl "k8s.io/kubernetes/pkg/util/sysctl" + "k8s.io/kubernetes/pkg/util/validation" +) + +const DefaultPluginName = "kubernetes.io/no-op" + +// Called when the node's Pod CIDR is known when using the +// controller manager's --allocate-node-cidrs=true option +const NET_PLUGIN_EVENT_POD_CIDR_CHANGE = "pod-cidr-change" +const NET_PLUGIN_EVENT_POD_CIDR_CHANGE_DETAIL_CIDR = "pod-cidr" + +// Plugin capabilities +const ( + // Indicates the plugin handles Kubernetes bandwidth shaping annotations internally + NET_PLUGIN_CAPABILITY_SHAPING int = 1 +) + +// Plugin is an interface to network plugins for the kubelet +type NetworkPlugin interface { + // Init initializes the plugin. This will be called exactly once + // before any other methods are called. + Init(host Host) error + + // Called on various events like: + // NET_PLUGIN_EVENT_POD_CIDR_CHANGE + Event(name string, details map[string]interface{}) + + // Name returns the plugin's name. This will be used when searching + // for a plugin by name, e.g. + Name() string + + // Returns a set of NET_PLUGIN_CAPABILITY_* + Capabilities() utilsets.Int + + // SetUpPod is the method called after the infra container of + // the pod has been created but before the other containers of the + // pod are launched. + SetUpPod(namespace string, name string, podInfraContainerID kubecontainer.DockerID) error + + // TearDownPod is the method called before a pod's infra container will be deleted + TearDownPod(namespace string, name string, podInfraContainerID kubecontainer.DockerID) error + + // Status is the method called to obtain the ipv4 or ipv6 addresses of the container + Status(namespace string, name string, podInfraContainerID kubecontainer.DockerID) (*PodNetworkStatus, error) +} + +// PodNetworkStatus stores the network status of a pod (currently just the primary IP address) +// This struct represents version "v1beta1" +type PodNetworkStatus struct { + unversioned.TypeMeta `json:",inline"` + + // IP is the primary ipv4/ipv6 address of the pod. Among other things it is the address that - + // - kube expects to be reachable across the cluster + // - service endpoints are constructed with + // - will be reported in the PodStatus.PodIP field (will override the IP reported by docker) + IP net.IP `json:"ip" description:"Primary IP address of the pod"` +} + +// Host is an interface that plugins can use to access the kubelet. +type Host interface { + // Get the pod structure by its name, namespace + GetPodByName(namespace, name string) (*api.Pod, bool) + + // GetKubeClient returns a client interface + GetKubeClient() clientset.Interface + + // GetContainerRuntime returns the container runtime that implements the containers (e.g. docker/rkt) + GetRuntime() kubecontainer.Runtime +} + +// InitNetworkPlugin inits the plugin that matches networkPluginName. Plugins must have unique names. +func InitNetworkPlugin(plugins []NetworkPlugin, networkPluginName string, host Host) (NetworkPlugin, error) { + if networkPluginName == "" { + // default to the no_op plugin + plug := &NoopNetworkPlugin{} + if err := plug.Init(host); err != nil { + return nil, err + } + return plug, nil + } + + pluginMap := map[string]NetworkPlugin{} + + allErrs := []error{} + for _, plugin := range plugins { + name := plugin.Name() + if !validation.IsQualifiedName(name) { + allErrs = append(allErrs, fmt.Errorf("network plugin has invalid name: %#v", plugin)) + continue + } + + if _, found := pluginMap[name]; found { + allErrs = append(allErrs, fmt.Errorf("network plugin %q was registered more than once", name)) + continue + } + pluginMap[name] = plugin + } + + chosenPlugin := pluginMap[networkPluginName] + if chosenPlugin != nil { + err := chosenPlugin.Init(host) + if err != nil { + allErrs = append(allErrs, fmt.Errorf("Network plugin %q failed init: %v", networkPluginName, err)) + } else { + glog.V(1).Infof("Loaded network plugin %q", networkPluginName) + } + } else { + allErrs = append(allErrs, fmt.Errorf("Network plugin %q not found.", networkPluginName)) + } + + return chosenPlugin, utilerrors.NewAggregate(allErrs) +} + +func UnescapePluginName(in string) string { + return strings.Replace(in, "~", "/", -1) +} + +type NoopNetworkPlugin struct { +} + +const sysctlBridgeCallIptables = "net/bridge/bridge-nf-call-iptables" + +func (plugin *NoopNetworkPlugin) Init(host Host) error { + // Set bridge-nf-call-iptables=1 to maintain compatibility with older + // kubernetes versions to ensure the iptables-based kube proxy functions + // correctly. Other plugins are responsible for setting this correctly + // depending on whether or not they connect containers to Linux bridges + // or use some other mechanism (ie, SDN vswitch). + + // Ensure the netfilter module is loaded on kernel >= 3.18; previously + // it was built-in. + utilexec.New().Command("modprobe", "br-netfilter").CombinedOutput() + if err := utilsysctl.SetSysctl(sysctlBridgeCallIptables, 1); err != nil { + glog.Warningf("can't set sysctl %s: %v", sysctlBridgeCallIptables, err) + } + + return nil +} + +func (plugin *NoopNetworkPlugin) Event(name string, details map[string]interface{}) { +} + +func (plugin *NoopNetworkPlugin) Name() string { + return DefaultPluginName +} + +func (plugin *NoopNetworkPlugin) Capabilities() utilsets.Int { + return utilsets.NewInt() +} + +func (plugin *NoopNetworkPlugin) SetUpPod(namespace string, name string, id kubecontainer.DockerID) error { + return nil +} + +func (plugin *NoopNetworkPlugin) TearDownPod(namespace string, name string, id kubecontainer.DockerID) error { + return nil +} + +func (plugin *NoopNetworkPlugin) Status(namespace string, name string, id kubecontainer.DockerID) (*PodNetworkStatus, error) { + return nil, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/network/plugins_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/network/plugins_test.go new file mode 100644 index 000000000..b7138ce3c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/network/plugins_test.go @@ -0,0 +1,37 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 network + +import ( + "testing" + + nettest "k8s.io/kubernetes/pkg/kubelet/network/testing" +) + +func TestSelectDefaultPlugin(t *testing.T) { + all_plugins := []NetworkPlugin{} + plug, err := InitNetworkPlugin(all_plugins, "", nettest.NewFakeHost(nil)) + if err != nil { + t.Fatalf("Unexpected error in selecting default plugin: %v", err) + } + if plug == nil { + t.Fatalf("Failed to select the default plugin.") + } + if plug.Name() != DefaultPluginName { + t.Errorf("Failed to select the default plugin. Expected %s. Got %s", DefaultPluginName, plug.Name()) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/network/testing/fake_host.go b/vendor/k8s.io/kubernetes/pkg/kubelet/network/testing/fake_host.go new file mode 100644 index 000000000..9b0f349ab --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/network/testing/fake_host.go @@ -0,0 +1,48 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 testing + +// helper for testing plugins +// a fake host is created here that can be used by plugins for testing + +import ( + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" +) + +type fakeNetworkHost struct { + kubeClient clientset.Interface +} + +func NewFakeHost(kubeClient clientset.Interface) *fakeNetworkHost { + host := &fakeNetworkHost{kubeClient: kubeClient} + return host +} + +func (fnh *fakeNetworkHost) GetPodByName(name, namespace string) (*api.Pod, bool) { + return nil, false +} + +func (fnh *fakeNetworkHost) GetKubeClient() clientset.Interface { + return nil +} + +func (nh *fakeNetworkHost) GetRuntime() kubecontainer.Runtime { + return &containertest.FakeRuntime{} +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/networks.go b/vendor/k8s.io/kubernetes/pkg/kubelet/networks.go new file mode 100644 index 000000000..43674f804 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/networks.go @@ -0,0 +1,41 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" +) + +// This just exports required functions from kubelet proper, for use by network +// plugins. +type networkHost struct { + kubelet *Kubelet +} + +func (nh *networkHost) GetPodByName(name, namespace string) (*api.Pod, bool) { + return nh.kubelet.GetPodByName(name, namespace) +} + +func (nh *networkHost) GetKubeClient() clientset.Interface { + return nh.kubelet.kubeClient +} + +func (nh *networkHost) GetRuntime() kubecontainer.Runtime { + return nh.kubelet.GetRuntime() +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/oom_watcher.go b/vendor/k8s.io/kubernetes/pkg/kubelet/oom_watcher.go new file mode 100644 index 000000000..12dd2c48f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/oom_watcher.go @@ -0,0 +1,72 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "github.com/golang/glog" + "github.com/google/cadvisor/events" + cadvisorapi "github.com/google/cadvisor/info/v1" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/kubelet/cadvisor" + "k8s.io/kubernetes/pkg/util/runtime" +) + +type OOMWatcher interface { + Start(ref *api.ObjectReference) error +} + +type realOOMWatcher struct { + cadvisor cadvisor.Interface + recorder record.EventRecorder +} + +func NewOOMWatcher(cadvisor cadvisor.Interface, recorder record.EventRecorder) OOMWatcher { + return &realOOMWatcher{ + cadvisor: cadvisor, + recorder: recorder, + } +} + +const systemOOMEvent = "SystemOOM" + +// Watches cadvisor for system oom's and records an event for every system oom encountered. +func (ow *realOOMWatcher) Start(ref *api.ObjectReference) error { + request := events.Request{ + EventType: map[cadvisorapi.EventType]bool{ + cadvisorapi.EventOom: true, + }, + ContainerName: "/", + IncludeSubcontainers: false, + } + eventChannel, err := ow.cadvisor.WatchEvents(&request) + if err != nil { + return err + } + + go func() { + defer runtime.HandleCrash() + + for event := range eventChannel.GetChannel() { + glog.V(2).Infof("Got sys oom event from cadvisor: %v", event) + ow.recorder.PastEventf(ref, unversioned.Time{Time: event.Timestamp}, api.EventTypeWarning, systemOOMEvent, "System OOM encountered") + } + glog.Errorf("Unexpectedly stopped receiving OOM notifications from cAdvisor") + }() + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/oom_watcher_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/oom_watcher_test.go new file mode 100644 index 000000000..5928e87ee --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/oom_watcher_test.go @@ -0,0 +1,39 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing" +) + +func TestBasic(t *testing.T) { + fakeRecorder := &record.FakeRecorder{} + mockCadvisor := &cadvisortest.Fake{} + node := &api.ObjectReference{} + oomWatcher := NewOOMWatcher(mockCadvisor, fakeRecorder) + err := oomWatcher.Start(node) + if err != nil { + t.Errorf("Should not have failed: %v", err) + } + + // TODO: Improve this test once cadvisor exports events.EventChannel as an interface + // and thereby allow using a mock version of cadvisor. +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/pleg/doc.go b/vendor/k8s.io/kubernetes/pkg/kubelet/pleg/doc.go new file mode 100644 index 000000000..c8782ee89 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/pleg/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 pleg contains types and a generic implementation of the pod +// lifecycle event generator. +package pleg diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/pleg/generic.go b/vendor/k8s.io/kubernetes/pkg/kubelet/pleg/generic.go new file mode 100644 index 000000000..bf0d44d37 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/pleg/generic.go @@ -0,0 +1,368 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 pleg + +import ( + "fmt" + "time" + + "github.com/golang/glog" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/metrics" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/atomic" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/wait" +) + +// GenericPLEG is an extremely simple generic PLEG that relies solely on +// periodic listing to discover container changes. It should be be used +// as temporary replacement for container runtimes do not support a proper +// event generator yet. +// +// Note that GenericPLEG assumes that a container would not be created, +// terminated, and garbage collected within one relist period. If such an +// incident happens, GenenricPLEG would miss all events regarding this +// container. In the case of relisting failure, the window may become longer. +// Note that this assumption is not unique -- many kubelet internal components +// rely on terminated containers as tombstones for bookkeeping purposes. The +// garbage collector is implemented to work with such situtations. However, to +// guarantee that kubelet can handle missing container events, it is +// recommended to set the relist period short and have an auxiliary, longer +// periodic sync in kubelet as the safety net. +type GenericPLEG struct { + // The period for relisting. + relistPeriod time.Duration + // The container runtime. + runtime kubecontainer.Runtime + // The channel from which the subscriber listens events. + eventChannel chan *PodLifecycleEvent + // The internal cache for pod/container information. + podRecords podRecords + // Time of the last relisting. + relistTime atomic.Value + // Cache for storing the runtime states required for syncing pods. + cache kubecontainer.Cache + // For testability. + clock util.Clock +} + +// plegContainerState has a one-to-one mapping to the +// kubecontainer.ContainerState except for the non-existent state. This state +// is introduced here to complete the state transition scenarios. +type plegContainerState string + +const ( + plegContainerRunning plegContainerState = "running" + plegContainerExited plegContainerState = "exited" + plegContainerUnknown plegContainerState = "unknown" + plegContainerNonExistent plegContainerState = "non-existent" +) + +func convertState(state kubecontainer.ContainerState) plegContainerState { + switch state { + case kubecontainer.ContainerStateRunning: + return plegContainerRunning + case kubecontainer.ContainerStateExited: + return plegContainerExited + case kubecontainer.ContainerStateUnknown: + return plegContainerUnknown + default: + panic(fmt.Sprintf("unrecognized container state: %v", state)) + } +} + +type podRecord struct { + old *kubecontainer.Pod + current *kubecontainer.Pod +} + +type podRecords map[types.UID]*podRecord + +func NewGenericPLEG(runtime kubecontainer.Runtime, channelCapacity int, + relistPeriod time.Duration, cache kubecontainer.Cache, clock util.Clock) PodLifecycleEventGenerator { + return &GenericPLEG{ + relistPeriod: relistPeriod, + runtime: runtime, + eventChannel: make(chan *PodLifecycleEvent, channelCapacity), + podRecords: make(podRecords), + cache: cache, + clock: clock, + } +} + +// Returns a channel from which the subscriber can receive PodLifecycleEvent +// events. +// TODO: support multiple subscribers. +func (g *GenericPLEG) Watch() chan *PodLifecycleEvent { + return g.eventChannel +} + +// Start spawns a goroutine to relist periodically. +func (g *GenericPLEG) Start() { + go wait.Until(g.relist, g.relistPeriod, wait.NeverStop) +} + +func (g *GenericPLEG) Healthy() (bool, error) { + relistTime := g.getRelistTime() + // TODO: Evaluate if we can reduce this threshold. + // The threshold needs to be greater than the relisting period + the + // relisting time, which can vary significantly. Set a conservative + // threshold so that we don't cause kubelet to be restarted unnecessarily. + threshold := 2 * time.Minute + if g.clock.Since(relistTime) > threshold { + return false, fmt.Errorf("pleg was last seen active at %v", relistTime) + } + return true, nil +} + +func generateEvent(podID types.UID, cid string, oldState, newState plegContainerState) *PodLifecycleEvent { + if newState == oldState { + return nil + } + glog.V(4).Infof("GenericPLEG: %v/%v: %v -> %v", podID, cid, oldState, newState) + switch newState { + case plegContainerRunning: + return &PodLifecycleEvent{ID: podID, Type: ContainerStarted, Data: cid} + case plegContainerExited: + return &PodLifecycleEvent{ID: podID, Type: ContainerDied, Data: cid} + case plegContainerUnknown: + return &PodLifecycleEvent{ID: podID, Type: ContainerChanged, Data: cid} + case plegContainerNonExistent: + // We report "ContainerDied" when container was stopped OR removed. We + // may want to distinguish the two cases in the future. + switch oldState { + case plegContainerExited: + // We already reported that the container died before. + return &PodLifecycleEvent{ID: podID, Type: ContainerRemoved, Data: cid} + default: + // TODO: We may want to generate a ContainerRemoved event as well. + // It's ok now because no one relies on the ContainerRemoved event. + return &PodLifecycleEvent{ID: podID, Type: ContainerDied, Data: cid} + } + default: + panic(fmt.Sprintf("unrecognized container state: %v", newState)) + } +} + +func (g *GenericPLEG) getRelistTime() time.Time { + val := g.relistTime.Load() + if val == nil { + return time.Time{} + } + return val.(time.Time) +} + +func (g *GenericPLEG) updateRelisTime(timestamp time.Time) { + g.relistTime.Store(timestamp) +} + +// relist queries the container runtime for list of pods/containers, compare +// with the internal pods/containers, and generats events accordingly. +func (g *GenericPLEG) relist() { + glog.V(5).Infof("GenericPLEG: Relisting") + + if lastRelistTime := g.getRelistTime(); !lastRelistTime.IsZero() { + metrics.PLEGRelistInterval.Observe(metrics.SinceInMicroseconds(lastRelistTime)) + } + + timestamp := g.clock.Now() + // Update the relist time. + g.updateRelisTime(timestamp) + defer func() { + metrics.PLEGRelistLatency.Observe(metrics.SinceInMicroseconds(timestamp)) + }() + + // Get all the pods. + podList, err := g.runtime.GetPods(true) + if err != nil { + glog.Errorf("GenericPLEG: Unable to retrieve pods: %v", err) + return + } + pods := kubecontainer.Pods(podList) + g.podRecords.setCurrent(pods) + + // Compare the old and the current pods, and generate events. + eventsByPodID := map[types.UID][]*PodLifecycleEvent{} + for pid := range g.podRecords { + oldPod := g.podRecords.getOld(pid) + pod := g.podRecords.getCurrent(pid) + // Get all containers in the old and the new pod. + allContainers := getContainersFromPods(oldPod, pod) + for _, container := range allContainers { + e := computeEvent(oldPod, pod, &container.ID) + updateEvents(eventsByPodID, e) + } + } + + // If there are events associated with a pod, we should update the + // podCache. + for pid, events := range eventsByPodID { + pod := g.podRecords.getCurrent(pid) + if g.cacheEnabled() { + // updateCache() will inspect the pod and update the cache. If an + // error occurs during the inspection, we want PLEG to retry again + // in the next relist. To achieve this, we do not update the + // associated podRecord of the pod, so that the change will be + // detect again in the next relist. + // TODO: If many pods changed during the same relist period, + // inspecting the pod and getting the PodStatus to update the cache + // serially may take a while. We should be aware of this and + // parallelize if needed. + if err := g.updateCache(pod, pid); err != nil { + glog.Errorf("PLEG: Ignoring events for pod %s/%s: %v", pod.Name, pod.Namespace, err) + continue + } + } + // Update the internal storage and send out the events. + g.podRecords.update(pid) + for i := range events { + // Filter out events that are not reliable and no other components use yet. + if events[i].Type == ContainerChanged || events[i].Type == ContainerRemoved { + continue + } + g.eventChannel <- events[i] + } + } + + if g.cacheEnabled() { + // Update the cache timestamp. This needs to happen *after* + // all pods have been properly updated in the cache. + g.cache.UpdateTime(timestamp) + } +} + +func getContainersFromPods(pods ...*kubecontainer.Pod) []*kubecontainer.Container { + cidSet := sets.NewString() + var containers []*kubecontainer.Container + for _, p := range pods { + if p == nil { + continue + } + for _, c := range p.Containers { + cid := string(c.ID.ID) + if cidSet.Has(cid) { + continue + } + cidSet.Insert(cid) + containers = append(containers, c) + } + } + return containers +} + +func computeEvent(oldPod, newPod *kubecontainer.Pod, cid *kubecontainer.ContainerID) *PodLifecycleEvent { + var pid types.UID + if oldPod != nil { + pid = oldPod.ID + } else if newPod != nil { + pid = newPod.ID + } + oldState := getContainerState(oldPod, cid) + newState := getContainerState(newPod, cid) + return generateEvent(pid, cid.ID, oldState, newState) +} + +func (g *GenericPLEG) cacheEnabled() bool { + return g.cache != nil +} + +func (g *GenericPLEG) updateCache(pod *kubecontainer.Pod, pid types.UID) error { + if pod == nil { + // The pod is missing in the current relist. This means that + // the pod has no visible (active or inactive) containers. + glog.V(4).Infof("PLEG: Delete status for pod %q", string(pid)) + g.cache.Delete(pid) + return nil + } + timestamp := g.clock.Now() + // TODO: Consider adding a new runtime method + // GetPodStatus(pod *kubecontainer.Pod) so that Docker can avoid listing + // all containers again. + status, err := g.runtime.GetPodStatus(pod.ID, pod.Name, pod.Namespace) + glog.V(4).Infof("PLEG: Write status for %s/%s: %+v (err: %v)", pod.Name, pod.Namespace, status, err) + g.cache.Set(pod.ID, status, err, timestamp) + return err +} + +func updateEvents(eventsByPodID map[types.UID][]*PodLifecycleEvent, e *PodLifecycleEvent) { + if e == nil { + return + } + eventsByPodID[e.ID] = append(eventsByPodID[e.ID], e) +} + +func getContainerState(pod *kubecontainer.Pod, cid *kubecontainer.ContainerID) plegContainerState { + // Default to the non-existent state. + state := plegContainerNonExistent + if pod == nil { + return state + } + container := pod.FindContainerByID(*cid) + if container == nil { + return state + } + return convertState(container.State) +} + +func (pr podRecords) getOld(id types.UID) *kubecontainer.Pod { + r, ok := pr[id] + if !ok { + return nil + } + return r.old +} + +func (pr podRecords) getCurrent(id types.UID) *kubecontainer.Pod { + r, ok := pr[id] + if !ok { + return nil + } + return r.current +} + +func (pr podRecords) setCurrent(pods []*kubecontainer.Pod) { + for i := range pr { + pr[i].current = nil + } + for _, pod := range pods { + if r, ok := pr[pod.ID]; ok { + r.current = pod + } else { + pr[pod.ID] = &podRecord{current: pod} + } + } +} + +func (pr podRecords) update(id types.UID) { + r, ok := pr[id] + if !ok { + return + } + pr.updateInternal(id, r) +} + +func (pr podRecords) updateInternal(id types.UID, r *podRecord) { + if r.current == nil { + // Pod no longer exists; delete the entry. + delete(pr, id) + return + } + r.old = r.current + r.current = nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/pleg/generic_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/pleg/generic_test.go new file mode 100644 index 000000000..3e3c0f24d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/pleg/generic_test.go @@ -0,0 +1,358 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 pleg + +import ( + "fmt" + "reflect" + "sort" + "testing" + "time" + + "github.com/stretchr/testify/assert" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" +) + +const ( + testContainerRuntimeType = "fooRuntime" +) + +type TestGenericPLEG struct { + pleg *GenericPLEG + runtime *containertest.FakeRuntime + clock *util.FakeClock +} + +func newTestGenericPLEG() *TestGenericPLEG { + fakeRuntime := &containertest.FakeRuntime{} + clock := util.NewFakeClock(time.Time{}) + // The channel capacity should be large enough to hold all events in a + // single test. + pleg := &GenericPLEG{ + relistPeriod: time.Hour, + runtime: fakeRuntime, + eventChannel: make(chan *PodLifecycleEvent, 100), + podRecords: make(podRecords), + clock: clock, + } + return &TestGenericPLEG{pleg: pleg, runtime: fakeRuntime, clock: clock} +} + +func getEventsFromChannel(ch <-chan *PodLifecycleEvent) []*PodLifecycleEvent { + events := []*PodLifecycleEvent{} + for len(ch) > 0 { + e := <-ch + events = append(events, e) + } + return events +} + +func createTestContainer(ID string, state kubecontainer.ContainerState) *kubecontainer.Container { + return &kubecontainer.Container{ + ID: kubecontainer.ContainerID{Type: testContainerRuntimeType, ID: ID}, + State: state, + } +} + +type sortableEvents []*PodLifecycleEvent + +func (a sortableEvents) Len() int { return len(a) } +func (a sortableEvents) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a sortableEvents) Less(i, j int) bool { + if a[i].ID != a[j].ID { + return a[i].ID < a[j].ID + } + return a[i].Data.(string) < a[j].Data.(string) +} + +func verifyEvents(t *testing.T, expected, actual []*PodLifecycleEvent) { + sort.Sort(sortableEvents(expected)) + sort.Sort(sortableEvents(actual)) + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Actual events differ from the expected; diff:\n %v", diff.ObjectDiff(expected, actual)) + } +} + +func TestRelisting(t *testing.T) { + testPleg := newTestGenericPLEG() + pleg, runtime := testPleg.pleg, testPleg.runtime + ch := pleg.Watch() + // The first relist should send a PodSync event to each pod. + runtime.AllPodList = []*kubecontainer.Pod{ + { + ID: "1234", + Containers: []*kubecontainer.Container{ + createTestContainer("c1", kubecontainer.ContainerStateExited), + createTestContainer("c2", kubecontainer.ContainerStateRunning), + createTestContainer("c3", kubecontainer.ContainerStateUnknown), + }, + }, + { + ID: "4567", + Containers: []*kubecontainer.Container{ + createTestContainer("c1", kubecontainer.ContainerStateExited), + }, + }, + } + pleg.relist() + // Report every running/exited container if we see them for the first time. + expected := []*PodLifecycleEvent{ + {ID: "1234", Type: ContainerStarted, Data: "c2"}, + {ID: "4567", Type: ContainerDied, Data: "c1"}, + {ID: "1234", Type: ContainerDied, Data: "c1"}, + } + actual := getEventsFromChannel(ch) + verifyEvents(t, expected, actual) + + // The second relist should not send out any event because no container + // changed. + pleg.relist() + verifyEvents(t, expected, actual) + + runtime.AllPodList = []*kubecontainer.Pod{ + { + ID: "1234", + Containers: []*kubecontainer.Container{ + createTestContainer("c2", kubecontainer.ContainerStateExited), + createTestContainer("c3", kubecontainer.ContainerStateRunning), + }, + }, + { + ID: "4567", + Containers: []*kubecontainer.Container{ + createTestContainer("c4", kubecontainer.ContainerStateRunning), + }, + }, + } + pleg.relist() + // Only report containers that transitioned to running or exited status. + expected = []*PodLifecycleEvent{ + {ID: "1234", Type: ContainerDied, Data: "c2"}, + {ID: "1234", Type: ContainerStarted, Data: "c3"}, + {ID: "4567", Type: ContainerStarted, Data: "c4"}, + } + + actual = getEventsFromChannel(ch) + verifyEvents(t, expected, actual) +} + +func TestDetectingContainerDeaths(t *testing.T) { + // Vary the number of relists after the container started and before the + // container died to account for the changes in pleg's internal states. + testReportMissingContainers(t, 1) + testReportMissingPods(t, 1) + + testReportMissingContainers(t, 3) + testReportMissingPods(t, 3) +} + +func testReportMissingContainers(t *testing.T, numRelists int) { + testPleg := newTestGenericPLEG() + pleg, runtime := testPleg.pleg, testPleg.runtime + ch := pleg.Watch() + runtime.AllPodList = []*kubecontainer.Pod{ + { + ID: "1234", + Containers: []*kubecontainer.Container{ + createTestContainer("c1", kubecontainer.ContainerStateRunning), + createTestContainer("c2", kubecontainer.ContainerStateRunning), + createTestContainer("c3", kubecontainer.ContainerStateExited), + }, + }, + } + // Relist and drain the events from the channel. + for i := 0; i < numRelists; i++ { + pleg.relist() + getEventsFromChannel(ch) + } + + // Container c2 was stopped and removed between relists. We should report + // the event. The exited container c3 was garbage collected (i.e., removed) + // between relists. We should ignore that event. + runtime.AllPodList = []*kubecontainer.Pod{ + { + ID: "1234", + Containers: []*kubecontainer.Container{ + createTestContainer("c1", kubecontainer.ContainerStateRunning), + }, + }, + } + pleg.relist() + expected := []*PodLifecycleEvent{ + {ID: "1234", Type: ContainerDied, Data: "c2"}, + } + actual := getEventsFromChannel(ch) + verifyEvents(t, expected, actual) +} + +func testReportMissingPods(t *testing.T, numRelists int) { + testPleg := newTestGenericPLEG() + pleg, runtime := testPleg.pleg, testPleg.runtime + ch := pleg.Watch() + runtime.AllPodList = []*kubecontainer.Pod{ + { + ID: "1234", + Containers: []*kubecontainer.Container{ + createTestContainer("c2", kubecontainer.ContainerStateRunning), + }, + }, + } + // Relist and drain the events from the channel. + for i := 0; i < numRelists; i++ { + pleg.relist() + getEventsFromChannel(ch) + } + + // Container c2 was stopped and removed between relists. We should report + // the event. + runtime.AllPodList = []*kubecontainer.Pod{} + pleg.relist() + expected := []*PodLifecycleEvent{ + {ID: "1234", Type: ContainerDied, Data: "c2"}, + } + actual := getEventsFromChannel(ch) + verifyEvents(t, expected, actual) +} + +func newTestGenericPLEGWithRuntimeMock() (*GenericPLEG, *containertest.Mock) { + runtimeMock := &containertest.Mock{} + pleg := &GenericPLEG{ + relistPeriod: time.Hour, + runtime: runtimeMock, + eventChannel: make(chan *PodLifecycleEvent, 100), + podRecords: make(podRecords), + cache: kubecontainer.NewCache(), + clock: util.RealClock{}, + } + return pleg, runtimeMock +} + +func createTestPodsStatusesAndEvents(num int) ([]*kubecontainer.Pod, []*kubecontainer.PodStatus, []*PodLifecycleEvent) { + var pods []*kubecontainer.Pod + var statuses []*kubecontainer.PodStatus + var events []*PodLifecycleEvent + for i := 0; i < num; i++ { + id := types.UID(fmt.Sprintf("test-pod-%d", i)) + cState := kubecontainer.ContainerStateRunning + container := createTestContainer(fmt.Sprintf("c%d", i), cState) + pod := &kubecontainer.Pod{ + ID: id, + Containers: []*kubecontainer.Container{container}, + } + status := &kubecontainer.PodStatus{ + ID: id, + ContainerStatuses: []*kubecontainer.ContainerStatus{{ID: container.ID, State: cState}}, + } + event := &PodLifecycleEvent{ID: pod.ID, Type: ContainerStarted, Data: container.ID.ID} + pods = append(pods, pod) + statuses = append(statuses, status) + events = append(events, event) + + } + return pods, statuses, events +} + +func TestRelistWithCache(t *testing.T) { + pleg, runtimeMock := newTestGenericPLEGWithRuntimeMock() + ch := pleg.Watch() + + pods, statuses, events := createTestPodsStatusesAndEvents(2) + runtimeMock.On("GetPods", true).Return(pods, nil) + runtimeMock.On("GetPodStatus", pods[0].ID, "", "").Return(statuses[0], nil).Once() + // Inject an error when querying runtime for the pod status for pods[1]. + statusErr := fmt.Errorf("unable to get status") + runtimeMock.On("GetPodStatus", pods[1].ID, "", "").Return(&kubecontainer.PodStatus{}, statusErr).Once() + + pleg.relist() + actualEvents := getEventsFromChannel(ch) + cases := []struct { + pod *kubecontainer.Pod + status *kubecontainer.PodStatus + error error + }{ + {pod: pods[0], status: statuses[0], error: nil}, + {pod: pods[1], status: &kubecontainer.PodStatus{}, error: statusErr}, + } + for i, c := range cases { + testStr := fmt.Sprintf("test[%d]", i) + actualStatus, actualErr := pleg.cache.Get(c.pod.ID) + assert.Equal(t, c.status, actualStatus, testStr) + assert.Equal(t, c.error, actualErr, testStr) + } + // pleg should not generate any event for pods[1] because of the error. + assert.Exactly(t, []*PodLifecycleEvent{events[0]}, actualEvents) + + // Return normal status for pods[1]. + runtimeMock.On("GetPodStatus", pods[1].ID, "", "").Return(statuses[1], nil).Once() + pleg.relist() + actualEvents = getEventsFromChannel(ch) + cases = []struct { + pod *kubecontainer.Pod + status *kubecontainer.PodStatus + error error + }{ + {pod: pods[0], status: statuses[0], error: nil}, + {pod: pods[1], status: statuses[1], error: nil}, + } + for i, c := range cases { + testStr := fmt.Sprintf("test[%d]", i) + actualStatus, actualErr := pleg.cache.Get(c.pod.ID) + assert.Equal(t, c.status, actualStatus, testStr) + assert.Equal(t, c.error, actualErr, testStr) + } + // Now that we are able to query status for pods[1], pleg should generate an event. + assert.Exactly(t, []*PodLifecycleEvent{events[1]}, actualEvents) +} + +func TestRemoveCacheEntry(t *testing.T) { + pleg, runtimeMock := newTestGenericPLEGWithRuntimeMock() + pods, statuses, _ := createTestPodsStatusesAndEvents(1) + runtimeMock.On("GetPods", true).Return(pods, nil).Once() + runtimeMock.On("GetPodStatus", pods[0].ID, "", "").Return(statuses[0], nil).Once() + // Does a relist to populate the cache. + pleg.relist() + // Delete the pod from runtime. Verify that the cache entry has been + // removed after relisting. + runtimeMock.On("GetPods", true).Return([]*kubecontainer.Pod{}, nil).Once() + pleg.relist() + actualStatus, actualErr := pleg.cache.Get(pods[0].ID) + assert.Equal(t, &kubecontainer.PodStatus{ID: pods[0].ID}, actualStatus) + assert.Equal(t, nil, actualErr) +} + +func TestHealthy(t *testing.T) { + testPleg := newTestGenericPLEG() + pleg, _, clock := testPleg.pleg, testPleg.runtime, testPleg.clock + ok, _ := pleg.Healthy() + assert.True(t, ok, "pleg should be healthy") + + // Advance the clock without any relisting. + clock.Step(time.Minute * 10) + ok, _ = pleg.Healthy() + assert.False(t, ok, "pleg should be unhealthy") + + // Relist and than advance the time by 1 minute. pleg should be healthy + // because this is within the allowed limit. + pleg.relist() + clock.Step(time.Minute * 1) + ok, _ = pleg.Healthy() + assert.True(t, ok, "pleg should be healthy") +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/pleg/pleg.go b/vendor/k8s.io/kubernetes/pkg/kubelet/pleg/pleg.go new file mode 100644 index 000000000..017982372 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/pleg/pleg.go @@ -0,0 +1,52 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 pleg + +import ( + "k8s.io/kubernetes/pkg/types" +) + +type PodLifeCycleEventType string + +const ( + ContainerStarted PodLifeCycleEventType = "ContainerStarted" + ContainerDied PodLifeCycleEventType = "ContainerDied" + // PodSync is used to trigger syncing of a pod when the observed change of + // the state of the pod cannot be captured by any single event above. + PodSync PodLifeCycleEventType = "PodSync" + // Do not use the events below because they are disabled in GenericPLEG. + ContainerRemoved PodLifeCycleEventType = "ContainerRemoved" + ContainerChanged PodLifeCycleEventType = "ContainerChanged" +) + +// PodLifecycleEvent is an event that reflects the change of the pod state. +type PodLifecycleEvent struct { + // The pod ID. + ID types.UID + // The type of the event. + Type PodLifeCycleEventType + // The accompanied data which varies based on the event type. + // - ContainerStarted/ContainerStopped: the container name (string). + // - All other event types: unused. + Data interface{} +} + +type PodLifecycleEventGenerator interface { + Start() + Watch() chan *PodLifecycleEvent + Healthy() (bool, error) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/pod/manager.go b/vendor/k8s.io/kubernetes/pkg/kubelet/pod/manager.go new file mode 100644 index 000000000..57b418102 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/pod/manager.go @@ -0,0 +1,285 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 pod + +import ( + "sync" + + "k8s.io/kubernetes/pkg/api" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/types" +) + +// Pod manager stores and manages access to the pods. +// +// Kubelet discovers pod updates from 3 sources: file, http, and apiserver. +// Pods from non-apiserver sources are called static pods, and API server is +// not aware of the existence of static pods. In order to monitor the status of +// such pods, kubelet creates a mirror pod for each static pod via the API +// server. +// +// A mirror pod has the same pod full name (name and namespace) as its static +// counterpart (albeit different metadata such as UID, etc). By leveraging the +// fact that kubelet reports the pod status using the pod full name, the status +// of the mirror pod always reflects the actual status of the static pod. +// When a static pod gets deleted, the associated orphaned mirror pod will +// also be removed. + +type Manager interface { + GetPods() []*api.Pod + GetPodByFullName(podFullName string) (*api.Pod, bool) + GetPodByName(namespace, name string) (*api.Pod, bool) + GetPodByUID(types.UID) (*api.Pod, bool) + GetPodByMirrorPod(*api.Pod) (*api.Pod, bool) + GetMirrorPodByPod(*api.Pod) (*api.Pod, bool) + GetPodsAndMirrorPods() ([]*api.Pod, []*api.Pod) + + // SetPods replaces the internal pods with the new pods. + // It is currently only used for testing. + SetPods(pods []*api.Pod) + + // Methods that modify a single pod. + AddPod(pod *api.Pod) + UpdatePod(pod *api.Pod) + DeletePod(pod *api.Pod) + + DeleteOrphanedMirrorPods() + TranslatePodUID(uid types.UID) types.UID + GetUIDTranslations() (podToMirror, mirrorToPod map[types.UID]types.UID) + IsMirrorPodOf(mirrorPod, pod *api.Pod) bool + MirrorClient +} + +// All maps in basicManager should be set by calling UpdatePods(); +// individual arrays/maps are not immutable and no other methods should attempt +// to modify them. +type basicManager struct { + // Protects all internal maps. + lock sync.RWMutex + + // Regular pods indexed by UID. + podByUID map[types.UID]*api.Pod + // Mirror pods indexed by UID. + mirrorPodByUID map[types.UID]*api.Pod + + // Pods indexed by full name for easy access. + podByFullName map[string]*api.Pod + mirrorPodByFullName map[string]*api.Pod + + // Mirror pod UID to pod UID map. + translationByUID map[types.UID]types.UID + + // A mirror pod client to create/delete mirror pods. + MirrorClient +} + +func NewBasicPodManager(client MirrorClient) Manager { + pm := &basicManager{} + pm.MirrorClient = client + pm.SetPods(nil) + return pm +} + +// Set the internal pods based on the new pods. +func (pm *basicManager) SetPods(newPods []*api.Pod) { + pm.lock.Lock() + defer pm.lock.Unlock() + + pm.podByUID = make(map[types.UID]*api.Pod) + pm.podByFullName = make(map[string]*api.Pod) + pm.mirrorPodByUID = make(map[types.UID]*api.Pod) + pm.mirrorPodByFullName = make(map[string]*api.Pod) + pm.translationByUID = make(map[types.UID]types.UID) + + pm.updatePodsInternal(newPods...) +} + +func (pm *basicManager) AddPod(pod *api.Pod) { + pm.UpdatePod(pod) +} + +func (pm *basicManager) UpdatePod(pod *api.Pod) { + pm.lock.Lock() + defer pm.lock.Unlock() + pm.updatePodsInternal(pod) +} + +func (pm *basicManager) updatePodsInternal(pods ...*api.Pod) { + for _, pod := range pods { + podFullName := kubecontainer.GetPodFullName(pod) + if IsMirrorPod(pod) { + pm.mirrorPodByUID[pod.UID] = pod + pm.mirrorPodByFullName[podFullName] = pod + if p, ok := pm.podByFullName[podFullName]; ok { + pm.translationByUID[pod.UID] = p.UID + } + } else { + pm.podByUID[pod.UID] = pod + pm.podByFullName[podFullName] = pod + if mirror, ok := pm.mirrorPodByFullName[podFullName]; ok { + pm.translationByUID[mirror.UID] = pod.UID + } + } + } +} + +func (pm *basicManager) DeletePod(pod *api.Pod) { + pm.lock.Lock() + defer pm.lock.Unlock() + podFullName := kubecontainer.GetPodFullName(pod) + if IsMirrorPod(pod) { + delete(pm.mirrorPodByUID, pod.UID) + delete(pm.mirrorPodByFullName, podFullName) + delete(pm.translationByUID, pod.UID) + } else { + delete(pm.podByUID, pod.UID) + delete(pm.podByFullName, podFullName) + } +} + +// GetPods returns the regular pods bound to the kubelet and their spec. +func (pm *basicManager) GetPods() []*api.Pod { + pm.lock.RLock() + defer pm.lock.RUnlock() + return podsMapToPods(pm.podByUID) +} + +// GetPodsAndMirrorPods returns the both regular and mirror pods. +func (pm *basicManager) GetPodsAndMirrorPods() ([]*api.Pod, []*api.Pod) { + pm.lock.RLock() + defer pm.lock.RUnlock() + pods := podsMapToPods(pm.podByUID) + mirrorPods := podsMapToPods(pm.mirrorPodByUID) + return pods, mirrorPods +} + +// Returns all pods (including mirror pods). +func (pm *basicManager) getAllPods() []*api.Pod { + return append(podsMapToPods(pm.podByUID), podsMapToPods(pm.mirrorPodByUID)...) +} + +// GetPodByUID provides the (non-mirror) pod that matches pod UID, as well as +// whether the pod is found. +func (pm *basicManager) GetPodByUID(uid types.UID) (*api.Pod, bool) { + pm.lock.RLock() + defer pm.lock.RUnlock() + pod, ok := pm.podByUID[uid] + return pod, ok +} + +// GetPodByName provides the (non-mirror) pod that matches namespace and name, +// as well as whether the pod was found. +func (pm *basicManager) GetPodByName(namespace, name string) (*api.Pod, bool) { + podFullName := kubecontainer.BuildPodFullName(name, namespace) + return pm.GetPodByFullName(podFullName) +} + +// GetPodByName returns the (non-mirror) pod that matches full name, as well as +// whether the pod was found. +func (pm *basicManager) GetPodByFullName(podFullName string) (*api.Pod, bool) { + pm.lock.RLock() + defer pm.lock.RUnlock() + pod, ok := pm.podByFullName[podFullName] + return pod, ok +} + +// If the UID belongs to a mirror pod, maps it to the UID of its static pod. +// Otherwise, return the original UID. All public-facing functions should +// perform this translation for UIDs because user may provide a mirror pod UID, +// which is not recognized by internal Kubelet functions. +func (pm *basicManager) TranslatePodUID(uid types.UID) types.UID { + if uid == "" { + return uid + } + + pm.lock.RLock() + defer pm.lock.RUnlock() + if translated, ok := pm.translationByUID[uid]; ok { + return translated + } + return uid +} + +func (pm *basicManager) GetUIDTranslations() (podToMirror, mirrorToPod map[types.UID]types.UID) { + pm.lock.RLock() + defer pm.lock.RUnlock() + + podToMirror = make(map[types.UID]types.UID, len(pm.translationByUID)) + mirrorToPod = make(map[types.UID]types.UID, len(pm.translationByUID)) + for k, v := range pm.translationByUID { + mirrorToPod[k] = v + podToMirror[v] = k + } + return podToMirror, mirrorToPod +} + +func (pm *basicManager) getOrphanedMirrorPodNames() []string { + pm.lock.RLock() + defer pm.lock.RUnlock() + var podFullNames []string + for podFullName := range pm.mirrorPodByFullName { + if _, ok := pm.podByFullName[podFullName]; !ok { + podFullNames = append(podFullNames, podFullName) + } + } + return podFullNames +} + +// Delete all mirror pods which do not have associated static pods. This method +// sends deletion requets to the API server, but does NOT modify the internal +// pod storage in basicManager. +func (pm *basicManager) DeleteOrphanedMirrorPods() { + podFullNames := pm.getOrphanedMirrorPodNames() + for _, podFullName := range podFullNames { + pm.MirrorClient.DeleteMirrorPod(podFullName) + } +} + +// Returns true if mirrorPod is a correct representation of pod; false otherwise. +func (pm *basicManager) IsMirrorPodOf(mirrorPod, pod *api.Pod) bool { + // Check name and namespace first. + if pod.Name != mirrorPod.Name || pod.Namespace != mirrorPod.Namespace { + return false + } + hash, ok := getHashFromMirrorPod(mirrorPod) + if !ok { + return false + } + return hash == getPodHash(pod) +} + +func podsMapToPods(UIDMap map[types.UID]*api.Pod) []*api.Pod { + pods := make([]*api.Pod, 0, len(UIDMap)) + for _, pod := range UIDMap { + pods = append(pods, pod) + } + return pods +} + +func (pm *basicManager) GetMirrorPodByPod(pod *api.Pod) (*api.Pod, bool) { + pm.lock.RLock() + defer pm.lock.RUnlock() + mirrorPod, ok := pm.mirrorPodByFullName[kubecontainer.GetPodFullName(pod)] + return mirrorPod, ok +} + +func (pm *basicManager) GetPodByMirrorPod(mirrorPod *api.Pod) (*api.Pod, bool) { + pm.lock.RLock() + defer pm.lock.RUnlock() + pod, ok := pm.podByFullName[kubecontainer.GetPodFullName(mirrorPod)] + return pod, ok +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/pod/manager_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/pod/manager_test.go new file mode 100644 index 000000000..965e24ef2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/pod/manager_test.go @@ -0,0 +1,110 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 pod + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + podtest "k8s.io/kubernetes/pkg/kubelet/pod/testing" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" +) + +// Stub out mirror client for testing purpose. +func newTestManager() (*basicManager, *podtest.FakeMirrorClient) { + fakeMirrorClient := podtest.NewFakeMirrorClient() + manager := NewBasicPodManager(fakeMirrorClient).(*basicManager) + return manager, fakeMirrorClient +} + +// Tests that pods/maps are properly set after the pod update, and the basic +// methods work correctly. +func TestGetSetPods(t *testing.T) { + mirrorPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "987654321", + Name: "bar", + Namespace: "default", + Annotations: map[string]string{ + kubetypes.ConfigSourceAnnotationKey: "api", + kubetypes.ConfigMirrorAnnotationKey: "mirror", + }, + }, + } + staticPod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "123456789", + Name: "bar", + Namespace: "default", + Annotations: map[string]string{kubetypes.ConfigSourceAnnotationKey: "file"}, + }, + } + + expectedPods := []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + UID: "999999999", + Name: "taco", + Namespace: "default", + Annotations: map[string]string{kubetypes.ConfigSourceAnnotationKey: "api"}, + }, + }, + staticPod, + } + updates := append(expectedPods, mirrorPod) + podManager, _ := newTestManager() + podManager.SetPods(updates) + + // Tests that all regular pods are recorded corrrectly. + actualPods := podManager.GetPods() + if len(actualPods) != len(expectedPods) { + t.Errorf("expected %d pods, got %d pods; expected pods %#v, got pods %#v", len(expectedPods), len(actualPods), + expectedPods, actualPods) + } + for _, expected := range expectedPods { + found := false + for _, actual := range actualPods { + if actual.UID == expected.UID { + if !reflect.DeepEqual(&expected, &actual) { + t.Errorf("pod was recorded incorrectly. expect: %#v, got: %#v", expected, actual) + } + found = true + break + } + } + if !found { + t.Errorf("pod %q was not found in %#v", expected.UID, actualPods) + } + } + // Tests UID translation works as expected. + if uid := podManager.TranslatePodUID(mirrorPod.UID); uid != staticPod.UID { + t.Errorf("unable to translate UID %q to the static POD's UID %q; %#v", + mirrorPod.UID, staticPod.UID, podManager.mirrorPodByUID) + } + + // Test the basic Get methods. + actualPod, ok := podManager.GetPodByFullName("bar_default") + if !ok || !reflect.DeepEqual(actualPod, staticPod) { + t.Errorf("unable to get pod by full name; expected: %#v, got: %#v", staticPod, actualPod) + } + actualPod, ok = podManager.GetPodByName("default", "bar") + if !ok || !reflect.DeepEqual(actualPod, staticPod) { + t.Errorf("unable to get pod by name; expected: %#v, got: %#v", staticPod, actualPod) + } + +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/pod/mirror_client.go b/vendor/k8s.io/kubernetes/pkg/kubelet/pod/mirror_client.go new file mode 100644 index 000000000..f2133d3c2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/pod/mirror_client.go @@ -0,0 +1,103 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 pod + +import ( + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" +) + +// Mirror client is used to create/delete a mirror pod. +type MirrorClient interface { + CreateMirrorPod(*api.Pod) error + DeleteMirrorPod(string) error +} + +type basicMirrorClient struct { + // mirror pods are stored in the kubelet directly because they need to be + // in sync with the internal pods. + apiserverClient clientset.Interface +} + +func NewBasicMirrorClient(apiserverClient clientset.Interface) MirrorClient { + return &basicMirrorClient{apiserverClient: apiserverClient} +} + +// Creates a mirror pod. +func (mc *basicMirrorClient) CreateMirrorPod(pod *api.Pod) error { + if mc.apiserverClient == nil { + return nil + } + // Make a copy of the pod. + copyPod := *pod + copyPod.Annotations = make(map[string]string) + + for k, v := range pod.Annotations { + copyPod.Annotations[k] = v + } + hash := getPodHash(pod) + copyPod.Annotations[kubetypes.ConfigMirrorAnnotationKey] = hash + apiPod, err := mc.apiserverClient.Core().Pods(copyPod.Namespace).Create(©Pod) + if err != nil && errors.IsAlreadyExists(err) { + // Check if the existing pod is the same as the pod we want to create. + if h, ok := apiPod.Annotations[kubetypes.ConfigMirrorAnnotationKey]; ok && h == hash { + return nil + } + } + return err +} + +// Deletes a mirror pod. +func (mc *basicMirrorClient) DeleteMirrorPod(podFullName string) error { + if mc.apiserverClient == nil { + return nil + } + name, namespace, err := kubecontainer.ParsePodFullName(podFullName) + if err != nil { + glog.Errorf("Failed to parse a pod full name %q", podFullName) + return err + } + glog.V(4).Infof("Deleting a mirror pod %q", podFullName) + if err := mc.apiserverClient.Core().Pods(namespace).Delete(name, api.NewDeleteOptions(0)); err != nil && !errors.IsNotFound(err) { + glog.Errorf("Failed deleting a mirror pod %q: %v", podFullName, err) + } + return nil +} + +func IsStaticPod(pod *api.Pod) bool { + source, err := kubetypes.GetPodSource(pod) + return err == nil && source != kubetypes.ApiserverSource +} + +func IsMirrorPod(pod *api.Pod) bool { + _, ok := pod.Annotations[kubetypes.ConfigMirrorAnnotationKey] + return ok +} + +func getHashFromMirrorPod(pod *api.Pod) (string, bool) { + hash, ok := pod.Annotations[kubetypes.ConfigMirrorAnnotationKey] + return hash, ok +} + +func getPodHash(pod *api.Pod) string { + // The annotation exists for all static pods. + return pod.Annotations[kubetypes.ConfigHashAnnotationKey] +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/pod/mirror_client_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/pod/mirror_client_test.go new file mode 100644 index 000000000..d8baa05f8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/pod/mirror_client_test.go @@ -0,0 +1,54 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 pod + +import ( + "testing" + + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" +) + +func TestParsePodFullName(t *testing.T) { + type nameTuple struct { + Name string + Namespace string + } + successfulCases := map[string]nameTuple{ + "bar_foo": {Name: "bar", Namespace: "foo"}, + "bar.org_foo.com": {Name: "bar.org", Namespace: "foo.com"}, + "bar-bar_foo": {Name: "bar-bar", Namespace: "foo"}, + } + failedCases := []string{"barfoo", "bar_foo_foo", ""} + + for podFullName, expected := range successfulCases { + name, namespace, err := kubecontainer.ParsePodFullName(podFullName) + if err != nil { + t.Errorf("unexpected error when parsing the full name: %v", err) + continue + } + if name != expected.Name || namespace != expected.Namespace { + t.Errorf("expected name %q, namespace %q; got name %q, namespace %q", + expected.Name, expected.Namespace, name, namespace) + } + } + for _, podFullName := range failedCases { + _, _, err := kubecontainer.ParsePodFullName(podFullName) + if err == nil { + t.Errorf("expected error when parsing the full name, got none") + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/pod/testing/fake_mirror_client.go b/vendor/k8s.io/kubernetes/pkg/kubelet/pod/testing/fake_mirror_client.go new file mode 100644 index 000000000..64bfd2351 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/pod/testing/fake_mirror_client.go @@ -0,0 +1,83 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 testing + +import ( + "sync" + + "k8s.io/kubernetes/pkg/api" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/util/sets" +) + +type FakeMirrorClient struct { + mirrorPodLock sync.RWMutex + // Note that a real mirror manager does not store the mirror pods in + // itself. This fake manager does this to track calls. + mirrorPods sets.String + createCounts map[string]int + deleteCounts map[string]int +} + +func NewFakeMirrorClient() *FakeMirrorClient { + m := FakeMirrorClient{} + m.mirrorPods = sets.NewString() + m.createCounts = make(map[string]int) + m.deleteCounts = make(map[string]int) + return &m +} + +func (fmc *FakeMirrorClient) CreateMirrorPod(pod *api.Pod) error { + fmc.mirrorPodLock.Lock() + defer fmc.mirrorPodLock.Unlock() + podFullName := kubecontainer.GetPodFullName(pod) + fmc.mirrorPods.Insert(podFullName) + fmc.createCounts[podFullName]++ + return nil +} + +func (fmc *FakeMirrorClient) DeleteMirrorPod(podFullName string) error { + fmc.mirrorPodLock.Lock() + defer fmc.mirrorPodLock.Unlock() + fmc.mirrorPods.Delete(podFullName) + fmc.deleteCounts[podFullName]++ + return nil +} + +func (fmc *FakeMirrorClient) HasPod(podFullName string) bool { + fmc.mirrorPodLock.RLock() + defer fmc.mirrorPodLock.RUnlock() + return fmc.mirrorPods.Has(podFullName) +} + +func (fmc *FakeMirrorClient) NumOfPods() int { + fmc.mirrorPodLock.RLock() + defer fmc.mirrorPodLock.RUnlock() + return fmc.mirrorPods.Len() +} + +func (fmc *FakeMirrorClient) GetPods() []string { + fmc.mirrorPodLock.RLock() + defer fmc.mirrorPodLock.RUnlock() + return fmc.mirrorPods.List() +} + +func (fmc *FakeMirrorClient) GetCounts(podFullName string) (int, int) { + fmc.mirrorPodLock.RLock() + defer fmc.mirrorPodLock.RUnlock() + return fmc.createCounts[podFullName], fmc.deleteCounts[podFullName] +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/pod_workers.go b/vendor/k8s.io/kubernetes/pkg/kubelet/pod_workers.go new file mode 100644 index 000000000..15e7cf668 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/pod_workers.go @@ -0,0 +1,238 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "sync" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/kubelet/util/queue" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" +) + +// PodWorkers is an abstract interface for testability. +type PodWorkers interface { + UpdatePod(pod *api.Pod, mirrorPod *api.Pod, updateType kubetypes.SyncPodType, updateComplete func()) + ForgetNonExistingPodWorkers(desiredPods map[types.UID]empty) + ForgetWorker(uid types.UID) +} + +type syncPodFnType func(*api.Pod, *api.Pod, *kubecontainer.PodStatus, kubetypes.SyncPodType) error + +const ( + // jitter factor for resyncInterval + workerResyncIntervalJitterFactor = 0.5 + + // jitter factor for backOffPeriod + workerBackOffPeriodJitterFactor = 0.5 +) + +type podWorkers struct { + // Protects all per worker fields. + podLock sync.Mutex + + // Tracks all running per-pod goroutines - per-pod goroutine will be + // processing updates received through its corresponding channel. + podUpdates map[types.UID]chan workUpdate + // Track the current state of per-pod goroutines. + // Currently all update request for a given pod coming when another + // update of this pod is being processed are ignored. + isWorking map[types.UID]bool + // Tracks the last undelivered work item for this pod - a work item is + // undelivered if it comes in while the worker is working. + lastUndeliveredWorkUpdate map[types.UID]workUpdate + + workQueue queue.WorkQueue + + // This function is run to sync the desired stated of pod. + // NOTE: This function has to be thread-safe - it can be called for + // different pods at the same time. + syncPodFn syncPodFnType + + // The EventRecorder to use + recorder record.EventRecorder + + // backOffPeriod is the duration to back off when there is a sync error. + backOffPeriod time.Duration + + // resyncInterval is the duration to wait until the next sync. + resyncInterval time.Duration + + // podCache stores kubecontainer.PodStatus for all pods. + podCache kubecontainer.Cache +} + +type workUpdate struct { + // The pod state to reflect. + pod *api.Pod + + // The mirror pod of pod; nil if it does not exist. + mirrorPod *api.Pod + + // Function to call when the update is complete. + updateCompleteFn func() + + // A string describing the type of this update, eg: create + updateType kubetypes.SyncPodType +} + +func newPodWorkers(syncPodFn syncPodFnType, recorder record.EventRecorder, workQueue queue.WorkQueue, + resyncInterval, backOffPeriod time.Duration, podCache kubecontainer.Cache) *podWorkers { + return &podWorkers{ + podUpdates: map[types.UID]chan workUpdate{}, + isWorking: map[types.UID]bool{}, + lastUndeliveredWorkUpdate: map[types.UID]workUpdate{}, + syncPodFn: syncPodFn, + recorder: recorder, + workQueue: workQueue, + resyncInterval: resyncInterval, + backOffPeriod: backOffPeriod, + podCache: podCache, + } +} + +func (p *podWorkers) managePodLoop(podUpdates <-chan workUpdate) { + var lastSyncTime time.Time + for newWork := range podUpdates { + err := func() error { + podID := newWork.pod.UID + // This is a blocking call that would return only if the cache + // has an entry for the pod that is newer than minRuntimeCache + // Time. This ensures the worker doesn't start syncing until + // after the cache is at least newer than the finished time of + // the previous sync. + status, err := p.podCache.GetNewerThan(podID, lastSyncTime) + if err != nil { + return err + } + err = p.syncPodFn(newWork.pod, newWork.mirrorPod, status, newWork.updateType) + lastSyncTime = time.Now() + if err != nil { + return err + } + newWork.updateCompleteFn() + return nil + }() + if err != nil { + glog.Errorf("Error syncing pod %s, skipping: %v", newWork.pod.UID, err) + p.recorder.Eventf(newWork.pod, api.EventTypeWarning, kubecontainer.FailedSync, "Error syncing pod, skipping: %v", err) + } + p.wrapUp(newWork.pod.UID, err) + } +} + +// Apply the new setting to the specified pod. updateComplete is called when the update is completed. +func (p *podWorkers) UpdatePod(pod *api.Pod, mirrorPod *api.Pod, updateType kubetypes.SyncPodType, updateComplete func()) { + uid := pod.UID + var podUpdates chan workUpdate + var exists bool + + p.podLock.Lock() + defer p.podLock.Unlock() + if podUpdates, exists = p.podUpdates[uid]; !exists { + // We need to have a buffer here, because checkForUpdates() method that + // puts an update into channel is called from the same goroutine where + // the channel is consumed. However, it is guaranteed that in such case + // the channel is empty, so buffer of size 1 is enough. + podUpdates = make(chan workUpdate, 1) + p.podUpdates[uid] = podUpdates + + // Creating a new pod worker either means this is a new pod, or that the + // kubelet just restarted. In either case the kubelet is willing to believe + // the status of the pod for the first pod worker sync. See corresponding + // comment in syncPod. + go func() { + defer runtime.HandleCrash() + p.managePodLoop(podUpdates) + }() + } + if !p.isWorking[pod.UID] { + p.isWorking[pod.UID] = true + podUpdates <- workUpdate{ + pod: pod, + mirrorPod: mirrorPod, + updateCompleteFn: updateComplete, + updateType: updateType, + } + } else { + p.lastUndeliveredWorkUpdate[pod.UID] = workUpdate{ + pod: pod, + mirrorPod: mirrorPod, + updateCompleteFn: updateComplete, + updateType: updateType, + } + } +} + +func (p *podWorkers) removeWorker(uid types.UID) { + if ch, ok := p.podUpdates[uid]; ok { + close(ch) + delete(p.podUpdates, uid) + // If there is an undelivered work update for this pod we need to remove it + // since per-pod goroutine won't be able to put it to the already closed + // channel when it finish processing the current work update. + if _, cached := p.lastUndeliveredWorkUpdate[uid]; cached { + delete(p.lastUndeliveredWorkUpdate, uid) + } + } +} +func (p *podWorkers) ForgetWorker(uid types.UID) { + p.podLock.Lock() + defer p.podLock.Unlock() + p.removeWorker(uid) +} + +func (p *podWorkers) ForgetNonExistingPodWorkers(desiredPods map[types.UID]empty) { + p.podLock.Lock() + defer p.podLock.Unlock() + for key := range p.podUpdates { + if _, exists := desiredPods[key]; !exists { + p.removeWorker(key) + } + } +} + +func (p *podWorkers) wrapUp(uid types.UID, syncErr error) { + // Requeue the last update if the last sync returned error. + switch { + case syncErr == nil: + // No error; requeue at the regular resync interval. + p.workQueue.Enqueue(uid, wait.Jitter(p.resyncInterval, workerResyncIntervalJitterFactor)) + default: + // Error occurred during the sync; back off and then retry. + p.workQueue.Enqueue(uid, wait.Jitter(p.backOffPeriod, workerBackOffPeriodJitterFactor)) + } + p.checkForUpdates(uid) +} + +func (p *podWorkers) checkForUpdates(uid types.UID) { + p.podLock.Lock() + defer p.podLock.Unlock() + if workUpdate, exists := p.lastUndeliveredWorkUpdate[uid]; exists { + p.podUpdates[uid] <- workUpdate + delete(p.lastUndeliveredWorkUpdate, uid) + } else { + p.isWorking[uid] = false + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/pod_workers_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/pod_workers_test.go new file mode 100644 index 000000000..7181b7532 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/pod_workers_test.go @@ -0,0 +1,283 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "reflect" + "sync" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/kubelet/util/queue" + "k8s.io/kubernetes/pkg/types" +) + +// fakePodWorkers runs sync pod function in serial, so we can have +// deterministic behaviour in testing. +type fakePodWorkers struct { + syncPodFn syncPodFnType + cache kubecontainer.Cache + t TestingInterface +} + +func (f *fakePodWorkers) UpdatePod(pod *api.Pod, mirrorPod *api.Pod, updateType kubetypes.SyncPodType, updateComplete func()) { + status, err := f.cache.Get(pod.UID) + if err != nil { + f.t.Errorf("Unexpected error: %v", err) + } + if err := f.syncPodFn(pod, mirrorPod, status, kubetypes.SyncPodUpdate); err != nil { + f.t.Errorf("Unexpected error: %v", err) + } +} + +func (f *fakePodWorkers) ForgetNonExistingPodWorkers(desiredPods map[types.UID]empty) {} + +func (f *fakePodWorkers) ForgetWorker(uid types.UID) {} + +type TestingInterface interface { + Errorf(format string, args ...interface{}) +} + +func newPod(uid, name string) *api.Pod { + return &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: types.UID(uid), + Name: name, + }, + } +} + +func createPodWorkers() (*podWorkers, map[types.UID][]string) { + lock := sync.Mutex{} + processed := make(map[types.UID][]string) + fakeRecorder := &record.FakeRecorder{} + fakeRuntime := &containertest.FakeRuntime{} + fakeCache := containertest.NewFakeCache(fakeRuntime) + podWorkers := newPodWorkers( + func(pod *api.Pod, mirrorPod *api.Pod, status *kubecontainer.PodStatus, updateType kubetypes.SyncPodType) error { + func() { + lock.Lock() + defer lock.Unlock() + processed[pod.UID] = append(processed[pod.UID], pod.Name) + }() + return nil + }, + fakeRecorder, + queue.NewBasicWorkQueue(), + time.Second, + time.Second, + fakeCache, + ) + return podWorkers, processed +} + +func drainWorkers(podWorkers *podWorkers, numPods int) { + for { + stillWorking := false + podWorkers.podLock.Lock() + for i := 0; i < numPods; i++ { + if podWorkers.isWorking[types.UID(string(i))] { + stillWorking = true + } + } + podWorkers.podLock.Unlock() + if !stillWorking { + break + } + time.Sleep(50 * time.Millisecond) + } +} + +func TestUpdatePod(t *testing.T) { + podWorkers, processed := createPodWorkers() + + // Check whether all pod updates will be processed. + numPods := 20 + for i := 0; i < numPods; i++ { + for j := i; j < numPods; j++ { + podWorkers.UpdatePod(newPod(string(j), string(i)), nil, kubetypes.SyncPodCreate, func() {}) + } + } + drainWorkers(podWorkers, numPods) + + if len(processed) != 20 { + t.Errorf("Not all pods processed: %v", len(processed)) + return + } + for i := 0; i < numPods; i++ { + uid := types.UID(i) + if len(processed[uid]) < 1 || len(processed[uid]) > i+1 { + t.Errorf("Pod %v processed %v times", i, len(processed[uid])) + continue + } + + first := 0 + last := len(processed[uid]) - 1 + if processed[uid][first] != string(0) { + t.Errorf("Pod %v: incorrect order %v, %v", i, first, processed[uid][first]) + + } + if processed[uid][last] != string(i) { + t.Errorf("Pod %v: incorrect order %v, %v", i, last, processed[uid][last]) + } + } +} + +func TestForgetNonExistingPodWorkers(t *testing.T) { + podWorkers, _ := createPodWorkers() + + numPods := 20 + for i := 0; i < numPods; i++ { + podWorkers.UpdatePod(newPod(string(i), "name"), nil, kubetypes.SyncPodUpdate, func() {}) + } + drainWorkers(podWorkers, numPods) + + if len(podWorkers.podUpdates) != numPods { + t.Errorf("Incorrect number of open channels %v", len(podWorkers.podUpdates)) + } + + desiredPods := map[types.UID]empty{} + desiredPods[types.UID(2)] = empty{} + desiredPods[types.UID(14)] = empty{} + podWorkers.ForgetNonExistingPodWorkers(desiredPods) + if len(podWorkers.podUpdates) != 2 { + t.Errorf("Incorrect number of open channels %v", len(podWorkers.podUpdates)) + } + if _, exists := podWorkers.podUpdates[types.UID(2)]; !exists { + t.Errorf("No updates channel for pod 2") + } + if _, exists := podWorkers.podUpdates[types.UID(14)]; !exists { + t.Errorf("No updates channel for pod 14") + } + + podWorkers.ForgetNonExistingPodWorkers(map[types.UID]empty{}) + if len(podWorkers.podUpdates) != 0 { + t.Errorf("Incorrect number of open channels %v", len(podWorkers.podUpdates)) + } +} + +type simpleFakeKubelet struct { + pod *api.Pod + mirrorPod *api.Pod + podStatus *kubecontainer.PodStatus + wg sync.WaitGroup +} + +func (kl *simpleFakeKubelet) syncPod(pod *api.Pod, mirrorPod *api.Pod, status *kubecontainer.PodStatus, updateType kubetypes.SyncPodType) error { + kl.pod, kl.mirrorPod, kl.podStatus = pod, mirrorPod, status + return nil +} + +func (kl *simpleFakeKubelet) syncPodWithWaitGroup(pod *api.Pod, mirrorPod *api.Pod, status *kubecontainer.PodStatus, updateType kubetypes.SyncPodType) error { + kl.pod, kl.mirrorPod, kl.podStatus = pod, mirrorPod, status + kl.wg.Done() + return nil +} + +// byContainerName sort the containers in a running pod by their names. +type byContainerName kubecontainer.Pod + +func (b byContainerName) Len() int { return len(b.Containers) } + +func (b byContainerName) Swap(i, j int) { + b.Containers[i], b.Containers[j] = b.Containers[j], b.Containers[i] +} + +func (b byContainerName) Less(i, j int) bool { + return b.Containers[i].Name < b.Containers[j].Name +} + +// TestFakePodWorkers verifies that the fakePodWorkers behaves the same way as the real podWorkers +// for their invocation of the syncPodFn. +func TestFakePodWorkers(t *testing.T) { + fakeRecorder := &record.FakeRecorder{} + fakeRuntime := &containertest.FakeRuntime{} + fakeCache := containertest.NewFakeCache(fakeRuntime) + + kubeletForRealWorkers := &simpleFakeKubelet{} + kubeletForFakeWorkers := &simpleFakeKubelet{} + + realPodWorkers := newPodWorkers(kubeletForRealWorkers.syncPodWithWaitGroup, fakeRecorder, queue.NewBasicWorkQueue(), time.Second, time.Second, fakeCache) + fakePodWorkers := &fakePodWorkers{kubeletForFakeWorkers.syncPod, fakeCache, t} + + tests := []struct { + pod *api.Pod + mirrorPod *api.Pod + }{ + { + &api.Pod{}, + &api.Pod{}, + }, + { + &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + }, + &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "fooMirror", + Namespace: "new", + }, + }, + }, + { + &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "98765", + Name: "bar", + Namespace: "new", + }, + }, + &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "98765", + Name: "barMirror", + Namespace: "new", + }, + }, + }, + } + + for i, tt := range tests { + kubeletForRealWorkers.wg.Add(1) + realPodWorkers.UpdatePod(tt.pod, tt.mirrorPod, kubetypes.SyncPodUpdate, func() {}) + fakePodWorkers.UpdatePod(tt.pod, tt.mirrorPod, kubetypes.SyncPodUpdate, func() {}) + + kubeletForRealWorkers.wg.Wait() + + if !reflect.DeepEqual(kubeletForRealWorkers.pod, kubeletForFakeWorkers.pod) { + t.Errorf("%d: Expected: %#v, Actual: %#v", i, kubeletForRealWorkers.pod, kubeletForFakeWorkers.pod) + } + + if !reflect.DeepEqual(kubeletForRealWorkers.mirrorPod, kubeletForFakeWorkers.mirrorPod) { + t.Errorf("%d: Expected: %#v, Actual: %#v", i, kubeletForRealWorkers.mirrorPod, kubeletForFakeWorkers.mirrorPod) + } + + if !reflect.DeepEqual(kubeletForRealWorkers.podStatus, kubeletForFakeWorkers.podStatus) { + t.Errorf("%d: Expected: %#v, Actual: %#v", i, kubeletForRealWorkers.podStatus, kubeletForFakeWorkers.podStatus) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/prober/common_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/common_test.go new file mode 100644 index 000000000..aeb61ca21 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/common_test.go @@ -0,0 +1,147 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 prober + +import ( + "reflect" + "sync" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/record" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + kubepod "k8s.io/kubernetes/pkg/kubelet/pod" + "k8s.io/kubernetes/pkg/kubelet/prober/results" + "k8s.io/kubernetes/pkg/kubelet/status" + "k8s.io/kubernetes/pkg/probe" + "k8s.io/kubernetes/pkg/util/exec" +) + +const ( + testContainerName = "cOnTaInEr_NaMe" + testPodUID = "pOd_UiD" +) + +var testContainerID = kubecontainer.ContainerID{Type: "test", ID: "cOnTaInEr_Id"} + +func getTestRunningStatus() api.PodStatus { + containerStatus := api.ContainerStatus{ + Name: testContainerName, + ContainerID: testContainerID.String(), + } + containerStatus.State.Running = &api.ContainerStateRunning{StartedAt: unversioned.Now()} + podStatus := api.PodStatus{ + Phase: api.PodRunning, + ContainerStatuses: []api.ContainerStatus{containerStatus}, + } + return podStatus +} + +func getTestPod() *api.Pod { + container := api.Container{ + Name: testContainerName, + } + pod := api.Pod{ + Spec: api.PodSpec{ + Containers: []api.Container{container}, + RestartPolicy: api.RestartPolicyNever, + }, + } + pod.Name = "testPod" + pod.UID = testPodUID + return &pod +} + +func setTestProbe(pod *api.Pod, probeType probeType, probeSpec api.Probe) { + // All tests rely on the fake exec prober. + probeSpec.Handler = api.Handler{ + Exec: &api.ExecAction{}, + } + + // Apply test defaults, overwridden for test speed. + defaults := map[string]int64{ + "TimeoutSeconds": 1, + "PeriodSeconds": 1, + "SuccessThreshold": 1, + "FailureThreshold": 1, + } + for field, value := range defaults { + f := reflect.ValueOf(&probeSpec).Elem().FieldByName(field) + if f.Int() == 0 { + f.SetInt(value) + } + } + + switch probeType { + case readiness: + pod.Spec.Containers[0].ReadinessProbe = &probeSpec + case liveness: + pod.Spec.Containers[0].LivenessProbe = &probeSpec + } +} + +func newTestManager() *manager { + refManager := kubecontainer.NewRefManager() + refManager.SetRef(testContainerID, &api.ObjectReference{}) // Suppress prober warnings. + podManager := kubepod.NewBasicPodManager(nil) + // Add test pod to pod manager, so that status manager can get the pod from pod manager if needed. + podManager.AddPod(getTestPod()) + m := NewManager( + status.NewManager(&fake.Clientset{}, podManager), + results.NewManager(), + nil, // runner + refManager, + &record.FakeRecorder{}, + ).(*manager) + // Don't actually execute probes. + m.prober.exec = fakeExecProber{probe.Success, nil} + return m +} + +func newTestWorker(m *manager, probeType probeType, probeSpec api.Probe) *worker { + pod := getTestPod() + setTestProbe(pod, probeType, probeSpec) + return newWorker(m, probeType, pod, pod.Spec.Containers[0]) +} + +type fakeExecProber struct { + result probe.Result + err error +} + +func (p fakeExecProber) Probe(_ exec.Cmd) (probe.Result, string, error) { + return p.result, "", p.err +} + +type syncExecProber struct { + sync.RWMutex + fakeExecProber +} + +func (p *syncExecProber) set(result probe.Result, err error) { + p.Lock() + defer p.Unlock() + p.result = result + p.err = err +} + +func (p *syncExecProber) Probe(cmd exec.Cmd) (probe.Result, string, error) { + p.RLock() + defer p.RUnlock() + return p.fakeExecProber.Probe(cmd) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/prober/manager.go b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/manager.go new file mode 100644 index 000000000..9e46f0be3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/manager.go @@ -0,0 +1,238 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 prober + +import ( + "sync" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/prober/results" + "k8s.io/kubernetes/pkg/kubelet/status" + "k8s.io/kubernetes/pkg/kubelet/util/format" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/wait" +) + +// Manager manages pod probing. It creates a probe "worker" for every container that specifies a +// probe (AddPod). The worker periodically probes its assigned container and caches the results. The +// manager use the cached probe results to set the appropriate Ready state in the PodStatus when +// requested (UpdatePodStatus). Updating probe parameters is not currently supported. +// TODO: Move liveness probing out of the runtime, to here. +type Manager interface { + // AddPod creates new probe workers for every container probe. This should be called for every + // pod created. + AddPod(pod *api.Pod) + + // RemovePod handles cleaning up the removed pod state, including terminating probe workers and + // deleting cached results. + RemovePod(pod *api.Pod) + + // CleanupPods handles cleaning up pods which should no longer be running. + // It takes a list of "active pods" which should not be cleaned up. + CleanupPods(activePods []*api.Pod) + + // UpdatePodStatus modifies the given PodStatus with the appropriate Ready state for each + // container based on container running status, cached probe results and worker states. + UpdatePodStatus(types.UID, *api.PodStatus) + + // Start starts the Manager sync loops. + Start() +} + +type manager struct { + // Map of active workers for probes + workers map[probeKey]*worker + // Lock for accessing & mutating workers + workerLock sync.RWMutex + + // The statusManager cache provides pod IP and container IDs for probing. + statusManager status.Manager + + // readinessManager manages the results of readiness probes + readinessManager results.Manager + + // livenessManager manages the results of liveness probes + livenessManager results.Manager + + // prober executes the probe actions. + prober *prober +} + +func NewManager( + statusManager status.Manager, + livenessManager results.Manager, + runner kubecontainer.ContainerCommandRunner, + refManager *kubecontainer.RefManager, + recorder record.EventRecorder) Manager { + + prober := newProber(runner, refManager, recorder) + readinessManager := results.NewManager() + return &manager{ + statusManager: statusManager, + prober: prober, + readinessManager: readinessManager, + livenessManager: livenessManager, + workers: make(map[probeKey]*worker), + } +} + +// Start syncing probe status. This should only be called once. +func (m *manager) Start() { + // Start syncing readiness. + go wait.Forever(m.updateReadiness, 0) +} + +// Key uniquely identifying container probes +type probeKey struct { + podUID types.UID + containerName string + probeType probeType +} + +// Type of probe (readiness or liveness) +type probeType int + +const ( + liveness probeType = iota + readiness +) + +// For debugging. +func (t probeType) String() string { + switch t { + case readiness: + return "Readiness" + case liveness: + return "Liveness" + default: + return "UNKNOWN" + } +} + +func (m *manager) AddPod(pod *api.Pod) { + m.workerLock.Lock() + defer m.workerLock.Unlock() + + key := probeKey{podUID: pod.UID} + for _, c := range pod.Spec.Containers { + key.containerName = c.Name + + if c.ReadinessProbe != nil { + key.probeType = readiness + if _, ok := m.workers[key]; ok { + glog.Errorf("Readiness probe already exists! %v - %v", + format.Pod(pod), c.Name) + return + } + w := newWorker(m, readiness, pod, c) + m.workers[key] = w + go w.run() + } + + if c.LivenessProbe != nil { + key.probeType = liveness + if _, ok := m.workers[key]; ok { + glog.Errorf("Liveness probe already exists! %v - %v", + format.Pod(pod), c.Name) + return + } + w := newWorker(m, liveness, pod, c) + m.workers[key] = w + go w.run() + } + } +} + +func (m *manager) RemovePod(pod *api.Pod) { + m.workerLock.RLock() + defer m.workerLock.RUnlock() + + key := probeKey{podUID: pod.UID} + for _, c := range pod.Spec.Containers { + key.containerName = c.Name + for _, probeType := range [...]probeType{readiness, liveness} { + key.probeType = probeType + if worker, ok := m.workers[key]; ok { + worker.stop() + } + } + } +} + +func (m *manager) CleanupPods(activePods []*api.Pod) { + desiredPods := make(map[types.UID]sets.Empty) + for _, pod := range activePods { + desiredPods[pod.UID] = sets.Empty{} + } + + m.workerLock.RLock() + defer m.workerLock.RUnlock() + + for key, worker := range m.workers { + if _, ok := desiredPods[key.podUID]; !ok { + worker.stop() + } + } +} + +func (m *manager) UpdatePodStatus(podUID types.UID, podStatus *api.PodStatus) { + for i, c := range podStatus.ContainerStatuses { + var ready bool + if c.State.Running == nil { + ready = false + } else if result, ok := m.readinessManager.Get(kubecontainer.ParseContainerID(c.ContainerID)); ok { + ready = result == results.Success + } else { + // The check whether there is a probe which hasn't run yet. + _, exists := m.getWorker(podUID, c.Name, readiness) + ready = !exists + } + podStatus.ContainerStatuses[i].Ready = ready + } +} + +func (m *manager) getWorker(podUID types.UID, containerName string, probeType probeType) (*worker, bool) { + m.workerLock.RLock() + defer m.workerLock.RUnlock() + worker, ok := m.workers[probeKey{podUID, containerName, probeType}] + return worker, ok +} + +// Called by the worker after exiting. +func (m *manager) removeWorker(podUID types.UID, containerName string, probeType probeType) { + m.workerLock.Lock() + defer m.workerLock.Unlock() + delete(m.workers, probeKey{podUID, containerName, probeType}) +} + +// workerCount returns the total number of probe workers. For testing. +func (m *manager) workerCount() int { + m.workerLock.Lock() + defer m.workerLock.Unlock() + return len(m.workers) +} + +func (m *manager) updateReadiness() { + update := <-m.readinessManager.Updates() + + ready := update.Result == results.Success + m.statusManager.SetContainerReadiness(update.PodUID, update.ContainerID, ready) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/prober/manager_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/manager_test.go new file mode 100644 index 000000000..1cf6e5c94 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/manager_test.go @@ -0,0 +1,411 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 prober + +import ( + "fmt" + "strconv" + "testing" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/prober/results" + "k8s.io/kubernetes/pkg/probe" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" +) + +func init() { + runtime.ReallyCrash = true +} + +var defaultProbe *api.Probe = &api.Probe{ + Handler: api.Handler{ + Exec: &api.ExecAction{}, + }, + TimeoutSeconds: 1, + PeriodSeconds: 1, + SuccessThreshold: 1, + FailureThreshold: 3, +} + +func TestAddRemovePods(t *testing.T) { + noProbePod := api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "no_probe_pod", + }, + Spec: api.PodSpec{ + Containers: []api.Container{{ + Name: "no_probe1", + }, { + Name: "no_probe2", + }}, + }, + } + + probePod := api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "probe_pod", + }, + Spec: api.PodSpec{ + Containers: []api.Container{{ + Name: "no_probe1", + }, { + Name: "readiness", + ReadinessProbe: defaultProbe, + }, { + Name: "no_probe2", + }, { + Name: "liveness", + LivenessProbe: defaultProbe, + }}, + }, + } + + m := newTestManager() + defer cleanup(t, m) + if err := expectProbes(m, nil); err != nil { + t.Error(err) + } + + // Adding a pod with no probes should be a no-op. + m.AddPod(&noProbePod) + if err := expectProbes(m, nil); err != nil { + t.Error(err) + } + + // Adding a pod with probes. + m.AddPod(&probePod) + probePaths := []probeKey{ + {"probe_pod", "readiness", readiness}, + {"probe_pod", "liveness", liveness}, + } + if err := expectProbes(m, probePaths); err != nil { + t.Error(err) + } + + // Removing un-probed pod. + m.RemovePod(&noProbePod) + if err := expectProbes(m, probePaths); err != nil { + t.Error(err) + } + + // Removing probed pod. + m.RemovePod(&probePod) + if err := waitForWorkerExit(m, probePaths); err != nil { + t.Fatal(err) + } + if err := expectProbes(m, nil); err != nil { + t.Error(err) + } + + // Removing already removed pods should be a no-op. + m.RemovePod(&probePod) + if err := expectProbes(m, nil); err != nil { + t.Error(err) + } +} + +func TestCleanupPods(t *testing.T) { + m := newTestManager() + defer cleanup(t, m) + podToCleanup := api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "pod_cleanup", + }, + Spec: api.PodSpec{ + Containers: []api.Container{{ + Name: "prober1", + ReadinessProbe: defaultProbe, + }, { + Name: "prober2", + LivenessProbe: defaultProbe, + }}, + }, + } + podToKeep := api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "pod_keep", + }, + Spec: api.PodSpec{ + Containers: []api.Container{{ + Name: "prober1", + ReadinessProbe: defaultProbe, + }, { + Name: "prober2", + LivenessProbe: defaultProbe, + }}, + }, + } + m.AddPod(&podToCleanup) + m.AddPod(&podToKeep) + + m.CleanupPods([]*api.Pod{&podToKeep}) + + removedProbes := []probeKey{ + {"pod_cleanup", "prober1", readiness}, + {"pod_cleanup", "prober2", liveness}, + } + expectedProbes := []probeKey{ + {"pod_keep", "prober1", readiness}, + {"pod_keep", "prober2", liveness}, + } + if err := waitForWorkerExit(m, removedProbes); err != nil { + t.Fatal(err) + } + if err := expectProbes(m, expectedProbes); err != nil { + t.Error(err) + } +} + +func TestCleanupRepeated(t *testing.T) { + m := newTestManager() + defer cleanup(t, m) + podTemplate := api.Pod{ + Spec: api.PodSpec{ + Containers: []api.Container{{ + Name: "prober1", + ReadinessProbe: defaultProbe, + LivenessProbe: defaultProbe, + }}, + }, + } + + const numTestPods = 100 + for i := 0; i < numTestPods; i++ { + pod := podTemplate + pod.UID = types.UID(strconv.Itoa(i)) + m.AddPod(&pod) + } + + for i := 0; i < 10; i++ { + m.CleanupPods([]*api.Pod{}) + } +} + +func TestUpdatePodStatus(t *testing.T) { + unprobed := api.ContainerStatus{ + Name: "unprobed_container", + ContainerID: "test://unprobed_container_id", + State: api.ContainerState{ + Running: &api.ContainerStateRunning{}, + }, + } + probedReady := api.ContainerStatus{ + Name: "probed_container_ready", + ContainerID: "test://probed_container_ready_id", + State: api.ContainerState{ + Running: &api.ContainerStateRunning{}, + }, + } + probedPending := api.ContainerStatus{ + Name: "probed_container_pending", + ContainerID: "test://probed_container_pending_id", + State: api.ContainerState{ + Running: &api.ContainerStateRunning{}, + }, + } + probedUnready := api.ContainerStatus{ + Name: "probed_container_unready", + ContainerID: "test://probed_container_unready_id", + State: api.ContainerState{ + Running: &api.ContainerStateRunning{}, + }, + } + terminated := api.ContainerStatus{ + Name: "terminated_container", + ContainerID: "test://terminated_container_id", + State: api.ContainerState{ + Terminated: &api.ContainerStateTerminated{}, + }, + } + podStatus := api.PodStatus{ + Phase: api.PodRunning, + ContainerStatuses: []api.ContainerStatus{ + unprobed, probedReady, probedPending, probedUnready, terminated, + }, + } + + m := newTestManager() + // no cleanup: using fake workers. + + // Setup probe "workers" and cached results. + m.workers = map[probeKey]*worker{ + probeKey{testPodUID, unprobed.Name, liveness}: {}, + probeKey{testPodUID, probedReady.Name, readiness}: {}, + probeKey{testPodUID, probedPending.Name, readiness}: {}, + probeKey{testPodUID, probedUnready.Name, readiness}: {}, + probeKey{testPodUID, terminated.Name, readiness}: {}, + } + m.readinessManager.Set(kubecontainer.ParseContainerID(probedReady.ContainerID), results.Success, &api.Pod{}) + m.readinessManager.Set(kubecontainer.ParseContainerID(probedUnready.ContainerID), results.Failure, &api.Pod{}) + m.readinessManager.Set(kubecontainer.ParseContainerID(terminated.ContainerID), results.Success, &api.Pod{}) + + m.UpdatePodStatus(testPodUID, &podStatus) + + expectedReadiness := map[probeKey]bool{ + probeKey{testPodUID, unprobed.Name, readiness}: true, + probeKey{testPodUID, probedReady.Name, readiness}: true, + probeKey{testPodUID, probedPending.Name, readiness}: false, + probeKey{testPodUID, probedUnready.Name, readiness}: false, + probeKey{testPodUID, terminated.Name, readiness}: false, + } + for _, c := range podStatus.ContainerStatuses { + expected, ok := expectedReadiness[probeKey{testPodUID, c.Name, readiness}] + if !ok { + t.Fatalf("Missing expectation for test case: %v", c.Name) + } + if expected != c.Ready { + t.Errorf("Unexpected readiness for container %v: Expected %v but got %v", + c.Name, expected, c.Ready) + } + } +} + +func TestUpdateReadiness(t *testing.T) { + testPod := getTestPod() + setTestProbe(testPod, readiness, api.Probe{}) + m := newTestManager() + defer cleanup(t, m) + + // Start syncing readiness without leaking goroutine. + stopCh := make(chan struct{}) + go wait.Until(m.updateReadiness, 0, stopCh) + defer func() { + close(stopCh) + // Send an update to exit updateReadiness() + m.readinessManager.Set(kubecontainer.ContainerID{}, results.Success, &api.Pod{}) + }() + + exec := syncExecProber{} + exec.set(probe.Success, nil) + m.prober.exec = &exec + + m.statusManager.SetPodStatus(testPod, getTestRunningStatus()) + + m.AddPod(testPod) + probePaths := []probeKey{{testPodUID, testContainerName, readiness}} + if err := expectProbes(m, probePaths); err != nil { + t.Error(err) + } + + // Wait for ready status. + if err := waitForReadyStatus(m, true); err != nil { + t.Error(err) + } + + // Prober fails. + exec.set(probe.Failure, nil) + + // Wait for failed status. + if err := waitForReadyStatus(m, false); err != nil { + t.Error(err) + } +} + +func expectProbes(m *manager, expectedProbes []probeKey) error { + m.workerLock.RLock() + defer m.workerLock.RUnlock() + + var unexpected []probeKey + missing := make([]probeKey, len(expectedProbes)) + copy(missing, expectedProbes) + +outer: + for probePath := range m.workers { + for i, expectedPath := range missing { + if probePath == expectedPath { + missing = append(missing[:i], missing[i+1:]...) + continue outer + } + } + unexpected = append(unexpected, probePath) + } + + if len(missing) == 0 && len(unexpected) == 0 { + return nil // Yay! + } + + return fmt.Errorf("Unexpected probes: %v; Missing probes: %v;", unexpected, missing) +} + +const interval = 1 * time.Second + +// Wait for the given workers to exit & clean up. +func waitForWorkerExit(m *manager, workerPaths []probeKey) error { + for _, w := range workerPaths { + condition := func() (bool, error) { + _, exists := m.getWorker(w.podUID, w.containerName, w.probeType) + return !exists, nil + } + if exited, _ := condition(); exited { + continue // Already exited, no need to poll. + } + glog.Infof("Polling %v", w) + if err := wait.Poll(interval, wait.ForeverTestTimeout, condition); err != nil { + return err + } + } + + return nil +} + +// Wait for the given workers to exit & clean up. +func waitForReadyStatus(m *manager, ready bool) error { + condition := func() (bool, error) { + status, ok := m.statusManager.GetPodStatus(testPodUID) + if !ok { + return false, fmt.Errorf("status not found: %q", testPodUID) + } + if len(status.ContainerStatuses) != 1 { + return false, fmt.Errorf("expected single container, found %d", len(status.ContainerStatuses)) + } + if status.ContainerStatuses[0].ContainerID != testContainerID.String() { + return false, fmt.Errorf("expected container %q, found %q", + testContainerID, status.ContainerStatuses[0].ContainerID) + } + return status.ContainerStatuses[0].Ready == ready, nil + } + glog.Infof("Polling for ready state %v", ready) + if err := wait.Poll(interval, wait.ForeverTestTimeout, condition); err != nil { + return err + } + + return nil +} + +// cleanup running probes to avoid leaking goroutines. +func cleanup(t *testing.T, m *manager) { + m.CleanupPods(nil) + + condition := func() (bool, error) { + workerCount := m.workerCount() + if workerCount > 0 { + glog.Infof("Waiting for %d workers to exit...", workerCount) + } + return workerCount == 0, nil + } + if exited, _ := condition(); exited { + return // Already exited, no need to poll. + } + if err := wait.Poll(interval, wait.ForeverTestTimeout, condition); err != nil { + t.Fatalf("Error during cleanup: %v", err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/prober/prober.go b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/prober.go new file mode 100644 index 000000000..11d225aaf --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/prober.go @@ -0,0 +1,236 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 prober + +import ( + "fmt" + "net" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/prober/results" + "k8s.io/kubernetes/pkg/kubelet/util/format" + "k8s.io/kubernetes/pkg/probe" + execprobe "k8s.io/kubernetes/pkg/probe/exec" + httprobe "k8s.io/kubernetes/pkg/probe/http" + tcprobe "k8s.io/kubernetes/pkg/probe/tcp" + "k8s.io/kubernetes/pkg/util/exec" + "k8s.io/kubernetes/pkg/util/intstr" + + "github.com/golang/glog" +) + +const maxProbeRetries = 3 + +// Prober helps to check the liveness/readiness of a container. +type prober struct { + exec execprobe.ExecProber + http httprobe.HTTPProber + tcp tcprobe.TCPProber + runner kubecontainer.ContainerCommandRunner + + refManager *kubecontainer.RefManager + recorder record.EventRecorder +} + +// NewProber creates a Prober, it takes a command runner and +// several container info managers. +func newProber( + runner kubecontainer.ContainerCommandRunner, + refManager *kubecontainer.RefManager, + recorder record.EventRecorder) *prober { + + return &prober{ + exec: execprobe.New(), + http: httprobe.New(), + tcp: tcprobe.New(), + runner: runner, + refManager: refManager, + recorder: recorder, + } +} + +// probe probes the container. +func (pb *prober) probe(probeType probeType, pod *api.Pod, status api.PodStatus, container api.Container, containerID kubecontainer.ContainerID) (results.Result, error) { + var probeSpec *api.Probe + switch probeType { + case readiness: + probeSpec = container.ReadinessProbe + case liveness: + probeSpec = container.LivenessProbe + default: + return results.Failure, fmt.Errorf("Unknown probe type: %q", probeType) + } + + ctrName := fmt.Sprintf("%s:%s", format.Pod(pod), container.Name) + if probeSpec == nil { + glog.Warningf("%s probe for %s is nil", probeType, ctrName) + return results.Success, nil + } + + result, output, err := pb.runProbeWithRetries(probeSpec, pod, status, container, containerID, maxProbeRetries) + if err != nil || result != probe.Success { + // Probe failed in one way or another. + ref, hasRef := pb.refManager.GetRef(containerID) + if !hasRef { + glog.Warningf("No ref for container %q (%s)", containerID.String(), ctrName) + } + if err != nil { + glog.V(1).Infof("%s probe for %q errored: %v", probeType, ctrName, err) + if hasRef { + pb.recorder.Eventf(ref, api.EventTypeWarning, kubecontainer.ContainerUnhealthy, "%s probe errored: %v", probeType, err) + } + } else { // result != probe.Success + glog.V(1).Infof("%s probe for %q failed (%v): %s", probeType, ctrName, result, output) + if hasRef { + pb.recorder.Eventf(ref, api.EventTypeWarning, kubecontainer.ContainerUnhealthy, "%s probe failed: %s", probeType, output) + } + } + return results.Failure, err + } + glog.V(3).Infof("%s probe for %q succeeded", probeType, ctrName) + return results.Success, nil +} + +// runProbeWithRetries tries to probe the container in a finite loop, it returns the last result +// if it never succeeds. +func (pb *prober) runProbeWithRetries(p *api.Probe, pod *api.Pod, status api.PodStatus, container api.Container, containerID kubecontainer.ContainerID, retries int) (probe.Result, string, error) { + var err error + var result probe.Result + var output string + for i := 0; i < retries; i++ { + result, output, err = pb.runProbe(p, pod, status, container, containerID) + if err == nil { + return result, output, nil + } + } + return result, output, err +} + +// buildHeaderMap takes a list of HTTPHeader string +// pairs and returns a a populated string->[]string http.Header map. +func buildHeader(headerList []api.HTTPHeader) http.Header { + headers := make(http.Header) + for _, header := range headerList { + headers[header.Name] = append(headers[header.Name], header.Value) + } + return headers +} + +func (pb *prober) runProbe(p *api.Probe, pod *api.Pod, status api.PodStatus, container api.Container, containerID kubecontainer.ContainerID) (probe.Result, string, error) { + timeout := time.Duration(p.TimeoutSeconds) * time.Second + if p.Exec != nil { + glog.V(4).Infof("Exec-Probe Pod: %v, Container: %v, Command: %v", pod, container, p.Exec.Command) + return pb.exec.Probe(pb.newExecInContainer(container, containerID, p.Exec.Command)) + } + if p.HTTPGet != nil { + scheme := strings.ToLower(string(p.HTTPGet.Scheme)) + host := p.HTTPGet.Host + if host == "" { + host = status.PodIP + } + port, err := extractPort(p.HTTPGet.Port, container) + if err != nil { + return probe.Unknown, "", err + } + path := p.HTTPGet.Path + glog.V(4).Infof("HTTP-Probe Host: %v://%v, Port: %v, Path: %v", scheme, host, port, path) + url := formatURL(scheme, host, port, path) + headers := buildHeader(p.HTTPGet.HTTPHeaders) + glog.V(4).Infof("HTTP-Probe Headers: %v", headers) + return pb.http.Probe(url, headers, timeout) + } + if p.TCPSocket != nil { + port, err := extractPort(p.TCPSocket.Port, container) + if err != nil { + return probe.Unknown, "", err + } + glog.V(4).Infof("TCP-Probe PodIP: %v, Port: %v, Timeout: %v", status.PodIP, port, timeout) + return pb.tcp.Probe(status.PodIP, port, timeout) + } + glog.Warningf("Failed to find probe builder for container: %v", container) + return probe.Unknown, "", fmt.Errorf("Missing probe handler for %s:%s", format.Pod(pod), container.Name) +} + +func extractPort(param intstr.IntOrString, container api.Container) (int, error) { + port := -1 + var err error + switch param.Type { + case intstr.Int: + port = param.IntValue() + case intstr.String: + if port, err = findPortByName(container, param.StrVal); err != nil { + // Last ditch effort - maybe it was an int stored as string? + if port, err = strconv.Atoi(param.StrVal); err != nil { + return port, err + } + } + default: + return port, fmt.Errorf("IntOrString had no kind: %+v", param) + } + if port > 0 && port < 65536 { + return port, nil + } + return port, fmt.Errorf("invalid port number: %v", port) +} + +// findPortByName is a helper function to look up a port in a container by name. +func findPortByName(container api.Container, portName string) (int, error) { + for _, port := range container.Ports { + if port.Name == portName { + return port.ContainerPort, nil + } + } + return 0, fmt.Errorf("port %s not found", portName) +} + +// formatURL formats a URL from args. For testability. +func formatURL(scheme string, host string, port int, path string) *url.URL { + return &url.URL{ + Scheme: scheme, + Host: net.JoinHostPort(host, strconv.Itoa(port)), + Path: path, + } +} + +type execInContainer struct { + run func() ([]byte, error) +} + +func (p *prober) newExecInContainer(container api.Container, containerID kubecontainer.ContainerID, cmd []string) exec.Cmd { + return execInContainer{func() ([]byte, error) { + return p.runner.RunInContainer(containerID, cmd) + }} +} + +func (eic execInContainer) CombinedOutput() ([]byte, error) { + return eic.run() +} + +func (eic execInContainer) Output() ([]byte, error) { + return nil, fmt.Errorf("unimplemented") +} + +func (eic execInContainer) SetDir(dir string) { + //unimplemented +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/prober/prober_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/prober_test.go new file mode 100644 index 000000000..add29804f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/prober_test.go @@ -0,0 +1,276 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 prober + +import ( + "errors" + "fmt" + "net/http" + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/prober/results" + "k8s.io/kubernetes/pkg/probe" + "k8s.io/kubernetes/pkg/util/intstr" +) + +func TestFormatURL(t *testing.T) { + testCases := []struct { + scheme string + host string + port int + path string + result string + }{ + {"http", "localhost", 93, "", "http://localhost:93"}, + {"https", "localhost", 93, "/path", "https://localhost:93/path"}, + } + for _, test := range testCases { + url := formatURL(test.scheme, test.host, test.port, test.path) + if url.String() != test.result { + t.Errorf("Expected %s, got %s", test.result, url.String()) + } + } +} + +func TestFindPortByName(t *testing.T) { + container := api.Container{ + Ports: []api.ContainerPort{ + { + Name: "foo", + ContainerPort: 8080, + }, + { + Name: "bar", + ContainerPort: 9000, + }, + }, + } + want := 8080 + got, err := findPortByName(container, "foo") + if got != want || err != nil { + t.Errorf("Expected %v, got %v, err: %v", want, got, err) + } +} + +func TestGetURLParts(t *testing.T) { + testCases := []struct { + probe *api.HTTPGetAction + ok bool + host string + port int + path string + }{ + {&api.HTTPGetAction{Host: "", Port: intstr.FromInt(-1), Path: ""}, false, "", -1, ""}, + {&api.HTTPGetAction{Host: "", Port: intstr.FromString(""), Path: ""}, false, "", -1, ""}, + {&api.HTTPGetAction{Host: "", Port: intstr.FromString("-1"), Path: ""}, false, "", -1, ""}, + {&api.HTTPGetAction{Host: "", Port: intstr.FromString("not-found"), Path: ""}, false, "", -1, ""}, + {&api.HTTPGetAction{Host: "", Port: intstr.FromString("found"), Path: ""}, true, "127.0.0.1", 93, ""}, + {&api.HTTPGetAction{Host: "", Port: intstr.FromInt(76), Path: ""}, true, "127.0.0.1", 76, ""}, + {&api.HTTPGetAction{Host: "", Port: intstr.FromString("118"), Path: ""}, true, "127.0.0.1", 118, ""}, + {&api.HTTPGetAction{Host: "hostname", Port: intstr.FromInt(76), Path: "path"}, true, "hostname", 76, "path"}, + } + + for _, test := range testCases { + state := api.PodStatus{PodIP: "127.0.0.1"} + container := api.Container{ + Ports: []api.ContainerPort{{Name: "found", ContainerPort: 93}}, + LivenessProbe: &api.Probe{ + Handler: api.Handler{ + HTTPGet: test.probe, + }, + }, + } + + scheme := test.probe.Scheme + if scheme == "" { + scheme = api.URISchemeHTTP + } + host := test.probe.Host + if host == "" { + host = state.PodIP + } + port, err := extractPort(test.probe.Port, container) + if test.ok && err != nil { + t.Errorf("Unexpected error: %v", err) + } + path := test.probe.Path + + if !test.ok && err == nil { + t.Errorf("Expected error for %+v, got %s%s:%d/%s", test, scheme, host, port, path) + } + if test.ok { + if host != test.host || port != test.port || path != test.path { + t.Errorf("Expected %s:%d/%s, got %s:%d/%s", + test.host, test.port, test.path, host, port, path) + } + } + } +} + +func TestGetTCPAddrParts(t *testing.T) { + testCases := []struct { + probe *api.TCPSocketAction + ok bool + host string + port int + }{ + {&api.TCPSocketAction{Port: intstr.FromInt(-1)}, false, "", -1}, + {&api.TCPSocketAction{Port: intstr.FromString("")}, false, "", -1}, + {&api.TCPSocketAction{Port: intstr.FromString("-1")}, false, "", -1}, + {&api.TCPSocketAction{Port: intstr.FromString("not-found")}, false, "", -1}, + {&api.TCPSocketAction{Port: intstr.FromString("found")}, true, "1.2.3.4", 93}, + {&api.TCPSocketAction{Port: intstr.FromInt(76)}, true, "1.2.3.4", 76}, + {&api.TCPSocketAction{Port: intstr.FromString("118")}, true, "1.2.3.4", 118}, + } + + for _, test := range testCases { + host := "1.2.3.4" + container := api.Container{ + Ports: []api.ContainerPort{{Name: "found", ContainerPort: 93}}, + LivenessProbe: &api.Probe{ + Handler: api.Handler{ + TCPSocket: test.probe, + }, + }, + } + port, err := extractPort(test.probe.Port, container) + if !test.ok && err == nil { + t.Errorf("Expected error for %+v, got %s:%d", test, host, port) + } + if test.ok && err != nil { + t.Errorf("Unexpected error: %v", err) + } + if test.ok { + if host != test.host || port != test.port { + t.Errorf("Expected %s:%d, got %s:%d", test.host, test.port, host, port) + } + } + } +} + +func TestHTTPHeaders(t *testing.T) { + testCases := []struct { + input []api.HTTPHeader + output http.Header + }{ + {[]api.HTTPHeader{}, http.Header{}}, + {[]api.HTTPHeader{ + {"X-Muffins-Or-Cupcakes", "Muffins"}, + }, http.Header{"X-Muffins-Or-Cupcakes": {"Muffins"}}}, + {[]api.HTTPHeader{ + {"X-Muffins-Or-Cupcakes", "Muffins"}, + {"X-Muffins-Or-Plumcakes", "Muffins!"}, + }, http.Header{"X-Muffins-Or-Cupcakes": {"Muffins"}, + "X-Muffins-Or-Plumcakes": {"Muffins!"}}}, + {[]api.HTTPHeader{ + {"X-Muffins-Or-Cupcakes", "Muffins"}, + {"X-Muffins-Or-Cupcakes", "Cupcakes, too"}, + }, http.Header{"X-Muffins-Or-Cupcakes": {"Muffins", "Cupcakes, too"}}}, + } + for _, test := range testCases { + headers := buildHeader(test.input) + if !reflect.DeepEqual(test.output, headers) { + t.Errorf("Expected %#v, got %#v", test.output, headers) + } + } +} + +func TestProbe(t *testing.T) { + prober := &prober{ + refManager: kubecontainer.NewRefManager(), + recorder: &record.FakeRecorder{}, + } + containerID := kubecontainer.ContainerID{Type: "test", ID: "foobar"} + + execProbe := &api.Probe{ + Handler: api.Handler{ + Exec: &api.ExecAction{}, + }, + } + tests := []struct { + probe *api.Probe + execError bool + expectError bool + execResult probe.Result + expectedResult results.Result + }{ + { // No probe + probe: nil, + expectedResult: results.Success, + }, + { // No handler + probe: &api.Probe{}, + expectError: true, + expectedResult: results.Failure, + }, + { // Probe fails + probe: execProbe, + execResult: probe.Failure, + expectedResult: results.Failure, + }, + { // Probe succeeds + probe: execProbe, + execResult: probe.Success, + expectedResult: results.Success, + }, + { // Probe result is unknown + probe: execProbe, + execResult: probe.Unknown, + expectedResult: results.Failure, + }, + { // Probe has an error + probe: execProbe, + execError: true, + expectError: true, + execResult: probe.Unknown, + expectedResult: results.Failure, + }, + } + + for i, test := range tests { + for _, probeType := range [...]probeType{liveness, readiness} { + testID := fmt.Sprintf("%d-%s", i, probeType) + testContainer := api.Container{} + switch probeType { + case liveness: + testContainer.LivenessProbe = test.probe + case readiness: + testContainer.ReadinessProbe = test.probe + } + if test.execError { + prober.exec = fakeExecProber{test.execResult, errors.New("exec error")} + } else { + prober.exec = fakeExecProber{test.execResult, nil} + } + + result, err := prober.probe(probeType, &api.Pod{}, api.PodStatus{}, testContainer, containerID) + if test.expectError && err == nil { + t.Errorf("[%s] Expected probe error but no error was returned.", testID) + } + if !test.expectError && err != nil { + t.Errorf("[%s] Didn't expect probe error but got: %v", testID, err) + } + if test.expectedResult != result { + t.Errorf("[%s] Expected result to be %v but was %v", testID, test.expectedResult, result) + } + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/prober/results/results_manager.go b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/results/results_manager.go new file mode 100644 index 000000000..9f9b1938d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/results/results_manager.go @@ -0,0 +1,121 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 results + +import ( + "sync" + + "k8s.io/kubernetes/pkg/api" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/types" +) + +// Manager provides a probe results cache and channel of updates. +type Manager interface { + // Get returns the cached result for the container with the given ID. + Get(kubecontainer.ContainerID) (Result, bool) + // Set sets the cached result for the container with the given ID. + // The pod is only included to be sent with the update. + Set(kubecontainer.ContainerID, Result, *api.Pod) + // Remove clears the cached result for the container with the given ID. + Remove(kubecontainer.ContainerID) + // Updates creates a channel that receives an Update whenever its result changes (but not + // removed). + // NOTE: The current implementation only supports a single updates channel. + Updates() <-chan Update +} + +// Result is the type for probe results. +type Result bool + +const ( + Success Result = true + Failure Result = false +) + +func (r Result) String() string { + switch r { + case Success: + return "Success" + case Failure: + return "Failure" + default: + return "UNKNOWN" + } +} + +// Update is an enum of the types of updates sent over the Updates channel. +type Update struct { + ContainerID kubecontainer.ContainerID + Result Result + PodUID types.UID +} + +// Manager implementation. +type manager struct { + // guards the cache + sync.RWMutex + // map of container ID -> probe Result + cache map[kubecontainer.ContainerID]Result + // channel of updates + updates chan Update +} + +var _ Manager = &manager{} + +// NewManager creates ane returns an empty results manager. +func NewManager() Manager { + return &manager{ + cache: make(map[kubecontainer.ContainerID]Result), + updates: make(chan Update, 20), + } +} + +func (m *manager) Get(id kubecontainer.ContainerID) (Result, bool) { + m.RLock() + defer m.RUnlock() + result, found := m.cache[id] + return result, found +} + +func (m *manager) Set(id kubecontainer.ContainerID, result Result, pod *api.Pod) { + if m.setInternal(id, result) { + m.updates <- Update{id, result, pod.UID} + } +} + +// Internal helper for locked portion of set. Returns whether an update should be sent. +func (m *manager) setInternal(id kubecontainer.ContainerID, result Result) bool { + m.Lock() + defer m.Unlock() + prev, exists := m.cache[id] + if !exists || prev != result { + m.cache[id] = result + return true + } + return false +} + +func (m *manager) Remove(id kubecontainer.ContainerID) { + m.Lock() + defer m.Unlock() + delete(m.cache, id) +} + +func (m *manager) Updates() <-chan Update { + return m.updates +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/prober/results/results_manager_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/results/results_manager_test.go new file mode 100644 index 000000000..9cc513598 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/results/results_manager_test.go @@ -0,0 +1,98 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 results + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "k8s.io/kubernetes/pkg/api" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/util/wait" +) + +func TestCacheOperations(t *testing.T) { + m := NewManager() + + unsetID := kubecontainer.ContainerID{Type: "test", ID: "unset"} + setID := kubecontainer.ContainerID{Type: "test", ID: "set"} + + _, found := m.Get(unsetID) + assert.False(t, found, "unset result found") + + m.Set(setID, Success, &api.Pod{}) + result, found := m.Get(setID) + assert.True(t, result == Success, "set result") + assert.True(t, found, "set result found") + + m.Remove(setID) + _, found = m.Get(setID) + assert.False(t, found, "removed result found") +} + +func TestUpdates(t *testing.T) { + m := NewManager() + + pod := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "test-pod"}} + fooID := kubecontainer.ContainerID{Type: "test", ID: "foo"} + barID := kubecontainer.ContainerID{Type: "test", ID: "bar"} + + expectUpdate := func(expected Update, msg string) { + select { + case u := <-m.Updates(): + if expected != u { + t.Errorf("Expected update %v, recieved %v: %s", expected, u, msg) + } + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Timed out waiting for update %v: %s", expected, msg) + } + } + + expectNoUpdate := func(msg string) { + // NOTE: Since updates are accumulated asynchronously, this method is not guaranteed to fail + // when it should. In the event it misses a failure, the following calls to expectUpdate should + // still fail. + select { + case u := <-m.Updates(): + t.Errorf("Unexpected update %v: %s", u, msg) + default: + // Pass + } + } + + // New result should always push an update. + m.Set(fooID, Success, pod) + expectUpdate(Update{fooID, Success, pod.UID}, "new success") + + m.Set(barID, Failure, pod) + expectUpdate(Update{barID, Failure, pod.UID}, "new failure") + + // Unchanged results should not send an update. + m.Set(fooID, Success, pod) + expectNoUpdate("unchanged foo") + + m.Set(barID, Failure, pod) + expectNoUpdate("unchanged bar") + + // Changed results should send an update. + m.Set(fooID, Failure, pod) + expectUpdate(Update{fooID, Failure, pod.UID}, "changed foo") + + m.Set(barID, Success, pod) + expectUpdate(Update{barID, Success, pod.UID}, "changed bar") +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/prober/testing/fake_manager.go b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/testing/fake_manager.go new file mode 100644 index 000000000..b0d4e5589 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/testing/fake_manager.go @@ -0,0 +1,36 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 testing + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/types" +) + +type FakeManager struct{} + +// Unused methods. +func (_ FakeManager) AddPod(_ *api.Pod) {} +func (_ FakeManager) RemovePod(_ *api.Pod) {} +func (_ FakeManager) CleanupPods(_ []*api.Pod) {} +func (_ FakeManager) Start() {} + +func (_ FakeManager) UpdatePodStatus(_ types.UID, podStatus *api.PodStatus) { + for i := range podStatus.ContainerStatuses { + podStatus.ContainerStatuses[i].Ready = true + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/prober/worker.go b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/worker.go new file mode 100644 index 000000000..5067dd547 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/worker.go @@ -0,0 +1,225 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 prober + +import ( + "math/rand" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/prober/results" + "k8s.io/kubernetes/pkg/kubelet/util/format" + "k8s.io/kubernetes/pkg/util/runtime" +) + +// worker handles the periodic probing of its assigned container. Each worker has a go-routine +// associated with it which runs the probe loop until the container permanently terminates, or the +// stop channel is closed. The worker uses the probe Manager's statusManager to get up-to-date +// container IDs. +type worker struct { + // Channel for stopping the probe. + stopCh chan struct{} + + // The pod containing this probe (read-only) + pod *api.Pod + + // The container to probe (read-only) + container api.Container + + // Describes the probe configuration (read-only) + spec *api.Probe + + // The type of the worker. + probeType probeType + + // The probe value during the initial delay. + initialValue results.Result + + // Where to store this workers results. + resultsManager results.Manager + probeManager *manager + + // The last known container ID for this worker. + containerID kubecontainer.ContainerID + // The last probe result for this worker. + lastResult results.Result + // How many times in a row the probe has returned the same result. + resultRun int + + // If set, skip probing. + onHold bool +} + +// Creates and starts a new probe worker. +func newWorker( + m *manager, + probeType probeType, + pod *api.Pod, + container api.Container) *worker { + + w := &worker{ + stopCh: make(chan struct{}, 1), // Buffer so stop() can be non-blocking. + pod: pod, + container: container, + probeType: probeType, + probeManager: m, + } + + switch probeType { + case readiness: + w.spec = container.ReadinessProbe + w.resultsManager = m.readinessManager + w.initialValue = results.Failure + case liveness: + w.spec = container.LivenessProbe + w.resultsManager = m.livenessManager + w.initialValue = results.Success + } + + return w +} + +// run periodically probes the container. +func (w *worker) run() { + probeTickerPeriod := time.Duration(w.spec.PeriodSeconds) * time.Second + probeTicker := time.NewTicker(probeTickerPeriod) + + defer func() { + // Clean up. + probeTicker.Stop() + if !w.containerID.IsEmpty() { + w.resultsManager.Remove(w.containerID) + } + + w.probeManager.removeWorker(w.pod.UID, w.container.Name, w.probeType) + }() + + // If kubelet restarted the probes could be started in rapid succession. + // Let the worker wait for a random portion of tickerPeriod before probing. + time.Sleep(time.Duration(rand.Float64() * float64(probeTickerPeriod))) + +probeLoop: + for w.doProbe() { + // Wait for next probe tick. + select { + case <-w.stopCh: + break probeLoop + case <-probeTicker.C: + // continue + } + } +} + +// stop stops the probe worker. The worker handles cleanup and removes itself from its manager. +// It is safe to call stop multiple times. +func (w *worker) stop() { + select { + case w.stopCh <- struct{}{}: + default: // Non-blocking. + } +} + +// doProbe probes the container once and records the result. +// Returns whether the worker should continue. +func (w *worker) doProbe() (keepGoing bool) { + defer runtime.HandleCrash(func(_ interface{}) { keepGoing = true }) + + status, ok := w.probeManager.statusManager.GetPodStatus(w.pod.UID) + if !ok { + // Either the pod has not been created yet, or it was already deleted. + glog.V(3).Infof("No status for pod: %v", format.Pod(w.pod)) + return true + } + + // Worker should terminate if pod is terminated. + if status.Phase == api.PodFailed || status.Phase == api.PodSucceeded { + glog.V(3).Infof("Pod %v %v, exiting probe worker", + format.Pod(w.pod), status.Phase) + return false + } + + c, ok := api.GetContainerStatus(status.ContainerStatuses, w.container.Name) + if !ok || len(c.ContainerID) == 0 { + // Either the container has not been created yet, or it was deleted. + glog.V(3).Infof("Probe target container not found: %v - %v", + format.Pod(w.pod), w.container.Name) + return true // Wait for more information. + } + + if w.containerID.String() != c.ContainerID { + if !w.containerID.IsEmpty() { + w.resultsManager.Remove(w.containerID) + } + w.containerID = kubecontainer.ParseContainerID(c.ContainerID) + w.resultsManager.Set(w.containerID, w.initialValue, w.pod) + // We've got a new container; resume probing. + w.onHold = false + } + + if w.onHold { + // Worker is on hold until there is a new container. + return true + } + + if c.State.Running == nil { + glog.V(3).Infof("Non-running container probed: %v - %v", + format.Pod(w.pod), w.container.Name) + if !w.containerID.IsEmpty() { + w.resultsManager.Set(w.containerID, results.Failure, w.pod) + } + // Abort if the container will not be restarted. + return c.State.Terminated == nil || + w.pod.Spec.RestartPolicy != api.RestartPolicyNever + } + + if int(time.Since(c.State.Running.StartedAt.Time).Seconds()) < w.spec.InitialDelaySeconds { + return true + } + + result, err := w.probeManager.prober.probe(w.probeType, w.pod, status, w.container, w.containerID) + if err != nil { + // Prober error, throw away the result. + return true + } + + if w.lastResult == result { + w.resultRun++ + } else { + w.lastResult = result + w.resultRun = 1 + } + + if (result == results.Failure && w.resultRun < w.spec.FailureThreshold) || + (result == results.Success && w.resultRun < w.spec.SuccessThreshold) { + // Success or failure is below threshold - leave the probe state unchanged. + return true + } + + w.resultsManager.Set(w.containerID, result, w.pod) + + if w.probeType == liveness && result == results.Failure { + // The container fails a liveness check, it will need to be restared. + // Stop probing until we see a new container ID. This is to reduce the + // chance of hitting #21751, where running `docker exec` when a + // container is being stopped may lead to corrupted container state. + w.onHold = true + } + + return true +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/prober/worker_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/worker_test.go new file mode 100644 index 000000000..2b23ad36a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/prober/worker_test.go @@ -0,0 +1,342 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 prober + +import ( + "fmt" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/record" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + kubepod "k8s.io/kubernetes/pkg/kubelet/pod" + "k8s.io/kubernetes/pkg/kubelet/prober/results" + "k8s.io/kubernetes/pkg/kubelet/status" + "k8s.io/kubernetes/pkg/probe" + "k8s.io/kubernetes/pkg/util/exec" + "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" +) + +func init() { + runtime.ReallyCrash = true +} + +func TestDoProbe(t *testing.T) { + m := newTestManager() + + // Test statuses. + runningStatus := getTestRunningStatus() + pendingStatus := getTestRunningStatus() + pendingStatus.ContainerStatuses[0].State.Running = nil + terminatedStatus := getTestRunningStatus() + terminatedStatus.ContainerStatuses[0].State.Running = nil + terminatedStatus.ContainerStatuses[0].State.Terminated = &api.ContainerStateTerminated{ + StartedAt: unversioned.Now(), + } + otherStatus := getTestRunningStatus() + otherStatus.ContainerStatuses[0].Name = "otherContainer" + failedStatus := getTestRunningStatus() + failedStatus.Phase = api.PodFailed + + tests := []struct { + probe api.Probe + podStatus *api.PodStatus + expectContinue bool + expectSet bool + expectedResult results.Result + }{ + { // No status. + expectContinue: true, + }, + { // Pod failed + podStatus: &failedStatus, + }, + { // No container status + podStatus: &otherStatus, + expectContinue: true, + }, + { // Container waiting + podStatus: &pendingStatus, + expectContinue: true, + expectSet: true, + }, + { // Container terminated + podStatus: &terminatedStatus, + expectSet: true, + }, + { // Probe successful. + podStatus: &runningStatus, + expectContinue: true, + expectSet: true, + expectedResult: results.Success, + }, + { // Initial delay passed + podStatus: &runningStatus, + probe: api.Probe{ + InitialDelaySeconds: -100, + }, + expectContinue: true, + expectSet: true, + expectedResult: results.Success, + }, + } + + for _, probeType := range [...]probeType{liveness, readiness} { + for i, test := range tests { + w := newTestWorker(m, probeType, test.probe) + if test.podStatus != nil { + m.statusManager.SetPodStatus(w.pod, *test.podStatus) + } + if c := w.doProbe(); c != test.expectContinue { + t.Errorf("[%s-%d] Expected continue to be %v but got %v", probeType, i, test.expectContinue, c) + } + result, ok := resultsManager(m, probeType).Get(testContainerID) + if ok != test.expectSet { + t.Errorf("[%s-%d] Expected to have result: %v but got %v", probeType, i, test.expectSet, ok) + } + if result != test.expectedResult { + t.Errorf("[%s-%d] Expected result: %v but got %v", probeType, i, test.expectedResult, result) + } + + // Clean up. + m.statusManager = status.NewManager(&fake.Clientset{}, kubepod.NewBasicPodManager(nil)) + resultsManager(m, probeType).Remove(testContainerID) + } + } +} + +func TestInitialDelay(t *testing.T) { + m := newTestManager() + + for _, probeType := range [...]probeType{liveness, readiness} { + w := newTestWorker(m, probeType, api.Probe{ + InitialDelaySeconds: 10, + }) + m.statusManager.SetPodStatus(w.pod, getTestRunningStatus()) + + expectContinue(t, w, w.doProbe(), "during initial delay") + expectResult(t, w, results.Result(probeType == liveness), "during initial delay") + + // 100 seconds later... + laterStatus := getTestRunningStatus() + laterStatus.ContainerStatuses[0].State.Running.StartedAt.Time = + time.Now().Add(-100 * time.Second) + m.statusManager.SetPodStatus(w.pod, laterStatus) + + // Second call should succeed (already waited). + expectContinue(t, w, w.doProbe(), "after initial delay") + expectResult(t, w, results.Success, "after initial delay") + } +} + +func TestFailureThreshold(t *testing.T) { + m := newTestManager() + w := newTestWorker(m, readiness, api.Probe{SuccessThreshold: 1, FailureThreshold: 3}) + m.statusManager.SetPodStatus(w.pod, getTestRunningStatus()) + + for i := 0; i < 2; i++ { + // First probe should succeed. + m.prober.exec = fakeExecProber{probe.Success, nil} + + for j := 0; j < 3; j++ { + msg := fmt.Sprintf("%d success (%d)", j+1, i) + expectContinue(t, w, w.doProbe(), msg) + expectResult(t, w, results.Success, msg) + } + + // Prober starts failing :( + m.prober.exec = fakeExecProber{probe.Failure, nil} + + // Next 2 probes should still be "success". + for j := 0; j < 2; j++ { + msg := fmt.Sprintf("%d failing (%d)", j+1, i) + expectContinue(t, w, w.doProbe(), msg) + expectResult(t, w, results.Success, msg) + } + + // Third & following fail. + for j := 0; j < 3; j++ { + msg := fmt.Sprintf("%d failure (%d)", j+3, i) + expectContinue(t, w, w.doProbe(), msg) + expectResult(t, w, results.Failure, msg) + } + } +} + +func TestSuccessThreshold(t *testing.T) { + m := newTestManager() + w := newTestWorker(m, readiness, api.Probe{SuccessThreshold: 3, FailureThreshold: 1}) + m.statusManager.SetPodStatus(w.pod, getTestRunningStatus()) + + // Start out failure. + w.resultsManager.Set(testContainerID, results.Failure, &api.Pod{}) + + for i := 0; i < 2; i++ { + // Probe defaults to Failure. + for j := 0; j < 2; j++ { + msg := fmt.Sprintf("%d success (%d)", j+1, i) + expectContinue(t, w, w.doProbe(), msg) + expectResult(t, w, results.Failure, msg) + } + + // Continuing success! + for j := 0; j < 3; j++ { + msg := fmt.Sprintf("%d success (%d)", j+3, i) + expectContinue(t, w, w.doProbe(), msg) + expectResult(t, w, results.Success, msg) + } + + // Prober flakes :( + m.prober.exec = fakeExecProber{probe.Failure, nil} + msg := fmt.Sprintf("1 failure (%d)", i) + expectContinue(t, w, w.doProbe(), msg) + expectResult(t, w, results.Failure, msg) + + // Back to success. + m.prober.exec = fakeExecProber{probe.Success, nil} + } +} + +func TestCleanUp(t *testing.T) { + m := newTestManager() + + for _, probeType := range [...]probeType{liveness, readiness} { + key := probeKey{testPodUID, testContainerName, probeType} + w := newTestWorker(m, probeType, api.Probe{}) + m.statusManager.SetPodStatus(w.pod, getTestRunningStatus()) + go w.run() + m.workers[key] = w + + // Wait for worker to run. + condition := func() (bool, error) { + ready, _ := resultsManager(m, probeType).Get(testContainerID) + return ready == results.Success, nil + } + if ready, _ := condition(); !ready { + if err := wait.Poll(100*time.Millisecond, wait.ForeverTestTimeout, condition); err != nil { + t.Fatalf("[%s] Error waiting for worker ready: %v", probeType, err) + } + } + + for i := 0; i < 10; i++ { + w.stop() // Stop should be callable multiple times without consequence. + } + if err := waitForWorkerExit(m, []probeKey{key}); err != nil { + t.Fatalf("[%s] error waiting for worker exit: %v", probeType, err) + } + + if _, ok := resultsManager(m, probeType).Get(testContainerID); ok { + t.Errorf("[%s] Expected result to be cleared.", probeType) + } + if _, ok := m.workers[key]; ok { + t.Errorf("[%s] Expected worker to be cleared.", probeType) + } + } +} + +func TestHandleCrash(t *testing.T) { + runtime.ReallyCrash = false // Test that we *don't* really crash. + + m := newTestManager() + w := newTestWorker(m, readiness, api.Probe{}) + m.statusManager.SetPodStatus(w.pod, getTestRunningStatus()) + + expectContinue(t, w, w.doProbe(), "Initial successful probe.") + expectResult(t, w, results.Success, "Initial successful probe.") + + // Prober starts crashing. + m.prober = &prober{ + refManager: kubecontainer.NewRefManager(), + recorder: &record.FakeRecorder{}, + exec: crashingExecProber{}, + } + + // doProbe should recover from the crash, and keep going. + expectContinue(t, w, w.doProbe(), "Crashing probe.") + expectResult(t, w, results.Success, "Crashing probe unchanged.") +} + +func expectResult(t *testing.T, w *worker, expectedResult results.Result, msg string) { + result, ok := resultsManager(w.probeManager, w.probeType).Get(w.containerID) + if !ok { + t.Errorf("[%s - %s] Expected result to be set, but was not set", w.probeType, msg) + } else if result != expectedResult { + t.Errorf("[%s - %s] Expected result to be %v, but was %v", + w.probeType, msg, expectedResult, result) + } +} + +func expectContinue(t *testing.T, w *worker, c bool, msg string) { + if !c { + t.Errorf("[%s - %s] Expected to continue, but did not", w.probeType, msg) + } +} + +func resultsManager(m *manager, probeType probeType) results.Manager { + switch probeType { + case readiness: + return m.readinessManager + case liveness: + return m.livenessManager + } + panic(fmt.Errorf("Unhandled case: %v", probeType)) +} + +type crashingExecProber struct{} + +func (p crashingExecProber) Probe(_ exec.Cmd) (probe.Result, string, error) { + panic("Intentional Probe crash.") +} + +func TestOnHoldOnLivenessCheckFailure(t *testing.T) { + m := newTestManager() + w := newTestWorker(m, liveness, api.Probe{SuccessThreshold: 1, FailureThreshold: 1}) + status := getTestRunningStatus() + m.statusManager.SetPodStatus(w.pod, getTestRunningStatus()) + + // First probe should fail. + m.prober.exec = fakeExecProber{probe.Failure, nil} + msg := "first probe" + expectContinue(t, w, w.doProbe(), msg) + expectResult(t, w, results.Failure, msg) + if !w.onHold { + t.Errorf("Prober should be on hold due to liveness check failure") + } + // Set fakeExecProber to return success. However, the result will remain + // failure because the worker is on hold and won't probe. + m.prober.exec = fakeExecProber{probe.Success, nil} + msg = "while on hold" + expectContinue(t, w, w.doProbe(), msg) + expectResult(t, w, results.Failure, msg) + if !w.onHold { + t.Errorf("Prober should be on hold due to liveness check failure") + } + + // Set a new container ID to lift the hold. The next probe will succeed. + status.ContainerStatuses[0].ContainerID = "test://newCont_ID" + m.statusManager.SetPodStatus(w.pod, status) + msg = "hold lifted" + expectContinue(t, w, w.doProbe(), msg) + expectResult(t, w, results.Success, msg) + if w.onHold { + t.Errorf("Prober should not be on hold anymore") + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/reason_cache.go b/vendor/k8s.io/kubernetes/pkg/kubelet/reason_cache.go new file mode 100644 index 000000000..6134ffe1b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/reason_cache.go @@ -0,0 +1,104 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "fmt" + "sync" + + "github.com/golang/groupcache/lru" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/types" +) + +// ReasonCache stores the failure reason of the latest container start +// in a string, keyed by _. The goal is to +// propagate this reason to the container status. This endeavor is +// "best-effort" for two reasons: +// 1. The cache is not persisted. +// 2. We use an LRU cache to avoid extra garbage collection work. This +// means that some entries may be recycled before a pod has been +// deleted. +// TODO(random-liu): Use more reliable cache which could collect garbage of failed pod. +// TODO(random-liu): Move reason cache to somewhere better. +type ReasonCache struct { + lock sync.RWMutex + cache *lru.Cache +} + +// reasonInfo is the cached item in ReasonCache +type reasonInfo struct { + reason error + message string +} + +// maxReasonCacheEntries is the cache entry number in lru cache. 1000 is a proper number +// for our 100 pods per node target. If we support more pods per node in the future, we +// may want to increase the number. +const maxReasonCacheEntries = 1000 + +func NewReasonCache() *ReasonCache { + return &ReasonCache{cache: lru.New(maxReasonCacheEntries)} +} + +func (c *ReasonCache) composeKey(uid types.UID, name string) string { + return fmt.Sprintf("%s_%s", uid, name) +} + +// add adds error reason into the cache +func (c *ReasonCache) add(uid types.UID, name string, reason error, message string) { + c.lock.Lock() + defer c.lock.Unlock() + c.cache.Add(c.composeKey(uid, name), reasonInfo{reason, message}) +} + +// Update updates the reason cache with the SyncPodResult. Only SyncResult with +// StartContainer action will change the cache. +func (c *ReasonCache) Update(uid types.UID, result kubecontainer.PodSyncResult) { + for _, r := range result.SyncResults { + if r.Action != kubecontainer.StartContainer { + continue + } + name := r.Target.(string) + if r.Error != nil { + c.add(uid, name, r.Error, r.Message) + } else { + c.Remove(uid, name) + } + } +} + +// Remove removes error reason from the cache +func (c *ReasonCache) Remove(uid types.UID, name string) { + c.lock.Lock() + defer c.lock.Unlock() + c.cache.Remove(c.composeKey(uid, name)) +} + +// Get gets error reason from the cache. The return values are error reason, error message and +// whether an error reason is found in the cache. If no error reason is found, empty string will +// be returned for error reason and error message. +func (c *ReasonCache) Get(uid types.UID, name string) (error, string, bool) { + c.lock.RLock() + defer c.lock.RUnlock() + value, ok := c.cache.Get(c.composeKey(uid, name)) + if !ok { + return nil, "", ok + } + info := value.(reasonInfo) + return info.reason, info.message, ok +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/reason_cache_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/reason_cache_test.go new file mode 100644 index 000000000..cc77ded57 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/reason_cache_test.go @@ -0,0 +1,69 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "testing" + + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/types" +) + +func TestReasonCache(t *testing.T) { + // Create test sync result + syncResult := kubecontainer.PodSyncResult{} + results := []*kubecontainer.SyncResult{ + // reason cache should be set for SyncResult with StartContainer action and error + kubecontainer.NewSyncResult(kubecontainer.StartContainer, "container_1"), + // reason cache should not be set for SyncResult with StartContainer action but without error + kubecontainer.NewSyncResult(kubecontainer.StartContainer, "container_2"), + // reason cache should not be set for SyncResult with other actions + kubecontainer.NewSyncResult(kubecontainer.KillContainer, "container_3"), + } + results[0].Fail(kubecontainer.ErrRunContainer, "message_1") + results[2].Fail(kubecontainer.ErrKillContainer, "message_3") + syncResult.AddSyncResult(results...) + uid := types.UID("pod_1") + + reasonCache := NewReasonCache() + reasonCache.Update(uid, syncResult) + assertReasonInfo(t, reasonCache, uid, results[0], true) + assertReasonInfo(t, reasonCache, uid, results[1], false) + assertReasonInfo(t, reasonCache, uid, results[2], false) + + reasonCache.Remove(uid, results[0].Target.(string)) + assertReasonInfo(t, reasonCache, uid, results[0], false) +} + +func assertReasonInfo(t *testing.T, cache *ReasonCache, uid types.UID, result *kubecontainer.SyncResult, found bool) { + name := result.Target.(string) + actualReason, actualMessage, ok := cache.Get(uid, name) + if ok && !found { + t.Fatalf("unexpected cache hit: %v, %q", actualReason, actualMessage) + } + if !ok && found { + t.Fatalf("corresponding reason info not found") + } + if !found { + return + } + reason := result.Error + message := result.Message + if actualReason != reason || actualMessage != message { + t.Errorf("expected %v %q, got %v %q", reason, message, actualReason, actualMessage) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/cap.go b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/cap.go new file mode 100644 index 000000000..a00057f9e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/cap.go @@ -0,0 +1,110 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 rkt + +// TODO(yifan): Export this to higher level package. +const ( + CAP_CHOWN = iota + CAP_DAC_OVERRIDE + CAP_DAC_READ_SEARCH + CAP_FOWNER + CAP_FSETID + CAP_KILL + CAP_SETGID + CAP_SETUID + CAP_SETPCAP + CAP_LINUX_IMMUTABLE + CAP_NET_BIND_SERVICE + CAP_NET_BROADCAST + CAP_NET_ADMIN + CAP_NET_RAW + CAP_IPC_LOCK + CAP_IPC_OWNER + CAP_SYS_MODULE + CAP_SYS_RAWIO + CAP_SYS_CHROOT + CAP_SYS_PTRACE + CAP_SYS_PACCT + CAP_SYS_ADMIN + CAP_SYS_BOOT + CAP_SYS_NICE + CAP_SYS_RESOURCE + CAP_SYS_TIME + CAP_SYS_TTY_CONFIG + CAP_MKNOD + CAP_LEASE + CAP_AUDIT_WRITE + CAP_AUDIT_CONTROL + CAP_SETFCAP + CAP_MAC_OVERRIDE + CAP_MAC_ADMIN + CAP_SYSLOG + CAP_WAKE_ALARM + CAP_BLOCK_SUSPEND + CAP_AUDIT_READ +) + +// TODO(yifan): Export this to higher level package. +var capabilityList = map[int]string{ + CAP_CHOWN: "CAP_CHOWN", + CAP_DAC_OVERRIDE: "CAP_DAC_OVERRIDE", + CAP_DAC_READ_SEARCH: "CAP_DAC_READ_SEARCH", + CAP_FOWNER: "CAP_FOWNER", + CAP_FSETID: "CAP_FSETID", + CAP_KILL: "CAP_KILL", + CAP_SETGID: "CAP_SETGID", + CAP_SETUID: "CAP_SETUID", + CAP_SETPCAP: "CAP_SETPCAP", + CAP_LINUX_IMMUTABLE: "CAP_LINUX_IMMUTABLE", + CAP_NET_BIND_SERVICE: "CAP_NET_BIND_SERVICE", + CAP_NET_BROADCAST: "CAP_NET_BROADCAST", + CAP_NET_ADMIN: "CAP_NET_ADMIN", + CAP_NET_RAW: "CAP_NET_RAW", + CAP_IPC_LOCK: "CAP_IPC_LOCK", + CAP_IPC_OWNER: "CAP_IPC_OWNER", + CAP_SYS_MODULE: "CAP_SYS_MODULE", + CAP_SYS_RAWIO: "CAP_SYS_RAWIO", + CAP_SYS_CHROOT: "CAP_SYS_CHROOT", + CAP_SYS_PTRACE: "CAP_SYS_PTRACE", + CAP_SYS_PACCT: "CAP_SYS_PACCT", + CAP_SYS_ADMIN: "CAP_SYS_ADMIN", + CAP_SYS_BOOT: "CAP_SYS_BOOT", + CAP_SYS_NICE: "CAP_SYS_NICE", + CAP_SYS_RESOURCE: "CAP_SYS_RESOURCE", + CAP_SYS_TIME: "CAP_SYS_TIME", + CAP_SYS_TTY_CONFIG: "CAP_SYS_TTY_CONFIG", + CAP_MKNOD: "CAP_MKNOD", + CAP_LEASE: "CAP_LEASE", + CAP_AUDIT_WRITE: "CAP_AUDIT_WRITE", + CAP_AUDIT_CONTROL: "CAP_AUDIT_CONTROL", + CAP_SETFCAP: "CAP_SETFCAP", + CAP_MAC_OVERRIDE: "CAP_MAC_OVERRIDE", + CAP_MAC_ADMIN: "CAP_MAC_ADMIN", + CAP_SYSLOG: "CAP_SYSLOG", + CAP_WAKE_ALARM: "CAP_WAKE_ALARM", + CAP_BLOCK_SUSPEND: "CAP_BLOCK_SUSPEND", + CAP_AUDIT_READ: "CAP_AUDIT_READ", +} + +// allCapabilities returns the capability list with all capabilities. +func allCapabilities() []string { + var capabilities []string + for _, cap := range capabilityList { + capabilities = append(capabilities, cap) + } + return capabilities +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/config.go b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/config.go new file mode 100644 index 000000000..809eefc54 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/config.go @@ -0,0 +1,106 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 rkt + +import ( + "fmt" + + rktapi "github.com/coreos/rkt/api/v1alpha" + "golang.org/x/net/context" +) + +// Config stores the global configuration for the rkt runtime. +// Detailed documents can be found at: +// https://github.com/coreos/rkt/blob/master/Documentation/commands.md#global-options +type Config struct { + // The absolute path to the binary, or leave empty to find it in $PATH. + Path string + // The rkt data directory. + Dir string + // The image to use as stage1. + Stage1Image string + // The debug flag for rkt. + Debug bool + // Comma-separated list of security features to disable. + // Allowed values: "none", "image", "tls", "ondisk", "http", "all". + InsecureOptions string + // The local config directory. + LocalConfigDir string + // The user config directory. + UserConfigDir string + // The system config directory. + SystemConfigDir string +} + +// buildGlobalOptions returns an array of global command line options. +func (c *Config) buildGlobalOptions() []string { + var result []string + if c == nil { + return result + } + + if c.Debug { + result = append(result, "--debug=true") + } + if c.InsecureOptions != "" { + result = append(result, fmt.Sprintf("--insecure-options=%s", c.InsecureOptions)) + } + if c.LocalConfigDir != "" { + result = append(result, fmt.Sprintf("--local-config=%s", c.LocalConfigDir)) + } + if c.UserConfigDir != "" { + result = append(result, fmt.Sprintf("--user-config=%s", c.UserConfigDir)) + } + if c.SystemConfigDir != "" { + result = append(result, fmt.Sprintf("--system-config=%s", c.SystemConfigDir)) + } + if c.Dir != "" { + result = append(result, fmt.Sprintf("--dir=%s", c.Dir)) + } + return result +} + +// getConfig gets configurations from the rkt API service +// and merge it with the existing config. The merge rule is +// that the fields in the provided config will override the +// result that get from the rkt api service. +func (r *Runtime) getConfig(cfg *Config) (*Config, error) { + resp, err := r.apisvc.GetInfo(context.Background(), &rktapi.GetInfoRequest{}) + if err != nil { + return nil, err + } + + flags := resp.Info.GlobalFlags + + if cfg.Dir == "" { + cfg.Dir = flags.Dir + } + if cfg.InsecureOptions == "" { + cfg.InsecureOptions = flags.InsecureFlags + } + if cfg.LocalConfigDir == "" { + cfg.LocalConfigDir = flags.LocalConfigDir + } + if cfg.UserConfigDir == "" { + cfg.UserConfigDir = flags.UserConfigDir + } + if cfg.SystemConfigDir == "" { + cfg.SystemConfigDir = flags.SystemConfigDir + } + + return cfg, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/container_id.go b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/container_id.go new file mode 100644 index 000000000..bb8a23284 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/container_id.go @@ -0,0 +1,55 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 rkt + +import ( + "fmt" + "strings" + + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" +) + +// containerID defines the ID of rkt containers, it will +// be returned to kubelet, and kubelet will use this for +// container level operations. +type containerID struct { + uuid string // rkt uuid of the pod. + appName string // Name of the app in that pod. +} + +// buildContainerID constructs the containers's ID using containerID, +// which consists of the pod uuid and the container name. +// The result can be used to uniquely identify a container. +func buildContainerID(c *containerID) kubecontainer.ContainerID { + return kubecontainer.ContainerID{ + Type: RktType, + ID: fmt.Sprintf("%s:%s", c.uuid, c.appName), + } +} + +// parseContainerID parses the containerID into pod uuid and the container name. The +// results can be used to get more information of the container. +func parseContainerID(id kubecontainer.ContainerID) (*containerID, error) { + tuples := strings.Split(id.ID, ":") + if len(tuples) != 2 { + return nil, fmt.Errorf("rkt: cannot parse container ID for: %v", id) + } + return &containerID{ + uuid: tuples[0], + appName: tuples[1], + }, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/doc.go b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/doc.go new file mode 100644 index 000000000..d45fb3f0e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 rkt contains the Containerruntime interface implementation for rkt. +package rkt diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/fake_rkt_interface_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/fake_rkt_interface_test.go new file mode 100644 index 000000000..d2bbf1b09 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/fake_rkt_interface_test.go @@ -0,0 +1,168 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 rkt + +import ( + "fmt" + "strconv" + "sync" + + "k8s.io/kubernetes/pkg/api" + + "github.com/coreos/go-systemd/dbus" + rktapi "github.com/coreos/rkt/api/v1alpha" + "golang.org/x/net/context" + "google.golang.org/grpc" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" +) + +// fakeRktInterface mocks the rktapi.PublicAPIClient interface for testing purpose. +type fakeRktInterface struct { + sync.Mutex + info rktapi.Info + images []*rktapi.Image + podFilters []*rktapi.PodFilter + pods []*rktapi.Pod + called []string + err error +} + +func newFakeRktInterface() *fakeRktInterface { + return &fakeRktInterface{} +} + +func (f *fakeRktInterface) CleanCalls() { + f.Lock() + defer f.Unlock() + f.called = nil +} + +func (f *fakeRktInterface) GetInfo(ctx context.Context, in *rktapi.GetInfoRequest, opts ...grpc.CallOption) (*rktapi.GetInfoResponse, error) { + f.Lock() + defer f.Unlock() + + f.called = append(f.called, "GetInfo") + return &rktapi.GetInfoResponse{Info: &f.info}, f.err +} + +func (f *fakeRktInterface) ListPods(ctx context.Context, in *rktapi.ListPodsRequest, opts ...grpc.CallOption) (*rktapi.ListPodsResponse, error) { + f.Lock() + defer f.Unlock() + + f.called = append(f.called, "ListPods") + f.podFilters = in.Filters + return &rktapi.ListPodsResponse{Pods: f.pods}, f.err +} + +func (f *fakeRktInterface) InspectPod(ctx context.Context, in *rktapi.InspectPodRequest, opts ...grpc.CallOption) (*rktapi.InspectPodResponse, error) { + f.Lock() + defer f.Unlock() + + f.called = append(f.called, "InspectPod") + for _, pod := range f.pods { + if pod.Id == in.Id { + return &rktapi.InspectPodResponse{Pod: pod}, f.err + } + } + return &rktapi.InspectPodResponse{Pod: nil}, f.err +} + +func (f *fakeRktInterface) ListImages(ctx context.Context, in *rktapi.ListImagesRequest, opts ...grpc.CallOption) (*rktapi.ListImagesResponse, error) { + f.Lock() + defer f.Unlock() + + f.called = append(f.called, "ListImages") + return &rktapi.ListImagesResponse{Images: f.images}, f.err +} + +func (f *fakeRktInterface) InspectImage(ctx context.Context, in *rktapi.InspectImageRequest, opts ...grpc.CallOption) (*rktapi.InspectImageResponse, error) { + return nil, fmt.Errorf("Not implemented") +} + +func (f *fakeRktInterface) ListenEvents(ctx context.Context, in *rktapi.ListenEventsRequest, opts ...grpc.CallOption) (rktapi.PublicAPI_ListenEventsClient, error) { + return nil, fmt.Errorf("Not implemented") +} + +func (f *fakeRktInterface) GetLogs(ctx context.Context, in *rktapi.GetLogsRequest, opts ...grpc.CallOption) (rktapi.PublicAPI_GetLogsClient, error) { + return nil, fmt.Errorf("Not implemented") +} + +// fakeSystemd mocks the systemdInterface for testing purpose. +// TODO(yifan): Remove this once we have a package for launching rkt pods. +// See https://github.com/coreos/rkt/issues/1769. +type fakeSystemd struct { + sync.Mutex + called []string + version string + err error +} + +func newFakeSystemd() *fakeSystemd { + return &fakeSystemd{} +} + +func (f *fakeSystemd) CleanCalls() { + f.Lock() + defer f.Unlock() + f.called = nil +} + +func (f *fakeSystemd) Version() (systemdVersion, error) { + f.Lock() + defer f.Unlock() + + f.called = append(f.called, "Version") + v, _ := strconv.Atoi(f.version) + return systemdVersion(v), f.err +} + +func (f *fakeSystemd) ListUnits() ([]dbus.UnitStatus, error) { + return nil, fmt.Errorf("Not implemented") +} + +func (f *fakeSystemd) StopUnit(name string, mode string, ch chan<- string) (int, error) { + return 0, fmt.Errorf("Not implemented") +} + +func (f *fakeSystemd) RestartUnit(name string, mode string, ch chan<- string) (int, error) { + return 0, fmt.Errorf("Not implemented") +} + +func (f *fakeSystemd) Reload() error { + return fmt.Errorf("Not implemented") +} + +// fakeRuntimeHelper implementes kubecontainer.RuntimeHelper interfaces for testing purpose. +type fakeRuntimeHelper struct { + dnsServers []string + dnsSearches []string + hostName string + hostDomain string + err error +} + +func (f *fakeRuntimeHelper) GenerateRunContainerOptions(pod *api.Pod, container *api.Container, podIP string) (*kubecontainer.RunContainerOptions, error) { + return nil, fmt.Errorf("Not implemented") +} + +func (f *fakeRuntimeHelper) GetClusterDNS(pod *api.Pod) ([]string, []string, error) { + return f.dnsServers, f.dnsSearches, f.err +} + +func (f *fakeRuntimeHelper) GeneratePodHostNameAndDomain(pod *api.Pod) (string, string) { + return f.hostName, f.hostDomain +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/image.go b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/image.go new file mode 100644 index 000000000..2a949042e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/image.go @@ -0,0 +1,217 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This file contains all image related functions for rkt runtime. +package rkt + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path" + "sort" + "strings" + + appcschema "github.com/appc/spec/schema" + rktapi "github.com/coreos/rkt/api/v1alpha" + "github.com/fsouza/go-dockerclient" + "github.com/golang/glog" + "golang.org/x/net/context" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/credentialprovider" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/util/parsers" +) + +// PullImage invokes 'rkt fetch' to download an aci. +// TODO(yifan): Now we only support docker images, this should be changed +// once the format of image is landed, see: +// +// http://issue.k8s.io/7203 +// +func (r *Runtime) PullImage(image kubecontainer.ImageSpec, pullSecrets []api.Secret) error { + img := image.Image + // TODO(yifan): The credential operation is a copy from dockertools package, + // Need to resolve the code duplication. + repoToPull, _ := parsers.ParseImageName(img) + keyring, err := credentialprovider.MakeDockerKeyring(pullSecrets, r.dockerKeyring) + if err != nil { + return err + } + + creds, ok := keyring.Lookup(repoToPull) + if !ok { + glog.V(1).Infof("Pulling image %s without credentials", img) + } + + // Let's update a json. + // TODO(yifan): Find a way to feed this to rkt. + if err := r.writeDockerAuthConfig(img, creds); err != nil { + return err + } + + if _, err := r.runCommand("fetch", dockerPrefix+img); err != nil { + glog.Errorf("Failed to fetch: %v", err) + return err + } + return nil +} + +func (r *Runtime) IsImagePresent(image kubecontainer.ImageSpec) (bool, error) { + images, err := r.listImages(image.Image, false) + return len(images) > 0, err +} + +// ListImages lists all the available appc images on the machine by invoking 'rkt image list'. +func (r *Runtime) ListImages() ([]kubecontainer.Image, error) { + listResp, err := r.apisvc.ListImages(context.Background(), &rktapi.ListImagesRequest{}) + if err != nil { + return nil, fmt.Errorf("couldn't list images: %v", err) + } + + images := make([]kubecontainer.Image, len(listResp.Images)) + for i, image := range listResp.Images { + images[i] = kubecontainer.Image{ + ID: image.Id, + RepoTags: []string{buildImageName(image)}, + Size: image.Size, + } + } + return images, nil +} + +// RemoveImage removes an on-disk image using 'rkt image rm'. +func (r *Runtime) RemoveImage(image kubecontainer.ImageSpec) error { + imageID, err := r.getImageID(image.Image) + if err != nil { + return err + } + if _, err := r.runCommand("image", "rm", imageID); err != nil { + return err + } + return nil +} + +// buildImageName constructs the image name for kubecontainer.Image. +func buildImageName(img *rktapi.Image) string { + return fmt.Sprintf("%s:%s", img.Name, img.Version) +} + +// getImageID tries to find the image ID for the given image name. +// imageName should be in the form of 'name[:version]', e.g., 'example.com/app:latest'. +// The name should matches the result of 'rkt image list'. If the version is empty, +// then 'latest' is assumed. +func (r *Runtime) getImageID(imageName string) (string, error) { + images, err := r.listImages(imageName, false) + if err != nil { + return "", err + } + if len(images) == 0 { + return "", fmt.Errorf("cannot find the image %q", imageName) + } + return images[0].Id, nil +} + +type sortByImportTime []*rktapi.Image + +func (s sortByImportTime) Len() int { return len(s) } +func (s sortByImportTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s sortByImportTime) Less(i, j int) bool { return s[i].ImportTimestamp < s[j].ImportTimestamp } + +// listImages lists the images that have the given name. If detail is true, +// then image manifest is also included in the result. +// Note that there could be more than one images that have the given name, we +// will return the result reversely sorted by the import time, so that the latest +// image comes first. +func (r *Runtime) listImages(image string, detail bool) ([]*rktapi.Image, error) { + repoToPull, tag := parsers.ParseImageName(image) + listResp, err := r.apisvc.ListImages(context.Background(), &rktapi.ListImagesRequest{ + Detail: detail, + Filters: []*rktapi.ImageFilter{ + { + // TODO(yifan): Add a field in the ImageFilter to match the whole name, + // not just keywords. + // https://github.com/coreos/rkt/issues/1872#issuecomment-166456938 + Keywords: []string{repoToPull}, + Labels: []*rktapi.KeyValue{{Key: "version", Value: tag}}, + }, + }, + }) + if err != nil { + return nil, fmt.Errorf("couldn't list images: %v", err) + } + + // TODO(yifan): Let the API service to sort the result: + // See https://github.com/coreos/rkt/issues/1911. + sort.Sort(sort.Reverse(sortByImportTime(listResp.Images))) + return listResp.Images, nil +} + +// getImageManifest retrieves the image manifest for the given image. +func (r *Runtime) getImageManifest(image string) (*appcschema.ImageManifest, error) { + var manifest appcschema.ImageManifest + + images, err := r.listImages(image, true) + if err != nil { + return nil, err + } + if len(images) == 0 { + return nil, fmt.Errorf("cannot find the image %q", image) + } + + return &manifest, json.Unmarshal(images[0].Manifest, &manifest) +} + +// TODO(yifan): This is very racy, unefficient, and unsafe, we need to provide +// different namespaces. See: https://github.com/coreos/rkt/issues/836. +func (r *Runtime) writeDockerAuthConfig(image string, credsSlice []docker.AuthConfiguration) error { + if len(credsSlice) == 0 { + return nil + } + + creds := docker.AuthConfiguration{} + // TODO handle multiple creds + if len(credsSlice) >= 1 { + creds = credsSlice[0] + } + + registry := "index.docker.io" + // Image spec: [/]/[: 0 { + // Need to add '-r' flag if we include '--since' and '-n' at the both time, + // see https://github.com/systemd/systemd/issues/1477 + cmd.Args = append(cmd.Args, "--since", time.Unix(since, 0).Format(journalSinceLayout)) + if logOptions.TailLines != nil { + cmd.Args = append(cmd.Args, "-r") + } + } + + outPipe, err := cmd.StdoutPipe() + if err != nil { + glog.Errorf("rkt: cannot create pipe for journalctl's stdout: %v", err) + return err + } + errPipe, err := cmd.StderrPipe() + if err != nil { + glog.Errorf("rkt: cannot create pipe for journalctl's stderr: %v", err) + return err + } + + if err := cmd.Start(); err != nil { + return err + } + + var wg sync.WaitGroup + + wg.Add(2) + + go pipeLog(&wg, logOptions, outPipe, stdout) + go pipeLog(&wg, logOptions, errPipe, stderr) + + // Wait until the logs are fed to stdout, stderr. + wg.Wait() + cmd.Wait() + + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/rkt.go b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/rkt.go new file mode 100644 index 000000000..1229491e4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/rkt.go @@ -0,0 +1,1545 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 rkt + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path" + "strconv" + "strings" + "syscall" + "time" + + appcschema "github.com/appc/spec/schema" + appctypes "github.com/appc/spec/schema/types" + "github.com/coreos/go-systemd/unit" + rktapi "github.com/coreos/rkt/api/v1alpha" + "github.com/golang/glog" + "golang.org/x/net/context" + "google.golang.org/grpc" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + "k8s.io/kubernetes/pkg/credentialprovider" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + proberesults "k8s.io/kubernetes/pkg/kubelet/prober/results" + "k8s.io/kubernetes/pkg/kubelet/util/format" + "k8s.io/kubernetes/pkg/securitycontext" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util" + utilexec "k8s.io/kubernetes/pkg/util/exec" + "k8s.io/kubernetes/pkg/util/flowcontrol" + "k8s.io/kubernetes/pkg/util/sets" + utilstrings "k8s.io/kubernetes/pkg/util/strings" +) + +const ( + RktType = "rkt" + DefaultRktAPIServiceEndpoint = "localhost:15441" + + minimumAppcVersion = "0.7.4" + minimumRktBinVersion = "1.2.1" + recommendedRktBinVersion = "1.2.1" + minimumRktApiVersion = "1.0.0-alpha" + minimumSystemdVersion = "219" + + systemdServiceDir = "/run/systemd/system" + rktDataDir = "/var/lib/rkt" + rktLocalConfigDir = "/etc/rkt" + + kubernetesUnitPrefix = "k8s" + unitKubernetesSection = "X-Kubernetes" + unitPodName = "POD" + unitRktID = "RktID" + unitRestartCount = "RestartCount" + + k8sRktKubeletAnno = "rkt.kubernetes.io/managed-by-kubelet" + k8sRktKubeletAnnoValue = "true" + k8sRktUIDAnno = "rkt.kubernetes.io/uid" + k8sRktNameAnno = "rkt.kubernetes.io/name" + k8sRktNamespaceAnno = "rkt.kubernetes.io/namespace" + //TODO: remove the creation time annotation once this is closed: https://github.com/coreos/rkt/issues/1789 + k8sRktCreationTimeAnno = "rkt.kubernetes.io/created" + k8sRktContainerHashAnno = "rkt.kubernetes.io/container-hash" + k8sRktRestartCountAnno = "rkt.kubernetes.io/restart-count" + k8sRktTerminationMessagePathAnno = "rkt.kubernetes.io/termination-message-path" + dockerPrefix = "docker://" + + authDir = "auth.d" + dockerAuthTemplate = `{"rktKind":"dockerAuth","rktVersion":"v1","registries":[%q],"credentials":{"user":%q,"password":%q}}` + + defaultRktAPIServiceAddr = "localhost:15441" + defaultNetworkName = "rkt.kubernetes.io" + + // ndots specifies the minimum number of dots that a domain name must contain for the resolver to consider it as FQDN (fully-qualified) + // we want to able to consider SRV lookup names like _dns._udp.kube-dns.default.svc to be considered relative. + // hence, setting ndots to be 5. + // TODO(yifan): Move this and dockertools.ndotsDNSOption to a common package. + defaultDNSOption = "ndots:5" + + // Annotations for the ENTRYPOINT and CMD for an ACI that's converted from Docker image. + // TODO(yifan): Import them from docker2aci. See https://github.com/appc/docker2aci/issues/133. + appcDockerEntrypoint = "appc.io/docker/entrypoint" + appcDockerCmd = "appc.io/docker/cmd" +) + +// Runtime implements the Containerruntime for rkt. The implementation +// uses systemd, so in order to run this runtime, systemd must be installed +// on the machine. +type Runtime struct { + systemd systemdInterface + // The grpc client for rkt api-service. + apisvcConn *grpc.ClientConn + apisvc rktapi.PublicAPIClient + config *Config + // TODO(yifan): Refactor this to be generic keyring. + dockerKeyring credentialprovider.DockerKeyring + + containerRefManager *kubecontainer.RefManager + runtimeHelper kubecontainer.RuntimeHelper + recorder record.EventRecorder + livenessManager proberesults.Manager + volumeGetter volumeGetter + imagePuller kubecontainer.ImagePuller + + versions versions +} + +var _ kubecontainer.Runtime = &Runtime{} + +// TODO(yifan): Remove this when volumeManager is moved to separate package. +type volumeGetter interface { + GetVolumes(podUID types.UID) (kubecontainer.VolumeMap, bool) +} + +// New creates the rkt container runtime which implements the container runtime interface. +// It will test if the rkt binary is in the $PATH, and whether we can get the +// version of it. If so, creates the rkt container runtime, otherwise returns an error. +func New( + apiEndpoint string, + config *Config, + runtimeHelper kubecontainer.RuntimeHelper, + recorder record.EventRecorder, + containerRefManager *kubecontainer.RefManager, + livenessManager proberesults.Manager, + volumeGetter volumeGetter, + imageBackOff *flowcontrol.Backoff, + serializeImagePulls bool, +) (*Runtime, error) { + // Create dbus connection. + systemd, err := newSystemd() + if err != nil { + return nil, fmt.Errorf("rkt: cannot create systemd interface: %v", err) + } + + // TODO(yifan): Use secure connection. + apisvcConn, err := grpc.Dial(apiEndpoint, grpc.WithInsecure()) + if err != nil { + return nil, fmt.Errorf("rkt: cannot connect to rkt api service: %v", err) + } + + // TODO(yifan): Get the rkt path from API service. + if config.Path == "" { + // No default rkt path was set, so try to find one in $PATH. + var err error + config.Path, err = exec.LookPath("rkt") + if err != nil { + return nil, fmt.Errorf("cannot find rkt binary: %v", err) + } + } + + rkt := &Runtime{ + systemd: systemd, + apisvcConn: apisvcConn, + apisvc: rktapi.NewPublicAPIClient(apisvcConn), + config: config, + dockerKeyring: credentialprovider.NewDockerKeyring(), + containerRefManager: containerRefManager, + runtimeHelper: runtimeHelper, + recorder: recorder, + livenessManager: livenessManager, + volumeGetter: volumeGetter, + } + + rkt.config, err = rkt.getConfig(rkt.config) + if err != nil { + return nil, fmt.Errorf("rkt: cannot get config from rkt api service: %v", err) + } + + if serializeImagePulls { + rkt.imagePuller = kubecontainer.NewSerializedImagePuller(recorder, rkt, imageBackOff) + } else { + rkt.imagePuller = kubecontainer.NewImagePuller(recorder, rkt, imageBackOff) + } + + if err := rkt.getVersions(); err != nil { + return nil, fmt.Errorf("rkt: error getting version info: %v", err) + } + + return rkt, nil +} + +func (r *Runtime) buildCommand(args ...string) *exec.Cmd { + cmd := exec.Command(r.config.Path) + cmd.Args = append(cmd.Args, r.config.buildGlobalOptions()...) + cmd.Args = append(cmd.Args, args...) + return cmd +} + +// convertToACName converts a string into ACName. +func convertToACName(name string) appctypes.ACName { + // Note that as the 'name' already matches 'DNS_LABEL' + // defined in pkg/api/types.go, there shouldn't be error or panic. + acname, _ := appctypes.SanitizeACName(name) + return *appctypes.MustACName(acname) +} + +// runCommand invokes rkt binary with arguments and returns the result +// from stdout in a list of strings. Each string in the list is a line. +func (r *Runtime) runCommand(args ...string) ([]string, error) { + glog.V(4).Info("rkt: Run command:", args) + + var stdout, stderr bytes.Buffer + cmd := r.buildCommand(args...) + cmd.Stdout, cmd.Stderr = &stdout, &stderr + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("failed to run %v: %v\nstdout: %v\nstderr: %v", args, err, stdout.String(), stderr.String()) + } + return strings.Split(strings.TrimSpace(stdout.String()), "\n"), nil +} + +// makePodServiceFileName constructs the unit file name for a pod using its UID. +func makePodServiceFileName(uid types.UID) string { + // TODO(yifan): Add name for readability? We need to consider the + // limit of the length. + return fmt.Sprintf("%s_%s.service", kubernetesUnitPrefix, uid) +} + +// setIsolators sets the apps' isolators according to the security context and resource spec. +func setIsolators(app *appctypes.App, c *api.Container, ctx *api.SecurityContext) error { + var isolators []appctypes.Isolator + + // Capabilities isolators. + if ctx != nil { + var addCaps, dropCaps []string + + if ctx.Capabilities != nil { + addCaps, dropCaps = securitycontext.MakeCapabilities(ctx.Capabilities.Add, ctx.Capabilities.Drop) + } + if ctx.Privileged != nil && *ctx.Privileged { + addCaps, dropCaps = allCapabilities(), []string{} + } + if len(addCaps) > 0 { + set, err := appctypes.NewLinuxCapabilitiesRetainSet(addCaps...) + if err != nil { + return err + } + isolators = append(isolators, set.AsIsolator()) + } + if len(dropCaps) > 0 { + set, err := appctypes.NewLinuxCapabilitiesRevokeSet(dropCaps...) + if err != nil { + return err + } + isolators = append(isolators, set.AsIsolator()) + } + } + + // Resources isolators. + type resource struct { + limit string + request string + } + + // If limit is empty, populate it with request and vice versa. + resources := make(map[api.ResourceName]*resource) + for name, quantity := range c.Resources.Limits { + resources[name] = &resource{limit: quantity.String(), request: quantity.String()} + } + for name, quantity := range c.Resources.Requests { + r, ok := resources[name] + if ok { + r.request = quantity.String() + continue + } + resources[name] = &resource{limit: quantity.String(), request: quantity.String()} + } + + for name, res := range resources { + switch name { + case api.ResourceCPU: + cpu, err := appctypes.NewResourceCPUIsolator(res.request, res.limit) + if err != nil { + return err + } + isolators = append(isolators, cpu.AsIsolator()) + case api.ResourceMemory: + memory, err := appctypes.NewResourceMemoryIsolator(res.request, res.limit) + if err != nil { + return err + } + isolators = append(isolators, memory.AsIsolator()) + default: + return fmt.Errorf("resource type not supported: %v", name) + } + } + + mergeIsolators(app, isolators) + return nil +} + +// mergeIsolators replaces the app.Isolators with isolators. +func mergeIsolators(app *appctypes.App, isolators []appctypes.Isolator) { + for _, is := range isolators { + found := false + for j, js := range app.Isolators { + if is.Name.Equals(js.Name) { + switch is.Name { + case appctypes.LinuxCapabilitiesRetainSetName: + // TODO(yifan): More fine grain merge for capability set instead of override. + fallthrough + case appctypes.LinuxCapabilitiesRevokeSetName: + fallthrough + case appctypes.ResourceCPUName: + fallthrough + case appctypes.ResourceMemoryName: + app.Isolators[j] = is + default: + panic(fmt.Sprintf("unexpected isolator name: %v", is.Name)) + } + found = true + break + } + } + if !found { + app.Isolators = append(app.Isolators, is) + } + } +} + +// mergeEnv merges the optEnv with the image's environments. +// The environments defined in the image will be overridden by +// the ones with the same name in optEnv. +func mergeEnv(app *appctypes.App, optEnv []kubecontainer.EnvVar) { + envMap := make(map[string]string) + for _, e := range app.Environment { + envMap[e.Name] = e.Value + } + for _, e := range optEnv { + envMap[e.Name] = e.Value + } + app.Environment = nil + for name, value := range envMap { + app.Environment = append(app.Environment, appctypes.EnvironmentVariable{ + Name: name, + Value: value, + }) + } +} + +// mergeMounts merges the optMounts with the image's mount points. +// The mount points defined in the image will be overridden by the ones +// with the same name in optMounts. +func mergeMounts(app *appctypes.App, optMounts []kubecontainer.Mount) { + mountMap := make(map[appctypes.ACName]appctypes.MountPoint) + for _, m := range app.MountPoints { + mountMap[m.Name] = m + } + for _, m := range optMounts { + mpName := convertToACName(m.Name) + mountMap[mpName] = appctypes.MountPoint{ + Name: mpName, + Path: m.ContainerPath, + ReadOnly: m.ReadOnly, + } + } + app.MountPoints = nil + for _, mount := range mountMap { + app.MountPoints = append(app.MountPoints, mount) + } +} + +// mergePortMappings merges the optPortMappings with the image's port mappings. +// The port mappings defined in the image will be overridden by the ones +// with the same name in optPortMappings. +func mergePortMappings(app *appctypes.App, optPortMappings []kubecontainer.PortMapping) { + portMap := make(map[appctypes.ACName]appctypes.Port) + for _, p := range app.Ports { + portMap[p.Name] = p + } + for _, p := range optPortMappings { + pName := convertToACName(p.Name) + portMap[pName] = appctypes.Port{ + Name: pName, + Protocol: string(p.Protocol), + Port: uint(p.ContainerPort), + } + } + app.Ports = nil + for _, port := range portMap { + app.Ports = append(app.Ports, port) + } +} + +func verifyNonRoot(app *appctypes.App, ctx *api.SecurityContext) error { + if ctx != nil && ctx.RunAsNonRoot != nil && *ctx.RunAsNonRoot { + if ctx.RunAsUser != nil && *ctx.RunAsUser == 0 { + return fmt.Errorf("container's runAsUser breaks non-root policy") + } + if ctx.RunAsUser == nil && app.User == "0" { + return fmt.Errorf("container has no runAsUser and image will run as root") + } + } + return nil +} + +func setSupplementaryGIDs(app *appctypes.App, podCtx *api.PodSecurityContext) { + if podCtx != nil { + app.SupplementaryGIDs = app.SupplementaryGIDs[:0] + for _, v := range podCtx.SupplementalGroups { + app.SupplementaryGIDs = append(app.SupplementaryGIDs, int(v)) + } + if podCtx.FSGroup != nil { + app.SupplementaryGIDs = append(app.SupplementaryGIDs, int(*podCtx.FSGroup)) + } + } +} + +// setApp merges the container spec with the image's manifest. +func setApp(imgManifest *appcschema.ImageManifest, c *api.Container, opts *kubecontainer.RunContainerOptions, ctx *api.SecurityContext, podCtx *api.PodSecurityContext) error { + app := imgManifest.App + + // Set up Exec. + var command, args []string + cmd, ok := imgManifest.Annotations.Get(appcDockerEntrypoint) + if ok { + err := json.Unmarshal([]byte(cmd), &command) + if err != nil { + return fmt.Errorf("cannot unmarshal ENTRYPOINT %q: %v", cmd, err) + } + } + ag, ok := imgManifest.Annotations.Get(appcDockerCmd) + if ok { + err := json.Unmarshal([]byte(ag), &args) + if err != nil { + return fmt.Errorf("cannot unmarshal CMD %q: %v", ag, err) + } + } + userCommand, userArgs := kubecontainer.ExpandContainerCommandAndArgs(c, opts.Envs) + + if len(userCommand) > 0 { + command = userCommand + args = nil // If 'command' is specified, then drop the default args. + } + if len(userArgs) > 0 { + args = userArgs + } + + exec := append(command, args...) + if len(exec) > 0 { + app.Exec = exec + } + + // Set UID and GIDs. + if err := verifyNonRoot(app, ctx); err != nil { + return err + } + if ctx != nil && ctx.RunAsUser != nil { + app.User = strconv.Itoa(int(*ctx.RunAsUser)) + } + setSupplementaryGIDs(app, podCtx) + + // If 'User' or 'Group' are still empty at this point, + // then apply the root UID and GID. + // TODO(yifan): Instead of using root GID, we should use + // the GID which the user is in. + if app.User == "" { + app.User = "0" + } + if app.Group == "" { + app.Group = "0" + } + + // Set working directory. + if len(c.WorkingDir) > 0 { + app.WorkingDirectory = c.WorkingDir + } + + // Notes that we don't create Mounts section in the pod manifest here, + // as Mounts will be automatically generated by rkt. + mergeMounts(app, opts.Mounts) + mergeEnv(app, opts.Envs) + mergePortMappings(app, opts.PortMappings) + + return setIsolators(app, c, ctx) +} + +// makePodManifest transforms a kubelet pod spec to the rkt pod manifest. +func (r *Runtime) makePodManifest(pod *api.Pod, pullSecrets []api.Secret) (*appcschema.PodManifest, error) { + manifest := appcschema.BlankPodManifest() + + listResp, err := r.apisvc.ListPods(context.Background(), &rktapi.ListPodsRequest{ + Detail: true, + Filters: kubernetesPodFilters(pod.UID), + }) + if err != nil { + return nil, fmt.Errorf("couldn't list pods: %v", err) + } + + restartCount := 0 + for _, pod := range listResp.Pods { + manifest := &appcschema.PodManifest{} + err = json.Unmarshal(pod.Manifest, manifest) + if err != nil { + glog.Warningf("rkt: error unmatshaling pod manifest: %v", err) + continue + } + + if countString, ok := manifest.Annotations.Get(k8sRktRestartCountAnno); ok { + num, err := strconv.Atoi(countString) + if err != nil { + glog.Warningf("rkt: error reading restart count on pod: %v", err) + continue + } + if num+1 > restartCount { + restartCount = num + 1 + } + } + } + + manifest.Annotations.Set(*appctypes.MustACIdentifier(k8sRktKubeletAnno), k8sRktKubeletAnnoValue) + manifest.Annotations.Set(*appctypes.MustACIdentifier(k8sRktUIDAnno), string(pod.UID)) + manifest.Annotations.Set(*appctypes.MustACIdentifier(k8sRktNameAnno), pod.Name) + manifest.Annotations.Set(*appctypes.MustACIdentifier(k8sRktNamespaceAnno), pod.Namespace) + manifest.Annotations.Set(*appctypes.MustACIdentifier(k8sRktCreationTimeAnno), strconv.FormatInt(time.Now().Unix(), 10)) + manifest.Annotations.Set(*appctypes.MustACIdentifier(k8sRktRestartCountAnno), strconv.Itoa(restartCount)) + + for _, c := range pod.Spec.Containers { + err := r.newAppcRuntimeApp(pod, c, pullSecrets, manifest) + if err != nil { + return nil, err + } + } + + volumeMap, ok := r.volumeGetter.GetVolumes(pod.UID) + if !ok { + return nil, fmt.Errorf("cannot get the volumes for pod %q", format.Pod(pod)) + } + + // Set global volumes. + for vname, volume := range volumeMap { + manifest.Volumes = append(manifest.Volumes, appctypes.Volume{ + Name: convertToACName(vname), + Kind: "host", + Source: volume.Mounter.GetPath(), + }) + } + + // TODO(yifan): Set pod-level isolators once it's supported in kubernetes. + return manifest, nil +} + +func makeContainerLogMount(opts *kubecontainer.RunContainerOptions, container *api.Container) (*kubecontainer.Mount, error) { + if opts.PodContainerDir == "" || container.TerminationMessagePath == "" { + return nil, nil + } + + // In docker runtime, the container log path contains the container ID. + // However, for rkt runtime, we cannot get the container ID before the + // the container is launched, so here we generate a random uuid to enable + // us to map a container's termination message path to an unique log file + // on the disk. + randomUID := util.NewUUID() + containerLogPath := path.Join(opts.PodContainerDir, string(randomUID)) + fs, err := os.Create(containerLogPath) + if err != nil { + return nil, err + } + + if err := fs.Close(); err != nil { + return nil, err + } + + mnt := &kubecontainer.Mount{ + // Use a random name for the termination message mount, so that + // when a container restarts, it will not overwrite the old termination + // message. + Name: fmt.Sprintf("termination-message-%s", randomUID), + ContainerPath: container.TerminationMessagePath, + HostPath: containerLogPath, + ReadOnly: false, + } + opts.Mounts = append(opts.Mounts, *mnt) + + return mnt, nil +} + +func (r *Runtime) newAppcRuntimeApp(pod *api.Pod, c api.Container, pullSecrets []api.Secret, manifest *appcschema.PodManifest) error { + if err, _ := r.imagePuller.PullImage(pod, &c, pullSecrets); err != nil { + return nil + } + imgManifest, err := r.getImageManifest(c.Image) + if err != nil { + return err + } + + if imgManifest.App == nil { + imgManifest.App = new(appctypes.App) + } + + imageID, err := r.getImageID(c.Image) + if err != nil { + return err + } + hash, err := appctypes.NewHash(imageID) + if err != nil { + return err + } + + // TODO: determine how this should be handled for rkt + opts, err := r.runtimeHelper.GenerateRunContainerOptions(pod, &c, "") + if err != nil { + return err + } + + // create the container log file and make a mount pair. + mnt, err := makeContainerLogMount(opts, &c) + if err != nil { + return err + } + + ctx := securitycontext.DetermineEffectiveSecurityContext(pod, &c) + if err := setApp(imgManifest, &c, opts, ctx, pod.Spec.SecurityContext); err != nil { + return err + } + + ra := appcschema.RuntimeApp{ + Name: convertToACName(c.Name), + Image: appcschema.RuntimeImage{ID: *hash}, + App: imgManifest.App, + Annotations: []appctypes.Annotation{ + { + Name: *appctypes.MustACIdentifier(k8sRktContainerHashAnno), + Value: strconv.FormatUint(kubecontainer.HashContainer(&c), 10), + }, + }, + } + + if mnt != nil { + ra.Annotations = append(ra.Annotations, appctypes.Annotation{ + Name: *appctypes.MustACIdentifier(k8sRktTerminationMessagePathAnno), + Value: mnt.HostPath, + }) + + manifest.Volumes = append(manifest.Volumes, appctypes.Volume{ + Name: convertToACName(mnt.Name), + Kind: "host", + Source: mnt.HostPath, + }) + } + + manifest.Apps = append(manifest.Apps, ra) + + // Set global ports. + for _, port := range opts.PortMappings { + manifest.Ports = append(manifest.Ports, appctypes.ExposedPort{ + Name: convertToACName(port.Name), + HostPort: uint(port.HostPort), + }) + } + + return nil +} + +func runningKubernetesPodFilters(uid types.UID) []*rktapi.PodFilter { + return []*rktapi.PodFilter{ + { + States: []rktapi.PodState{ + rktapi.PodState_POD_STATE_RUNNING, + }, + Annotations: []*rktapi.KeyValue{ + { + Key: k8sRktKubeletAnno, + Value: k8sRktKubeletAnnoValue, + }, + { + Key: k8sRktUIDAnno, + Value: string(uid), + }, + }, + }, + } +} + +func kubernetesPodFilters(uid types.UID) []*rktapi.PodFilter { + return []*rktapi.PodFilter{ + { + Annotations: []*rktapi.KeyValue{ + { + Key: k8sRktKubeletAnno, + Value: k8sRktKubeletAnnoValue, + }, + { + Key: k8sRktUIDAnno, + Value: string(uid), + }, + }, + }, + } +} + +func newUnitOption(section, name, value string) *unit.UnitOption { + return &unit.UnitOption{Section: section, Name: name, Value: value} +} + +// apiPodToruntimePod converts an api.Pod to kubelet/container.Pod. +func apiPodToruntimePod(uuid string, pod *api.Pod) *kubecontainer.Pod { + p := &kubecontainer.Pod{ + ID: pod.UID, + Name: pod.Name, + Namespace: pod.Namespace, + } + for i := range pod.Spec.Containers { + c := &pod.Spec.Containers[i] + p.Containers = append(p.Containers, &kubecontainer.Container{ + ID: buildContainerID(&containerID{uuid, c.Name}), + Name: c.Name, + Image: c.Image, + Hash: kubecontainer.HashContainer(c), + Created: time.Now().Unix(), + }) + } + return p +} + +// serviceFilePath returns the absolute path of the service file. +func serviceFilePath(serviceName string) string { + return path.Join(systemdServiceDir, serviceName) +} + +// generateRunCommand crafts a 'rkt run-prepared' command with necessary parameters. +func (r *Runtime) generateRunCommand(pod *api.Pod, uuid string) (string, error) { + runPrepared := r.buildCommand("run-prepared").Args + + // Setup network configuration. + if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostNetwork { + runPrepared = append(runPrepared, "--net=host") + } else { + runPrepared = append(runPrepared, fmt.Sprintf("--net=%s", defaultNetworkName)) + } + + // Setup DNS. + dnsServers, dnsSearches, err := r.runtimeHelper.GetClusterDNS(pod) + if err != nil { + return "", err + } + for _, server := range dnsServers { + runPrepared = append(runPrepared, fmt.Sprintf("--dns=%s", server)) + } + for _, search := range dnsSearches { + runPrepared = append(runPrepared, fmt.Sprintf("--dns-search=%s", search)) + } + if len(dnsServers) > 0 || len(dnsSearches) > 0 { + runPrepared = append(runPrepared, fmt.Sprintf("--dns-opt=%s", defaultDNSOption)) + } + + // TODO(yifan): host domain is not being used. + hostname, _ := r.runtimeHelper.GeneratePodHostNameAndDomain(pod) + runPrepared = append(runPrepared, fmt.Sprintf("--hostname=%s", hostname)) + runPrepared = append(runPrepared, uuid) + return strings.Join(runPrepared, " "), nil +} + +// preparePod will: +// +// 1. Invoke 'rkt prepare' to prepare the pod, and get the rkt pod uuid. +// 2. Create the unit file and save it under systemdUnitDir. +// +// On success, it will return a string that represents name of the unit file +// and the runtime pod. +func (r *Runtime) preparePod(pod *api.Pod, pullSecrets []api.Secret) (string, *kubecontainer.Pod, error) { + // Generate the pod manifest from the pod spec. + manifest, err := r.makePodManifest(pod, pullSecrets) + if err != nil { + return "", nil, err + } + manifestFile, err := ioutil.TempFile("", fmt.Sprintf("manifest-%s-", pod.Name)) + if err != nil { + return "", nil, err + } + defer func() { + manifestFile.Close() + if err := os.Remove(manifestFile.Name()); err != nil { + glog.Warningf("rkt: Cannot remove temp manifest file %q: %v", manifestFile.Name(), err) + } + }() + + data, err := json.Marshal(manifest) + if err != nil { + return "", nil, err + } + + glog.V(4).Infof("Generating pod manifest for pod %q: %v", format.Pod(pod), string(data)) + // Since File.Write returns error if the written length is less than len(data), + // so check error is enough for us. + if _, err := manifestFile.Write(data); err != nil { + return "", nil, err + } + + // Run 'rkt prepare' to get the rkt UUID. + cmds := []string{"prepare", "--quiet", "--pod-manifest", manifestFile.Name()} + if r.config.Stage1Image != "" { + cmds = append(cmds, "--stage1-path", r.config.Stage1Image) + } + output, err := r.runCommand(cmds...) + if err != nil { + return "", nil, err + } + if len(output) != 1 { + return "", nil, fmt.Errorf("invalid output from 'rkt prepare': %v", output) + } + uuid := output[0] + glog.V(4).Infof("'rkt prepare' returns %q", uuid) + + // Create systemd service file for the rkt pod. + runPrepared, err := r.generateRunCommand(pod, uuid) + if err != nil { + return "", nil, fmt.Errorf("failed to generate 'rkt run-prepared' command: %v", err) + } + + // TODO handle pod.Spec.HostPID + // TODO handle pod.Spec.HostIPC + + units := []*unit.UnitOption{ + newUnitOption("Service", "ExecStart", runPrepared), + // This enables graceful stop. + newUnitOption("Service", "KillMode", "mixed"), + } + + // Check if there's old rkt pod corresponding to the same pod, if so, update the restart count. + var needReload bool + serviceName := makePodServiceFileName(pod.UID) + if _, err := os.Stat(serviceFilePath(serviceName)); err == nil { + // Service file already exists, that means the pod is being restarted. + needReload = true + } + + glog.V(4).Infof("rkt: Creating service file %q for pod %q", serviceName, format.Pod(pod)) + serviceFile, err := os.Create(serviceFilePath(serviceName)) + if err != nil { + return "", nil, err + } + if _, err := io.Copy(serviceFile, unit.Serialize(units)); err != nil { + return "", nil, err + } + serviceFile.Close() + if needReload { + if err := r.systemd.Reload(); err != nil { + return "", nil, err + } + } + + return serviceName, apiPodToruntimePod(uuid, pod), nil +} + +// generateEvents is a helper function that generates some container +// life cycle events for containers in a pod. +func (r *Runtime) generateEvents(runtimePod *kubecontainer.Pod, reason string, failure error) { + // Set up container references. + for _, c := range runtimePod.Containers { + containerID := c.ID + id, err := parseContainerID(containerID) + if err != nil { + glog.Warningf("Invalid container ID %q", containerID) + continue + } + + ref, ok := r.containerRefManager.GetRef(containerID) + if !ok { + glog.Warningf("No ref for container %q", containerID) + continue + } + + // Note that 'rkt id' is the pod id. + uuid := utilstrings.ShortenString(id.uuid, 8) + switch reason { + case "Created": + r.recorder.Eventf(ref, api.EventTypeNormal, kubecontainer.CreatedContainer, "Created with rkt id %v", uuid) + case "Started": + r.recorder.Eventf(ref, api.EventTypeNormal, kubecontainer.StartedContainer, "Started with rkt id %v", uuid) + case "Failed": + r.recorder.Eventf(ref, api.EventTypeWarning, kubecontainer.FailedToStartContainer, "Failed to start with rkt id %v with error %v", uuid, failure) + case "Killing": + r.recorder.Eventf(ref, api.EventTypeNormal, kubecontainer.KillingContainer, "Killing with rkt id %v", uuid) + default: + glog.Errorf("rkt: Unexpected event %q", reason) + } + } + return +} + +// RunPod first creates the unit file for a pod, and then +// starts the unit over d-bus. +func (r *Runtime) RunPod(pod *api.Pod, pullSecrets []api.Secret) error { + glog.V(4).Infof("Rkt starts to run pod: name %q.", format.Pod(pod)) + + name, runtimePod, prepareErr := r.preparePod(pod, pullSecrets) + + // Set container references and generate events. + // If preparedPod fails, then send out 'failed' events for each container. + // Otherwise, store the container references so we can use them later to send events. + for i, c := range pod.Spec.Containers { + ref, err := kubecontainer.GenerateContainerRef(pod, &c) + if err != nil { + glog.Errorf("Couldn't make a ref to pod %q, container %v: '%v'", format.Pod(pod), c.Name, err) + continue + } + if prepareErr != nil { + r.recorder.Eventf(ref, api.EventTypeWarning, kubecontainer.FailedToCreateContainer, "Failed to create rkt container with error: %v", prepareErr) + continue + } + containerID := runtimePod.Containers[i].ID + r.containerRefManager.SetRef(containerID, ref) + } + + if prepareErr != nil { + return prepareErr + } + + r.generateEvents(runtimePod, "Created", nil) + + // RestartUnit has the same effect as StartUnit if the unit is not running, besides it can restart + // a unit if the unit file is changed and reloaded. + reschan := make(chan string) + _, err := r.systemd.RestartUnit(name, "replace", reschan) + if err != nil { + r.generateEvents(runtimePod, "Failed", err) + return err + } + + res := <-reschan + if res != "done" { + err := fmt.Errorf("Failed to restart unit %q: %s", name, res) + r.generateEvents(runtimePod, "Failed", err) + return err + } + + r.generateEvents(runtimePod, "Started", nil) + + return nil +} + +// convertRktPod will convert a rktapi.Pod to a kubecontainer.Pod +func (r *Runtime) convertRktPod(rktpod *rktapi.Pod) (*kubecontainer.Pod, error) { + manifest := &appcschema.PodManifest{} + err := json.Unmarshal(rktpod.Manifest, manifest) + if err != nil { + return nil, err + } + + podUID, ok := manifest.Annotations.Get(k8sRktUIDAnno) + if !ok { + return nil, fmt.Errorf("pod is missing annotation %s", k8sRktUIDAnno) + } + podName, ok := manifest.Annotations.Get(k8sRktNameAnno) + if !ok { + return nil, fmt.Errorf("pod is missing annotation %s", k8sRktNameAnno) + } + podNamespace, ok := manifest.Annotations.Get(k8sRktNamespaceAnno) + if !ok { + return nil, fmt.Errorf("pod is missing annotation %s", k8sRktNamespaceAnno) + } + podCreatedString, ok := manifest.Annotations.Get(k8sRktCreationTimeAnno) + if !ok { + return nil, fmt.Errorf("pod is missing annotation %s", k8sRktCreationTimeAnno) + } + podCreated, err := strconv.ParseInt(podCreatedString, 10, 64) + if err != nil { + return nil, fmt.Errorf("couldn't parse pod creation timestamp: %v", err) + } + + kubepod := &kubecontainer.Pod{ + ID: types.UID(podUID), + Name: podName, + Namespace: podNamespace, + } + + for i, app := range rktpod.Apps { + // The order of the apps is determined by the rkt pod manifest. + // TODO(yifan): Let the server to unmarshal the annotations? https://github.com/coreos/rkt/issues/1872 + hashStr, ok := manifest.Apps[i].Annotations.Get(k8sRktContainerHashAnno) + if !ok { + return nil, fmt.Errorf("app %q is missing annotation %s", app.Name, k8sRktContainerHashAnno) + } + containerHash, err := strconv.ParseUint(hashStr, 10, 64) + if err != nil { + return nil, fmt.Errorf("couldn't parse container's hash %q: %v", hashStr, err) + } + + kubepod.Containers = append(kubepod.Containers, &kubecontainer.Container{ + ID: buildContainerID(&containerID{rktpod.Id, app.Name}), + Name: app.Name, + // By default, the version returned by rkt API service will be "latest" if not specified. + Image: fmt.Sprintf("%s:%s", app.Image.Name, app.Image.Version), + Hash: containerHash, + Created: podCreated, + State: appStateToContainerState(app.State), + }) + } + + return kubepod, nil +} + +// GetPods runs 'systemctl list-unit' and 'rkt list' to get the list of rkt pods. +// Then it will use the result to construct a list of container runtime pods. +// If all is false, then only running pods will be returned, otherwise all pods will be +// returned. +func (r *Runtime) GetPods(all bool) ([]*kubecontainer.Pod, error) { + glog.V(4).Infof("Rkt getting pods") + + listReq := &rktapi.ListPodsRequest{ + Detail: true, + Filters: []*rktapi.PodFilter{ + { + Annotations: []*rktapi.KeyValue{ + { + Key: k8sRktKubeletAnno, + Value: k8sRktKubeletAnnoValue, + }, + }, + }, + }, + } + if !all { + listReq.Filters[0].States = []rktapi.PodState{rktapi.PodState_POD_STATE_RUNNING} + } + listResp, err := r.apisvc.ListPods(context.Background(), listReq) + if err != nil { + return nil, fmt.Errorf("couldn't list pods: %v", err) + } + + pods := make(map[types.UID]*kubecontainer.Pod) + var podIDs []types.UID + for _, pod := range listResp.Pods { + pod, err := r.convertRktPod(pod) + if err != nil { + glog.Warningf("rkt: Cannot construct pod from unit file: %v.", err) + continue + } + + // Group pods together. + oldPod, found := pods[pod.ID] + if !found { + pods[pod.ID] = pod + podIDs = append(podIDs, pod.ID) + continue + } + + oldPod.Containers = append(oldPod.Containers, pod.Containers...) + } + + // Convert map to list, using the consistent order from the podIDs array. + var result []*kubecontainer.Pod + for _, id := range podIDs { + result = append(result, pods[id]) + } + + return result, nil +} + +// KillPod invokes 'systemctl kill' to kill the unit that runs the pod. +// TODO(yifan): Handle network plugin. +func (r *Runtime) KillPod(pod *api.Pod, runningPod kubecontainer.Pod) error { + glog.V(4).Infof("Rkt is killing pod: name %q.", runningPod.Name) + + serviceName := makePodServiceFileName(runningPod.ID) + r.generateEvents(&runningPod, "Killing", nil) + for _, c := range runningPod.Containers { + r.containerRefManager.ClearRef(c.ID) + } + + // Touch the systemd service file to update the mod time so it will + // not be garbage collected too soon. + if err := os.Chtimes(serviceFilePath(serviceName), time.Now(), time.Now()); err != nil { + glog.Errorf("rkt: Failed to change the modification time of the service file %q: %v", serviceName, err) + return err + } + + // Since all service file have 'KillMode=mixed', the processes in + // the unit's cgroup will receive a SIGKILL if the normal stop timeouts. + reschan := make(chan string) + _, err := r.systemd.StopUnit(serviceName, "replace", reschan) + if err != nil { + glog.Errorf("rkt: Failed to stop unit %q: %v", serviceName, err) + return err + } + + res := <-reschan + if res != "done" { + err := fmt.Errorf("invalid result: %s", res) + glog.Errorf("rkt: Failed to stop unit %q: %v", serviceName, err) + return err + } + + return nil +} + +func (r *Runtime) Type() string { + return RktType +} + +func (r *Runtime) Version() (kubecontainer.Version, error) { + r.versions.RLock() + defer r.versions.RUnlock() + return r.versions.binVersion, nil +} + +func (r *Runtime) APIVersion() (kubecontainer.Version, error) { + r.versions.RLock() + defer r.versions.RUnlock() + return r.versions.apiVersion, nil +} + +// Status returns error if rkt is unhealthy, nil otherwise. +func (r *Runtime) Status() error { + return r.checkVersion(minimumRktBinVersion, recommendedRktBinVersion, minimumAppcVersion, minimumRktApiVersion, minimumSystemdVersion) +} + +// SyncPod syncs the running pod to match the specified desired pod. +func (r *Runtime) SyncPod(pod *api.Pod, podStatus api.PodStatus, internalPodStatus *kubecontainer.PodStatus, pullSecrets []api.Secret, backOff *flowcontrol.Backoff) (result kubecontainer.PodSyncResult) { + var err error + defer func() { + if err != nil { + result.Fail(err) + } + }() + // TODO: (random-liu) Stop using running pod in SyncPod() + // TODO: (random-liu) Rename podStatus to apiPodStatus, rename internalPodStatus to podStatus, and use new pod status as much as possible, + // we may stop using apiPodStatus someday. + runningPod := kubecontainer.ConvertPodStatusToRunningPod(internalPodStatus) + // Add references to all containers. + unidentifiedContainers := make(map[kubecontainer.ContainerID]*kubecontainer.Container) + for _, c := range runningPod.Containers { + unidentifiedContainers[c.ID] = c + } + + restartPod := false + for _, container := range pod.Spec.Containers { + expectedHash := kubecontainer.HashContainer(&container) + + c := runningPod.FindContainerByName(container.Name) + if c == nil { + if kubecontainer.ShouldContainerBeRestarted(&container, pod, internalPodStatus) { + glog.V(3).Infof("Container %+v is dead, but RestartPolicy says that we should restart it.", container) + // TODO(yifan): Containers in one pod are fate-sharing at this moment, see: + // https://github.com/appc/spec/issues/276. + restartPod = true + break + } + continue + } + + // TODO: check for non-root image directives. See ../docker/manager.go#SyncPod + + // TODO(yifan): Take care of host network change. + containerChanged := c.Hash != 0 && c.Hash != expectedHash + if containerChanged { + glog.Infof("Pod %q container %q hash changed (%d vs %d), it will be killed and re-created.", format.Pod(pod), container.Name, c.Hash, expectedHash) + restartPod = true + break + } + + liveness, found := r.livenessManager.Get(c.ID) + if found && liveness != proberesults.Success && pod.Spec.RestartPolicy != api.RestartPolicyNever { + glog.Infof("Pod %q container %q is unhealthy, it will be killed and re-created.", format.Pod(pod), container.Name) + restartPod = true + break + } + + delete(unidentifiedContainers, c.ID) + } + + // If there is any unidentified containers, restart the pod. + if len(unidentifiedContainers) > 0 { + restartPod = true + } + + if restartPod { + // Kill the pod only if the pod is actually running. + if len(runningPod.Containers) > 0 { + if err = r.KillPod(pod, runningPod); err != nil { + return + } + } + if err = r.RunPod(pod, pullSecrets); err != nil { + return + } + } + return +} + +// GarbageCollect collects the pods/containers. +// TODO(yifan): Enforce the gc policy, also, it would be better if we can +// just GC kubernetes pods. +func (r *Runtime) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy) error { + if err := exec.Command("systemctl", "reset-failed").Run(); err != nil { + glog.Errorf("rkt: Failed to reset failed systemd services: %v, continue to gc anyway...", err) + } + + if _, err := r.runCommand("gc", "--grace-period="+gcPolicy.MinAge.String(), "--expire-prepared="+gcPolicy.MinAge.String()); err != nil { + glog.Errorf("rkt: Failed to gc: %v", err) + } + + // GC all inactive systemd service files. + units, err := r.systemd.ListUnits() + if err != nil { + glog.Errorf("rkt: Failed to list units: %v", err) + return err + } + runningKubernetesUnits := sets.NewString() + for _, u := range units { + if strings.HasPrefix(u.Name, kubernetesUnitPrefix) && u.SubState == "running" { + runningKubernetesUnits.Insert(u.Name) + } + } + + files, err := ioutil.ReadDir(systemdServiceDir) + if err != nil { + glog.Errorf("rkt: Failed to read the systemd service directory: %v", err) + return err + } + for _, f := range files { + if strings.HasPrefix(f.Name(), kubernetesUnitPrefix) && !runningKubernetesUnits.Has(f.Name()) && f.ModTime().Before(time.Now().Add(-gcPolicy.MinAge)) { + glog.V(4).Infof("rkt: Removing inactive systemd service file: %v", f.Name()) + if err := os.Remove(serviceFilePath(f.Name())); err != nil { + glog.Warningf("rkt: Failed to remove inactive systemd service file %v: %v", f.Name(), err) + } + } + } + return nil +} + +// Note: In rkt, the container ID is in the form of "UUID:appName", where +// appName is the container name. +// TODO(yifan): If the rkt is using lkvm as the stage1 image, then this function will fail. +func (r *Runtime) RunInContainer(containerID kubecontainer.ContainerID, cmd []string) ([]byte, error) { + glog.V(4).Infof("Rkt running in container.") + + id, err := parseContainerID(containerID) + if err != nil { + return nil, err + } + args := append([]string{}, "enter", fmt.Sprintf("--app=%s", id.appName), id.uuid) + args = append(args, cmd...) + + result, err := r.buildCommand(args...).CombinedOutput() + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + err = &rktExitError{exitErr} + } + } + return result, err +} + +// rktExitError implemets /pkg/util/exec.ExitError interface. +type rktExitError struct{ *exec.ExitError } + +var _ utilexec.ExitError = &rktExitError{} + +func (r *rktExitError) ExitStatus() int { + if status, ok := r.Sys().(syscall.WaitStatus); ok { + return status.ExitStatus() + } + return 0 +} + +func (r *Runtime) AttachContainer(containerID kubecontainer.ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error { + return fmt.Errorf("unimplemented") +} + +// Note: In rkt, the container ID is in the form of "UUID:appName", where UUID is +// the rkt UUID, and appName is the container name. +// TODO(yifan): If the rkt is using lkvm as the stage1 image, then this function will fail. +func (r *Runtime) ExecInContainer(containerID kubecontainer.ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error { + glog.V(4).Infof("Rkt execing in container.") + + id, err := parseContainerID(containerID) + if err != nil { + return err + } + args := append([]string{}, "enter", fmt.Sprintf("--app=%s", id.appName), id.uuid) + args = append(args, cmd...) + command := r.buildCommand(args...) + + if tty { + p, err := kubecontainer.StartPty(command) + if err != nil { + return err + } + defer p.Close() + + // make sure to close the stdout stream + defer stdout.Close() + + if stdin != nil { + go io.Copy(p, stdin) + } + if stdout != nil { + go io.Copy(stdout, p) + } + return command.Wait() + } + if stdin != nil { + // Use an os.Pipe here as it returns true *os.File objects. + // This way, if you run 'kubectl exec -i bash' (no tty) and type 'exit', + // the call below to command.Run() can unblock because its Stdin is the read half + // of the pipe. + r, w, err := os.Pipe() + if err != nil { + return err + } + go io.Copy(w, stdin) + + command.Stdin = r + } + if stdout != nil { + command.Stdout = stdout + } + if stderr != nil { + command.Stderr = stderr + } + return command.Run() +} + +// PortForward executes socat in the pod's network namespace and copies +// data between stream (representing the user's local connection on their +// computer) and the specified port in the container. +// +// TODO: +// - match cgroups of container +// - should we support nsenter + socat on the host? (current impl) +// - should we support nsenter + socat in a container, running with elevated privs and --pid=host? +// +// TODO(yifan): Merge with the same function in dockertools. +// TODO(yifan): If the rkt is using lkvm as the stage1 image, then this function will fail. +func (r *Runtime) PortForward(pod *kubecontainer.Pod, port uint16, stream io.ReadWriteCloser) error { + glog.V(4).Infof("Rkt port forwarding in container.") + + listResp, err := r.apisvc.ListPods(context.Background(), &rktapi.ListPodsRequest{ + Detail: true, + Filters: runningKubernetesPodFilters(pod.ID), + }) + if err != nil { + return fmt.Errorf("couldn't list pods: %v", err) + } + + if len(listResp.Pods) != 1 { + var podlist []string + for _, p := range listResp.Pods { + podlist = append(podlist, p.Id) + } + return fmt.Errorf("more than one running rkt pod for the kubernetes pod [%s]", strings.Join(podlist, ", ")) + } + + socatPath, lookupErr := exec.LookPath("socat") + if lookupErr != nil { + return fmt.Errorf("unable to do port forwarding: socat not found.") + } + + args := []string{"-t", fmt.Sprintf("%d", listResp.Pods[0].Pid), "-n", socatPath, "-", fmt.Sprintf("TCP4:localhost:%d", port)} + + nsenterPath, lookupErr := exec.LookPath("nsenter") + if lookupErr != nil { + return fmt.Errorf("unable to do port forwarding: nsenter not found.") + } + + command := exec.Command(nsenterPath, args...) + command.Stdout = stream + + // If we use Stdin, command.Run() won't return until the goroutine that's copying + // from stream finishes. Unfortunately, if you have a client like telnet connected + // via port forwarding, as long as the user's telnet client is connected to the user's + // local listener that port forwarding sets up, the telnet session never exits. This + // means that even if socat has finished running, command.Run() won't ever return + // (because the client still has the connection and stream open). + // + // The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe + // when the command (socat) exits. + inPipe, err := command.StdinPipe() + if err != nil { + return fmt.Errorf("unable to do port forwarding: error creating stdin pipe: %v", err) + } + go func() { + io.Copy(inPipe, stream) + inPipe.Close() + }() + + return command.Run() +} + +// appStateToContainerState converts rktapi.AppState to kubecontainer.ContainerState. +func appStateToContainerState(state rktapi.AppState) kubecontainer.ContainerState { + switch state { + case rktapi.AppState_APP_STATE_RUNNING: + return kubecontainer.ContainerStateRunning + case rktapi.AppState_APP_STATE_EXITED: + return kubecontainer.ContainerStateExited + } + return kubecontainer.ContainerStateUnknown +} + +// getPodInfo returns the pod manifest, creation time and restart count of the pod. +func getPodInfo(pod *rktapi.Pod) (podManifest *appcschema.PodManifest, creationTime time.Time, restartCount int, err error) { + // TODO(yifan): The manifest is only used for getting the annotations. + // Consider to let the server to unmarshal the annotations. + var manifest appcschema.PodManifest + if err = json.Unmarshal(pod.Manifest, &manifest); err != nil { + return + } + + creationTimeStr, ok := manifest.Annotations.Get(k8sRktCreationTimeAnno) + if !ok { + err = fmt.Errorf("no creation timestamp in pod manifest") + return + } + unixSec, err := strconv.ParseInt(creationTimeStr, 10, 64) + if err != nil { + return + } + + if countString, ok := manifest.Annotations.Get(k8sRktRestartCountAnno); ok { + restartCount, err = strconv.Atoi(countString) + if err != nil { + return + } + } + + return &manifest, time.Unix(unixSec, 0), restartCount, nil +} + +// populateContainerStatus fills the container status according to the app's information. +func populateContainerStatus(pod rktapi.Pod, app rktapi.App, runtimeApp appcschema.RuntimeApp, restartCount int, creationTime time.Time) (*kubecontainer.ContainerStatus, error) { + hashStr, ok := runtimeApp.Annotations.Get(k8sRktContainerHashAnno) + if !ok { + return nil, fmt.Errorf("No container hash in pod manifest") + } + + hashNum, err := strconv.ParseUint(hashStr, 10, 64) + if err != nil { + return nil, err + } + + var reason, message string + if app.State == rktapi.AppState_APP_STATE_EXITED { + if app.ExitCode == 0 { + reason = "Completed" + } else { + reason = "Error" + } + } + + terminationMessagePath, ok := runtimeApp.Annotations.Get(k8sRktTerminationMessagePathAnno) + if ok { + if data, err := ioutil.ReadFile(terminationMessagePath); err != nil { + message = fmt.Sprintf("Error on reading termination-log %s: %v", terminationMessagePath, err) + } else { + message = string(data) + } + } + + return &kubecontainer.ContainerStatus{ + ID: buildContainerID(&containerID{uuid: pod.Id, appName: app.Name}), + Name: app.Name, + State: appStateToContainerState(app.State), + // TODO(yifan): Use the creation/start/finished timestamp when it's implemented. + CreatedAt: creationTime, + StartedAt: creationTime, + ExitCode: int(app.ExitCode), + // By default, the version returned by rkt API service will be "latest" if not specified. + Image: fmt.Sprintf("%s:%s", app.Image.Name, app.Image.Version), + ImageID: "rkt://" + app.Image.Id, // TODO(yifan): Add the prefix only in api.PodStatus. + Hash: hashNum, + // TODO(yifan): Note that now all apps share the same restart count, this might + // change once apps don't share the same lifecycle. + // See https://github.com/appc/spec/pull/547. + RestartCount: restartCount, + Reason: reason, + Message: message, + }, nil +} + +func (r *Runtime) GetPodStatus(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) { + podStatus := &kubecontainer.PodStatus{ + ID: uid, + Name: name, + Namespace: namespace, + } + + listResp, err := r.apisvc.ListPods(context.Background(), &rktapi.ListPodsRequest{ + Detail: true, + Filters: kubernetesPodFilters(uid), + }) + if err != nil { + return nil, fmt.Errorf("couldn't list pods: %v", err) + } + + var latestPod *rktapi.Pod + var latestRestartCount int = -1 + + // In this loop, we group all containers from all pods together, + // also we try to find the latest pod, so we can fill other info of the pod below. + for _, pod := range listResp.Pods { + manifest, creationTime, restartCount, err := getPodInfo(pod) + if err != nil { + glog.Warningf("rkt: Couldn't get necessary info from the rkt pod, (uuid %q): %v", pod.Id, err) + continue + } + + if restartCount > latestRestartCount { + latestPod = pod + latestRestartCount = restartCount + } + + for i, app := range pod.Apps { + // The order of the apps is determined by the rkt pod manifest. + // TODO(yifan): Save creationTime, restartCount in each app's annotation, + // so we don't need to pass them. + cs, err := populateContainerStatus(*pod, *app, manifest.Apps[i], restartCount, creationTime) + if err != nil { + glog.Warningf("rkt: Failed to populate container status(uuid %q, app %q): %v", pod.Id, app.Name, err) + continue + } + podStatus.ContainerStatuses = append(podStatus.ContainerStatuses, cs) + } + } + + if latestPod != nil { + // Try to fill the IP info. + for _, n := range latestPod.Networks { + if n.Name == defaultNetworkName { + podStatus.IP = n.Ipv4 + } + } + } + + return podStatus, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/rkt_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/rkt_test.go new file mode 100644 index 000000000..bad8d5ea3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/rkt_test.go @@ -0,0 +1,1151 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 rkt + +import ( + "encoding/json" + "fmt" + "os" + "sort" + "testing" + "time" + + appcschema "github.com/appc/spec/schema" + appctypes "github.com/appc/spec/schema/types" + rktapi "github.com/coreos/rkt/api/v1alpha" + "github.com/stretchr/testify/assert" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + utiltesting "k8s.io/kubernetes/pkg/util/testing" +) + +func mustMarshalPodManifest(man *appcschema.PodManifest) []byte { + manblob, err := json.Marshal(man) + if err != nil { + panic(err) + } + return manblob +} + +func mustMarshalImageManifest(man *appcschema.ImageManifest) []byte { + manblob, err := json.Marshal(man) + if err != nil { + panic(err) + } + return manblob +} + +func mustRktHash(hash string) *appctypes.Hash { + h, err := appctypes.NewHash(hash) + if err != nil { + panic(err) + } + return h +} + +func makeRktPod(rktPodState rktapi.PodState, + rktPodID, podUID, podName, podNamespace, + podIP, podCreationTs, podRestartCount string, + appNames, imgIDs, imgNames, containerHashes []string, + appStates []rktapi.AppState, exitcodes []int32) *rktapi.Pod { + + podManifest := &appcschema.PodManifest{ + ACKind: appcschema.PodManifestKind, + ACVersion: appcschema.AppContainerVersion, + Annotations: appctypes.Annotations{ + appctypes.Annotation{ + Name: *appctypes.MustACIdentifier(k8sRktKubeletAnno), + Value: k8sRktKubeletAnnoValue, + }, + appctypes.Annotation{ + Name: *appctypes.MustACIdentifier(k8sRktUIDAnno), + Value: podUID, + }, + appctypes.Annotation{ + Name: *appctypes.MustACIdentifier(k8sRktNameAnno), + Value: podName, + }, + appctypes.Annotation{ + Name: *appctypes.MustACIdentifier(k8sRktNamespaceAnno), + Value: podNamespace, + }, + appctypes.Annotation{ + Name: *appctypes.MustACIdentifier(k8sRktCreationTimeAnno), + Value: podCreationTs, + }, + appctypes.Annotation{ + Name: *appctypes.MustACIdentifier(k8sRktRestartCountAnno), + Value: podRestartCount, + }, + }, + } + + appNum := len(appNames) + if appNum != len(imgNames) || + appNum != len(imgIDs) || + appNum != len(containerHashes) || + appNum != len(appStates) { + panic("inconsistent app number") + } + + apps := make([]*rktapi.App, appNum) + for i := range appNames { + apps[i] = &rktapi.App{ + Name: appNames[i], + State: appStates[i], + Image: &rktapi.Image{ + Id: imgIDs[i], + Name: imgNames[i], + Version: "latest", + Manifest: mustMarshalImageManifest( + &appcschema.ImageManifest{ + ACKind: appcschema.ImageManifestKind, + ACVersion: appcschema.AppContainerVersion, + Name: *appctypes.MustACIdentifier(imgNames[i]), + Annotations: appctypes.Annotations{ + appctypes.Annotation{ + Name: *appctypes.MustACIdentifier(k8sRktContainerHashAnno), + Value: containerHashes[i], + }, + }, + }, + ), + }, + ExitCode: exitcodes[i], + } + podManifest.Apps = append(podManifest.Apps, appcschema.RuntimeApp{ + Name: *appctypes.MustACName(appNames[i]), + Image: appcschema.RuntimeImage{ID: *mustRktHash("sha512-foo")}, + Annotations: appctypes.Annotations{ + appctypes.Annotation{ + Name: *appctypes.MustACIdentifier(k8sRktContainerHashAnno), + Value: containerHashes[i], + }, + }, + }) + } + + return &rktapi.Pod{ + Id: rktPodID, + State: rktPodState, + Networks: []*rktapi.Network{{Name: defaultNetworkName, Ipv4: podIP}}, + Apps: apps, + Manifest: mustMarshalPodManifest(podManifest), + } +} + +func TestCheckVersion(t *testing.T) { + fr := newFakeRktInterface() + fs := newFakeSystemd() + r := &Runtime{apisvc: fr, systemd: fs} + + fr.info = rktapi.Info{ + RktVersion: "1.2.3+git", + AppcVersion: "1.2.4+git", + ApiVersion: "1.2.6-alpha", + } + fs.version = "100" + tests := []struct { + minimumRktBinVersion string + recommendedRktBinVersion string + minimumAppcVersion string + minimumRktApiVersion string + minimumSystemdVersion string + err error + calledGetInfo bool + calledSystemVersion bool + }{ + // Good versions. + { + "1.2.3", + "1.2.3", + "1.2.4", + "1.2.5", + "99", + nil, + true, + true, + }, + // Good versions. + { + "1.2.3+git", + "1.2.3+git", + "1.2.4+git", + "1.2.6-alpha", + "100", + nil, + true, + true, + }, + // Requires greater binary version. + { + "1.2.4", + "1.2.4", + "1.2.4", + "1.2.6-alpha", + "100", + fmt.Errorf("rkt: binary version is too old(%v), requires at least %v", fr.info.RktVersion, "1.2.4"), + true, + true, + }, + // Requires greater Appc version. + { + "1.2.3", + "1.2.3", + "1.2.5", + "1.2.6-alpha", + "100", + fmt.Errorf("rkt: appc version is too old(%v), requires at least %v", fr.info.AppcVersion, "1.2.5"), + true, + true, + }, + // Requires greater API version. + { + "1.2.3", + "1.2.3", + "1.2.4", + "1.2.6", + "100", + fmt.Errorf("rkt: API version is too old(%v), requires at least %v", fr.info.ApiVersion, "1.2.6"), + true, + true, + }, + // Requires greater API version. + { + "1.2.3", + "1.2.3", + "1.2.4", + "1.2.7", + "100", + fmt.Errorf("rkt: API version is too old(%v), requires at least %v", fr.info.ApiVersion, "1.2.7"), + true, + true, + }, + // Requires greater systemd version. + { + "1.2.3", + "1.2.3", + "1.2.4", + "1.2.7", + "101", + fmt.Errorf("rkt: systemd version(%v) is too old, requires at least %v", fs.version, "101"), + false, + true, + }, + } + + for i, tt := range tests { + testCaseHint := fmt.Sprintf("test case #%d", i) + err := r.checkVersion(tt.minimumRktBinVersion, tt.recommendedRktBinVersion, tt.minimumAppcVersion, tt.minimumRktApiVersion, tt.minimumSystemdVersion) + assert.Equal(t, tt.err, err, testCaseHint) + + if tt.calledGetInfo { + assert.Equal(t, fr.called, []string{"GetInfo"}, testCaseHint) + } + if tt.calledSystemVersion { + assert.Equal(t, fs.called, []string{"Version"}, testCaseHint) + } + if err == nil { + assert.Equal(t, fr.info.RktVersion, r.versions.binVersion.String(), testCaseHint) + assert.Equal(t, fr.info.AppcVersion, r.versions.appcVersion.String(), testCaseHint) + assert.Equal(t, fr.info.ApiVersion, r.versions.apiVersion.String(), testCaseHint) + } + fr.CleanCalls() + fs.CleanCalls() + } +} + +func TestListImages(t *testing.T) { + fr := newFakeRktInterface() + fs := newFakeSystemd() + r := &Runtime{apisvc: fr, systemd: fs} + + tests := []struct { + images []*rktapi.Image + expected []kubecontainer.Image + }{ + {nil, []kubecontainer.Image{}}, + { + []*rktapi.Image{ + { + Id: "sha512-a2fb8f390702", + Name: "quay.io/coreos/alpine-sh", + Version: "latest", + }, + }, + []kubecontainer.Image{ + { + ID: "sha512-a2fb8f390702", + RepoTags: []string{"quay.io/coreos/alpine-sh:latest"}, + }, + }, + }, + { + []*rktapi.Image{ + { + Id: "sha512-a2fb8f390702", + Name: "quay.io/coreos/alpine-sh", + Version: "latest", + Size: 400, + }, + { + Id: "sha512-c6b597f42816", + Name: "coreos.com/rkt/stage1-coreos", + Version: "0.10.0", + Size: 400, + }, + }, + []kubecontainer.Image{ + { + ID: "sha512-a2fb8f390702", + RepoTags: []string{"quay.io/coreos/alpine-sh:latest"}, + Size: 400, + }, + { + ID: "sha512-c6b597f42816", + RepoTags: []string{"coreos.com/rkt/stage1-coreos:0.10.0"}, + Size: 400, + }, + }, + }, + } + + for i, tt := range tests { + fr.images = tt.images + + images, err := r.ListImages() + if err != nil { + t.Errorf("%v", err) + } + assert.Equal(t, tt.expected, images) + assert.Equal(t, fr.called, []string{"ListImages"}, fmt.Sprintf("test case %d: unexpected called list", i)) + + fr.CleanCalls() + } +} + +func TestGetPods(t *testing.T) { + fr := newFakeRktInterface() + fs := newFakeSystemd() + r := &Runtime{apisvc: fr, systemd: fs} + + tests := []struct { + pods []*rktapi.Pod + result []*kubecontainer.Pod + }{ + // No pods. + {}, + // One pod. + { + []*rktapi.Pod{ + makeRktPod(rktapi.PodState_POD_STATE_RUNNING, + "uuid-4002", "42", "guestbook", "default", + "10.10.10.42", "100000", "7", + []string{"app-1", "app-2"}, + []string{"img-id-1", "img-id-2"}, + []string{"img-name-1", "img-name-2"}, + []string{"1001", "1002"}, + []rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_EXITED}, + []int32{0, 0}, + ), + }, + []*kubecontainer.Pod{ + { + ID: "42", + Name: "guestbook", + Namespace: "default", + Containers: []*kubecontainer.Container{ + { + ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-1"), + Name: "app-1", + Image: "img-name-1:latest", + Hash: 1001, + Created: 100000, + State: "running", + }, + { + ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-2"), + Name: "app-2", + Image: "img-name-2:latest", + Hash: 1002, + Created: 100000, + State: "exited", + }, + }, + }, + }, + }, + // Multiple pods. + { + []*rktapi.Pod{ + makeRktPod(rktapi.PodState_POD_STATE_RUNNING, + "uuid-4002", "42", "guestbook", "default", + "10.10.10.42", "100000", "7", + []string{"app-1", "app-2"}, + []string{"img-id-1", "img-id-2"}, + []string{"img-name-1", "img-name-2"}, + []string{"1001", "1002"}, + []rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_EXITED}, + []int32{0, 0}, + ), + makeRktPod(rktapi.PodState_POD_STATE_EXITED, + "uuid-4003", "43", "guestbook", "default", + "10.10.10.43", "90000", "7", + []string{"app-11", "app-22"}, + []string{"img-id-11", "img-id-22"}, + []string{"img-name-11", "img-name-22"}, + []string{"10011", "10022"}, + []rktapi.AppState{rktapi.AppState_APP_STATE_EXITED, rktapi.AppState_APP_STATE_EXITED}, + []int32{0, 0}, + ), + makeRktPod(rktapi.PodState_POD_STATE_EXITED, + "uuid-4004", "43", "guestbook", "default", + "10.10.10.44", "100000", "8", + []string{"app-11", "app-22"}, + []string{"img-id-11", "img-id-22"}, + []string{"img-name-11", "img-name-22"}, + []string{"10011", "10022"}, + []rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_RUNNING}, + []int32{0, 0}, + ), + }, + []*kubecontainer.Pod{ + { + ID: "42", + Name: "guestbook", + Namespace: "default", + Containers: []*kubecontainer.Container{ + { + ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-1"), + Name: "app-1", + Image: "img-name-1:latest", + Hash: 1001, + Created: 100000, + State: "running", + }, + { + ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-2"), + Name: "app-2", + Image: "img-name-2:latest", + Hash: 1002, + Created: 100000, + State: "exited", + }, + }, + }, + { + ID: "43", + Name: "guestbook", + Namespace: "default", + Containers: []*kubecontainer.Container{ + { + ID: kubecontainer.BuildContainerID("rkt", "uuid-4003:app-11"), + Name: "app-11", + Image: "img-name-11:latest", + Hash: 10011, + Created: 90000, + State: "exited", + }, + { + ID: kubecontainer.BuildContainerID("rkt", "uuid-4003:app-22"), + Name: "app-22", + Image: "img-name-22:latest", + Hash: 10022, + Created: 90000, + State: "exited", + }, + { + ID: kubecontainer.BuildContainerID("rkt", "uuid-4004:app-11"), + Name: "app-11", + Image: "img-name-11:latest", + Hash: 10011, + Created: 100000, + State: "running", + }, + { + ID: kubecontainer.BuildContainerID("rkt", "uuid-4004:app-22"), + Name: "app-22", + Image: "img-name-22:latest", + Hash: 10022, + Created: 100000, + State: "running", + }, + }, + }, + }, + }, + } + + for i, tt := range tests { + testCaseHint := fmt.Sprintf("test case #%d", i) + fr.pods = tt.pods + + pods, err := r.GetPods(true) + if err != nil { + t.Errorf("test case #%d: unexpected error: %v", i, err) + } + + assert.Equal(t, tt.result, pods, testCaseHint) + assert.Equal(t, []string{"ListPods"}, fr.called, fmt.Sprintf("test case %d: unexpected called list", i)) + + fr.CleanCalls() + } +} + +func TestGetPodsFilters(t *testing.T) { + fr := newFakeRktInterface() + fs := newFakeSystemd() + r := &Runtime{apisvc: fr, systemd: fs} + + for _, test := range []struct { + All bool + ExpectedFilters []*rktapi.PodFilter + }{ + { + true, + []*rktapi.PodFilter{ + { + Annotations: []*rktapi.KeyValue{ + { + Key: k8sRktKubeletAnno, + Value: k8sRktKubeletAnnoValue, + }, + }, + }, + }, + }, + { + false, + []*rktapi.PodFilter{ + { + States: []rktapi.PodState{rktapi.PodState_POD_STATE_RUNNING}, + Annotations: []*rktapi.KeyValue{ + { + Key: k8sRktKubeletAnno, + Value: k8sRktKubeletAnnoValue, + }, + }, + }, + }, + }, + } { + _, err := r.GetPods(test.All) + if err != nil { + t.Errorf("%v", err) + } + assert.Equal(t, test.ExpectedFilters, fr.podFilters, "filters didn't match when all=%b", test.All) + } +} + +func TestGetPodStatus(t *testing.T) { + fr := newFakeRktInterface() + fs := newFakeSystemd() + r := &Runtime{apisvc: fr, systemd: fs} + + tests := []struct { + pods []*rktapi.Pod + result *kubecontainer.PodStatus + }{ + // No pods. + { + nil, + &kubecontainer.PodStatus{ID: "42", Name: "guestbook", Namespace: "default"}, + }, + // One pod. + { + []*rktapi.Pod{ + makeRktPod(rktapi.PodState_POD_STATE_RUNNING, + "uuid-4002", "42", "guestbook", "default", + "10.10.10.42", "100000", "7", + []string{"app-1", "app-2"}, + []string{"img-id-1", "img-id-2"}, + []string{"img-name-1", "img-name-2"}, + []string{"1001", "1002"}, + []rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_EXITED}, + []int32{0, 0}, + ), + }, + &kubecontainer.PodStatus{ + ID: "42", + Name: "guestbook", + Namespace: "default", + IP: "10.10.10.42", + ContainerStatuses: []*kubecontainer.ContainerStatus{ + { + ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-1"), + Name: "app-1", + State: kubecontainer.ContainerStateRunning, + CreatedAt: time.Unix(100000, 0), + StartedAt: time.Unix(100000, 0), + Image: "img-name-1:latest", + ImageID: "rkt://img-id-1", + Hash: 1001, + RestartCount: 7, + }, + { + ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-2"), + Name: "app-2", + State: kubecontainer.ContainerStateExited, + CreatedAt: time.Unix(100000, 0), + StartedAt: time.Unix(100000, 0), + Image: "img-name-2:latest", + ImageID: "rkt://img-id-2", + Hash: 1002, + RestartCount: 7, + Reason: "Completed", + }, + }, + }, + }, + // Multiple pods. + { + []*rktapi.Pod{ + makeRktPod(rktapi.PodState_POD_STATE_EXITED, + "uuid-4002", "42", "guestbook", "default", + "10.10.10.42", "90000", "7", + []string{"app-1", "app-2"}, + []string{"img-id-1", "img-id-2"}, + []string{"img-name-1", "img-name-2"}, + []string{"1001", "1002"}, + []rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_EXITED}, + []int32{0, 0}, + ), + makeRktPod(rktapi.PodState_POD_STATE_RUNNING, // The latest pod is running. + "uuid-4003", "42", "guestbook", "default", + "10.10.10.42", "100000", "10", + []string{"app-1", "app-2"}, + []string{"img-id-1", "img-id-2"}, + []string{"img-name-1", "img-name-2"}, + []string{"1001", "1002"}, + []rktapi.AppState{rktapi.AppState_APP_STATE_RUNNING, rktapi.AppState_APP_STATE_EXITED}, + []int32{0, 1}, + ), + }, + &kubecontainer.PodStatus{ + ID: "42", + Name: "guestbook", + Namespace: "default", + IP: "10.10.10.42", + // Result should contain all containers. + ContainerStatuses: []*kubecontainer.ContainerStatus{ + { + ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-1"), + Name: "app-1", + State: kubecontainer.ContainerStateRunning, + CreatedAt: time.Unix(90000, 0), + StartedAt: time.Unix(90000, 0), + Image: "img-name-1:latest", + ImageID: "rkt://img-id-1", + Hash: 1001, + RestartCount: 7, + }, + { + ID: kubecontainer.BuildContainerID("rkt", "uuid-4002:app-2"), + Name: "app-2", + State: kubecontainer.ContainerStateExited, + CreatedAt: time.Unix(90000, 0), + StartedAt: time.Unix(90000, 0), + Image: "img-name-2:latest", + ImageID: "rkt://img-id-2", + Hash: 1002, + RestartCount: 7, + Reason: "Completed", + }, + { + ID: kubecontainer.BuildContainerID("rkt", "uuid-4003:app-1"), + Name: "app-1", + State: kubecontainer.ContainerStateRunning, + CreatedAt: time.Unix(100000, 0), + StartedAt: time.Unix(100000, 0), + Image: "img-name-1:latest", + ImageID: "rkt://img-id-1", + Hash: 1001, + RestartCount: 10, + }, + { + ID: kubecontainer.BuildContainerID("rkt", "uuid-4003:app-2"), + Name: "app-2", + State: kubecontainer.ContainerStateExited, + CreatedAt: time.Unix(100000, 0), + StartedAt: time.Unix(100000, 0), + Image: "img-name-2:latest", + ImageID: "rkt://img-id-2", + Hash: 1002, + RestartCount: 10, + ExitCode: 1, + Reason: "Error", + }, + }, + }, + }, + } + + for i, tt := range tests { + testCaseHint := fmt.Sprintf("test case #%d", i) + fr.pods = tt.pods + + status, err := r.GetPodStatus("42", "guestbook", "default") + if err != nil { + t.Errorf("test case #%d: unexpected error: %v", i, err) + } + + assert.Equal(t, tt.result, status, testCaseHint) + assert.Equal(t, []string{"ListPods"}, fr.called, testCaseHint) + fr.CleanCalls() + } +} + +func generateCapRetainIsolator(t *testing.T, caps ...string) appctypes.Isolator { + retain, err := appctypes.NewLinuxCapabilitiesRetainSet(caps...) + if err != nil { + t.Fatalf("Error generating cap retain isolator: %v", err) + } + return retain.AsIsolator() +} + +func generateCapRevokeIsolator(t *testing.T, caps ...string) appctypes.Isolator { + revoke, err := appctypes.NewLinuxCapabilitiesRevokeSet(caps...) + if err != nil { + t.Fatalf("Error generating cap revoke isolator: %v", err) + } + return revoke.AsIsolator() +} + +func generateCPUIsolator(t *testing.T, request, limit string) appctypes.Isolator { + cpu, err := appctypes.NewResourceCPUIsolator(request, limit) + if err != nil { + t.Fatalf("Error generating cpu resource isolator: %v", err) + } + return cpu.AsIsolator() +} + +func generateMemoryIsolator(t *testing.T, request, limit string) appctypes.Isolator { + memory, err := appctypes.NewResourceMemoryIsolator(request, limit) + if err != nil { + t.Fatalf("Error generating memory resource isolator: %v", err) + } + return memory.AsIsolator() +} + +func baseApp(t *testing.T) *appctypes.App { + return &appctypes.App{ + Exec: appctypes.Exec{"/bin/foo", "bar"}, + SupplementaryGIDs: []int{4, 5, 6}, + WorkingDirectory: "/foo", + Environment: []appctypes.EnvironmentVariable{ + {"env-foo", "bar"}, + }, + MountPoints: []appctypes.MountPoint{ + {Name: *appctypes.MustACName("mnt-foo"), Path: "/mnt-foo", ReadOnly: false}, + }, + Ports: []appctypes.Port{ + {Name: *appctypes.MustACName("port-foo"), Protocol: "TCP", Port: 4242}, + }, + Isolators: []appctypes.Isolator{ + generateCapRetainIsolator(t, "CAP_SYS_ADMIN"), + generateCapRevokeIsolator(t, "CAP_NET_ADMIN"), + generateCPUIsolator(t, "100m", "200m"), + generateMemoryIsolator(t, "10M", "20M"), + }, + } +} + +func baseImageManifest(t *testing.T) *appcschema.ImageManifest { + img := &appcschema.ImageManifest{App: baseApp(t)} + entrypoint, err := json.Marshal([]string{"/bin/foo"}) + if err != nil { + t.Fatal(err) + } + cmd, err := json.Marshal([]string{"bar"}) + if err != nil { + t.Fatal(err) + } + img.Annotations.Set(*appctypes.MustACIdentifier(appcDockerEntrypoint), string(entrypoint)) + img.Annotations.Set(*appctypes.MustACIdentifier(appcDockerCmd), string(cmd)) + return img +} + +func baseAppWithRootUserGroup(t *testing.T) *appctypes.App { + app := baseApp(t) + app.User, app.Group = "0", "0" + return app +} + +type envByName []appctypes.EnvironmentVariable + +func (s envByName) Len() int { return len(s) } +func (s envByName) Less(i, j int) bool { return s[i].Name < s[j].Name } +func (s envByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +type mountsByName []appctypes.MountPoint + +func (s mountsByName) Len() int { return len(s) } +func (s mountsByName) Less(i, j int) bool { return s[i].Name < s[j].Name } +func (s mountsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +type portsByName []appctypes.Port + +func (s portsByName) Len() int { return len(s) } +func (s portsByName) Less(i, j int) bool { return s[i].Name < s[j].Name } +func (s portsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +type isolatorsByName []appctypes.Isolator + +func (s isolatorsByName) Len() int { return len(s) } +func (s isolatorsByName) Less(i, j int) bool { return s[i].Name < s[j].Name } +func (s isolatorsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func sortAppFields(app *appctypes.App) { + sort.Sort(envByName(app.Environment)) + sort.Sort(mountsByName(app.MountPoints)) + sort.Sort(portsByName(app.Ports)) + sort.Sort(isolatorsByName(app.Isolators)) +} + +func TestSetApp(t *testing.T) { + tmpDir, err := utiltesting.MkTmpdir("rkt_test") + if err != nil { + t.Fatalf("error creating temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + rootUser := int64(0) + nonRootUser := int64(42) + runAsNonRootTrue := true + fsgid := int64(3) + + tests := []struct { + container *api.Container + opts *kubecontainer.RunContainerOptions + ctx *api.SecurityContext + podCtx *api.PodSecurityContext + expect *appctypes.App + err error + }{ + // Nothing should change, but the "User" and "Group" should be filled. + { + container: &api.Container{}, + opts: &kubecontainer.RunContainerOptions{}, + ctx: nil, + podCtx: nil, + expect: baseAppWithRootUserGroup(t), + err: nil, + }, + + // error verifying non-root. + { + container: &api.Container{}, + opts: &kubecontainer.RunContainerOptions{}, + ctx: &api.SecurityContext{ + RunAsNonRoot: &runAsNonRootTrue, + RunAsUser: &rootUser, + }, + podCtx: nil, + expect: nil, + err: fmt.Errorf("container has no runAsUser and image will run as root"), + }, + + // app's args should be changed. + { + container: &api.Container{ + Args: []string{"foo"}, + }, + opts: &kubecontainer.RunContainerOptions{}, + ctx: nil, + podCtx: nil, + expect: &appctypes.App{ + Exec: appctypes.Exec{"/bin/foo", "foo"}, + User: "0", + Group: "0", + SupplementaryGIDs: []int{4, 5, 6}, + WorkingDirectory: "/foo", + Environment: []appctypes.EnvironmentVariable{ + {"env-foo", "bar"}, + }, + MountPoints: []appctypes.MountPoint{ + {Name: *appctypes.MustACName("mnt-foo"), Path: "/mnt-foo", ReadOnly: false}, + }, + Ports: []appctypes.Port{ + {Name: *appctypes.MustACName("port-foo"), Protocol: "TCP", Port: 4242}, + }, + Isolators: []appctypes.Isolator{ + generateCapRetainIsolator(t, "CAP_SYS_ADMIN"), + generateCapRevokeIsolator(t, "CAP_NET_ADMIN"), + generateCPUIsolator(t, "100m", "200m"), + generateMemoryIsolator(t, "10M", "20M"), + }, + }, + err: nil, + }, + + // app should be changed. + { + container: &api.Container{ + Command: []string{"/bin/bar", "$(env-bar)"}, + WorkingDir: tmpDir, + Resources: api.ResourceRequirements{ + Limits: api.ResourceList{"cpu": resource.MustParse("50m"), "memory": resource.MustParse("50M")}, + Requests: api.ResourceList{"cpu": resource.MustParse("5m"), "memory": resource.MustParse("5M")}, + }, + }, + opts: &kubecontainer.RunContainerOptions{ + Envs: []kubecontainer.EnvVar{ + {Name: "env-bar", Value: "foo"}, + }, + Mounts: []kubecontainer.Mount{ + {Name: "mnt-bar", ContainerPath: "/mnt-bar", ReadOnly: true}, + }, + PortMappings: []kubecontainer.PortMapping{ + {Name: "port-bar", Protocol: api.ProtocolTCP, ContainerPort: 1234}, + }, + }, + ctx: &api.SecurityContext{ + Capabilities: &api.Capabilities{ + Add: []api.Capability{"CAP_SYS_CHROOT", "CAP_SYS_BOOT"}, + Drop: []api.Capability{"CAP_SETUID", "CAP_SETGID"}, + }, + RunAsUser: &nonRootUser, + RunAsNonRoot: &runAsNonRootTrue, + }, + podCtx: &api.PodSecurityContext{ + SupplementalGroups: []int64{1, 2}, + FSGroup: &fsgid, + }, + expect: &appctypes.App{ + Exec: appctypes.Exec{"/bin/bar", "foo"}, + User: "42", + Group: "0", + SupplementaryGIDs: []int{1, 2, 3}, + WorkingDirectory: tmpDir, + Environment: []appctypes.EnvironmentVariable{ + {"env-foo", "bar"}, + {"env-bar", "foo"}, + }, + MountPoints: []appctypes.MountPoint{ + {Name: *appctypes.MustACName("mnt-foo"), Path: "/mnt-foo", ReadOnly: false}, + {Name: *appctypes.MustACName("mnt-bar"), Path: "/mnt-bar", ReadOnly: true}, + }, + Ports: []appctypes.Port{ + {Name: *appctypes.MustACName("port-foo"), Protocol: "TCP", Port: 4242}, + {Name: *appctypes.MustACName("port-bar"), Protocol: "TCP", Port: 1234}, + }, + Isolators: []appctypes.Isolator{ + generateCapRetainIsolator(t, "CAP_SYS_CHROOT", "CAP_SYS_BOOT"), + generateCapRevokeIsolator(t, "CAP_SETUID", "CAP_SETGID"), + generateCPUIsolator(t, "5m", "50m"), + generateMemoryIsolator(t, "5M", "50M"), + }, + }, + }, + + // app should be changed. (env, mounts, ports, are overrided). + { + container: &api.Container{ + Name: "hello-world", + Command: []string{"/bin/hello", "$(env-foo)"}, + Args: []string{"hello", "world", "$(env-bar)"}, + WorkingDir: tmpDir, + Resources: api.ResourceRequirements{ + Limits: api.ResourceList{"cpu": resource.MustParse("50m")}, + Requests: api.ResourceList{"memory": resource.MustParse("5M")}, + }, + }, + opts: &kubecontainer.RunContainerOptions{ + Envs: []kubecontainer.EnvVar{ + {Name: "env-foo", Value: "foo"}, + {Name: "env-bar", Value: "bar"}, + }, + Mounts: []kubecontainer.Mount{ + {Name: "mnt-foo", ContainerPath: "/mnt-bar", ReadOnly: true}, + }, + PortMappings: []kubecontainer.PortMapping{ + {Name: "port-foo", Protocol: api.ProtocolTCP, ContainerPort: 1234}, + }, + }, + ctx: &api.SecurityContext{ + Capabilities: &api.Capabilities{ + Add: []api.Capability{"CAP_SYS_CHROOT", "CAP_SYS_BOOT"}, + Drop: []api.Capability{"CAP_SETUID", "CAP_SETGID"}, + }, + RunAsUser: &nonRootUser, + RunAsNonRoot: &runAsNonRootTrue, + }, + podCtx: &api.PodSecurityContext{ + SupplementalGroups: []int64{1, 2}, + FSGroup: &fsgid, + }, + expect: &appctypes.App{ + Exec: appctypes.Exec{"/bin/hello", "foo", "hello", "world", "bar"}, + User: "42", + Group: "0", + SupplementaryGIDs: []int{1, 2, 3}, + WorkingDirectory: tmpDir, + Environment: []appctypes.EnvironmentVariable{ + {"env-foo", "foo"}, + {"env-bar", "bar"}, + }, + MountPoints: []appctypes.MountPoint{ + {Name: *appctypes.MustACName("mnt-foo"), Path: "/mnt-bar", ReadOnly: true}, + }, + Ports: []appctypes.Port{ + {Name: *appctypes.MustACName("port-foo"), Protocol: "TCP", Port: 1234}, + }, + Isolators: []appctypes.Isolator{ + generateCapRetainIsolator(t, "CAP_SYS_CHROOT", "CAP_SYS_BOOT"), + generateCapRevokeIsolator(t, "CAP_SETUID", "CAP_SETGID"), + generateCPUIsolator(t, "50m", "50m"), + generateMemoryIsolator(t, "5M", "5M"), + }, + }, + }, + } + + for i, tt := range tests { + testCaseHint := fmt.Sprintf("test case #%d", i) + img := baseImageManifest(t) + err := setApp(img, tt.container, tt.opts, tt.ctx, tt.podCtx) + if err == nil && tt.err != nil || err != nil && tt.err == nil { + t.Errorf("%s: expect %v, saw %v", testCaseHint, tt.err, err) + } + if err == nil { + sortAppFields(tt.expect) + sortAppFields(img.App) + assert.Equal(t, tt.expect, img.App, testCaseHint) + } + } +} + +func TestGenerateRunCommand(t *testing.T) { + tests := []struct { + pod *api.Pod + uuid string + + dnsServers []string + dnsSearches []string + hostName string + err error + + expect string + }{ + // Case #0, returns error. + { + &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod-name-foo", + }, + Spec: api.PodSpec{}, + }, + "rkt-uuid-foo", + []string{}, + []string{}, + "", + fmt.Errorf("failed to get cluster dns"), + "", + }, + // Case #1, returns no dns, with private-net. + { + &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod-name-foo", + }, + }, + "rkt-uuid-foo", + []string{}, + []string{}, + "pod-hostname-foo", + nil, + "/bin/rkt/rkt --insecure-options=image,ondisk --local-config=/var/rkt/local/data --dir=/var/data run-prepared --net=rkt.kubernetes.io --hostname=pod-hostname-foo rkt-uuid-foo", + }, + // Case #2, returns no dns, with host-net. + { + &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod-name-foo", + }, + Spec: api.PodSpec{ + SecurityContext: &api.PodSecurityContext{ + HostNetwork: true, + }, + }, + }, + "rkt-uuid-foo", + []string{}, + []string{}, + "pod-hostname-foo", + nil, + "/bin/rkt/rkt --insecure-options=image,ondisk --local-config=/var/rkt/local/data --dir=/var/data run-prepared --net=host --hostname=pod-hostname-foo rkt-uuid-foo", + }, + // Case #3, returns dns, dns searches, with private-net. + { + &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod-name-foo", + }, + Spec: api.PodSpec{ + SecurityContext: &api.PodSecurityContext{ + HostNetwork: false, + }, + }, + }, + "rkt-uuid-foo", + []string{"127.0.0.1"}, + []string{"."}, + "pod-hostname-foo", + nil, + "/bin/rkt/rkt --insecure-options=image,ondisk --local-config=/var/rkt/local/data --dir=/var/data run-prepared --net=rkt.kubernetes.io --dns=127.0.0.1 --dns-search=. --dns-opt=ndots:5 --hostname=pod-hostname-foo rkt-uuid-foo", + }, + // Case #4, returns dns, dns searches, with host-network. + { + &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod-name-foo", + }, + Spec: api.PodSpec{ + SecurityContext: &api.PodSecurityContext{ + HostNetwork: true, + }, + }, + }, + "rkt-uuid-foo", + []string{"127.0.0.1"}, + []string{"."}, + "pod-hostname-foo", + nil, + "/bin/rkt/rkt --insecure-options=image,ondisk --local-config=/var/rkt/local/data --dir=/var/data run-prepared --net=host --dns=127.0.0.1 --dns-search=. --dns-opt=ndots:5 --hostname=pod-hostname-foo rkt-uuid-foo", + }, + } + + rkt := &Runtime{ + config: &Config{ + Path: "/bin/rkt/rkt", + Stage1Image: "/bin/rkt/stage1-coreos.aci", + Dir: "/var/data", + InsecureOptions: "image,ondisk", + LocalConfigDir: "/var/rkt/local/data", + }, + } + + for i, tt := range tests { + testCaseHint := fmt.Sprintf("test case #%d", i) + rkt.runtimeHelper = &fakeRuntimeHelper{tt.dnsServers, tt.dnsSearches, tt.hostName, "", tt.err} + + result, err := rkt.generateRunCommand(tt.pod, tt.uuid) + assert.Equal(t, tt.err, err, testCaseHint) + assert.Equal(t, tt.expect, result, testCaseHint) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/systemd.go b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/systemd.go new file mode 100644 index 000000000..14b2dc9db --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/systemd.go @@ -0,0 +1,103 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 rkt + +import ( + "fmt" + "os/exec" + "strconv" + "strings" + + "github.com/coreos/go-systemd/dbus" +) + +// systemdVersion is a type wraps the int to implement kubecontainer.Version interface. +type systemdVersion int + +func (s systemdVersion) String() string { + return fmt.Sprintf("%d", s) +} + +func (s systemdVersion) Compare(other string) (int, error) { + v, err := strconv.Atoi(other) + if err != nil { + return -1, err + } + if int(s) < v { + return -1, nil + } else if int(s) > v { + return 1, nil + } + return 0, nil +} + +// systemdInterface is an abstraction of the go-systemd/dbus to make +// it mockable for testing. +// TODO(yifan): Eventually we should move these functionalities to: +// 1. a package for launching/stopping rkt pods. +// 2. rkt api-service interface for listing pods. +// See https://github.com/coreos/rkt/issues/1769. +type systemdInterface interface { + // Version returns the version of the systemd. + Version() (systemdVersion, error) + // ListUnits lists all the loaded units. + ListUnits() ([]dbus.UnitStatus, error) + // StopUnits stops the unit with the given name. + StopUnit(name string, mode string, ch chan<- string) (int, error) + // StopUnits restarts the unit with the given name. + RestartUnit(name string, mode string, ch chan<- string) (int, error) + // Reload is equivalent to 'systemctl daemon-reload'. + Reload() error +} + +// systemd implements the systemdInterface using dbus and systemctl. +// All the functions other then Version() are already implemented by go-systemd/dbus. +type systemd struct { + *dbus.Conn +} + +// newSystemd creates a systemd object that implements systemdInterface. +func newSystemd() (*systemd, error) { + dbusConn, err := dbus.New() + if err != nil { + return nil, err + } + return &systemd{dbusConn}, nil +} + +// Version returns the version of the systemd. +func (s *systemd) Version() (systemdVersion, error) { + output, err := exec.Command("systemctl", "--version").Output() + if err != nil { + return -1, err + } + // Example output of 'systemctl --version': + // + // systemd 215 + // +PAM +AUDIT +SELINUX +IMA +SYSVINIT +LIBCRYPTSETUP +GCRYPT +ACL +XZ -SECCOMP -APPARMOR + // + lines := strings.Split(string(output), "\n") + tuples := strings.Split(lines[0], " ") + if len(tuples) != 2 { + return -1, fmt.Errorf("rkt: Failed to parse version %v", lines) + } + result, err := strconv.Atoi(string(tuples[1])) + if err != nil { + return -1, err + } + return systemdVersion(result), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/version.go b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/version.go new file mode 100644 index 000000000..32b66b29b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/rkt/version.go @@ -0,0 +1,158 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 rkt + +import ( + "fmt" + "sync" + + "github.com/coreos/go-semver/semver" + rktapi "github.com/coreos/rkt/api/v1alpha" + "github.com/golang/glog" + "golang.org/x/net/context" +) + +type versions struct { + sync.RWMutex + binVersion rktVersion + apiVersion rktVersion + appcVersion rktVersion + systemdVersion systemdVersion +} + +// rktVersion implementes kubecontainer.Version interface by implementing +// Compare() and String() (which is implemented by the underlying semver.Version) +type rktVersion struct { + *semver.Version +} + +func newRktVersion(version string) (rktVersion, error) { + sem, err := semver.NewVersion(version) + if err != nil { + return rktVersion{}, err + } + return rktVersion{sem}, nil +} + +func (r rktVersion) Compare(other string) (int, error) { + v, err := semver.NewVersion(other) + if err != nil { + return -1, err + } + + if r.LessThan(*v) { + return -1, nil + } + if v.LessThan(*r.Version) { + return 1, nil + } + return 0, nil +} + +func (r *Runtime) getVersions() error { + r.versions.Lock() + defer r.versions.Unlock() + + // Get systemd version. + var err error + r.versions.systemdVersion, err = r.systemd.Version() + if err != nil { + return err + } + + // Example for the version strings returned by GetInfo(): + // RktVersion:"0.10.0+gitb7349b1" AppcVersion:"0.7.1" ApiVersion:"1.0.0-alpha" + resp, err := r.apisvc.GetInfo(context.Background(), &rktapi.GetInfoRequest{}) + if err != nil { + return err + } + + // Get rkt binary version. + r.versions.binVersion, err = newRktVersion(resp.Info.RktVersion) + if err != nil { + return err + } + + // Get Appc version. + r.versions.appcVersion, err = newRktVersion(resp.Info.AppcVersion) + if err != nil { + return err + } + + // Get rkt API version. + r.versions.apiVersion, err = newRktVersion(resp.Info.ApiVersion) + if err != nil { + return err + } + return nil +} + +// checkVersion tests whether the rkt/systemd/rkt-api-service that meet the version requirement. +// If all version requirements are met, it returns nil. +func (r *Runtime) checkVersion(minimumRktBinVersion, recommendedRktBinVersion, minimumAppcVersion, minimumRktApiVersion, minimumSystemdVersion string) error { + if err := r.getVersions(); err != nil { + return err + } + + r.versions.RLock() + defer r.versions.RUnlock() + + // Check systemd version. + result, err := r.versions.systemdVersion.Compare(minimumSystemdVersion) + if err != nil { + return err + } + if result < 0 { + return fmt.Errorf("rkt: systemd version(%v) is too old, requires at least %v", r.versions.systemdVersion, minimumSystemdVersion) + } + + // Check rkt binary version. + result, err = r.versions.binVersion.Compare(minimumRktBinVersion) + if err != nil { + return err + } + if result < 0 { + return fmt.Errorf("rkt: binary version is too old(%v), requires at least %v", r.versions.binVersion, minimumRktBinVersion) + } + result, err = r.versions.binVersion.Compare(recommendedRktBinVersion) + if err != nil { + return err + } + if result != 0 { + // TODO(yifan): Record an event to expose the information. + glog.Warningf("rkt: current binary version %q is not recommended (recommended version %q)", r.versions.binVersion, recommendedRktBinVersion) + } + + // Check Appc version. + result, err = r.versions.appcVersion.Compare(minimumAppcVersion) + if err != nil { + return err + } + if result < 0 { + return fmt.Errorf("rkt: appc version is too old(%v), requires at least %v", r.versions.appcVersion, minimumAppcVersion) + } + + // Check rkt API version. + result, err = r.versions.apiVersion.Compare(minimumRktApiVersion) + if err != nil { + return err + } + if result < 0 { + return fmt.Errorf("rkt: API version is too old(%v), requires at least %v", r.versions.apiVersion, minimumRktApiVersion) + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/root_context_linux.go b/vendor/k8s.io/kubernetes/pkg/kubelet/root_context_linux.go new file mode 100644 index 000000000..a694d71ba --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/root_context_linux.go @@ -0,0 +1,35 @@ +// +build linux + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "github.com/opencontainers/runc/libcontainer/selinux" +) + +// getRootDirContext gets the SELinux context of the kubelet rootDir +// or returns an error. +func (kl *Kubelet) getRootDirContext() (string, error) { + // If SELinux is not enabled, return an empty string + if !selinux.SelinuxEnabled() { + return "", nil + } + + // Get the SELinux context of the rootDir. + return selinux.Getfilecon(kl.getRootDir()) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/root_context_unsupported.go b/vendor/k8s.io/kubernetes/pkg/kubelet/root_context_unsupported.go new file mode 100644 index 000000000..826ac34f0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/root_context_unsupported.go @@ -0,0 +1,24 @@ +// +build !linux + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubelet + +func (kl *Kubelet) getRootDirContext() (string, error) { + // For now, just return a blank security context on unsupported platforms + return "", nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/runonce.go b/vendor/k8s.io/kubernetes/pkg/kubelet/runonce.go new file mode 100644 index 000000000..c959dee3b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/runonce.go @@ -0,0 +1,151 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "fmt" + "os" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/kubelet/util/format" +) + +const ( + runOnceManifestDelay = 1 * time.Second + runOnceMaxRetries = 10 + runOnceRetryDelay = 1 * time.Second + runOnceRetryDelayBackoff = 2 +) + +type RunPodResult struct { + Pod *api.Pod + Err error +} + +// RunOnce polls from one configuration update and run the associated pods. +func (kl *Kubelet) RunOnce(updates <-chan kubetypes.PodUpdate) ([]RunPodResult, error) { + // Setup filesystem directories. + if err := kl.setupDataDirs(); err != nil { + return nil, err + } + + // If the container logs directory does not exist, create it. + if _, err := os.Stat(containerLogsDir); err != nil { + if err := kl.os.Mkdir(containerLogsDir, 0755); err != nil { + glog.Errorf("Failed to create directory %q: %v", containerLogsDir, err) + } + } + + select { + case u := <-updates: + glog.Infof("processing manifest with %d pods", len(u.Pods)) + result, err := kl.runOnce(u.Pods, runOnceRetryDelay) + glog.Infof("finished processing %d pods", len(u.Pods)) + return result, err + case <-time.After(runOnceManifestDelay): + return nil, fmt.Errorf("no pod manifest update after %v", runOnceManifestDelay) + } +} + +// runOnce runs a given set of pods and returns their status. +func (kl *Kubelet) runOnce(pods []*api.Pod, retryDelay time.Duration) (results []RunPodResult, err error) { + ch := make(chan RunPodResult) + admitted := []*api.Pod{} + for _, pod := range pods { + // Check if we can admit the pod. + if ok, reason, message := kl.canAdmitPod(append(admitted, pod), pod); !ok { + kl.rejectPod(pod, reason, message) + } else { + admitted = append(admitted, pod) + } + go func(pod *api.Pod) { + err := kl.runPod(pod, retryDelay) + ch <- RunPodResult{pod, err} + }(pod) + } + + glog.Infof("waiting for %d pods", len(pods)) + failedPods := []string{} + for i := 0; i < len(pods); i++ { + res := <-ch + results = append(results, res) + if res.Err != nil { + // TODO(proppy): report which containers failed the pod. + glog.Infof("failed to start pod %q: %v", format.Pod(res.Pod), res.Err) + failedPods = append(failedPods, format.Pod(res.Pod)) + } else { + glog.Infof("started pod %q", format.Pod(res.Pod)) + } + } + if len(failedPods) > 0 { + return results, fmt.Errorf("error running pods: %v", failedPods) + } + glog.Infof("%d pods started", len(pods)) + return results, err +} + +// runPod runs a single pod and wait until all containers are running. +func (kl *Kubelet) runPod(pod *api.Pod, retryDelay time.Duration) error { + delay := retryDelay + retry := 0 + for { + status, err := kl.containerRuntime.GetPodStatus(pod.UID, pod.Name, pod.Namespace) + if err != nil { + return fmt.Errorf("Unable to get status for pod %q: %v", format.Pod(pod), err) + } + + if kl.isPodRunning(pod, status) { + glog.Infof("pod %q containers running", format.Pod(pod)) + return nil + } + glog.Infof("pod %q containers not running: syncing", format.Pod(pod)) + + glog.Infof("Creating a mirror pod for static pod %q", format.Pod(pod)) + if err := kl.podManager.CreateMirrorPod(pod); err != nil { + glog.Errorf("Failed creating a mirror pod %q: %v", format.Pod(pod), err) + } + mirrorPod, _ := kl.podManager.GetMirrorPodByPod(pod) + + if err = kl.syncPod(pod, mirrorPod, status, kubetypes.SyncPodUpdate); err != nil { + return fmt.Errorf("error syncing pod %q: %v", format.Pod(pod), err) + } + if retry >= runOnceMaxRetries { + return fmt.Errorf("timeout error: pod %q containers not running after %d retries", format.Pod(pod), runOnceMaxRetries) + } + // TODO(proppy): health checking would be better than waiting + checking the state at the next iteration. + glog.Infof("pod %q containers synced, waiting for %v", format.Pod(pod), delay) + time.Sleep(delay) + retry++ + delay *= runOnceRetryDelayBackoff + } +} + +// isPodRunning returns true if all containers of a manifest are running. +func (kl *Kubelet) isPodRunning(pod *api.Pod, status *kubecontainer.PodStatus) bool { + for _, c := range pod.Spec.Containers { + cs := status.FindContainerStatusByName(c.Name) + if cs == nil || cs.State != kubecontainer.ContainerStateRunning { + glog.Infof("Container %q for pod %q not running", c.Name, format.Pod(pod)) + return false + } + } + return true +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/runonce_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/runonce_test.go new file mode 100644 index 000000000..a999d5d83 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/runonce_test.go @@ -0,0 +1,125 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "os" + "testing" + "time" + + cadvisorapi "github.com/google/cadvisor/info/v1" + cadvisorapiv2 "github.com/google/cadvisor/info/v2" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/record" + cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing" + "k8s.io/kubernetes/pkg/kubelet/cm" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" + "k8s.io/kubernetes/pkg/kubelet/network" + nettest "k8s.io/kubernetes/pkg/kubelet/network/testing" + kubepod "k8s.io/kubernetes/pkg/kubelet/pod" + podtest "k8s.io/kubernetes/pkg/kubelet/pod/testing" + "k8s.io/kubernetes/pkg/kubelet/status" + "k8s.io/kubernetes/pkg/util" + utiltesting "k8s.io/kubernetes/pkg/util/testing" +) + +func TestRunOnce(t *testing.T) { + cadvisor := &cadvisortest.Mock{} + cadvisor.On("MachineInfo").Return(&cadvisorapi.MachineInfo{}, nil) + cadvisor.On("DockerImagesFsInfo").Return(cadvisorapiv2.FsInfo{ + Usage: 400 * mb, + Capacity: 1000 * mb, + Available: 600 * mb, + }, nil) + cadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{ + Usage: 9 * mb, + Capacity: 10 * mb, + }, nil) + podManager := kubepod.NewBasicPodManager(podtest.NewFakeMirrorClient()) + diskSpaceManager, _ := newDiskSpaceManager(cadvisor, DiskSpacePolicy{}) + fakeRuntime := &containertest.FakeRuntime{} + basePath, err := utiltesting.MkTmpdir("kubelet") + if err != nil { + t.Fatalf("can't make a temp rootdir %v", err) + } + defer os.RemoveAll(basePath) + kb := &Kubelet{ + rootDirectory: basePath, + recorder: &record.FakeRecorder{}, + cadvisor: cadvisor, + nodeLister: testNodeLister{}, + nodeInfo: testNodeInfo{}, + statusManager: status.NewManager(nil, podManager), + containerRefManager: kubecontainer.NewRefManager(), + podManager: podManager, + os: containertest.FakeOS{}, + volumeManager: newVolumeManager(), + diskSpaceManager: diskSpaceManager, + containerRuntime: fakeRuntime, + reasonCache: NewReasonCache(), + clock: util.RealClock{}, + kubeClient: &fake.Clientset{}, + } + kb.containerManager = cm.NewStubContainerManager() + + kb.networkPlugin, _ = network.InitNetworkPlugin([]network.NetworkPlugin{}, "", nettest.NewFakeHost(nil)) + if err := kb.setupDataDirs(); err != nil { + t.Errorf("Failed to init data dirs: %v", err) + } + + pods := []*api.Pod{ + { + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "bar"}, + }, + }, + }, + } + podManager.SetPods(pods) + // The original test here is totally meaningless, because fakeruntime will always return an empty podStatus. While + // the originial logic of isPodRunning happens to return true when podstatus is empty, so the test can always pass. + // Now the logic in isPodRunning is changed, to let the test pass, we set the podstatus directly in fake runtime. + // This is also a meaningless test, because the isPodRunning will also always return true after setting this. However, + // because runonce is never used in kubernetes now, we should deprioritize the cleanup work. + // TODO(random-liu) Fix the test, make it meaningful. + fakeRuntime.PodStatus = kubecontainer.PodStatus{ + ContainerStatuses: []*kubecontainer.ContainerStatus{ + { + Name: "bar", + State: kubecontainer.ContainerStateRunning, + }, + }, + } + results, err := kb.runOnce(pods, time.Millisecond) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if results[0].Err != nil { + t.Errorf("unexpected run pod error: %v", results[0].Err) + } + if results[0].Pod.Name != "foo" { + t.Errorf("unexpected pod: %q", results[0].Pod.Name) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/runtime.go b/vendor/k8s.io/kubernetes/pkg/kubelet/runtime.go new file mode 100644 index 000000000..9b26f1116 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/runtime.go @@ -0,0 +1,104 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "fmt" + "sync" + "time" +) + +type runtimeState struct { + sync.Mutex + lastBaseRuntimeSync time.Time + baseRuntimeSyncThreshold time.Duration + networkError error + internalError error + cidr string + initError error +} + +func (s *runtimeState) setRuntimeSync(t time.Time) { + s.Lock() + defer s.Unlock() + s.lastBaseRuntimeSync = t +} + +func (s *runtimeState) setInternalError(err error) { + s.Lock() + defer s.Unlock() + s.internalError = err +} + +func (s *runtimeState) setNetworkState(err error) { + s.Lock() + defer s.Unlock() + s.networkError = err +} + +func (s *runtimeState) setPodCIDR(cidr string) { + s.Lock() + defer s.Unlock() + s.cidr = cidr +} + +func (s *runtimeState) podCIDR() string { + s.Lock() + defer s.Unlock() + return s.cidr +} + +func (s *runtimeState) setInitError(err error) { + s.Lock() + defer s.Unlock() + s.initError = err +} + +func (s *runtimeState) errors() []string { + s.Lock() + defer s.Unlock() + var ret []string + if s.initError != nil { + ret = append(ret, s.initError.Error()) + } + if s.networkError != nil { + ret = append(ret, s.networkError.Error()) + } + if !s.lastBaseRuntimeSync.Add(s.baseRuntimeSyncThreshold).After(time.Now()) { + ret = append(ret, "container runtime is down") + } + if s.internalError != nil { + ret = append(ret, s.internalError.Error()) + } + return ret +} + +func newRuntimeState( + runtimeSyncThreshold time.Duration, + configureNetwork bool, +) *runtimeState { + var networkError error = nil + if configureNetwork { + networkError = fmt.Errorf("network state unknown") + } + return &runtimeState{ + lastBaseRuntimeSync: time.Time{}, + baseRuntimeSyncThreshold: runtimeSyncThreshold, + networkError: networkError, + internalError: nil, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/auth.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/auth.go new file mode 100644 index 000000000..0ab25512c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/auth.go @@ -0,0 +1,37 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 server + +import ( + "k8s.io/kubernetes/pkg/auth/authenticator" + "k8s.io/kubernetes/pkg/auth/authorizer" +) + +// KubeletAuth implements AuthInterface +type KubeletAuth struct { + // authenticator identifies the user for requests to the Kubelet API + authenticator.Request + // authorizerAttributeGetter builds authorization.Attributes for a request to the Kubelet API + authorizer.RequestAttributesGetter + // authorizer determines whether a given authorization.Attributes is allowed + authorizer.Authorizer +} + +// NewKubeletAuth returns a kubelet.AuthInterface composed of the given authenticator, attribute getter, and authorizer +func NewKubeletAuth(authenticator authenticator.Request, authorizerAttributeGetter authorizer.RequestAttributesGetter, authorizer authorizer.Authorizer) AuthInterface { + return &KubeletAuth{authenticator, authorizerAttributeGetter, authorizer} +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/doc.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/doc.go new file mode 100644 index 000000000..edb357a8e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 server contains functions related to serving Kubelet's external interface. +package server diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/portforward/constants.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/portforward/constants.go new file mode 100644 index 000000000..f43867067 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/portforward/constants.go @@ -0,0 +1,21 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 portforward contains server-side logic for handling port forwarding requests. +package portforward + +// The subprotocol "portforward.k8s.io" is used for port forwarding. +const PortForwardProtocolV1Name = "portforward.k8s.io" diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/attach.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/attach.go new file mode 100644 index 000000000..0f9ba7ff5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/attach.go @@ -0,0 +1,53 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 remotecommand + +import ( + "errors" + "fmt" + "io" + "net/http" + "time" + + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/runtime" +) + +// Attacher knows how to attach to a running container in a pod. +type Attacher interface { + // AttachContainer attaches to the running container in the pod, copying data between in/out/err + // and the container's stdin/stdout/stderr. + AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool) error +} + +// ServeAttach handles requests to attach to a container. After creating/receiving the required +// streams, it delegates the actual attaching to attacher. +func ServeAttach(w http.ResponseWriter, req *http.Request, attacher Attacher, podName string, uid types.UID, container string, idleTimeout, streamCreationTimeout time.Duration, supportedProtocols []string) { + ctx, ok := createStreams(req, w, supportedProtocols, idleTimeout, streamCreationTimeout) + if !ok { + // error is handled by createStreams + return + } + defer ctx.conn.Close() + + err := attacher.AttachContainer(podName, uid, container, ctx.stdinStream, ctx.stdoutStream, ctx.stderrStream, ctx.tty) + if err != nil { + msg := fmt.Sprintf("error attaching to container: %v", err) + runtime.HandleError(errors.New(msg)) + fmt.Fprint(ctx.errorStream, msg) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/contants.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/contants.go new file mode 100644 index 000000000..f45cc6440 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/contants.go @@ -0,0 +1,36 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 remotecommand + +import "time" + +const ( + DefaultStreamCreationTimeout = 30 * time.Second + + // The SPDY subprotocol "channel.k8s.io" is used for remote command + // attachment/execution. This represents the initial unversioned subprotocol, + // which has the known bugs http://issues.k8s.io/13394 and + // http://issues.k8s.io/13395. + StreamProtocolV1Name = "channel.k8s.io" + + // The SPDY subprotocol "v2.channel.k8s.io" is used for remote command + // attachment/execution. It is the second version of the subprotocol and + // resolves the issues present in the first version. + StreamProtocolV2Name = "v2.channel.k8s.io" +) + +var SupportedStreamingProtocols = []string{StreamProtocolV2Name, StreamProtocolV1Name} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/doc.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/doc.go new file mode 100644 index 000000000..482e9afc1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 remotecommand contains functions related to executing commands in and attaching to pods. +package remotecommand diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/exec.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/exec.go new file mode 100644 index 000000000..df9a4b585 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/exec.go @@ -0,0 +1,57 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 remotecommand + +import ( + "errors" + "fmt" + "io" + "net/http" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/runtime" +) + +// Executor knows how to execute a command in a container in a pod. +type Executor interface { + // ExecInContainer executes a command in a container in the pod, copying data + // between in/out/err and the container's stdin/stdout/stderr. + ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error +} + +// ServeExec handles requests to execute a command in a container. After +// creating/receiving the required streams, it delegates the actual execution +// to the executor. +func ServeExec(w http.ResponseWriter, req *http.Request, executor Executor, podName string, uid types.UID, container string, idleTimeout, streamCreationTimeout time.Duration, supportedProtocols []string) { + ctx, ok := createStreams(req, w, supportedProtocols, idleTimeout, streamCreationTimeout) + if !ok { + // error is handled by createStreams + return + } + defer ctx.conn.Close() + + cmd := req.URL.Query()[api.ExecCommandParamm] + + err := executor.ExecInContainer(podName, uid, container, cmd, ctx.stdinStream, ctx.stdoutStream, ctx.stderrStream, ctx.tty) + if err != nil { + msg := fmt.Sprintf("error executing command in container: %v", err) + runtime.HandleError(errors.New(msg)) + fmt.Fprint(ctx.errorStream, msg) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/httpstream.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/httpstream.go new file mode 100644 index 000000000..4b0c588e9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/httpstream.go @@ -0,0 +1,277 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 remotecommand + +import ( + "errors" + "fmt" + "io" + "net/http" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/util/httpstream" + "k8s.io/kubernetes/pkg/util/httpstream/spdy" + "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wsstream" + + "github.com/golang/glog" +) + +// options contains details about which streams are required for +// remote command execution. +type options struct { + stdin bool + stdout bool + stderr bool + tty bool + expectedStreams int +} + +// newOptions creates a new options from the Request. +func newOptions(req *http.Request) (*options, error) { + tty := req.FormValue(api.ExecTTYParam) == "1" + stdin := req.FormValue(api.ExecStdinParam) == "1" + stdout := req.FormValue(api.ExecStdoutParam) == "1" + stderr := req.FormValue(api.ExecStderrParam) == "1" + if tty && stderr { + // TODO: make this an error before we reach this method + glog.V(4).Infof("Access to exec with tty and stderr is not supported, bypassing stderr") + stderr = false + } + + // count the streams client asked for, starting with 1 + expectedStreams := 1 + if stdin { + expectedStreams++ + } + if stdout { + expectedStreams++ + } + if stderr { + expectedStreams++ + } + + if expectedStreams == 1 { + return nil, fmt.Errorf("you must specify at least 1 of stdin, stdout, stderr") + } + + return &options{ + stdin: stdin, + stdout: stdout, + stderr: stderr, + tty: tty, + expectedStreams: expectedStreams, + }, nil +} + +// context contains the connection and streams used when +// forwarding an attach or execute session into a container. +type context struct { + conn io.Closer + stdinStream io.ReadCloser + stdoutStream io.WriteCloser + stderrStream io.WriteCloser + errorStream io.WriteCloser + tty bool +} + +// streamAndReply holds both a Stream and a channel that is closed when the stream's reply frame is +// enqueued. Consumers can wait for replySent to be closed prior to proceeding, to ensure that the +// replyFrame is enqueued before the connection's goaway frame is sent (e.g. if a stream was +// received and right after, the connection gets closed). +type streamAndReply struct { + httpstream.Stream + replySent <-chan struct{} +} + +// waitStreamReply waits until either replySent or stop is closed. If replySent is closed, it sends +// an empty struct to the notify channel. +func waitStreamReply(replySent <-chan struct{}, notify chan<- struct{}, stop <-chan struct{}) { + select { + case <-replySent: + notify <- struct{}{} + case <-stop: + } +} + +func createStreams(req *http.Request, w http.ResponseWriter, supportedStreamProtocols []string, idleTimeout, streamCreationTimeout time.Duration) (*context, bool) { + opts, err := newOptions(req) + if err != nil { + runtime.HandleError(err) + w.WriteHeader(http.StatusBadRequest) + fmt.Fprint(w, err.Error()) + return nil, false + } + + if wsstream.IsWebSocketRequest(req) { + return createWebSocketStreams(req, w, opts, idleTimeout) + } + + protocol, err := httpstream.Handshake(req, w, supportedStreamProtocols) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprint(w, err.Error()) + return nil, false + } + + streamCh := make(chan streamAndReply) + + upgrader := spdy.NewResponseUpgrader() + conn := upgrader.UpgradeResponse(w, req, func(stream httpstream.Stream, replySent <-chan struct{}) error { + streamCh <- streamAndReply{Stream: stream, replySent: replySent} + return nil + }) + // from this point on, we can no longer call methods on response + if conn == nil { + // The upgrader is responsible for notifying the client of any errors that + // occurred during upgrading. All we can do is return here at this point + // if we weren't successful in upgrading. + return nil, false + } + + conn.SetIdleTimeout(idleTimeout) + + var handler protocolHandler + switch protocol { + case StreamProtocolV2Name: + handler = &v2ProtocolHandler{} + case "": + glog.V(4).Infof("Client did not request protocol negotiaion. Falling back to %q", StreamProtocolV1Name) + fallthrough + case StreamProtocolV1Name: + handler = &v1ProtocolHandler{} + } + + expired := time.NewTimer(streamCreationTimeout) + + ctx, err := handler.waitForStreams(streamCh, opts.expectedStreams, expired.C) + if err != nil { + runtime.HandleError(err) + return nil, false + } + + ctx.conn = conn + ctx.tty = opts.tty + return ctx, true +} + +type protocolHandler interface { + // waitForStreams waits for the expected streams or a timeout, returning a + // remoteCommandContext if all the streams were received, or an error if not. + waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error) +} + +// v2ProtocolHandler implements the V2 protocol version for streaming command execution. +type v2ProtocolHandler struct{} + +func (*v2ProtocolHandler) waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error) { + ctx := &context{} + receivedStreams := 0 + replyChan := make(chan struct{}) + stop := make(chan struct{}) + defer close(stop) +WaitForStreams: + for { + select { + case stream := <-streams: + streamType := stream.Headers().Get(api.StreamType) + switch streamType { + case api.StreamTypeError: + ctx.errorStream = stream + go waitStreamReply(stream.replySent, replyChan, stop) + case api.StreamTypeStdin: + ctx.stdinStream = stream + go waitStreamReply(stream.replySent, replyChan, stop) + case api.StreamTypeStdout: + ctx.stdoutStream = stream + go waitStreamReply(stream.replySent, replyChan, stop) + case api.StreamTypeStderr: + ctx.stderrStream = stream + go waitStreamReply(stream.replySent, replyChan, stop) + default: + runtime.HandleError(fmt.Errorf("Unexpected stream type: %q", streamType)) + } + case <-replyChan: + receivedStreams++ + if receivedStreams == expectedStreams { + break WaitForStreams + } + case <-expired: + // TODO find a way to return the error to the user. Maybe use a separate + // stream to report errors? + return nil, errors.New("timed out waiting for client to create streams") + } + } + + return ctx, nil +} + +// v1ProtocolHandler implements the V1 protocol version for streaming command execution. +type v1ProtocolHandler struct{} + +func (*v1ProtocolHandler) waitForStreams(streams <-chan streamAndReply, expectedStreams int, expired <-chan time.Time) (*context, error) { + ctx := &context{} + receivedStreams := 0 + replyChan := make(chan struct{}) + stop := make(chan struct{}) + defer close(stop) +WaitForStreams: + for { + select { + case stream := <-streams: + streamType := stream.Headers().Get(api.StreamType) + switch streamType { + case api.StreamTypeError: + ctx.errorStream = stream + + // This defer statement shouldn't be here, but due to previous refactoring, it ended up in + // here. This is what 1.0.x kubelets do, so we're retaining that behavior. This is fixed in + // the v2ProtocolHandler. + defer stream.Reset() + + go waitStreamReply(stream.replySent, replyChan, stop) + case api.StreamTypeStdin: + ctx.stdinStream = stream + go waitStreamReply(stream.replySent, replyChan, stop) + case api.StreamTypeStdout: + ctx.stdoutStream = stream + go waitStreamReply(stream.replySent, replyChan, stop) + case api.StreamTypeStderr: + ctx.stderrStream = stream + go waitStreamReply(stream.replySent, replyChan, stop) + default: + runtime.HandleError(fmt.Errorf("Unexpected stream type: %q", streamType)) + } + case <-replyChan: + receivedStreams++ + if receivedStreams == expectedStreams { + break WaitForStreams + } + case <-expired: + // TODO find a way to return the error to the user. Maybe use a separate + // stream to report errors? + return nil, errors.New("timed out waiting for client to create streams") + } + } + + if ctx.stdinStream != nil { + ctx.stdinStream.Close() + } + + return ctx, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/websocket.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/websocket.go new file mode 100644 index 000000000..06a84c8e7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/remotecommand/websocket.go @@ -0,0 +1,77 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 remotecommand + +import ( + "net/http" + "time" + + "k8s.io/kubernetes/pkg/httplog" + "k8s.io/kubernetes/pkg/util/wsstream" + + "github.com/golang/glog" +) + +// standardShellChannels returns the standard channel types for a shell connection (STDIN 0, STDOUT 1, STDERR 2) +// along with the approximate duplex value. Supported subprotocols are "channel.k8s.io" and +// "base64.channel.k8s.io". +func standardShellChannels(stdin, stdout, stderr bool) []wsstream.ChannelType { + // open three half-duplex channels + channels := []wsstream.ChannelType{wsstream.ReadChannel, wsstream.WriteChannel, wsstream.WriteChannel} + if !stdin { + channels[0] = wsstream.IgnoreChannel + } + if !stdout { + channels[1] = wsstream.IgnoreChannel + } + if !stderr { + channels[2] = wsstream.IgnoreChannel + } + return channels +} + +// createWebSocketStreams returns a remoteCommandContext containing the websocket connection and +// streams needed to perform an exec or an attach. +func createWebSocketStreams(req *http.Request, w http.ResponseWriter, opts *options, idleTimeout time.Duration) (*context, bool) { + // open the requested channels, and always open the error channel + channels := append(standardShellChannels(opts.stdin, opts.stdout, opts.stderr), wsstream.WriteChannel) + conn := wsstream.NewConn(channels...) + conn.SetIdleTimeout(idleTimeout) + streams, err := conn.Open(httplog.Unlogged(w), req) + if err != nil { + glog.Errorf("Unable to upgrade websocket connection: %v", err) + return nil, false + } + // Send an empty message to the lowest writable channel to notify the client the connection is established + // TODO: make generic to SPDY and WebSockets and do it outside of this method? + switch { + case opts.stdout: + streams[1].Write([]byte{}) + case opts.stderr: + streams[2].Write([]byte{}) + default: + streams[3].Write([]byte{}) + } + return &context{ + conn: conn, + stdinStream: streams[0], + stdoutStream: streams[1], + stderrStream: streams[2], + errorStream: streams[3], + tty: opts.tty, + }, true +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/server.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/server.go new file mode 100644 index 000000000..15bd523a0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/server.go @@ -0,0 +1,937 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 server + +import ( + "crypto/tls" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/http/pprof" + "reflect" + "strconv" + "strings" + "sync" + "time" + + restful "github.com/emicklei/go-restful" + "github.com/golang/glog" + cadvisorapi "github.com/google/cadvisor/info/v1" + cadvisorapiv2 "github.com/google/cadvisor/info/v2" + "github.com/prometheus/client_golang/prometheus" + + "k8s.io/kubernetes/pkg/api" + apierrs "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/auth/authenticator" + "k8s.io/kubernetes/pkg/auth/authorizer" + "k8s.io/kubernetes/pkg/healthz" + "k8s.io/kubernetes/pkg/httplog" + "k8s.io/kubernetes/pkg/kubelet/cm" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/server/portforward" + "k8s.io/kubernetes/pkg/kubelet/server/remotecommand" + "k8s.io/kubernetes/pkg/kubelet/server/stats" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/configz" + "k8s.io/kubernetes/pkg/util/flushwriter" + "k8s.io/kubernetes/pkg/util/httpstream" + "k8s.io/kubernetes/pkg/util/httpstream/spdy" + "k8s.io/kubernetes/pkg/util/limitwriter" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/volume" +) + +// Server is a http.Handler which exposes kubelet functionality over HTTP. +type Server struct { + auth AuthInterface + host HostInterface + restfulCont containerInterface + resourceAnalyzer stats.ResourceAnalyzer +} + +type TLSOptions struct { + Config *tls.Config + CertFile string + KeyFile string +} + +// containerInterface defines the restful.Container functions used on the root container +type containerInterface interface { + Add(service *restful.WebService) *restful.Container + Handle(path string, handler http.Handler) + Filter(filter restful.FilterFunction) + ServeHTTP(w http.ResponseWriter, r *http.Request) + RegisteredWebServices() []*restful.WebService + + // RegisteredHandlePaths returns the paths of handlers registered directly with the container (non-web-services) + // Used to test filters are being applied on non-web-service handlers + RegisteredHandlePaths() []string +} + +// filteringContainer delegates all Handle(...) calls to Container.HandleWithFilter(...), +// so we can ensure restful.FilterFunctions are used for all handlers +type filteringContainer struct { + *restful.Container + registeredHandlePaths []string +} + +func (a *filteringContainer) Handle(path string, handler http.Handler) { + a.HandleWithFilter(path, handler) + a.registeredHandlePaths = append(a.registeredHandlePaths, path) +} +func (a *filteringContainer) RegisteredHandlePaths() []string { + return a.registeredHandlePaths +} + +// ListenAndServeKubeletServer initializes a server to respond to HTTP network requests on the Kubelet. +func ListenAndServeKubeletServer(host HostInterface, resourceAnalyzer stats.ResourceAnalyzer, address net.IP, port uint, tlsOptions *TLSOptions, auth AuthInterface, enableDebuggingHandlers bool) { + glog.Infof("Starting to listen on %s:%d", address, port) + handler := NewServer(host, resourceAnalyzer, auth, enableDebuggingHandlers) + s := &http.Server{ + Addr: net.JoinHostPort(address.String(), strconv.FormatUint(uint64(port), 10)), + Handler: &handler, + MaxHeaderBytes: 1 << 20, + } + if tlsOptions != nil { + s.TLSConfig = tlsOptions.Config + glog.Fatal(s.ListenAndServeTLS(tlsOptions.CertFile, tlsOptions.KeyFile)) + } else { + glog.Fatal(s.ListenAndServe()) + } +} + +// ListenAndServeKubeletReadOnlyServer initializes a server to respond to HTTP network requests on the Kubelet. +func ListenAndServeKubeletReadOnlyServer(host HostInterface, resourceAnalyzer stats.ResourceAnalyzer, address net.IP, port uint) { + glog.V(1).Infof("Starting to listen read-only on %s:%d", address, port) + s := NewServer(host, resourceAnalyzer, nil, false) + + server := &http.Server{ + Addr: net.JoinHostPort(address.String(), strconv.FormatUint(uint64(port), 10)), + Handler: &s, + MaxHeaderBytes: 1 << 20, + } + glog.Fatal(server.ListenAndServe()) +} + +// AuthInterface contains all methods required by the auth filters +type AuthInterface interface { + authenticator.Request + authorizer.RequestAttributesGetter + authorizer.Authorizer +} + +// HostInterface contains all the kubelet methods required by the server. +// For testablitiy. +type HostInterface interface { + GetContainerInfo(podFullName string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) + GetContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) + GetRawContainerInfo(containerName string, req *cadvisorapi.ContainerInfoRequest, subcontainers bool) (map[string]*cadvisorapi.ContainerInfo, error) + GetCachedMachineInfo() (*cadvisorapi.MachineInfo, error) + GetPods() []*api.Pod + GetRunningPods() ([]*api.Pod, error) + GetPodByName(namespace, name string) (*api.Pod, bool) + RunInContainer(name string, uid types.UID, container string, cmd []string) ([]byte, error) + ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error + AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool) error + GetKubeletContainerLogs(podFullName, containerName string, logOptions *api.PodLogOptions, stdout, stderr io.Writer) error + ServeLogs(w http.ResponseWriter, req *http.Request) + PortForward(name string, uid types.UID, port uint16, stream io.ReadWriteCloser) error + StreamingConnectionIdleTimeout() time.Duration + ResyncInterval() time.Duration + GetHostname() string + GetNode() (*api.Node, error) + GetNodeConfig() cm.NodeConfig + LatestLoopEntryTime() time.Time + DockerImagesFsInfo() (cadvisorapiv2.FsInfo, error) + RootFsInfo() (cadvisorapiv2.FsInfo, error) + ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool) + PLEGHealthCheck() (bool, error) +} + +// NewServer initializes and configures a kubelet.Server object to handle HTTP requests. +func NewServer(host HostInterface, resourceAnalyzer stats.ResourceAnalyzer, auth AuthInterface, enableDebuggingHandlers bool) Server { + server := Server{ + host: host, + resourceAnalyzer: resourceAnalyzer, + auth: auth, + restfulCont: &filteringContainer{Container: restful.NewContainer()}, + } + if auth != nil { + server.InstallAuthFilter() + } + server.InstallDefaultHandlers() + if enableDebuggingHandlers { + server.InstallDebuggingHandlers() + } + return server +} + +// InstallAuthFilter installs authentication filters with the restful Container. +func (s *Server) InstallAuthFilter() { + s.restfulCont.Filter(func(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { + // Authenticate + u, ok, err := s.auth.AuthenticateRequest(req.Request) + if err != nil { + glog.Errorf("Unable to authenticate the request due to an error: %v", err) + resp.WriteErrorString(http.StatusUnauthorized, "Unauthorized") + return + } + if !ok { + resp.WriteErrorString(http.StatusUnauthorized, "Unauthorized") + return + } + + // Get authorization attributes + attrs := s.auth.GetRequestAttributes(u, req.Request) + + // Authorize + if err := s.auth.Authorize(attrs); err != nil { + msg := fmt.Sprintf("Forbidden (user=%s, verb=%s, namespace=%s, resource=%s)", u.GetName(), attrs.GetVerb(), attrs.GetNamespace(), attrs.GetResource()) + glog.V(2).Info(msg) + resp.WriteErrorString(http.StatusForbidden, msg) + return + } + + // Continue + chain.ProcessFilter(req, resp) + }) +} + +// InstallDefaultHandlers registers the default set of supported HTTP request +// patterns with the restful Container. +func (s *Server) InstallDefaultHandlers() { + healthz.InstallHandler(s.restfulCont, + healthz.PingHealthz, + healthz.NamedCheck("syncloop", s.syncLoopHealthCheck), + healthz.NamedCheck("pleg", s.plegHealthCheck), + ) + var ws *restful.WebService + ws = new(restful.WebService) + ws. + Path("/pods"). + Produces(restful.MIME_JSON) + ws.Route(ws.GET(""). + To(s.getPods). + Operation("getPods")) + s.restfulCont.Add(ws) + + s.restfulCont.Add(stats.CreateHandlers(s.host, s.resourceAnalyzer)) + s.restfulCont.Handle("/metrics", prometheus.Handler()) + + ws = new(restful.WebService) + ws. + Path("/spec/"). + Produces(restful.MIME_JSON) + ws.Route(ws.GET(""). + To(s.getSpec). + Operation("getSpec"). + Writes(cadvisorapi.MachineInfo{})) + s.restfulCont.Add(ws) +} + +const pprofBasePath = "/debug/pprof/" + +// InstallDeguggingHandlers registers the HTTP request patterns that serve logs or run commands/containers +func (s *Server) InstallDebuggingHandlers() { + var ws *restful.WebService + + ws = new(restful.WebService) + ws. + Path("/run") + ws.Route(ws.POST("/{podNamespace}/{podID}/{containerName}"). + To(s.getRun). + Operation("getRun")) + ws.Route(ws.POST("/{podNamespace}/{podID}/{uid}/{containerName}"). + To(s.getRun). + Operation("getRun")) + s.restfulCont.Add(ws) + + ws = new(restful.WebService) + ws. + Path("/exec") + ws.Route(ws.GET("/{podNamespace}/{podID}/{containerName}"). + To(s.getExec). + Operation("getExec")) + ws.Route(ws.POST("/{podNamespace}/{podID}/{containerName}"). + To(s.getExec). + Operation("getExec")) + ws.Route(ws.GET("/{podNamespace}/{podID}/{uid}/{containerName}"). + To(s.getExec). + Operation("getExec")) + ws.Route(ws.POST("/{podNamespace}/{podID}/{uid}/{containerName}"). + To(s.getExec). + Operation("getExec")) + s.restfulCont.Add(ws) + + ws = new(restful.WebService) + ws. + Path("/attach") + ws.Route(ws.GET("/{podNamespace}/{podID}/{containerName}"). + To(s.getAttach). + Operation("getAttach")) + ws.Route(ws.POST("/{podNamespace}/{podID}/{containerName}"). + To(s.getAttach). + Operation("getAttach")) + ws.Route(ws.GET("/{podNamespace}/{podID}/{uid}/{containerName}"). + To(s.getAttach). + Operation("getAttach")) + ws.Route(ws.POST("/{podNamespace}/{podID}/{uid}/{containerName}"). + To(s.getAttach). + Operation("getAttach")) + s.restfulCont.Add(ws) + + ws = new(restful.WebService) + ws. + Path("/portForward") + ws.Route(ws.POST("/{podNamespace}/{podID}"). + To(s.getPortForward). + Operation("getPortForward")) + ws.Route(ws.POST("/{podNamespace}/{podID}/{uid}"). + To(s.getPortForward). + Operation("getPortForward")) + s.restfulCont.Add(ws) + + ws = new(restful.WebService) + ws. + Path("/logs/") + ws.Route(ws.GET(""). + To(s.getLogs). + Operation("getLogs")) + ws.Route(ws.GET("/{logpath:*}"). + To(s.getLogs). + Operation("getLogs")) + s.restfulCont.Add(ws) + + ws = new(restful.WebService) + ws. + Path("/containerLogs") + ws.Route(ws.GET("/{podNamespace}/{podID}/{containerName}"). + To(s.getContainerLogs). + Operation("getContainerLogs")) + s.restfulCont.Add(ws) + + configz.InstallHandler(s.restfulCont) + + handlePprofEndpoint := func(req *restful.Request, resp *restful.Response) { + name := strings.TrimPrefix(req.Request.URL.Path, pprofBasePath) + switch name { + case "profile": + pprof.Profile(resp, req.Request) + case "symbol": + pprof.Symbol(resp, req.Request) + case "cmdline": + pprof.Cmdline(resp, req.Request) + default: + pprof.Index(resp, req.Request) + } + } + + // Setup pporf handlers. + ws = new(restful.WebService).Path(pprofBasePath) + ws.Route(ws.GET("/{subpath:*}").To(func(req *restful.Request, resp *restful.Response) { + handlePprofEndpoint(req, resp) + })).Doc("pprof endpoint") + s.restfulCont.Add(ws) + + // The /runningpods endpoint is used for testing only. + ws = new(restful.WebService) + ws. + Path("/runningpods/"). + Produces(restful.MIME_JSON) + ws.Route(ws.GET(""). + To(s.getRunningPods). + Operation("getRunningPods")) + s.restfulCont.Add(ws) +} + +type httpHandler struct { + f func(w http.ResponseWriter, r *http.Request) +} + +func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.f(w, r) +} + +// Checks if kubelet's sync loop that updates containers is working. +func (s *Server) syncLoopHealthCheck(req *http.Request) error { + duration := s.host.ResyncInterval() * 2 + minDuration := time.Minute * 5 + if duration < minDuration { + duration = minDuration + } + enterLoopTime := s.host.LatestLoopEntryTime() + if !enterLoopTime.IsZero() && time.Now().After(enterLoopTime.Add(duration)) { + return fmt.Errorf("Sync Loop took longer than expected.") + } + return nil +} + +// Checks if pleg, which lists pods periodically, is healthy. +func (s *Server) plegHealthCheck(req *http.Request) error { + if ok, err := s.host.PLEGHealthCheck(); !ok { + return fmt.Errorf("PLEG took longer than expected: %v", err) + } + return nil +} + +// getContainerLogs handles containerLogs request against the Kubelet +func (s *Server) getContainerLogs(request *restful.Request, response *restful.Response) { + podNamespace := request.PathParameter("podNamespace") + podID := request.PathParameter("podID") + containerName := request.PathParameter("containerName") + + if len(podID) == 0 { + // TODO: Why return JSON when the rest return plaintext errors? + // TODO: Why return plaintext errors? + response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podID."}`)) + return + } + if len(containerName) == 0 { + // TODO: Why return JSON when the rest return plaintext errors? + response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing container name."}`)) + return + } + if len(podNamespace) == 0 { + // TODO: Why return JSON when the rest return plaintext errors? + response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podNamespace."}`)) + return + } + + query := request.Request.URL.Query() + // backwards compatibility for the "tail" query parameter + if tail := request.QueryParameter("tail"); len(tail) > 0 { + query["tailLines"] = []string{tail} + // "all" is the same as omitting tail + if tail == "all" { + delete(query, "tailLines") + } + } + // container logs on the kubelet are locked to the v1 API version of PodLogOptions + logOptions := &api.PodLogOptions{} + if err := api.ParameterCodec.DecodeParameters(query, v1.SchemeGroupVersion, logOptions); err != nil { + response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Unable to decode query."}`)) + return + } + logOptions.TypeMeta = unversioned.TypeMeta{} + if errs := validation.ValidatePodLogOptions(logOptions); len(errs) > 0 { + response.WriteError(apierrs.StatusUnprocessableEntity, fmt.Errorf(`{"message": "Invalid request."}`)) + return + } + + pod, ok := s.host.GetPodByName(podNamespace, podID) + if !ok { + response.WriteError(http.StatusNotFound, fmt.Errorf("pod %q does not exist\n", podID)) + return + } + // Check if containerName is valid. + containerExists := false + for _, container := range pod.Spec.Containers { + if container.Name == containerName { + containerExists = true + } + } + if !containerExists { + response.WriteError(http.StatusNotFound, fmt.Errorf("container %q not found in pod %q\n", containerName, podID)) + return + } + + if _, ok := response.ResponseWriter.(http.Flusher); !ok { + response.WriteError(http.StatusInternalServerError, fmt.Errorf("unable to convert %v into http.Flusher, cannot show logs\n", reflect.TypeOf(response))) + return + } + fw := flushwriter.Wrap(response.ResponseWriter) + if logOptions.LimitBytes != nil { + fw = limitwriter.New(fw, *logOptions.LimitBytes) + } + response.Header().Set("Transfer-Encoding", "chunked") + if err := s.host.GetKubeletContainerLogs(kubecontainer.GetPodFullName(pod), containerName, logOptions, fw, fw); err != nil { + if err != limitwriter.ErrMaximumWrite { + response.WriteError(http.StatusBadRequest, err) + } + return + } +} + +// encodePods creates an api.PodList object from pods and returns the encoded +// PodList. +func encodePods(pods []*api.Pod) (data []byte, err error) { + podList := new(api.PodList) + for _, pod := range pods { + podList.Items = append(podList.Items, *pod) + } + // TODO: this needs to be parameterized to the kubelet, not hardcoded. Depends on Kubelet + // as API server refactor. + // TODO: Locked to v1, needs to be made generic + codec := api.Codecs.LegacyCodec(unversioned.GroupVersion{Group: api.GroupName, Version: "v1"}) + return runtime.Encode(codec, podList) +} + +// getPods returns a list of pods bound to the Kubelet and their spec. +func (s *Server) getPods(request *restful.Request, response *restful.Response) { + pods := s.host.GetPods() + data, err := encodePods(pods) + if err != nil { + response.WriteError(http.StatusInternalServerError, err) + return + } + writeJsonResponse(response, data) +} + +// getRunningPods returns a list of pods running on Kubelet. The list is +// provided by the container runtime, and is different from the list returned +// by getPods, which is a set of desired pods to run. +func (s *Server) getRunningPods(request *restful.Request, response *restful.Response) { + pods, err := s.host.GetRunningPods() + if err != nil { + response.WriteError(http.StatusInternalServerError, err) + return + } + data, err := encodePods(pods) + if err != nil { + response.WriteError(http.StatusInternalServerError, err) + return + } + writeJsonResponse(response, data) +} + +// getLogs handles logs requests against the Kubelet. +func (s *Server) getLogs(request *restful.Request, response *restful.Response) { + s.host.ServeLogs(response, request.Request) +} + +// getSpec handles spec requests against the Kubelet. +func (s *Server) getSpec(request *restful.Request, response *restful.Response) { + info, err := s.host.GetCachedMachineInfo() + if err != nil { + response.WriteError(http.StatusInternalServerError, err) + return + } + response.WriteEntity(info) +} + +func getContainerCoordinates(request *restful.Request) (namespace, pod string, uid types.UID, container string) { + namespace = request.PathParameter("podNamespace") + pod = request.PathParameter("podID") + if uidStr := request.PathParameter("uid"); uidStr != "" { + uid = types.UID(uidStr) + } + container = request.PathParameter("containerName") + return +} + +// getAttach handles requests to attach to a container. +func (s *Server) getAttach(request *restful.Request, response *restful.Response) { + podNamespace, podID, uid, container := getContainerCoordinates(request) + pod, ok := s.host.GetPodByName(podNamespace, podID) + if !ok { + response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist")) + return + } + + remotecommand.ServeAttach(response.ResponseWriter, + request.Request, + s.host, + kubecontainer.GetPodFullName(pod), + uid, + container, + s.host.StreamingConnectionIdleTimeout(), + remotecommand.DefaultStreamCreationTimeout, + remotecommand.SupportedStreamingProtocols) +} + +// getExec handles requests to run a command inside a container. +func (s *Server) getExec(request *restful.Request, response *restful.Response) { + podNamespace, podID, uid, container := getContainerCoordinates(request) + pod, ok := s.host.GetPodByName(podNamespace, podID) + if !ok { + response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist")) + return + } + + remotecommand.ServeExec(response.ResponseWriter, + request.Request, + s.host, + kubecontainer.GetPodFullName(pod), + uid, + container, + s.host.StreamingConnectionIdleTimeout(), + remotecommand.DefaultStreamCreationTimeout, + remotecommand.SupportedStreamingProtocols) +} + +// getRun handles requests to run a command inside a container. +func (s *Server) getRun(request *restful.Request, response *restful.Response) { + podNamespace, podID, uid, container := getContainerCoordinates(request) + pod, ok := s.host.GetPodByName(podNamespace, podID) + if !ok { + response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist")) + return + } + command := strings.Split(request.QueryParameter("cmd"), " ") + data, err := s.host.RunInContainer(kubecontainer.GetPodFullName(pod), uid, container, command) + if err != nil { + response.WriteError(http.StatusInternalServerError, err) + return + } + writeJsonResponse(response, data) +} + +func getPodCoordinates(request *restful.Request) (namespace, pod string, uid types.UID) { + namespace = request.PathParameter("podNamespace") + pod = request.PathParameter("podID") + if uidStr := request.PathParameter("uid"); uidStr != "" { + uid = types.UID(uidStr) + } + return +} + +// Derived from go-restful writeJSON. +func writeJsonResponse(response *restful.Response, data []byte) { + if data == nil { + response.WriteHeader(http.StatusOK) + // do not write a nil representation + return + } + response.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON) + response.WriteHeader(http.StatusOK) + if _, err := response.Write(data); err != nil { + glog.Errorf("Error writing response: %v", err) + } +} + +// PortForwarder knows how to forward content from a data stream to/from a port +// in a pod. +type PortForwarder interface { + // PortForwarder copies data between a data stream and a port in a pod. + PortForward(name string, uid types.UID, port uint16, stream io.ReadWriteCloser) error +} + +// getPortForward handles a new restful port forward request. It determines the +// pod name and uid and then calls ServePortForward. +func (s *Server) getPortForward(request *restful.Request, response *restful.Response) { + podNamespace, podID, uid := getPodCoordinates(request) + pod, ok := s.host.GetPodByName(podNamespace, podID) + if !ok { + response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist")) + return + } + + podName := kubecontainer.GetPodFullName(pod) + + ServePortForward(response.ResponseWriter, request.Request, s.host, podName, uid, s.host.StreamingConnectionIdleTimeout(), remotecommand.DefaultStreamCreationTimeout) +} + +// ServePortForward handles a port forwarding request. A single request is +// kept alive as long as the client is still alive and the connection has not +// been timed out due to idleness. This function handles multiple forwarded +// connections; i.e., multiple `curl http://localhost:8888/` requests will be +// handled by a single invocation of ServePortForward. +func ServePortForward(w http.ResponseWriter, req *http.Request, portForwarder PortForwarder, podName string, uid types.UID, idleTimeout time.Duration, streamCreationTimeout time.Duration) { + supportedPortForwardProtocols := []string{portforward.PortForwardProtocolV1Name} + _, err := httpstream.Handshake(req, w, supportedPortForwardProtocols) + // negotiated protocol isn't currently used server side, but could be in the future + if err != nil { + // Handshake writes the error to the client + utilruntime.HandleError(err) + return + } + + streamChan := make(chan httpstream.Stream, 1) + + glog.V(5).Infof("Upgrading port forward response") + upgrader := spdy.NewResponseUpgrader() + conn := upgrader.UpgradeResponse(w, req, portForwardStreamReceived(streamChan)) + if conn == nil { + return + } + defer conn.Close() + + glog.V(5).Infof("(conn=%p) setting port forwarding streaming connection idle timeout to %v", conn, idleTimeout) + conn.SetIdleTimeout(idleTimeout) + + h := &portForwardStreamHandler{ + conn: conn, + streamChan: streamChan, + streamPairs: make(map[string]*portForwardStreamPair), + streamCreationTimeout: streamCreationTimeout, + pod: podName, + uid: uid, + forwarder: portForwarder, + } + h.run() +} + +// portForwardStreamReceived is the httpstream.NewStreamHandler for port +// forward streams. It checks each stream's port and stream type headers, +// rejecting any streams that with missing or invalid values. Each valid +// stream is sent to the streams channel. +func portForwardStreamReceived(streams chan httpstream.Stream) func(httpstream.Stream, <-chan struct{}) error { + return func(stream httpstream.Stream, replySent <-chan struct{}) error { + // make sure it has a valid port header + portString := stream.Headers().Get(api.PortHeader) + if len(portString) == 0 { + return fmt.Errorf("%q header is required", api.PortHeader) + } + port, err := strconv.ParseUint(portString, 10, 16) + if err != nil { + return fmt.Errorf("unable to parse %q as a port: %v", portString, err) + } + if port < 1 { + return fmt.Errorf("port %q must be > 0", portString) + } + + // make sure it has a valid stream type header + streamType := stream.Headers().Get(api.StreamType) + if len(streamType) == 0 { + return fmt.Errorf("%q header is required", api.StreamType) + } + if streamType != api.StreamTypeError && streamType != api.StreamTypeData { + return fmt.Errorf("invalid stream type %q", streamType) + } + + streams <- stream + return nil + } +} + +// portForwardStreamHandler is capable of processing multiple port forward +// requests over a single httpstream.Connection. +type portForwardStreamHandler struct { + conn httpstream.Connection + streamChan chan httpstream.Stream + streamPairsLock sync.RWMutex + streamPairs map[string]*portForwardStreamPair + streamCreationTimeout time.Duration + pod string + uid types.UID + forwarder PortForwarder +} + +// getStreamPair returns a portForwardStreamPair for requestID. This creates a +// new pair if one does not yet exist for the requestID. The returned bool is +// true if the pair was created. +func (h *portForwardStreamHandler) getStreamPair(requestID string) (*portForwardStreamPair, bool) { + h.streamPairsLock.Lock() + defer h.streamPairsLock.Unlock() + + if p, ok := h.streamPairs[requestID]; ok { + glog.V(5).Infof("(conn=%p, request=%s) found existing stream pair", h.conn, requestID) + return p, false + } + + glog.V(5).Infof("(conn=%p, request=%s) creating new stream pair", h.conn, requestID) + + p := newPortForwardPair(requestID) + h.streamPairs[requestID] = p + + return p, true +} + +// monitorStreamPair waits for the pair to receive both its error and data +// streams, or for the timeout to expire (whichever happens first), and then +// removes the pair. +func (h *portForwardStreamHandler) monitorStreamPair(p *portForwardStreamPair, timeout <-chan time.Time) { + select { + case <-timeout: + err := fmt.Errorf("(conn=%v, request=%s) timed out waiting for streams", h.conn, p.requestID) + utilruntime.HandleError(err) + p.printError(err.Error()) + case <-p.complete: + glog.V(5).Infof("(conn=%v, request=%s) successfully received error and data streams", h.conn, p.requestID) + } + h.removeStreamPair(p.requestID) +} + +// hasStreamPair returns a bool indicating if a stream pair for requestID +// exists. +func (h *portForwardStreamHandler) hasStreamPair(requestID string) bool { + h.streamPairsLock.RLock() + defer h.streamPairsLock.RUnlock() + + _, ok := h.streamPairs[requestID] + return ok +} + +// removeStreamPair removes the stream pair identified by requestID from streamPairs. +func (h *portForwardStreamHandler) removeStreamPair(requestID string) { + h.streamPairsLock.Lock() + defer h.streamPairsLock.Unlock() + + delete(h.streamPairs, requestID) +} + +// requestID returns the request id for stream. +func (h *portForwardStreamHandler) requestID(stream httpstream.Stream) string { + requestID := stream.Headers().Get(api.PortForwardRequestIDHeader) + if len(requestID) == 0 { + glog.V(5).Infof("(conn=%p) stream received without %s header", h.conn, api.PortForwardRequestIDHeader) + // If we get here, it's because the connection came from an older client + // that isn't generating the request id header + // (https://github.com/kubernetes/kubernetes/blob/843134885e7e0b360eb5441e85b1410a8b1a7a0c/pkg/client/unversioned/portforward/portforward.go#L258-L287) + // + // This is a best-effort attempt at supporting older clients. + // + // When there aren't concurrent new forwarded connections, each connection + // will have a pair of streams (data, error), and the stream IDs will be + // consecutive odd numbers, e.g. 1 and 3 for the first connection. Convert + // the stream ID into a pseudo-request id by taking the stream type and + // using id = stream.Identifier() when the stream type is error, + // and id = stream.Identifier() - 2 when it's data. + // + // NOTE: this only works when there are not concurrent new streams from + // multiple forwarded connections; it's a best-effort attempt at supporting + // old clients that don't generate request ids. If there are concurrent + // new connections, it's possible that 1 connection gets streams whose IDs + // are not consecutive (e.g. 5 and 9 instead of 5 and 7). + streamType := stream.Headers().Get(api.StreamType) + switch streamType { + case api.StreamTypeError: + requestID = strconv.Itoa(int(stream.Identifier())) + case api.StreamTypeData: + requestID = strconv.Itoa(int(stream.Identifier()) - 2) + } + + glog.V(5).Infof("(conn=%p) automatically assigning request ID=%q from stream type=%s, stream ID=%d", h.conn, requestID, streamType, stream.Identifier()) + } + return requestID +} + +// run is the main loop for the portForwardStreamHandler. It processes new +// streams, invoking portForward for each complete stream pair. The loop exits +// when the httpstream.Connection is closed. +func (h *portForwardStreamHandler) run() { + glog.V(5).Infof("(conn=%p) waiting for port forward streams", h.conn) +Loop: + for { + select { + case <-h.conn.CloseChan(): + glog.V(5).Infof("(conn=%p) upgraded connection closed", h.conn) + break Loop + case stream := <-h.streamChan: + requestID := h.requestID(stream) + streamType := stream.Headers().Get(api.StreamType) + glog.V(5).Infof("(conn=%p, request=%s) received new stream of type %s", h.conn, requestID, streamType) + + p, created := h.getStreamPair(requestID) + if created { + go h.monitorStreamPair(p, time.After(h.streamCreationTimeout)) + } + if complete, err := p.add(stream); err != nil { + msg := fmt.Sprintf("error processing stream for request %s: %v", requestID, err) + utilruntime.HandleError(errors.New(msg)) + p.printError(msg) + } else if complete { + go h.portForward(p) + } + } + } +} + +// portForward invokes the portForwardStreamHandler's forwarder.PortForward +// function for the given stream pair. +func (h *portForwardStreamHandler) portForward(p *portForwardStreamPair) { + defer p.dataStream.Close() + defer p.errorStream.Close() + + portString := p.dataStream.Headers().Get(api.PortHeader) + port, _ := strconv.ParseUint(portString, 10, 16) + + glog.V(5).Infof("(conn=%p, request=%s) invoking forwarder.PortForward for port %s", h.conn, p.requestID, portString) + err := h.forwarder.PortForward(h.pod, h.uid, uint16(port), p.dataStream) + glog.V(5).Infof("(conn=%p, request=%s) done invoking forwarder.PortForward for port %s", h.conn, p.requestID, portString) + + if err != nil { + msg := fmt.Errorf("error forwarding port %d to pod %s, uid %v: %v", port, h.pod, h.uid, err) + utilruntime.HandleError(msg) + fmt.Fprint(p.errorStream, msg.Error()) + } +} + +// portForwardStreamPair represents the error and data streams for a port +// forwarding request. +type portForwardStreamPair struct { + lock sync.RWMutex + requestID string + dataStream httpstream.Stream + errorStream httpstream.Stream + complete chan struct{} +} + +// newPortForwardPair creates a new portForwardStreamPair. +func newPortForwardPair(requestID string) *portForwardStreamPair { + return &portForwardStreamPair{ + requestID: requestID, + complete: make(chan struct{}), + } +} + +// add adds the stream to the portForwardStreamPair. If the pair already +// contains a stream for the new stream's type, an error is returned. add +// returns true if both the data and error streams for this pair have been +// received. +func (p *portForwardStreamPair) add(stream httpstream.Stream) (bool, error) { + p.lock.Lock() + defer p.lock.Unlock() + + switch stream.Headers().Get(api.StreamType) { + case api.StreamTypeError: + if p.errorStream != nil { + return false, errors.New("error stream already assigned") + } + p.errorStream = stream + case api.StreamTypeData: + if p.dataStream != nil { + return false, errors.New("data stream already assigned") + } + p.dataStream = stream + } + + complete := p.errorStream != nil && p.dataStream != nil + if complete { + close(p.complete) + } + return complete, nil +} + +// printError writes s to p.errorStream if p.errorStream has been set. +func (p *portForwardStreamPair) printError(s string) { + p.lock.RLock() + defer p.lock.RUnlock() + if p.errorStream != nil { + fmt.Fprint(p.errorStream, s) + } +} + +// ServeHTTP responds to HTTP requests on the Kubelet. +func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { + defer httplog.NewLogged(req, &w).StacktraceWhen( + httplog.StatusIsNot( + http.StatusOK, + http.StatusMovedPermanently, + http.StatusTemporaryRedirect, + http.StatusNotFound, + http.StatusSwitchingProtocols, + ), + ).Log() + s.restfulCont.ServeHTTP(w, req) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/server_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/server_test.go new file mode 100644 index 000000000..c52413b6f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/server_test.go @@ -0,0 +1,1686 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 server + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/httptest" + "net/http/httputil" + "reflect" + "strconv" + "strings" + "testing" + "time" + + cadvisorapi "github.com/google/cadvisor/info/v1" + cadvisorapiv2 "github.com/google/cadvisor/info/v2" + "k8s.io/kubernetes/pkg/api" + apierrs "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/auth/authorizer" + "k8s.io/kubernetes/pkg/auth/user" + "k8s.io/kubernetes/pkg/kubelet/cm" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/kubelet/server/stats" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/httpstream" + "k8s.io/kubernetes/pkg/util/httpstream/spdy" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/volume" +) + +type fakeKubelet struct { + podByNameFunc func(namespace, name string) (*api.Pod, bool) + containerInfoFunc func(podFullName string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) + rawInfoFunc func(query *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) + machineInfoFunc func() (*cadvisorapi.MachineInfo, error) + podsFunc func() []*api.Pod + runningPodsFunc func() ([]*api.Pod, error) + logFunc func(w http.ResponseWriter, req *http.Request) + runFunc func(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) + execFunc func(pod string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error + attachFunc func(pod string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool) error + portForwardFunc func(name string, uid types.UID, port uint16, stream io.ReadWriteCloser) error + containerLogsFunc func(podFullName, containerName string, logOptions *api.PodLogOptions, stdout, stderr io.Writer) error + streamingConnectionIdleTimeoutFunc func() time.Duration + hostnameFunc func() string + resyncInterval time.Duration + loopEntryTime time.Time + plegHealth bool +} + +func (fk *fakeKubelet) ResyncInterval() time.Duration { + return fk.resyncInterval +} + +func (fk *fakeKubelet) LatestLoopEntryTime() time.Time { + return fk.loopEntryTime +} + +func (fk *fakeKubelet) GetPodByName(namespace, name string) (*api.Pod, bool) { + return fk.podByNameFunc(namespace, name) +} + +func (fk *fakeKubelet) GetContainerInfo(podFullName string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) { + return fk.containerInfoFunc(podFullName, uid, containerName, req) +} + +func (fk *fakeKubelet) GetRawContainerInfo(containerName string, req *cadvisorapi.ContainerInfoRequest, subcontainers bool) (map[string]*cadvisorapi.ContainerInfo, error) { + return fk.rawInfoFunc(req) +} + +func (fk *fakeKubelet) GetCachedMachineInfo() (*cadvisorapi.MachineInfo, error) { + return fk.machineInfoFunc() +} + +func (fk *fakeKubelet) GetPods() []*api.Pod { + return fk.podsFunc() +} + +func (fk *fakeKubelet) GetRunningPods() ([]*api.Pod, error) { + return fk.runningPodsFunc() +} + +func (fk *fakeKubelet) ServeLogs(w http.ResponseWriter, req *http.Request) { + fk.logFunc(w, req) +} + +func (fk *fakeKubelet) GetKubeletContainerLogs(podFullName, containerName string, logOptions *api.PodLogOptions, stdout, stderr io.Writer) error { + return fk.containerLogsFunc(podFullName, containerName, logOptions, stdout, stderr) +} + +func (fk *fakeKubelet) GetHostname() string { + return fk.hostnameFunc() +} + +func (fk *fakeKubelet) RunInContainer(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) { + return fk.runFunc(podFullName, uid, containerName, cmd) +} + +func (fk *fakeKubelet) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error { + return fk.execFunc(name, uid, container, cmd, in, out, err, tty) +} + +func (fk *fakeKubelet) AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool) error { + return fk.attachFunc(name, uid, container, in, out, err, tty) +} + +func (fk *fakeKubelet) PortForward(name string, uid types.UID, port uint16, stream io.ReadWriteCloser) error { + return fk.portForwardFunc(name, uid, port, stream) +} + +func (fk *fakeKubelet) StreamingConnectionIdleTimeout() time.Duration { + return fk.streamingConnectionIdleTimeoutFunc() +} + +func (fk *fakeKubelet) PLEGHealthCheck() (bool, error) { return fk.plegHealth, nil } + +// Unused functions +func (_ *fakeKubelet) GetContainerInfoV2(_ string, _ cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) { + return nil, nil +} + +func (_ *fakeKubelet) DockerImagesFsInfo() (cadvisorapiv2.FsInfo, error) { + return cadvisorapiv2.FsInfo{}, fmt.Errorf("Unsupported Operation DockerImagesFsInfo") +} + +func (_ *fakeKubelet) RootFsInfo() (cadvisorapiv2.FsInfo, error) { + return cadvisorapiv2.FsInfo{}, fmt.Errorf("Unsupport Operation RootFsInfo") +} + +func (_ *fakeKubelet) GetNode() (*api.Node, error) { return nil, nil } +func (_ *fakeKubelet) GetNodeConfig() cm.NodeConfig { return cm.NodeConfig{} } + +func (fk *fakeKubelet) ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool) { + return map[string]volume.Volume{}, true +} + +type fakeAuth struct { + authenticateFunc func(*http.Request) (user.Info, bool, error) + attributesFunc func(user.Info, *http.Request) authorizer.Attributes + authorizeFunc func(authorizer.Attributes) (err error) +} + +func (f *fakeAuth) AuthenticateRequest(req *http.Request) (user.Info, bool, error) { + return f.authenticateFunc(req) +} +func (f *fakeAuth) GetRequestAttributes(u user.Info, req *http.Request) authorizer.Attributes { + return f.attributesFunc(u, req) +} +func (f *fakeAuth) Authorize(a authorizer.Attributes) (err error) { + return f.authorizeFunc(a) +} + +type serverTestFramework struct { + serverUnderTest *Server + fakeKubelet *fakeKubelet + fakeAuth *fakeAuth + testHTTPServer *httptest.Server +} + +func newServerTest() *serverTestFramework { + fw := &serverTestFramework{} + fw.fakeKubelet = &fakeKubelet{ + hostnameFunc: func() string { + return "127.0.0.1" + }, + podByNameFunc: func(namespace, name string) (*api.Pod, bool) { + return &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + }, true + }, + plegHealth: true, + } + fw.fakeAuth = &fakeAuth{ + authenticateFunc: func(req *http.Request) (user.Info, bool, error) { + return &user.DefaultInfo{Name: "test"}, true, nil + }, + attributesFunc: func(u user.Info, req *http.Request) authorizer.Attributes { + return &authorizer.AttributesRecord{User: u} + }, + authorizeFunc: func(a authorizer.Attributes) (err error) { + return nil + }, + } + server := NewServer( + fw.fakeKubelet, + stats.NewResourceAnalyzer(fw.fakeKubelet, time.Minute), + fw.fakeAuth, + true) + fw.serverUnderTest = &server + // TODO: Close() this when fix #19254 + fw.testHTTPServer = httptest.NewServer(fw.serverUnderTest) + return fw +} + +// encodeJSON returns obj marshalled as a JSON string, panicing on any errors +func encodeJSON(obj interface{}) string { + data, err := json.Marshal(obj) + if err != nil { + panic(err) + } + return string(data) +} + +func readResp(resp *http.Response) (string, error) { + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + return string(body), err +} + +// A helper function to return the correct pod name. +func getPodName(name, namespace string) string { + if namespace == "" { + namespace = kubetypes.NamespaceDefault + } + return name + "_" + namespace +} + +func TestContainerInfo(t *testing.T) { + fw := newServerTest() + expectedInfo := &cadvisorapi.ContainerInfo{} + podID := "somepod" + expectedPodID := getPodName(podID, "") + expectedContainerName := "goodcontainer" + fw.fakeKubelet.containerInfoFunc = func(podID string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) { + if podID != expectedPodID || containerName != expectedContainerName { + return nil, fmt.Errorf("bad podID or containerName: podID=%v; containerName=%v", podID, containerName) + } + return expectedInfo, nil + } + + resp, err := http.Get(fw.testHTTPServer.URL + fmt.Sprintf("/stats/%v/%v", podID, expectedContainerName)) + if err != nil { + t.Fatalf("Got error GETing: %v", err) + } + defer resp.Body.Close() + var receivedInfo cadvisorapi.ContainerInfo + err = json.NewDecoder(resp.Body).Decode(&receivedInfo) + if err != nil { + t.Fatalf("received invalid json data: %v", err) + } + if !receivedInfo.Eq(expectedInfo) { + t.Errorf("received wrong data: %#v", receivedInfo) + } +} + +func TestContainerInfoWithUidNamespace(t *testing.T) { + fw := newServerTest() + expectedInfo := &cadvisorapi.ContainerInfo{} + podID := "somepod" + expectedNamespace := "custom" + expectedPodID := getPodName(podID, expectedNamespace) + expectedContainerName := "goodcontainer" + expectedUid := "9b01b80f-8fb4-11e4-95ab-4200af06647" + fw.fakeKubelet.containerInfoFunc = func(podID string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) { + if podID != expectedPodID || string(uid) != expectedUid || containerName != expectedContainerName { + return nil, fmt.Errorf("bad podID or uid or containerName: podID=%v; uid=%v; containerName=%v", podID, uid, containerName) + } + return expectedInfo, nil + } + + resp, err := http.Get(fw.testHTTPServer.URL + fmt.Sprintf("/stats/%v/%v/%v/%v", expectedNamespace, podID, expectedUid, expectedContainerName)) + if err != nil { + t.Fatalf("Got error GETing: %v", err) + } + defer resp.Body.Close() + var receivedInfo cadvisorapi.ContainerInfo + err = json.NewDecoder(resp.Body).Decode(&receivedInfo) + if err != nil { + t.Fatalf("received invalid json data: %v", err) + } + if !receivedInfo.Eq(expectedInfo) { + t.Errorf("received wrong data: %#v", receivedInfo) + } +} + +func TestContainerNotFound(t *testing.T) { + fw := newServerTest() + podID := "somepod" + expectedNamespace := "custom" + expectedContainerName := "slowstartcontainer" + expectedUid := "9b01b80f-8fb4-11e4-95ab-4200af06647" + fw.fakeKubelet.containerInfoFunc = func(podID string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) { + return nil, kubecontainer.ErrContainerNotFound + } + resp, err := http.Get(fw.testHTTPServer.URL + fmt.Sprintf("/stats/%v/%v/%v/%v", expectedNamespace, podID, expectedUid, expectedContainerName)) + if err != nil { + t.Fatalf("Got error GETing: %v", err) + } + if resp.StatusCode != http.StatusNotFound { + t.Fatalf("Received status %d expecting %d", resp.StatusCode, http.StatusNotFound) + } + defer resp.Body.Close() +} + +func TestRootInfo(t *testing.T) { + fw := newServerTest() + expectedInfo := &cadvisorapi.ContainerInfo{ + ContainerReference: cadvisorapi.ContainerReference{ + Name: "/", + }, + } + fw.fakeKubelet.rawInfoFunc = func(req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) { + return map[string]*cadvisorapi.ContainerInfo{ + expectedInfo.Name: expectedInfo, + }, nil + } + + resp, err := http.Get(fw.testHTTPServer.URL + "/stats") + if err != nil { + t.Fatalf("Got error GETing: %v", err) + } + defer resp.Body.Close() + var receivedInfo cadvisorapi.ContainerInfo + err = json.NewDecoder(resp.Body).Decode(&receivedInfo) + if err != nil { + t.Fatalf("received invalid json data: %v", err) + } + if !receivedInfo.Eq(expectedInfo) { + t.Errorf("received wrong data: %#v, expected %#v", receivedInfo, expectedInfo) + } +} + +func TestSubcontainerContainerInfo(t *testing.T) { + fw := newServerTest() + const kubeletContainer = "/kubelet" + const kubeletSubContainer = "/kubelet/sub" + expectedInfo := map[string]*cadvisorapi.ContainerInfo{ + kubeletContainer: { + ContainerReference: cadvisorapi.ContainerReference{ + Name: kubeletContainer, + }, + }, + kubeletSubContainer: { + ContainerReference: cadvisorapi.ContainerReference{ + Name: kubeletSubContainer, + }, + }, + } + fw.fakeKubelet.rawInfoFunc = func(req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) { + return expectedInfo, nil + } + + request := fmt.Sprintf("{\"containerName\":%q, \"subcontainers\": true}", kubeletContainer) + resp, err := http.Post(fw.testHTTPServer.URL+"/stats/container", "application/json", bytes.NewBuffer([]byte(request))) + if err != nil { + t.Fatalf("Got error GETing: %v", err) + } + defer resp.Body.Close() + var receivedInfo map[string]*cadvisorapi.ContainerInfo + err = json.NewDecoder(resp.Body).Decode(&receivedInfo) + if err != nil { + t.Fatalf("Received invalid json data: %v", err) + } + if len(receivedInfo) != len(expectedInfo) { + t.Errorf("Received wrong data: %#v, expected %#v", receivedInfo, expectedInfo) + } + + for _, containerName := range []string{kubeletContainer, kubeletSubContainer} { + if _, ok := receivedInfo[containerName]; !ok { + t.Errorf("Expected container %q to be present in result: %#v", containerName, receivedInfo) + } + if !receivedInfo[containerName].Eq(expectedInfo[containerName]) { + t.Errorf("Invalid result for %q: Expected %#v, received %#v", containerName, expectedInfo[containerName], receivedInfo[containerName]) + } + } +} + +func TestMachineInfo(t *testing.T) { + fw := newServerTest() + expectedInfo := &cadvisorapi.MachineInfo{ + NumCores: 4, + MemoryCapacity: 1024, + } + fw.fakeKubelet.machineInfoFunc = func() (*cadvisorapi.MachineInfo, error) { + return expectedInfo, nil + } + + resp, err := http.Get(fw.testHTTPServer.URL + "/spec") + if err != nil { + t.Fatalf("Got error GETing: %v", err) + } + defer resp.Body.Close() + var receivedInfo cadvisorapi.MachineInfo + err = json.NewDecoder(resp.Body).Decode(&receivedInfo) + if err != nil { + t.Fatalf("received invalid json data: %v", err) + } + if !reflect.DeepEqual(&receivedInfo, expectedInfo) { + t.Errorf("received wrong data: %#v", receivedInfo) + } +} + +func TestServeLogs(t *testing.T) { + fw := newServerTest() + + content := string(`
kubelet.loggoogle.log
`) + + fw.fakeKubelet.logFunc = func(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-Type", "text/html") + w.Write([]byte(content)) + } + + resp, err := http.Get(fw.testHTTPServer.URL + "/logs/") + if err != nil { + t.Fatalf("Got error GETing: %v", err) + } + defer resp.Body.Close() + + body, err := httputil.DumpResponse(resp, true) + if err != nil { + // copying the response body did not work + t.Errorf("Cannot copy resp: %#v", err) + } + result := string(body) + if !strings.Contains(result, "kubelet.log") || !strings.Contains(result, "google.log") { + t.Errorf("Received wrong data: %s", result) + } +} + +func TestServeRunInContainer(t *testing.T) { + fw := newServerTest() + output := "foo bar" + podNamespace := "other" + podName := "foo" + expectedPodName := getPodName(podName, podNamespace) + expectedContainerName := "baz" + expectedCommand := "ls -a" + fw.fakeKubelet.runFunc = func(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) { + if podFullName != expectedPodName { + t.Errorf("expected %s, got %s", expectedPodName, podFullName) + } + if containerName != expectedContainerName { + t.Errorf("expected %s, got %s", expectedContainerName, containerName) + } + if strings.Join(cmd, " ") != expectedCommand { + t.Errorf("expected: %s, got %v", expectedCommand, cmd) + } + + return []byte(output), nil + } + + resp, err := http.Post(fw.testHTTPServer.URL+"/run/"+podNamespace+"/"+podName+"/"+expectedContainerName+"?cmd=ls%20-a", "", nil) + + if err != nil { + t.Fatalf("Got error POSTing: %v", err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + // copying the response body did not work + t.Errorf("Cannot copy resp: %#v", err) + } + result := string(body) + if result != output { + t.Errorf("expected %s, got %s", output, result) + } +} + +func TestServeRunInContainerWithUID(t *testing.T) { + fw := newServerTest() + output := "foo bar" + podNamespace := "other" + podName := "foo" + expectedPodName := getPodName(podName, podNamespace) + expectedUID := "7e00838d_-_3523_-_11e4_-_8421_-_42010af0a720" + expectedContainerName := "baz" + expectedCommand := "ls -a" + fw.fakeKubelet.runFunc = func(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) { + if podFullName != expectedPodName { + t.Errorf("expected %s, got %s", expectedPodName, podFullName) + } + if string(uid) != expectedUID { + t.Errorf("expected %s, got %s", expectedUID, uid) + } + if containerName != expectedContainerName { + t.Errorf("expected %s, got %s", expectedContainerName, containerName) + } + if strings.Join(cmd, " ") != expectedCommand { + t.Errorf("expected: %s, got %v", expectedCommand, cmd) + } + + return []byte(output), nil + } + + resp, err := http.Post(fw.testHTTPServer.URL+"/run/"+podNamespace+"/"+podName+"/"+expectedUID+"/"+expectedContainerName+"?cmd=ls%20-a", "", nil) + + if err != nil { + t.Fatalf("Got error POSTing: %v", err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + // copying the response body did not work + t.Errorf("Cannot copy resp: %#v", err) + } + result := string(body) + if result != output { + t.Errorf("expected %s, got %s", output, result) + } +} + +func TestHealthCheck(t *testing.T) { + fw := newServerTest() + fw.fakeKubelet.hostnameFunc = func() string { + return "127.0.0.1" + } + + // Test with correct hostname, Docker version + assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz") + + // Test with incorrect hostname + fw.fakeKubelet.hostnameFunc = func() string { + return "fake" + } + assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz") +} + +func assertHealthFails(t *testing.T, httpURL string, expectedErrorCode int) { + resp, err := http.Get(httpURL) + if err != nil { + t.Fatalf("Got error GETing: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != expectedErrorCode { + t.Errorf("expected status code %d, got %d", expectedErrorCode, resp.StatusCode) + } +} + +type authTestCase struct { + Method string + Path string +} + +func TestAuthFilters(t *testing.T) { + fw := newServerTest() + + testcases := []authTestCase{} + + // This is a sanity check that the Handle->HandleWithFilter() delegation is working + // Ideally, these would move to registered web services and this list would get shorter + expectedPaths := []string{"/healthz", "/metrics"} + paths := sets.NewString(fw.serverUnderTest.restfulCont.RegisteredHandlePaths()...) + for _, expectedPath := range expectedPaths { + if !paths.Has(expectedPath) { + t.Errorf("Expected registered handle path %s was missing", expectedPath) + } + } + + // Test all the non-web-service handlers + for _, path := range fw.serverUnderTest.restfulCont.RegisteredHandlePaths() { + testcases = append(testcases, authTestCase{"GET", path}) + testcases = append(testcases, authTestCase{"POST", path}) + // Test subpaths for directory handlers + if strings.HasSuffix(path, "/") { + testcases = append(testcases, authTestCase{"GET", path + "foo"}) + testcases = append(testcases, authTestCase{"POST", path + "foo"}) + } + } + + // Test all the generated web-service paths + for _, ws := range fw.serverUnderTest.restfulCont.RegisteredWebServices() { + for _, r := range ws.Routes() { + testcases = append(testcases, authTestCase{r.Method, r.Path}) + } + } + + for _, tc := range testcases { + var ( + expectedUser = &user.DefaultInfo{Name: "test"} + expectedAttributes = &authorizer.AttributesRecord{User: expectedUser} + + calledAuthenticate = false + calledAuthorize = false + calledAttributes = false + ) + + fw.fakeAuth.authenticateFunc = func(req *http.Request) (user.Info, bool, error) { + calledAuthenticate = true + return expectedUser, true, nil + } + fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes { + calledAttributes = true + if u != expectedUser { + t.Fatalf("%s: expected user %v, got %v", tc.Path, expectedUser, u) + } + return expectedAttributes + } + fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (err error) { + calledAuthorize = true + if a != expectedAttributes { + t.Fatalf("%s: expected attributes %v, got %v", tc.Path, expectedAttributes, a) + } + return errors.New("Forbidden") + } + + req, err := http.NewRequest(tc.Method, fw.testHTTPServer.URL+tc.Path, nil) + if err != nil { + t.Errorf("%s: unexpected error: %v", tc.Path, err) + continue + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Errorf("%s: unexpected error: %v", tc.Path, err) + continue + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusForbidden { + t.Errorf("%s: unexpected status code %d", tc.Path, resp.StatusCode) + continue + } + + if !calledAuthenticate { + t.Errorf("%s: Authenticate was not called", tc.Path) + continue + } + if !calledAttributes { + t.Errorf("%s: Attributes were not called", tc.Path) + continue + } + if !calledAuthorize { + t.Errorf("%s: Authorize was not called", tc.Path) + continue + } + } +} + +func TestAuthenticationFailure(t *testing.T) { + var ( + expectedUser = &user.DefaultInfo{Name: "test"} + expectedAttributes = &authorizer.AttributesRecord{User: expectedUser} + + calledAuthenticate = false + calledAuthorize = false + calledAttributes = false + ) + + fw := newServerTest() + fw.fakeAuth.authenticateFunc = func(req *http.Request) (user.Info, bool, error) { + calledAuthenticate = true + return nil, false, nil + } + fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes { + calledAttributes = true + return expectedAttributes + } + fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (err error) { + calledAuthorize = true + return errors.New("not allowed") + } + + assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusUnauthorized) + + if !calledAuthenticate { + t.Fatalf("Authenticate was not called") + } + if calledAttributes { + t.Fatalf("Attributes was called unexpectedly") + } + if calledAuthorize { + t.Fatalf("Authorize was called unexpectedly") + } +} + +func TestAuthorizationSuccess(t *testing.T) { + var ( + expectedUser = &user.DefaultInfo{Name: "test"} + expectedAttributes = &authorizer.AttributesRecord{User: expectedUser} + + calledAuthenticate = false + calledAuthorize = false + calledAttributes = false + ) + + fw := newServerTest() + fw.fakeAuth.authenticateFunc = func(req *http.Request) (user.Info, bool, error) { + calledAuthenticate = true + return expectedUser, true, nil + } + fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes { + calledAttributes = true + return expectedAttributes + } + fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (err error) { + calledAuthorize = true + return nil + } + + assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz") + + if !calledAuthenticate { + t.Fatalf("Authenticate was not called") + } + if !calledAttributes { + t.Fatalf("Attributes were not called") + } + if !calledAuthorize { + t.Fatalf("Authorize was not called") + } +} + +func TestSyncLoopCheck(t *testing.T) { + fw := newServerTest() + fw.fakeKubelet.hostnameFunc = func() string { + return "127.0.0.1" + } + + fw.fakeKubelet.resyncInterval = time.Minute + fw.fakeKubelet.loopEntryTime = time.Now() + + // Test with correct hostname, Docker version + assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz") + + fw.fakeKubelet.loopEntryTime = time.Now().Add(time.Minute * -10) + assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusInternalServerError) +} + +func TestPLEGHealthCheck(t *testing.T) { + fw := newServerTest() + fw.fakeKubelet.hostnameFunc = func() string { + return "127.0.0.1" + } + + // Test with failed pleg health check. + fw.fakeKubelet.plegHealth = false + assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusInternalServerError) +} + +// returns http response status code from the HTTP GET +func assertHealthIsOk(t *testing.T, httpURL string) { + resp, err := http.Get(httpURL) + if err != nil { + t.Fatalf("Got error GETing: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + t.Errorf("expected status code %d, got %d", http.StatusOK, resp.StatusCode) + } + body, readErr := ioutil.ReadAll(resp.Body) + if readErr != nil { + // copying the response body did not work + t.Fatalf("Cannot copy resp: %#v", readErr) + } + result := string(body) + if !strings.Contains(result, "ok") { + t.Errorf("expected body contains ok, got %s", result) + } +} + +func setPodByNameFunc(fw *serverTestFramework, namespace, pod, container string) { + fw.fakeKubelet.podByNameFunc = func(namespace, name string) (*api.Pod, bool) { + return &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Namespace: namespace, + Name: pod, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: container, + }, + }, + }, + }, true + } +} + +func setGetContainerLogsFunc(fw *serverTestFramework, t *testing.T, expectedPodName, expectedContainerName string, expectedLogOptions *api.PodLogOptions, output string) { + fw.fakeKubelet.containerLogsFunc = func(podFullName, containerName string, logOptions *api.PodLogOptions, stdout, stderr io.Writer) error { + if podFullName != expectedPodName { + t.Errorf("expected %s, got %s", expectedPodName, podFullName) + } + if containerName != expectedContainerName { + t.Errorf("expected %s, got %s", expectedContainerName, containerName) + } + if !reflect.DeepEqual(expectedLogOptions, logOptions) { + t.Errorf("expected %#v, got %#v", expectedLogOptions, logOptions) + } + + io.WriteString(stdout, output) + return nil + } +} + +// TODO: I really want to be a table driven test +func TestContainerLogs(t *testing.T) { + fw := newServerTest() + output := "foo bar" + podNamespace := "other" + podName := "foo" + expectedPodName := getPodName(podName, podNamespace) + expectedContainerName := "baz" + setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) + setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &api.PodLogOptions{}, output) + resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName) + if err != nil { + t.Errorf("Got error GETing: %v", err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading container logs: %v", err) + } + result := string(body) + if result != output { + t.Errorf("Expected: '%v', got: '%v'", output, result) + } +} + +func TestContainerLogsWithLimitBytes(t *testing.T) { + fw := newServerTest() + output := "foo bar" + podNamespace := "other" + podName := "foo" + expectedPodName := getPodName(podName, podNamespace) + expectedContainerName := "baz" + bytes := int64(3) + setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) + setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &api.PodLogOptions{LimitBytes: &bytes}, output) + resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?limitBytes=3") + if err != nil { + t.Errorf("Got error GETing: %v", err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading container logs: %v", err) + } + result := string(body) + if result != output[:bytes] { + t.Errorf("Expected: '%v', got: '%v'", output[:bytes], result) + } +} + +func TestContainerLogsWithTail(t *testing.T) { + fw := newServerTest() + output := "foo bar" + podNamespace := "other" + podName := "foo" + expectedPodName := getPodName(podName, podNamespace) + expectedContainerName := "baz" + expectedTail := int64(5) + setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) + setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &api.PodLogOptions{TailLines: &expectedTail}, output) + resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?tailLines=5") + if err != nil { + t.Errorf("Got error GETing: %v", err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading container logs: %v", err) + } + result := string(body) + if result != output { + t.Errorf("Expected: '%v', got: '%v'", output, result) + } +} + +func TestContainerLogsWithLegacyTail(t *testing.T) { + fw := newServerTest() + output := "foo bar" + podNamespace := "other" + podName := "foo" + expectedPodName := getPodName(podName, podNamespace) + expectedContainerName := "baz" + expectedTail := int64(5) + setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) + setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &api.PodLogOptions{TailLines: &expectedTail}, output) + resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?tail=5") + if err != nil { + t.Errorf("Got error GETing: %v", err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading container logs: %v", err) + } + result := string(body) + if result != output { + t.Errorf("Expected: '%v', got: '%v'", output, result) + } +} + +func TestContainerLogsWithTailAll(t *testing.T) { + fw := newServerTest() + output := "foo bar" + podNamespace := "other" + podName := "foo" + expectedPodName := getPodName(podName, podNamespace) + expectedContainerName := "baz" + setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) + setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &api.PodLogOptions{}, output) + resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?tail=all") + if err != nil { + t.Errorf("Got error GETing: %v", err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading container logs: %v", err) + } + result := string(body) + if result != output { + t.Errorf("Expected: '%v', got: '%v'", output, result) + } +} + +func TestContainerLogsWithInvalidTail(t *testing.T) { + fw := newServerTest() + output := "foo bar" + podNamespace := "other" + podName := "foo" + expectedPodName := getPodName(podName, podNamespace) + expectedContainerName := "baz" + setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) + setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &api.PodLogOptions{}, output) + resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?tail=-1") + if err != nil { + t.Errorf("Got error GETing: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != apierrs.StatusUnprocessableEntity { + t.Errorf("Unexpected non-error reading container logs: %#v", resp) + } +} + +func TestContainerLogsWithFollow(t *testing.T) { + fw := newServerTest() + output := "foo bar" + podNamespace := "other" + podName := "foo" + expectedPodName := getPodName(podName, podNamespace) + expectedContainerName := "baz" + setPodByNameFunc(fw, podNamespace, podName, expectedContainerName) + setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &api.PodLogOptions{Follow: true}, output) + resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?follow=1") + if err != nil { + t.Errorf("Got error GETing: %v", err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Error reading container logs: %v", err) + } + result := string(body) + if result != output { + t.Errorf("Expected: '%v', got: '%v'", output, result) + } +} + +func TestServeExecInContainerIdleTimeout(t *testing.T) { + fw := newServerTest() + + fw.fakeKubelet.streamingConnectionIdleTimeoutFunc = func() time.Duration { + return 100 * time.Millisecond + } + + podNamespace := "other" + podName := "foo" + expectedContainerName := "baz" + + url := fw.testHTTPServer.URL + "/exec/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?c=ls&c=-a&" + api.ExecStdinParam + "=1" + + upgradeRoundTripper := spdy.NewSpdyRoundTripper(nil) + c := &http.Client{Transport: upgradeRoundTripper} + + resp, err := c.Post(url, "", nil) + if err != nil { + t.Fatalf("Got error POSTing: %v", err) + } + defer resp.Body.Close() + + upgradeRoundTripper.Dialer = &net.Dialer{ + Deadline: time.Now().Add(60 * time.Second), + Timeout: 60 * time.Second, + } + conn, err := upgradeRoundTripper.NewConnection(resp) + if err != nil { + t.Fatalf("Unexpected error creating streaming connection: %s", err) + } + if conn == nil { + t.Fatal("Unexpected nil connection") + } + + <-conn.CloseChan() +} + +func testExecAttach(t *testing.T, verb string) { + tests := []struct { + stdin bool + stdout bool + stderr bool + tty bool + responseStatusCode int + uid bool + }{ + {responseStatusCode: http.StatusBadRequest}, + {stdin: true, responseStatusCode: http.StatusSwitchingProtocols}, + {stdout: true, responseStatusCode: http.StatusSwitchingProtocols}, + {stderr: true, responseStatusCode: http.StatusSwitchingProtocols}, + {stdout: true, stderr: true, responseStatusCode: http.StatusSwitchingProtocols}, + {stdout: true, stderr: true, tty: true, responseStatusCode: http.StatusSwitchingProtocols}, + {stdin: true, stdout: true, stderr: true, responseStatusCode: http.StatusSwitchingProtocols}, + } + + for i, test := range tests { + fw := newServerTest() + + fw.fakeKubelet.streamingConnectionIdleTimeoutFunc = func() time.Duration { + return 0 + } + + podNamespace := "other" + podName := "foo" + expectedPodName := getPodName(podName, podNamespace) + expectedUid := "9b01b80f-8fb4-11e4-95ab-4200af06647" + expectedContainerName := "baz" + expectedCommand := "ls -a" + expectedStdin := "stdin" + expectedStdout := "stdout" + expectedStderr := "stderr" + done := make(chan struct{}) + clientStdoutReadDone := make(chan struct{}) + clientStderrReadDone := make(chan struct{}) + execInvoked := false + attachInvoked := false + + testStreamFunc := func(podFullName string, uid types.UID, containerName string, cmd []string, in io.Reader, out, stderr io.WriteCloser, tty bool, done chan struct{}) error { + defer close(done) + + if podFullName != expectedPodName { + t.Fatalf("%d: podFullName: expected %s, got %s", i, expectedPodName, podFullName) + } + if test.uid && string(uid) != expectedUid { + t.Fatalf("%d: uid: expected %v, got %v", i, expectedUid, uid) + } + if containerName != expectedContainerName { + t.Fatalf("%d: containerName: expected %s, got %s", i, expectedContainerName, containerName) + } + + if test.stdin { + if in == nil { + t.Fatalf("%d: stdin: expected non-nil", i) + } + b := make([]byte, 10) + n, err := in.Read(b) + if err != nil { + t.Fatalf("%d: error reading from stdin: %v", i, err) + } + if e, a := expectedStdin, string(b[0:n]); e != a { + t.Fatalf("%d: stdin: expected to read %v, got %v", i, e, a) + } + } else if in != nil { + t.Fatalf("%d: stdin: expected nil: %#v", i, in) + } + + if test.stdout { + if out == nil { + t.Fatalf("%d: stdout: expected non-nil", i) + } + _, err := out.Write([]byte(expectedStdout)) + if err != nil { + t.Fatalf("%d:, error writing to stdout: %v", i, err) + } + out.Close() + <-clientStdoutReadDone + } else if out != nil { + t.Fatalf("%d: stdout: expected nil: %#v", i, out) + } + + if tty { + if stderr != nil { + t.Fatalf("%d: tty set but received non-nil stderr: %v", i, stderr) + } + } else if test.stderr { + if stderr == nil { + t.Fatalf("%d: stderr: expected non-nil", i) + } + _, err := stderr.Write([]byte(expectedStderr)) + if err != nil { + t.Fatalf("%d:, error writing to stderr: %v", i, err) + } + stderr.Close() + <-clientStderrReadDone + } else if stderr != nil { + t.Fatalf("%d: stderr: expected nil: %#v", i, stderr) + } + + return nil + } + + fw.fakeKubelet.execFunc = func(podFullName string, uid types.UID, containerName string, cmd []string, in io.Reader, out, stderr io.WriteCloser, tty bool) error { + execInvoked = true + if strings.Join(cmd, " ") != expectedCommand { + t.Fatalf("%d: cmd: expected: %s, got %v", i, expectedCommand, cmd) + } + return testStreamFunc(podFullName, uid, containerName, cmd, in, out, stderr, tty, done) + } + + fw.fakeKubelet.attachFunc = func(podFullName string, uid types.UID, containerName string, in io.Reader, out, stderr io.WriteCloser, tty bool) error { + attachInvoked = true + return testStreamFunc(podFullName, uid, containerName, nil, in, out, stderr, tty, done) + } + + var url string + if test.uid { + url = fw.testHTTPServer.URL + "/" + verb + "/" + podNamespace + "/" + podName + "/" + expectedUid + "/" + expectedContainerName + "?ignore=1" + } else { + url = fw.testHTTPServer.URL + "/" + verb + "/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?ignore=1" + } + if verb == "exec" { + url += "&command=ls&command=-a" + } + if test.stdin { + url += "&" + api.ExecStdinParam + "=1" + } + if test.stdout { + url += "&" + api.ExecStdoutParam + "=1" + } + if test.stderr && !test.tty { + url += "&" + api.ExecStderrParam + "=1" + } + if test.tty { + url += "&" + api.ExecTTYParam + "=1" + } + + var ( + resp *http.Response + err error + upgradeRoundTripper httpstream.UpgradeRoundTripper + c *http.Client + ) + + if test.responseStatusCode != http.StatusSwitchingProtocols { + c = &http.Client{} + } else { + upgradeRoundTripper = spdy.NewRoundTripper(nil) + c = &http.Client{Transport: upgradeRoundTripper} + } + + resp, err = c.Post(url, "", nil) + if err != nil { + t.Fatalf("%d: Got error POSTing: %v", i, err) + } + defer resp.Body.Close() + + _, err = ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("%d: Error reading response body: %v", i, err) + } + + if e, a := test.responseStatusCode, resp.StatusCode; e != a { + t.Fatalf("%d: response status: expected %v, got %v", i, e, a) + } + + if test.responseStatusCode != http.StatusSwitchingProtocols { + continue + } + + conn, err := upgradeRoundTripper.NewConnection(resp) + if err != nil { + t.Fatalf("Unexpected error creating streaming connection: %s", err) + } + if conn == nil { + t.Fatalf("%d: unexpected nil conn", i) + } + defer conn.Close() + + h := http.Header{} + h.Set(api.StreamType, api.StreamTypeError) + if _, err := conn.CreateStream(h); err != nil { + t.Fatalf("%d: error creating error stream: %v", i, err) + } + + if test.stdin { + h.Set(api.StreamType, api.StreamTypeStdin) + stream, err := conn.CreateStream(h) + if err != nil { + t.Fatalf("%d: error creating stdin stream: %v", i, err) + } + _, err = stream.Write([]byte(expectedStdin)) + if err != nil { + t.Fatalf("%d: error writing to stdin stream: %v", i, err) + } + } + + var stdoutStream httpstream.Stream + if test.stdout { + h.Set(api.StreamType, api.StreamTypeStdout) + stdoutStream, err = conn.CreateStream(h) + if err != nil { + t.Fatalf("%d: error creating stdout stream: %v", i, err) + } + } + + var stderrStream httpstream.Stream + if test.stderr && !test.tty { + h.Set(api.StreamType, api.StreamTypeStderr) + stderrStream, err = conn.CreateStream(h) + if err != nil { + t.Fatalf("%d: error creating stderr stream: %v", i, err) + } + } + + if test.stdout { + output := make([]byte, 10) + n, err := stdoutStream.Read(output) + close(clientStdoutReadDone) + if err != nil { + t.Fatalf("%d: error reading from stdout stream: %v", i, err) + } + if e, a := expectedStdout, string(output[0:n]); e != a { + t.Fatalf("%d: stdout: expected '%v', got '%v'", i, e, a) + } + } + + if test.stderr && !test.tty { + output := make([]byte, 10) + n, err := stderrStream.Read(output) + close(clientStderrReadDone) + if err != nil { + t.Fatalf("%d: error reading from stderr stream: %v", i, err) + } + if e, a := expectedStderr, string(output[0:n]); e != a { + t.Fatalf("%d: stderr: expected '%v', got '%v'", i, e, a) + } + } + + // wait for the server to finish before checking if the attach/exec funcs were invoked + <-done + + if verb == "exec" { + if !execInvoked { + t.Errorf("%d: exec was not invoked", i) + } + if attachInvoked { + t.Errorf("%d: attach should not have been invoked", i) + } + } else { + if !attachInvoked { + t.Errorf("%d: attach was not invoked", i) + } + if execInvoked { + t.Errorf("%d: exec should not have been invoked", i) + } + } + } +} + +func TestServeExecInContainer(t *testing.T) { + testExecAttach(t, "exec") +} + +func TestServeAttachContainer(t *testing.T) { + testExecAttach(t, "attach") +} + +func TestServePortForwardIdleTimeout(t *testing.T) { + fw := newServerTest() + + fw.fakeKubelet.streamingConnectionIdleTimeoutFunc = func() time.Duration { + return 100 * time.Millisecond + } + + podNamespace := "other" + podName := "foo" + + url := fw.testHTTPServer.URL + "/portForward/" + podNamespace + "/" + podName + + upgradeRoundTripper := spdy.NewRoundTripper(nil) + c := &http.Client{Transport: upgradeRoundTripper} + + resp, err := c.Post(url, "", nil) + if err != nil { + t.Fatalf("Got error POSTing: %v", err) + } + defer resp.Body.Close() + + conn, err := upgradeRoundTripper.NewConnection(resp) + if err != nil { + t.Fatalf("Unexpected error creating streaming connection: %s", err) + } + if conn == nil { + t.Fatal("Unexpected nil connection") + } + defer conn.Close() + + <-conn.CloseChan() +} + +func TestServePortForward(t *testing.T) { + tests := []struct { + port string + uid bool + clientData string + containerData string + shouldError bool + }{ + {port: "", shouldError: true}, + {port: "abc", shouldError: true}, + {port: "-1", shouldError: true}, + {port: "65536", shouldError: true}, + {port: "0", shouldError: true}, + {port: "1", shouldError: false}, + {port: "8000", shouldError: false}, + {port: "8000", clientData: "client data", containerData: "container data", shouldError: false}, + {port: "65535", shouldError: false}, + {port: "65535", uid: true, shouldError: false}, + } + + podNamespace := "other" + podName := "foo" + expectedPodName := getPodName(podName, podNamespace) + expectedUid := "9b01b80f-8fb4-11e4-95ab-4200af06647" + + for i, test := range tests { + fw := newServerTest() + + fw.fakeKubelet.streamingConnectionIdleTimeoutFunc = func() time.Duration { + return 0 + } + + portForwardFuncDone := make(chan struct{}) + + fw.fakeKubelet.portForwardFunc = func(name string, uid types.UID, port uint16, stream io.ReadWriteCloser) error { + defer close(portForwardFuncDone) + + if e, a := expectedPodName, name; e != a { + t.Fatalf("%d: pod name: expected '%v', got '%v'", i, e, a) + } + + if e, a := expectedUid, uid; test.uid && e != string(a) { + t.Fatalf("%d: uid: expected '%v', got '%v'", i, e, a) + } + + p, err := strconv.ParseUint(test.port, 10, 16) + if err != nil { + t.Fatalf("%d: error parsing port string '%s': %v", i, test.port, err) + } + if e, a := uint16(p), port; e != a { + t.Fatalf("%d: port: expected '%v', got '%v'", i, e, a) + } + + if test.clientData != "" { + fromClient := make([]byte, 32) + n, err := stream.Read(fromClient) + if err != nil { + t.Fatalf("%d: error reading client data: %v", i, err) + } + if e, a := test.clientData, string(fromClient[0:n]); e != a { + t.Fatalf("%d: client data: expected to receive '%v', got '%v'", i, e, a) + } + } + + if test.containerData != "" { + _, err := stream.Write([]byte(test.containerData)) + if err != nil { + t.Fatalf("%d: error writing container data: %v", i, err) + } + } + + return nil + } + + var url string + if test.uid { + url = fmt.Sprintf("%s/portForward/%s/%s/%s", fw.testHTTPServer.URL, podNamespace, podName, expectedUid) + } else { + url = fmt.Sprintf("%s/portForward/%s/%s", fw.testHTTPServer.URL, podNamespace, podName) + } + + upgradeRoundTripper := spdy.NewRoundTripper(nil) + c := &http.Client{Transport: upgradeRoundTripper} + + resp, err := c.Post(url, "", nil) + if err != nil { + t.Fatalf("%d: Got error POSTing: %v", i, err) + } + defer resp.Body.Close() + + conn, err := upgradeRoundTripper.NewConnection(resp) + if err != nil { + t.Fatalf("Unexpected error creating streaming connection: %s", err) + } + if conn == nil { + t.Fatalf("%d: Unexpected nil connection", i) + } + defer conn.Close() + + headers := http.Header{} + headers.Set("streamType", "error") + headers.Set("port", test.port) + errorStream, err := conn.CreateStream(headers) + _ = errorStream + haveErr := err != nil + if e, a := test.shouldError, haveErr; e != a { + t.Fatalf("%d: create stream: expected err=%t, got %t: %v", i, e, a, err) + } + + if test.shouldError { + continue + } + + headers.Set("streamType", "data") + headers.Set("port", test.port) + dataStream, err := conn.CreateStream(headers) + haveErr = err != nil + if e, a := test.shouldError, haveErr; e != a { + t.Fatalf("%d: create stream: expected err=%t, got %t: %v", i, e, a, err) + } + + if test.clientData != "" { + _, err := dataStream.Write([]byte(test.clientData)) + if err != nil { + t.Fatalf("%d: unexpected error writing client data: %v", i, err) + } + } + + if test.containerData != "" { + fromContainer := make([]byte, 32) + n, err := dataStream.Read(fromContainer) + if err != nil { + t.Fatalf("%d: unexpected error reading container data: %v", i, err) + } + if e, a := test.containerData, string(fromContainer[0:n]); e != a { + t.Fatalf("%d: expected to receive '%v' from container, got '%v'", i, e, a) + } + } + + <-portForwardFuncDone + } +} + +type fakeHttpStream struct { + headers http.Header + id uint32 +} + +func newFakeHttpStream() *fakeHttpStream { + return &fakeHttpStream{ + headers: make(http.Header), + } +} + +var _ httpstream.Stream = &fakeHttpStream{} + +func (s *fakeHttpStream) Read(data []byte) (int, error) { + return 0, nil +} + +func (s *fakeHttpStream) Write(data []byte) (int, error) { + return 0, nil +} + +func (s *fakeHttpStream) Close() error { + return nil +} + +func (s *fakeHttpStream) Reset() error { + return nil +} + +func (s *fakeHttpStream) Headers() http.Header { + return s.headers +} + +func (s *fakeHttpStream) Identifier() uint32 { + return s.id +} + +func TestPortForwardStreamReceived(t *testing.T) { + tests := map[string]struct { + port string + streamType string + expectedError string + }{ + "missing port": { + expectedError: `"port" header is required`, + }, + "unable to parse port": { + port: "abc", + expectedError: `unable to parse "abc" as a port: strconv.ParseUint: parsing "abc": invalid syntax`, + }, + "negative port": { + port: "-1", + expectedError: `unable to parse "-1" as a port: strconv.ParseUint: parsing "-1": invalid syntax`, + }, + "missing stream type": { + port: "80", + expectedError: `"streamType" header is required`, + }, + "valid port with error stream": { + port: "80", + streamType: "error", + }, + "valid port with data stream": { + port: "80", + streamType: "data", + }, + "invalid stream type": { + port: "80", + streamType: "foo", + expectedError: `invalid stream type "foo"`, + }, + } + for name, test := range tests { + streams := make(chan httpstream.Stream, 1) + f := portForwardStreamReceived(streams) + stream := newFakeHttpStream() + if len(test.port) > 0 { + stream.headers.Set("port", test.port) + } + if len(test.streamType) > 0 { + stream.headers.Set("streamType", test.streamType) + } + replySent := make(chan struct{}) + err := f(stream, replySent) + close(replySent) + if len(test.expectedError) > 0 { + if err == nil { + t.Errorf("%s: expected err=%q, but it was nil", name, test.expectedError) + } + if e, a := test.expectedError, err.Error(); e != a { + t.Errorf("%s: expected err=%q, got %q", name, e, a) + } + continue + } + if err != nil { + t.Errorf("%s: unexpected error %v", name, err) + continue + } + if s := <-streams; s != stream { + t.Errorf("%s: expected stream %#v, got %#v", name, stream, s) + } + } +} + +func TestGetStreamPair(t *testing.T) { + timeout := make(chan time.Time) + + h := &portForwardStreamHandler{ + streamPairs: make(map[string]*portForwardStreamPair), + } + + // test adding a new entry + p, created := h.getStreamPair("1") + if p == nil { + t.Fatalf("unexpected nil pair") + } + if !created { + t.Fatal("expected created=true") + } + if p.dataStream != nil { + t.Errorf("unexpected non-nil data stream") + } + if p.errorStream != nil { + t.Errorf("unexpected non-nil error stream") + } + + // start the monitor for this pair + monitorDone := make(chan struct{}) + go func() { + h.monitorStreamPair(p, timeout) + close(monitorDone) + }() + + if !h.hasStreamPair("1") { + t.Fatal("This should still be true") + } + + // make sure we can retrieve an existing entry + p2, created := h.getStreamPair("1") + if created { + t.Fatal("expected created=false") + } + if p != p2 { + t.Fatalf("retrieving an existing pair: expected %#v, got %#v", p, p2) + } + + // removed via complete + dataStream := newFakeHttpStream() + dataStream.headers.Set(api.StreamType, api.StreamTypeData) + complete, err := p.add(dataStream) + if err != nil { + t.Fatalf("unexpected error adding data stream to pair: %v", err) + } + if complete { + t.Fatalf("unexpected complete") + } + + errorStream := newFakeHttpStream() + errorStream.headers.Set(api.StreamType, api.StreamTypeError) + complete, err = p.add(errorStream) + if err != nil { + t.Fatalf("unexpected error adding error stream to pair: %v", err) + } + if !complete { + t.Fatal("unexpected incomplete") + } + + // make sure monitorStreamPair completed + <-monitorDone + + // make sure the pair was removed + if h.hasStreamPair("1") { + t.Fatal("expected removal of pair after both data and error streams received") + } + + // removed via timeout + p, created = h.getStreamPair("2") + if !created { + t.Fatal("expected created=true") + } + if p == nil { + t.Fatal("expected p not to be nil") + } + monitorDone = make(chan struct{}) + go func() { + h.monitorStreamPair(p, timeout) + close(monitorDone) + }() + // cause the timeout + close(timeout) + // make sure monitorStreamPair completed + <-monitorDone + if h.hasStreamPair("2") { + t.Fatal("expected stream pair to be removed") + } +} + +func TestRequestID(t *testing.T) { + h := &portForwardStreamHandler{} + + s := newFakeHttpStream() + s.headers.Set(api.StreamType, api.StreamTypeError) + s.id = 1 + if e, a := "1", h.requestID(s); e != a { + t.Errorf("expected %q, got %q", e, a) + } + + s.headers.Set(api.StreamType, api.StreamTypeData) + s.id = 3 + if e, a := "1", h.requestID(s); e != a { + t.Errorf("expected %q, got %q", e, a) + } + + s.id = 7 + s.headers.Set(api.PortForwardRequestIDHeader, "2") + if e, a := "2", h.requestID(s); e != a { + t.Errorf("expected %q, got %q", e, a) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/doc.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/doc.go new file mode 100644 index 000000000..289fdae70 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 stats handles exporting Kubelet and container stats. +// NOTE: We intend to move this functionality into a standalone pod, so this package should be very +// loosely coupled to the rest of the Kubelet. +package stats diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/fs_resource_analyzer.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/fs_resource_analyzer.go new file mode 100644 index 000000000..c45e34694 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/fs_resource_analyzer.go @@ -0,0 +1,107 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 stats + +import ( + "sync" + "sync/atomic" + "time" + + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/wait" + + "github.com/golang/glog" +) + +// Map to PodVolumeStats pointers since the addresses for map values are not constant and can cause pain +// if we need ever to get a pointer to one of the values (e.g. you can't) +type Cache map[types.UID]*volumeStatCalculator + +// fsResourceAnalyzerInterface is for embedding fs functions into ResourceAnalyzer +type fsResourceAnalyzerInterface interface { + GetPodVolumeStats(uid types.UID) (PodVolumeStats, bool) +} + +// diskResourceAnalyzer provider stats about fs resource usage +type fsResourceAnalyzer struct { + statsProvider StatsProvider + calcPeriod time.Duration + cachedVolumeStats atomic.Value + startOnce sync.Once +} + +var _ fsResourceAnalyzerInterface = &fsResourceAnalyzer{} + +// newFsResourceAnalyzer returns a new fsResourceAnalyzer implementation +func newFsResourceAnalyzer(statsProvider StatsProvider, calcVolumePeriod time.Duration) *fsResourceAnalyzer { + r := &fsResourceAnalyzer{ + statsProvider: statsProvider, + calcPeriod: calcVolumePeriod, + } + r.cachedVolumeStats.Store(make(Cache)) + return r +} + +// Start eager background caching of volume stats. +func (s *fsResourceAnalyzer) Start() { + s.startOnce.Do(func() { + if s.calcPeriod <= 0 { + glog.Info("Volume stats collection disabled.") + return + } + glog.Info("Starting FS ResourceAnalyzer") + go wait.Forever(func() { s.updateCachedPodVolumeStats() }, s.calcPeriod) + }) +} + +// updateCachedPodVolumeStats calculates and caches the PodVolumeStats for every Pod known to the kubelet. +func (s *fsResourceAnalyzer) updateCachedPodVolumeStats() { + oldCache := s.cachedVolumeStats.Load().(Cache) + newCache := make(Cache) + + // Copy existing entries to new map, creating/starting new entries for pods missing from the cache + for _, pod := range s.statsProvider.GetPods() { + if value, found := oldCache[pod.GetUID()]; !found { + newCache[pod.GetUID()] = newVolumeStatCalculator(s.statsProvider, s.calcPeriod, pod).StartOnce() + } else { + newCache[pod.GetUID()] = value + } + } + + // Stop entries for pods that have been deleted + for uid, entry := range oldCache { + if _, found := newCache[uid]; !found { + entry.StopOnce() + } + } + + // Update the cache reference + s.cachedVolumeStats.Store(newCache) +} + +// GetPodVolumeStats returns the PodVolumeStats for a given pod. Results are looked up from a cache that +// is eagerly populated in the background, and never calculated on the fly. +func (s *fsResourceAnalyzer) GetPodVolumeStats(uid types.UID) (PodVolumeStats, bool) { + cache := s.cachedVolumeStats.Load().(Cache) + if statCalc, found := cache[uid]; !found { + // TODO: Differentiate between stats being empty + // See issue #20679 + return PodVolumeStats{}, false + } else { + return statCalc.GetLatest() + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/handler.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/handler.go new file mode 100644 index 000000000..c253ae295 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/handler.go @@ -0,0 +1,238 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 stats + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "path" + "time" + + "github.com/emicklei/go-restful" + "github.com/golang/glog" + cadvisorapi "github.com/google/cadvisor/info/v1" + cadvisorapiv2 "github.com/google/cadvisor/info/v2" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/kubelet/cm" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/volume" +) + +// Host methods required by stats handlers. +type StatsProvider interface { + GetContainerInfo(podFullName string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) + GetContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) + GetRawContainerInfo(containerName string, req *cadvisorapi.ContainerInfoRequest, subcontainers bool) (map[string]*cadvisorapi.ContainerInfo, error) + GetPodByName(namespace, name string) (*api.Pod, bool) + GetNode() (*api.Node, error) + GetNodeConfig() cm.NodeConfig + DockerImagesFsInfo() (cadvisorapiv2.FsInfo, error) + RootFsInfo() (cadvisorapiv2.FsInfo, error) + ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool) + GetPods() []*api.Pod +} + +type handler struct { + provider StatsProvider + summaryProvider SummaryProvider +} + +func CreateHandlers(provider StatsProvider, resourceAnalyzer ResourceAnalyzer) *restful.WebService { + h := &handler{provider, NewSummaryProvider(provider, resourceAnalyzer)} + + ws := &restful.WebService{} + ws.Path("/stats/"). + Produces(restful.MIME_JSON) + + endpoints := []struct { + path string + handler restful.RouteFunction + }{ + {"", h.handleStats}, + {"/summary", h.handleSummary}, + {"/container", h.handleSystemContainer}, + {"/{podName}/{containerName}", h.handlePodContainer}, + {"/{namespace}/{podName}/{uid}/{containerName}", h.handlePodContainer}, + } + + for _, e := range endpoints { + for _, method := range []string{"GET", "POST"} { + ws.Route(ws. + Method(method). + Path(e.path). + To(e.handler)) + } + } + + return ws +} + +type StatsRequest struct { + // The name of the container for which to request stats. + // Default: / + ContainerName string `json:"containerName,omitempty"` + + // Max number of stats to return. + // If start and end time are specified this limit is ignored. + // Default: 60 + NumStats int `json:"num_stats,omitempty"` + + // Start time for which to query information. + // If omitted, the beginning of time is assumed. + Start time.Time `json:"start,omitempty"` + + // End time for which to query information. + // If omitted, current time is assumed. + End time.Time `json:"end,omitempty"` + + // Whether to also include information from subcontainers. + // Default: false. + Subcontainers bool `json:"subcontainers,omitempty"` +} + +func (r *StatsRequest) cadvisorRequest() *cadvisorapi.ContainerInfoRequest { + return &cadvisorapi.ContainerInfoRequest{ + NumStats: r.NumStats, + Start: r.Start, + End: r.End, + } +} + +func parseStatsRequest(request *restful.Request) (StatsRequest, error) { + // Default request. + query := StatsRequest{ + NumStats: 60, + } + + err := json.NewDecoder(request.Request.Body).Decode(&query) + if err != nil && err != io.EOF { + return query, err + } + return query, nil +} + +// Handles root container stats requests to /stats +func (h *handler) handleStats(request *restful.Request, response *restful.Response) { + query, err := parseStatsRequest(request) + if err != nil { + handleError(response, err) + return + } + + // Root container stats. + statsMap, err := h.provider.GetRawContainerInfo("/", query.cadvisorRequest(), false) + if err != nil { + handleError(response, err) + return + } + writeResponse(response, statsMap["/"]) +} + +// Handles stats summary requests to /stats/summary +func (h *handler) handleSummary(request *restful.Request, response *restful.Response) { + summary, err := h.summaryProvider.Get() + if err != nil { + handleError(response, err) + } else { + writeResponse(response, summary) + } +} + +// Handles non-kubernetes container stats requests to /stats/container/ +func (h *handler) handleSystemContainer(request *restful.Request, response *restful.Response) { + query, err := parseStatsRequest(request) + if err != nil { + handleError(response, err) + return + } + + // Non-Kubernetes container stats. + containerName := path.Join("/", query.ContainerName) + stats, err := h.provider.GetRawContainerInfo( + containerName, query.cadvisorRequest(), query.Subcontainers) + if err != nil { + handleError(response, err) + return + } + writeResponse(response, stats) +} + +// Handles kubernetes pod/container stats requests to: +// /stats// +// /stats//// +func (h *handler) handlePodContainer(request *restful.Request, response *restful.Response) { + query, err := parseStatsRequest(request) + if err != nil { + handleError(response, err) + return + } + + // Default parameters. + params := map[string]string{ + "namespace": api.NamespaceDefault, + "uid": "", + } + for k, v := range request.PathParameters() { + params[k] = v + } + + if params["podName"] == "" || params["containerName"] == "" { + response.WriteErrorString(http.StatusBadRequest, + fmt.Sprintf("Invalid pod container request: %v", params)) + return + } + + pod, ok := h.provider.GetPodByName(params["namespace"], params["podName"]) + if !ok { + glog.V(4).Infof("Container not found: %v", params) + handleError(response, kubecontainer.ErrContainerNotFound) + return + } + stats, err := h.provider.GetContainerInfo( + kubecontainer.GetPodFullName(pod), + types.UID(params["uid"]), + params["containerName"], + query.cadvisorRequest()) + + if err != nil { + handleError(response, err) + return + } + writeResponse(response, stats) +} + +func writeResponse(response *restful.Response, stats interface{}) { + if err := response.WriteAsJson(stats); err != nil { + glog.Errorf("Error writing response: %v", err) + } +} + +// handleError serializes an error object into an HTTP response. +func handleError(response *restful.Response, err error) { + switch err { + case kubecontainer.ErrContainerNotFound: + response.WriteError(http.StatusNotFound, err) + default: + msg := fmt.Sprintf("Internal Error: %v", err) + glog.Errorf("HTTP InternalServerError: %s", msg) + response.WriteErrorString(http.StatusInternalServerError, msg) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/mocks_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/mocks_test.go new file mode 100644 index 000000000..b95a823d2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/mocks_test.go @@ -0,0 +1,244 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 stats + +import "github.com/stretchr/testify/mock" + +import cadvisorapi "github.com/google/cadvisor/info/v1" +import cadvisorapiv2 "github.com/google/cadvisor/info/v2" +import "k8s.io/kubernetes/pkg/api" +import "k8s.io/kubernetes/pkg/kubelet/cm" + +import "k8s.io/kubernetes/pkg/types" +import "k8s.io/kubernetes/pkg/volume" + +// DO NOT EDIT +// GENERATED BY mockery + +type MockStatsProvider struct { + mock.Mock +} + +// GetContainerInfo provides a mock function with given fields: podFullName, uid, containerName, req +func (_m *MockStatsProvider) GetContainerInfo(podFullName string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) { + ret := _m.Called(podFullName, uid, containerName, req) + + var r0 *cadvisorapi.ContainerInfo + if rf, ok := ret.Get(0).(func(string, types.UID, string, *cadvisorapi.ContainerInfoRequest) *cadvisorapi.ContainerInfo); ok { + r0 = rf(podFullName, uid, containerName, req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*cadvisorapi.ContainerInfo) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, types.UID, string, *cadvisorapi.ContainerInfoRequest) error); ok { + r1 = rf(podFullName, uid, containerName, req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetContainerInfoV2 provides a mock function with given fields: name, options +func (_m *MockStatsProvider) GetContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) { + ret := _m.Called(name, options) + + var r0 map[string]cadvisorapiv2.ContainerInfo + if rf, ok := ret.Get(0).(func(string, cadvisorapiv2.RequestOptions) map[string]cadvisorapiv2.ContainerInfo); ok { + r0 = rf(name, options) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]cadvisorapiv2.ContainerInfo) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, cadvisorapiv2.RequestOptions) error); ok { + r1 = rf(name, options) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetRawContainerInfo provides a mock function with given fields: containerName, req, subcontainers +func (_m *MockStatsProvider) GetRawContainerInfo(containerName string, req *cadvisorapi.ContainerInfoRequest, subcontainers bool) (map[string]*cadvisorapi.ContainerInfo, error) { + ret := _m.Called(containerName, req, subcontainers) + + var r0 map[string]*cadvisorapi.ContainerInfo + if rf, ok := ret.Get(0).(func(string, *cadvisorapi.ContainerInfoRequest, bool) map[string]*cadvisorapi.ContainerInfo); ok { + r0 = rf(containerName, req, subcontainers) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]*cadvisorapi.ContainerInfo) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, *cadvisorapi.ContainerInfoRequest, bool) error); ok { + r1 = rf(containerName, req, subcontainers) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetPodByName provides a mock function with given fields: namespace, name +func (_m *MockStatsProvider) GetPodByName(namespace string, name string) (*api.Pod, bool) { + ret := _m.Called(namespace, name) + + var r0 *api.Pod + if rf, ok := ret.Get(0).(func(string, string) *api.Pod); ok { + r0 = rf(namespace, name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*api.Pod) + } + } + + var r1 bool + if rf, ok := ret.Get(1).(func(string, string) bool); ok { + r1 = rf(namespace, name) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// GetNode provides a mock function with given fields: +func (_m *MockStatsProvider) GetNode() (*api.Node, error) { + ret := _m.Called() + + var r0 *api.Node + if rf, ok := ret.Get(0).(func() *api.Node); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*api.Node) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetNodeConfig provides a mock function with given fields: +func (_m *MockStatsProvider) GetNodeConfig() cm.NodeConfig { + ret := _m.Called() + + var r0 cm.NodeConfig + if rf, ok := ret.Get(0).(func() cm.NodeConfig); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(cm.NodeConfig) + } + + return r0 +} + +// DockerImagesFsInfo provides a mock function with given fields: +func (_m *MockStatsProvider) DockerImagesFsInfo() (cadvisorapiv2.FsInfo, error) { + ret := _m.Called() + + var r0 cadvisorapiv2.FsInfo + if rf, ok := ret.Get(0).(func() cadvisorapiv2.FsInfo); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(cadvisorapiv2.FsInfo) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RootFsInfo provides a mock function with given fields: +func (_m *MockStatsProvider) RootFsInfo() (cadvisorapiv2.FsInfo, error) { + ret := _m.Called() + + var r0 cadvisorapiv2.FsInfo + if rf, ok := ret.Get(0).(func() cadvisorapiv2.FsInfo); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(cadvisorapiv2.FsInfo) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListVolumesForPod provides a mock function with given fields: podUID +func (_m *MockStatsProvider) ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool) { + ret := _m.Called(podUID) + + var r0 map[string]volume.Volume + if rf, ok := ret.Get(0).(func(types.UID) map[string]volume.Volume); ok { + r0 = rf(podUID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]volume.Volume) + } + } + + var r1 bool + if rf, ok := ret.Get(1).(func(types.UID) bool); ok { + r1 = rf(podUID) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// GetPods provides a mock function with given fields: +func (_m *MockStatsProvider) GetPods() []*api.Pod { + ret := _m.Called() + + var r0 []*api.Pod + if rf, ok := ret.Get(0).(func() []*api.Pod); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*api.Pod) + } + } + + return r0 +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/resource_analyzer.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/resource_analyzer.go new file mode 100644 index 000000000..34f05d75a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/resource_analyzer.go @@ -0,0 +1,43 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 stats + +import "time" + +// ResourceAnalyzer provides statistics on node resource consumption +type ResourceAnalyzer interface { + Start() + + fsResourceAnalyzerInterface +} + +// resourceAnalyzer implements ResourceAnalyzer +type resourceAnalyzer struct { + *fsResourceAnalyzer +} + +var _ ResourceAnalyzer = &resourceAnalyzer{} + +// NewResourceAnalyzer returns a new ResourceAnalyzer +func NewResourceAnalyzer(statsProvider StatsProvider, calVolumeFrequency time.Duration) ResourceAnalyzer { + return &resourceAnalyzer{newFsResourceAnalyzer(statsProvider, calVolumeFrequency)} +} + +// Start starts background functions necessary for the ResourceAnalyzer to function +func (ra *resourceAnalyzer) Start() { + ra.fsResourceAnalyzer.Start() +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/summary.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/summary.go new file mode 100644 index 000000000..56c370a0b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/summary.go @@ -0,0 +1,363 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 stats + +import ( + "fmt" + "runtime" + "strings" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats" + "k8s.io/kubernetes/pkg/kubelet/cm" + "k8s.io/kubernetes/pkg/kubelet/dockertools" + "k8s.io/kubernetes/pkg/kubelet/leaky" + "k8s.io/kubernetes/pkg/kubelet/network" + "k8s.io/kubernetes/pkg/types" + + "github.com/golang/glog" + cadvisorapiv1 "github.com/google/cadvisor/info/v1" + cadvisorapiv2 "github.com/google/cadvisor/info/v2" +) + +type SummaryProvider interface { + // Get provides a new Summary using the latest results from cadvisor + Get() (*stats.Summary, error) +} + +type summaryProviderImpl struct { + provider StatsProvider + resourceAnalyzer ResourceAnalyzer +} + +var _ SummaryProvider = &summaryProviderImpl{} + +// NewSummaryProvider returns a new SummaryProvider +func NewSummaryProvider(statsProvider StatsProvider, resourceAnalyzer ResourceAnalyzer) SummaryProvider { + stackBuff := []byte{} + runtime.Stack(stackBuff, false) + return &summaryProviderImpl{statsProvider, resourceAnalyzer} +} + +// Get implements the SummaryProvider interface +// Query cadvisor for the latest resource metrics and build into a summary +func (sp *summaryProviderImpl) Get() (*stats.Summary, error) { + options := cadvisorapiv2.RequestOptions{ + IdType: cadvisorapiv2.TypeName, + Count: 2, // 2 samples are needed to compute "instantaneous" CPU + Recursive: true, + } + infos, err := sp.provider.GetContainerInfoV2("/", options) + if err != nil { + return nil, err + } + + node, err := sp.provider.GetNode() + if err != nil { + return nil, err + } + + nodeConfig := sp.provider.GetNodeConfig() + rootFsInfo, err := sp.provider.RootFsInfo() + if err != nil { + return nil, err + } + imageFsInfo, err := sp.provider.DockerImagesFsInfo() + if err != nil { + return nil, err + } + + sb := &summaryBuilder{sp.resourceAnalyzer, node, nodeConfig, rootFsInfo, imageFsInfo, infos} + return sb.build() +} + +// summaryBuilder aggregates the datastructures provided by cadvisor into a Summary result +type summaryBuilder struct { + resourceAnalyzer ResourceAnalyzer + node *api.Node + nodeConfig cm.NodeConfig + rootFsInfo cadvisorapiv2.FsInfo + imageFsInfo cadvisorapiv2.FsInfo + infos map[string]cadvisorapiv2.ContainerInfo +} + +// build returns a Summary from aggregating the input data +func (sb *summaryBuilder) build() (*stats.Summary, error) { + rootInfo, found := sb.infos["/"] + if !found { + return nil, fmt.Errorf("Missing stats for root container") + } + + rootStats := sb.containerInfoV2ToStats("", &rootInfo) + nodeStats := stats.NodeStats{ + NodeName: sb.node.Name, + CPU: rootStats.CPU, + Memory: rootStats.Memory, + Network: sb.containerInfoV2ToNetworkStats("node:"+sb.node.Name, &rootInfo), + Fs: &stats.FsStats{ + AvailableBytes: &sb.rootFsInfo.Available, + CapacityBytes: &sb.rootFsInfo.Capacity, + UsedBytes: &sb.rootFsInfo.Usage}, + StartTime: rootStats.StartTime, + } + + systemContainers := map[string]string{ + stats.SystemContainerKubelet: sb.nodeConfig.KubeletCgroupsName, + stats.SystemContainerRuntime: sb.nodeConfig.RuntimeCgroupsName, + stats.SystemContainerMisc: sb.nodeConfig.SystemCgroupsName, + } + for sys, name := range systemContainers { + if info, ok := sb.infos[name]; ok { + nodeStats.SystemContainers = append(nodeStats.SystemContainers, sb.containerInfoV2ToStats(sys, &info)) + } + } + + summary := stats.Summary{ + Node: nodeStats, + Pods: sb.buildSummaryPods(), + } + return &summary, nil +} + +// containerInfoV2FsStats populates the container fs stats +func (sb *summaryBuilder) containerInfoV2FsStats( + info *cadvisorapiv2.ContainerInfo, + cs *stats.ContainerStats) { + + // The container logs live on the node rootfs device + cs.Logs = &stats.FsStats{ + AvailableBytes: &sb.rootFsInfo.Available, + CapacityBytes: &sb.rootFsInfo.Capacity, + } + + // The container rootFs lives on the imageFs devices (which may not be the node root fs) + cs.Rootfs = &stats.FsStats{ + AvailableBytes: &sb.imageFsInfo.Available, + CapacityBytes: &sb.imageFsInfo.Capacity, + } + + lcs, found := sb.latestContainerStats(info) + if !found { + return + } + cfs := lcs.Filesystem + if cfs != nil && cfs.BaseUsageBytes != nil { + rootfsUsage := *cfs.BaseUsageBytes + cs.Rootfs.UsedBytes = &rootfsUsage + if cfs.TotalUsageBytes != nil { + logsUsage := *cfs.TotalUsageBytes - *cfs.BaseUsageBytes + cs.Logs.UsedBytes = &logsUsage + } + } +} + +// latestContainerStats returns the latest container stats from cadvisor, or nil if none exist +func (sb *summaryBuilder) latestContainerStats(info *cadvisorapiv2.ContainerInfo) (*cadvisorapiv2.ContainerStats, bool) { + stats := info.Stats + if len(stats) < 1 { + return nil, false + } + latest := stats[len(stats)-1] + if latest == nil { + return nil, false + } + return latest, true +} + +// buildSummaryPods aggregates and returns the container stats in cinfos by the Pod managing the container. +// Containers not managed by a Pod are omitted. +func (sb *summaryBuilder) buildSummaryPods() []stats.PodStats { + // Map each container to a pod and update the PodStats with container data + podToStats := map[stats.PodReference]*stats.PodStats{} + for key, cinfo := range sb.infos { + // on systemd using devicemapper each mount into the container has an associated cgroup. + // we ignore them to ensure we do not get duplicate entries in our summary. + // for details on .mount units: http://man7.org/linux/man-pages/man5/systemd.mount.5.html + if strings.HasSuffix(key, ".mount") { + continue + } + // Build the Pod key if this container is managed by a Pod + if !sb.isPodManagedContainer(&cinfo) { + continue + } + ref := sb.buildPodRef(&cinfo) + + // Lookup the PodStats for the pod using the PodRef. If none exists, initialize a new entry. + podStats, found := podToStats[ref] + if !found { + podStats = &stats.PodStats{PodRef: ref} + podToStats[ref] = podStats + } + + // Update the PodStats entry with the stats from the container by adding it to stats.Containers + containerName := dockertools.GetContainerName(cinfo.Spec.Labels) + if containerName == leaky.PodInfraContainerName { + // Special case for infrastructure container which is hidden from the user and has network stats + podStats.Network = sb.containerInfoV2ToNetworkStats("pod:"+ref.Namespace+"_"+ref.Name, &cinfo) + podStats.StartTime = unversioned.NewTime(cinfo.Spec.CreationTime) + } else { + podStats.Containers = append(podStats.Containers, sb.containerInfoV2ToStats(containerName, &cinfo)) + } + } + + // Add each PodStats to the result + result := make([]stats.PodStats, 0, len(podToStats)) + for _, podStats := range podToStats { + // Lookup the volume stats for each pod + podUID := types.UID(podStats.PodRef.UID) + if vstats, found := sb.resourceAnalyzer.GetPodVolumeStats(podUID); found { + podStats.VolumeStats = vstats.Volumes + } + result = append(result, *podStats) + } + return result +} + +// buildPodRef returns a PodReference that identifies the Pod managing cinfo +func (sb *summaryBuilder) buildPodRef(cinfo *cadvisorapiv2.ContainerInfo) stats.PodReference { + podName := dockertools.GetPodName(cinfo.Spec.Labels) + podNamespace := dockertools.GetPodNamespace(cinfo.Spec.Labels) + podUID := dockertools.GetPodUID(cinfo.Spec.Labels) + return stats.PodReference{Name: podName, Namespace: podNamespace, UID: podUID} +} + +// isPodManagedContainer returns true if the cinfo container is managed by a Pod +func (sb *summaryBuilder) isPodManagedContainer(cinfo *cadvisorapiv2.ContainerInfo) bool { + podName := dockertools.GetPodName(cinfo.Spec.Labels) + podNamespace := dockertools.GetPodNamespace(cinfo.Spec.Labels) + managed := podName != "" && podNamespace != "" + if !managed && podName != podNamespace { + glog.Warningf( + "Expect container to have either both podName (%s) and podNamespace (%s) labels, or neither.", + podName, podNamespace) + } + return managed +} + +func (sb *summaryBuilder) containerInfoV2ToStats( + name string, + info *cadvisorapiv2.ContainerInfo) stats.ContainerStats { + cStats := stats.ContainerStats{ + StartTime: unversioned.NewTime(info.Spec.CreationTime), + Name: name, + } + cstat, found := sb.latestContainerStats(info) + if !found { + return cStats + } + if info.Spec.HasCpu { + cpuStats := stats.CPUStats{ + Time: unversioned.NewTime(cstat.Timestamp), + } + if cstat.CpuInst != nil { + cpuStats.UsageNanoCores = &cstat.CpuInst.Usage.Total + } + if cstat.Cpu != nil { + cpuStats.UsageCoreNanoSeconds = &cstat.Cpu.Usage.Total + } + cStats.CPU = &cpuStats + } + if info.Spec.HasMemory { + pageFaults := cstat.Memory.ContainerData.Pgfault + majorPageFaults := cstat.Memory.ContainerData.Pgmajfault + cStats.Memory = &stats.MemoryStats{ + Time: unversioned.NewTime(cstat.Timestamp), + UsageBytes: &cstat.Memory.Usage, + WorkingSetBytes: &cstat.Memory.WorkingSet, + RSSBytes: &cstat.Memory.RSS, + PageFaults: &pageFaults, + MajorPageFaults: &majorPageFaults, + } + } + sb.containerInfoV2FsStats(info, &cStats) + cStats.UserDefinedMetrics = sb.containerInfoV2ToUserDefinedMetrics(info) + return cStats +} + +func (sb *summaryBuilder) containerInfoV2ToNetworkStats(name string, info *cadvisorapiv2.ContainerInfo) *stats.NetworkStats { + if !info.Spec.HasNetwork { + return nil + } + cstat, found := sb.latestContainerStats(info) + if !found { + return nil + } + for _, inter := range cstat.Network.Interfaces { + if inter.Name == network.DefaultInterfaceName { + return &stats.NetworkStats{ + Time: unversioned.NewTime(cstat.Timestamp), + RxBytes: &inter.RxBytes, + RxErrors: &inter.RxErrors, + TxBytes: &inter.TxBytes, + TxErrors: &inter.TxErrors, + } + } + } + glog.Warningf("Missing default interface %q for s", network.DefaultInterfaceName, name) + return nil +} + +func (sb *summaryBuilder) containerInfoV2ToUserDefinedMetrics(info *cadvisorapiv2.ContainerInfo) []stats.UserDefinedMetric { + type specVal struct { + ref stats.UserDefinedMetricDescriptor + valType cadvisorapiv1.DataType + time time.Time + value float64 + } + udmMap := map[string]*specVal{} + for _, spec := range info.Spec.CustomMetrics { + udmMap[spec.Name] = &specVal{ + ref: stats.UserDefinedMetricDescriptor{ + Name: spec.Name, + Type: stats.UserDefinedMetricType(spec.Type), + Units: spec.Units, + }, + valType: spec.Format, + } + } + for _, stat := range info.Stats { + for name, values := range stat.CustomMetrics { + specVal, ok := udmMap[name] + if !ok { + glog.Warningf("spec for custom metric %q is missing from cAdvisor output. Spec: %+v, Metrics: %+v", name, info.Spec, stat.CustomMetrics) + continue + } + for _, value := range values { + // Pick the most recent value + if value.Timestamp.Before(specVal.time) { + continue + } + specVal.time = value.Timestamp + specVal.value = value.FloatValue + if specVal.valType == cadvisorapiv1.IntType { + specVal.value = float64(value.IntValue) + } + } + } + } + var udm []stats.UserDefinedMetric + for _, specVal := range udmMap { + udm = append(udm, stats.UserDefinedMetric{ + UserDefinedMetricDescriptor: specVal.ref, + Time: unversioned.NewTime(specVal.time), + Value: specVal.value, + }) + } + return udm +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/summary_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/summary_test.go new file mode 100644 index 000000000..bb5b1ef9d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/summary_test.go @@ -0,0 +1,389 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 stats + +import ( + "testing" + "time" + + "github.com/google/cadvisor/info/v1" + "github.com/google/cadvisor/info/v2" + fuzz "github.com/google/gofuzz" + "github.com/stretchr/testify/assert" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + kubestats "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats" + "k8s.io/kubernetes/pkg/kubelet/cm" + "k8s.io/kubernetes/pkg/kubelet/leaky" +) + +const ( + // Offsets from seed value in generated container stats. + offsetCPUUsageCores = iota + offsetCPUUsageCoreSeconds + offsetMemPageFaults + offsetMemMajorPageFaults + offsetMemUsageBytes + offsetMemRSSBytes + offsetMemWorkingSetBytes + offsetNetRxBytes + offsetNetRxErrors + offsetNetTxBytes + offsetNetTxErrors +) + +var ( + timestamp = time.Now() + creationTime = timestamp.Add(-5 * time.Minute) +) + +func TestBuildSummary(t *testing.T) { + node := api.Node{} + node.Name = "FooNode" + nodeConfig := cm.NodeConfig{ + RuntimeCgroupsName: "/docker-daemon", + SystemCgroupsName: "/system", + KubeletCgroupsName: "/kubelet", + } + const ( + namespace0 = "test0" + namespace2 = "test2" + ) + const ( + seedRoot = 0 + seedRuntime = 100 + seedKubelet = 200 + seedMisc = 300 + seedPod0Infra = 1000 + seedPod0Container0 = 2000 + seedPod0Container1 = 2001 + seedPod1Infra = 3000 + seedPod1Container = 4000 + seedPod2Infra = 5000 + seedPod2Container = 6000 + ) + const ( + pName0 = "pod0" + pName1 = "pod1" + pName2 = "pod0" // ensure pName2 conflicts with pName0, but is in a different namespace + ) + const ( + cName00 = "c0" + cName01 = "c1" + cName10 = "c0" // ensure cName10 conflicts with cName02, but is in a different pod + cName20 = "c1" // ensure cName20 conflicts with cName01, but is in a different pod + namespace + ) + + prf0 := kubestats.PodReference{Name: pName0, Namespace: namespace0, UID: "UID" + pName0} + prf1 := kubestats.PodReference{Name: pName1, Namespace: namespace0, UID: "UID" + pName1} + prf2 := kubestats.PodReference{Name: pName2, Namespace: namespace2, UID: "UID" + pName2} + infos := map[string]v2.ContainerInfo{ + "/": summaryTestContainerInfo(seedRoot, "", "", ""), + "/docker-daemon": summaryTestContainerInfo(seedRuntime, "", "", ""), + "/kubelet": summaryTestContainerInfo(seedKubelet, "", "", ""), + "/system": summaryTestContainerInfo(seedMisc, "", "", ""), + // Pod0 - Namespace0 + "/pod0-i": summaryTestContainerInfo(seedPod0Infra, pName0, namespace0, leaky.PodInfraContainerName), + "/pod0-c0": summaryTestContainerInfo(seedPod0Container0, pName0, namespace0, cName00), + "/pod0-c2": summaryTestContainerInfo(seedPod0Container1, pName0, namespace0, cName01), + // Pod1 - Namespace0 + "/pod1-i": summaryTestContainerInfo(seedPod1Infra, pName1, namespace0, leaky.PodInfraContainerName), + "/pod1-c0": summaryTestContainerInfo(seedPod1Container, pName1, namespace0, cName10), + // Pod2 - Namespace2 + "/pod2-i": summaryTestContainerInfo(seedPod2Infra, pName2, namespace2, leaky.PodInfraContainerName), + "/pod2-c0": summaryTestContainerInfo(seedPod2Container, pName2, namespace2, cName20), + } + + rootfs := v2.FsInfo{} + imagefs := v2.FsInfo{} + + sb := &summaryBuilder{ + newFsResourceAnalyzer(&MockStatsProvider{}, time.Minute*5), &node, nodeConfig, rootfs, imagefs, infos} + summary, err := sb.build() + + assert.NoError(t, err) + nodeStats := summary.Node + assert.Equal(t, "FooNode", nodeStats.NodeName) + assert.EqualValues(t, testTime(creationTime, seedRoot).Unix(), nodeStats.StartTime.Time.Unix()) + checkCPUStats(t, "Node", seedRoot, nodeStats.CPU) + checkMemoryStats(t, "Node", seedRoot, nodeStats.Memory) + checkNetworkStats(t, "Node", seedRoot, nodeStats.Network) + + systemSeeds := map[string]int{ + kubestats.SystemContainerRuntime: seedRuntime, + kubestats.SystemContainerKubelet: seedKubelet, + kubestats.SystemContainerMisc: seedMisc, + } + for _, sys := range nodeStats.SystemContainers { + name := sys.Name + seed, found := systemSeeds[name] + if !found { + t.Errorf("Unknown SystemContainer: %q", name) + } + assert.EqualValues(t, testTime(creationTime, seed).Unix(), sys.StartTime.Time.Unix(), name+".StartTime") + checkCPUStats(t, name, seed, sys.CPU) + checkMemoryStats(t, name, seed, sys.Memory) + } + + assert.Equal(t, 3, len(summary.Pods)) + indexPods := make(map[kubestats.PodReference]kubestats.PodStats, len(summary.Pods)) + for _, pod := range summary.Pods { + indexPods[pod.PodRef] = pod + } + + // Validate Pod0 Results + ps, found := indexPods[prf0] + assert.True(t, found) + assert.Len(t, ps.Containers, 2) + indexCon := make(map[string]kubestats.ContainerStats, len(ps.Containers)) + for _, con := range ps.Containers { + indexCon[con.Name] = con + } + con := indexCon[cName00] + assert.EqualValues(t, testTime(creationTime, seedPod0Container0).Unix(), con.StartTime.Time.Unix()) + checkCPUStats(t, "container", seedPod0Container0, con.CPU) + checkMemoryStats(t, "container", seedPod0Container0, con.Memory) + + con = indexCon[cName01] + assert.EqualValues(t, testTime(creationTime, seedPod0Container1).Unix(), con.StartTime.Time.Unix()) + checkCPUStats(t, "container", seedPod0Container1, con.CPU) + checkMemoryStats(t, "container", seedPod0Container1, con.Memory) + + assert.EqualValues(t, testTime(creationTime, seedPod0Infra).Unix(), ps.StartTime.Time.Unix()) + checkNetworkStats(t, "Pod", seedPod0Infra, ps.Network) + + // Validate Pod1 Results + ps, found = indexPods[prf1] + assert.True(t, found) + assert.Len(t, ps.Containers, 1) + con = ps.Containers[0] + assert.Equal(t, cName10, con.Name) + checkCPUStats(t, "container", seedPod1Container, con.CPU) + checkMemoryStats(t, "container", seedPod1Container, con.Memory) + checkNetworkStats(t, "Pod", seedPod1Infra, ps.Network) + + // Validate Pod2 Results + ps, found = indexPods[prf2] + assert.True(t, found) + assert.Len(t, ps.Containers, 1) + con = ps.Containers[0] + assert.Equal(t, cName20, con.Name) + checkCPUStats(t, "container", seedPod2Container, con.CPU) + checkMemoryStats(t, "container", seedPod2Container, con.Memory) + checkNetworkStats(t, "Pod", seedPod2Infra, ps.Network) +} + +func generateCustomMetricSpec() []v1.MetricSpec { + f := fuzz.New().NilChance(0).Funcs( + func(e *v1.MetricSpec, c fuzz.Continue) { + c.Fuzz(&e.Name) + switch c.Intn(3) { + case 0: + e.Type = v1.MetricGauge + case 1: + e.Type = v1.MetricCumulative + case 2: + e.Type = v1.MetricDelta + } + switch c.Intn(2) { + case 0: + e.Format = v1.IntType + case 1: + e.Format = v1.FloatType + } + c.Fuzz(&e.Units) + }) + var ret []v1.MetricSpec + f.Fuzz(&ret) + return ret +} + +func generateCustomMetrics(spec []v1.MetricSpec) map[string][]v1.MetricVal { + ret := map[string][]v1.MetricVal{} + for _, metricSpec := range spec { + f := fuzz.New().NilChance(0).Funcs( + func(e *v1.MetricVal, c fuzz.Continue) { + switch metricSpec.Format { + case v1.IntType: + c.Fuzz(&e.IntValue) + case v1.FloatType: + c.Fuzz(&e.FloatValue) + } + }) + + var metrics []v1.MetricVal + f.Fuzz(&metrics) + ret[metricSpec.Name] = metrics + } + return ret +} + +func summaryTestContainerInfo(seed int, podName string, podNamespace string, containerName string) v2.ContainerInfo { + labels := map[string]string{} + if podName != "" { + labels = map[string]string{ + "io.kubernetes.pod.name": podName, + "io.kubernetes.pod.uid": "UID" + podName, + "io.kubernetes.pod.namespace": podNamespace, + "io.kubernetes.container.name": containerName, + } + } + spec := v2.ContainerSpec{ + CreationTime: testTime(creationTime, seed), + HasCpu: true, + HasMemory: true, + HasNetwork: true, + Labels: labels, + CustomMetrics: generateCustomMetricSpec(), + } + + stats := v2.ContainerStats{ + Timestamp: testTime(timestamp, seed), + Cpu: &v1.CpuStats{}, + CpuInst: &v2.CpuInstStats{}, + Memory: &v1.MemoryStats{ + Usage: uint64(seed + offsetMemUsageBytes), + WorkingSet: uint64(seed + offsetMemWorkingSetBytes), + RSS: uint64(seed + offsetMemRSSBytes), + ContainerData: v1.MemoryStatsMemoryData{ + Pgfault: uint64(seed + offsetMemPageFaults), + Pgmajfault: uint64(seed + offsetMemMajorPageFaults), + }, + }, + Network: &v2.NetworkStats{ + Interfaces: []v1.InterfaceStats{{ + Name: "eth0", + RxBytes: uint64(seed + offsetNetRxBytes), + RxErrors: uint64(seed + offsetNetRxErrors), + TxBytes: uint64(seed + offsetNetTxBytes), + TxErrors: uint64(seed + offsetNetTxErrors), + }, { + Name: "cbr0", + RxBytes: 100, + RxErrors: 100, + TxBytes: 100, + TxErrors: 100, + }}, + }, + CustomMetrics: generateCustomMetrics(spec.CustomMetrics), + } + stats.Cpu.Usage.Total = uint64(seed + offsetCPUUsageCoreSeconds) + stats.CpuInst.Usage.Total = uint64(seed + offsetCPUUsageCores) + return v2.ContainerInfo{ + Spec: spec, + Stats: []*v2.ContainerStats{&stats}, + } +} + +func testTime(base time.Time, seed int) time.Time { + return base.Add(time.Duration(seed) * time.Second) +} + +func checkNetworkStats(t *testing.T, label string, seed int, stats *kubestats.NetworkStats) { + assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".Net.Time") + assert.EqualValues(t, seed+offsetNetRxBytes, *stats.RxBytes, label+".Net.RxBytes") + assert.EqualValues(t, seed+offsetNetRxErrors, *stats.RxErrors, label+".Net.RxErrors") + assert.EqualValues(t, seed+offsetNetTxBytes, *stats.TxBytes, label+".Net.TxBytes") + assert.EqualValues(t, seed+offsetNetTxErrors, *stats.TxErrors, label+".Net.TxErrors") +} + +func checkCPUStats(t *testing.T, label string, seed int, stats *kubestats.CPUStats) { + assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".CPU.Time") + assert.EqualValues(t, seed+offsetCPUUsageCores, *stats.UsageNanoCores, label+".CPU.UsageCores") + assert.EqualValues(t, seed+offsetCPUUsageCoreSeconds, *stats.UsageCoreNanoSeconds, label+".CPU.UsageCoreSeconds") +} + +func checkMemoryStats(t *testing.T, label string, seed int, stats *kubestats.MemoryStats) { + assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".Mem.Time") + assert.EqualValues(t, seed+offsetMemUsageBytes, *stats.UsageBytes, label+".Mem.UsageBytes") + assert.EqualValues(t, seed+offsetMemWorkingSetBytes, *stats.WorkingSetBytes, label+".Mem.WorkingSetBytes") + assert.EqualValues(t, seed+offsetMemRSSBytes, *stats.RSSBytes, label+".Mem.RSSBytes") + assert.EqualValues(t, seed+offsetMemPageFaults, *stats.PageFaults, label+".Mem.PageFaults") + assert.EqualValues(t, seed+offsetMemMajorPageFaults, *stats.MajorPageFaults, label+".Mem.MajorPageFaults") +} + +func TestCustomMetrics(t *testing.T) { + spec := []v1.MetricSpec{ + { + Name: "qos", + Type: v1.MetricGauge, + Format: v1.IntType, + Units: "per second", + }, + { + Name: "cpuLoad", + Type: v1.MetricCumulative, + Format: v1.FloatType, + Units: "count", + }, + } + timestamp1 := time.Now() + timestamp2 := time.Now().Add(time.Minute) + metrics := map[string][]v1.MetricVal{ + "qos": { + { + Timestamp: timestamp1, + IntValue: 10, + }, + { + Timestamp: timestamp2, + IntValue: 100, + }, + }, + "cpuLoad": { + { + Timestamp: timestamp1, + FloatValue: 1.2, + }, + { + Timestamp: timestamp2, + FloatValue: 2.1, + }, + }, + } + cInfo := v2.ContainerInfo{ + Spec: v2.ContainerSpec{ + CustomMetrics: spec, + }, + Stats: []*v2.ContainerStats{ + { + CustomMetrics: metrics, + }, + }, + } + sb := &summaryBuilder{} + assert.Contains(t, sb.containerInfoV2ToUserDefinedMetrics(&cInfo), + kubestats.UserDefinedMetric{ + UserDefinedMetricDescriptor: kubestats.UserDefinedMetricDescriptor{ + Name: "qos", + Type: kubestats.MetricGauge, + Units: "per second", + }, + Time: unversioned.NewTime(timestamp2), + Value: 100, + }, + kubestats.UserDefinedMetric{ + UserDefinedMetricDescriptor: kubestats.UserDefinedMetricDescriptor{ + Name: "cpuLoad", + Type: kubestats.MetricCumulative, + Units: "count", + }, + Time: unversioned.NewTime(timestamp2), + Value: 2.1, + }) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/volume_stat_caculator.go b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/volume_stat_caculator.go new file mode 100644 index 000000000..cb15a0453 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/server/stats/volume_stat_caculator.go @@ -0,0 +1,122 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 stats + +import ( + "sync" + "sync/atomic" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats" + "k8s.io/kubernetes/pkg/kubelet/util/format" + "k8s.io/kubernetes/pkg/util/wait" + "k8s.io/kubernetes/pkg/volume" + + "github.com/golang/glog" +) + +// volumeStatCalculator calculates volume metrics for a given pod periodically in the background and caches the result +type volumeStatCalculator struct { + statsProvider StatsProvider + jitterPeriod time.Duration + pod *api.Pod + stopChannel chan struct{} + startO sync.Once + stopO sync.Once + latest atomic.Value +} + +// PodVolumeStats encapsulates all VolumeStats for a pod +type PodVolumeStats struct { + Volumes []stats.VolumeStats +} + +// newVolumeStatCalculator creates a new VolumeStatCalculator +func newVolumeStatCalculator(statsProvider StatsProvider, jitterPeriod time.Duration, pod *api.Pod) *volumeStatCalculator { + return &volumeStatCalculator{ + statsProvider: statsProvider, + jitterPeriod: jitterPeriod, + pod: pod, + stopChannel: make(chan struct{}), + } +} + +// StartOnce starts pod volume calc that will occur periodically in the background until s.StopOnce is called +func (s *volumeStatCalculator) StartOnce() *volumeStatCalculator { + s.startO.Do(func() { + go wait.JitterUntil(func() { + s.calcAndStoreStats() + }, s.jitterPeriod, 1.0, s.stopChannel) + }) + return s +} + +// StopOnce stops background pod volume calculation. Will not stop a currently executing calculations until +// they complete their current iteration. +func (s *volumeStatCalculator) StopOnce() *volumeStatCalculator { + s.stopO.Do(func() { + close(s.stopChannel) + }) + return s +} + +// getLatest returns the most recent PodVolumeStats from the cache +func (s *volumeStatCalculator) GetLatest() (PodVolumeStats, bool) { + if result := s.latest.Load(); result == nil { + return PodVolumeStats{}, false + } else { + return result.(PodVolumeStats), true + } +} + +// calcAndStoreStats calculates PodVolumeStats for a given pod and writes the result to the s.latest cache. +func (s *volumeStatCalculator) calcAndStoreStats() { + // Find all Volumes for the Pod + volumes, found := s.statsProvider.ListVolumesForPod(s.pod.UID) + if !found { + return + } + + // Call GetMetrics on each Volume and copy the result to a new VolumeStats.FsStats + stats := make([]stats.VolumeStats, 0, len(volumes)) + for name, v := range volumes { + metric, err := v.GetMetrics() + if err != nil { + // Expected for Volumes that don't support Metrics + // TODO: Disambiguate unsupported from errors + // See issue #20676 + glog.V(4).Infof("Failed to calculate volume metrics for pod %s volume %s: %+v", format.Pod(s.pod), name, err) + continue + } + stats = append(stats, s.parsePodVolumeStats(name, metric)) + } + + // Store the new stats + s.latest.Store(PodVolumeStats{Volumes: stats}) +} + +// parsePodVolumeStats converts (internal) volume.Metrics to (external) stats.VolumeStats structures +func (s *volumeStatCalculator) parsePodVolumeStats(podName string, metric *volume.Metrics) stats.VolumeStats { + available := uint64(metric.Available.Value()) + capacity := uint64(metric.Capacity.Value()) + used := uint64((metric.Used.Value())) + return stats.VolumeStats{ + Name: podName, + FsStats: stats.FsStats{AvailableBytes: &available, CapacityBytes: &capacity, UsedBytes: &used}, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/status/generate.go b/vendor/k8s.io/kubernetes/pkg/kubelet/status/generate.go new file mode 100644 index 000000000..05d845470 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/status/generate.go @@ -0,0 +1,79 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 status + +import ( + "fmt" + "strings" + + "k8s.io/kubernetes/pkg/api" +) + +// GeneratePodReadyCondition returns ready condition if all containers in a pod are ready, else it +// returns an unready condition. +func GeneratePodReadyCondition(spec *api.PodSpec, containerStatuses []api.ContainerStatus, podPhase api.PodPhase) api.PodCondition { + // Find if all containers are ready or not. + if containerStatuses == nil { + return api.PodCondition{ + Type: api.PodReady, + Status: api.ConditionFalse, + Reason: "UnknownContainerStatuses", + } + } + unknownContainers := []string{} + unreadyContainers := []string{} + for _, container := range spec.Containers { + if containerStatus, ok := api.GetContainerStatus(containerStatuses, container.Name); ok { + if !containerStatus.Ready { + unreadyContainers = append(unreadyContainers, container.Name) + } + } else { + unknownContainers = append(unknownContainers, container.Name) + } + } + + // If all containers are known and succeeded, just return PodCompleted. + if podPhase == api.PodSucceeded && len(unknownContainers) == 0 { + return api.PodCondition{ + Type: api.PodReady, + Status: api.ConditionFalse, + Reason: "PodCompleted", + } + } + + unreadyMessages := []string{} + if len(unknownContainers) > 0 { + unreadyMessages = append(unreadyMessages, fmt.Sprintf("containers with unknown status: %s", unknownContainers)) + } + if len(unreadyContainers) > 0 { + unreadyMessages = append(unreadyMessages, fmt.Sprintf("containers with unready status: %s", unreadyContainers)) + } + unreadyMessage := strings.Join(unreadyMessages, ", ") + if unreadyMessage != "" { + return api.PodCondition{ + Type: api.PodReady, + Status: api.ConditionFalse, + Reason: "ContainersNotReady", + Message: unreadyMessage, + } + } + + return api.PodCondition{ + Type: api.PodReady, + Status: api.ConditionTrue, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/status/generate_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/status/generate_test.go new file mode 100644 index 000000000..2d39c238d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/status/generate_test.go @@ -0,0 +1,143 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 status + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" +) + +func TestGeneratePodReadyCondition(t *testing.T) { + tests := []struct { + spec *api.PodSpec + containerStatuses []api.ContainerStatus + podPhase api.PodPhase + expected api.PodCondition + }{ + { + spec: nil, + containerStatuses: nil, + podPhase: api.PodRunning, + expected: getReadyCondition(false, "UnknownContainerStatuses", ""), + }, + { + spec: &api.PodSpec{}, + containerStatuses: []api.ContainerStatus{}, + podPhase: api.PodRunning, + expected: getReadyCondition(true, "", ""), + }, + { + spec: &api.PodSpec{ + Containers: []api.Container{ + {Name: "1234"}, + }, + }, + containerStatuses: []api.ContainerStatus{}, + podPhase: api.PodRunning, + expected: getReadyCondition(false, "ContainersNotReady", "containers with unknown status: [1234]"), + }, + { + spec: &api.PodSpec{ + Containers: []api.Container{ + {Name: "1234"}, + {Name: "5678"}, + }, + }, + containerStatuses: []api.ContainerStatus{ + getReadyStatus("1234"), + getReadyStatus("5678"), + }, + podPhase: api.PodRunning, + expected: getReadyCondition(true, "", ""), + }, + { + spec: &api.PodSpec{ + Containers: []api.Container{ + {Name: "1234"}, + {Name: "5678"}, + }, + }, + containerStatuses: []api.ContainerStatus{ + getReadyStatus("1234"), + }, + podPhase: api.PodRunning, + expected: getReadyCondition(false, "ContainersNotReady", "containers with unknown status: [5678]"), + }, + { + spec: &api.PodSpec{ + Containers: []api.Container{ + {Name: "1234"}, + {Name: "5678"}, + }, + }, + containerStatuses: []api.ContainerStatus{ + getReadyStatus("1234"), + getNotReadyStatus("5678"), + }, + podPhase: api.PodRunning, + expected: getReadyCondition(false, "ContainersNotReady", "containers with unready status: [5678]"), + }, + { + spec: &api.PodSpec{ + Containers: []api.Container{ + {Name: "1234"}, + }, + }, + containerStatuses: []api.ContainerStatus{ + getNotReadyStatus("1234"), + }, + podPhase: api.PodSucceeded, + expected: getReadyCondition(false, "PodCompleted", ""), + }, + } + + for i, test := range tests { + condition := GeneratePodReadyCondition(test.spec, test.containerStatuses, test.podPhase) + if !reflect.DeepEqual(condition, test.expected) { + t.Errorf("On test case %v, expected:\n%+v\ngot\n%+v\n", i, test.expected, condition) + } + } +} + +func getReadyCondition(ready bool, reason, message string) api.PodCondition { + status := api.ConditionFalse + if ready { + status = api.ConditionTrue + } + return api.PodCondition{ + Type: api.PodReady, + Status: status, + Reason: reason, + Message: message, + } +} + +func getReadyStatus(cName string) api.ContainerStatus { + return api.ContainerStatus{ + Name: cName, + Ready: true, + } +} + +func getNotReadyStatus(cName string) api.ContainerStatus { + return api.ContainerStatus{ + Name: cName, + Ready: false, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/status/manager.go b/vendor/k8s.io/kubernetes/pkg/kubelet/status/manager.go new file mode 100644 index 000000000..934360c37 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/status/manager.go @@ -0,0 +1,519 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 status + +import ( + "sort" + "sync" + "time" + + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/unversioned" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + kubepod "k8s.io/kubernetes/pkg/kubelet/pod" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/kubelet/util/format" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/diff" + "k8s.io/kubernetes/pkg/util/wait" +) + +// A wrapper around api.PodStatus that includes a version to enforce that stale pod statuses are +// not sent to the API server. +type versionedPodStatus struct { + status api.PodStatus + // Monotonically increasing version number (per pod). + version uint64 + // Pod name & namespace, for sending updates to API server. + podName string + podNamespace string +} + +type podStatusSyncRequest struct { + podUID types.UID + status versionedPodStatus +} + +// Updates pod statuses in apiserver. Writes only when new status has changed. +// All methods are thread-safe. +type manager struct { + kubeClient clientset.Interface + podManager kubepod.Manager + // Map from pod UID to sync status of the corresponding pod. + podStatuses map[types.UID]versionedPodStatus + podStatusesLock sync.RWMutex + podStatusChannel chan podStatusSyncRequest + // Map from (mirror) pod UID to latest status version successfully sent to the API server. + // apiStatusVersions must only be accessed from the sync thread. + apiStatusVersions map[types.UID]uint64 +} + +// status.Manager is the Source of truth for kubelet pod status, and should be kept up-to-date with +// the latest api.PodStatus. It also syncs updates back to the API server. +type Manager interface { + // Start the API server status sync loop. + Start() + + // GetPodStatus returns the cached status for the provided pod UID, as well as whether it + // was a cache hit. + GetPodStatus(uid types.UID) (api.PodStatus, bool) + + // SetPodStatus caches updates the cached status for the given pod, and triggers a status update. + SetPodStatus(pod *api.Pod, status api.PodStatus) + + // SetContainerReadiness updates the cached container status with the given readiness, and + // triggers a status update. + SetContainerReadiness(podUID types.UID, containerID kubecontainer.ContainerID, ready bool) + + // TerminatePod resets the container status for the provided pod to terminated and triggers + // a status update. + TerminatePod(pod *api.Pod) + + // RemoveOrphanedStatuses scans the status cache and removes any entries for pods not included in + // the provided podUIDs. + RemoveOrphanedStatuses(podUIDs map[types.UID]bool) +} + +const syncPeriod = 10 * time.Second + +func NewManager(kubeClient clientset.Interface, podManager kubepod.Manager) Manager { + return &manager{ + kubeClient: kubeClient, + podManager: podManager, + podStatuses: make(map[types.UID]versionedPodStatus), + podStatusChannel: make(chan podStatusSyncRequest, 1000), // Buffer up to 1000 statuses + apiStatusVersions: make(map[types.UID]uint64), + } +} + +// isStatusEqual returns true if the given pod statuses are equal, false otherwise. +// This method normalizes the status before comparing so as to make sure that meaningless +// changes will be ignored. +func isStatusEqual(oldStatus, status *api.PodStatus) bool { + return api.Semantic.DeepEqual(status, oldStatus) +} + +func (m *manager) Start() { + // Don't start the status manager if we don't have a client. This will happen + // on the master, where the kubelet is responsible for bootstrapping the pods + // of the master components. + if m.kubeClient == nil { + glog.Infof("Kubernetes client is nil, not starting status manager.") + return + } + + glog.Info("Starting to sync pod status with apiserver") + syncTicker := time.Tick(syncPeriod) + // syncPod and syncBatch share the same go routine to avoid sync races. + go wait.Forever(func() { + select { + case syncRequest := <-m.podStatusChannel: + m.syncPod(syncRequest.podUID, syncRequest.status) + case <-syncTicker: + m.syncBatch() + } + }, 0) +} + +func (m *manager) GetPodStatus(uid types.UID) (api.PodStatus, bool) { + m.podStatusesLock.RLock() + defer m.podStatusesLock.RUnlock() + status, ok := m.podStatuses[m.podManager.TranslatePodUID(uid)] + return status.status, ok +} + +func (m *manager) SetPodStatus(pod *api.Pod, status api.PodStatus) { + m.podStatusesLock.Lock() + defer m.podStatusesLock.Unlock() + // Make sure we're caching a deep copy. + status, err := copyStatus(&status) + if err != nil { + return + } + // Force a status update if deletion timestamp is set. This is necessary + // because if the pod is in the non-running state, the pod worker still + // needs to be able to trigger an update and/or deletion. + m.updateStatusInternal(pod, status, pod.DeletionTimestamp != nil) +} + +func (m *manager) SetContainerReadiness(podUID types.UID, containerID kubecontainer.ContainerID, ready bool) { + m.podStatusesLock.Lock() + defer m.podStatusesLock.Unlock() + + pod, ok := m.podManager.GetPodByUID(podUID) + if !ok { + glog.V(4).Infof("Pod %q has been deleted, no need to update readiness", string(podUID)) + return + } + + oldStatus, found := m.podStatuses[pod.UID] + if !found { + glog.Warningf("Container readiness changed before pod has synced: %q - %q", + format.Pod(pod), containerID.String()) + return + } + + // Find the container to update. + containerIndex := -1 + for i, c := range oldStatus.status.ContainerStatuses { + if c.ContainerID == containerID.String() { + containerIndex = i + break + } + } + if containerIndex == -1 { + glog.Warningf("Container readiness changed for unknown container: %q - %q", + format.Pod(pod), containerID.String()) + return + } + + if oldStatus.status.ContainerStatuses[containerIndex].Ready == ready { + glog.V(4).Infof("Container readiness unchanged (%v): %q - %q", ready, + format.Pod(pod), containerID.String()) + return + } + + // Make sure we're not updating the cached version. + status, err := copyStatus(&oldStatus.status) + if err != nil { + return + } + status.ContainerStatuses[containerIndex].Ready = ready + + // Update pod condition. + readyConditionIndex := -1 + for i, condition := range status.Conditions { + if condition.Type == api.PodReady { + readyConditionIndex = i + break + } + } + readyCondition := GeneratePodReadyCondition(&pod.Spec, status.ContainerStatuses, status.Phase) + if readyConditionIndex != -1 { + status.Conditions[readyConditionIndex] = readyCondition + } else { + glog.Warningf("PodStatus missing PodReady condition: %+v", status) + status.Conditions = append(status.Conditions, readyCondition) + } + + m.updateStatusInternal(pod, status, false) +} + +func (m *manager) TerminatePod(pod *api.Pod) { + m.podStatusesLock.Lock() + defer m.podStatusesLock.Unlock() + oldStatus := &pod.Status + if cachedStatus, ok := m.podStatuses[pod.UID]; ok { + oldStatus = &cachedStatus.status + } + status, err := copyStatus(oldStatus) + if err != nil { + return + } + for i := range status.ContainerStatuses { + status.ContainerStatuses[i].State = api.ContainerState{ + Terminated: &api.ContainerStateTerminated{}, + } + } + m.updateStatusInternal(pod, pod.Status, true) +} + +// updateStatusInternal updates the internal status cache, and queues an update to the api server if +// necessary. Returns whether an update was triggered. +// This method IS NOT THREAD SAFE and must be called from a locked function. +func (m *manager) updateStatusInternal(pod *api.Pod, status api.PodStatus, forceUpdate bool) bool { + var oldStatus api.PodStatus + cachedStatus, isCached := m.podStatuses[pod.UID] + if isCached { + oldStatus = cachedStatus.status + } else if mirrorPod, ok := m.podManager.GetMirrorPodByPod(pod); ok { + oldStatus = mirrorPod.Status + } else { + oldStatus = pod.Status + } + + // Set ReadyCondition.LastTransitionTime. + if readyCondition := api.GetPodReadyCondition(status); readyCondition != nil { + // Need to set LastTransitionTime. + lastTransitionTime := unversioned.Now() + oldReadyCondition := api.GetPodReadyCondition(oldStatus) + if oldReadyCondition != nil && readyCondition.Status == oldReadyCondition.Status { + lastTransitionTime = oldReadyCondition.LastTransitionTime + } + readyCondition.LastTransitionTime = lastTransitionTime + } + + // ensure that the start time does not change across updates. + if oldStatus.StartTime != nil && !oldStatus.StartTime.IsZero() { + status.StartTime = oldStatus.StartTime + } else if status.StartTime.IsZero() { + // if the status has no start time, we need to set an initial time + now := unversioned.Now() + status.StartTime = &now + } + + normalizeStatus(&status) + // The intent here is to prevent concurrent updates to a pod's status from + // clobbering each other so the phase of a pod progresses monotonically. + if isCached && isStatusEqual(&cachedStatus.status, &status) && !forceUpdate { + glog.V(3).Infof("Ignoring same status for pod %q, status: %+v", format.Pod(pod), status) + return false // No new status. + } + + newStatus := versionedPodStatus{ + status: status, + version: cachedStatus.version + 1, + podName: pod.Name, + podNamespace: pod.Namespace, + } + m.podStatuses[pod.UID] = newStatus + + select { + case m.podStatusChannel <- podStatusSyncRequest{pod.UID, newStatus}: + return true + default: + // Let the periodic syncBatch handle the update if the channel is full. + // We can't block, since we hold the mutex lock. + glog.V(4).Infof("Skpping the status update for pod %q for now because the channel is full; status: %+v", + format.Pod(pod), status) + return false + } +} + +// deletePodStatus simply removes the given pod from the status cache. +func (m *manager) deletePodStatus(uid types.UID) { + m.podStatusesLock.Lock() + defer m.podStatusesLock.Unlock() + delete(m.podStatuses, uid) +} + +// TODO(filipg): It'd be cleaner if we can do this without signal from user. +func (m *manager) RemoveOrphanedStatuses(podUIDs map[types.UID]bool) { + m.podStatusesLock.Lock() + defer m.podStatusesLock.Unlock() + for key := range m.podStatuses { + if _, ok := podUIDs[key]; !ok { + glog.V(5).Infof("Removing %q from status map.", key) + delete(m.podStatuses, key) + } + } +} + +// syncBatch syncs pods statuses with the apiserver. +func (m *manager) syncBatch() { + var updatedStatuses []podStatusSyncRequest + podToMirror, mirrorToPod := m.podManager.GetUIDTranslations() + func() { // Critical section + m.podStatusesLock.RLock() + defer m.podStatusesLock.RUnlock() + + // Clean up orphaned versions. + for uid := range m.apiStatusVersions { + _, hasPod := m.podStatuses[uid] + _, hasMirror := mirrorToPod[uid] + if !hasPod && !hasMirror { + delete(m.apiStatusVersions, uid) + } + } + + for uid, status := range m.podStatuses { + syncedUID := uid + if mirrorUID, ok := podToMirror[uid]; ok { + syncedUID = mirrorUID + } + if m.needsUpdate(syncedUID, status) { + updatedStatuses = append(updatedStatuses, podStatusSyncRequest{uid, status}) + } else if m.needsReconcile(uid, status.status) { + // Delete the apiStatusVersions here to force an update on the pod status + // In most cases the deleted apiStatusVersions here should be filled + // soon after the following syncPod() [If the syncPod() sync an update + // successfully]. + delete(m.apiStatusVersions, syncedUID) + updatedStatuses = append(updatedStatuses, podStatusSyncRequest{uid, status}) + } + } + }() + + for _, update := range updatedStatuses { + m.syncPod(update.podUID, update.status) + } +} + +// syncPod syncs the given status with the API server. The caller must not hold the lock. +func (m *manager) syncPod(uid types.UID, status versionedPodStatus) { + if !m.needsUpdate(uid, status) { + glog.V(1).Infof("Status for pod %q is up-to-date; skipping", uid) + return + } + + // TODO: make me easier to express from client code + pod, err := m.kubeClient.Core().Pods(status.podNamespace).Get(status.podName) + if errors.IsNotFound(err) { + glog.V(3).Infof("Pod %q (%s) does not exist on the server", status.podName, uid) + // If the Pod is deleted the status will be cleared in + // RemoveOrphanedStatuses, so we just ignore the update here. + return + } + if err == nil { + translatedUID := m.podManager.TranslatePodUID(pod.UID) + if len(translatedUID) > 0 && translatedUID != uid { + glog.V(3).Infof("Pod %q was deleted and then recreated, skipping status update", format.Pod(pod)) + m.deletePodStatus(uid) + return + } + pod.Status = status.status + // TODO: handle conflict as a retry, make that easier too. + pod, err = m.kubeClient.Core().Pods(pod.Namespace).UpdateStatus(pod) + if err == nil { + glog.V(3).Infof("Status for pod %q updated successfully: %+v", format.Pod(pod), status) + m.apiStatusVersions[pod.UID] = status.version + if kubepod.IsMirrorPod(pod) { + // We don't handle graceful deletion of mirror pods. + return + } + if pod.DeletionTimestamp == nil { + return + } + if !notRunning(pod.Status.ContainerStatuses) { + glog.V(3).Infof("Pod %q is terminated, but some containers are still running", format.Pod(pod)) + return + } + if err := m.kubeClient.Core().Pods(pod.Namespace).Delete(pod.Name, api.NewDeleteOptions(0)); err == nil { + glog.V(3).Infof("Pod %q fully terminated and removed from etcd", format.Pod(pod)) + m.deletePodStatus(uid) + return + } + } + } + + // We failed to update status, wait for periodic sync to retry. + glog.Warningf("Failed to update status for pod %q: %v", format.Pod(pod), err) +} + +// needsUpdate returns whether the status is stale for the given pod UID. +// This method is not thread safe, and most only be accessed by the sync thread. +func (m *manager) needsUpdate(uid types.UID, status versionedPodStatus) bool { + latest, ok := m.apiStatusVersions[uid] + return !ok || latest < status.version +} + +// needsReconcile compares the given status with the status in the pod manager (which +// in fact comes from apiserver), returns whether the status needs to be reconciled with +// the apiserver. Now when pod status is inconsistent between apiserver and kubelet, +// kubelet should forcibly send an update to reconclie the inconsistence, because kubelet +// should be the source of truth of pod status. +// NOTE(random-liu): It's simpler to pass in mirror pod uid and get mirror pod by uid, but +// now the pod manager only supports getting mirror pod by static pod, so we have to pass +// static pod uid here. +// TODO(random-liu): Simplify the logic when mirror pod manager is added. +func (m *manager) needsReconcile(uid types.UID, status api.PodStatus) bool { + // The pod could be a static pod, so we should translate first. + pod, ok := m.podManager.GetPodByUID(uid) + if !ok { + glog.V(4).Infof("Pod %q has been deleted, no need to reconcile", string(uid)) + return false + } + // If the pod is a static pod, we should check its mirror pod, because only status in mirror pod is meaningful to us. + if kubepod.IsStaticPod(pod) { + mirrorPod, ok := m.podManager.GetMirrorPodByPod(pod) + if !ok { + glog.V(4).Infof("Static pod %q has no corresponding mirror pod, no need to reconcile", format.Pod(pod)) + return false + } + pod = mirrorPod + } + + podStatus, err := copyStatus(&pod.Status) + if err != nil { + return false + } + normalizeStatus(&podStatus) + + if isStatusEqual(&podStatus, &status) { + // If the status from the source is the same with the cached status, + // reconcile is not needed. Just return. + return false + } + glog.V(3).Infof("Pod status is inconsistent with cached status for pod %q, a reconciliation should be triggered:\n %+v", format.Pod(pod), + diff.ObjectDiff(podStatus, status)) + + return true +} + +// We add this function, because apiserver only supports *RFC3339* now, which means that the timestamp returned by +// apiserver has no nanosecond infromation. However, the timestamp returned by unversioned.Now() contains nanosecond, +// so when we do comparison between status from apiserver and cached status, isStatusEqual() will always return false. +// There is related issue #15262 and PR #15263 about this. +// In fact, the best way to solve this is to do it on api side. However for now, we normalize the status locally in +// kubelet temporarily. +// TODO(random-liu): Remove timestamp related logic after apiserver supports nanosecond or makes it consistent. +func normalizeStatus(status *api.PodStatus) *api.PodStatus { + normalizeTimeStamp := func(t *unversioned.Time) { + *t = t.Rfc3339Copy() + } + normalizeContainerState := func(c *api.ContainerState) { + if c.Running != nil { + normalizeTimeStamp(&c.Running.StartedAt) + } + if c.Terminated != nil { + normalizeTimeStamp(&c.Terminated.StartedAt) + normalizeTimeStamp(&c.Terminated.FinishedAt) + } + } + + if status.StartTime != nil { + normalizeTimeStamp(status.StartTime) + } + for i := range status.Conditions { + condition := &status.Conditions[i] + normalizeTimeStamp(&condition.LastProbeTime) + normalizeTimeStamp(&condition.LastTransitionTime) + } + for i := range status.ContainerStatuses { + cstatus := &status.ContainerStatuses[i] + normalizeContainerState(&cstatus.State) + normalizeContainerState(&cstatus.LastTerminationState) + } + // Sort the container statuses, so that the order won't affect the result of comparison + sort.Sort(kubetypes.SortedContainerStatuses(status.ContainerStatuses)) + return status +} + +// notRunning returns true if every status is terminated or waiting, or the status list +// is empty. +func notRunning(statuses []api.ContainerStatus) bool { + for _, status := range statuses { + if status.State.Terminated == nil && status.State.Waiting == nil { + return false + } + } + return true +} + +func copyStatus(source *api.PodStatus) (api.PodStatus, error) { + clone, err := api.Scheme.DeepCopy(source) + if err != nil { + glog.Errorf("Failed to clone status %+v: %v", source, err) + return api.PodStatus{}, err + } + status := *clone.(*api.PodStatus) + return status, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/status/manager_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/status/manager_test.go new file mode 100644 index 000000000..2ad41a580 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/status/manager_test.go @@ -0,0 +1,780 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 status + +import ( + "fmt" + "math/rand" + "strconv" + "testing" + "time" + + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/client/testing/core" + + "github.com/stretchr/testify/assert" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/unversioned" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + kubepod "k8s.io/kubernetes/pkg/kubelet/pod" + podtest "k8s.io/kubernetes/pkg/kubelet/pod/testing" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/runtime" +) + +// Generate new instance of test pod with the same initial value. +func getTestPod() *api.Pod { + return &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + } +} + +// After adding reconciliation, if status in pod manager is different from the cached status, a reconciliation +// will be triggered, which will mess up all the old unit test. +// To simplify the implementation of unit test, we add testSyncBatch() here, it will make sure the statuses in +// pod manager the same with cached ones before syncBatch() so as to avoid reconciling. +func (m *manager) testSyncBatch() { + for uid, status := range m.podStatuses { + pod, ok := m.podManager.GetPodByUID(uid) + if ok { + pod.Status = status.status + } + pod, ok = m.podManager.GetMirrorPodByPod(pod) + if ok { + pod.Status = status.status + } + } + m.syncBatch() +} + +func newTestManager(kubeClient clientset.Interface) *manager { + podManager := kubepod.NewBasicPodManager(podtest.NewFakeMirrorClient()) + podManager.AddPod(getTestPod()) + return NewManager(kubeClient, podManager).(*manager) +} + +func generateRandomMessage() string { + return strconv.Itoa(rand.Int()) +} + +func getRandomPodStatus() api.PodStatus { + return api.PodStatus{ + Message: generateRandomMessage(), + } +} + +func verifyActions(t *testing.T, kubeClient clientset.Interface, expectedActions []core.Action) { + actions := kubeClient.(*fake.Clientset).Actions() + if len(actions) != len(expectedActions) { + t.Fatalf("unexpected actions, got: %+v expected: %+v", actions, expectedActions) + return + } + for i := 0; i < len(actions); i++ { + e := expectedActions[i] + a := actions[i] + if !a.Matches(e.GetVerb(), e.GetResource()) || a.GetSubresource() != e.GetSubresource() { + t.Errorf("unexpected actions, got: %+v expected: %+v", actions, expectedActions) + } + } +} + +func verifyUpdates(t *testing.T, manager *manager, expectedUpdates int) { + // Consume all updates in the channel. + numUpdates := 0 + for { + hasUpdate := true + select { + case <-manager.podStatusChannel: + numUpdates++ + default: + hasUpdate = false + } + + if !hasUpdate { + break + } + } + + if numUpdates != expectedUpdates { + t.Errorf("unexpected number of updates %d, expected %d", numUpdates, expectedUpdates) + } +} + +func TestNewStatus(t *testing.T) { + syncer := newTestManager(&fake.Clientset{}) + testPod := getTestPod() + syncer.SetPodStatus(testPod, getRandomPodStatus()) + verifyUpdates(t, syncer, 1) + + status := expectPodStatus(t, syncer, testPod) + if status.StartTime.IsZero() { + t.Errorf("SetPodStatus did not set a proper start time value") + } +} + +func TestNewStatusPreservesPodStartTime(t *testing.T) { + syncer := newTestManager(&fake.Clientset{}) + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Status: api.PodStatus{}, + } + now := unversioned.Now() + startTime := unversioned.NewTime(now.Time.Add(-1 * time.Minute)) + pod.Status.StartTime = &startTime + syncer.SetPodStatus(pod, getRandomPodStatus()) + + status := expectPodStatus(t, syncer, pod) + if !status.StartTime.Time.Equal(startTime.Time) { + t.Errorf("Unexpected start time, expected %v, actual %v", startTime, status.StartTime) + } +} + +func getReadyPodStatus() api.PodStatus { + return api.PodStatus{ + Conditions: []api.PodCondition{ + { + Type: api.PodReady, + Status: api.ConditionTrue, + }, + }, + } +} + +func TestNewStatusSetsReadyTransitionTime(t *testing.T) { + syncer := newTestManager(&fake.Clientset{}) + podStatus := getReadyPodStatus() + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Status: api.PodStatus{}, + } + syncer.SetPodStatus(pod, podStatus) + verifyUpdates(t, syncer, 1) + status := expectPodStatus(t, syncer, pod) + readyCondition := api.GetPodReadyCondition(status) + if readyCondition.LastTransitionTime.IsZero() { + t.Errorf("Unexpected: last transition time not set") + } +} + +func TestChangedStatus(t *testing.T) { + syncer := newTestManager(&fake.Clientset{}) + testPod := getTestPod() + syncer.SetPodStatus(testPod, getRandomPodStatus()) + syncer.SetPodStatus(testPod, getRandomPodStatus()) + verifyUpdates(t, syncer, 2) +} + +func TestChangedStatusKeepsStartTime(t *testing.T) { + syncer := newTestManager(&fake.Clientset{}) + testPod := getTestPod() + now := unversioned.Now() + firstStatus := getRandomPodStatus() + firstStatus.StartTime = &now + syncer.SetPodStatus(testPod, firstStatus) + syncer.SetPodStatus(testPod, getRandomPodStatus()) + verifyUpdates(t, syncer, 2) + finalStatus := expectPodStatus(t, syncer, testPod) + if finalStatus.StartTime.IsZero() { + t.Errorf("StartTime should not be zero") + } + expected := now.Rfc3339Copy() + if !finalStatus.StartTime.Equal(expected) { + t.Errorf("Expected %v, but got %v", expected, finalStatus.StartTime) + } +} + +func TestChangedStatusUpdatesLastTransitionTime(t *testing.T) { + syncer := newTestManager(&fake.Clientset{}) + podStatus := getReadyPodStatus() + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Status: api.PodStatus{}, + } + syncer.SetPodStatus(pod, podStatus) + verifyUpdates(t, syncer, 1) + oldStatus := expectPodStatus(t, syncer, pod) + anotherStatus := getReadyPodStatus() + anotherStatus.Conditions[0].Status = api.ConditionFalse + syncer.SetPodStatus(pod, anotherStatus) + verifyUpdates(t, syncer, 1) + newStatus := expectPodStatus(t, syncer, pod) + + oldReadyCondition := api.GetPodReadyCondition(oldStatus) + newReadyCondition := api.GetPodReadyCondition(newStatus) + if newReadyCondition.LastTransitionTime.IsZero() { + t.Errorf("Unexpected: last transition time not set") + } + if newReadyCondition.LastTransitionTime.Before(oldReadyCondition.LastTransitionTime) { + t.Errorf("Unexpected: new transition time %s, is before old transition time %s", newReadyCondition.LastTransitionTime, oldReadyCondition.LastTransitionTime) + } +} + +func TestUnchangedStatus(t *testing.T) { + syncer := newTestManager(&fake.Clientset{}) + testPod := getTestPod() + podStatus := getRandomPodStatus() + syncer.SetPodStatus(testPod, podStatus) + syncer.SetPodStatus(testPod, podStatus) + verifyUpdates(t, syncer, 1) +} + +func TestUnchangedStatusPreservesLastTransitionTime(t *testing.T) { + syncer := newTestManager(&fake.Clientset{}) + podStatus := getReadyPodStatus() + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + UID: "12345678", + Name: "foo", + Namespace: "new", + }, + Status: api.PodStatus{}, + } + syncer.SetPodStatus(pod, podStatus) + verifyUpdates(t, syncer, 1) + oldStatus := expectPodStatus(t, syncer, pod) + anotherStatus := getReadyPodStatus() + syncer.SetPodStatus(pod, anotherStatus) + // No update. + verifyUpdates(t, syncer, 0) + newStatus := expectPodStatus(t, syncer, pod) + + oldReadyCondition := api.GetPodReadyCondition(oldStatus) + newReadyCondition := api.GetPodReadyCondition(newStatus) + if newReadyCondition.LastTransitionTime.IsZero() { + t.Errorf("Unexpected: last transition time not set") + } + if !oldReadyCondition.LastTransitionTime.Equal(newReadyCondition.LastTransitionTime) { + t.Errorf("Unexpected: new transition time %s, is not equal to old transition time %s", newReadyCondition.LastTransitionTime, oldReadyCondition.LastTransitionTime) + } +} + +func TestSyncBatchIgnoresNotFound(t *testing.T) { + client := fake.Clientset{} + syncer := newTestManager(&client) + client.AddReactor("get", "pods", func(action core.Action) (bool, runtime.Object, error) { + return true, nil, errors.NewNotFound(api.Resource("pods"), "test-pod") + }) + syncer.SetPodStatus(getTestPod(), getRandomPodStatus()) + syncer.testSyncBatch() + + verifyActions(t, syncer.kubeClient, []core.Action{ + core.GetActionImpl{ActionImpl: core.ActionImpl{Verb: "get", Resource: "pods"}}, + }) +} + +func TestSyncBatch(t *testing.T) { + syncer := newTestManager(&fake.Clientset{}) + testPod := getTestPod() + syncer.kubeClient = fake.NewSimpleClientset(testPod) + syncer.SetPodStatus(testPod, getRandomPodStatus()) + syncer.testSyncBatch() + verifyActions(t, syncer.kubeClient, []core.Action{ + core.GetActionImpl{ActionImpl: core.ActionImpl{Verb: "get", Resource: "pods"}}, + core.UpdateActionImpl{ActionImpl: core.ActionImpl{Verb: "update", Resource: "pods", Subresource: "status"}}, + }, + ) +} + +func TestSyncBatchChecksMismatchedUID(t *testing.T) { + syncer := newTestManager(&fake.Clientset{}) + pod := getTestPod() + pod.UID = "first" + syncer.podManager.AddPod(pod) + differentPod := getTestPod() + differentPod.UID = "second" + syncer.podManager.AddPod(differentPod) + syncer.kubeClient = fake.NewSimpleClientset(pod) + syncer.SetPodStatus(differentPod, getRandomPodStatus()) + syncer.testSyncBatch() + verifyActions(t, syncer.kubeClient, []core.Action{ + core.GetActionImpl{ActionImpl: core.ActionImpl{Verb: "get", Resource: "pods"}}, + }) +} + +func TestSyncBatchNoDeadlock(t *testing.T) { + client := &fake.Clientset{} + m := newTestManager(client) + pod := getTestPod() + + // Setup fake client. + var ret api.Pod + var err error + client.AddReactor("*", "pods", func(action core.Action) (bool, runtime.Object, error) { + switch action := action.(type) { + case core.GetAction: + assert.Equal(t, pod.Name, action.GetName(), "Unexpeted GetAction: %+v", action) + case core.UpdateAction: + assert.Equal(t, pod.Name, action.GetObject().(*api.Pod).Name, "Unexpeted UpdateAction: %+v", action) + default: + assert.Fail(t, "Unexpected Action: %+v", action) + } + return true, &ret, err + }) + + pod.Status.ContainerStatuses = []api.ContainerStatus{{State: api.ContainerState{Running: &api.ContainerStateRunning{}}}} + + getAction := core.GetActionImpl{ActionImpl: core.ActionImpl{Verb: "get", Resource: "pods"}} + updateAction := core.UpdateActionImpl{ActionImpl: core.ActionImpl{Verb: "update", Resource: "pods", Subresource: "status"}} + + // Pod not found. + ret = *pod + err = errors.NewNotFound(api.Resource("pods"), pod.Name) + m.SetPodStatus(pod, getRandomPodStatus()) + m.testSyncBatch() + verifyActions(t, client, []core.Action{getAction}) + client.ClearActions() + + // Pod was recreated. + ret.UID = "other_pod" + err = nil + m.SetPodStatus(pod, getRandomPodStatus()) + m.testSyncBatch() + verifyActions(t, client, []core.Action{getAction}) + client.ClearActions() + + // Pod not deleted (success case). + ret = *pod + m.SetPodStatus(pod, getRandomPodStatus()) + m.testSyncBatch() + verifyActions(t, client, []core.Action{getAction, updateAction}) + client.ClearActions() + + // Pod is terminated, but still running. + pod.DeletionTimestamp = new(unversioned.Time) + m.SetPodStatus(pod, getRandomPodStatus()) + m.testSyncBatch() + verifyActions(t, client, []core.Action{getAction, updateAction}) + client.ClearActions() + + // Pod is terminated successfully. + pod.Status.ContainerStatuses[0].State.Running = nil + pod.Status.ContainerStatuses[0].State.Terminated = &api.ContainerStateTerminated{} + m.SetPodStatus(pod, getRandomPodStatus()) + m.testSyncBatch() + verifyActions(t, client, []core.Action{getAction, updateAction}) + client.ClearActions() + + // Error case. + err = fmt.Errorf("intentional test error") + m.SetPodStatus(pod, getRandomPodStatus()) + m.testSyncBatch() + verifyActions(t, client, []core.Action{getAction}) + client.ClearActions() +} + +func TestStaleUpdates(t *testing.T) { + pod := getTestPod() + client := fake.NewSimpleClientset(pod) + m := newTestManager(client) + + status := api.PodStatus{Message: "initial status"} + m.SetPodStatus(pod, status) + status.Message = "first version bump" + m.SetPodStatus(pod, status) + status.Message = "second version bump" + m.SetPodStatus(pod, status) + verifyUpdates(t, m, 3) + + t.Logf("First sync pushes latest status.") + m.testSyncBatch() + verifyActions(t, m.kubeClient, []core.Action{ + core.GetActionImpl{ActionImpl: core.ActionImpl{Verb: "get", Resource: "pods"}}, + core.UpdateActionImpl{ActionImpl: core.ActionImpl{Verb: "update", Resource: "pods", Subresource: "status"}}, + }) + client.ClearActions() + + for i := 0; i < 2; i++ { + t.Logf("Next 2 syncs should be ignored (%d).", i) + m.testSyncBatch() + verifyActions(t, m.kubeClient, []core.Action{}) + } + + t.Log("Unchanged status should not send an update.") + m.SetPodStatus(pod, status) + verifyUpdates(t, m, 0) + + t.Log("... unless it's stale.") + m.apiStatusVersions[pod.UID] = m.apiStatusVersions[pod.UID] - 1 + + m.SetPodStatus(pod, status) + m.testSyncBatch() + verifyActions(t, m.kubeClient, []core.Action{ + core.GetActionImpl{ActionImpl: core.ActionImpl{Verb: "get", Resource: "pods"}}, + core.UpdateActionImpl{ActionImpl: core.ActionImpl{Verb: "update", Resource: "pods", Subresource: "status"}}, + }) + + // Nothing stuck in the pipe. + verifyUpdates(t, m, 0) +} + +// shuffle returns a new shuffled list of container statuses. +func shuffle(statuses []api.ContainerStatus) []api.ContainerStatus { + numStatuses := len(statuses) + randIndexes := rand.Perm(numStatuses) + shuffled := make([]api.ContainerStatus, numStatuses) + for i := 0; i < numStatuses; i++ { + shuffled[i] = statuses[randIndexes[i]] + } + return shuffled +} + +func TestStatusEquality(t *testing.T) { + containerStatus := []api.ContainerStatus{} + for i := 0; i < 10; i++ { + s := api.ContainerStatus{ + Name: fmt.Sprintf("container%d", i), + } + containerStatus = append(containerStatus, s) + } + podStatus := api.PodStatus{ + ContainerStatuses: containerStatus, + } + for i := 0; i < 10; i++ { + oldPodStatus := api.PodStatus{ + ContainerStatuses: shuffle(podStatus.ContainerStatuses), + } + normalizeStatus(&oldPodStatus) + normalizeStatus(&podStatus) + if !isStatusEqual(&oldPodStatus, &podStatus) { + t.Fatalf("Order of container statuses should not affect normalized equality.") + } + } +} + +func TestStaticPodStatus(t *testing.T) { + staticPod := getTestPod() + staticPod.Annotations = map[string]string{kubetypes.ConfigSourceAnnotationKey: "file"} + mirrorPod := getTestPod() + mirrorPod.UID = "mirror-12345678" + mirrorPod.Annotations = map[string]string{ + kubetypes.ConfigSourceAnnotationKey: "api", + kubetypes.ConfigMirrorAnnotationKey: "mirror", + } + client := fake.NewSimpleClientset(mirrorPod) + m := newTestManager(client) + m.podManager.AddPod(staticPod) + m.podManager.AddPod(mirrorPod) + // Verify setup. + assert.True(t, kubepod.IsStaticPod(staticPod), "SetUp error: staticPod") + assert.True(t, kubepod.IsMirrorPod(mirrorPod), "SetUp error: mirrorPod") + assert.Equal(t, m.podManager.TranslatePodUID(mirrorPod.UID), staticPod.UID) + + status := getRandomPodStatus() + now := unversioned.Now() + status.StartTime = &now + + m.SetPodStatus(staticPod, status) + retrievedStatus := expectPodStatus(t, m, staticPod) + normalizeStatus(&status) + assert.True(t, isStatusEqual(&status, &retrievedStatus), "Expected: %+v, Got: %+v", status, retrievedStatus) + retrievedStatus, _ = m.GetPodStatus(mirrorPod.UID) + assert.True(t, isStatusEqual(&status, &retrievedStatus), "Expected: %+v, Got: %+v", status, retrievedStatus) + // Should translate mirrorPod / staticPod UID. + m.testSyncBatch() + verifyActions(t, m.kubeClient, []core.Action{ + core.GetActionImpl{ActionImpl: core.ActionImpl{Verb: "get", Resource: "pods"}}, + core.UpdateActionImpl{ActionImpl: core.ActionImpl{Verb: "update", Resource: "pods", Subresource: "status"}}, + }) + updateAction := client.Actions()[1].(core.UpdateActionImpl) + updatedPod := updateAction.Object.(*api.Pod) + assert.Equal(t, mirrorPod.UID, updatedPod.UID, "Expected mirrorPod (%q), but got %q", mirrorPod.UID, updatedPod.UID) + assert.True(t, isStatusEqual(&status, &updatedPod.Status), "Expected: %+v, Got: %+v", status, updatedPod.Status) + client.ClearActions() + + // No changes. + m.testSyncBatch() + verifyActions(t, m.kubeClient, []core.Action{}) + + // Mirror pod identity changes. + m.podManager.DeletePod(mirrorPod) + mirrorPod.UID = "new-mirror-pod" + mirrorPod.Status = api.PodStatus{} + m.podManager.AddPod(mirrorPod) + // Expect update to new mirrorPod. + m.testSyncBatch() + verifyActions(t, m.kubeClient, []core.Action{ + core.GetActionImpl{ActionImpl: core.ActionImpl{Verb: "get", Resource: "pods"}}, + core.UpdateActionImpl{ActionImpl: core.ActionImpl{Verb: "update", Resource: "pods", Subresource: "status"}}, + }) + updateAction = client.Actions()[1].(core.UpdateActionImpl) + updatedPod = updateAction.Object.(*api.Pod) + assert.Equal(t, mirrorPod.UID, updatedPod.UID, "Expected mirrorPod (%q), but got %q", mirrorPod.UID, updatedPod.UID) + assert.True(t, isStatusEqual(&status, &updatedPod.Status), "Expected: %+v, Got: %+v", status, updatedPod.Status) +} + +func TestSetContainerReadiness(t *testing.T) { + cID1 := kubecontainer.ContainerID{Type: "test", ID: "1"} + cID2 := kubecontainer.ContainerID{Type: "test", ID: "2"} + containerStatuses := []api.ContainerStatus{ + { + Name: "c1", + ContainerID: cID1.String(), + Ready: false, + }, { + Name: "c2", + ContainerID: cID2.String(), + Ready: false, + }, + } + status := api.PodStatus{ + ContainerStatuses: containerStatuses, + Conditions: []api.PodCondition{{ + Type: api.PodReady, + Status: api.ConditionFalse, + }}, + } + pod := getTestPod() + pod.Spec.Containers = []api.Container{{Name: "c1"}, {Name: "c2"}} + + // Verify expected readiness of containers & pod. + verifyReadiness := func(step string, status *api.PodStatus, c1Ready, c2Ready, podReady bool) { + for _, c := range status.ContainerStatuses { + switch c.ContainerID { + case cID1.String(): + if c.Ready != c1Ready { + t.Errorf("[%s] Expected readiness of c1 to be %v but was %v", step, c1Ready, c.Ready) + } + case cID2.String(): + if c.Ready != c2Ready { + t.Errorf("[%s] Expected readiness of c2 to be %v but was %v", step, c2Ready, c.Ready) + } + default: + t.Fatalf("[%s] Unexpected container: %+v", step, c) + } + } + if status.Conditions[0].Type != api.PodReady { + t.Fatalf("[%s] Unexpected condition: %+v", step, status.Conditions[0]) + } else if ready := (status.Conditions[0].Status == api.ConditionTrue); ready != podReady { + t.Errorf("[%s] Expected readiness of pod to be %v but was %v", step, podReady, ready) + } + } + + m := newTestManager(&fake.Clientset{}) + // Add test pod because the container spec has been changed. + m.podManager.AddPod(pod) + + t.Log("Setting readiness before status should fail.") + m.SetContainerReadiness(pod.UID, cID1, true) + verifyUpdates(t, m, 0) + if status, ok := m.GetPodStatus(pod.UID); ok { + t.Errorf("Unexpected PodStatus: %+v", status) + } + + t.Log("Setting initial status.") + m.SetPodStatus(pod, status) + verifyUpdates(t, m, 1) + status = expectPodStatus(t, m, pod) + verifyReadiness("initial", &status, false, false, false) + + t.Log("Setting unchanged readiness should do nothing.") + m.SetContainerReadiness(pod.UID, cID1, false) + verifyUpdates(t, m, 0) + status = expectPodStatus(t, m, pod) + verifyReadiness("unchanged", &status, false, false, false) + + t.Log("Setting container readiness should generate update but not pod readiness.") + m.SetContainerReadiness(pod.UID, cID1, true) + verifyUpdates(t, m, 1) + status = expectPodStatus(t, m, pod) + verifyReadiness("c1 ready", &status, true, false, false) + + t.Log("Setting both containers to ready should update pod readiness.") + m.SetContainerReadiness(pod.UID, cID2, true) + verifyUpdates(t, m, 1) + status = expectPodStatus(t, m, pod) + verifyReadiness("all ready", &status, true, true, true) + + t.Log("Setting non-existant container readiness should fail.") + m.SetContainerReadiness(pod.UID, kubecontainer.ContainerID{Type: "test", ID: "foo"}, true) + verifyUpdates(t, m, 0) + status = expectPodStatus(t, m, pod) + verifyReadiness("ignore non-existant", &status, true, true, true) +} + +func TestSyncBatchCleanupVersions(t *testing.T) { + m := newTestManager(&fake.Clientset{}) + testPod := getTestPod() + mirrorPod := getTestPod() + mirrorPod.UID = "mirror-uid" + mirrorPod.Name = "mirror_pod" + mirrorPod.Annotations = map[string]string{ + kubetypes.ConfigSourceAnnotationKey: "api", + kubetypes.ConfigMirrorAnnotationKey: "mirror", + } + + // Orphaned pods should be removed. + m.apiStatusVersions[testPod.UID] = 100 + m.apiStatusVersions[mirrorPod.UID] = 200 + m.testSyncBatch() + if _, ok := m.apiStatusVersions[testPod.UID]; ok { + t.Errorf("Should have cleared status for testPod") + } + if _, ok := m.apiStatusVersions[mirrorPod.UID]; ok { + t.Errorf("Should have cleared status for mirrorPod") + } + + // Non-orphaned pods should not be removed. + m.SetPodStatus(testPod, getRandomPodStatus()) + m.podManager.AddPod(mirrorPod) + staticPod := mirrorPod + staticPod.UID = "static-uid" + staticPod.Annotations = map[string]string{kubetypes.ConfigSourceAnnotationKey: "file"} + m.podManager.AddPod(staticPod) + m.apiStatusVersions[testPod.UID] = 100 + m.apiStatusVersions[mirrorPod.UID] = 200 + m.testSyncBatch() + if _, ok := m.apiStatusVersions[testPod.UID]; !ok { + t.Errorf("Should not have cleared status for testPod") + } + if _, ok := m.apiStatusVersions[mirrorPod.UID]; !ok { + t.Errorf("Should not have cleared status for mirrorPod") + } +} + +func TestReconcilePodStatus(t *testing.T) { + testPod := getTestPod() + client := fake.NewSimpleClientset(testPod) + syncer := newTestManager(client) + syncer.SetPodStatus(testPod, getRandomPodStatus()) + // Call syncBatch directly to test reconcile + syncer.syncBatch() // The apiStatusVersions should be set now + + podStatus, ok := syncer.GetPodStatus(testPod.UID) + if !ok { + t.Fatalf("Should find pod status for pod: %+v", testPod) + } + testPod.Status = podStatus + + // If the pod status is the same, a reconciliation is not needed, + // syncBatch should do nothing + syncer.podManager.UpdatePod(testPod) + if syncer.needsReconcile(testPod.UID, podStatus) { + t.Errorf("Pod status is the same, a reconciliation is not needed") + } + client.ClearActions() + syncer.syncBatch() + verifyActions(t, client, []core.Action{}) + + // If the pod status is the same, only the timestamp is in Rfc3339 format (lower precision without nanosecond), + // a reconciliation is not needed, syncBatch should do nothing. + // The StartTime should have been set in SetPodStatus(). + // TODO(random-liu): Remove this later when api becomes consistent for timestamp. + normalizedStartTime := testPod.Status.StartTime.Rfc3339Copy() + testPod.Status.StartTime = &normalizedStartTime + syncer.podManager.UpdatePod(testPod) + if syncer.needsReconcile(testPod.UID, podStatus) { + t.Errorf("Pod status only differs for timestamp format, a reconciliation is not needed") + } + client.ClearActions() + syncer.syncBatch() + verifyActions(t, client, []core.Action{}) + + // If the pod status is different, a reconciliation is needed, syncBatch should trigger an update + testPod.Status = getRandomPodStatus() + syncer.podManager.UpdatePod(testPod) + if !syncer.needsReconcile(testPod.UID, podStatus) { + t.Errorf("Pod status is different, a reconciliation is needed") + } + client.ClearActions() + syncer.syncBatch() + verifyActions(t, client, []core.Action{ + core.GetActionImpl{ActionImpl: core.ActionImpl{Verb: "get", Resource: "pods"}}, + core.UpdateActionImpl{ActionImpl: core.ActionImpl{Verb: "update", Resource: "pods", Subresource: "status"}}, + }) +} + +func expectPodStatus(t *testing.T, m *manager, pod *api.Pod) api.PodStatus { + status, ok := m.GetPodStatus(pod.UID) + if !ok { + t.Fatalf("Expected PodStatus for %q not found", pod.UID) + } + return status +} + +func TestDeletePods(t *testing.T) { + pod := getTestPod() + // Set the deletion timestamp. + pod.DeletionTimestamp = new(unversioned.Time) + client := fake.NewSimpleClientset(pod) + m := newTestManager(client) + m.podManager.AddPod(pod) + + status := getRandomPodStatus() + now := unversioned.Now() + status.StartTime = &now + m.SetPodStatus(pod, status) + + m.testSyncBatch() + // Expect to see an delete action. + verifyActions(t, m.kubeClient, []core.Action{ + core.GetActionImpl{ActionImpl: core.ActionImpl{Verb: "get", Resource: "pods"}}, + core.UpdateActionImpl{ActionImpl: core.ActionImpl{Verb: "update", Resource: "pods", Subresource: "status"}}, + core.DeleteActionImpl{ActionImpl: core.ActionImpl{Verb: "delete", Resource: "pods"}}, + }) +} + +func TestDoNotDeleteMirrorPods(t *testing.T) { + staticPod := getTestPod() + staticPod.Annotations = map[string]string{kubetypes.ConfigSourceAnnotationKey: "file"} + mirrorPod := getTestPod() + mirrorPod.UID = "mirror-12345678" + mirrorPod.Annotations = map[string]string{ + kubetypes.ConfigSourceAnnotationKey: "api", + kubetypes.ConfigMirrorAnnotationKey: "mirror", + } + // Set the deletion timestamp. + mirrorPod.DeletionTimestamp = new(unversioned.Time) + client := fake.NewSimpleClientset(mirrorPod) + m := newTestManager(client) + m.podManager.AddPod(staticPod) + m.podManager.AddPod(mirrorPod) + // Verify setup. + assert.True(t, kubepod.IsStaticPod(staticPod), "SetUp error: staticPod") + assert.True(t, kubepod.IsMirrorPod(mirrorPod), "SetUp error: mirrorPod") + assert.Equal(t, m.podManager.TranslatePodUID(mirrorPod.UID), staticPod.UID) + + status := getRandomPodStatus() + now := unversioned.Now() + status.StartTime = &now + m.SetPodStatus(staticPod, status) + + m.testSyncBatch() + // Expect not to see an delete action. + verifyActions(t, m.kubeClient, []core.Action{ + core.GetActionImpl{ActionImpl: core.ActionImpl{Verb: "get", Resource: "pods"}}, + core.UpdateActionImpl{ActionImpl: core.ActionImpl{Verb: "update", Resource: "pods", Subresource: "status"}}, + }) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/types/constants.go b/vendor/k8s.io/kubernetes/pkg/kubelet/types/constants.go new file mode 100644 index 000000000..060fec752 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/types/constants.go @@ -0,0 +1,22 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 types + +const ( + // system default DNS resolver configuration + ResolvConfDefault = "/etc/resolv.conf" +) diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/types/doc.go b/vendor/k8s.io/kubernetes/pkg/kubelet/types/doc.go new file mode 100644 index 000000000..104ff4e35 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/types/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// Common types in the Kubelet. +package types diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/types/pod_update.go b/vendor/k8s.io/kubernetes/pkg/kubelet/types/pod_update.go new file mode 100644 index 000000000..f93576445 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/types/pod_update.go @@ -0,0 +1,123 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 types + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" +) + +const ConfigSourceAnnotationKey = "kubernetes.io/config.source" +const ConfigMirrorAnnotationKey = "kubernetes.io/config.mirror" +const ConfigFirstSeenAnnotationKey = "kubernetes.io/config.seen" +const ConfigHashAnnotationKey = "kubernetes.io/config.hash" + +// PodOperation defines what changes will be made on a pod configuration. +type PodOperation int + +const ( + // This is the current pod configuration + SET PodOperation = iota + // Pods with the given ids are new to this source + ADD + // Pods with the given ids have been removed from this source + REMOVE + // Pods with the given ids have been updated in this source + UPDATE + // Pods with the given ids have unexpected status in this source, + // kubelet should reconcile status with this source + RECONCILE + + // These constants identify the sources of pods + // Updates from a file + FileSource = "file" + // Updates from querying a web page + HTTPSource = "http" + // Updates from Kubernetes API Server + ApiserverSource = "api" + // Updates from all sources + AllSource = "*" + + NamespaceDefault = api.NamespaceDefault +) + +// PodUpdate defines an operation sent on the channel. You can add or remove single services by +// sending an array of size one and Op == ADD|REMOVE (with REMOVE, only the ID is required). +// For setting the state of the system to a given state for this source configuration, set +// Pods as desired and Op to SET, which will reset the system state to that specified in this +// operation for this source channel. To remove all pods, set Pods to empty object and Op to SET. +// +// Additionally, Pods should never be nil - it should always point to an empty slice. While +// functionally similar, this helps our unit tests properly check that the correct PodUpdates +// are generated. +type PodUpdate struct { + Pods []*api.Pod + Op PodOperation + Source string +} + +// Gets all validated sources from the specified sources. +func GetValidatedSources(sources []string) ([]string, error) { + validated := make([]string, 0, len(sources)) + for _, source := range sources { + switch source { + case AllSource: + return []string{FileSource, HTTPSource, ApiserverSource}, nil + case FileSource, HTTPSource, ApiserverSource: + validated = append(validated, source) + break + case "": + break + default: + return []string{}, fmt.Errorf("unknown pod source %q", source) + } + } + return validated, nil +} + +// GetPodSource returns the source of the pod based on the annotation. +func GetPodSource(pod *api.Pod) (string, error) { + if pod.Annotations != nil { + if source, ok := pod.Annotations[ConfigSourceAnnotationKey]; ok { + return source, nil + } + } + return "", fmt.Errorf("cannot get source of pod %q", pod.UID) +} + +// SyncPodType classifies pod updates, eg: create, update. +type SyncPodType int + +const ( + SyncPodSync SyncPodType = iota + SyncPodUpdate + SyncPodCreate +) + +func (sp SyncPodType) String() string { + switch sp { + case SyncPodCreate: + return "create" + case SyncPodUpdate: + return "update" + case SyncPodSync: + return "sync" + default: + return "unknown" + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/types/pod_update_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/types/pod_update_test.go new file mode 100644 index 000000000..a753bb587 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/types/pod_update_test.go @@ -0,0 +1,44 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetValidatedSources(t *testing.T) { + // Empty. + sources, err := GetValidatedSources([]string{""}) + require.NoError(t, err) + require.Len(t, sources, 0) + + // Success. + sources, err = GetValidatedSources([]string{FileSource, ApiserverSource}) + require.NoError(t, err) + require.Len(t, sources, 2) + + // All. + sources, err = GetValidatedSources([]string{AllSource}) + require.NoError(t, err) + require.Len(t, sources, 3) + + // Unknown source. + sources, err = GetValidatedSources([]string{"taco"}) + require.Error(t, err) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/types/types.go b/vendor/k8s.io/kubernetes/pkg/kubelet/types/types.go new file mode 100644 index 000000000..7776ee9e3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/types/types.go @@ -0,0 +1,77 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 types + +import ( + "net/http" + "time" + + "k8s.io/kubernetes/pkg/api" +) + +// TODO: Reconcile custom types in kubelet/types and this subpackage + +type HttpGetter interface { + Get(url string) (*http.Response, error) +} + +// Timestamp wraps around time.Time and offers utilities to format and parse +// the time using RFC3339Nano +type Timestamp struct { + time time.Time +} + +// NewTimestamp returns a Timestamp object using the current time. +func NewTimestamp() *Timestamp { + return &Timestamp{time.Now()} +} + +// ConvertToTimestamp takes a string, parses it using the RFC3339Nano layout, +// and converts it to a Timestamp object. +func ConvertToTimestamp(timeString string) *Timestamp { + parsed, _ := time.Parse(time.RFC3339Nano, timeString) + return &Timestamp{parsed} +} + +// Get returns the time as time.Time. +func (t *Timestamp) Get() time.Time { + return t.time +} + +// GetString returns the time in the string format using the RFC3339Nano +// layout. +func (t *Timestamp) GetString() string { + return t.time.Format(time.RFC3339Nano) +} + +// A type to help sort container statuses based on container names. +type SortedContainerStatuses []api.ContainerStatus + +func (s SortedContainerStatuses) Len() int { return len(s) } +func (s SortedContainerStatuses) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s SortedContainerStatuses) Less(i, j int) bool { + return s[i].Name < s[j].Name +} + +// Reservation represents reserved resources for non-pod components. +type Reservation struct { + // System represents resources reserved for non-kubernetes components. + System api.ResourceList + // Kubernetes represents resources reserved for kubernetes system components. + Kubernetes api.ResourceList +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/util.go b/vendor/k8s.io/kubernetes/pkg/kubelet/util.go new file mode 100644 index 000000000..a06a57ce5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/util.go @@ -0,0 +1,110 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/capabilities" + kubetypes "k8s.io/kubernetes/pkg/kubelet/types" + "k8s.io/kubernetes/pkg/securitycontext" +) + +// Check whether we have the capabilities to run the specified pod. +func canRunPod(pod *api.Pod) error { + if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostNetwork { + allowed, err := allowHostNetwork(pod) + if err != nil { + return err + } + if !allowed { + return fmt.Errorf("pod with UID %q specified host networking, but is disallowed", pod.UID) + } + } + + if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostPID { + allowed, err := allowHostPID(pod) + if err != nil { + return err + } + if !allowed { + return fmt.Errorf("pod with UID %q specified host PID, but is disallowed", pod.UID) + } + } + + if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostIPC { + allowed, err := allowHostIPC(pod) + if err != nil { + return err + } + if !allowed { + return fmt.Errorf("pod with UID %q specified host ipc, but is disallowed", pod.UID) + } + } + + if !capabilities.Get().AllowPrivileged { + for _, container := range pod.Spec.Containers { + if securitycontext.HasPrivilegedRequest(&container) { + return fmt.Errorf("pod with UID %q specified privileged container, but is disallowed", pod.UID) + } + } + } + return nil +} + +// Determined whether the specified pod is allowed to use host networking +func allowHostNetwork(pod *api.Pod) (bool, error) { + podSource, err := kubetypes.GetPodSource(pod) + if err != nil { + return false, err + } + for _, source := range capabilities.Get().PrivilegedSources.HostNetworkSources { + if source == podSource { + return true, nil + } + } + return false, nil +} + +// Determined whether the specified pod is allowed to use host networking +func allowHostPID(pod *api.Pod) (bool, error) { + podSource, err := kubetypes.GetPodSource(pod) + if err != nil { + return false, err + } + for _, source := range capabilities.Get().PrivilegedSources.HostPIDSources { + if source == podSource { + return true, nil + } + } + return false, nil +} + +// Determined whether the specified pod is allowed to use host ipc +func allowHostIPC(pod *api.Pod) (bool, error) { + podSource, err := kubetypes.GetPodSource(pod) + if err != nil { + return false, err + } + for _, source := range capabilities.Get().PrivilegedSources.HostIPCSources { + if source == podSource { + return true, nil + } + } + return false, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/util/doc.go b/vendor/k8s.io/kubernetes/pkg/kubelet/util/doc.go new file mode 100644 index 000000000..b7e74c7f2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/util/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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. +*/ + +// Utility functions. +package util diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/util/format/pod.go b/vendor/k8s.io/kubernetes/pkg/kubelet/util/format/pod.go new file mode 100644 index 000000000..506f2a785 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/util/format/pod.go @@ -0,0 +1,48 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 format + +import ( + "fmt" + "strings" + + "k8s.io/kubernetes/pkg/api" +) + +type podHandler func(*api.Pod) string + +// Pod returns a string reprenetating a pod in a human readable format, +// with pod UID as part of the string. +func Pod(pod *api.Pod) string { + // Use underscore as the delimiter because it is not allowed in pod name + // (DNS subdomain format), while allowed in the container name format. + return fmt.Sprintf("%s_%s(%s)", pod.Name, pod.Namespace, pod.UID) +} + +// Pods returns a string representating a list of pods in a human +// readable format. +func Pods(pods []*api.Pod) string { + return aggregatePods(pods, Pod) +} + +func aggregatePods(pods []*api.Pod, handler podHandler) string { + podStrings := make([]string, 0, len(pods)) + for _, pod := range pods { + podStrings = append(podStrings, handler(pod)) + } + return fmt.Sprintf(strings.Join(podStrings, ", ")) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/util/queue/work_queue.go b/vendor/k8s.io/kubernetes/pkg/kubelet/util/queue/work_queue.go new file mode 100644 index 000000000..c54d93e62 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/util/queue/work_queue.go @@ -0,0 +1,67 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 queue + +import ( + "sync" + "time" + + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util" +) + +// WorkQueue allows queuing items with a timestamp. An item is +// considered ready to process if the timestamp has expired. +type WorkQueue interface { + // GetWork dequeues and returns all ready items. + GetWork() []types.UID + // Enqueue inserts a new item or overwrites an existing item. + Enqueue(item types.UID, delay time.Duration) +} + +type basicWorkQueue struct { + clock util.Clock + lock sync.Mutex + queue map[types.UID]time.Time +} + +var _ WorkQueue = &basicWorkQueue{} + +func NewBasicWorkQueue() WorkQueue { + queue := make(map[types.UID]time.Time) + return &basicWorkQueue{queue: queue, clock: util.RealClock{}} +} + +func (q *basicWorkQueue) GetWork() []types.UID { + q.lock.Lock() + defer q.lock.Unlock() + now := q.clock.Now() + var items []types.UID + for k, v := range q.queue { + if v.Before(now) { + items = append(items, k) + delete(q.queue, k) + } + } + return items +} + +func (q *basicWorkQueue) Enqueue(item types.UID, delay time.Duration) { + q.lock.Lock() + defer q.lock.Unlock() + q.queue[item] = q.clock.Now().Add(delay) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/util/queue/work_queue_test.go b/vendor/k8s.io/kubernetes/pkg/kubelet/util/queue/work_queue_test.go new file mode 100644 index 000000000..40ba6d95d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/util/queue/work_queue_test.go @@ -0,0 +1,65 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 queue + +import ( + "testing" + "time" + + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/sets" +) + +func newTestBasicWorkQueue() (*basicWorkQueue, *util.FakeClock) { + fakeClock := util.NewFakeClock(time.Now()) + wq := &basicWorkQueue{ + clock: fakeClock, + queue: make(map[types.UID]time.Time), + } + return wq, fakeClock +} + +func compareResults(t *testing.T, expected, actual []types.UID) { + expectedSet := sets.NewString() + for _, u := range expected { + expectedSet.Insert(string(u)) + } + actualSet := sets.NewString() + for _, u := range actual { + actualSet.Insert(string(u)) + } + if !expectedSet.Equal(actualSet) { + t.Errorf("Expected %#v, got %#v", expectedSet.List(), actualSet.List()) + } +} + +func TestGetWork(t *testing.T) { + q, clock := newTestBasicWorkQueue() + q.Enqueue(types.UID("foo1"), -1*time.Minute) + q.Enqueue(types.UID("foo2"), -1*time.Minute) + q.Enqueue(types.UID("foo3"), 1*time.Minute) + q.Enqueue(types.UID("foo4"), 1*time.Minute) + expected := []types.UID{types.UID("foo1"), types.UID("foo2")} + compareResults(t, expected, q.GetWork()) + compareResults(t, []types.UID{}, q.GetWork()) + // Dial the time to 1 hour ahead. + clock.Step(time.Hour) + expected = []types.UID{types.UID("foo3"), types.UID("foo4")} + compareResults(t, expected, q.GetWork()) + compareResults(t, []types.UID{}, q.GetWork()) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/volume_manager.go b/vendor/k8s.io/kubernetes/pkg/kubelet/volume_manager.go new file mode 100644 index 000000000..d72432518 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/volume_manager.go @@ -0,0 +1,62 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "sync" + + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/types" +) + +// volumeManager manages the volumes for the pods running on the kubelet. +// Currently it only does book keeping, but it can be expanded to +// take care of the volumePlugins. +type volumeManager struct { + lock sync.RWMutex + volumeMaps map[types.UID]kubecontainer.VolumeMap +} + +func newVolumeManager() *volumeManager { + vm := &volumeManager{} + vm.volumeMaps = make(map[types.UID]kubecontainer.VolumeMap) + return vm +} + +// SetVolumes sets the volume map for a pod. +// TODO(yifan): Currently we assume the volume is already mounted, so we only do a book keeping here. +func (vm *volumeManager) SetVolumes(podUID types.UID, podVolumes kubecontainer.VolumeMap) { + vm.lock.Lock() + defer vm.lock.Unlock() + vm.volumeMaps[podUID] = podVolumes +} + +// GetVolumes returns the volume map which are already mounted on the host machine +// for a pod. +func (vm *volumeManager) GetVolumes(podUID types.UID) (kubecontainer.VolumeMap, bool) { + vm.lock.RLock() + defer vm.lock.RUnlock() + vol, ok := vm.volumeMaps[podUID] + return vol, ok +} + +// DeleteVolumes removes the reference to a volume map for a pod. +func (vm *volumeManager) DeleteVolumes(podUID types.UID) { + vm.lock.Lock() + defer vm.lock.Unlock() + delete(vm.volumeMaps, podUID) +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubelet/volumes.go b/vendor/k8s.io/kubernetes/pkg/kubelet/volumes.go new file mode 100644 index 000000000..448a8e107 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubelet/volumes.go @@ -0,0 +1,349 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 kubelet + +import ( + "fmt" + "io/ioutil" + "path" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/cloudprovider" + kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/io" + "k8s.io/kubernetes/pkg/util/mount" + "k8s.io/kubernetes/pkg/util/strings" + "k8s.io/kubernetes/pkg/volume" +) + +var errUnsupportedVolumeType = fmt.Errorf("unsupported volume type") + +// This just exports required functions from kubelet proper, for use by volume +// plugins. +type volumeHost struct { + kubelet *Kubelet +} + +func (vh *volumeHost) GetPluginDir(pluginName string) string { + return vh.kubelet.getPluginDir(pluginName) +} + +func (vh *volumeHost) GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string { + return vh.kubelet.getPodVolumeDir(podUID, pluginName, volumeName) +} + +func (vh *volumeHost) GetPodPluginDir(podUID types.UID, pluginName string) string { + return vh.kubelet.getPodPluginDir(podUID, pluginName) +} + +func (vh *volumeHost) GetKubeClient() clientset.Interface { + return vh.kubelet.kubeClient +} + +func (vh *volumeHost) NewWrapperMounter(volName string, spec volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { + // The name of wrapper volume is set to "wrapped_{wrapped_volume_name}" + wrapperVolumeName := "wrapped_" + volName + if spec.Volume != nil { + spec.Volume.Name = wrapperVolumeName + } + + b, err := vh.kubelet.newVolumeMounterFromPlugins(&spec, pod, opts) + if err == nil && b == nil { + return nil, errUnsupportedVolumeType + } + return b, nil +} + +func (vh *volumeHost) NewWrapperUnmounter(volName string, spec volume.Spec, podUID types.UID) (volume.Unmounter, error) { + // The name of wrapper volume is set to "wrapped_{wrapped_volume_name}" + wrapperVolumeName := "wrapped_" + volName + if spec.Volume != nil { + spec.Volume.Name = wrapperVolumeName + } + + plugin, err := vh.kubelet.volumePluginMgr.FindPluginBySpec(&spec) + if err != nil { + return nil, err + } + if plugin == nil { + // Not found but not an error + return nil, nil + } + c, err := plugin.NewUnmounter(spec.Name(), podUID) + if err == nil && c == nil { + return nil, errUnsupportedVolumeType + } + return c, nil +} + +func (vh *volumeHost) GetCloudProvider() cloudprovider.Interface { + return vh.kubelet.cloud +} + +func (vh *volumeHost) GetMounter() mount.Interface { + return vh.kubelet.mounter +} + +func (vh *volumeHost) GetWriter() io.Writer { + return vh.kubelet.writer +} + +// Returns the hostname of the host kubelet is running on +func (vh *volumeHost) GetHostName() string { + return vh.kubelet.hostname +} + +// mountExternalVolumes mounts the volumes declared in a pod, attaching them +// to the host if necessary, and returns a map containing information about +// the volumes for the pod or an error. This method is run multiple times, +// and requires that implementations of Attach() and SetUp() be idempotent. +// +// Note, in the future, the attach-detach controller will handle attaching and +// detaching volumes; this call site will be maintained for backward- +// compatibility with current behavior of static pods and pods created via the +// Kubelet's http API. +func (kl *Kubelet) mountExternalVolumes(pod *api.Pod) (kubecontainer.VolumeMap, error) { + podVolumes := make(kubecontainer.VolumeMap) + for i := range pod.Spec.Volumes { + volSpec := &pod.Spec.Volumes[i] + var fsGroup *int64 + if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.FSGroup != nil { + fsGroup = pod.Spec.SecurityContext.FSGroup + } + + rootContext, err := kl.getRootDirContext() + if err != nil { + return nil, err + } + + // Try to use a plugin for this volume. + internal := volume.NewSpecFromVolume(volSpec) + mounter, err := kl.newVolumeMounterFromPlugins(internal, pod, volume.VolumeOptions{RootContext: rootContext}) + if err != nil { + glog.Errorf("Could not create volume mounter for pod %s: %v", pod.UID, err) + return nil, err + } + if mounter == nil { + return nil, errUnsupportedVolumeType + } + + // some volumes require attachment before mounter's setup. + // The plugin can be nil, but non-nil errors are legitimate errors. + // For non-nil plugins, Attachment to a node is required before Mounter's setup. + attacher, err := kl.newVolumeAttacherFromPlugins(internal, pod, volume.VolumeOptions{RootContext: rootContext}) + if err != nil { + glog.Errorf("Could not create volume attacher for pod %s: %v", pod.UID, err) + return nil, err + } + if attacher != nil { + err = attacher.Attach() + if err != nil { + return nil, err + } + } + + err = mounter.SetUp(fsGroup) + if err != nil { + return nil, err + } + podVolumes[volSpec.Name] = kubecontainer.VolumeInfo{Mounter: mounter} + } + return podVolumes, nil +} + +type volumeTuple struct { + Kind string + Name string +} + +// ListVolumesForPod returns a map of the volumes associated with the given pod +func (kl *Kubelet) ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool) { + result := map[string]volume.Volume{} + vm, ok := kl.volumeManager.GetVolumes(podUID) + if !ok { + return result, false + } + for name, info := range vm { + result[name] = info.Mounter + } + return result, true +} + +// getPodVolumes examines the directory structure for a pod and returns +// information about the name and kind of each presently mounted volume, or an +// error. +func (kl *Kubelet) getPodVolumes(podUID types.UID) ([]*volumeTuple, error) { + var volumes []*volumeTuple + podVolDir := kl.getPodVolumesDir(podUID) + volumeKindDirs, err := ioutil.ReadDir(podVolDir) + if err != nil { + glog.Errorf("Could not read directory %s: %v", podVolDir, err) + } + for _, volumeKindDir := range volumeKindDirs { + volumeKind := volumeKindDir.Name() + volumeKindPath := path.Join(podVolDir, volumeKind) + // ioutil.ReadDir exits without returning any healthy dir when encountering the first lstat error + // but skipping dirs means no cleanup for healthy volumes. switching to a no-exit api solves this problem + volumeNameDirs, volumeNameDirsStat, err := util.ReadDirNoExit(volumeKindPath) + if err != nil { + return []*volumeTuple{}, fmt.Errorf("could not read directory %s: %v", volumeKindPath, err) + } + for i, volumeNameDir := range volumeNameDirs { + if volumeNameDir != nil { + volumes = append(volumes, &volumeTuple{Kind: volumeKind, Name: volumeNameDir.Name()}) + } else { + glog.Errorf("Could not read directory %s: %v", podVolDir, volumeNameDirsStat[i]) + } + } + } + return volumes, nil +} + +// cleanerTuple is a union struct to allow separating detaching from the cleaner. +// some volumes require detachment but not all. Unmounter cannot be nil but Detacher is optional. +type cleanerTuple struct { + Unmounter volume.Unmounter + Detacher *volume.Detacher +} + +// getPodVolumesFromDisk examines directory structure to determine volumes that +// are presently active and mounted. Returns a union struct containing a volume.Unmounter +// and potentially a volume.Detacher. +func (kl *Kubelet) getPodVolumesFromDisk() map[string]cleanerTuple { + currentVolumes := make(map[string]cleanerTuple) + podUIDs, err := kl.listPodsFromDisk() + if err != nil { + glog.Errorf("Could not get pods from disk: %v", err) + return map[string]cleanerTuple{} + } + // Find the volumes for each on-disk pod. + for _, podUID := range podUIDs { + volumes, err := kl.getPodVolumes(podUID) + if err != nil { + glog.Errorf("%v", err) + continue + } + for _, volume := range volumes { + identifier := fmt.Sprintf("%s/%s", podUID, volume.Name) + glog.V(4).Infof("Making a volume.Unmounter for volume %s/%s of pod %s", volume.Kind, volume.Name, podUID) + // TODO(thockin) This should instead return a reference to an extant + // volume object, except that we don't actually hold on to pod specs + // or volume objects. + + // Try to use a plugin for this volume. + unmounter, err := kl.newVolumeUnmounterFromPlugins(volume.Kind, volume.Name, podUID) + if err != nil { + glog.Errorf("Could not create volume unmounter for %s: %v", volume.Name, err) + continue + } + if unmounter == nil { + glog.Errorf("Could not create volume unmounter for %s: %v", volume.Name, errUnsupportedVolumeType) + continue + } + + tuple := cleanerTuple{Unmounter: unmounter} + detacher, err := kl.newVolumeDetacherFromPlugins(volume.Kind, volume.Name, podUID) + // plugin can be nil but a non-nil error is a legitimate error + if err != nil { + glog.Errorf("Could not create volume detacher for %s: %v", volume.Name, err) + continue + } + if detacher != nil { + tuple.Detacher = &detacher + } + currentVolumes[identifier] = tuple + } + } + return currentVolumes +} + +func (kl *Kubelet) newVolumeMounterFromPlugins(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Mounter, error) { + plugin, err := kl.volumePluginMgr.FindPluginBySpec(spec) + if err != nil { + return nil, fmt.Errorf("can't use volume plugins for %s: %v", spec.Name(), err) + } + if plugin == nil { + // Not found but not an error + return nil, nil + } + physicalMounter, err := plugin.NewMounter(spec, pod, opts) + if err != nil { + return nil, fmt.Errorf("failed to instantiate volume physicalMounter for %s: %v", spec.Name(), err) + } + glog.V(10).Infof("Used volume plugin %q to mount %s", plugin.Name(), spec.Name()) + return physicalMounter, nil +} + +func (kl *Kubelet) newVolumeAttacherFromPlugins(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Attacher, error) { + plugin, err := kl.volumePluginMgr.FindAttachablePluginBySpec(spec) + if err != nil { + return nil, fmt.Errorf("can't use volume plugins for %s: %v", spec.Name(), err) + } + if plugin == nil { + // Not found but not an error. + return nil, nil + } + + attacher, err := plugin.NewAttacher(spec) + if err != nil { + return nil, fmt.Errorf("failed to instantiate volume attacher for %s: %v", spec.Name(), err) + } + glog.V(3).Infof("Used volume plugin %q to attach %s/%s", plugin.Name(), spec.Name()) + return attacher, nil +} + +func (kl *Kubelet) newVolumeUnmounterFromPlugins(kind string, name string, podUID types.UID) (volume.Unmounter, error) { + plugName := strings.UnescapeQualifiedNameForDisk(kind) + plugin, err := kl.volumePluginMgr.FindPluginByName(plugName) + if err != nil { + // TODO: Maybe we should launch a cleanup of this dir? + return nil, fmt.Errorf("can't use volume plugins for %s/%s: %v", podUID, kind, err) + } + if plugin == nil { + // Not found but not an error. + return nil, nil + } + unmounter, err := plugin.NewUnmounter(name, podUID) + if err != nil { + return nil, fmt.Errorf("failed to instantiate volume plugin for %s/%s: %v", podUID, kind, err) + } + glog.V(5).Infof("Used volume plugin %q to unmount %s/%s", plugin.Name(), podUID, kind) + return unmounter, nil +} + +func (kl *Kubelet) newVolumeDetacherFromPlugins(kind string, name string, podUID types.UID) (volume.Detacher, error) { + plugName := strings.UnescapeQualifiedNameForDisk(kind) + plugin, err := kl.volumePluginMgr.FindAttachablePluginByName(plugName) + if err != nil { + return nil, fmt.Errorf("can't use volume plugins for %s/%s: %v", podUID, kind, err) + } + if plugin == nil { + // Not found but not an error. + return nil, nil + } + + detacher, err := plugin.NewDetacher(name, podUID) + if err != nil { + return nil, fmt.Errorf("failed to instantiate volume plugin for %s/%s: %v", podUID, kind, err) + } + glog.V(3).Infof("Used volume plugin %q to detach %s/%s", plugin.Name(), podUID, kind) + return detacher, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubemark/hollow_kubelet.go b/vendor/k8s.io/kubernetes/pkg/kubemark/hollow_kubelet.go new file mode 100644 index 000000000..3ea315e23 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubemark/hollow_kubelet.go @@ -0,0 +1,86 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubemark + +import ( + "time" + + kubeletapp "k8s.io/kubernetes/cmd/kubelet/app" + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/kubelet/cadvisor" + "k8s.io/kubernetes/pkg/kubelet/cm" + containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" + "k8s.io/kubernetes/pkg/kubelet/dockertools" + "k8s.io/kubernetes/pkg/volume/empty_dir" + "k8s.io/kubernetes/test/integration" + + "github.com/golang/glog" +) + +type HollowKubelet struct { + KubeletConfig *kubeletapp.KubeletConfig +} + +func NewHollowKubelet( + nodeName string, + client *clientset.Clientset, + cadvisorInterface cadvisor.Interface, + dockerClient dockertools.DockerInterface, + kubeletPort, kubeletReadOnlyPort int, + containerManager cm.ContainerManager, + maxPods int, +) *HollowKubelet { + testRootDir := integration.MakeTempDirOrDie("hollow-kubelet.", "") + manifestFilePath := integration.MakeTempDirOrDie("manifest", testRootDir) + glog.Infof("Using %s as root dir for hollow-kubelet", testRootDir) + + return &HollowKubelet{ + KubeletConfig: kubeletapp.SimpleKubelet( + client, + dockerClient, + nodeName, + testRootDir, + "", /* manifest-url */ + "0.0.0.0", /* bind address */ + uint(kubeletPort), + uint(kubeletReadOnlyPort), + api.NamespaceDefault, + empty_dir.ProbeVolumePlugins(), + nil, /* tls-options */ + cadvisorInterface, + manifestFilePath, + nil, /* cloud-provider */ + containertest.FakeOS{}, /* os-interface */ + 20*time.Second, /* FileCheckFrequency */ + 20*time.Second, /* HTTPCheckFrequency */ + 1*time.Minute, /* MinimumGCAge */ + 10*time.Second, /* NodeStatusUpdateFrequency */ + 10*time.Second, /* SyncFrequency */ + 5*time.Minute, /* OutOfDiskTransitionFrequency */ + maxPods, + containerManager, + nil, + ), + } +} + +// Starts this HollowKubelet and blocks. +func (hk *HollowKubelet) Run() { + kubeletapp.RunKubelet(hk.KubeletConfig) + select {} +} diff --git a/vendor/k8s.io/kubernetes/pkg/kubemark/hollow_proxy.go b/vendor/k8s.io/kubernetes/pkg/kubemark/hollow_proxy.go new file mode 100644 index 000000000..7a3920eef --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/kubemark/hollow_proxy.go @@ -0,0 +1,91 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubemark + +import ( + "time" + + proxyapp "k8s.io/kubernetes/cmd/kube-proxy/app" + "k8s.io/kubernetes/cmd/kube-proxy/app/options" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/record" + client "k8s.io/kubernetes/pkg/client/unversioned" + proxyconfig "k8s.io/kubernetes/pkg/proxy/config" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util" + utiliptables "k8s.io/kubernetes/pkg/util/iptables" + + "github.com/golang/glog" +) + +type HollowProxy struct { + ProxyServer *proxyapp.ProxyServer +} + +type FakeProxyHandler struct{} + +func (*FakeProxyHandler) OnServiceUpdate(services []api.Service) {} +func (*FakeProxyHandler) OnEndpointsUpdate(endpoints []api.Endpoints) {} + +type FakeProxier struct{} + +func (*FakeProxier) OnServiceUpdate(services []api.Service) {} +func (*FakeProxier) Sync() {} +func (*FakeProxier) SyncLoop() { + select {} +} + +func NewHollowProxyOrDie( + nodeName string, + client *client.Client, + endpointsConfig *proxyconfig.EndpointsConfig, + serviceConfig *proxyconfig.ServiceConfig, + iptInterface utiliptables.Interface, + broadcaster record.EventBroadcaster, + recorder record.EventRecorder, +) *HollowProxy { + // Create and start Hollow Proxy + config := options.NewProxyConfig() + config.OOMScoreAdj = util.IntPtr(0) + config.ResourceContainer = "" + config.NodeRef = &api.ObjectReference{ + Kind: "Node", + Name: nodeName, + UID: types.UID(nodeName), + Namespace: "", + } + proxyconfig.NewSourceAPI( + client, + 30*time.Second, + serviceConfig.Channel("api"), + endpointsConfig.Channel("api"), + ) + + hollowProxy, err := proxyapp.NewProxyServer(client, config, iptInterface, &FakeProxier{}, broadcaster, recorder, nil, "fake") + if err != nil { + glog.Fatalf("Error while creating ProxyServer: %v\n", err) + } + return &HollowProxy{ + ProxyServer: hollowProxy, + } +} + +func (hp *HollowProxy) Run() { + if err := hp.ProxyServer.Run(); err != nil { + glog.Fatalf("Error while running proxy: %v\n", err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/labels/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/labels/deep_copy_generated.go new file mode 100644 index 000000000..e48099d2d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/labels/deep_copy_generated.go @@ -0,0 +1,45 @@ +// +build !ignore_autogenerated + +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package labels + +import ( + conversion "k8s.io/kubernetes/pkg/conversion" + sets "k8s.io/kubernetes/pkg/util/sets" +) + +func DeepCopy_labels_Requirement(in Requirement, out *Requirement, c *conversion.Cloner) error { + out.key = in.key + out.operator = in.operator + if in.strValues != nil { + in, out := in.strValues, &out.strValues + *out = make(sets.String) + for key, val := range in { + newVal := new(sets.Empty) + if err := sets.DeepCopy_sets_Empty(val, newVal, c); err != nil { + return err + } + (*out)[key] = *newVal + } + } else { + out.strValues = nil + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/labels/selector.go b/vendor/k8s.io/kubernetes/pkg/labels/selector.go index 520377486..fb48b10e9 100644 --- a/vendor/k8s.io/kubernetes/pkg/labels/selector.go +++ b/vendor/k8s.io/kubernetes/pkg/labels/selector.go @@ -72,8 +72,8 @@ const ( NotEqualsOperator Operator = "!=" NotInOperator Operator = "notin" ExistsOperator Operator = "exists" - GreaterThanOperator Operator = "Gt" - LessThanOperator Operator = "Lt" + GreaterThanOperator Operator = "gt" + LessThanOperator Operator = "lt" ) func NewSelector() Selector { @@ -743,13 +743,25 @@ func (p *Parser) parseExactValue() (sets.String, error) { // (5) A requirement with just !KEY requires that the KEY not exist. // func Parse(selector string) (Selector, error) { - p := &Parser{l: &Lexer{s: selector, pos: 0}} - items, error := p.parse() - if error == nil { - sort.Sort(ByKey(items)) // sort to grant determistic parsing - return internalSelector(items), error + parsedSelector, err := parse(selector) + if err == nil { + return parsedSelector, nil } - return nil, error + return nil, err +} + +// parse parses the string representation of the selector and returns the internalSelector struct. +// The callers of this method can then decide how to return the internalSelector struct to their +// callers. This function has two callers now, one returns a Selector interface and the other +// returns a list of requirements. +func parse(selector string) (internalSelector, error) { + p := &Parser{l: &Lexer{s: selector, pos: 0}} + items, err := p.parse() + if err != nil { + return nil, err + } + sort.Sort(ByKey(items)) // sort to grant determistic parsing + return internalSelector(items), err } var qualifiedNameErrorMsg string = fmt.Sprintf(`must be a qualified name (at most %d characters, matching regex %s), with an optional DNS subdomain prefix (at most %d characters, matching regex %s) and slash (/): e.g. "MyName" or "example.com/MyName"`, validation.QualifiedNameMaxLength, validation.QualifiedNameFmt, validation.DNS1123SubdomainMaxLength, validation.DNS1123SubdomainFmt) @@ -788,3 +800,12 @@ func SelectorFromSet(ls Set) Selector { sort.Sort(ByKey(requirements)) return internalSelector(requirements) } + +// ParseToRequirements takes a string representing a selector and returns a list of +// requirements. This function is suitable for those callers that perform additional +// processing on selector requirements. +// See the documentation for Parse() function for more details. +// TODO: Consider exporting the internalSelector type instead. +func ParseToRequirements(selector string) ([]Requirement, error) { + return parse(selector) +} diff --git a/vendor/k8s.io/kubernetes/pkg/master/OWNERS b/vendor/k8s.io/kubernetes/pkg/master/OWNERS new file mode 100644 index 000000000..eb143ef50 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/master/OWNERS @@ -0,0 +1,6 @@ +assignees: + - davidopp + - derekwaynecarr + - lavalamp + - mikedanese + - nikhiljindal diff --git a/vendor/k8s.io/kubernetes/pkg/master/controller.go b/vendor/k8s.io/kubernetes/pkg/master/controller.go new file mode 100644 index 000000000..ca40a6839 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/master/controller.go @@ -0,0 +1,376 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 master + +import ( + "fmt" + "net" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/endpoints" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/registry/endpoint" + "k8s.io/kubernetes/pkg/registry/namespace" + "k8s.io/kubernetes/pkg/registry/service" + servicecontroller "k8s.io/kubernetes/pkg/registry/service/ipallocator/controller" + portallocatorcontroller "k8s.io/kubernetes/pkg/registry/service/portallocator/controller" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/intstr" + utilnet "k8s.io/kubernetes/pkg/util/net" + "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" +) + +// Controller is the controller manager for the core bootstrap Kubernetes controller +// loops, which manage creating the "kubernetes" service, the "default" +// namespace, and provide the IP repair check on service IPs +type Controller struct { + NamespaceRegistry namespace.Registry + ServiceRegistry service.Registry + // TODO: MasterCount is yucky + MasterCount int + + ServiceClusterIPRegistry service.RangeRegistry + ServiceClusterIPInterval time.Duration + ServiceClusterIPRange *net.IPNet + + ServiceNodePortRegistry service.RangeRegistry + ServiceNodePortInterval time.Duration + ServiceNodePortRange utilnet.PortRange + + EndpointRegistry endpoint.Registry + EndpointInterval time.Duration + + PublicIP net.IP + + ServiceIP net.IP + ServicePort int + ExtraServicePorts []api.ServicePort + ExtraEndpointPorts []api.EndpointPort + PublicServicePort int + KubernetesServiceNodePort int + + runner *util.Runner +} + +// Start begins the core controller loops that must exist for bootstrapping +// a cluster. +func (c *Controller) Start() { + if c.runner != nil { + return + } + + repairClusterIPs := servicecontroller.NewRepair(c.ServiceClusterIPInterval, c.ServiceRegistry, c.ServiceClusterIPRange, c.ServiceClusterIPRegistry) + repairNodePorts := portallocatorcontroller.NewRepair(c.ServiceNodePortInterval, c.ServiceRegistry, c.ServiceNodePortRange, c.ServiceNodePortRegistry) + + // run all of the controllers once prior to returning from Start. + if err := repairClusterIPs.RunOnce(); err != nil { + // If we fail to repair cluster IPs apiserver is useless. We should restart and retry. + glog.Fatalf("Unable to perform initial IP allocation check: %v", err) + } + if err := repairNodePorts.RunOnce(); err != nil { + // If we fail to repair node ports apiserver is useless. We should restart and retry. + glog.Fatalf("Unable to perform initial service nodePort check: %v", err) + } + // Service definition is reconciled during first run to correct port and type per expectations. + if err := c.UpdateKubernetesService(true); err != nil { + glog.Errorf("Unable to perform initial Kubernetes service initialization: %v", err) + } + + c.runner = util.NewRunner(c.RunKubernetesService, repairClusterIPs.RunUntil, repairNodePorts.RunUntil) + c.runner.Start() +} + +// RunKubernetesService periodically updates the kubernetes service +func (c *Controller) RunKubernetesService(ch chan struct{}) { + wait.Until(func() { + // Service definition is not reconciled after first + // run, ports and type will be corrected only during + // start. + if err := c.UpdateKubernetesService(false); err != nil { + runtime.HandleError(fmt.Errorf("unable to sync kubernetes service: %v", err)) + } + }, c.EndpointInterval, ch) +} + +// UpdateKubernetesService attempts to update the default Kube service. +func (c *Controller) UpdateKubernetesService(reconcile bool) error { + // Update service & endpoint records. + // TODO: when it becomes possible to change this stuff, + // stop polling and start watching. + // TODO: add endpoints of all replicas, not just the elected master. + if err := c.CreateNamespaceIfNeeded(api.NamespaceDefault); err != nil { + return err + } + if c.ServiceIP != nil { + servicePorts, serviceType := createPortAndServiceSpec(c.ServicePort, c.KubernetesServiceNodePort, "https", c.ExtraServicePorts) + if err := c.CreateOrUpdateMasterServiceIfNeeded("kubernetes", c.ServiceIP, servicePorts, serviceType, reconcile); err != nil { + return err + } + endpointPorts := createEndpointPortSpec(c.PublicServicePort, "https", c.ExtraEndpointPorts) + if err := c.ReconcileEndpoints("kubernetes", c.PublicIP, endpointPorts, reconcile); err != nil { + return err + } + } + return nil +} + +// CreateNamespaceIfNeeded will create the namespace that contains the master services if it doesn't already exist +func (c *Controller) CreateNamespaceIfNeeded(ns string) error { + ctx := api.NewContext() + if _, err := c.NamespaceRegistry.GetNamespace(ctx, api.NamespaceDefault); err == nil { + // the namespace already exists + return nil + } + newNs := &api.Namespace{ + ObjectMeta: api.ObjectMeta{ + Name: ns, + Namespace: "", + }, + } + err := c.NamespaceRegistry.CreateNamespace(ctx, newNs) + if err != nil && errors.IsAlreadyExists(err) { + err = nil + } + return err +} + +// createPortAndServiceSpec creates an array of service ports. +// If the NodePort value is 0, just the servicePort is used, otherwise, a node port is exposed. +func createPortAndServiceSpec(servicePort int, nodePort int, servicePortName string, extraServicePorts []api.ServicePort) ([]api.ServicePort, api.ServiceType) { + //Use the Cluster IP type for the service port if NodePort isn't provided. + //Otherwise, we will be binding the master service to a NodePort. + servicePorts := []api.ServicePort{{Protocol: api.ProtocolTCP, + Port: servicePort, + Name: servicePortName, + TargetPort: intstr.FromInt(servicePort)}} + serviceType := api.ServiceTypeClusterIP + if nodePort > 0 { + servicePorts[0].NodePort = nodePort + serviceType = api.ServiceTypeNodePort + } + if extraServicePorts != nil { + servicePorts = append(servicePorts, extraServicePorts...) + } + return servicePorts, serviceType +} + +// createEndpointPortSpec creates an array of endpoint ports +func createEndpointPortSpec(endpointPort int, endpointPortName string, extraEndpointPorts []api.EndpointPort) []api.EndpointPort { + endpointPorts := []api.EndpointPort{{Protocol: api.ProtocolTCP, + Port: endpointPort, + Name: endpointPortName, + }} + if extraEndpointPorts != nil { + endpointPorts = append(endpointPorts, extraEndpointPorts...) + } + return endpointPorts +} + +// CreateMasterServiceIfNeeded will create the specified service if it +// doesn't already exist. +func (c *Controller) CreateOrUpdateMasterServiceIfNeeded(serviceName string, serviceIP net.IP, servicePorts []api.ServicePort, serviceType api.ServiceType, reconcile bool) error { + ctx := api.NewDefaultContext() + if s, err := c.ServiceRegistry.GetService(ctx, serviceName); err == nil { + // The service already exists. + if reconcile { + if svc, updated := getMasterServiceUpdateIfNeeded(s, servicePorts, serviceType); updated { + glog.Warningf("Resetting master service %q to %#v", serviceName, svc) + _, err := c.ServiceRegistry.UpdateService(ctx, svc) + return err + } + } + return nil + } + svc := &api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: serviceName, + Namespace: api.NamespaceDefault, + Labels: map[string]string{"provider": "kubernetes", "component": "apiserver"}, + }, + Spec: api.ServiceSpec{ + Ports: servicePorts, + // maintained by this code, not by the pod selector + Selector: nil, + ClusterIP: serviceIP.String(), + SessionAffinity: api.ServiceAffinityClientIP, + Type: serviceType, + }, + } + if err := rest.BeforeCreate(service.Strategy, ctx, svc); err != nil { + return err + } + + _, err := c.ServiceRegistry.CreateService(ctx, svc) + if err != nil && errors.IsAlreadyExists(err) { + err = nil + } + return err +} + +// ReconcileEndpoints sets the endpoints for the given apiserver service (ro or rw). +// ReconcileEndpoints expects that the endpoints objects it manages will all be +// managed only by ReconcileEndpoints; therefore, to understand this, you need only +// understand the requirements and the body of this function. +// +// Requirements: +// * All apiservers MUST use the same ports for their {rw, ro} services. +// * All apiservers MUST use ReconcileEndpoints and only ReconcileEndpoints to manage the +// endpoints for their {rw, ro} services. +// * All apiservers MUST know and agree on the number of apiservers expected +// to be running (c.masterCount). +// * ReconcileEndpoints is called periodically from all apiservers. +// +func (c *Controller) ReconcileEndpoints(serviceName string, ip net.IP, endpointPorts []api.EndpointPort, reconcilePorts bool) error { + ctx := api.NewDefaultContext() + e, err := c.EndpointRegistry.GetEndpoints(ctx, serviceName) + if err != nil { + e = &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: serviceName, + Namespace: api.NamespaceDefault, + }, + } + } + + // First, determine if the endpoint is in the format we expect (one + // subset, ports matching endpointPorts, N IP addresses). + formatCorrect, ipCorrect, portsCorrect := checkEndpointSubsetFormat(e, ip.String(), endpointPorts, c.MasterCount, reconcilePorts) + if !formatCorrect { + // Something is egregiously wrong, just re-make the endpoints record. + e.Subsets = []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: ip.String()}}, + Ports: endpointPorts, + }} + glog.Warningf("Resetting endpoints for master service %q to %v", serviceName, e) + return c.EndpointRegistry.UpdateEndpoints(ctx, e) + } + if ipCorrect && portsCorrect { + return nil + } + if !ipCorrect { + // We *always* add our own IP address. + e.Subsets[0].Addresses = append(e.Subsets[0].Addresses, api.EndpointAddress{IP: ip.String()}) + + // Lexicographic order is retained by this step. + e.Subsets = endpoints.RepackSubsets(e.Subsets) + + // If too many IP addresses, remove the ones lexicographically after our + // own IP address. Given the requirements stated at the top of + // this function, this should cause the list of IP addresses to + // become eventually correct. + if addrs := &e.Subsets[0].Addresses; len(*addrs) > c.MasterCount { + // addrs is a pointer because we're going to mutate it. + for i, addr := range *addrs { + if addr.IP == ip.String() { + for len(*addrs) > c.MasterCount { + // wrap around if necessary. + remove := (i + 1) % len(*addrs) + *addrs = append((*addrs)[:remove], (*addrs)[remove+1:]...) + } + break + } + } + } + } + if !portsCorrect { + // Reset ports. + e.Subsets[0].Ports = endpointPorts + } + glog.Warningf("Resetting endpoints for master service %q to %v", serviceName, e) + return c.EndpointRegistry.UpdateEndpoints(ctx, e) +} + +// Determine if the endpoint is in the format ReconcileEndpoints expects. +// +// Return values: +// * formatCorrect is true if exactly one subset is found. +// * ipCorrect is true when current master's IP is found and the number +// of addresses is less than or equal to the master count. +// * portsCorrect is true when endpoint ports exactly match provided ports. +// portsCorrect is only evaluated when reconcilePorts is set to true. +func checkEndpointSubsetFormat(e *api.Endpoints, ip string, ports []api.EndpointPort, count int, reconcilePorts bool) (formatCorrect bool, ipCorrect bool, portsCorrect bool) { + if len(e.Subsets) != 1 { + return false, false, false + } + sub := &e.Subsets[0] + portsCorrect = true + if reconcilePorts { + if len(sub.Ports) != len(ports) { + portsCorrect = false + } + for i, port := range ports { + if len(sub.Ports) <= i || port != sub.Ports[i] { + portsCorrect = false + break + } + } + } + for _, addr := range sub.Addresses { + if addr.IP == ip { + ipCorrect = len(sub.Addresses) <= count + break + } + } + return true, ipCorrect, portsCorrect +} + +// * getMasterServiceUpdateIfNeeded sets service attributes for the +// given apiserver service. +// * getMasterServiceUpdateIfNeeded expects that the service object it +// manages will be managed only by getMasterServiceUpdateIfNeeded; +// therefore, to understand this, you need only understand the +// requirements and the body of this function. +// * getMasterServiceUpdateIfNeeded ensures that the correct ports are +// are set. +// +// Requirements: +// * All apiservers MUST use getMasterServiceUpdateIfNeeded and only +// getMasterServiceUpdateIfNeeded to manage service attributes +// * updateMasterService is called periodically from all apiservers. +func getMasterServiceUpdateIfNeeded(svc *api.Service, servicePorts []api.ServicePort, serviceType api.ServiceType) (s *api.Service, updated bool) { + // Determine if the service is in the format we expect + // (servicePorts are present and service type matches) + formatCorrect := checkServiceFormat(svc, servicePorts, serviceType) + if formatCorrect { + return svc, false + } + svc.Spec.Ports = servicePorts + svc.Spec.Type = serviceType + return svc, true +} + +// Determine if the service is in the correct format +// getMasterServiceUpdateIfNeeded expects (servicePorts are correct +// and service type matches). +func checkServiceFormat(s *api.Service, ports []api.ServicePort, serviceType api.ServiceType) (formatCorrect bool) { + if s.Spec.Type != serviceType { + return false + } + if len(ports) != len(s.Spec.Ports) { + return false + } + for i, port := range ports { + if port != s.Spec.Ports[i] { + return false + } + } + return true +} diff --git a/vendor/k8s.io/kubernetes/pkg/master/controller_test.go b/vendor/k8s.io/kubernetes/pkg/master/controller_test.go new file mode 100644 index 000000000..e79c4af69 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/master/controller_test.go @@ -0,0 +1,869 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 master + +import ( + "errors" + "net" + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/util/intstr" +) + +func TestReconcileEndpoints(t *testing.T) { + ns := api.NamespaceDefault + om := func(name string) api.ObjectMeta { + return api.ObjectMeta{Namespace: ns, Name: name} + } + reconcile_tests := []struct { + testName string + serviceName string + ip string + endpointPorts []api.EndpointPort + additionalMasters int + endpoints *api.EndpointsList + expectUpdate *api.Endpoints // nil means none expected + }{ + { + testName: "no existing endpoints", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + endpoints: nil, + expectUpdate: &api.Endpoints{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }, + }, + { + testName: "existing endpoints satisfy", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }}, + }, + }, + { + testName: "existing endpoints satisfy but too many", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}, {IP: "4.3.2.1"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }}, + }, + expectUpdate: &api.Endpoints{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }, + }, + { + testName: "existing endpoints satisfy but too many + extra masters", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + additionalMasters: 3, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "1.2.3.4"}, + {IP: "4.3.2.1"}, + {IP: "4.3.2.2"}, + {IP: "4.3.2.3"}, + {IP: "4.3.2.4"}, + }, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }}, + }, + expectUpdate: &api.Endpoints{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "1.2.3.4"}, + {IP: "4.3.2.2"}, + {IP: "4.3.2.3"}, + {IP: "4.3.2.4"}, + }, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }, + }, + { + testName: "existing endpoints satisfy but too many + extra masters + delete first", + serviceName: "foo", + ip: "4.3.2.4", + endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + additionalMasters: 3, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "1.2.3.4"}, + {IP: "4.3.2.1"}, + {IP: "4.3.2.2"}, + {IP: "4.3.2.3"}, + {IP: "4.3.2.4"}, + }, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }}, + }, + expectUpdate: &api.Endpoints{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "4.3.2.1"}, + {IP: "4.3.2.2"}, + {IP: "4.3.2.3"}, + {IP: "4.3.2.4"}, + }, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }, + }, + { + testName: "existing endpoints satisfy and endpoint addresses length less than master count", + serviceName: "foo", + ip: "4.3.2.2", + endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + additionalMasters: 3, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "4.3.2.1"}, + {IP: "4.3.2.2"}, + }, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }}, + }, + expectUpdate: nil, + }, + { + testName: "existing endpoints current IP missing and address length less than master count", + serviceName: "foo", + ip: "4.3.2.2", + endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + additionalMasters: 3, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "4.3.2.1"}, + }, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }}, + }, + expectUpdate: &api.Endpoints{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "4.3.2.1"}, + {IP: "4.3.2.2"}, + }, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }, + }, + { + testName: "existing endpoints wrong name", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("bar"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }}, + }, + expectUpdate: &api.Endpoints{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }, + }, + { + testName: "existing endpoints wrong IP", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "4.3.2.1"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }}, + }, + expectUpdate: &api.Endpoints{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }, + }, + { + testName: "existing endpoints wrong port", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 9090, Protocol: "TCP"}}, + }}, + }}, + }, + expectUpdate: &api.Endpoints{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }, + }, + { + testName: "existing endpoints wrong protocol", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "UDP"}}, + }}, + }}, + }, + expectUpdate: &api.Endpoints{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }, + }, + { + testName: "existing endpoints wrong port name", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{{Name: "baz", Port: 8080, Protocol: "TCP"}}, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }}, + }, + expectUpdate: &api.Endpoints{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "baz", Port: 8080, Protocol: "TCP"}}, + }}, + }, + }, + { + testName: "existing endpoints extra service ports satisfy", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{ + {Name: "foo", Port: 8080, Protocol: "TCP"}, + {Name: "bar", Port: 1000, Protocol: "TCP"}, + {Name: "baz", Port: 1010, Protocol: "TCP"}, + }, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{ + {Name: "foo", Port: 8080, Protocol: "TCP"}, + {Name: "bar", Port: 1000, Protocol: "TCP"}, + {Name: "baz", Port: 1010, Protocol: "TCP"}, + }, + }}, + }}, + }, + }, + { + testName: "existing endpoints extra service ports missing port", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{ + {Name: "foo", Port: 8080, Protocol: "TCP"}, + {Name: "bar", Port: 1000, Protocol: "TCP"}, + }, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }}, + }, + expectUpdate: &api.Endpoints{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{ + {Name: "foo", Port: 8080, Protocol: "TCP"}, + {Name: "bar", Port: 1000, Protocol: "TCP"}, + }, + }}, + }, + }, + } + for _, test := range reconcile_tests { + master := Controller{MasterCount: test.additionalMasters + 1} + registry := ®istrytest.EndpointRegistry{ + Endpoints: test.endpoints, + } + master.EndpointRegistry = registry + err := master.ReconcileEndpoints(test.serviceName, net.ParseIP(test.ip), test.endpointPorts, true) + if err != nil { + t.Errorf("case %q: unexpected error: %v", test.testName, err) + } + if test.expectUpdate != nil { + if len(registry.Updates) != 1 { + t.Errorf("case %q: unexpected updates: %v", test.testName, registry.Updates) + } else if e, a := test.expectUpdate, ®istry.Updates[0]; !reflect.DeepEqual(e, a) { + t.Errorf("case %q: expected update:\n%#v\ngot:\n%#v\n", test.testName, e, a) + } + } + if test.expectUpdate == nil && len(registry.Updates) > 0 { + t.Errorf("case %q: no update expected, yet saw: %v", test.testName, registry.Updates) + } + } + + non_reconcile_tests := []struct { + testName string + serviceName string + ip string + endpointPorts []api.EndpointPort + additionalMasters int + endpoints *api.EndpointsList + expectUpdate *api.Endpoints // nil means none expected + }{ + { + testName: "existing endpoints extra service ports missing port no update", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{ + {Name: "foo", Port: 8080, Protocol: "TCP"}, + {Name: "bar", Port: 1000, Protocol: "TCP"}, + }, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }}, + }, + expectUpdate: nil, + }, + { + testName: "existing endpoints extra service ports, wrong ports, wrong IP", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{ + {Name: "foo", Port: 8080, Protocol: "TCP"}, + {Name: "bar", Port: 1000, Protocol: "TCP"}, + }, + endpoints: &api.EndpointsList{ + Items: []api.Endpoints{{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "4.3.2.1"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }}, + }, + expectUpdate: &api.Endpoints{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }, + }, + { + testName: "no existing endpoints", + serviceName: "foo", + ip: "1.2.3.4", + endpointPorts: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + endpoints: nil, + expectUpdate: &api.Endpoints{ + ObjectMeta: om("foo"), + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "foo", Port: 8080, Protocol: "TCP"}}, + }}, + }, + }, + } + for _, test := range non_reconcile_tests { + master := Controller{MasterCount: test.additionalMasters + 1} + registry := ®istrytest.EndpointRegistry{ + Endpoints: test.endpoints, + } + master.EndpointRegistry = registry + err := master.ReconcileEndpoints(test.serviceName, net.ParseIP(test.ip), test.endpointPorts, false) + if err != nil { + t.Errorf("case %q: unexpected error: %v", test.testName, err) + } + if test.expectUpdate != nil { + if len(registry.Updates) != 1 { + t.Errorf("case %q: unexpected updates: %v", test.testName, registry.Updates) + } else if e, a := test.expectUpdate, ®istry.Updates[0]; !reflect.DeepEqual(e, a) { + t.Errorf("case %q: expected update:\n%#v\ngot:\n%#v\n", test.testName, e, a) + } + } + if test.expectUpdate == nil && len(registry.Updates) > 0 { + t.Errorf("case %q: no update expected, yet saw: %v", test.testName, registry.Updates) + } + } + +} + +func TestCreateOrUpdateMasterService(t *testing.T) { + ns := api.NamespaceDefault + om := func(name string) api.ObjectMeta { + return api.ObjectMeta{Namespace: ns, Name: name} + } + + create_tests := []struct { + testName string + serviceName string + servicePorts []api.ServicePort + serviceType api.ServiceType + expectCreate *api.Service // nil means none expected + }{ + { + testName: "service does not exist", + serviceName: "foo", + servicePorts: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + serviceType: api.ServiceTypeClusterIP, + expectCreate: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + }, + } + for _, test := range create_tests { + master := Controller{MasterCount: 1} + registry := ®istrytest.ServiceRegistry{ + Err: errors.New("unable to get svc"), + } + master.ServiceRegistry = registry + master.CreateOrUpdateMasterServiceIfNeeded(test.serviceName, net.ParseIP("1.2.3.4"), test.servicePorts, test.serviceType, false) + if test.expectCreate != nil { + if len(registry.List.Items) != 1 { + t.Errorf("case %q: unexpected creations: %v", test.testName, registry.List.Items) + } else if e, a := test.expectCreate.Spec, registry.List.Items[0].Spec; !reflect.DeepEqual(e, a) { + t.Errorf("case %q: expected create:\n%#v\ngot:\n%#v\n", test.testName, e, a) + } + } + if test.expectCreate == nil && len(registry.List.Items) > 1 { + t.Errorf("case %q: no create expected, yet saw: %v", test.testName, registry.List.Items) + } + } + + reconcile_tests := []struct { + testName string + serviceName string + servicePorts []api.ServicePort + serviceType api.ServiceType + service *api.Service + expectUpdate *api.Service // nil means none expected + }{ + { + testName: "service definition wrong port", + serviceName: "foo", + servicePorts: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + serviceType: api.ServiceTypeClusterIP, + service: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 8000, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + expectUpdate: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + }, + { + testName: "service definition missing port", + serviceName: "foo", + servicePorts: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + {Name: "baz", Port: 1000, Protocol: "TCP", TargetPort: intstr.FromInt(1000)}, + }, + serviceType: api.ServiceTypeClusterIP, + service: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + expectUpdate: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + {Name: "baz", Port: 1000, Protocol: "TCP", TargetPort: intstr.FromInt(1000)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + }, + { + testName: "service definition incorrect port", + serviceName: "foo", + servicePorts: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + serviceType: api.ServiceTypeClusterIP, + service: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "bar", Port: 1000, Protocol: "UDP", TargetPort: intstr.FromInt(1000)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + expectUpdate: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + }, + { + testName: "service definition incorrect port name", + serviceName: "foo", + servicePorts: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + serviceType: api.ServiceTypeClusterIP, + service: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 1000, Protocol: "UDP", TargetPort: intstr.FromInt(1000)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + expectUpdate: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + }, + { + testName: "service definition incorrect target port", + serviceName: "foo", + servicePorts: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + serviceType: api.ServiceTypeClusterIP, + service: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(1000)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + expectUpdate: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + }, + { + testName: "service definition incorrect protocol", + serviceName: "foo", + servicePorts: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + serviceType: api.ServiceTypeClusterIP, + service: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "UDP", TargetPort: intstr.FromInt(8080)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + expectUpdate: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + }, + { + testName: "service definition has incorrect type", + serviceName: "foo", + servicePorts: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + serviceType: api.ServiceTypeClusterIP, + service: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeNodePort, + }, + }, + expectUpdate: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + }, + { + testName: "service definition satisfies", + serviceName: "foo", + servicePorts: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + serviceType: api.ServiceTypeClusterIP, + service: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + expectUpdate: nil, + }, + } + for _, test := range reconcile_tests { + master := Controller{MasterCount: 1} + registry := ®istrytest.ServiceRegistry{ + Service: test.service, + } + master.ServiceRegistry = registry + err := master.CreateOrUpdateMasterServiceIfNeeded(test.serviceName, net.ParseIP("1.2.3.4"), test.servicePorts, test.serviceType, true) + if err != nil { + t.Errorf("case %q: unexpected error: %v", test.testName, err) + } + if test.expectUpdate != nil { + if len(registry.Updates) != 1 { + t.Errorf("case %q: unexpected updates: %v", test.testName, registry.Updates) + } else if e, a := test.expectUpdate, ®istry.Updates[0]; !reflect.DeepEqual(e, a) { + t.Errorf("case %q: expected update:\n%#v\ngot:\n%#v\n", test.testName, e, a) + } + } + if test.expectUpdate == nil && len(registry.Updates) > 0 { + t.Errorf("case %q: no update expected, yet saw: %v", test.testName, registry.Updates) + } + } + + non_reconcile_tests := []struct { + testName string + serviceName string + servicePorts []api.ServicePort + serviceType api.ServiceType + service *api.Service + expectUpdate *api.Service // nil means none expected + }{ + { + testName: "service definition wrong port, no expected update", + serviceName: "foo", + servicePorts: []api.ServicePort{ + {Name: "foo", Port: 8080, Protocol: "TCP", TargetPort: intstr.FromInt(8080)}, + }, + serviceType: api.ServiceTypeClusterIP, + service: &api.Service{ + ObjectMeta: om("foo"), + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{ + {Name: "foo", Port: 1000, Protocol: "TCP", TargetPort: intstr.FromInt(1000)}, + }, + Selector: nil, + ClusterIP: "1.2.3.4", + SessionAffinity: api.ServiceAffinityClientIP, + Type: api.ServiceTypeClusterIP, + }, + }, + expectUpdate: nil, + }, + } + for _, test := range non_reconcile_tests { + master := Controller{MasterCount: 1} + registry := ®istrytest.ServiceRegistry{ + Service: test.service, + } + master.ServiceRegistry = registry + err := master.CreateOrUpdateMasterServiceIfNeeded(test.serviceName, net.ParseIP("1.2.3.4"), test.servicePorts, test.serviceType, false) + if err != nil { + t.Errorf("case %q: unexpected error: %v", test.testName, err) + } + if test.expectUpdate != nil { + if len(registry.Updates) != 1 { + t.Errorf("case %q: unexpected updates: %v", test.testName, registry.Updates) + } else if e, a := test.expectUpdate, ®istry.Updates[0]; !reflect.DeepEqual(e, a) { + t.Errorf("case %q: expected update:\n%#v\ngot:\n%#v\n", test.testName, e, a) + } + } + if test.expectUpdate == nil && len(registry.Updates) > 0 { + t.Errorf("case %q: no update expected, yet saw: %v", test.testName, registry.Updates) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/master/doc.go b/vendor/k8s.io/kubernetes/pkg/master/doc.go new file mode 100644 index 000000000..cc21977b8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/master/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 master contains code for setting up and running a Kubernetes +// cluster master. +package master diff --git a/vendor/k8s.io/kubernetes/pkg/master/import_known_versions.go b/vendor/k8s.io/kubernetes/pkg/master/import_known_versions.go new file mode 100644 index 000000000..30be6db50 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/master/import_known_versions.go @@ -0,0 +1,36 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 master + +// These imports are the API groups the API server will support. +import ( + "fmt" + + _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/apimachinery/registered" + _ "k8s.io/kubernetes/pkg/apis/authorization/install" + _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" + _ "k8s.io/kubernetes/pkg/apis/batch/install" + _ "k8s.io/kubernetes/pkg/apis/componentconfig/install" + _ "k8s.io/kubernetes/pkg/apis/extensions/install" +) + +func init() { + if missingVersions := registered.ValidateEnvRequestedVersions(); len(missingVersions) != 0 { + panic(fmt.Sprintf("KUBE_API_VERSIONS contains versions that are not installed: %q.", missingVersions)) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/master/master.go b/vendor/k8s.io/kubernetes/pkg/master/master.go new file mode 100644 index 000000000..faf2b0eb6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/master/master.go @@ -0,0 +1,876 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 master + +import ( + "fmt" + "io" + "net" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/unversioned" + apiv1 "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/apis/autoscaling" + autoscalingapiv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1" + "k8s.io/kubernetes/pkg/apis/batch" + batchapiv1 "k8s.io/kubernetes/pkg/apis/batch/v1" + "k8s.io/kubernetes/pkg/apis/extensions" + extensionsapiv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/apiserver" + apiservermetrics "k8s.io/kubernetes/pkg/apiserver/metrics" + "k8s.io/kubernetes/pkg/genericapiserver" + "k8s.io/kubernetes/pkg/healthz" + kubeletclient "k8s.io/kubernetes/pkg/kubelet/client" + "k8s.io/kubernetes/pkg/master/ports" + "k8s.io/kubernetes/pkg/registry/componentstatus" + configmapetcd "k8s.io/kubernetes/pkg/registry/configmap/etcd" + controlleretcd "k8s.io/kubernetes/pkg/registry/controller/etcd" + deploymentetcd "k8s.io/kubernetes/pkg/registry/deployment/etcd" + "k8s.io/kubernetes/pkg/registry/endpoint" + endpointsetcd "k8s.io/kubernetes/pkg/registry/endpoint/etcd" + eventetcd "k8s.io/kubernetes/pkg/registry/event/etcd" + expcontrolleretcd "k8s.io/kubernetes/pkg/registry/experimental/controller/etcd" + "k8s.io/kubernetes/pkg/registry/generic" + ingressetcd "k8s.io/kubernetes/pkg/registry/ingress/etcd" + jobetcd "k8s.io/kubernetes/pkg/registry/job/etcd" + limitrangeetcd "k8s.io/kubernetes/pkg/registry/limitrange/etcd" + "k8s.io/kubernetes/pkg/registry/namespace" + namespaceetcd "k8s.io/kubernetes/pkg/registry/namespace/etcd" + "k8s.io/kubernetes/pkg/registry/node" + nodeetcd "k8s.io/kubernetes/pkg/registry/node/etcd" + pvetcd "k8s.io/kubernetes/pkg/registry/persistentvolume/etcd" + pvcetcd "k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/etcd" + podetcd "k8s.io/kubernetes/pkg/registry/pod/etcd" + pspetcd "k8s.io/kubernetes/pkg/registry/podsecuritypolicy/etcd" + podtemplateetcd "k8s.io/kubernetes/pkg/registry/podtemplate/etcd" + replicasetetcd "k8s.io/kubernetes/pkg/registry/replicaset/etcd" + resourcequotaetcd "k8s.io/kubernetes/pkg/registry/resourcequota/etcd" + secretetcd "k8s.io/kubernetes/pkg/registry/secret/etcd" + "k8s.io/kubernetes/pkg/registry/service" + etcdallocator "k8s.io/kubernetes/pkg/registry/service/allocator/etcd" + serviceetcd "k8s.io/kubernetes/pkg/registry/service/etcd" + ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator" + serviceaccountetcd "k8s.io/kubernetes/pkg/registry/serviceaccount/etcd" + thirdpartyresourceetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd" + "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata" + thirdpartyresourcedataetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/etcd" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" + etcdmetrics "k8s.io/kubernetes/pkg/storage/etcd/metrics" + etcdutil "k8s.io/kubernetes/pkg/storage/etcd/util" + "k8s.io/kubernetes/pkg/util/wait" + + daemonetcd "k8s.io/kubernetes/pkg/registry/daemonset/etcd" + horizontalpodautoscaleretcd "k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/etcd" + + "github.com/golang/glog" + "github.com/prometheus/client_golang/prometheus" + "k8s.io/kubernetes/pkg/registry/service/allocator" + "k8s.io/kubernetes/pkg/registry/service/portallocator" +) + +type Config struct { + *genericapiserver.Config + + EnableCoreControllers bool + DeleteCollectionWorkers int + EventTTL time.Duration + KubeletClient kubeletclient.KubeletClient + // Used to start and monitor tunneling + Tunneler Tunneler + + disableThirdPartyControllerForTesting bool +} + +// Master contains state for a Kubernetes cluster master/api server. +type Master struct { + *genericapiserver.GenericAPIServer + + // Map of v1 resources to their REST storages. + v1ResourcesStorage map[string]rest.Storage + + enableCoreControllers bool + deleteCollectionWorkers int + // registries are internal client APIs for accessing the storage layer + // TODO: define the internal typed interface in a way that clients can + // also be replaced + nodeRegistry node.Registry + namespaceRegistry namespace.Registry + serviceRegistry service.Registry + endpointRegistry endpoint.Registry + serviceClusterIPAllocator service.RangeRegistry + serviceNodePortAllocator service.RangeRegistry + + // storage for third party objects + thirdPartyStorage storage.Interface + // map from api path to a tuple of (storage for the objects, APIGroup) + thirdPartyResources map[string]thirdPartyEntry + // protects the map + thirdPartyResourcesLock sync.RWMutex + // Useful for reliable testing. Shouldn't be used otherwise. + disableThirdPartyControllerForTesting bool + + // Used to start and monitor tunneling + tunneler Tunneler +} + +// thirdPartyEntry combines objects storage and API group into one struct +// for easy lookup. +type thirdPartyEntry struct { + storage *thirdpartyresourcedataetcd.REST + group unversioned.APIGroup +} + +// New returns a new instance of Master from the given config. +// Certain config fields will be set to a default value if unset. +// Certain config fields must be specified, including: +// KubeletClient +func New(c *Config) (*Master, error) { + if c.KubeletClient == nil { + return nil, fmt.Errorf("Master.New() called with config.KubeletClient == nil") + } + + s, err := genericapiserver.New(c.Config) + if err != nil { + return nil, err + } + + m := &Master{ + GenericAPIServer: s, + enableCoreControllers: c.EnableCoreControllers, + deleteCollectionWorkers: c.DeleteCollectionWorkers, + tunneler: c.Tunneler, + + disableThirdPartyControllerForTesting: c.disableThirdPartyControllerForTesting, + } + m.InstallAPIs(c) + + // TODO: Attempt clean shutdown? + if m.enableCoreControllers { + m.NewBootstrapController().Start() + } + + return m, nil +} + +func resetMetrics(w http.ResponseWriter, req *http.Request) { + apiservermetrics.Reset() + etcdmetrics.Reset() + io.WriteString(w, "metrics reset\n") +} + +func (m *Master) InstallAPIs(c *Config) { + apiGroupsInfo := []genericapiserver.APIGroupInfo{} + + // Install v1 unless disabled. + if c.APIResourceConfigSource.AnyResourcesForVersionEnabled(apiv1.SchemeGroupVersion) { + // Install v1 API. + m.initV1ResourcesStorage(c) + apiGroupInfo := genericapiserver.APIGroupInfo{ + GroupMeta: *registered.GroupOrDie(api.GroupName), + VersionedResourcesStorageMap: map[string]map[string]rest.Storage{ + "v1": m.v1ResourcesStorage, + }, + IsLegacyGroup: true, + Scheme: api.Scheme, + ParameterCodec: api.ParameterCodec, + NegotiatedSerializer: api.Codecs, + NegotiatedStreamSerializer: api.StreamCodecs, + } + if autoscalingGroupVersion := (unversioned.GroupVersion{Group: "autoscaling", Version: "v1"}); registered.IsEnabledVersion(autoscalingGroupVersion) { + apiGroupInfo.SubresourceGroupVersionKind = map[string]unversioned.GroupVersionKind{ + "replicationcontrollers/scale": autoscalingGroupVersion.WithKind("Scale"), + } + } + apiGroupsInfo = append(apiGroupsInfo, apiGroupInfo) + } + + // Run the tunneler. + healthzChecks := []healthz.HealthzChecker{} + if m.tunneler != nil { + m.tunneler.Run(m.getNodeAddresses) + healthzChecks = append(healthzChecks, healthz.NamedCheck("SSH Tunnel Check", m.IsTunnelSyncHealthy)) + prometheus.NewGaugeFunc(prometheus.GaugeOpts{ + Name: "apiserver_proxy_tunnel_sync_latency_secs", + Help: "The time since the last successful synchronization of the SSH tunnels for proxy requests.", + }, func() float64 { return float64(m.tunneler.SecondsSinceSync()) }) + } + + // TODO(nikhiljindal): Refactor generic parts of support services (like /versions) to genericapiserver. + apiserver.InstallSupport(m.MuxHelper, m.RootWebService, healthzChecks...) + if c.EnableProfiling { + m.MuxHelper.HandleFunc("/resetMetrics", resetMetrics) + } + + // Install root web services + m.HandlerContainer.Add(m.RootWebService) + + // allGroups records all supported groups at /apis + allGroups := []unversioned.APIGroup{} + + // Install extensions unless disabled. + if c.APIResourceConfigSource.AnyResourcesForVersionEnabled(extensionsapiv1beta1.SchemeGroupVersion) { + m.thirdPartyStorage = c.StorageDestinations.APIGroups[extensions.GroupName].Default + m.thirdPartyResources = map[string]thirdPartyEntry{} + + extensionResources := m.getExtensionResources(c) + extensionsGroupMeta := registered.GroupOrDie(extensions.GroupName) + // Update the preferred version as per StorageVersions in the config. + storageVersion, found := c.StorageVersions[extensionsGroupMeta.GroupVersion.Group] + if !found { + glog.Fatalf("Couldn't find storage version of group %v", extensionsGroupMeta.GroupVersion.Group) + } + preferedGroupVersion, err := unversioned.ParseGroupVersion(storageVersion) + if err != nil { + glog.Fatalf("Error in parsing group version %s: %v", storageVersion, err) + } + extensionsGroupMeta.GroupVersion = preferedGroupVersion + + apiGroupInfo := genericapiserver.APIGroupInfo{ + GroupMeta: *extensionsGroupMeta, + VersionedResourcesStorageMap: map[string]map[string]rest.Storage{ + "v1beta1": extensionResources, + }, + OptionsExternalVersion: ®istered.GroupOrDie(api.GroupName).GroupVersion, + Scheme: api.Scheme, + ParameterCodec: api.ParameterCodec, + NegotiatedSerializer: api.Codecs, + NegotiatedStreamSerializer: api.StreamCodecs, + } + apiGroupsInfo = append(apiGroupsInfo, apiGroupInfo) + + extensionsGVForDiscovery := unversioned.GroupVersionForDiscovery{ + GroupVersion: extensionsGroupMeta.GroupVersion.String(), + Version: extensionsGroupMeta.GroupVersion.Version, + } + group := unversioned.APIGroup{ + Name: extensionsGroupMeta.GroupVersion.Group, + Versions: []unversioned.GroupVersionForDiscovery{extensionsGVForDiscovery}, + PreferredVersion: extensionsGVForDiscovery, + } + allGroups = append(allGroups, group) + } + + // Install autoscaling unless disabled. + if c.APIResourceConfigSource.AnyResourcesForVersionEnabled(autoscalingapiv1.SchemeGroupVersion) { + autoscalingResources := m.getAutoscalingResources(c) + autoscalingGroupMeta := registered.GroupOrDie(autoscaling.GroupName) + + // Hard code preferred group version to autoscaling/v1 + autoscalingGroupMeta.GroupVersion = autoscalingapiv1.SchemeGroupVersion + + apiGroupInfo := genericapiserver.APIGroupInfo{ + GroupMeta: *autoscalingGroupMeta, + VersionedResourcesStorageMap: map[string]map[string]rest.Storage{ + "v1": autoscalingResources, + }, + OptionsExternalVersion: ®istered.GroupOrDie(api.GroupName).GroupVersion, + Scheme: api.Scheme, + ParameterCodec: api.ParameterCodec, + NegotiatedSerializer: api.Codecs, + NegotiatedStreamSerializer: api.StreamCodecs, + } + apiGroupsInfo = append(apiGroupsInfo, apiGroupInfo) + + autoscalingGVForDiscovery := unversioned.GroupVersionForDiscovery{ + GroupVersion: autoscalingGroupMeta.GroupVersion.String(), + Version: autoscalingGroupMeta.GroupVersion.Version, + } + group := unversioned.APIGroup{ + Name: autoscalingGroupMeta.GroupVersion.Group, + Versions: []unversioned.GroupVersionForDiscovery{autoscalingGVForDiscovery}, + PreferredVersion: autoscalingGVForDiscovery, + } + allGroups = append(allGroups, group) + } + + // Install batch unless disabled. + if c.APIResourceConfigSource.AnyResourcesForVersionEnabled(batchapiv1.SchemeGroupVersion) { + batchResources := m.getBatchResources(c) + batchGroupMeta := registered.GroupOrDie(batch.GroupName) + + // Hard code preferred group version to batch/v1 + batchGroupMeta.GroupVersion = batchapiv1.SchemeGroupVersion + + apiGroupInfo := genericapiserver.APIGroupInfo{ + GroupMeta: *batchGroupMeta, + VersionedResourcesStorageMap: map[string]map[string]rest.Storage{ + "v1": batchResources, + }, + OptionsExternalVersion: ®istered.GroupOrDie(api.GroupName).GroupVersion, + Scheme: api.Scheme, + ParameterCodec: api.ParameterCodec, + NegotiatedSerializer: api.Codecs, + NegotiatedStreamSerializer: api.StreamCodecs, + } + apiGroupsInfo = append(apiGroupsInfo, apiGroupInfo) + + batchGVForDiscovery := unversioned.GroupVersionForDiscovery{ + GroupVersion: batchGroupMeta.GroupVersion.String(), + Version: batchGroupMeta.GroupVersion.Version, + } + group := unversioned.APIGroup{ + Name: batchGroupMeta.GroupVersion.Group, + Versions: []unversioned.GroupVersionForDiscovery{batchGVForDiscovery}, + PreferredVersion: batchGVForDiscovery, + } + allGroups = append(allGroups, group) + } + + if err := m.InstallAPIGroups(apiGroupsInfo); err != nil { + glog.Fatalf("Error in registering group versions: %v", err) + } +} + +func (m *Master) initV1ResourcesStorage(c *Config) { + dbClient := func(resource string) storage.Interface { return c.StorageDestinations.Get("", resource) } + restOptions := func(resource string) generic.RESTOptions { + return generic.RESTOptions{ + Storage: dbClient(resource), + Decorator: m.StorageDecorator(), + DeleteCollectionWorkers: m.deleteCollectionWorkers, + } + } + + podTemplateStorage := podtemplateetcd.NewREST(restOptions("podTemplates")) + + eventStorage := eventetcd.NewREST(restOptions("events"), uint64(c.EventTTL.Seconds())) + limitRangeStorage := limitrangeetcd.NewREST(restOptions("limitRanges")) + + resourceQuotaStorage, resourceQuotaStatusStorage := resourcequotaetcd.NewREST(restOptions("resourceQuotas")) + secretStorage := secretetcd.NewREST(restOptions("secrets")) + serviceAccountStorage := serviceaccountetcd.NewREST(restOptions("serviceAccounts")) + persistentVolumeStorage, persistentVolumeStatusStorage := pvetcd.NewREST(restOptions("persistentVolumes")) + persistentVolumeClaimStorage, persistentVolumeClaimStatusStorage := pvcetcd.NewREST(restOptions("persistentVolumeClaims")) + configMapStorage := configmapetcd.NewREST(restOptions("configMaps")) + + namespaceStorage, namespaceStatusStorage, namespaceFinalizeStorage := namespaceetcd.NewREST(restOptions("namespaces")) + m.namespaceRegistry = namespace.NewRegistry(namespaceStorage) + + endpointsStorage := endpointsetcd.NewREST(restOptions("endpoints")) + m.endpointRegistry = endpoint.NewRegistry(endpointsStorage) + + nodeStorage := nodeetcd.NewStorage(restOptions("nodes"), c.KubeletClient, m.ProxyTransport) + m.nodeRegistry = node.NewRegistry(nodeStorage.Node) + + podStorage := podetcd.NewStorage( + restOptions("pods"), + kubeletclient.ConnectionInfoGetter(nodeStorage.Node), + m.ProxyTransport, + ) + + serviceStorage, serviceStatusStorage := serviceetcd.NewREST(restOptions("services")) + m.serviceRegistry = service.NewRegistry(serviceStorage) + + var serviceClusterIPRegistry service.RangeRegistry + serviceClusterIPRange := m.ServiceClusterIPRange + if serviceClusterIPRange == nil { + glog.Fatalf("service clusterIPRange is nil") + return + } + serviceClusterIPAllocator := ipallocator.NewAllocatorCIDRRange(serviceClusterIPRange, func(max int, rangeSpec string) allocator.Interface { + mem := allocator.NewAllocationMap(max, rangeSpec) + etcd := etcdallocator.NewEtcd(mem, "/ranges/serviceips", api.Resource("serviceipallocations"), dbClient("services")) + serviceClusterIPRegistry = etcd + return etcd + }) + m.serviceClusterIPAllocator = serviceClusterIPRegistry + + var serviceNodePortRegistry service.RangeRegistry + serviceNodePortAllocator := portallocator.NewPortAllocatorCustom(m.ServiceNodePortRange, func(max int, rangeSpec string) allocator.Interface { + mem := allocator.NewAllocationMap(max, rangeSpec) + etcd := etcdallocator.NewEtcd(mem, "/ranges/servicenodeports", api.Resource("servicenodeportallocations"), dbClient("services")) + serviceNodePortRegistry = etcd + return etcd + }) + m.serviceNodePortAllocator = serviceNodePortRegistry + + controllerStorage := controlleretcd.NewStorage(restOptions("replicationControllers")) + + serviceRest := service.NewStorage(m.serviceRegistry, m.endpointRegistry, serviceClusterIPAllocator, serviceNodePortAllocator, m.ProxyTransport) + + // TODO: Factor out the core API registration + m.v1ResourcesStorage = map[string]rest.Storage{ + "pods": podStorage.Pod, + "pods/attach": podStorage.Attach, + "pods/status": podStorage.Status, + "pods/log": podStorage.Log, + "pods/exec": podStorage.Exec, + "pods/portforward": podStorage.PortForward, + "pods/proxy": podStorage.Proxy, + "pods/binding": podStorage.Binding, + "bindings": podStorage.Binding, + + "podTemplates": podTemplateStorage, + + "replicationControllers": controllerStorage.Controller, + "replicationControllers/status": controllerStorage.Status, + + "services": serviceRest.Service, + "services/proxy": serviceRest.Proxy, + "services/status": serviceStatusStorage, + + "endpoints": endpointsStorage, + + "nodes": nodeStorage.Node, + "nodes/status": nodeStorage.Status, + "nodes/proxy": nodeStorage.Proxy, + + "events": eventStorage, + + "limitRanges": limitRangeStorage, + "resourceQuotas": resourceQuotaStorage, + "resourceQuotas/status": resourceQuotaStatusStorage, + "namespaces": namespaceStorage, + "namespaces/status": namespaceStatusStorage, + "namespaces/finalize": namespaceFinalizeStorage, + "secrets": secretStorage, + "serviceAccounts": serviceAccountStorage, + "persistentVolumes": persistentVolumeStorage, + "persistentVolumes/status": persistentVolumeStatusStorage, + "persistentVolumeClaims": persistentVolumeClaimStorage, + "persistentVolumeClaims/status": persistentVolumeClaimStatusStorage, + "configMaps": configMapStorage, + + "componentStatuses": componentstatus.NewStorage(func() map[string]apiserver.Server { return m.getServersToValidate(c) }), + } + if registered.IsEnabledVersion(unversioned.GroupVersion{Group: "autoscaling", Version: "v1"}) { + m.v1ResourcesStorage["replicationControllers/scale"] = controllerStorage.Scale + } +} + +// NewBootstrapController returns a controller for watching the core capabilities of the master. +func (m *Master) NewBootstrapController() *Controller { + return &Controller{ + NamespaceRegistry: m.namespaceRegistry, + ServiceRegistry: m.serviceRegistry, + MasterCount: m.MasterCount, + + EndpointRegistry: m.endpointRegistry, + EndpointInterval: 10 * time.Second, + + ServiceClusterIPRegistry: m.serviceClusterIPAllocator, + ServiceClusterIPRange: m.ServiceClusterIPRange, + ServiceClusterIPInterval: 3 * time.Minute, + + ServiceNodePortRegistry: m.serviceNodePortAllocator, + ServiceNodePortRange: m.ServiceNodePortRange, + ServiceNodePortInterval: 3 * time.Minute, + + PublicIP: m.ClusterIP, + + ServiceIP: m.ServiceReadWriteIP, + ServicePort: m.ServiceReadWritePort, + ExtraServicePorts: m.ExtraServicePorts, + ExtraEndpointPorts: m.ExtraEndpointPorts, + PublicServicePort: m.PublicReadWritePort, + KubernetesServiceNodePort: m.KubernetesServiceNodePort, + } +} + +func (m *Master) getServersToValidate(c *Config) map[string]apiserver.Server { + serversToValidate := map[string]apiserver.Server{ + "controller-manager": {Addr: "127.0.0.1", Port: ports.ControllerManagerPort, Path: "/healthz"}, + "scheduler": {Addr: "127.0.0.1", Port: ports.SchedulerPort, Path: "/healthz"}, + } + + for ix, machine := range c.StorageDestinations.Backends() { + etcdUrl, err := url.Parse(machine) + if err != nil { + glog.Errorf("Failed to parse etcd url for validation: %v", err) + continue + } + var port int + var addr string + if strings.Contains(etcdUrl.Host, ":") { + var portString string + addr, portString, err = net.SplitHostPort(etcdUrl.Host) + if err != nil { + glog.Errorf("Failed to split host/port: %s (%v)", etcdUrl.Host, err) + continue + } + port, _ = strconv.Atoi(portString) + } else { + addr = etcdUrl.Host + port = 4001 + } + // TODO: etcd health checking should be abstracted in the storage tier + serversToValidate[fmt.Sprintf("etcd-%d", ix)] = apiserver.Server{Addr: addr, Port: port, Path: "/health", Validate: etcdutil.EtcdHealthCheck} + } + return serversToValidate +} + +// HasThirdPartyResource returns true if a particular third party resource currently installed. +func (m *Master) HasThirdPartyResource(rsrc *extensions.ThirdPartyResource) (bool, error) { + _, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc) + if err != nil { + return false, err + } + path := makeThirdPartyPath(group) + services := m.HandlerContainer.RegisteredWebServices() + for ix := range services { + if services[ix].RootPath() == path { + return true, nil + } + } + return false, nil +} + +func (m *Master) removeThirdPartyStorage(path string) error { + m.thirdPartyResourcesLock.Lock() + defer m.thirdPartyResourcesLock.Unlock() + storage, found := m.thirdPartyResources[path] + if found { + if err := m.removeAllThirdPartyResources(storage.storage); err != nil { + return err + } + delete(m.thirdPartyResources, path) + m.RemoveAPIGroupForDiscovery(getThirdPartyGroupName(path)) + } + return nil +} + +// RemoveThirdPartyResource removes all resources matching `path`. Also deletes any stored data +func (m *Master) RemoveThirdPartyResource(path string) error { + if err := m.removeThirdPartyStorage(path); err != nil { + return err + } + + services := m.HandlerContainer.RegisteredWebServices() + for ix := range services { + root := services[ix].RootPath() + if root == path || strings.HasPrefix(root, path+"/") { + m.HandlerContainer.Remove(services[ix]) + } + } + return nil +} + +func (m *Master) removeAllThirdPartyResources(registry *thirdpartyresourcedataetcd.REST) error { + ctx := api.NewDefaultContext() + existingData, err := registry.List(ctx, nil) + if err != nil { + return err + } + list, ok := existingData.(*extensions.ThirdPartyResourceDataList) + if !ok { + return fmt.Errorf("expected a *ThirdPartyResourceDataList, got %#v", list) + } + for ix := range list.Items { + item := &list.Items[ix] + if _, err := registry.Delete(ctx, item.Name, nil); err != nil { + return err + } + } + return nil +} + +// ListThirdPartyResources lists all currently installed third party resources +func (m *Master) ListThirdPartyResources() []string { + m.thirdPartyResourcesLock.RLock() + defer m.thirdPartyResourcesLock.RUnlock() + result := []string{} + for key := range m.thirdPartyResources { + result = append(result, key) + } + return result +} + +func (m *Master) addThirdPartyResourceStorage(path string, storage *thirdpartyresourcedataetcd.REST, apiGroup unversioned.APIGroup) { + m.thirdPartyResourcesLock.Lock() + defer m.thirdPartyResourcesLock.Unlock() + m.thirdPartyResources[path] = thirdPartyEntry{storage, apiGroup} + m.AddAPIGroupForDiscovery(apiGroup) +} + +// InstallThirdPartyResource installs a third party resource specified by 'rsrc'. When a resource is +// installed a corresponding RESTful resource is added as a valid path in the web service provided by +// the master. +// +// For example, if you install a resource ThirdPartyResource{ Name: "foo.company.com", Versions: {"v1"} } +// then the following RESTful resource is created on the server: +// http:///apis/company.com/v1/foos/... +func (m *Master) InstallThirdPartyResource(rsrc *extensions.ThirdPartyResource) error { + kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc) + if err != nil { + return err + } + thirdparty := m.thirdpartyapi(group, kind, rsrc.Versions[0].Name) + if err := thirdparty.InstallREST(m.HandlerContainer); err != nil { + glog.Fatalf("Unable to setup thirdparty api: %v", err) + } + path := makeThirdPartyPath(group) + groupVersion := unversioned.GroupVersionForDiscovery{ + GroupVersion: group + "/" + rsrc.Versions[0].Name, + Version: rsrc.Versions[0].Name, + } + apiGroup := unversioned.APIGroup{ + Name: group, + Versions: []unversioned.GroupVersionForDiscovery{groupVersion}, + } + apiserver.AddGroupWebService(api.Codecs, m.HandlerContainer, path, apiGroup) + m.addThirdPartyResourceStorage(path, thirdparty.Storage[strings.ToLower(kind)+"s"].(*thirdpartyresourcedataetcd.REST), apiGroup) + apiserver.InstallServiceErrorHandler(api.Codecs, m.HandlerContainer, m.NewRequestInfoResolver(), []string{thirdparty.GroupVersion.String()}) + return nil +} + +func (m *Master) thirdpartyapi(group, kind, version string) *apiserver.APIGroupVersion { + resourceStorage := thirdpartyresourcedataetcd.NewREST( + generic.RESTOptions{Storage: m.thirdPartyStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: m.deleteCollectionWorkers}, group, kind) + + apiRoot := makeThirdPartyPath("") + + storage := map[string]rest.Storage{ + strings.ToLower(kind) + "s": resourceStorage, + } + + optionsExternalVersion := registered.GroupOrDie(api.GroupName).GroupVersion + internalVersion := unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal} + externalVersion := unversioned.GroupVersion{Group: group, Version: version} + + return &apiserver.APIGroupVersion{ + Root: apiRoot, + GroupVersion: externalVersion, + RequestInfoResolver: m.NewRequestInfoResolver(), + + Creater: thirdpartyresourcedata.NewObjectCreator(group, version, api.Scheme), + Convertor: api.Scheme, + Typer: api.Scheme, + + Mapper: thirdpartyresourcedata.NewMapper(registered.GroupOrDie(extensions.GroupName).RESTMapper, kind, version, group), + Linker: registered.GroupOrDie(extensions.GroupName).SelfLinker, + Storage: storage, + OptionsExternalVersion: &optionsExternalVersion, + + Serializer: thirdpartyresourcedata.NewNegotiatedSerializer(api.Codecs, kind, externalVersion, internalVersion), + StreamSerializer: thirdpartyresourcedata.NewNegotiatedSerializer(api.StreamCodecs, kind, externalVersion, internalVersion), + ParameterCodec: thirdpartyresourcedata.NewThirdPartyParameterCodec(api.ParameterCodec), + + Context: m.RequestContextMapper, + + MinRequestTimeout: m.MinRequestTimeout, + } +} + +// getExperimentalResources returns the resources for extensions api +func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage { + restOptions := func(resource string) generic.RESTOptions { + return generic.RESTOptions{ + Storage: c.StorageDestinations.Get(extensions.GroupName, resource), + Decorator: m.StorageDecorator(), + DeleteCollectionWorkers: m.deleteCollectionWorkers, + } + } + // TODO update when we support more than one version of this group + version := extensionsapiv1beta1.SchemeGroupVersion + + storage := map[string]rest.Storage{} + + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("horizontalpodautoscalers")) { + m.constructHPAResources(c, storage) + controllerStorage := expcontrolleretcd.NewStorage( + generic.RESTOptions{Storage: c.StorageDestinations.Get("", "replicationControllers"), Decorator: m.StorageDecorator(), DeleteCollectionWorkers: m.deleteCollectionWorkers}) + storage["replicationcontrollers"] = controllerStorage.ReplicationController + storage["replicationcontrollers/scale"] = controllerStorage.Scale + } + thirdPartyResourceStorage := thirdpartyresourceetcd.NewREST(restOptions("thirdpartyresources")) + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("thirdpartyresources")) { + thirdPartyControl := ThirdPartyController{ + master: m, + thirdPartyResourceRegistry: thirdPartyResourceStorage, + } + if !m.disableThirdPartyControllerForTesting { + go wait.Forever(func() { + if err := thirdPartyControl.SyncResources(); err != nil { + glog.Warningf("third party resource sync failed: %v", err) + } + }, 10*time.Second) + } + storage["thirdpartyresources"] = thirdPartyResourceStorage + } + + daemonSetStorage, daemonSetStatusStorage := daemonetcd.NewREST(restOptions("daemonsets")) + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("daemonsets")) { + storage["daemonsets"] = daemonSetStorage + storage["daemonsets/status"] = daemonSetStatusStorage + } + deploymentStorage := deploymentetcd.NewStorage(restOptions("deployments")) + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("deployments")) { + storage["deployments"] = deploymentStorage.Deployment + storage["deployments/status"] = deploymentStorage.Status + storage["deployments/rollback"] = deploymentStorage.Rollback + storage["deployments/scale"] = deploymentStorage.Scale + } + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("jobs")) { + m.constructJobResources(c, storage) + } + ingressStorage, ingressStatusStorage := ingressetcd.NewREST(restOptions("ingresses")) + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("ingresses")) { + storage["ingresses"] = ingressStorage + storage["ingresses/status"] = ingressStatusStorage + } + podSecurityPolicyStorage := pspetcd.NewREST(restOptions("podsecuritypolicy")) + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("podsecuritypolicy")) { + storage["podSecurityPolicies"] = podSecurityPolicyStorage + } + replicaSetStorage := replicasetetcd.NewStorage(restOptions("replicasets")) + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("replicasets")) { + storage["replicasets"] = replicaSetStorage.ReplicaSet + storage["replicasets/status"] = replicaSetStorage.Status + storage["replicasets/scale"] = replicaSetStorage.Scale + } + + return storage +} + +// constructHPAResources makes HPA resources and adds them to the storage map. +// They're installed in both autoscaling and extensions. It's assumed that +// you've already done the check that they should be on. +func (m *Master) constructHPAResources(c *Config, restStorage map[string]rest.Storage) { + // Note that hpa's storage settings are changed by changing the autoscaling + // group. Clearly we want all hpas to be stored in the same place no + // matter where they're accessed from. + restOptions := func(resource string) generic.RESTOptions { + return generic.RESTOptions{ + Storage: c.StorageDestinations.Search([]string{autoscaling.GroupName, extensions.GroupName}, resource), + Decorator: m.StorageDecorator(), + DeleteCollectionWorkers: m.deleteCollectionWorkers, + } + } + autoscalerStorage, autoscalerStatusStorage := horizontalpodautoscaleretcd.NewREST(restOptions("horizontalpodautoscalers")) + restStorage["horizontalpodautoscalers"] = autoscalerStorage + restStorage["horizontalpodautoscalers/status"] = autoscalerStatusStorage +} + +// getAutoscalingResources returns the resources for autoscaling api +func (m *Master) getAutoscalingResources(c *Config) map[string]rest.Storage { + // TODO update when we support more than one version of this group + version := autoscalingapiv1.SchemeGroupVersion + + storage := map[string]rest.Storage{} + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("horizontalpodautoscalers")) { + m.constructHPAResources(c, storage) + } + return storage +} + +// constructJobResources makes Job resources and adds them to the storage map. +// They're installed in both batch and extensions. It's assumed that you've +// already done the check that they should be on. +func (m *Master) constructJobResources(c *Config, restStorage map[string]rest.Storage) { + // Note that job's storage settings are changed by changing the batch + // group. Clearly we want all jobs to be stored in the same place no + // matter where they're accessed from. + restOptions := func(resource string) generic.RESTOptions { + return generic.RESTOptions{ + Storage: c.StorageDestinations.Search([]string{batch.GroupName, extensions.GroupName}, resource), + Decorator: m.StorageDecorator(), + DeleteCollectionWorkers: m.deleteCollectionWorkers, + } + } + jobStorage, jobStatusStorage := jobetcd.NewREST(restOptions("jobs")) + restStorage["jobs"] = jobStorage + restStorage["jobs/status"] = jobStatusStorage +} + +// getBatchResources returns the resources for batch api +func (m *Master) getBatchResources(c *Config) map[string]rest.Storage { + // TODO update when we support more than one version of this group + version := batchapiv1.SchemeGroupVersion + + storage := map[string]rest.Storage{} + if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("jobs")) { + m.constructJobResources(c, storage) + } + return storage +} + +// findExternalAddress returns ExternalIP of provided node with fallback to LegacyHostIP. +func findExternalAddress(node *api.Node) (string, error) { + var fallback string + for ix := range node.Status.Addresses { + addr := &node.Status.Addresses[ix] + if addr.Type == api.NodeExternalIP { + return addr.Address, nil + } + if fallback == "" && addr.Type == api.NodeLegacyHostIP { + fallback = addr.Address + } + } + if fallback != "" { + return fallback, nil + } + return "", fmt.Errorf("Couldn't find external address: %v", node) +} + +func (m *Master) getNodeAddresses() ([]string, error) { + nodes, err := m.nodeRegistry.ListNodes(api.NewDefaultContext(), nil) + if err != nil { + return nil, err + } + addrs := []string{} + for ix := range nodes.Items { + node := &nodes.Items[ix] + addr, err := findExternalAddress(node) + if err != nil { + return nil, err + } + addrs = append(addrs, addr) + } + return addrs, nil +} + +func (m *Master) IsTunnelSyncHealthy(req *http.Request) error { + if m.tunneler == nil { + return nil + } + lag := m.tunneler.SecondsSinceSync() + if lag > 600 { + return fmt.Errorf("Tunnel sync is taking to long: %d", lag) + } + sshKeyLag := m.tunneler.SecondsSinceSSHKeySync() + if sshKeyLag > 600 { + return fmt.Errorf("SSHKey sync is taking to long: %d", sshKeyLag) + } + return nil +} + +func DefaultAPIResourceConfigSource() *genericapiserver.ResourceConfig { + ret := genericapiserver.NewResourceConfig() + ret.EnableVersions(apiv1.SchemeGroupVersion, extensionsapiv1beta1.SchemeGroupVersion, batchapiv1.SchemeGroupVersion, autoscalingapiv1.SchemeGroupVersion) + + // all extensions resources except these are disabled by default + ret.EnableResources( + extensionsapiv1beta1.SchemeGroupVersion.WithResource("daemonsets"), + extensionsapiv1beta1.SchemeGroupVersion.WithResource("deployments"), + extensionsapiv1beta1.SchemeGroupVersion.WithResource("horizontalpodautoscalers"), + extensionsapiv1beta1.SchemeGroupVersion.WithResource("ingresses"), + extensionsapiv1beta1.SchemeGroupVersion.WithResource("jobs"), + extensionsapiv1beta1.SchemeGroupVersion.WithResource("replicasets"), + extensionsapiv1beta1.SchemeGroupVersion.WithResource("thirdpartyresources"), + ) + + return ret +} diff --git a/vendor/k8s.io/kubernetes/pkg/master/master_test.go b/vendor/k8s.io/kubernetes/pkg/master/master_test.go new file mode 100644 index 000000000..d8bf77ff4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/master/master_test.go @@ -0,0 +1,1033 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 master + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/http/httptest" + "reflect" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apiserver" + utilnet "k8s.io/kubernetes/pkg/util/net" + "k8s.io/kubernetes/pkg/util/sets" + + "k8s.io/kubernetes/pkg/apis/autoscaling" + "k8s.io/kubernetes/pkg/apis/batch" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/genericapiserver" + "k8s.io/kubernetes/pkg/kubelet/client" + "k8s.io/kubernetes/pkg/registry/endpoint" + "k8s.io/kubernetes/pkg/registry/namespace" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" + etcdstorage "k8s.io/kubernetes/pkg/storage/etcd" + "k8s.io/kubernetes/pkg/storage/etcd/etcdtest" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + "k8s.io/kubernetes/pkg/util/intstr" + + "github.com/stretchr/testify/assert" + "golang.org/x/net/context" +) + +// setUp is a convience function for setting up for (most) tests. +func setUp(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert.Assertions) { + server := etcdtesting.NewEtcdTestClientServer(t) + + master := &Master{ + GenericAPIServer: &genericapiserver.GenericAPIServer{}, + } + config := Config{ + Config: &genericapiserver.Config{}, + } + storageVersions := make(map[string]string) + storageDestinations := genericapiserver.NewStorageDestinations() + storageDestinations.AddAPIGroup( + api.GroupName, etcdstorage.NewEtcdStorage(server.Client, testapi.Default.Codec(), etcdtest.PathPrefix(), false)) + storageDestinations.AddAPIGroup( + autoscaling.GroupName, etcdstorage.NewEtcdStorage(server.Client, testapi.Autoscaling.Codec(), etcdtest.PathPrefix(), false)) + storageDestinations.AddAPIGroup( + batch.GroupName, etcdstorage.NewEtcdStorage(server.Client, testapi.Batch.Codec(), etcdtest.PathPrefix(), false)) + storageDestinations.AddAPIGroup( + extensions.GroupName, etcdstorage.NewEtcdStorage(server.Client, testapi.Extensions.Codec(), etcdtest.PathPrefix(), false)) + + config.StorageDestinations = storageDestinations + storageVersions[api.GroupName] = testapi.Default.GroupVersion().String() + storageVersions[autoscaling.GroupName] = testapi.Autoscaling.GroupVersion().String() + storageVersions[batch.GroupName] = testapi.Batch.GroupVersion().String() + storageVersions[extensions.GroupName] = testapi.Extensions.GroupVersion().String() + config.StorageVersions = storageVersions + config.PublicAddress = net.ParseIP("192.168.10.4") + master.nodeRegistry = registrytest.NewNodeRegistry([]string{"node1", "node2"}, api.NodeResources{}) + + return master, server, config, assert.New(t) +} + +func newMaster(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert.Assertions) { + _, etcdserver, config, assert := setUp(t) + + config.Serializer = api.Codecs + config.KubeletClient = client.FakeKubeletClient{} + config.APIPrefix = "/api" + config.APIGroupPrefix = "/apis" + config.APIResourceConfigSource = DefaultAPIResourceConfigSource() + + config.ProxyDialer = func(network, addr string) (net.Conn, error) { return nil, nil } + config.ProxyTLSClientConfig = &tls.Config{} + + // TODO: this is kind of hacky. The trouble is that the sync loop + // runs in a go-routine and there is no way to validate in the test + // that the sync routine has actually run. The right answer here + // is probably to add some sort of callback that we can register + // to validate that it's actually been run, but for now we don't + // run the sync routine and register types manually. + config.disableThirdPartyControllerForTesting = true + + master, err := New(&config) + if err != nil { + t.Fatalf("Error in bringing up the master: %v", err) + } + + return master, etcdserver, config, assert +} + +// TestNew verifies that the New function returns a Master +// using the configuration properly. +func TestNew(t *testing.T) { + master, etcdserver, config, assert := newMaster(t) + defer etcdserver.Terminate(t) + + // Verify many of the variables match their config counterparts + assert.Equal(master.enableCoreControllers, config.EnableCoreControllers) + assert.Equal(master.tunneler, config.Tunneler) + assert.Equal(master.APIPrefix, config.APIPrefix) + assert.Equal(master.APIGroupPrefix, config.APIGroupPrefix) + assert.Equal(master.RequestContextMapper, config.RequestContextMapper) + assert.Equal(master.MasterCount, config.MasterCount) + assert.Equal(master.ClusterIP, config.PublicAddress) + assert.Equal(master.PublicReadWritePort, config.ReadWritePort) + assert.Equal(master.ServiceReadWriteIP, config.ServiceReadWriteIP) + + // These functions should point to the same memory location + masterDialer, _ := utilnet.Dialer(master.ProxyTransport) + masterDialerFunc := fmt.Sprintf("%p", masterDialer) + configDialerFunc := fmt.Sprintf("%p", config.ProxyDialer) + assert.Equal(masterDialerFunc, configDialerFunc) + + assert.Equal(master.ProxyTransport.(*http.Transport).TLSClientConfig, config.ProxyTLSClientConfig) +} + +// TestNamespaceSubresources ensures the namespace subresource parsing in apiserver/handlers.go doesn't drift +func TestNamespaceSubresources(t *testing.T) { + master, etcdserver, _, _ := newMaster(t) + defer etcdserver.Terminate(t) + + expectedSubresources := apiserver.NamespaceSubResourcesForTest + foundSubresources := sets.NewString() + + for k := range master.v1ResourcesStorage { + parts := strings.Split(k, "/") + if len(parts) == 2 && parts[0] == "namespaces" { + foundSubresources.Insert(parts[1]) + } + } + + if !reflect.DeepEqual(expectedSubresources.List(), foundSubresources.List()) { + t.Errorf("Expected namespace subresources %#v, got %#v. Update apiserver/handlers.go#namespaceSubresources", expectedSubresources.List(), foundSubresources.List()) + } +} + +// TestGetServersToValidate verifies the unexported getServersToValidate function +func TestGetServersToValidate(t *testing.T) { + master, etcdserver, config, assert := setUp(t) + defer etcdserver.Terminate(t) + + servers := master.getServersToValidate(&config) + + // Expected servers to validate: scheduler, controller-manager and etcd. + assert.Equal(3, len(servers), "unexpected server list: %#v", servers) + + for _, server := range []string{"scheduler", "controller-manager", "etcd-0"} { + if _, ok := servers[server]; !ok { + t.Errorf("server list missing: %s", server) + } + } +} + +// TestFindExternalAddress verifies both pass and fail cases for the unexported +// findExternalAddress function +func TestFindExternalAddress(t *testing.T) { + assert := assert.New(t) + expectedIP := "172.0.0.1" + + nodes := []*api.Node{new(api.Node), new(api.Node), new(api.Node)} + nodes[0].Status.Addresses = []api.NodeAddress{{"ExternalIP", expectedIP}} + nodes[1].Status.Addresses = []api.NodeAddress{{"LegacyHostIP", expectedIP}} + nodes[2].Status.Addresses = []api.NodeAddress{{"ExternalIP", expectedIP}, {"LegacyHostIP", "172.0.0.2"}} + + // Pass Case + for _, node := range nodes { + ip, err := findExternalAddress(node) + assert.NoError(err, "error getting node external address") + assert.Equal(expectedIP, ip, "expected ip to be %s, but was %s", expectedIP, ip) + } + + // Fail case + _, err := findExternalAddress(new(api.Node)) + assert.Error(err, "expected findExternalAddress to fail on a node with missing ip information") +} + +// TestNewBootstrapController verifies master fields are properly copied into controller +func TestNewBootstrapController(t *testing.T) { + // Tests a subset of inputs to ensure they are set properly in the controller + master, etcdserver, _, assert := setUp(t) + defer etcdserver.Terminate(t) + + portRange := utilnet.PortRange{Base: 10, Size: 10} + + master.namespaceRegistry = namespace.NewRegistry(nil) + master.serviceRegistry = registrytest.NewServiceRegistry() + master.endpointRegistry = endpoint.NewRegistry(nil) + + master.ServiceNodePortRange = portRange + master.MasterCount = 1 + master.ServiceReadWritePort = 1000 + master.PublicReadWritePort = 1010 + + controller := master.NewBootstrapController() + + assert.Equal(controller.NamespaceRegistry, master.namespaceRegistry) + assert.Equal(controller.EndpointRegistry, master.endpointRegistry) + assert.Equal(controller.ServiceRegistry, master.serviceRegistry) + assert.Equal(controller.ServiceNodePortRange, portRange) + assert.Equal(controller.MasterCount, master.MasterCount) + assert.Equal(controller.ServicePort, master.ServiceReadWritePort) + assert.Equal(controller.PublicServicePort, master.PublicReadWritePort) +} + +// TestControllerServicePorts verifies master extraServicePorts are +// correctly copied into controller +func TestControllerServicePorts(t *testing.T) { + master, etcdserver, _, assert := setUp(t) + defer etcdserver.Terminate(t) + + master.namespaceRegistry = namespace.NewRegistry(nil) + master.serviceRegistry = registrytest.NewServiceRegistry() + master.endpointRegistry = endpoint.NewRegistry(nil) + + master.ExtraServicePorts = []api.ServicePort{ + { + Name: "additional-port-1", + Port: 1000, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(1000), + }, + { + Name: "additional-port-2", + Port: 1010, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(1010), + }, + } + + controller := master.NewBootstrapController() + + assert.Equal(1000, controller.ExtraServicePorts[0].Port) + assert.Equal(1010, controller.ExtraServicePorts[1].Port) +} + +// TestGetNodeAddresses verifies that proper results are returned +// when requesting node addresses. +func TestGetNodeAddresses(t *testing.T) { + master, etcdserver, _, assert := setUp(t) + defer etcdserver.Terminate(t) + + // Fail case (no addresses associated with nodes) + nodes, _ := master.nodeRegistry.ListNodes(api.NewDefaultContext(), nil) + addrs, err := master.getNodeAddresses() + + assert.Error(err, "getNodeAddresses should have caused an error as there are no addresses.") + assert.Equal([]string(nil), addrs) + + // Pass case with External type IP + nodes, _ = master.nodeRegistry.ListNodes(api.NewDefaultContext(), nil) + for index := range nodes.Items { + nodes.Items[index].Status.Addresses = []api.NodeAddress{{Type: api.NodeExternalIP, Address: "127.0.0.1"}} + } + addrs, err = master.getNodeAddresses() + assert.NoError(err, "getNodeAddresses should not have returned an error.") + assert.Equal([]string{"127.0.0.1", "127.0.0.1"}, addrs) + + // Pass case with LegacyHost type IP + nodes, _ = master.nodeRegistry.ListNodes(api.NewDefaultContext(), nil) + for index := range nodes.Items { + nodes.Items[index].Status.Addresses = []api.NodeAddress{{Type: api.NodeLegacyHostIP, Address: "127.0.0.2"}} + } + addrs, err = master.getNodeAddresses() + assert.NoError(err, "getNodeAddresses failback should not have returned an error.") + assert.Equal([]string{"127.0.0.2", "127.0.0.2"}, addrs) +} + +// Because we need to be backwards compatible with release 1.1, at endpoints +// that exist in release 1.1, the responses should have empty APIVersion. +func TestAPIVersionOfDiscoveryEndpoints(t *testing.T) { + master, etcdserver, _, assert := newMaster(t) + defer etcdserver.Terminate(t) + + server := httptest.NewServer(master.HandlerContainer.ServeMux) + + // /api exists in release-1.1 + resp, err := http.Get(server.URL + "/api") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + apiVersions := unversioned.APIVersions{} + assert.NoError(decodeResponse(resp, &apiVersions)) + assert.Equal(apiVersions.APIVersion, "") + + // /api/v1 exists in release-1.1 + resp, err = http.Get(server.URL + "/api/v1") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + resourceList := unversioned.APIResourceList{} + assert.NoError(decodeResponse(resp, &resourceList)) + assert.Equal(resourceList.APIVersion, "") + + // /apis exists in release-1.1 + resp, err = http.Get(server.URL + "/apis") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + groupList := unversioned.APIGroupList{} + assert.NoError(decodeResponse(resp, &groupList)) + assert.Equal(groupList.APIVersion, "") + + // /apis/extensions exists in release-1.1 + resp, err = http.Get(server.URL + "/apis/extensions") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + group := unversioned.APIGroup{} + assert.NoError(decodeResponse(resp, &group)) + assert.Equal(group.APIVersion, "") + + // /apis/extensions/v1beta1 exists in release-1.1 + resp, err = http.Get(server.URL + "/apis/extensions/v1beta1") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + resourceList = unversioned.APIResourceList{} + assert.NoError(decodeResponse(resp, &resourceList)) + assert.Equal(resourceList.APIVersion, "") + + // /apis/autoscaling doesn't exist in release-1.1, so the APIVersion field + // should be non-empty in the results returned by the server. + resp, err = http.Get(server.URL + "/apis/autoscaling") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + group = unversioned.APIGroup{} + assert.NoError(decodeResponse(resp, &group)) + assert.Equal(group.APIVersion, "v1") + + // apis/autoscaling/v1 doesn't exist in release-1.1, so the APIVersion field + // should be non-empty in the results returned by the server. + + resp, err = http.Get(server.URL + "/apis/autoscaling/v1") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + resourceList = unversioned.APIResourceList{} + assert.NoError(decodeResponse(resp, &resourceList)) + assert.Equal(resourceList.APIVersion, "v1") + +} + +func TestDiscoveryAtAPIS(t *testing.T) { + // TODO(caesarxuchao): make this pass now that batch is added, + // and rewrite it so that the indexes do not need to change each time a new api group is added. + /* + master, etcdserver, config, assert := newMaster(t) + defer etcdserver.Terminate(t) + + server := httptest.NewServer(master.HandlerContainer.ServeMux) + resp, err := http.Get(server.URL + "/apis") + if !assert.NoError(err) { + t.Errorf("unexpected error: %v", err) + } + + assert.Equal(http.StatusOK, resp.StatusCode) + + groupList := unversioned.APIGroupList{} + assert.NoError(decodeResponse(resp, &groupList)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + expectGroupNames := []string{autoscaling.GroupName, batch.GroupName, extensions.GroupName} + expectVersions := [][]unversioned.GroupVersionForDiscovery{ + { + { + GroupVersion: testapi.Autoscaling.GroupVersion().String(), + Version: testapi.Autoscaling.GroupVersion().Version, + }, + }, + { + { + GroupVersion: testapi.Batch.GroupVersion().String(), + Version: testapi.Batch.GroupVersion().Version, + }, + }, + { + { + GroupVersion: testapi.Extensions.GroupVersion().String(), + Version: testapi.Extensions.GroupVersion().Version, + }, + }, + } + expectPreferredVersion := []unversioned.GroupVersionForDiscovery{ + { + GroupVersion: config.StorageVersions[autoscaling.GroupName], + Version: apiutil.GetVersion(config.StorageVersions[autoscaling.GroupName]), + }, + { + GroupVersion: config.StorageVersions[batch.GroupName], + Version: apiutil.GetVersion(config.StorageVersions[batch.GroupName]), + }, + { + GroupVersion: config.StorageVersions[extensions.GroupName], + Version: apiutil.GetVersion(config.StorageVersions[extensions.GroupName]), + }, + } + + + assert.Equal(2, len(groupList.Groups)) + assert.Equal(expectGroupNames[0], groupList.Groups[0].Name) + assert.Equal(expectGroupNames[1], groupList.Groups[1].Name) + + assert.Equal(expectVersions[0], groupList.Groups[0].Versions) + assert.Equal(expectVersions[1], groupList.Groups[1].Versions) + + assert.Equal(expectPreferredVersion[0], groupList.Groups[0].PreferredVersion) + assert.Equal(expectPreferredVersion[1], groupList.Groups[1].PreferredVersion) + + thirdPartyGV := unversioned.GroupVersionForDiscovery{GroupVersion: "company.com/v1", Version: "v1"} + master.addThirdPartyResourceStorage("/apis/company.com/v1", nil, + unversioned.APIGroup{ + Name: "company.com", + Versions: []unversioned.GroupVersionForDiscovery{thirdPartyGV}, + PreferredVersion: thirdPartyGV, + }) + + resp, err = http.Get(server.URL + "/apis") + if !assert.NoError(err) { + t.Errorf("unexpected error: %v", err) + } + + assert.Equal(http.StatusOK, resp.StatusCode) + + assert.NoError(decodeResponse(resp, &groupList)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + thirdPartyGroupName := "company.com" + thirdPartyExpectVersions := []unversioned.GroupVersionForDiscovery{thirdPartyGV} + + assert.Equal(4, len(groupList.Groups)) + // autoscaling group + assert.Equal(expectGroupNames[0], groupList.Groups[0].Name) + assert.Equal(expectVersions[0], groupList.Groups[0].Versions) + assert.Equal(expectPreferredVersion[0], groupList.Groups[0].PreferredVersion) + // batch group + assert.Equal(expectGroupNames[1], groupList.Groups[1].Name) + assert.Equal(expectVersions[1], groupList.Groups[1].Versions) + assert.Equal(expectPreferredVersion[1], groupList.Groups[1].PreferredVersion) + // third party + assert.Equal(thirdPartyGroupName, groupList.Groups[2].Name) + assert.Equal(thirdPartyExpectVersions, groupList.Groups[2].Versions) + assert.Equal(thirdPartyGV, groupList.Groups[2].PreferredVersion) + // extensions group + assert.Equal(expectGroupNames[2], groupList.Groups[3].Name) + assert.Equal(expectVersions[2], groupList.Groups[3].Versions) + assert.Equal(expectPreferredVersion[2], groupList.Groups[3].PreferredVersion) + */ +} + +var versionsToTest = []string{"v1", "v3"} + +type Foo struct { + unversioned.TypeMeta `json:",inline"` + api.ObjectMeta `json:"metadata,omitempty" description:"standard object metadata"` + + SomeField string `json:"someField"` + OtherField int `json:"otherField"` +} + +type FooList struct { + unversioned.TypeMeta `json:",inline"` + unversioned.ListMeta `json:"metadata,omitempty" description:"standard list metadata; see http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata"` + + Items []Foo `json:"items"` +} + +func initThirdParty(t *testing.T, version string) (*Master, *etcdtesting.EtcdTestServer, *httptest.Server, *assert.Assertions) { + master, etcdserver, _, assert := newMaster(t) + + api := &extensions.ThirdPartyResource{ + ObjectMeta: api.ObjectMeta{ + Name: "foo.company.com", + }, + Versions: []extensions.APIVersion{ + { + Name: version, + }, + }, + } + _, master.ServiceClusterIPRange, _ = net.ParseCIDR("10.0.0.0/24") + + if !assert.NoError(master.InstallThirdPartyResource(api)) { + t.FailNow() + } + + server := httptest.NewServer(master.HandlerContainer.ServeMux) + return master, etcdserver, server, assert +} + +func TestInstallThirdPartyAPIList(t *testing.T) { + for _, version := range versionsToTest { + testInstallThirdPartyAPIListVersion(t, version) + } +} + +func testInstallThirdPartyAPIListVersion(t *testing.T, version string) { + tests := []struct { + items []Foo + }{ + {}, + { + items: []Foo{}, + }, + { + items: []Foo{ + { + ObjectMeta: api.ObjectMeta{ + Name: "test", + }, + TypeMeta: unversioned.TypeMeta{ + Kind: "Foo", + APIVersion: version, + }, + SomeField: "test field", + OtherField: 10, + }, + { + ObjectMeta: api.ObjectMeta{ + Name: "bar", + }, + TypeMeta: unversioned.TypeMeta{ + Kind: "Foo", + APIVersion: version, + }, + SomeField: "test field another", + OtherField: 20, + }, + }, + }, + } + for _, test := range tests { + func() { + master, etcdserver, server, assert := initThirdParty(t, version) + // TODO: Uncomment when fix #19254 + // defer server.Close() + defer etcdserver.Terminate(t) + + if test.items != nil { + err := storeThirdPartyList(master.thirdPartyStorage, "/ThirdPartyResourceData/company.com/foos/default", test.items) + if !assert.NoError(err) { + return + } + } + + resp, err := http.Get(server.URL + "/apis/company.com/" + version + "/namespaces/default/foos") + if !assert.NoError(err) { + return + } + defer resp.Body.Close() + + assert.Equal(http.StatusOK, resp.StatusCode) + + data, err := ioutil.ReadAll(resp.Body) + assert.NoError(err) + + list := FooList{} + if err = json.Unmarshal(data, &list); err != nil { + t.Errorf("unexpected error: %v", err) + } + + if test.items == nil { + if len(list.Items) != 0 { + t.Errorf("expected no items, saw: %v", list.Items) + } + return + } + + if len(list.Items) != len(test.items) { + t.Fatalf("unexpected length: %d vs %d", len(list.Items), len(test.items)) + } + // The order of elements in LIST is not guaranteed. + mapping := make(map[string]int) + for ix := range test.items { + mapping[test.items[ix].Name] = ix + } + for ix := range list.Items { + // Copy things that are set dynamically on the server + expectedObj := test.items[mapping[list.Items[ix].Name]] + expectedObj.SelfLink = list.Items[ix].SelfLink + expectedObj.ResourceVersion = list.Items[ix].ResourceVersion + expectedObj.Namespace = list.Items[ix].Namespace + expectedObj.UID = list.Items[ix].UID + expectedObj.CreationTimestamp = list.Items[ix].CreationTimestamp + + // We endure the order of items by sorting them (using 'mapping') + // so that this function passes. + if !reflect.DeepEqual(list.Items[ix], expectedObj) { + t.Errorf("expected:\n%#v\nsaw:\n%#v\n", expectedObj, list.Items[ix]) + } + } + }() + } +} + +func encodeToThirdParty(name string, obj interface{}) (runtime.Object, error) { + serial, err := json.Marshal(obj) + if err != nil { + return nil, err + } + thirdPartyData := extensions.ThirdPartyResourceData{ + ObjectMeta: api.ObjectMeta{Name: name}, + Data: serial, + } + return &thirdPartyData, nil +} + +func storeThirdPartyObject(s storage.Interface, path, name string, obj interface{}) error { + data, err := encodeToThirdParty(name, obj) + if err != nil { + return err + } + return s.Set(context.TODO(), etcdtest.AddPrefix(path), data, nil, 0) +} + +func storeThirdPartyList(s storage.Interface, path string, list []Foo) error { + for _, obj := range list { + if err := storeThirdPartyObject(s, path+"/"+obj.Name, obj.Name, obj); err != nil { + return err + } + } + return nil +} + +func decodeResponse(resp *http.Response, obj interface{}) error { + defer resp.Body.Close() + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + if err := json.Unmarshal(data, obj); err != nil { + return err + } + return nil +} + +func TestInstallThirdPartyAPIGet(t *testing.T) { + for _, version := range versionsToTest { + testInstallThirdPartyAPIGetVersion(t, version) + } +} + +func testInstallThirdPartyAPIGetVersion(t *testing.T, version string) { + master, etcdserver, server, assert := initThirdParty(t, version) + // TODO: Uncomment when fix #19254 + // defer server.Close() + defer etcdserver.Terminate(t) + + expectedObj := Foo{ + ObjectMeta: api.ObjectMeta{ + Name: "test", + }, + TypeMeta: unversioned.TypeMeta{ + Kind: "Foo", + APIVersion: version, + }, + SomeField: "test field", + OtherField: 10, + } + if !assert.NoError(storeThirdPartyObject(master.thirdPartyStorage, "/ThirdPartyResourceData/company.com/foos/default/test", "test", expectedObj)) { + t.FailNow() + return + } + + resp, err := http.Get(server.URL + "/apis/company.com/" + version + "/namespaces/default/foos/test") + if !assert.NoError(err) { + return + } + + assert.Equal(http.StatusOK, resp.StatusCode) + + item := Foo{} + assert.NoError(decodeResponse(resp, &item)) + if !assert.False(reflect.DeepEqual(item, expectedObj)) { + t.Errorf("expected objects to not be equal:\n%v\nsaw:\n%v\n", expectedObj, item) + } + // Fill in data that the apiserver injects + expectedObj.SelfLink = item.SelfLink + expectedObj.ResourceVersion = item.ResourceVersion + if !assert.True(reflect.DeepEqual(item, expectedObj)) { + t.Errorf("expected:\n%#v\nsaw:\n%#v\n", expectedObj, item) + } +} + +func TestInstallThirdPartyAPIPost(t *testing.T) { + for _, version := range versionsToTest { + testInstallThirdPartyAPIPostForVersion(t, version) + } +} + +func testInstallThirdPartyAPIPostForVersion(t *testing.T, version string) { + master, etcdserver, server, assert := initThirdParty(t, version) + // TODO: Uncomment when fix #19254 + // defer server.Close() + defer etcdserver.Terminate(t) + + inputObj := Foo{ + ObjectMeta: api.ObjectMeta{ + Name: "test", + }, + TypeMeta: unversioned.TypeMeta{ + Kind: "Foo", + APIVersion: "company.com/" + version, + }, + SomeField: "test field", + OtherField: 10, + } + data, err := json.Marshal(inputObj) + if !assert.NoError(err) { + return + } + + resp, err := http.Post(server.URL+"/apis/company.com/"+version+"/namespaces/default/foos", "application/json", bytes.NewBuffer(data)) + if !assert.NoError(err) { + t.Fatalf("unexpected error: %v", err) + } + + assert.Equal(http.StatusCreated, resp.StatusCode) + + item := Foo{} + assert.NoError(decodeResponse(resp, &item)) + + // fill in fields set by the apiserver + expectedObj := inputObj + expectedObj.SelfLink = item.SelfLink + expectedObj.ResourceVersion = item.ResourceVersion + expectedObj.Namespace = item.Namespace + expectedObj.UID = item.UID + expectedObj.CreationTimestamp = item.CreationTimestamp + if !assert.True(reflect.DeepEqual(item, expectedObj)) { + t.Errorf("expected:\n%v\nsaw:\n%v\n", expectedObj, item) + } + + thirdPartyObj := extensions.ThirdPartyResourceData{} + err = master.thirdPartyStorage.Get( + context.TODO(), etcdtest.AddPrefix("/ThirdPartyResourceData/company.com/foos/default/test"), + &thirdPartyObj, false) + if !assert.NoError(err) { + t.FailNow() + } + + item = Foo{} + assert.NoError(json.Unmarshal(thirdPartyObj.Data, &item)) + + if !assert.True(reflect.DeepEqual(item, inputObj)) { + t.Errorf("expected:\n%v\nsaw:\n%v\n", inputObj, item) + } +} + +func TestInstallThirdPartyAPIDelete(t *testing.T) { + for _, version := range versionsToTest { + testInstallThirdPartyAPIDeleteVersion(t, version) + } +} + +func testInstallThirdPartyAPIDeleteVersion(t *testing.T, version string) { + master, etcdserver, server, assert := initThirdParty(t, version) + // TODO: Uncomment when fix #19254 + // defer server.Close() + defer etcdserver.Terminate(t) + + expectedObj := Foo{ + ObjectMeta: api.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + TypeMeta: unversioned.TypeMeta{ + Kind: "Foo", + }, + SomeField: "test field", + OtherField: 10, + } + if !assert.NoError(storeThirdPartyObject(master.thirdPartyStorage, "/ThirdPartyResourceData/company.com/foos/default/test", "test", expectedObj)) { + t.FailNow() + return + } + + resp, err := http.Get(server.URL + "/apis/company.com/" + version + "/namespaces/default/foos/test") + if !assert.NoError(err) { + return + } + + assert.Equal(http.StatusOK, resp.StatusCode) + + item := Foo{} + assert.NoError(decodeResponse(resp, &item)) + + // Fill in fields set by the apiserver + expectedObj.SelfLink = item.SelfLink + expectedObj.ResourceVersion = item.ResourceVersion + expectedObj.Namespace = item.Namespace + if !assert.True(reflect.DeepEqual(item, expectedObj)) { + t.Errorf("expected:\n%v\nsaw:\n%v\n", expectedObj, item) + } + + resp, err = httpDelete(server.URL + "/apis/company.com/" + version + "/namespaces/default/foos/test") + if !assert.NoError(err) { + return + } + + assert.Equal(http.StatusOK, resp.StatusCode) + + resp, err = http.Get(server.URL + "/apis/company.com/" + version + "/namespaces/default/foos/test") + if !assert.NoError(err) { + return + } + + assert.Equal(http.StatusNotFound, resp.StatusCode) + + expectedDeletedKey := etcdtest.AddPrefix("ThirdPartyResourceData/company.com/foos/default/test") + thirdPartyObj := extensions.ThirdPartyResourceData{} + err = master.thirdPartyStorage.Get( + context.TODO(), expectedDeletedKey, &thirdPartyObj, false) + if !storage.IsNotFound(err) { + t.Errorf("expected deletion didn't happen: %v", err) + } +} + +func httpDelete(url string) (*http.Response, error) { + req, err := http.NewRequest("DELETE", url, nil) + if err != nil { + return nil, err + } + client := &http.Client{} + return client.Do(req) +} + +func TestInstallThirdPartyAPIListOptions(t *testing.T) { + for _, version := range versionsToTest { + testInstallThirdPartyAPIListOptionsForVersion(t, version) + } +} + +func testInstallThirdPartyAPIListOptionsForVersion(t *testing.T, version string) { + _, etcdserver, server, assert := initThirdParty(t, version) + // TODO: Uncomment when fix #19254 + // defer server.Close() + defer etcdserver.Terminate(t) + + // send a GET request with query parameter + resp, err := httpGetWithRV(server.URL + "/apis/company.com/" + version + "/namespaces/default/foos") + if !assert.NoError(err) { + t.Fatalf("unexpected error: %v", err) + } + assert.Equal(http.StatusOK, resp.StatusCode) +} + +func httpGetWithRV(url string) (*http.Response, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + q := req.URL.Query() + // resourceversion is part of a ListOptions + q.Add("resourceversion", "0") + req.URL.RawQuery = q.Encode() + client := &http.Client{} + return client.Do(req) +} + +func TestInstallThirdPartyResourceRemove(t *testing.T) { + for _, version := range versionsToTest { + testInstallThirdPartyResourceRemove(t, version) + } +} + +func testInstallThirdPartyResourceRemove(t *testing.T, version string) { + master, etcdserver, server, assert := initThirdParty(t, version) + // TODO: Uncomment when fix #19254 + // defer server.Close() + defer etcdserver.Terminate(t) + + expectedObj := Foo{ + ObjectMeta: api.ObjectMeta{ + Name: "test", + }, + TypeMeta: unversioned.TypeMeta{ + Kind: "Foo", + }, + SomeField: "test field", + OtherField: 10, + } + if !assert.NoError(storeThirdPartyObject(master.thirdPartyStorage, "/ThirdPartyResourceData/company.com/foos/default/test", "test", expectedObj)) { + t.FailNow() + return + } + secondObj := expectedObj + secondObj.Name = "bar" + if !assert.NoError(storeThirdPartyObject(master.thirdPartyStorage, "/ThirdPartyResourceData/company.com/foos/default/bar", "bar", secondObj)) { + t.FailNow() + return + } + + resp, err := http.Get(server.URL + "/apis/company.com/" + version + "/namespaces/default/foos/test") + if !assert.NoError(err) { + t.FailNow() + return + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("unexpected status: %v", resp) + } + + item := Foo{} + if err := decodeResponse(resp, &item); err != nil { + t.Errorf("unexpected error: %v", err) + } + + // TODO: validate etcd set things here + item.ObjectMeta = expectedObj.ObjectMeta + + if !assert.True(reflect.DeepEqual(item, expectedObj)) { + t.Errorf("expected:\n%v\nsaw:\n%v\n", expectedObj, item) + } + + path := makeThirdPartyPath("company.com") + master.RemoveThirdPartyResource(path) + + resp, err = http.Get(server.URL + "/apis/company.com/" + version + "/namespaces/default/foos/test") + if !assert.NoError(err) { + return + } + + if resp.StatusCode != http.StatusNotFound { + t.Errorf("unexpected status: %v", resp) + } + + expectedDeletedKeys := []string{ + etcdtest.AddPrefix("/ThirdPartyResourceData/company.com/foos/default/test"), + etcdtest.AddPrefix("/ThirdPartyResourceData/company.com/foos/default/bar"), + } + for _, key := range expectedDeletedKeys { + thirdPartyObj := extensions.ThirdPartyResourceData{} + err := master.thirdPartyStorage.Get(context.TODO(), key, &thirdPartyObj, false) + if !storage.IsNotFound(err) { + t.Errorf("expected deletion didn't happen: %v", err) + } + } + installed := master.ListThirdPartyResources() + if len(installed) != 0 { + t.Errorf("Resource(s) still installed: %v", installed) + } + services := master.HandlerContainer.RegisteredWebServices() + for ix := range services { + if strings.HasPrefix(services[ix].RootPath(), "/apis/company.com") { + t.Errorf("Web service still installed at %s: %#v", services[ix].RootPath(), services[ix]) + } + } +} + +func TestThirdPartyDiscovery(t *testing.T) { + for _, version := range versionsToTest { + testThirdPartyDiscovery(t, version) + } +} + +func testThirdPartyDiscovery(t *testing.T, version string) { + _, etcdserver, server, assert := initThirdParty(t, version) + // TODO: Uncomment when fix #19254 + // defer server.Close() + defer etcdserver.Terminate(t) + + resp, err := http.Get(server.URL + "/apis/company.com/") + if !assert.NoError(err) { + return + } + assert.Equal(http.StatusOK, resp.StatusCode) + + group := unversioned.APIGroup{} + assert.NoError(decodeResponse(resp, &group)) + assert.Equal(group.APIVersion, "v1") + assert.Equal(group.Kind, "APIGroup") + assert.Equal(group.Name, "company.com") + assert.Equal(group.Versions, []unversioned.GroupVersionForDiscovery{ + { + GroupVersion: "company.com/" + version, + Version: version, + }, + }) + assert.Equal(group.PreferredVersion, unversioned.GroupVersionForDiscovery{}) + + resp, err = http.Get(server.URL + "/apis/company.com/" + version) + if !assert.NoError(err) { + return + } + assert.Equal(http.StatusOK, resp.StatusCode) + + resourceList := unversioned.APIResourceList{} + assert.NoError(decodeResponse(resp, &resourceList)) + assert.Equal(resourceList.APIVersion, "v1") + assert.Equal(resourceList.Kind, "APIResourceList") + assert.Equal(resourceList.GroupVersion, "company.com/"+version) + assert.Equal(resourceList.APIResources, []unversioned.APIResource{ + { + Name: "foos", + Namespaced: true, + Kind: "Foo", + }, + }) +} diff --git a/vendor/k8s.io/kubernetes/pkg/master/thirdparty_controller.go b/vendor/k8s.io/kubernetes/pkg/master/thirdparty_controller.go new file mode 100644 index 000000000..15b44dfc4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/master/thirdparty_controller.go @@ -0,0 +1,130 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 master + +import ( + "fmt" + "strings" + + "k8s.io/kubernetes/pkg/api" + expapi "k8s.io/kubernetes/pkg/apis/extensions" + thirdpartyresourceetcd "k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd" + "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/sets" +) + +const thirdpartyprefix = "/apis" + +func makeThirdPartyPath(group string) string { + if len(group) == 0 { + return thirdpartyprefix + } + return thirdpartyprefix + "/" + group +} + +func getThirdPartyGroupName(path string) string { + return strings.TrimPrefix(strings.TrimPrefix(path, thirdpartyprefix), "/") +} + +// resourceInterface is the interface for the parts of the master that know how to add/remove +// third party resources. Extracted into an interface for injection for testing. +type resourceInterface interface { + // Remove a third party resource based on the RESTful path for that resource + RemoveThirdPartyResource(path string) error + // Install a third party resource described by 'rsrc' + InstallThirdPartyResource(rsrc *expapi.ThirdPartyResource) error + // Is a particular third party resource currently installed? + HasThirdPartyResource(rsrc *expapi.ThirdPartyResource) (bool, error) + // List all currently installed third party resources + ListThirdPartyResources() []string +} + +// ThirdPartyController is a control loop that knows how to synchronize ThirdPartyResource objects with +// RESTful resources which are present in the API server. +type ThirdPartyController struct { + master resourceInterface + thirdPartyResourceRegistry *thirdpartyresourceetcd.REST +} + +// Synchronize a single resource with RESTful resources on the master +func (t *ThirdPartyController) SyncOneResource(rsrc *expapi.ThirdPartyResource) error { + // TODO: we also need to test if the existing installed resource matches the resource we are sync-ing. + // Currently, if there is an older, incompatible resource installed, we won't remove it. We should detect + // older, incompatible resources and remove them before testing if the resource exists. + hasResource, err := t.master.HasThirdPartyResource(rsrc) + if err != nil { + return err + } + if !hasResource { + return t.master.InstallThirdPartyResource(rsrc) + } + return nil +} + +// Synchronize all resources with RESTful resources on the master +func (t *ThirdPartyController) SyncResources() error { + list, err := t.thirdPartyResourceRegistry.List(api.NewDefaultContext(), nil) + if err != nil { + return err + } + return t.syncResourceList(list) +} + +func (t *ThirdPartyController) syncResourceList(list runtime.Object) error { + existing := sets.String{} + switch list := list.(type) { + case *expapi.ThirdPartyResourceList: + // Loop across all schema objects for third party resources + for ix := range list.Items { + item := &list.Items[ix] + // extract the api group and resource kind from the schema + _, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(item) + if err != nil { + return err + } + // place it in the set of resources that we expect, so that we don't delete it in the delete pass + existing.Insert(makeThirdPartyPath(group)) + // ensure a RESTful resource for this schema exists on the master + if err := t.SyncOneResource(item); err != nil { + return err + } + } + default: + return fmt.Errorf("expected a *ThirdPartyResourceList, got %#v", list) + } + // deletion phase, get all installed RESTful resources + installed := t.master.ListThirdPartyResources() + for _, installedAPI := range installed { + found := false + // search across the expected restful resources to see if this resource belongs to one of the expected ones + for _, apiPath := range existing.List() { + if installedAPI == apiPath || strings.HasPrefix(installedAPI, apiPath+"/") { + found = true + break + } + } + // not expected, delete the resource + if !found { + if err := t.master.RemoveThirdPartyResource(installedAPI); err != nil { + return err + } + } + } + + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/master/thirdparty_controller_test.go b/vendor/k8s.io/kubernetes/pkg/master/thirdparty_controller_test.go new file mode 100644 index 000000000..be1b77756 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/master/thirdparty_controller_test.go @@ -0,0 +1,204 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 master + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + expapi "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata" + "k8s.io/kubernetes/pkg/util/sets" +) + +type FakeAPIInterface struct { + removed []string + installed []*expapi.ThirdPartyResource + apis []string + t *testing.T +} + +func (f *FakeAPIInterface) RemoveThirdPartyResource(path string) error { + f.removed = append(f.removed, path) + return nil +} + +func (f *FakeAPIInterface) InstallThirdPartyResource(rsrc *expapi.ThirdPartyResource) error { + f.installed = append(f.installed, rsrc) + _, group, _ := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc) + f.apis = append(f.apis, makeThirdPartyPath(group)) + return nil +} + +func (f *FakeAPIInterface) HasThirdPartyResource(rsrc *expapi.ThirdPartyResource) (bool, error) { + if f.apis == nil { + return false, nil + } + _, group, _ := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc) + path := makeThirdPartyPath(group) + for _, api := range f.apis { + if api == path { + return true, nil + } + } + return false, nil +} + +func (f *FakeAPIInterface) ListThirdPartyResources() []string { + return f.apis +} + +func TestSyncAPIs(t *testing.T) { + tests := []struct { + list *expapi.ThirdPartyResourceList + apis []string + expectedInstalled []string + expectedRemoved []string + name string + }{ + { + list: &expapi.ThirdPartyResourceList{ + Items: []expapi.ThirdPartyResource{ + { + ObjectMeta: api.ObjectMeta{ + Name: "foo.example.com", + }, + }, + }, + }, + expectedInstalled: []string{"foo.example.com"}, + name: "simple add", + }, + { + list: &expapi.ThirdPartyResourceList{ + Items: []expapi.ThirdPartyResource{ + { + ObjectMeta: api.ObjectMeta{ + Name: "foo.example.com", + }, + }, + }, + }, + apis: []string{ + "/apis/example.com", + "/apis/example.com/v1", + }, + name: "does nothing", + }, + { + list: &expapi.ThirdPartyResourceList{ + Items: []expapi.ThirdPartyResource{ + { + ObjectMeta: api.ObjectMeta{ + Name: "foo.example.com", + }, + }, + }, + }, + apis: []string{ + "/apis/example.com", + "/apis/example.com/v1", + "/apis/example.co", + "/apis/example.co/v1", + }, + name: "deletes substring API", + expectedRemoved: []string{ + "/apis/example.co", + "/apis/example.co/v1", + }, + }, + { + list: &expapi.ThirdPartyResourceList{ + Items: []expapi.ThirdPartyResource{ + { + ObjectMeta: api.ObjectMeta{ + Name: "foo.example.com", + }, + }, + { + ObjectMeta: api.ObjectMeta{ + Name: "foo.company.com", + }, + }, + }, + }, + apis: []string{ + "/apis/company.com", + "/apis/company.com/v1", + }, + expectedInstalled: []string{"foo.example.com"}, + name: "adds with existing", + }, + { + list: &expapi.ThirdPartyResourceList{ + Items: []expapi.ThirdPartyResource{ + { + ObjectMeta: api.ObjectMeta{ + Name: "foo.example.com", + }, + }, + }, + }, + apis: []string{ + "/apis/company.com", + "/apis/company.com/v1", + }, + expectedInstalled: []string{"foo.example.com"}, + expectedRemoved: []string{"/apis/company.com", "/apis/company.com/v1"}, + name: "removes with existing", + }, + } + + for _, test := range tests { + fake := FakeAPIInterface{ + apis: test.apis, + t: t, + } + + cntrl := ThirdPartyController{master: &fake} + + if err := cntrl.syncResourceList(test.list); err != nil { + t.Errorf("[%s] unexpected error: %v", test.name, err) + } + if len(test.expectedInstalled) != len(fake.installed) { + t.Errorf("[%s] unexpected installed APIs: %d, expected %d (%#v)", test.name, len(fake.installed), len(test.expectedInstalled), fake.installed[0]) + continue + } else { + names := sets.String{} + for ix := range fake.installed { + names.Insert(fake.installed[ix].Name) + } + for _, name := range test.expectedInstalled { + if !names.Has(name) { + t.Errorf("[%s] missing installed API: %s", test.name, name) + } + } + } + if len(test.expectedRemoved) != len(fake.removed) { + t.Errorf("[%s] unexpected installed APIs: %d, expected %d", test.name, len(fake.removed), len(test.expectedRemoved)) + continue + } else { + names := sets.String{} + names.Insert(fake.removed...) + for _, name := range test.expectedRemoved { + if !names.Has(name) { + t.Errorf("[%s] missing removed API: %s (%s)", test.name, name, names) + } + } + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/master/tunneler.go b/vendor/k8s.io/kubernetes/pkg/master/tunneler.go new file mode 100644 index 000000000..2da04849a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/master/tunneler.go @@ -0,0 +1,203 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 master + +import ( + "io/ioutil" + "net" + "net/url" + "os" + "sync/atomic" + "time" + + "k8s.io/kubernetes/pkg/ssh" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/wait" + + "github.com/golang/glog" + "github.com/prometheus/client_golang/prometheus" +) + +type InstallSSHKey func(user string, data []byte) error + +type AddressFunc func() (addresses []string, err error) + +type Tunneler interface { + Run(AddressFunc) + Stop() + Dial(net, addr string) (net.Conn, error) + SecondsSinceSync() int64 + SecondsSinceSSHKeySync() int64 +} + +type SSHTunneler struct { + SSHUser string + SSHKeyfile string + InstallSSHKey InstallSSHKey + HealthCheckURL *url.URL + + tunnels *ssh.SSHTunnelList + lastSync int64 // Seconds since Epoch + lastSSHKeySync int64 // Seconds since Epoch + lastSyncMetric prometheus.GaugeFunc + clock util.Clock + + getAddresses AddressFunc + stopChan chan struct{} +} + +func NewSSHTunneler(sshUser, sshKeyfile string, healthCheckURL *url.URL, installSSHKey InstallSSHKey) Tunneler { + return &SSHTunneler{ + SSHUser: sshUser, + SSHKeyfile: sshKeyfile, + InstallSSHKey: installSSHKey, + HealthCheckURL: healthCheckURL, + clock: util.RealClock{}, + } +} + +// Run establishes tunnel loops and returns +func (c *SSHTunneler) Run(getAddresses AddressFunc) { + if c.stopChan != nil { + return + } + c.stopChan = make(chan struct{}) + + // Save the address getter + if getAddresses != nil { + c.getAddresses = getAddresses + } + + // Usernames are capped @ 32 + if len(c.SSHUser) > 32 { + glog.Warning("SSH User is too long, truncating to 32 chars") + c.SSHUser = c.SSHUser[0:32] + } + glog.Infof("Setting up proxy: %s %s", c.SSHUser, c.SSHKeyfile) + + // public keyfile is written last, so check for that. + publicKeyFile := c.SSHKeyfile + ".pub" + exists, err := util.FileExists(publicKeyFile) + if err != nil { + glog.Errorf("Error detecting if key exists: %v", err) + } else if !exists { + glog.Infof("Key doesn't exist, attempting to create") + if err := generateSSHKey(c.SSHKeyfile, publicKeyFile); err != nil { + glog.Errorf("Failed to create key pair: %v", err) + } + } + + c.tunnels = ssh.NewSSHTunnelList(c.SSHUser, c.SSHKeyfile, c.HealthCheckURL, c.stopChan) + // Sync loop to ensure that the SSH key has been installed. + c.lastSSHKeySync = c.clock.Now().Unix() + c.installSSHKeySyncLoop(c.SSHUser, publicKeyFile) + // Sync tunnelList w/ nodes. + c.lastSync = c.clock.Now().Unix() + c.nodesSyncLoop() +} + +// Stop gracefully shuts down the tunneler +func (c *SSHTunneler) Stop() { + if c.stopChan != nil { + close(c.stopChan) + c.stopChan = nil + } +} + +func (c *SSHTunneler) Dial(net, addr string) (net.Conn, error) { + return c.tunnels.Dial(net, addr) +} + +func (c *SSHTunneler) SecondsSinceSync() int64 { + now := c.clock.Now().Unix() + then := atomic.LoadInt64(&c.lastSync) + return now - then +} + +func (c *SSHTunneler) SecondsSinceSSHKeySync() int64 { + now := c.clock.Now().Unix() + then := atomic.LoadInt64(&c.lastSSHKeySync) + return now - then +} + +func (c *SSHTunneler) installSSHKeySyncLoop(user, publicKeyfile string) { + go wait.Until(func() { + if c.InstallSSHKey == nil { + glog.Error("Won't attempt to install ssh key: InstallSSHKey function is nil") + return + } + key, err := ssh.ParsePublicKeyFromFile(publicKeyfile) + if err != nil { + glog.Errorf("Failed to load public key: %v", err) + return + } + keyData, err := ssh.EncodeSSHKey(key) + if err != nil { + glog.Errorf("Failed to encode public key: %v", err) + return + } + if err := c.InstallSSHKey(user, keyData); err != nil { + glog.Errorf("Failed to install ssh key: %v", err) + return + } + atomic.StoreInt64(&c.lastSSHKeySync, c.clock.Now().Unix()) + }, 5*time.Minute, c.stopChan) +} + +// nodesSyncLoop lists nodes ever 15 seconds, calling Update() on the TunnelList +// each time (Update() is a noop if no changes are necessary). +func (c *SSHTunneler) nodesSyncLoop() { + // TODO (cjcullen) make this watch. + go wait.Until(func() { + addrs, err := c.getAddresses() + glog.Infof("Calling update w/ addrs: %v", addrs) + if err != nil { + glog.Errorf("Failed to getAddresses: %v", err) + } + c.tunnels.Update(addrs) + atomic.StoreInt64(&c.lastSync, c.clock.Now().Unix()) + }, 15*time.Second, c.stopChan) +} + +func generateSSHKey(privateKeyfile, publicKeyfile string) error { + private, public, err := ssh.GenerateKey(2048) + if err != nil { + return err + } + // If private keyfile already exists, we must have only made it halfway + // through last time, so delete it. + exists, err := util.FileExists(privateKeyfile) + if err != nil { + glog.Errorf("Error detecting if private key exists: %v", err) + } else if exists { + glog.Infof("Private key exists, but public key does not") + if err := os.Remove(privateKeyfile); err != nil { + glog.Errorf("Failed to remove stale private key: %v", err) + } + } + if err := ioutil.WriteFile(privateKeyfile, ssh.EncodePrivateKey(private), 0600); err != nil { + return err + } + publicKeyBytes, err := ssh.EncodePublicKey(public) + if err != nil { + return err + } + if err := ioutil.WriteFile(publicKeyfile+".tmp", publicKeyBytes, 0600); err != nil { + return err + } + return os.Rename(publicKeyfile+".tmp", publicKeyfile) +} diff --git a/vendor/k8s.io/kubernetes/pkg/master/tunneler_test.go b/vendor/k8s.io/kubernetes/pkg/master/tunneler_test.go new file mode 100644 index 000000000..3f208ee12 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/master/tunneler_test.go @@ -0,0 +1,127 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 master + +import ( + "fmt" + "os" + "path/filepath" + "testing" + "time" + + "k8s.io/kubernetes/pkg/util" + + "github.com/stretchr/testify/assert" +) + +// TestSecondsSinceSync verifies that proper results are returned +// when checking the time between syncs +func TestSecondsSinceSync(t *testing.T) { + tunneler := &SSHTunneler{} + assert := assert.New(t) + + tunneler.lastSync = time.Date(2015, time.January, 1, 1, 1, 1, 1, time.UTC).Unix() + + // Nano Second. No difference. + tunneler.clock = util.NewFakeClock(time.Date(2015, time.January, 1, 1, 1, 1, 2, time.UTC)) + assert.Equal(int64(0), tunneler.SecondsSinceSync()) + + // Second + tunneler.clock = util.NewFakeClock(time.Date(2015, time.January, 1, 1, 1, 2, 1, time.UTC)) + assert.Equal(int64(1), tunneler.SecondsSinceSync()) + + // Minute + tunneler.clock = util.NewFakeClock(time.Date(2015, time.January, 1, 1, 2, 1, 1, time.UTC)) + assert.Equal(int64(60), tunneler.SecondsSinceSync()) + + // Hour + tunneler.clock = util.NewFakeClock(time.Date(2015, time.January, 1, 2, 1, 1, 1, time.UTC)) + assert.Equal(int64(3600), tunneler.SecondsSinceSync()) + + // Day + tunneler.clock = util.NewFakeClock(time.Date(2015, time.January, 2, 1, 1, 1, 1, time.UTC)) + assert.Equal(int64(86400), tunneler.SecondsSinceSync()) + + // Month + tunneler.clock = util.NewFakeClock(time.Date(2015, time.February, 1, 1, 1, 1, 1, time.UTC)) + assert.Equal(int64(2678400), tunneler.SecondsSinceSync()) + + // Future Month. Should be -Month. + tunneler.lastSync = time.Date(2015, time.February, 1, 1, 1, 1, 1, time.UTC).Unix() + tunneler.clock = util.NewFakeClock(time.Date(2015, time.January, 1, 1, 1, 1, 1, time.UTC)) + assert.Equal(int64(-2678400), tunneler.SecondsSinceSync()) +} + +// TestIsTunnelSyncHealthy verifies that the 600 second lag test +// is honored. +func TestIsTunnelSyncHealthy(t *testing.T) { + tunneler := &SSHTunneler{} + master, etcdserver, _, assert := setUp(t) + defer etcdserver.Terminate(t) + master.tunneler = tunneler + + // Pass case: 540 second lag + tunneler.lastSync = time.Date(2015, time.January, 1, 1, 1, 1, 1, time.UTC).Unix() + tunneler.lastSSHKeySync = time.Date(2015, time.January, 1, 1, 1, 1, 1, time.UTC).Unix() + tunneler.clock = util.NewFakeClock(time.Date(2015, time.January, 1, 1, 9, 1, 1, time.UTC)) + err := master.IsTunnelSyncHealthy(nil) + assert.NoError(err, "IsTunnelSyncHealthy() should not have returned an error.") + + // Fail case: 720 second lag + tunneler.clock = util.NewFakeClock(time.Date(2015, time.January, 1, 1, 12, 1, 1, time.UTC)) + err = master.IsTunnelSyncHealthy(nil) + assert.Error(err, "IsTunnelSyncHealthy() should have returned an error.") +} + +// generateTempFile creates a temporary file path +func generateTempFilePath(prefix string) string { + tmpPath, _ := filepath.Abs(fmt.Sprintf("%s/%s-%d", os.TempDir(), prefix, time.Now().Unix())) + return tmpPath +} + +// TestGenerateSSHKey verifies that SSH key generation does indeed +// generate keys even with keys already exist. +func TestGenerateSSHKey(t *testing.T) { + assert := assert.New(t) + + privateKey := generateTempFilePath("private") + publicKey := generateTempFilePath("public") + + // Make sure we have no test keys laying around + os.Remove(privateKey) + os.Remove(publicKey) + + // Pass case: Sunny day case + err := generateSSHKey(privateKey, publicKey) + assert.NoError(err, "generateSSHKey should not have retuend an error: %s", err) + + // Pass case: PrivateKey exists test case + os.Remove(publicKey) + err = generateSSHKey(privateKey, publicKey) + assert.NoError(err, "generateSSHKey should not have retuend an error: %s", err) + + // Pass case: PublicKey exists test case + os.Remove(privateKey) + err = generateSSHKey(privateKey, publicKey) + assert.NoError(err, "generateSSHKey should not have retuend an error: %s", err) + + // Make sure we have no test keys laying around + os.Remove(privateKey) + os.Remove(publicKey) + + // TODO: testing error cases where the file can not be removed? +} diff --git a/vendor/k8s.io/kubernetes/pkg/metrics/api_server_metrics.go b/vendor/k8s.io/kubernetes/pkg/metrics/api_server_metrics.go new file mode 100644 index 000000000..e905ab4b9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/metrics/api_server_metrics.go @@ -0,0 +1,79 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 metrics + +import ( + "k8s.io/kubernetes/pkg/util/sets" + + "github.com/prometheus/common/model" +) + +var KnownApiServerMetrics = map[string][]string{ + "apiserver_request_count": {"verb", "resource", "client", "code"}, + "apiserver_request_latencies_bucket": {"verb", "resource", "le"}, + "apiserver_request_latencies_count": {"verb", "resource"}, + "apiserver_request_latencies_sum": {"verb", "resource"}, + "apiserver_request_latencies_summary": {"verb", "resource", "quantile"}, + "apiserver_request_latencies_summary_count": {"verb", "resource"}, + "apiserver_request_latencies_summary_sum": {"verb", "resource"}, + "etcd_helper_cache_entry_count": {}, + "etcd_helper_cache_hit_count": {}, + "etcd_helper_cache_miss_count": {}, + "etcd_request_cache_add_latencies_summary": {"quantile"}, + "etcd_request_cache_add_latencies_summary_count": {}, + "etcd_request_cache_add_latencies_summary_sum": {}, + "etcd_request_cache_get_latencies_summary": {"quantile"}, + "etcd_request_cache_get_latencies_summary_count": {}, + "etcd_request_cache_get_latencies_summary_sum": {}, + "etcd_request_latencies_summary": {"operation", "type", "quantile"}, + "etcd_request_latencies_summary_count": {"operation", "type"}, + "etcd_request_latencies_summary_sum": {"operation", "type"}, + "rest_client_request_latency_microseconds": {"url", "verb", "quantile"}, + "rest_client_request_latency_microseconds_count": {"url", "verb"}, + "rest_client_request_latency_microseconds_sum": {"url", "verb"}, + "rest_client_request_status_codes": {"code", "host", "method"}, +} + +type ApiServerMetrics Metrics + +func (m *ApiServerMetrics) Equal(o ApiServerMetrics) bool { + return (*Metrics)(m).Equal(Metrics(o)) +} + +func NewApiServerMetrics() ApiServerMetrics { + result := NewMetrics() + for metric := range KnownApiServerMetrics { + result[metric] = make(model.Samples, 0) + } + return ApiServerMetrics(result) +} + +func parseApiServerMetrics(data string, unknownMetrics sets.String) (ApiServerMetrics, error) { + result := NewApiServerMetrics() + if err := parseMetrics(data, KnownApiServerMetrics, (*Metrics)(&result), unknownMetrics); err != nil { + return ApiServerMetrics{}, err + } + return result, nil +} + +func (g *MetricsGrabber) getMetricsFromApiServer() (string, error) { + rawOutput, err := g.client.Get().RequestURI("/metrics").Do().Raw() + if err != nil { + return "", err + } + return string(rawOutput), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/metrics/controller_manager_metrics.go b/vendor/k8s.io/kubernetes/pkg/metrics/controller_manager_metrics.go new file mode 100644 index 000000000..defbfc0bb --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/metrics/controller_manager_metrics.go @@ -0,0 +1,63 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 metrics + +import ( + "k8s.io/kubernetes/pkg/util/sets" + + "github.com/prometheus/common/model" +) + +var KnownControllerManagerMetrics = map[string][]string{ + "etcd_helper_cache_entry_count": {}, + "etcd_helper_cache_hit_count": {}, + "etcd_helper_cache_miss_count": {}, + "etcd_request_cache_add_latencies_summary": {"quantile"}, + "etcd_request_cache_add_latencies_summary_count": {}, + "etcd_request_cache_add_latencies_summary_sum": {}, + "etcd_request_cache_get_latencies_summary": {"quantile"}, + "etcd_request_cache_get_latencies_summary_count": {}, + "etcd_request_cache_get_latencies_summary_sum": {}, + "get_token_count": {}, + "get_token_fail_count": {}, + "rest_client_request_latency_microseconds": {"url", "verb", "quantile"}, + "rest_client_request_latency_microseconds_count": {"url", "verb"}, + "rest_client_request_latency_microseconds_sum": {"url", "verb"}, + "rest_client_request_status_codes": {"method", "code", "host"}, +} + +type ControllerManagerMetrics Metrics + +func (m *ControllerManagerMetrics) Equal(o ControllerManagerMetrics) bool { + return (*Metrics)(m).Equal(Metrics(o)) +} + +func NewControllerManagerMetrics() ControllerManagerMetrics { + result := NewMetrics() + for metric := range KnownControllerManagerMetrics { + result[metric] = make(model.Samples, 0) + } + return ControllerManagerMetrics(result) +} + +func parseControllerManagerMetrics(data string, unknownMetrics sets.String) (ControllerManagerMetrics, error) { + result := NewControllerManagerMetrics() + if err := parseMetrics(data, KnownControllerManagerMetrics, (*Metrics)(&result), unknownMetrics); err != nil { + return ControllerManagerMetrics{}, err + } + return result, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/metrics/generic_metrics.go b/vendor/k8s.io/kubernetes/pkg/metrics/generic_metrics.go new file mode 100644 index 000000000..b0905bc5e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/metrics/generic_metrics.go @@ -0,0 +1,158 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 metrics + +import ( + "fmt" + "io" + "reflect" + "strings" + + "k8s.io/kubernetes/pkg/util/sets" + + "github.com/golang/glog" + "github.com/prometheus/common/expfmt" + "github.com/prometheus/common/model" +) + +var CommonMetrics = map[string][]string{ + "get_token_count": {}, + "get_token_fail_count": {}, + "go_gc_duration_seconds": {"quantile"}, + "go_gc_duration_seconds_count": {}, + "go_gc_duration_seconds_sum": {}, + "go_goroutines": {}, + "http_request_duration_microseconds": {"handler", "quantile"}, + "http_request_duration_microseconds_count": {"handler"}, + "http_request_duration_microseconds_sum": {"handler"}, + "http_request_size_bytes": {"handler", "quantile"}, + "http_request_size_bytes_count": {"handler"}, + "http_request_size_bytes_sum": {"handler"}, + "http_requests_total": {"handler", "method", "code"}, + "http_response_size_bytes": {"handler", "quantile"}, + "http_response_size_bytes_count": {"handler"}, + "http_response_size_bytes_sum": {"handler"}, + "kubernetes_build_info": {"major", "minor", "gitCommit", "gitTreeState", "gitVersion", "buildDate", "goVersion", "compiler", "platform"}, + "process_cpu_seconds_total": {}, + "process_max_fds": {}, + "process_open_fds": {}, + "process_resident_memory_bytes": {}, + "process_start_time_seconds": {}, + "process_virtual_memory_bytes": {}, + "ssh_tunnel_open_count": {}, + "ssh_tunnel_open_fail_count": {}, +} + +type Metrics map[string]model.Samples + +func (m *Metrics) Equal(o Metrics) bool { + leftKeySet := []string{} + rightKeySet := []string{} + for k := range *m { + leftKeySet = append(leftKeySet, k) + } + for k := range o { + rightKeySet = append(rightKeySet, k) + } + if !reflect.DeepEqual(leftKeySet, rightKeySet) { + return false + } + for _, k := range leftKeySet { + if !(*m)[k].Equal(o[k]) { + return false + } + } + return true +} + +func PrintSample(sample *model.Sample) string { + buf := make([]string, 0) + // Id is a VERY special label. For 'normal' container it's usless, but it's necessary + // for 'system' containers (e.g. /docker-daemon, /kubelet, etc.). We know if that's the + // case by checking if there's a label "kubernetes_container_name" present. It's hacky + // but it works... + _, normalContainer := sample.Metric["kubernetes_container_name"] + for k, v := range sample.Metric { + if strings.HasPrefix(string(k), "__") || KubeletMetricsLabelsToSkip.Has(string(k)) { + continue + } + + if string(k) == "id" && normalContainer { + continue + } + buf = append(buf, fmt.Sprintf("%v=%v", string(k), v)) + } + return fmt.Sprintf("[%v] = %v", strings.Join(buf, ","), sample.Value) +} + +func NewMetrics() Metrics { + result := make(Metrics) + for metric := range CommonMetrics { + result[metric] = make(model.Samples, 0) + } + return result +} + +func parseMetrics(data string, knownMetrics map[string][]string, output *Metrics, unknownMetrics sets.String) error { + dec, err := expfmt.NewDecoder(strings.NewReader(data), expfmt.FmtText) + if err != nil { + return err + } + decoder := expfmt.SampleDecoder{ + Dec: dec, + Opts: &expfmt.DecodeOptions{}, + } + + for { + var v model.Vector + if err = decoder.Decode(&v); err != nil { + if err == io.EOF { + // Expected loop termination condition. + return nil + } + glog.Warningf("Invalid Decode. Skipping.") + continue + } + for _, metric := range v { + name := string(metric.Metric[model.MetricNameLabel]) + _, isCommonMetric := CommonMetrics[name] + _, isKnownMetric := knownMetrics[name] + if isKnownMetric || isCommonMetric { + (*output)[name] = append((*output)[name], metric) + } else { + glog.Warningf("Unknown metric %v", metric) + if unknownMetrics != nil { + unknownMetrics.Insert(name) + } + } + } + } +} + +func (g *MetricsGrabber) getMetricsFromPod(podName string, namespace string, port int) (string, error) { + rawOutput, err := g.client.Get(). + Prefix("proxy"). + Namespace(namespace). + Resource("pods"). + Name(fmt.Sprintf("%v:%v", podName, port)). + Suffix("metrics"). + Do().Raw() + if err != nil { + return "", err + } + return string(rawOutput), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/metrics/kubelet_metrics.go b/vendor/k8s.io/kubernetes/pkg/metrics/kubelet_metrics.go new file mode 100644 index 000000000..323b80fa5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/metrics/kubelet_metrics.go @@ -0,0 +1,161 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 metrics + +import ( + "fmt" + "time" + + "k8s.io/kubernetes/pkg/util/sets" + + "github.com/prometheus/common/model" +) + +var NecessaryKubeletMetrics = map[string][]string{ + "cadvisor_version_info": {"cadvisorRevision", "cadvisorVersion", "dockerVersion", "kernelVersion", "osVersion"}, + "container_cpu_system_seconds_total": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_cpu_usage_seconds_total": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name", "cpu"}, + "container_cpu_user_seconds_total": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_fs_io_current": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_fs_io_time_seconds_total": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_fs_io_time_weighted_seconds_total": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_fs_limit_bytes": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_fs_read_seconds_total": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_fs_reads_merged_total": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_fs_reads_total": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_fs_sector_reads_total": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_fs_sector_writes_total": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_fs_usage_bytes": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_fs_write_seconds_total": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_fs_writes_merged_total": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_fs_writes_total": {"device", "id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_last_seen": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_memory_cache": {}, + "container_memory_rss": {}, + "container_memory_failcnt": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_memory_failures_total": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name", "scope", "type"}, + "container_memory_usage_bytes": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_memory_working_set_bytes": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_network_receive_bytes_total": {"id", "interface", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_network_receive_errors_total": {"id", "image", "interface", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_network_receive_packets_dropped_total": {"id", "image", "interface", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_network_receive_packets_total": {"id", "image", "interface", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_network_transmit_bytes_total": {"id", "interface", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_network_transmit_errors_total": {"id", "interface", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_network_transmit_packets_dropped_total": {"id", "interface", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_network_transmit_packets_total": {"id", "interface", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_scrape_error": {}, + "container_spec_cpu_period": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_spec_cpu_shares": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_spec_memory_limit_bytes": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_spec_memory_swap_limit_bytes": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_start_time_seconds": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name"}, + "container_tasks_state": {"id", "image", "kubernetes_container_name", "kubernetes_namespace", "kubernetes_pod_name", "name", "state"}, + "kubelet_container_manager_latency_microseconds": {"operation_type", "quantile"}, + "kubelet_container_manager_latency_microseconds_count": {"operation_type"}, + "kubelet_container_manager_latency_microseconds_sum": {"operation_type"}, + "kubelet_containers_per_pod_count": {"quantile"}, + "kubelet_containers_per_pod_count_count": {}, + "kubelet_containers_per_pod_count_sum": {}, + "kubelet_docker_errors": {"operation_type"}, + "kubelet_docker_operations_latency_microseconds": {"operation_type", "quantile"}, + "kubelet_docker_operations_latency_microseconds_count": {"operation_type"}, + "kubelet_docker_operations_latency_microseconds_sum": {"operation_type"}, + "kubelet_generate_pod_status_latency_microseconds": {"quantile"}, + "kubelet_generate_pod_status_latency_microseconds_count": {}, + "kubelet_generate_pod_status_latency_microseconds_sum": {}, + "kubelet_pleg_relist_latency_microseconds": {"quantile"}, + "kubelet_pleg_relist_latency_microseconds_sum": {}, + "kubelet_pleg_relist_latency_microseconds_count": {}, + "kubelet_pleg_relist_interval_microseconds": {"quantile"}, + "kubelet_pleg_relist_interval_microseconds_sum": {}, + "kubelet_pleg_relist_interval_microseconds_count": {}, + "kubelet_pod_start_latency_microseconds": {"quantile"}, + "kubelet_pod_start_latency_microseconds_count": {}, + "kubelet_pod_start_latency_microseconds_sum": {}, + "kubelet_pod_worker_latency_microseconds": {"operation_type", "quantile"}, + "kubelet_pod_worker_latency_microseconds_count": {"operation_type"}, + "kubelet_pod_worker_latency_microseconds_sum": {"operation_type"}, + "kubelet_pod_worker_start_latency_microseconds": {"quantile"}, + "kubelet_pod_worker_start_latency_microseconds_count": {}, + "kubelet_pod_worker_start_latency_microseconds_sum": {}, + "kubelet_running_container_count": {}, + "kubelet_running_pod_count": {}, + "kubelet_sync_pods_latency_microseconds": {"quantile"}, + "kubelet_sync_pods_latency_microseconds_count": {}, + "kubelet_sync_pods_latency_microseconds_sum": {}, + "machine_cpu_cores": {}, + "machine_memory_bytes": {}, + "rest_client_request_latency_microseconds": {"quantile", "url", "verb"}, + "rest_client_request_latency_microseconds_count": {"url", "verb"}, + "rest_client_request_latency_microseconds_sum": {"url", "verb"}, + "rest_client_request_status_codes": {"code", "host", "method"}, +} + +var KubeletMetricsLabelsToSkip = sets.NewString( + "kubernetes_namespace", + "image", + "name", +) + +type KubeletMetrics Metrics + +func (m *KubeletMetrics) Equal(o KubeletMetrics) bool { + return (*Metrics)(m).Equal(Metrics(o)) +} + +func NewKubeletMetrics() KubeletMetrics { + result := NewMetrics() + for metric := range NecessaryKubeletMetrics { + result[metric] = make(model.Samples, 0) + } + return KubeletMetrics(result) +} + +func parseKubeletMetrics(data string) (KubeletMetrics, error) { + result := NewKubeletMetrics() + if err := parseMetrics(data, NecessaryKubeletMetrics, (*Metrics)(&result), nil); err != nil { + return KubeletMetrics{}, err + } + return result, nil +} + +func (g *MetricsGrabber) getMetricsFromNode(nodeName string, kubeletPort int) (string, error) { + // There's a problem with timing out during proxy. Wrapping this in a goroutine to prevent deadlock. + // Hanging goroutine will be leaked. + finished := make(chan struct{}) + var err error + var rawOutput []byte + go func() { + rawOutput, err = g.client.Get(). + Prefix("proxy"). + Resource("nodes"). + Name(fmt.Sprintf("%v:%v", nodeName, kubeletPort)). + Suffix("metrics"). + Do().Raw() + finished <- struct{}{} + }() + select { + case <-time.After(ProxyTimeout): + return "", fmt.Errorf("Timed out when waiting for proxy to gather metrics from %v", nodeName) + case <-finished: + if err != nil { + return "", err + } + return string(rawOutput), nil + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/metrics/metrics_grabber.go b/vendor/k8s.io/kubernetes/pkg/metrics/metrics_grabber.go new file mode 100644 index 000000000..6058d71b9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/metrics/metrics_grabber.go @@ -0,0 +1,188 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 metrics + +import ( + "fmt" + "time" + + "k8s.io/kubernetes/pkg/api" + client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/master/ports" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/system" + + "github.com/golang/glog" +) + +const ( + ProxyTimeout = 2 * time.Minute +) + +type MetricsCollection struct { + ApiServerMetrics ApiServerMetrics + ControllerManagerMetrics ControllerManagerMetrics + KubeletMetrics map[string]KubeletMetrics + SchedulerMetrics SchedulerMetrics +} + +type MetricsGrabber struct { + client *client.Client + grabFromApiServer bool + grabFromControllerManager bool + grabFromKubelets bool + grabFromScheduler bool + masterName string + registeredMaster bool +} + +func NewMetricsGrabber(c *client.Client, kubelets bool, scheduler bool, controllers bool, apiServer bool) (*MetricsGrabber, error) { + registeredMaster := false + masterName := "" + nodeList, err := c.Nodes().List(api.ListOptions{}) + if err != nil { + return nil, err + } + if len(nodeList.Items) < 1 { + glog.Warning("Can't find any Nodes in the API server to grab metrics from") + } + for _, node := range nodeList.Items { + if system.IsMasterNode(&node) { + registeredMaster = true + masterName = node.Name + break + } + } + if !registeredMaster { + scheduler = false + controllers = false + glog.Warningf("Master node is not registered. Grabbing metrics from Scheduler and ControllerManager is disabled.") + } + + return &MetricsGrabber{ + client: c, + grabFromApiServer: apiServer, + grabFromControllerManager: controllers, + grabFromKubelets: kubelets, + grabFromScheduler: scheduler, + masterName: masterName, + registeredMaster: registeredMaster, + }, nil +} + +func (g *MetricsGrabber) GrabFromKubelet(nodeName string) (KubeletMetrics, error) { + nodes, err := g.client.Nodes().List(api.ListOptions{FieldSelector: fields.Set{api.ObjectNameField: nodeName}.AsSelector()}) + if err != nil { + return KubeletMetrics{}, err + } + if len(nodes.Items) != 1 { + return KubeletMetrics{}, fmt.Errorf("Error listing nodes with name %v, got %v", nodeName, nodes.Items) + } + kubeletPort := nodes.Items[0].Status.DaemonEndpoints.KubeletEndpoint.Port + return g.grabFromKubeletInternal(nodeName, kubeletPort) +} + +func (g *MetricsGrabber) grabFromKubeletInternal(nodeName string, kubeletPort int) (KubeletMetrics, error) { + if kubeletPort <= 0 || kubeletPort > 65535 { + return KubeletMetrics{}, fmt.Errorf("Invalid Kubelet port %v. Skipping Kubelet's metrics gathering.", kubeletPort) + } + output, err := g.getMetricsFromNode(nodeName, kubeletPort) + if err != nil { + return KubeletMetrics{}, err + } + return parseKubeletMetrics(output) +} + +func (g *MetricsGrabber) GrabFromScheduler(unknownMetrics sets.String) (SchedulerMetrics, error) { + if !g.registeredMaster { + return SchedulerMetrics{}, fmt.Errorf("Master's Kubelet is not registered. Skipping Scheduler's metrics gathering.") + } + output, err := g.getMetricsFromPod(fmt.Sprintf("%v-%v", "kube-scheduler", g.masterName), api.NamespaceSystem, ports.SchedulerPort) + if err != nil { + return SchedulerMetrics{}, err + } + return parseSchedulerMetrics(output, unknownMetrics) +} + +func (g *MetricsGrabber) GrabFromControllerManager(unknownMetrics sets.String) (ControllerManagerMetrics, error) { + if !g.registeredMaster { + return ControllerManagerMetrics{}, fmt.Errorf("Master's Kubelet is not registered. Skipping ControllerManager's metrics gathering.") + } + output, err := g.getMetricsFromPod(fmt.Sprintf("%v-%v", "kube-controller-manager", g.masterName), api.NamespaceSystem, ports.ControllerManagerPort) + if err != nil { + return ControllerManagerMetrics{}, err + } + return parseControllerManagerMetrics(output, unknownMetrics) +} + +func (g *MetricsGrabber) GrabFromApiServer(unknownMetrics sets.String) (ApiServerMetrics, error) { + output, err := g.getMetricsFromApiServer() + if err != nil { + return ApiServerMetrics{}, nil + } + return parseApiServerMetrics(output, unknownMetrics) +} + +func (g *MetricsGrabber) Grab(unknownMetrics sets.String) (MetricsCollection, error) { + result := MetricsCollection{} + var errs []error + if g.grabFromApiServer { + metrics, err := g.GrabFromApiServer(nil) + if err != nil { + errs = append(errs, err) + } else { + result.ApiServerMetrics = metrics + } + } + if g.grabFromScheduler { + metrics, err := g.GrabFromScheduler(nil) + if err != nil { + errs = append(errs, err) + } else { + result.SchedulerMetrics = metrics + } + } + if g.grabFromControllerManager { + metrics, err := g.GrabFromControllerManager(nil) + if err != nil { + errs = append(errs, err) + } else { + result.ControllerManagerMetrics = metrics + } + } + if g.grabFromKubelets { + result.KubeletMetrics = make(map[string]KubeletMetrics) + nodes, err := g.client.Nodes().List(api.ListOptions{}) + if err != nil { + errs = append(errs, err) + } else { + for _, node := range nodes.Items { + kubeletPort := node.Status.DaemonEndpoints.KubeletEndpoint.Port + metrics, err := g.grabFromKubeletInternal(node.Name, kubeletPort) + if err != nil { + errs = append(errs, err) + } + result.KubeletMetrics[node.Name] = metrics + } + } + } + if len(errs) > 0 { + return MetricsCollection{}, fmt.Errorf("Errors while grabbing metrics: %v", errs) + } + return result, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/metrics/scheduler_metrics.go b/vendor/k8s.io/kubernetes/pkg/metrics/scheduler_metrics.go new file mode 100644 index 000000000..831ce6217 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/metrics/scheduler_metrics.go @@ -0,0 +1,61 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 metrics + +import ( + "k8s.io/kubernetes/pkg/util/sets" + + "github.com/prometheus/common/model" +) + +var KnownSchedulerMetrics = map[string][]string{ + "rest_client_request_latency_microseconds": {"url", "verb", "quantile"}, + "rest_client_request_latency_microseconds_count": {"url", "verb"}, + "rest_client_request_latency_microseconds_sum": {"url", "verb"}, + "rest_client_request_status_codes": {"code", "host", "method"}, + "scheduler_binding_latency_microseconds_bucket": {"le"}, + "scheduler_binding_latency_microseconds_count": {}, + "scheduler_binding_latency_microseconds_sum": {}, + "scheduler_e2e_scheduling_latency_microseconds_bucket": {"le"}, + "scheduler_e2e_scheduling_latency_microseconds_count": {}, + "scheduler_e2e_scheduling_latency_microseconds_sum": {}, + "scheduler_scheduling_algorithm_latency_microseconds_bucket": {"le"}, + "scheduler_scheduling_algorithm_latency_microseconds_count": {}, + "scheduler_scheduling_algorithm_latency_microseconds_sum": {}, +} + +type SchedulerMetrics Metrics + +func (m *SchedulerMetrics) Equal(o SchedulerMetrics) bool { + return (*Metrics)(m).Equal(Metrics(o)) +} + +func NewSchedulerMetrics() SchedulerMetrics { + result := NewMetrics() + for metric := range KnownSchedulerMetrics { + result[metric] = make(model.Samples, 0) + } + return SchedulerMetrics(result) +} + +func parseSchedulerMetrics(data string, unknownMetrics sets.String) (SchedulerMetrics, error) { + result := NewSchedulerMetrics() + if err := parseMetrics(data, KnownSchedulerMetrics, (*Metrics)(&result), unknownMetrics); err != nil { + return SchedulerMetrics{}, err + } + return result, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/probe/doc.go b/vendor/k8s.io/kubernetes/pkg/probe/doc.go new file mode 100644 index 000000000..dbdfe44db --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/probe/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 probe contains utilities for health probing, as well as health status information. +package probe diff --git a/vendor/k8s.io/kubernetes/pkg/probe/exec/exec.go b/vendor/k8s.io/kubernetes/pkg/probe/exec/exec.go new file mode 100644 index 000000000..a8ea0f6e3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/probe/exec/exec.go @@ -0,0 +1,51 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 exec + +import ( + "k8s.io/kubernetes/pkg/probe" + "k8s.io/kubernetes/pkg/util/exec" + + "github.com/golang/glog" +) + +func New() ExecProber { + return execProber{} +} + +type ExecProber interface { + Probe(e exec.Cmd) (probe.Result, string, error) +} + +type execProber struct{} + +func (pr execProber) Probe(e exec.Cmd) (probe.Result, string, error) { + data, err := e.CombinedOutput() + glog.V(4).Infof("Exec probe response: %q", string(data)) + if err != nil { + exit, ok := err.(exec.ExitError) + if ok { + if exit.ExitStatus() == 0 { + return probe.Success, string(data), nil + } else { + return probe.Failure, string(data), nil + } + } + return probe.Unknown, "", err + } + return probe.Success, string(data), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/probe/exec/exec_test.go b/vendor/k8s.io/kubernetes/pkg/probe/exec/exec_test.go new file mode 100644 index 000000000..6c01871dd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/probe/exec/exec_test.go @@ -0,0 +1,100 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 exec + +import ( + "fmt" + "testing" + + "k8s.io/kubernetes/pkg/probe" +) + +type FakeCmd struct { + out []byte + stdout []byte + err error +} + +func (f *FakeCmd) CombinedOutput() ([]byte, error) { + return f.out, f.err +} + +func (f *FakeCmd) Output() ([]byte, error) { + return f.stdout, f.err +} + +func (f *FakeCmd) SetDir(dir string) {} + +type fakeExitError struct { + exited bool + statusCode int +} + +func (f *fakeExitError) String() string { + return f.Error() +} + +func (f *fakeExitError) Error() string { + return "fake exit" +} + +func (f *fakeExitError) Exited() bool { + return f.exited +} + +func (f *fakeExitError) ExitStatus() int { + return f.statusCode +} + +func TestExec(t *testing.T) { + prober := New() + + tests := []struct { + expectedStatus probe.Result + expectError bool + output string + err error + }{ + // Ok + {probe.Success, false, "OK", nil}, + // Ok + {probe.Success, false, "OK", &fakeExitError{true, 0}}, + // Run returns error + {probe.Unknown, true, "", fmt.Errorf("test error")}, + // Unhealthy + {probe.Failure, false, "Fail", &fakeExitError{true, 1}}, + } + for i, test := range tests { + fake := FakeCmd{ + out: []byte(test.output), + err: test.err, + } + status, output, err := prober.Probe(&fake) + if status != test.expectedStatus { + t.Errorf("[%d] expected %v, got %v", i, test.expectedStatus, status) + } + if err != nil && test.expectError == false { + t.Errorf("[%d] unexpected error: %v", i, err) + } + if err == nil && test.expectError == true { + t.Errorf("[%d] unexpected non-error", i) + } + if test.output != output { + t.Errorf("[%d] expected %s, got %s", i, test.output, output) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/probe/http/http.go b/vendor/k8s.io/kubernetes/pkg/probe/http/http.go new file mode 100644 index 000000000..9e8b0e116 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/probe/http/http.go @@ -0,0 +1,84 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 http + +import ( + "crypto/tls" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "time" + + "k8s.io/kubernetes/pkg/probe" + utilnet "k8s.io/kubernetes/pkg/util/net" + + "github.com/golang/glog" +) + +func New() HTTPProber { + tlsConfig := &tls.Config{InsecureSkipVerify: true} + transport := utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: tlsConfig, DisableKeepAlives: true}) + return httpProber{transport} +} + +type HTTPProber interface { + Probe(url *url.URL, headers http.Header, timeout time.Duration) (probe.Result, string, error) +} + +type httpProber struct { + transport *http.Transport +} + +// Probe returns a ProbeRunner capable of running an http check. +func (pr httpProber) Probe(url *url.URL, headers http.Header, timeout time.Duration) (probe.Result, string, error) { + return DoHTTPProbe(url, headers, &http.Client{Timeout: timeout, Transport: pr.transport}) +} + +type HTTPGetInterface interface { + Do(req *http.Request) (*http.Response, error) +} + +// DoHTTPProbe checks if a GET request to the url succeeds. +// If the HTTP response code is successful (i.e. 400 > code >= 200), it returns Success. +// If the HTTP response code is unsuccessful or HTTP communication fails, it returns Failure. +// This is exported because some other packages may want to do direct HTTP probes. +func DoHTTPProbe(url *url.URL, headers http.Header, client HTTPGetInterface) (probe.Result, string, error) { + req, err := http.NewRequest("GET", url.String(), nil) + if err != nil { + // Convert errors into failures to catch timeouts. + return probe.Failure, err.Error(), nil + } + req.Header = headers + res, err := client.Do(req) + if err != nil { + // Convert errors into failures to catch timeouts. + return probe.Failure, err.Error(), nil + } + defer res.Body.Close() + b, err := ioutil.ReadAll(res.Body) + if err != nil { + return probe.Failure, "", err + } + body := string(b) + if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusBadRequest { + glog.V(4).Infof("Probe succeeded for %s, Response: %v", url.String(), *res) + return probe.Success, body, nil + } + glog.V(4).Infof("Probe failed for %s with request headers %v, response body: %v", url.String(), headers, body) + return probe.Failure, fmt.Sprintf("HTTP probe failed with statuscode: %d", res.StatusCode), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/probe/http/http_test.go b/vendor/k8s.io/kubernetes/pkg/probe/http/http_test.go new file mode 100644 index 000000000..748abe748 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/probe/http/http_test.go @@ -0,0 +1,140 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 http + +import ( + "fmt" + "net" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "strings" + "testing" + "time" + + "k8s.io/kubernetes/pkg/probe" +) + +const FailureCode int = -1 + +func containsAny(s string, substrs []string) bool { + for _, substr := range substrs { + if strings.Contains(s, substr) { + return true + } + } + return false +} + +func TestHTTPProbeChecker(t *testing.T) { + handleReq := func(s int, body string) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(s) + w.Write([]byte(body)) + } + } + + prober := New() + testCases := []struct { + handler func(w http.ResponseWriter, r *http.Request) + reqHeaders http.Header + health probe.Result + // go1.5: error message changed for timeout, need to support + // both old and new + accBodies []string + }{ + // The probe will be filled in below. This is primarily testing that an HTTP GET happens. + { + handleReq(http.StatusOK, "ok body"), + nil, + probe.Success, + []string{"ok body"}, + }, + { + // Echo handler that returns the contents of request headers in the body + func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + output := "" + for k, arr := range r.Header { + for _, v := range arr { + output += fmt.Sprintf("%s: %s\n", k, v) + } + } + w.Write([]byte(output)) + }, + http.Header{ + "X-Muffins-Or-Cupcakes": {"muffins"}, + }, + probe.Success, + []string{ + "X-Muffins-Or-Cupcakes: muffins", + }, + }, + { + handleReq(FailureCode, "fail body"), + nil, + probe.Failure, + []string{ + fmt.Sprintf("HTTP probe failed with statuscode: %d", FailureCode), + fmt.Sprintf("malformed HTTP status code \"%d\"", FailureCode), + }, + }, + { + func(w http.ResponseWriter, r *http.Request) { + time.Sleep(3 * time.Second) + }, + nil, + probe.Failure, + []string{ + "use of closed network connection", + "request canceled (Client.Timeout exceeded while awaiting headers)", + }, + }, + } + for _, test := range testCases { + // TODO: Close() this when fix #19254 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + test.handler(w, r) + })) + u, err := url.Parse(server.URL) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + _, port, err := net.SplitHostPort(u.Host) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + _, err = strconv.Atoi(port) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + health, output, err := prober.Probe(u, test.reqHeaders, 1*time.Second) + if test.health == probe.Unknown && err == nil { + t.Errorf("Expected error") + } + if test.health != probe.Unknown && err != nil { + t.Errorf("Unexpected error: %v", err) + } + if health != test.health { + t.Errorf("Expected %v, got %v", test.health, health) + } + if !containsAny(output, test.accBodies) { + t.Errorf("Expected one of %#v, got %v", test.accBodies, output) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/probe/probe.go b/vendor/k8s.io/kubernetes/pkg/probe/probe.go new file mode 100644 index 000000000..f175860d7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/probe/probe.go @@ -0,0 +1,25 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 probe + +type Result string + +const ( + Success Result = "success" + Failure Result = "failure" + Unknown Result = "unknown" +) diff --git a/vendor/k8s.io/kubernetes/pkg/probe/tcp/tcp.go b/vendor/k8s.io/kubernetes/pkg/probe/tcp/tcp.go new file mode 100644 index 000000000..8e3676fbd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/probe/tcp/tcp.go @@ -0,0 +1,58 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 tcp + +import ( + "net" + "strconv" + "time" + + "k8s.io/kubernetes/pkg/probe" + + "github.com/golang/glog" +) + +func New() TCPProber { + return tcpProber{} +} + +type TCPProber interface { + Probe(host string, port int, timeout time.Duration) (probe.Result, string, error) +} + +type tcpProber struct{} + +func (pr tcpProber) Probe(host string, port int, timeout time.Duration) (probe.Result, string, error) { + return DoTCPProbe(net.JoinHostPort(host, strconv.Itoa(port)), timeout) +} + +// DoTCPProbe checks that a TCP socket to the address can be opened. +// If the socket can be opened, it returns Success +// If the socket fails to open, it returns Failure. +// This is exported because some other packages may want to do direct TCP probes. +func DoTCPProbe(addr string, timeout time.Duration) (probe.Result, string, error) { + conn, err := net.DialTimeout("tcp", addr, timeout) + if err != nil { + // Convert errors to failures to handle timeouts. + return probe.Failure, err.Error(), nil + } + err = conn.Close() + if err != nil { + glog.Errorf("Unexpected error closing TCP probe socket: %v (%#v)", err, err) + } + return probe.Success, "", nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/probe/tcp/tcp_test.go b/vendor/k8s.io/kubernetes/pkg/probe/tcp/tcp_test.go new file mode 100644 index 000000000..606121806 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/probe/tcp/tcp_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 tcp + +import ( + "net" + "net/http" + "net/http/httptest" + "strconv" + "strings" + "testing" + "time" + + "k8s.io/kubernetes/pkg/probe" +) + +func containsAny(s string, substrs []string) bool { + for _, substr := range substrs { + if strings.Contains(s, substr) { + return true + } + } + return false +} + +func TestTcpHealthChecker(t *testing.T) { + // Setup a test server that responds to probing correctly + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + // TODO: Uncomment when fix #19254 + // defer server.Close() + tHost, tPortStr, err := net.SplitHostPort(server.Listener.Addr().String()) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + tPort, err := strconv.Atoi(tPortStr) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + tests := []struct { + host string + port int + + expectedStatus probe.Result + expectedError error + // Some errors are different depending on your system. + // The test passes as long as the output matches one of them. + expectedOutputs []string + }{ + // A connection is made and probing would succeed + {tHost, tPort, probe.Success, nil, []string{""}}, + // No connection can be made and probing would fail + {tHost, -1, probe.Failure, nil, []string{ + "unknown port", + "Servname not supported for ai_socktype", + "nodename nor servname provided, or not known", + "dial tcp: invalid port", + }}, + } + + prober := New() + for i, tt := range tests { + status, output, err := prober.Probe(tt.host, tt.port, 1*time.Second) + if status != tt.expectedStatus { + t.Errorf("#%d: expected status=%v, get=%v", i, tt.expectedStatus, status) + } + if err != tt.expectedError { + t.Errorf("#%d: expected error=%v, get=%v", i, tt.expectedError, err) + } + if !containsAny(output, tt.expectedOutputs) { + t.Errorf("#%d: expected output=one of %#v, get=%s", i, tt.expectedOutputs, output) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/OWNERS b/vendor/k8s.io/kubernetes/pkg/proxy/OWNERS new file mode 100644 index 000000000..3e9886c07 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/OWNERS @@ -0,0 +1,3 @@ +assignees: + - ArtfulCoder + - thockin diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/config/api.go b/vendor/k8s.io/kubernetes/pkg/proxy/config/api.go new file mode 100644 index 000000000..55951e5a7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/config/api.go @@ -0,0 +1,61 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 config + +import ( + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/cache" + client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/fields" +) + +// NewSourceAPI creates config source that watches for changes to the services and endpoints. +func NewSourceAPI(c *client.Client, period time.Duration, servicesChan chan<- ServiceUpdate, endpointsChan chan<- EndpointsUpdate) { + servicesLW := cache.NewListWatchFromClient(c, "services", api.NamespaceAll, fields.Everything()) + endpointsLW := cache.NewListWatchFromClient(c, "endpoints", api.NamespaceAll, fields.Everything()) + + newServicesSourceApiFromLW(servicesLW, period, servicesChan) + newEndpointsSourceApiFromLW(endpointsLW, period, endpointsChan) +} + +func newServicesSourceApiFromLW(servicesLW cache.ListerWatcher, period time.Duration, servicesChan chan<- ServiceUpdate) { + servicesPush := func(objs []interface{}) { + var services []api.Service + for _, o := range objs { + services = append(services, *(o.(*api.Service))) + } + servicesChan <- ServiceUpdate{Op: SET, Services: services} + } + + serviceQueue := cache.NewUndeltaStore(servicesPush, cache.MetaNamespaceKeyFunc) + cache.NewReflector(servicesLW, &api.Service{}, serviceQueue, period).Run() +} + +func newEndpointsSourceApiFromLW(endpointsLW cache.ListerWatcher, period time.Duration, endpointsChan chan<- EndpointsUpdate) { + endpointsPush := func(objs []interface{}) { + var endpoints []api.Endpoints + for _, o := range objs { + endpoints = append(endpoints, *(o.(*api.Endpoints))) + } + endpointsChan <- EndpointsUpdate{Op: SET, Endpoints: endpoints} + } + + endpointQueue := cache.NewUndeltaStore(endpointsPush, cache.MetaNamespaceKeyFunc) + cache.NewReflector(endpointsLW, &api.Endpoints{}, endpointQueue, period).Run() +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/config/api_test.go b/vendor/k8s.io/kubernetes/pkg/proxy/config/api_test.go new file mode 100644 index 000000000..6e5b0e11d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/config/api_test.go @@ -0,0 +1,245 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 config + +import ( + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/client/cache" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/watch" +) + +type fakeLW struct { + listResp runtime.Object + watchResp watch.Interface +} + +func (lw fakeLW) List(options api.ListOptions) (runtime.Object, error) { + return lw.listResp, nil +} + +func (lw fakeLW) Watch(options api.ListOptions) (watch.Interface, error) { + return lw.watchResp, nil +} + +var _ cache.ListerWatcher = fakeLW{} + +func TestNewServicesSourceApi_UpdatesAndMultipleServices(t *testing.T) { + service1v1 := &api.Service{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "s1"}, + Spec: api.ServiceSpec{Ports: []api.ServicePort{{Protocol: "TCP", Port: 10}}}} + service1v2 := &api.Service{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "s1"}, + Spec: api.ServiceSpec{Ports: []api.ServicePort{{Protocol: "TCP", Port: 20}}}} + service2 := &api.Service{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "s2"}, + Spec: api.ServiceSpec{Ports: []api.ServicePort{{Protocol: "TCP", Port: 30}}}} + + // Setup fake api client. + fakeWatch := watch.NewFake() + lw := fakeLW{ + listResp: &api.ServiceList{Items: []api.Service{}}, + watchResp: fakeWatch, + } + + ch := make(chan ServiceUpdate) + + newServicesSourceApiFromLW(lw, 30*time.Second, ch) + + got, ok := <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + expected := ServiceUpdate{Op: SET, Services: []api.Service{}} + if !api.Semantic.DeepEqual(expected, got) { + t.Errorf("Expected %#v; Got %#v", expected, got) + } + + // Add the first service + fakeWatch.Add(service1v1) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + expected = ServiceUpdate{Op: SET, Services: []api.Service{*service1v1}} + if !api.Semantic.DeepEqual(expected, got) { + t.Errorf("Expected %#v; Got %#v", expected, got) + } + + // Add another service + fakeWatch.Add(service2) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + // Could be sorted either of these two ways: + expectedA := ServiceUpdate{Op: SET, Services: []api.Service{*service1v1, *service2}} + expectedB := ServiceUpdate{Op: SET, Services: []api.Service{*service2, *service1v1}} + + if !api.Semantic.DeepEqual(expectedA, got) && !api.Semantic.DeepEqual(expectedB, got) { + t.Errorf("Expected %#v or %#v, Got %#v", expectedA, expectedB, got) + } + + // Modify service1 + fakeWatch.Modify(service1v2) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + expectedA = ServiceUpdate{Op: SET, Services: []api.Service{*service1v2, *service2}} + expectedB = ServiceUpdate{Op: SET, Services: []api.Service{*service2, *service1v2}} + + if !api.Semantic.DeepEqual(expectedA, got) && !api.Semantic.DeepEqual(expectedB, got) { + t.Errorf("Expected %#v or %#v, Got %#v", expectedA, expectedB, got) + } + + // Delete service1 + fakeWatch.Delete(service1v2) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + expected = ServiceUpdate{Op: SET, Services: []api.Service{*service2}} + if !api.Semantic.DeepEqual(expected, got) { + t.Errorf("Expected %#v, Got %#v", expected, got) + } + + // Delete service2 + fakeWatch.Delete(service2) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + expected = ServiceUpdate{Op: SET, Services: []api.Service{}} + if !api.Semantic.DeepEqual(expected, got) { + t.Errorf("Expected %#v, Got %#v", expected, got) + } +} + +func TestNewEndpointsSourceApi_UpdatesAndMultipleEndpoints(t *testing.T) { + endpoints1v1 := &api.Endpoints{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "e1"}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "1.2.3.4"}, + }, + Ports: []api.EndpointPort{{Port: 8080, Protocol: "TCP"}}, + }}, + } + endpoints1v2 := &api.Endpoints{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "e1"}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "1.2.3.4"}, + {IP: "4.3.2.1"}, + }, + Ports: []api.EndpointPort{{Port: 8080, Protocol: "TCP"}}, + }}, + } + endpoints2 := &api.Endpoints{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "e2"}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{ + {IP: "5.6.7.8"}, + }, + Ports: []api.EndpointPort{{Port: 80, Protocol: "TCP"}}, + }}, + } + + // Setup fake api client. + fakeWatch := watch.NewFake() + lw := fakeLW{ + listResp: &api.EndpointsList{Items: []api.Endpoints{}}, + watchResp: fakeWatch, + } + + ch := make(chan EndpointsUpdate) + + newEndpointsSourceApiFromLW(lw, 30*time.Second, ch) + + got, ok := <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + expected := EndpointsUpdate{Op: SET, Endpoints: []api.Endpoints{}} + if !api.Semantic.DeepEqual(expected, got) { + t.Errorf("Expected %#v; Got %#v", expected, got) + } + + // Add the first endpoints + fakeWatch.Add(endpoints1v1) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + expected = EndpointsUpdate{Op: SET, Endpoints: []api.Endpoints{*endpoints1v1}} + if !api.Semantic.DeepEqual(expected, got) { + t.Errorf("Expected %#v; Got %#v", expected, got) + } + + // Add another endpoints + fakeWatch.Add(endpoints2) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + // Could be sorted either of these two ways: + expectedA := EndpointsUpdate{Op: SET, Endpoints: []api.Endpoints{*endpoints1v1, *endpoints2}} + expectedB := EndpointsUpdate{Op: SET, Endpoints: []api.Endpoints{*endpoints2, *endpoints1v1}} + + if !api.Semantic.DeepEqual(expectedA, got) && !api.Semantic.DeepEqual(expectedB, got) { + t.Errorf("Expected %#v or %#v, Got %#v", expectedA, expectedB, got) + } + + // Modify endpoints1 + fakeWatch.Modify(endpoints1v2) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + expectedA = EndpointsUpdate{Op: SET, Endpoints: []api.Endpoints{*endpoints1v2, *endpoints2}} + expectedB = EndpointsUpdate{Op: SET, Endpoints: []api.Endpoints{*endpoints2, *endpoints1v2}} + + if !api.Semantic.DeepEqual(expectedA, got) && !api.Semantic.DeepEqual(expectedB, got) { + t.Errorf("Expected %#v or %#v, Got %#v", expectedA, expectedB, got) + } + + // Delete endpoints1 + fakeWatch.Delete(endpoints1v2) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + expected = EndpointsUpdate{Op: SET, Endpoints: []api.Endpoints{*endpoints2}} + if !api.Semantic.DeepEqual(expected, got) { + t.Errorf("Expected %#v, Got %#v", expected, got) + } + + // Delete endpoints2 + fakeWatch.Delete(endpoints2) + got, ok = <-ch + if !ok { + t.Errorf("Unable to read from channel when expected") + } + expected = EndpointsUpdate{Op: SET, Endpoints: []api.Endpoints{}} + if !api.Semantic.DeepEqual(expected, got) { + t.Errorf("Expected %#v, Got %#v", expected, got) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/config/config.go b/vendor/k8s.io/kubernetes/pkg/proxy/config/config.go new file mode 100644 index 000000000..2181ca93f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/config/config.go @@ -0,0 +1,299 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 config + +import ( + "sync" + + "github.com/davecgh/go-spew/spew" + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/config" +) + +// Operation is a type of operation of services or endpoints. +type Operation int + +// These are the available operation types. +const ( + SET Operation = iota + ADD + REMOVE +) + +// ServiceUpdate describes an operation of services, sent on the channel. +// You can add or remove single services by sending an array of size one and Op == ADD|REMOVE. +// For setting the state of the system to a given state for this source configuration, set Services as desired and Op to SET, +// which will reset the system state to that specified in this operation for this source channel. +// To remove all services, set Services to empty array and Op to SET +type ServiceUpdate struct { + Services []api.Service + Op Operation +} + +// EndpointsUpdate describes an operation of endpoints, sent on the channel. +// You can add or remove single endpoints by sending an array of size one and Op == ADD|REMOVE. +// For setting the state of the system to a given state for this source configuration, set Endpoints as desired and Op to SET, +// which will reset the system state to that specified in this operation for this source channel. +// To remove all endpoints, set Endpoints to empty array and Op to SET +type EndpointsUpdate struct { + Endpoints []api.Endpoints + Op Operation +} + +// ServiceConfigHandler is an abstract interface of objects which receive update notifications for the set of services. +type ServiceConfigHandler interface { + // OnServiceUpdate gets called when a configuration has been changed by one of the sources. + // This is the union of all the configuration sources. + OnServiceUpdate(services []api.Service) +} + +// EndpointsConfigHandler is an abstract interface of objects which receive update notifications for the set of endpoints. +type EndpointsConfigHandler interface { + // OnEndpointsUpdate gets called when endpoints configuration is changed for a given + // service on any of the configuration sources. An example is when a new + // service comes up, or when containers come up or down for an existing service. + OnEndpointsUpdate(endpoints []api.Endpoints) +} + +// EndpointsConfig tracks a set of endpoints configurations. +// It accepts "set", "add" and "remove" operations of endpoints via channels, and invokes registered handlers on change. +type EndpointsConfig struct { + mux *config.Mux + bcaster *config.Broadcaster + store *endpointsStore +} + +// NewEndpointsConfig creates a new EndpointsConfig. +// It immediately runs the created EndpointsConfig. +func NewEndpointsConfig() *EndpointsConfig { + // The updates channel is used to send interrupts to the Endpoints handler. + // It's buffered because we never want to block for as long as there is a + // pending interrupt, but don't want to drop them if the handler is doing + // work. + updates := make(chan struct{}, 1) + store := &endpointsStore{updates: updates, endpoints: make(map[string]map[types.NamespacedName]api.Endpoints)} + mux := config.NewMux(store) + bcaster := config.NewBroadcaster() + go watchForUpdates(bcaster, store, updates) + return &EndpointsConfig{mux, bcaster, store} +} + +func (c *EndpointsConfig) RegisterHandler(handler EndpointsConfigHandler) { + c.bcaster.Add(config.ListenerFunc(func(instance interface{}) { + glog.V(3).Infof("Calling handler.OnEndpointsUpdate()") + handler.OnEndpointsUpdate(instance.([]api.Endpoints)) + })) +} + +func (c *EndpointsConfig) Channel(source string) chan EndpointsUpdate { + ch := c.mux.Channel(source) + endpointsCh := make(chan EndpointsUpdate) + go func() { + for update := range endpointsCh { + ch <- update + } + close(ch) + }() + return endpointsCh +} + +func (c *EndpointsConfig) Config() []api.Endpoints { + return c.store.MergedState().([]api.Endpoints) +} + +type endpointsStore struct { + endpointLock sync.RWMutex + endpoints map[string]map[types.NamespacedName]api.Endpoints + updates chan<- struct{} +} + +func (s *endpointsStore) Merge(source string, change interface{}) error { + s.endpointLock.Lock() + endpoints := s.endpoints[source] + if endpoints == nil { + endpoints = make(map[types.NamespacedName]api.Endpoints) + } + update := change.(EndpointsUpdate) + switch update.Op { + case ADD: + glog.V(5).Infof("Adding new endpoint from source %s : %s", source, spew.Sdump(update.Endpoints)) + for _, value := range update.Endpoints { + name := types.NamespacedName{Namespace: value.Namespace, Name: value.Name} + endpoints[name] = value + } + case REMOVE: + glog.V(5).Infof("Removing an endpoint %s", spew.Sdump(update)) + for _, value := range update.Endpoints { + name := types.NamespacedName{Namespace: value.Namespace, Name: value.Name} + delete(endpoints, name) + } + case SET: + glog.V(5).Infof("Setting endpoints %s", spew.Sdump(update)) + // Clear the old map entries by just creating a new map + endpoints = make(map[types.NamespacedName]api.Endpoints) + for _, value := range update.Endpoints { + name := types.NamespacedName{Namespace: value.Namespace, Name: value.Name} + endpoints[name] = value + } + default: + glog.V(4).Infof("Received invalid update type: %s", spew.Sdump(update)) + } + s.endpoints[source] = endpoints + s.endpointLock.Unlock() + if s.updates != nil { + // Since we record the snapshot before sending this signal, it's + // possible that the consumer ends up performing an extra update. + select { + case s.updates <- struct{}{}: + default: + glog.V(4).Infof("Endpoints handler already has a pending interrupt.") + } + } + return nil +} + +func (s *endpointsStore) MergedState() interface{} { + s.endpointLock.RLock() + defer s.endpointLock.RUnlock() + endpoints := make([]api.Endpoints, 0) + for _, sourceEndpoints := range s.endpoints { + for _, value := range sourceEndpoints { + endpoints = append(endpoints, value) + } + } + return endpoints +} + +// ServiceConfig tracks a set of service configurations. +// It accepts "set", "add" and "remove" operations of services via channels, and invokes registered handlers on change. +type ServiceConfig struct { + mux *config.Mux + bcaster *config.Broadcaster + store *serviceStore +} + +// NewServiceConfig creates a new ServiceConfig. +// It immediately runs the created ServiceConfig. +func NewServiceConfig() *ServiceConfig { + // The updates channel is used to send interrupts to the Services handler. + // It's buffered because we never want to block for as long as there is a + // pending interrupt, but don't want to drop them if the handler is doing + // work. + updates := make(chan struct{}, 1) + store := &serviceStore{updates: updates, services: make(map[string]map[types.NamespacedName]api.Service)} + mux := config.NewMux(store) + bcaster := config.NewBroadcaster() + go watchForUpdates(bcaster, store, updates) + return &ServiceConfig{mux, bcaster, store} +} + +func (c *ServiceConfig) RegisterHandler(handler ServiceConfigHandler) { + c.bcaster.Add(config.ListenerFunc(func(instance interface{}) { + glog.V(3).Infof("Calling handler.OnServiceUpdate()") + handler.OnServiceUpdate(instance.([]api.Service)) + })) +} + +func (c *ServiceConfig) Channel(source string) chan ServiceUpdate { + ch := c.mux.Channel(source) + serviceCh := make(chan ServiceUpdate) + go func() { + for update := range serviceCh { + ch <- update + } + close(ch) + }() + return serviceCh +} + +func (c *ServiceConfig) Config() []api.Service { + return c.store.MergedState().([]api.Service) +} + +type serviceStore struct { + serviceLock sync.RWMutex + services map[string]map[types.NamespacedName]api.Service + updates chan<- struct{} +} + +func (s *serviceStore) Merge(source string, change interface{}) error { + s.serviceLock.Lock() + services := s.services[source] + if services == nil { + services = make(map[types.NamespacedName]api.Service) + } + update := change.(ServiceUpdate) + switch update.Op { + case ADD: + glog.V(5).Infof("Adding new service from source %s : %s", source, spew.Sdump(update.Services)) + for _, value := range update.Services { + name := types.NamespacedName{Namespace: value.Namespace, Name: value.Name} + services[name] = value + } + case REMOVE: + glog.V(5).Infof("Removing a service %s", spew.Sdump(update)) + for _, value := range update.Services { + name := types.NamespacedName{Namespace: value.Namespace, Name: value.Name} + delete(services, name) + } + case SET: + glog.V(5).Infof("Setting services %s", spew.Sdump(update)) + // Clear the old map entries by just creating a new map + services = make(map[types.NamespacedName]api.Service) + for _, value := range update.Services { + name := types.NamespacedName{Namespace: value.Namespace, Name: value.Name} + services[name] = value + } + default: + glog.V(4).Infof("Received invalid update type: %s", spew.Sdump(update)) + } + s.services[source] = services + s.serviceLock.Unlock() + if s.updates != nil { + // Since we record the snapshot before sending this signal, it's + // possible that the consumer ends up performing an extra update. + select { + case s.updates <- struct{}{}: + default: + glog.V(4).Infof("Service handler already has a pending interrupt.") + } + } + return nil +} + +func (s *serviceStore) MergedState() interface{} { + s.serviceLock.RLock() + defer s.serviceLock.RUnlock() + services := make([]api.Service, 0) + for _, sourceServices := range s.services { + for _, value := range sourceServices { + services = append(services, value) + } + } + return services +} + +// watchForUpdates invokes bcaster.Notify() with the latest version of an object +// when changes occur. +func watchForUpdates(bcaster *config.Broadcaster, accessor config.Accessor, updates <-chan struct{}) { + for true { + <-updates + bcaster.Notify(accessor.MergedState()) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/config/config_test.go b/vendor/k8s.io/kubernetes/pkg/proxy/config/config_test.go new file mode 100644 index 000000000..7855a93ec --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/config/config_test.go @@ -0,0 +1,343 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 config_test + +import ( + "reflect" + "sort" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + . "k8s.io/kubernetes/pkg/proxy/config" + "k8s.io/kubernetes/pkg/util/wait" +) + +const TomcatPort int = 8080 +const TomcatName = "tomcat" + +var TomcatEndpoints = map[string]string{"c0": "1.1.1.1:18080", "c1": "2.2.2.2:18081"} + +const MysqlPort int = 3306 +const MysqlName = "mysql" + +var MysqlEndpoints = map[string]string{"c0": "1.1.1.1:13306", "c3": "2.2.2.2:13306"} + +type sortedServices []api.Service + +func (s sortedServices) Len() int { + return len(s) +} +func (s sortedServices) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} +func (s sortedServices) Less(i, j int) bool { + return s[i].Name < s[j].Name +} + +type ServiceHandlerMock struct { + updated chan []api.Service + waits int +} + +func NewServiceHandlerMock() *ServiceHandlerMock { + return &ServiceHandlerMock{updated: make(chan []api.Service, 5)} +} + +func (h *ServiceHandlerMock) OnServiceUpdate(services []api.Service) { + sort.Sort(sortedServices(services)) + h.updated <- services +} + +func (h *ServiceHandlerMock) ValidateServices(t *testing.T, expectedServices []api.Service) { + // We might get 1 or more updates for N service updates, because we + // over write older snapshots of services from the producer go-routine + // if the consumer falls behind. + var services []api.Service + for { + select { + case services = <-h.updated: + if reflect.DeepEqual(services, expectedServices) { + return + } + // Unittests will hard timeout in 5m with a stack trace, prevent that + // and surface a clearer reason for failure. + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Timed out. Expected %#v, Got %#v", expectedServices, services) + return + } + } +} + +type sortedEndpoints []api.Endpoints + +func (s sortedEndpoints) Len() int { + return len(s) +} +func (s sortedEndpoints) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} +func (s sortedEndpoints) Less(i, j int) bool { + return s[i].Name < s[j].Name +} + +type EndpointsHandlerMock struct { + updated chan []api.Endpoints + waits int +} + +func NewEndpointsHandlerMock() *EndpointsHandlerMock { + return &EndpointsHandlerMock{updated: make(chan []api.Endpoints, 5)} +} + +func (h *EndpointsHandlerMock) OnEndpointsUpdate(endpoints []api.Endpoints) { + sort.Sort(sortedEndpoints(endpoints)) + h.updated <- endpoints +} + +func (h *EndpointsHandlerMock) ValidateEndpoints(t *testing.T, expectedEndpoints []api.Endpoints) { + // We might get 1 or more updates for N endpoint updates, because we + // over write older snapshots of endpoints from the producer go-routine + // if the consumer falls behind. Unittests will hard timeout in 5m. + var endpoints []api.Endpoints + for { + select { + case endpoints = <-h.updated: + if reflect.DeepEqual(endpoints, expectedEndpoints) { + return + } + // Unittests will hard timeout in 5m with a stack trace, prevent that + // and surface a clearer reason for failure. + case <-time.After(wait.ForeverTestTimeout): + t.Errorf("Timed out. Expected %#v, Got %#v", expectedEndpoints, endpoints) + return + } + } +} + +func CreateServiceUpdate(op Operation, services ...api.Service) ServiceUpdate { + ret := ServiceUpdate{Op: op} + ret.Services = make([]api.Service, len(services)) + for i, value := range services { + ret.Services[i] = value + } + return ret +} + +func CreateEndpointsUpdate(op Operation, endpoints ...api.Endpoints) EndpointsUpdate { + ret := EndpointsUpdate{Op: op} + ret.Endpoints = make([]api.Endpoints, len(endpoints)) + for i, value := range endpoints { + ret.Endpoints[i] = value + } + return ret +} + +func TestNewServiceAddedAndNotified(t *testing.T) { + config := NewServiceConfig() + channel := config.Channel("one") + handler := NewServiceHandlerMock() + config.RegisterHandler(handler) + serviceUpdate := CreateServiceUpdate(ADD, api.Service{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, + Spec: api.ServiceSpec{Ports: []api.ServicePort{{Protocol: "TCP", Port: 10}}}, + }) + channel <- serviceUpdate + handler.ValidateServices(t, serviceUpdate.Services) + +} + +func TestServiceAddedRemovedSetAndNotified(t *testing.T) { + config := NewServiceConfig() + channel := config.Channel("one") + handler := NewServiceHandlerMock() + config.RegisterHandler(handler) + serviceUpdate := CreateServiceUpdate(ADD, api.Service{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, + Spec: api.ServiceSpec{Ports: []api.ServicePort{{Protocol: "TCP", Port: 10}}}, + }) + channel <- serviceUpdate + handler.ValidateServices(t, serviceUpdate.Services) + + serviceUpdate2 := CreateServiceUpdate(ADD, api.Service{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "bar"}, + Spec: api.ServiceSpec{Ports: []api.ServicePort{{Protocol: "TCP", Port: 20}}}, + }) + channel <- serviceUpdate2 + services := []api.Service{serviceUpdate2.Services[0], serviceUpdate.Services[0]} + handler.ValidateServices(t, services) + + serviceUpdate3 := CreateServiceUpdate(REMOVE, api.Service{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, + }) + channel <- serviceUpdate3 + services = []api.Service{serviceUpdate2.Services[0]} + handler.ValidateServices(t, services) + + serviceUpdate4 := CreateServiceUpdate(SET, api.Service{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "foobar"}, + Spec: api.ServiceSpec{Ports: []api.ServicePort{{Protocol: "TCP", Port: 99}}}, + }) + channel <- serviceUpdate4 + services = []api.Service{serviceUpdate4.Services[0]} + handler.ValidateServices(t, services) +} + +func TestNewMultipleSourcesServicesAddedAndNotified(t *testing.T) { + config := NewServiceConfig() + channelOne := config.Channel("one") + channelTwo := config.Channel("two") + if channelOne == channelTwo { + t.Error("Same channel handed back for one and two") + } + handler := NewServiceHandlerMock() + config.RegisterHandler(handler) + serviceUpdate1 := CreateServiceUpdate(ADD, api.Service{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, + Spec: api.ServiceSpec{Ports: []api.ServicePort{{Protocol: "TCP", Port: 10}}}, + }) + serviceUpdate2 := CreateServiceUpdate(ADD, api.Service{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "bar"}, + Spec: api.ServiceSpec{Ports: []api.ServicePort{{Protocol: "TCP", Port: 20}}}, + }) + channelOne <- serviceUpdate1 + channelTwo <- serviceUpdate2 + services := []api.Service{serviceUpdate2.Services[0], serviceUpdate1.Services[0]} + handler.ValidateServices(t, services) +} + +func TestNewMultipleSourcesServicesMultipleHandlersAddedAndNotified(t *testing.T) { + config := NewServiceConfig() + channelOne := config.Channel("one") + channelTwo := config.Channel("two") + handler := NewServiceHandlerMock() + handler2 := NewServiceHandlerMock() + config.RegisterHandler(handler) + config.RegisterHandler(handler2) + serviceUpdate1 := CreateServiceUpdate(ADD, api.Service{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, + Spec: api.ServiceSpec{Ports: []api.ServicePort{{Protocol: "TCP", Port: 10}}}, + }) + serviceUpdate2 := CreateServiceUpdate(ADD, api.Service{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "bar"}, + Spec: api.ServiceSpec{Ports: []api.ServicePort{{Protocol: "TCP", Port: 20}}}, + }) + channelOne <- serviceUpdate1 + channelTwo <- serviceUpdate2 + services := []api.Service{serviceUpdate2.Services[0], serviceUpdate1.Services[0]} + handler.ValidateServices(t, services) + handler2.ValidateServices(t, services) +} + +func TestNewMultipleSourcesEndpointsMultipleHandlersAddedAndNotified(t *testing.T) { + config := NewEndpointsConfig() + channelOne := config.Channel("one") + channelTwo := config.Channel("two") + handler := NewEndpointsHandlerMock() + handler2 := NewEndpointsHandlerMock() + config.RegisterHandler(handler) + config.RegisterHandler(handler2) + endpointsUpdate1 := CreateEndpointsUpdate(ADD, api.Endpoints{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}, + Ports: []api.EndpointPort{{Port: 80}}, + }}, + }) + endpointsUpdate2 := CreateEndpointsUpdate(ADD, api.Endpoints{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "bar"}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "3.3.3.3"}, {IP: "4.4.4.4"}}, + Ports: []api.EndpointPort{{Port: 80}}, + }}, + }) + channelOne <- endpointsUpdate1 + channelTwo <- endpointsUpdate2 + + endpoints := []api.Endpoints{endpointsUpdate2.Endpoints[0], endpointsUpdate1.Endpoints[0]} + handler.ValidateEndpoints(t, endpoints) + handler2.ValidateEndpoints(t, endpoints) +} + +func TestNewMultipleSourcesEndpointsMultipleHandlersAddRemoveSetAndNotified(t *testing.T) { + config := NewEndpointsConfig() + channelOne := config.Channel("one") + channelTwo := config.Channel("two") + handler := NewEndpointsHandlerMock() + handler2 := NewEndpointsHandlerMock() + config.RegisterHandler(handler) + config.RegisterHandler(handler2) + endpointsUpdate1 := CreateEndpointsUpdate(ADD, api.Endpoints{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}, + Ports: []api.EndpointPort{{Port: 80}}, + }}, + }) + endpointsUpdate2 := CreateEndpointsUpdate(ADD, api.Endpoints{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "bar"}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "3.3.3.3"}, {IP: "4.4.4.4"}}, + Ports: []api.EndpointPort{{Port: 80}}, + }}, + }) + channelOne <- endpointsUpdate1 + channelTwo <- endpointsUpdate2 + + endpoints := []api.Endpoints{endpointsUpdate2.Endpoints[0], endpointsUpdate1.Endpoints[0]} + handler.ValidateEndpoints(t, endpoints) + handler2.ValidateEndpoints(t, endpoints) + + // Add one more + endpointsUpdate3 := CreateEndpointsUpdate(ADD, api.Endpoints{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "foobar"}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "5.5.5.5"}, {IP: "6.6.6.6"}}, + Ports: []api.EndpointPort{{Port: 80}}, + }}, + }) + channelTwo <- endpointsUpdate3 + endpoints = []api.Endpoints{endpointsUpdate2.Endpoints[0], endpointsUpdate1.Endpoints[0], endpointsUpdate3.Endpoints[0]} + handler.ValidateEndpoints(t, endpoints) + handler2.ValidateEndpoints(t, endpoints) + + // Update the "foo" service with new endpoints + endpointsUpdate1 = CreateEndpointsUpdate(ADD, api.Endpoints{ + ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "7.7.7.7"}}, + Ports: []api.EndpointPort{{Port: 80}}, + }}, + }) + channelOne <- endpointsUpdate1 + endpoints = []api.Endpoints{endpointsUpdate2.Endpoints[0], endpointsUpdate1.Endpoints[0], endpointsUpdate3.Endpoints[0]} + handler.ValidateEndpoints(t, endpoints) + handler2.ValidateEndpoints(t, endpoints) + + // Remove "bar" service + endpointsUpdate2 = CreateEndpointsUpdate(REMOVE, api.Endpoints{ObjectMeta: api.ObjectMeta{Namespace: "testnamespace", Name: "bar"}}) + channelTwo <- endpointsUpdate2 + + endpoints = []api.Endpoints{endpointsUpdate1.Endpoints[0], endpointsUpdate3.Endpoints[0]} + handler.ValidateEndpoints(t, endpoints) + handler2.ValidateEndpoints(t, endpoints) +} + +// TODO: Add a unittest for interrupts getting processed in a timely manner. +// Currently this module has a circular dependency with config, and so it's +// named config_test, which means even test methods need to be public. This +// is refactoring that we can avoid by resolving the dependency. diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/config/doc.go b/vendor/k8s.io/kubernetes/pkg/proxy/config/doc.go new file mode 100644 index 000000000..035d99c4e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/config/doc.go @@ -0,0 +1,25 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 config provides decoupling between various configuration sources (etcd, files,...) and +// the pieces that actually care about them (loadbalancer, proxy). Config takes 1 or more +// configuration sources and allows for incremental (add/remove) and full replace (set) +// changes from each of the sources, then creates a union of the configuration and provides +// a unified view for both service handlers as well as endpoint handlers. There is no attempt +// to resolve conflicts of any sort. Basic idea is that each configuration source gets a channel +// from the Config service and pushes updates to it via that channel. Config then keeps track of +// incremental & replace changes and distributes them to listeners as appropriate. +package config diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/doc.go b/vendor/k8s.io/kubernetes/pkg/proxy/doc.go new file mode 100644 index 000000000..05b801a2b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 proxy implements the layer-3 network proxy. +package proxy diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/iptables/proxier.go b/vendor/k8s.io/kubernetes/pkg/proxy/iptables/proxier.go new file mode 100644 index 000000000..2e5db0461 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/iptables/proxier.go @@ -0,0 +1,1116 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 iptables + +// +// NOTE: this needs to be tested in e2e since it uses iptables for everything. +// + +import ( + "bytes" + "crypto/sha256" + "encoding/base32" + "fmt" + "net" + "os" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/coreos/go-semver/semver" + "github.com/davecgh/go-spew/spew" + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/proxy" + "k8s.io/kubernetes/pkg/types" + utilexec "k8s.io/kubernetes/pkg/util/exec" + utiliptables "k8s.io/kubernetes/pkg/util/iptables" + "k8s.io/kubernetes/pkg/util/slice" + utilsysctl "k8s.io/kubernetes/pkg/util/sysctl" +) + +// iptablesMinVersion is the minimum version of iptables for which we will use the Proxier +// from this package instead of the userspace Proxier. While most of the +// features we need were available earlier, the '-C' flag was added more +// recently. We use that indirectly in Ensure* functions, and if we don't +// have it, we have to be extra careful about the exact args we feed in being +// the same as the args we read back (iptables itself normalizes some args). +// This is the "new" Proxier, so we require "new" versions of tools. +const iptablesMinVersion = utiliptables.MinCheckVersion + +// the services chain +const kubeServicesChain utiliptables.Chain = "KUBE-SERVICES" + +// the nodeports chain +const kubeNodePortsChain utiliptables.Chain = "KUBE-NODEPORTS" + +// the kubernetes postrouting chain +const kubePostroutingChain utiliptables.Chain = "KUBE-POSTROUTING" + +// the mark-for-masquerade chain +const kubeMarkMasqChain utiliptables.Chain = "KUBE-MARK-MASQ" + +// the mark we apply to traffic needing SNAT +// TODO(thockin): Remove this for v1.3 or v1.4. +const oldIptablesMasqueradeMark = "0x4d415351" + +// IptablesVersioner can query the current iptables version. +type IptablesVersioner interface { + // returns "X.Y.Z" + GetVersion() (string, error) +} + +// KernelCompatTester tests whether the required kernel capabilities are +// present to run the iptables proxier. +type KernelCompatTester interface { + IsCompatible() error +} + +// CanUseIptablesProxier returns true if we should use the iptables Proxier +// instead of the "classic" userspace Proxier. This is determined by checking +// the iptables version and for the existence of kernel features. It may return +// an error if it fails to get the iptables version without error, in which +// case it will also return false. +func CanUseIptablesProxier(iptver IptablesVersioner, kcompat KernelCompatTester) (bool, error) { + minVersion, err := semver.NewVersion(iptablesMinVersion) + if err != nil { + return false, err + } + // returns "X.Y.Z" + versionString, err := iptver.GetVersion() + if err != nil { + return false, err + } + version, err := semver.NewVersion(versionString) + if err != nil { + return false, err + } + if version.LessThan(*minVersion) { + return false, nil + } + + // Check that the kernel supports what we need. + if err := kcompat.IsCompatible(); err != nil { + return false, err + } + return true, nil +} + +type LinuxKernelCompatTester struct{} + +func (lkct LinuxKernelCompatTester) IsCompatible() error { + // Check for the required sysctls. We don't care about the value, just + // that it exists. If this Proxier is chosen, we'll initialize it as we + // need. + _, err := utilsysctl.GetSysctl(sysctlRouteLocalnet) + return err +} + +const sysctlRouteLocalnet = "net/ipv4/conf/all/route_localnet" +const sysctlBridgeCallIptables = "net/bridge/bridge-nf-call-iptables" + +// internal struct for string service information +type serviceInfo struct { + clusterIP net.IP + port int + protocol api.Protocol + nodePort int + loadBalancerStatus api.LoadBalancerStatus + sessionAffinityType api.ServiceAffinity + stickyMaxAgeSeconds int + externalIPs []string +} + +// returns a new serviceInfo struct +func newServiceInfo(service proxy.ServicePortName) *serviceInfo { + return &serviceInfo{ + sessionAffinityType: api.ServiceAffinityNone, // default + stickyMaxAgeSeconds: 180, // TODO: paramaterize this in the API. + } +} + +// Proxier is an iptables based proxy for connections between a localhost:lport +// and services that provide the actual backends. +type Proxier struct { + mu sync.Mutex // protects the following fields + serviceMap map[proxy.ServicePortName]*serviceInfo + endpointsMap map[proxy.ServicePortName][]string + portsMap map[localPort]closeable + haveReceivedServiceUpdate bool // true once we've seen an OnServiceUpdate event + haveReceivedEndpointsUpdate bool // true once we've seen an OnEndpointsUpdate event + + // These are effectively const and do not need the mutex to be held. + syncPeriod time.Duration + iptables utiliptables.Interface + masqueradeAll bool + masqueradeMark string +} + +type localPort struct { + desc string + ip string + port int + protocol string +} + +func (lp *localPort) String() string { + return fmt.Sprintf("%q (%s:%d/%s)", lp.desc, lp.ip, lp.port, lp.protocol) +} + +type closeable interface { + Close() error +} + +// Proxier implements ProxyProvider +var _ proxy.ProxyProvider = &Proxier{} + +// NewProxier returns a new Proxier given an iptables Interface instance. +// Because of the iptables logic, it is assumed that there is only a single Proxier active on a machine. +// An error will be returned if iptables fails to update or acquire the initial lock. +// Once a proxier is created, it will keep iptables up to date in the background and +// will not terminate if a particular iptables call fails. +func NewProxier(ipt utiliptables.Interface, exec utilexec.Interface, syncPeriod time.Duration, masqueradeAll bool, masqueradeBit int) (*Proxier, error) { + // Set the route_localnet sysctl we need for + if err := utilsysctl.SetSysctl(sysctlRouteLocalnet, 1); err != nil { + return nil, fmt.Errorf("can't set sysctl %s: %v", sysctlRouteLocalnet, err) + } + + // Proxy needs br_netfilter and bridge-nf-call-iptables=1 when containers + // are connected to a Linux bridge (but not SDN bridges). Until most + // plugins handle this, log when config is missing + warnBrNetfilter := false + if _, err := os.Stat("/sys/module/br_netfilter"); os.IsNotExist(err) { + warnBrNetfilter = true + } + if val, err := utilsysctl.GetSysctl(sysctlBridgeCallIptables); err == nil && val != 1 { + warnBrNetfilter = true + } + if warnBrNetfilter { + glog.Infof("missing br-netfilter module or unset br-nf-call-iptables; proxy may not work as intended") + } + + // Generate the masquerade mark to use for SNAT rules. + if masqueradeBit < 0 || masqueradeBit > 31 { + return nil, fmt.Errorf("invalid iptables-masquerade-bit %v not in [0, 31]", masqueradeBit) + } + masqueradeValue := 1 << uint(masqueradeBit) + masqueradeMark := fmt.Sprintf("%#08x/%#08x", masqueradeValue, masqueradeValue) + + return &Proxier{ + serviceMap: make(map[proxy.ServicePortName]*serviceInfo), + endpointsMap: make(map[proxy.ServicePortName][]string), + portsMap: make(map[localPort]closeable), + syncPeriod: syncPeriod, + iptables: ipt, + masqueradeAll: masqueradeAll, + masqueradeMark: masqueradeMark, + }, nil +} + +// CleanupLeftovers removes all iptables rules and chains created by the Proxier +// It returns true if an error was encountered. Errors are logged. +func CleanupLeftovers(ipt utiliptables.Interface) (encounteredError bool) { + // Unlink the services chain. + args := []string{ + "-m", "comment", "--comment", "kubernetes service portals", + "-j", string(kubeServicesChain), + } + tableChainsWithJumpServices := []struct { + table utiliptables.Table + chain utiliptables.Chain + }{ + {utiliptables.TableFilter, utiliptables.ChainOutput}, + {utiliptables.TableNAT, utiliptables.ChainOutput}, + {utiliptables.TableNAT, utiliptables.ChainPrerouting}, + } + for _, tc := range tableChainsWithJumpServices { + if err := ipt.DeleteRule(tc.table, tc.chain, args...); err != nil { + if !utiliptables.IsNotFoundError(err) { + glog.Errorf("Error removing pure-iptables proxy rule: %v", err) + encounteredError = true + } + } + } + + // Unlink the postrouting chain. + args = []string{ + "-m", "comment", "--comment", "kubernetes postrouting rules", + "-j", string(kubePostroutingChain), + } + if err := ipt.DeleteRule(utiliptables.TableNAT, utiliptables.ChainPostrouting, args...); err != nil { + if !utiliptables.IsNotFoundError(err) { + glog.Errorf("Error removing pure-iptables proxy rule: %v", err) + encounteredError = true + } + } + + // Flush and remove all of our chains. + if iptablesSaveRaw, err := ipt.Save(utiliptables.TableNAT); err != nil { + glog.Errorf("Failed to execute iptables-save for %s: %v", utiliptables.TableNAT, err) + encounteredError = true + } else { + existingNATChains := getChainLines(utiliptables.TableNAT, iptablesSaveRaw) + natChains := bytes.NewBuffer(nil) + natRules := bytes.NewBuffer(nil) + writeLine(natChains, "*nat") + // Start with chains we know we need to remove. + for _, chain := range []utiliptables.Chain{kubeServicesChain, kubeNodePortsChain, kubePostroutingChain, kubeMarkMasqChain} { + if _, found := existingNATChains[chain]; found { + chainString := string(chain) + writeLine(natChains, existingNATChains[chain]) // flush + writeLine(natRules, "-X", chainString) // delete + } + } + // Hunt for service and endpoint chains. + for chain := range existingNATChains { + chainString := string(chain) + if strings.HasPrefix(chainString, "KUBE-SVC-") || strings.HasPrefix(chainString, "KUBE-SEP-") { + writeLine(natChains, existingNATChains[chain]) // flush + writeLine(natRules, "-X", chainString) // delete + } + } + writeLine(natRules, "COMMIT") + natLines := append(natChains.Bytes(), natRules.Bytes()...) + // Write it. + err = ipt.Restore(utiliptables.TableNAT, natLines, utiliptables.NoFlushTables, utiliptables.RestoreCounters) + if err != nil { + glog.Errorf("Failed to execute iptables-restore for %s: %v", utiliptables.TableNAT, err) + encounteredError = true + } + } + { + filterBuf := bytes.NewBuffer(nil) + writeLine(filterBuf, "*filter") + writeLine(filterBuf, fmt.Sprintf(":%s - [0:0]", kubeServicesChain)) + writeLine(filterBuf, fmt.Sprintf("-X %s", kubeServicesChain)) + writeLine(filterBuf, "COMMIT") + // Write it. + if err := ipt.Restore(utiliptables.TableFilter, filterBuf.Bytes(), utiliptables.NoFlushTables, utiliptables.RestoreCounters); err != nil { + glog.Errorf("Failed to execute iptables-restore for %s: %v", utiliptables.TableFilter, err) + encounteredError = true + } + } + + // Clean up the older SNAT rule which was directly in POSTROUTING. + // TODO(thockin): Remove this for v1.3 or v1.4. + args = []string{ + "-m", "comment", "--comment", "kubernetes service traffic requiring SNAT", + "-m", "mark", "--mark", oldIptablesMasqueradeMark, + "-j", "MASQUERADE", + } + if err := ipt.DeleteRule(utiliptables.TableNAT, utiliptables.ChainPostrouting, args...); err != nil { + if !utiliptables.IsNotFoundError(err) { + glog.Errorf("Error removing old-style SNAT rule: %v", err) + encounteredError = true + } + } + + return encounteredError +} + +func (proxier *Proxier) sameConfig(info *serviceInfo, service *api.Service, port *api.ServicePort) bool { + if info.protocol != port.Protocol || info.port != port.Port || info.nodePort != port.NodePort { + return false + } + if !info.clusterIP.Equal(net.ParseIP(service.Spec.ClusterIP)) { + return false + } + if !ipsEqual(info.externalIPs, service.Spec.ExternalIPs) { + return false + } + if !api.LoadBalancerStatusEqual(&info.loadBalancerStatus, &service.Status.LoadBalancer) { + return false + } + if info.sessionAffinityType != service.Spec.SessionAffinity { + return false + } + return true +} + +func ipsEqual(lhs, rhs []string) bool { + if len(lhs) != len(rhs) { + return false + } + for i := range lhs { + if lhs[i] != rhs[i] { + return false + } + } + return true +} + +// Sync is called to immediately synchronize the proxier state to iptables +func (proxier *Proxier) Sync() { + proxier.mu.Lock() + defer proxier.mu.Unlock() + proxier.syncProxyRules() +} + +// SyncLoop runs periodic work. This is expected to run as a goroutine or as the main loop of the app. It does not return. +func (proxier *Proxier) SyncLoop() { + t := time.NewTicker(proxier.syncPeriod) + defer t.Stop() + for { + <-t.C + glog.V(6).Infof("Periodic sync") + proxier.Sync() + } +} + +// OnServiceUpdate tracks the active set of service proxies. +// They will be synchronized using syncProxyRules() +func (proxier *Proxier) OnServiceUpdate(allServices []api.Service) { + start := time.Now() + defer func() { + glog.V(4).Infof("OnServiceUpdate took %v for %d services", time.Since(start), len(allServices)) + }() + proxier.mu.Lock() + defer proxier.mu.Unlock() + proxier.haveReceivedServiceUpdate = true + + activeServices := make(map[proxy.ServicePortName]bool) // use a map as a set + + for i := range allServices { + service := &allServices[i] + svcName := types.NamespacedName{ + Namespace: service.Namespace, + Name: service.Name, + } + + // if ClusterIP is "None" or empty, skip proxying + if !api.IsServiceIPSet(service) { + glog.V(3).Infof("Skipping service %s due to clusterIP = %q", svcName, service.Spec.ClusterIP) + continue + } + + for i := range service.Spec.Ports { + servicePort := &service.Spec.Ports[i] + + serviceName := proxy.ServicePortName{ + NamespacedName: svcName, + Port: servicePort.Name, + } + activeServices[serviceName] = true + info, exists := proxier.serviceMap[serviceName] + if exists && proxier.sameConfig(info, service, servicePort) { + // Nothing changed. + continue + } + if exists { + // Something changed. + glog.V(3).Infof("Something changed for service %q: removing it", serviceName) + delete(proxier.serviceMap, serviceName) + } + serviceIP := net.ParseIP(service.Spec.ClusterIP) + glog.V(1).Infof("Adding new service %q at %s:%d/%s", serviceName, serviceIP, servicePort.Port, servicePort.Protocol) + info = newServiceInfo(serviceName) + info.clusterIP = serviceIP + info.port = servicePort.Port + info.protocol = servicePort.Protocol + info.nodePort = servicePort.NodePort + info.externalIPs = service.Spec.ExternalIPs + // Deep-copy in case the service instance changes + info.loadBalancerStatus = *api.LoadBalancerStatusDeepCopy(&service.Status.LoadBalancer) + info.sessionAffinityType = service.Spec.SessionAffinity + proxier.serviceMap[serviceName] = info + + glog.V(4).Infof("added serviceInfo(%s): %s", serviceName, spew.Sdump(info)) + } + } + + // Remove services missing from the update. + for name := range proxier.serviceMap { + if !activeServices[name] { + glog.V(1).Infof("Removing service %q", name) + delete(proxier.serviceMap, name) + } + } + + proxier.syncProxyRules() +} + +// OnEndpointsUpdate takes in a slice of updated endpoints. +func (proxier *Proxier) OnEndpointsUpdate(allEndpoints []api.Endpoints) { + start := time.Now() + defer func() { + glog.V(4).Infof("OnEndpointsUpdate took %v for %d endpoints", time.Since(start), len(allEndpoints)) + }() + + proxier.mu.Lock() + defer proxier.mu.Unlock() + proxier.haveReceivedEndpointsUpdate = true + + activeEndpoints := make(map[proxy.ServicePortName]bool) // use a map as a set + + // Update endpoints for services. + for i := range allEndpoints { + svcEndpoints := &allEndpoints[i] + + // We need to build a map of portname -> all ip:ports for that + // portname. Explode Endpoints.Subsets[*] into this structure. + portsToEndpoints := map[string][]hostPortPair{} + for i := range svcEndpoints.Subsets { + ss := &svcEndpoints.Subsets[i] + for i := range ss.Ports { + port := &ss.Ports[i] + for i := range ss.Addresses { + addr := &ss.Addresses[i] + portsToEndpoints[port.Name] = append(portsToEndpoints[port.Name], hostPortPair{addr.IP, port.Port}) + } + } + } + + for portname := range portsToEndpoints { + svcPort := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: svcEndpoints.Namespace, Name: svcEndpoints.Name}, Port: portname} + curEndpoints := proxier.endpointsMap[svcPort] + newEndpoints := flattenValidEndpoints(portsToEndpoints[portname]) + if len(curEndpoints) != len(newEndpoints) || !slicesEquiv(slice.CopyStrings(curEndpoints), newEndpoints) { + glog.V(1).Infof("Setting endpoints for %q to %+v", svcPort, newEndpoints) + proxier.endpointsMap[svcPort] = newEndpoints + } + activeEndpoints[svcPort] = true + } + } + + // Remove endpoints missing from the update. + for name := range proxier.endpointsMap { + if !activeEndpoints[name] { + glog.V(2).Infof("Removing endpoints for %q", name) + delete(proxier.endpointsMap, name) + } + } + + proxier.syncProxyRules() +} + +// used in OnEndpointsUpdate +type hostPortPair struct { + host string + port int +} + +func isValidEndpoint(hpp *hostPortPair) bool { + return hpp.host != "" && hpp.port > 0 +} + +// Tests whether two slices are equivalent. This sorts both slices in-place. +func slicesEquiv(lhs, rhs []string) bool { + if len(lhs) != len(rhs) { + return false + } + if reflect.DeepEqual(slice.SortStrings(lhs), slice.SortStrings(rhs)) { + return true + } + return false +} + +func flattenValidEndpoints(endpoints []hostPortPair) []string { + // Convert Endpoint objects into strings for easier use later. + var result []string + for i := range endpoints { + hpp := &endpoints[i] + if isValidEndpoint(hpp) { + result = append(result, net.JoinHostPort(hpp.host, strconv.Itoa(hpp.port))) + } else { + glog.Warningf("got invalid endpoint: %+v", *hpp) + } + } + return result +} + +// servicePortChainName takes the ServicePortName for a service and +// returns the associated iptables chain. This is computed by hashing (sha256) +// then encoding to base32 and truncating with the prefix "KUBE-SVC-". We do +// this because Iptables Chain Names must be <= 28 chars long, and the longer +// they are the harder they are to read. +func servicePortChainName(s proxy.ServicePortName, protocol string) utiliptables.Chain { + hash := sha256.Sum256([]byte(s.String() + protocol)) + encoded := base32.StdEncoding.EncodeToString(hash[:]) + return utiliptables.Chain("KUBE-SVC-" + encoded[:16]) +} + +// This is the same as servicePortChainName but with the endpoint included. +func servicePortEndpointChainName(s proxy.ServicePortName, protocol string, endpoint string) utiliptables.Chain { + hash := sha256.Sum256([]byte(s.String() + protocol + endpoint)) + encoded := base32.StdEncoding.EncodeToString(hash[:]) + return utiliptables.Chain("KUBE-SEP-" + encoded[:16]) +} + +// This is where all of the iptables-save/restore calls happen. +// The only other iptables rules are those that are setup in iptablesInit() +// assumes proxier.mu is held +func (proxier *Proxier) syncProxyRules() { + start := time.Now() + defer func() { + glog.V(4).Infof("syncProxyRules took %v", time.Since(start)) + }() + // don't sync rules till we've received services and endpoints + if !proxier.haveReceivedEndpointsUpdate || !proxier.haveReceivedServiceUpdate { + glog.V(2).Info("Not syncing iptables until Services and Endpoints have been received from master") + return + } + glog.V(3).Infof("Syncing iptables rules") + + // Create and link the kube services chain. + { + tablesNeedServicesChain := []utiliptables.Table{utiliptables.TableFilter, utiliptables.TableNAT} + for _, table := range tablesNeedServicesChain { + if _, err := proxier.iptables.EnsureChain(table, kubeServicesChain); err != nil { + glog.Errorf("Failed to ensure that %s chain %s exists: %v", table, kubeServicesChain, err) + return + } + } + + tableChainsNeedJumpServices := []struct { + table utiliptables.Table + chain utiliptables.Chain + }{ + {utiliptables.TableFilter, utiliptables.ChainOutput}, + {utiliptables.TableNAT, utiliptables.ChainOutput}, + {utiliptables.TableNAT, utiliptables.ChainPrerouting}, + } + comment := "kubernetes service portals" + args := []string{"-m", "comment", "--comment", comment, "-j", string(kubeServicesChain)} + for _, tc := range tableChainsNeedJumpServices { + if _, err := proxier.iptables.EnsureRule(utiliptables.Prepend, tc.table, tc.chain, args...); err != nil { + glog.Errorf("Failed to ensure that %s chain %s jumps to %s: %v", tc.table, tc.chain, kubeServicesChain, err) + return + } + } + } + + // Create and link the kube postrouting chain. + { + if _, err := proxier.iptables.EnsureChain(utiliptables.TableNAT, kubePostroutingChain); err != nil { + glog.Errorf("Failed to ensure that %s chain %s exists: %v", utiliptables.TableNAT, kubePostroutingChain, err) + return + } + + comment := "kubernetes postrouting rules" + args := []string{"-m", "comment", "--comment", comment, "-j", string(kubePostroutingChain)} + if _, err := proxier.iptables.EnsureRule(utiliptables.Prepend, utiliptables.TableNAT, utiliptables.ChainPostrouting, args...); err != nil { + glog.Errorf("Failed to ensure that %s chain %s jumps to %s: %v", utiliptables.TableNAT, utiliptables.ChainPostrouting, kubePostroutingChain, err) + return + } + } + + // Get iptables-save output so we can check for existing chains and rules. + // This will be a map of chain name to chain with rules as stored in iptables-save/iptables-restore + existingFilterChains := make(map[utiliptables.Chain]string) + iptablesSaveRaw, err := proxier.iptables.Save(utiliptables.TableFilter) + if err != nil { // if we failed to get any rules + glog.Errorf("Failed to execute iptables-save, syncing all rules: %v", err) + } else { // otherwise parse the output + existingFilterChains = getChainLines(utiliptables.TableFilter, iptablesSaveRaw) + } + + existingNATChains := make(map[utiliptables.Chain]string) + iptablesSaveRaw, err = proxier.iptables.Save(utiliptables.TableNAT) + if err != nil { // if we failed to get any rules + glog.Errorf("Failed to execute iptables-save, syncing all rules: %v", err) + } else { // otherwise parse the output + existingNATChains = getChainLines(utiliptables.TableNAT, iptablesSaveRaw) + } + + filterChains := bytes.NewBuffer(nil) + filterRules := bytes.NewBuffer(nil) + natChains := bytes.NewBuffer(nil) + natRules := bytes.NewBuffer(nil) + + // Write table headers. + writeLine(filterChains, "*filter") + writeLine(natChains, "*nat") + + // Make sure we keep stats for the top-level chains, if they existed + // (which most should have because we created them above). + if chain, ok := existingFilterChains[kubeServicesChain]; ok { + writeLine(filterChains, chain) + } else { + writeLine(filterChains, makeChainLine(kubeServicesChain)) + } + if chain, ok := existingNATChains[kubeServicesChain]; ok { + writeLine(natChains, chain) + } else { + writeLine(natChains, makeChainLine(kubeServicesChain)) + } + if chain, ok := existingNATChains[kubeNodePortsChain]; ok { + writeLine(natChains, chain) + } else { + writeLine(natChains, makeChainLine(kubeNodePortsChain)) + } + if chain, ok := existingNATChains[kubePostroutingChain]; ok { + writeLine(natChains, chain) + } else { + writeLine(natChains, makeChainLine(kubePostroutingChain)) + } + if chain, ok := existingNATChains[kubeMarkMasqChain]; ok { + writeLine(natChains, chain) + } else { + writeLine(natChains, makeChainLine(kubeMarkMasqChain)) + } + + // Install the kubernetes-specific postrouting rules. We use a whole chain for + // this so that it is easier to flush and change, for example if the mark + // value should ever change. + writeLine(natRules, []string{ + "-A", string(kubePostroutingChain), + "-m", "comment", "--comment", `"kubernetes service traffic requiring SNAT"`, + "-m", "mark", "--mark", proxier.masqueradeMark, + "-j", "MASQUERADE", + }...) + + // Install the kubernetes-specific masquerade mark rule. We use a whole chain for + // this so that it is easier to flush and change, for example if the mark + // value should ever change. + writeLine(natRules, []string{ + "-A", string(kubeMarkMasqChain), + "-j", "MARK", "--set-xmark", proxier.masqueradeMark, + }...) + + // Accumulate NAT chains to keep. + activeNATChains := map[utiliptables.Chain]bool{} // use a map as a set + + // Accumulate new local ports that we have opened. + newLocalPorts := map[localPort]closeable{} + + // Build rules for each service. + for svcName, svcInfo := range proxier.serviceMap { + protocol := strings.ToLower(string(svcInfo.protocol)) + + // Create the per-service chain, retaining counters if possible. + svcChain := servicePortChainName(svcName, protocol) + if chain, ok := existingNATChains[svcChain]; ok { + writeLine(natChains, chain) + } else { + writeLine(natChains, makeChainLine(svcChain)) + } + activeNATChains[svcChain] = true + + // Capture the clusterIP. + args := []string{ + "-A", string(kubeServicesChain), + "-m", "comment", "--comment", fmt.Sprintf(`"%s cluster IP"`, svcName.String()), + "-m", protocol, "-p", protocol, + "-d", fmt.Sprintf("%s/32", svcInfo.clusterIP.String()), + "--dport", fmt.Sprintf("%d", svcInfo.port), + } + if proxier.masqueradeAll { + writeLine(natRules, append(args, "-j", string(kubeMarkMasqChain))...) + } + writeLine(natRules, append(args, "-j", string(svcChain))...) + + // Capture externalIPs. + for _, externalIP := range svcInfo.externalIPs { + // If the "external" IP happens to be an IP that is local to this + // machine, hold the local port open so no other process can open it + // (because the socket might open but it would never work). + if local, err := isLocalIP(externalIP); err != nil { + glog.Errorf("can't determine if IP is local, assuming not: %v", err) + } else if local { + lp := localPort{ + desc: "externalIP for " + svcName.String(), + ip: externalIP, + port: svcInfo.port, + protocol: protocol, + } + if proxier.portsMap[lp] != nil { + newLocalPorts[lp] = proxier.portsMap[lp] + } else { + socket, err := openLocalPort(&lp) + if err != nil { + glog.Errorf("can't open %s, skipping this externalIP: %v", lp.String(), err) + continue + } + newLocalPorts[lp] = socket + } + } // We're holding the port, so it's OK to install iptables rules. + args := []string{ + "-A", string(kubeServicesChain), + "-m", "comment", "--comment", fmt.Sprintf(`"%s external IP"`, svcName.String()), + "-m", protocol, "-p", protocol, + "-d", fmt.Sprintf("%s/32", externalIP), + "--dport", fmt.Sprintf("%d", svcInfo.port), + } + // We have to SNAT packets to external IPs. + writeLine(natRules, append(args, "-j", string(kubeMarkMasqChain))...) + + // Allow traffic for external IPs that does not come from a bridge (i.e. not from a container) + // nor from a local process to be forwarded to the service. + // This rule roughly translates to "all traffic from off-machine". + // This is imperfect in the face of network plugins that might not use a bridge, but we can revisit that later. + externalTrafficOnlyArgs := append(args, + "-m", "physdev", "!", "--physdev-is-in", + "-m", "addrtype", "!", "--src-type", "LOCAL") + writeLine(natRules, append(externalTrafficOnlyArgs, "-j", string(svcChain))...) + dstLocalOnlyArgs := append(args, "-m", "addrtype", "--dst-type", "LOCAL") + // Allow traffic bound for external IPs that happen to be recognized as local IPs to stay local. + // This covers cases like GCE load-balancers which get added to the local routing table. + writeLine(natRules, append(dstLocalOnlyArgs, "-j", string(svcChain))...) + } + + // Capture load-balancer ingress. + for _, ingress := range svcInfo.loadBalancerStatus.Ingress { + if ingress.IP != "" { + args := []string{ + "-A", string(kubeServicesChain), + "-m", "comment", "--comment", fmt.Sprintf(`"%s loadbalancer IP"`, svcName.String()), + "-m", protocol, "-p", protocol, + "-d", fmt.Sprintf("%s/32", ingress.IP), + "--dport", fmt.Sprintf("%d", svcInfo.port), + } + // We have to SNAT packets from external IPs. + writeLine(natRules, append(args, "-j", string(kubeMarkMasqChain))...) + writeLine(natRules, append(args, "-j", string(svcChain))...) + } + } + + // Capture nodeports. If we had more than 2 rules it might be + // worthwhile to make a new per-service chain for nodeport rules, but + // with just 2 rules it ends up being a waste and a cognitive burden. + if svcInfo.nodePort != 0 { + // Hold the local port open so no other process can open it + // (because the socket might open but it would never work). + lp := localPort{ + desc: "nodePort for " + svcName.String(), + ip: "", + port: svcInfo.nodePort, + protocol: protocol, + } + if proxier.portsMap[lp] != nil { + newLocalPorts[lp] = proxier.portsMap[lp] + } else { + socket, err := openLocalPort(&lp) + if err != nil { + glog.Errorf("can't open %s, skipping this nodePort: %v", lp.String(), err) + continue + } + newLocalPorts[lp] = socket + } // We're holding the port, so it's OK to install iptables rules. + + args := []string{ + "-A", string(kubeNodePortsChain), + "-m", "comment", "--comment", svcName.String(), + "-m", protocol, "-p", protocol, + "--dport", fmt.Sprintf("%d", svcInfo.nodePort), + } + // Nodeports need SNAT. + writeLine(natRules, append(args, "-j", string(kubeMarkMasqChain))...) + // Jump to the service chain. + writeLine(natRules, append(args, "-j", string(svcChain))...) + } + + // If the service has no endpoints then reject packets. + if len(proxier.endpointsMap[svcName]) == 0 { + writeLine(filterRules, + "-A", string(kubeServicesChain), + "-m", "comment", "--comment", fmt.Sprintf(`"%s has no endpoints"`, svcName.String()), + "-m", protocol, "-p", protocol, + "-d", fmt.Sprintf("%s/32", svcInfo.clusterIP.String()), + "--dport", fmt.Sprintf("%d", svcInfo.port), + "-j", "REJECT", + ) + continue + } + + // Generate the per-endpoint chains. We do this in multiple passes so we + // can group rules together. + endpoints := make([]string, 0) + endpointChains := make([]utiliptables.Chain, 0) + for _, ep := range proxier.endpointsMap[svcName] { + endpoints = append(endpoints, ep) + endpointChain := servicePortEndpointChainName(svcName, protocol, ep) + endpointChains = append(endpointChains, endpointChain) + + // Create the endpoint chain, retaining counters if possible. + if chain, ok := existingNATChains[utiliptables.Chain(endpointChain)]; ok { + writeLine(natChains, chain) + } else { + writeLine(natChains, makeChainLine(endpointChain)) + } + activeNATChains[endpointChain] = true + } + + // First write session affinity rules, if applicable. + if svcInfo.sessionAffinityType == api.ServiceAffinityClientIP { + for _, endpointChain := range endpointChains { + writeLine(natRules, + "-A", string(svcChain), + "-m", "comment", "--comment", svcName.String(), + "-m", "recent", "--name", string(endpointChain), + "--rcheck", "--seconds", fmt.Sprintf("%d", svcInfo.stickyMaxAgeSeconds), "--reap", + "-j", string(endpointChain)) + } + } + + // Now write loadbalancing & DNAT rules. + n := len(endpointChains) + for i, endpointChain := range endpointChains { + // Balancing rules in the per-service chain. + args := []string{ + "-A", string(svcChain), + "-m", "comment", "--comment", svcName.String(), + } + if i < (n - 1) { + // Each rule is a probabilistic match. + args = append(args, + "-m", "statistic", + "--mode", "random", + "--probability", fmt.Sprintf("%0.5f", 1.0/float64(n-i))) + } + // The final (or only if n == 1) rule is a guaranteed match. + args = append(args, "-j", string(endpointChain)) + writeLine(natRules, args...) + + // Rules in the per-endpoint chain. + args = []string{ + "-A", string(endpointChain), + "-m", "comment", "--comment", svcName.String(), + } + // Handle traffic that loops back to the originator with SNAT. + // Technically we only need to do this if the endpoint is on this + // host, but we don't have that information, so we just do this for + // all endpoints. + // TODO: if we grow logic to get this node's pod CIDR, we can use it. + writeLine(natRules, append(args, + "-s", fmt.Sprintf("%s/32", strings.Split(endpoints[i], ":")[0]), + "-j", string(kubeMarkMasqChain))...) + + // Update client-affinity lists. + if svcInfo.sessionAffinityType == api.ServiceAffinityClientIP { + args = append(args, "-m", "recent", "--name", string(endpointChain), "--set") + } + // DNAT to final destination. + args = append(args, "-m", protocol, "-p", protocol, "-j", "DNAT", "--to-destination", endpoints[i]) + writeLine(natRules, args...) + } + } + + // Delete chains no longer in use. + for chain := range existingNATChains { + if !activeNATChains[chain] { + chainString := string(chain) + if !strings.HasPrefix(chainString, "KUBE-SVC-") && !strings.HasPrefix(chainString, "KUBE-SEP-") { + // Ignore chains that aren't ours. + continue + } + // We must (as per iptables) write a chain-line for it, which has + // the nice effect of flushing the chain. Then we can remove the + // chain. + writeLine(natChains, existingNATChains[chain]) + writeLine(natRules, "-X", chainString) + } + } + + // Finally, tail-call to the nodeports chain. This needs to be after all + // other service portal rules. + writeLine(natRules, + "-A", string(kubeServicesChain), + "-m", "comment", "--comment", `"kubernetes service nodeports; NOTE: this must be the last rule in this chain"`, + "-m", "addrtype", "--dst-type", "LOCAL", + "-j", string(kubeNodePortsChain)) + + // Write the end-of-table markers. + writeLine(filterRules, "COMMIT") + writeLine(natRules, "COMMIT") + + // Sync rules. + // NOTE: NoFlushTables is used so we don't flush non-kubernetes chains in the table. + filterLines := append(filterChains.Bytes(), filterRules.Bytes()...) + natLines := append(natChains.Bytes(), natRules.Bytes()...) + lines := append(filterLines, natLines...) + + glog.V(3).Infof("Restoring iptables rules: %s", lines) + err = proxier.iptables.RestoreAll(lines, utiliptables.NoFlushTables, utiliptables.RestoreCounters) + if err != nil { + glog.Errorf("Failed to execute iptables-restore: %v", err) + // Revert new local ports. + for k, v := range newLocalPorts { + glog.Errorf("Closing local port %s", k.String()) + v.Close() + } + return + } + + // Close old local ports and save new ones. + for k, v := range proxier.portsMap { + if newLocalPorts[k] == nil { + v.Close() + } + } + proxier.portsMap = newLocalPorts + + // Clean up the older SNAT rule which was directly in POSTROUTING. + // TODO(thockin): Remove this for v1.3 or v1.4. + args := []string{ + "-m", "comment", "--comment", "kubernetes service traffic requiring SNAT", + "-m", "mark", "--mark", oldIptablesMasqueradeMark, + "-j", "MASQUERADE", + } + if err := proxier.iptables.DeleteRule(utiliptables.TableNAT, utiliptables.ChainPostrouting, args...); err != nil { + if !utiliptables.IsNotFoundError(err) { + glog.Errorf("Error removing old-style SNAT rule: %v", err) + } + } +} + +// Join all words with spaces, terminate with newline and write to buf. +func writeLine(buf *bytes.Buffer, words ...string) { + buf.WriteString(strings.Join(words, " ") + "\n") +} + +// return an iptables-save/restore formatted chain line given a Chain +func makeChainLine(chain utiliptables.Chain) string { + return fmt.Sprintf(":%s - [0:0]", chain) +} + +// getChainLines parses a table's iptables-save data to find chains in the table. +// It returns a map of iptables.Chain to string where the string is the chain line from the save (with counters etc). +func getChainLines(table utiliptables.Table, save []byte) map[utiliptables.Chain]string { + chainsMap := make(map[utiliptables.Chain]string) + tablePrefix := "*" + string(table) + readIndex := 0 + // find beginning of table + for readIndex < len(save) { + line, n := readLine(readIndex, save) + readIndex = n + if strings.HasPrefix(line, tablePrefix) { + break + } + } + // parse table lines + for readIndex < len(save) { + line, n := readLine(readIndex, save) + readIndex = n + if len(line) == 0 { + continue + } + if strings.HasPrefix(line, "COMMIT") || strings.HasPrefix(line, "*") { + break + } else if strings.HasPrefix(line, "#") { + continue + } else if strings.HasPrefix(line, ":") && len(line) > 1 { + chain := utiliptables.Chain(strings.SplitN(line[1:], " ", 2)[0]) + chainsMap[chain] = line + } + } + return chainsMap +} + +func readLine(readIndex int, byteArray []byte) (string, int) { + currentReadIndex := readIndex + + // consume left spaces + for currentReadIndex < len(byteArray) { + if byteArray[currentReadIndex] == ' ' { + currentReadIndex++ + } else { + break + } + } + + // leftTrimIndex stores the left index of the line after the line is left-trimmed + leftTrimIndex := currentReadIndex + + // rightTrimIndex stores the right index of the line after the line is right-trimmed + // it is set to -1 since the correct value has not yet been determined. + rightTrimIndex := -1 + + for ; currentReadIndex < len(byteArray); currentReadIndex++ { + if byteArray[currentReadIndex] == ' ' { + // set rightTrimIndex + if rightTrimIndex == -1 { + rightTrimIndex = currentReadIndex + } + } else if (byteArray[currentReadIndex] == '\n') || (currentReadIndex == (len(byteArray) - 1)) { + // end of line or byte buffer is reached + if currentReadIndex <= leftTrimIndex { + return "", currentReadIndex + 1 + } + // set the rightTrimIndex + if rightTrimIndex == -1 { + rightTrimIndex = currentReadIndex + if currentReadIndex == (len(byteArray)-1) && (byteArray[currentReadIndex] != '\n') { + // ensure that the last character is part of the returned string, + // unless the last character is '\n' + rightTrimIndex = currentReadIndex + 1 + } + } + return string(byteArray[leftTrimIndex:rightTrimIndex]), currentReadIndex + 1 + } else { + // unset rightTrimIndex + rightTrimIndex = -1 + } + } + return "", currentReadIndex +} + +func isLocalIP(ip string) (bool, error) { + addrs, err := net.InterfaceAddrs() + if err != nil { + return false, err + } + for i := range addrs { + intf, _, err := net.ParseCIDR(addrs[i].String()) + if err != nil { + return false, err + } + if net.ParseIP(ip).Equal(intf) { + return true, nil + } + } + return false, nil +} + +func openLocalPort(lp *localPort) (closeable, error) { + // For ports on node IPs, open the actual port and hold it, even though we + // use iptables to redirect traffic. + // This ensures a) that it's safe to use that port and b) that (a) stays + // true. The risk is that some process on the node (e.g. sshd or kubelet) + // is using a port and we give that same port out to a Service. That would + // be bad because iptables would silently claim the traffic but the process + // would never know. + // NOTE: We should not need to have a real listen()ing socket - bind() + // should be enough, but I can't figure out a way to e2e test without + // it. Tools like 'ss' and 'netstat' do not show sockets that are + // bind()ed but not listen()ed, and at least the default debian netcat + // has no way to avoid about 10 seconds of retries. + var socket closeable + switch lp.protocol { + case "tcp": + listener, err := net.Listen("tcp", net.JoinHostPort(lp.ip, strconv.Itoa(lp.port))) + if err != nil { + return nil, err + } + socket = listener + case "udp": + addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(lp.ip, strconv.Itoa(lp.port))) + if err != nil { + return nil, err + } + conn, err := net.ListenUDP("udp", addr) + if err != nil { + return nil, err + } + socket = conn + default: + return nil, fmt.Errorf("unknown protocol %q", lp.protocol) + } + glog.V(2).Infof("Opened local port %s", lp.String()) + return socket, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/iptables/proxier_test.go b/vendor/k8s.io/kubernetes/pkg/proxy/iptables/proxier_test.go new file mode 100644 index 000000000..2d3a9c858 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/iptables/proxier_test.go @@ -0,0 +1,159 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 iptables + +import ( + "testing" + + utiliptables "k8s.io/kubernetes/pkg/util/iptables" +) + +func checkAllLines(t *testing.T, table utiliptables.Table, save []byte, expectedLines map[utiliptables.Chain]string) { + chainLines := getChainLines(table, save) + for chain, line := range chainLines { + if expected, exists := expectedLines[chain]; exists { + if expected != line { + t.Errorf("getChainLines expected chain line not present. For chain: %s Expected: %s Got: %s", chain, expected, line) + } + } else { + t.Errorf("getChainLines expected chain not present: %s", chain) + } + } +} + +func TestReadLinesFromByteBuffer(t *testing.T) { + testFn := func(byteArray []byte, expected []string) { + index := 0 + readIndex := 0 + for ; readIndex < len(byteArray); index++ { + line, n := readLine(readIndex, byteArray) + readIndex = n + if expected[index] != line { + t.Errorf("expected:%q, actual:%q", expected[index], line) + } + } // for + if readIndex < len(byteArray) { + t.Errorf("Byte buffer was only partially read. Buffer length is:%d, readIndex is:%d", len(byteArray), readIndex) + } + if index < len(expected) { + t.Errorf("All expected strings were not compared. expected arr length:%d, matched count:%d", len(expected), index-1) + } + } + + byteArray1 := []byte("\n Line 1 \n\n\n L ine4 \nLine 5 \n \n") + expected1 := []string{"", "Line 1", "", "", "L ine4", "Line 5", ""} + testFn(byteArray1, expected1) + + byteArray1 = []byte("") + expected1 = []string{} + testFn(byteArray1, expected1) + + byteArray1 = []byte("\n\n") + expected1 = []string{"", ""} + testFn(byteArray1, expected1) +} + +func TestGetChainLines(t *testing.T) { + iptables_save := `# Generated by iptables-save v1.4.7 on Wed Oct 29 14:56:01 2014 + *nat + :PREROUTING ACCEPT [2136997:197881818] + :POSTROUTING ACCEPT [4284525:258542680] + :OUTPUT ACCEPT [5901660:357267963] + -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER + COMMIT + # Completed on Wed Oct 29 14:56:01 2014` + expected := map[utiliptables.Chain]string{ + utiliptables.ChainPrerouting: ":PREROUTING ACCEPT [2136997:197881818]", + utiliptables.ChainPostrouting: ":POSTROUTING ACCEPT [4284525:258542680]", + utiliptables.ChainOutput: ":OUTPUT ACCEPT [5901660:357267963]", + } + checkAllLines(t, utiliptables.TableNAT, []byte(iptables_save), expected) +} + +func TestGetChainLinesMultipleTables(t *testing.T) { + iptables_save := `# Generated by iptables-save v1.4.21 on Fri Aug 7 14:47:37 2015 + *nat + :PREROUTING ACCEPT [2:138] + :INPUT ACCEPT [0:0] + :OUTPUT ACCEPT [0:0] + :POSTROUTING ACCEPT [0:0] + :DOCKER - [0:0] + :KUBE-NODEPORT-CONTAINER - [0:0] + :KUBE-NODEPORT-HOST - [0:0] + :KUBE-PORTALS-CONTAINER - [0:0] + :KUBE-PORTALS-HOST - [0:0] + :KUBE-SVC-1111111111111111 - [0:0] + :KUBE-SVC-2222222222222222 - [0:0] + :KUBE-SVC-3333333333333333 - [0:0] + :KUBE-SVC-4444444444444444 - [0:0] + :KUBE-SVC-5555555555555555 - [0:0] + :KUBE-SVC-6666666666666666 - [0:0] + -A PREROUTING -m comment --comment "handle ClusterIPs; NOTE: this must be before the NodePort rules" -j KUBE-PORTALS-CONTAINER + -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER + -A PREROUTING -m addrtype --dst-type LOCAL -m comment --comment "handle service NodePorts; NOTE: this must be the last rule in the chain" -j KUBE-NODEPORT-CONTAINER + -A OUTPUT -m comment --comment "handle ClusterIPs; NOTE: this must be before the NodePort rules" -j KUBE-PORTALS-HOST + -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER + -A OUTPUT -m addrtype --dst-type LOCAL -m comment --comment "handle service NodePorts; NOTE: this must be the last rule in the chain" -j KUBE-NODEPORT-HOST + -A POSTROUTING -s 10.246.1.0/24 ! -o cbr0 -j MASQUERADE + -A POSTROUTING -s 10.0.2.15/32 -d 10.0.2.15/32 -m comment --comment "handle pod connecting to self" -j MASQUERADE + -A KUBE-PORTALS-CONTAINER -d 10.247.0.1/32 -p tcp -m comment --comment "portal for default/kubernetes:" -m state --state NEW -m tcp --dport 443 -j KUBE-SVC-5555555555555555 + -A KUBE-PORTALS-CONTAINER -d 10.247.0.10/32 -p udp -m comment --comment "portal for kube-system/kube-dns:dns" -m state --state NEW -m udp --dport 53 -j KUBE-SVC-6666666666666666 + -A KUBE-PORTALS-CONTAINER -d 10.247.0.10/32 -p tcp -m comment --comment "portal for kube-system/kube-dns:dns-tcp" -m state --state NEW -m tcp --dport 53 -j KUBE-SVC-2222222222222222 + -A KUBE-PORTALS-HOST -d 10.247.0.1/32 -p tcp -m comment --comment "portal for default/kubernetes:" -m state --state NEW -m tcp --dport 443 -j KUBE-SVC-5555555555555555 + -A KUBE-PORTALS-HOST -d 10.247.0.10/32 -p udp -m comment --comment "portal for kube-system/kube-dns:dns" -m state --state NEW -m udp --dport 53 -j KUBE-SVC-6666666666666666 + -A KUBE-PORTALS-HOST -d 10.247.0.10/32 -p tcp -m comment --comment "portal for kube-system/kube-dns:dns-tcp" -m state --state NEW -m tcp --dport 53 -j KUBE-SVC-2222222222222222 + -A KUBE-SVC-1111111111111111 -p udp -m comment --comment "kube-system/kube-dns:dns" -m recent --set --name KUBE-SVC-1111111111111111 --mask 255.255.255.255 --rsource -j DNAT --to-destination 10.246.1.2:53 + -A KUBE-SVC-2222222222222222 -m comment --comment "kube-system/kube-dns:dns-tcp" -j KUBE-SVC-3333333333333333 + -A KUBE-SVC-3333333333333333 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp" -m recent --set --name KUBE-SVC-3333333333333333 --mask 255.255.255.255 --rsource -j DNAT --to-destination 10.246.1.2:53 + -A KUBE-SVC-4444444444444444 -p tcp -m comment --comment "default/kubernetes:" -m recent --set --name KUBE-SVC-4444444444444444 --mask 255.255.255.255 --rsource -j DNAT --to-destination 10.245.1.2:443 + -A KUBE-SVC-5555555555555555 -m comment --comment "default/kubernetes:" -j KUBE-SVC-4444444444444444 + -A KUBE-SVC-6666666666666666 -m comment --comment "kube-system/kube-dns:dns" -j KUBE-SVC-1111111111111111 + COMMIT + # Completed on Fri Aug 7 14:47:37 2015 + # Generated by iptables-save v1.4.21 on Fri Aug 7 14:47:37 2015 + *filter + :INPUT ACCEPT [17514:83115836] + :FORWARD ACCEPT [0:0] + :OUTPUT ACCEPT [8909:688225] + :DOCKER - [0:0] + -A FORWARD -o cbr0 -j DOCKER + -A FORWARD -o cbr0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT + -A FORWARD -i cbr0 ! -o cbr0 -j ACCEPT + -A FORWARD -i cbr0 -o cbr0 -j ACCEPT + COMMIT + ` + expected := map[utiliptables.Chain]string{ + utiliptables.ChainPrerouting: ":PREROUTING ACCEPT [2:138]", + utiliptables.Chain("INPUT"): ":INPUT ACCEPT [0:0]", + utiliptables.Chain("OUTPUT"): ":OUTPUT ACCEPT [0:0]", + utiliptables.ChainPostrouting: ":POSTROUTING ACCEPT [0:0]", + utiliptables.Chain("DOCKER"): ":DOCKER - [0:0]", + utiliptables.Chain("KUBE-NODEPORT-CONTAINER"): ":KUBE-NODEPORT-CONTAINER - [0:0]", + utiliptables.Chain("KUBE-NODEPORT-HOST"): ":KUBE-NODEPORT-HOST - [0:0]", + utiliptables.Chain("KUBE-PORTALS-CONTAINER"): ":KUBE-PORTALS-CONTAINER - [0:0]", + utiliptables.Chain("KUBE-PORTALS-HOST"): ":KUBE-PORTALS-HOST - [0:0]", + utiliptables.Chain("KUBE-SVC-1111111111111111"): ":KUBE-SVC-1111111111111111 - [0:0]", + utiliptables.Chain("KUBE-SVC-2222222222222222"): ":KUBE-SVC-2222222222222222 - [0:0]", + utiliptables.Chain("KUBE-SVC-3333333333333333"): ":KUBE-SVC-3333333333333333 - [0:0]", + utiliptables.Chain("KUBE-SVC-4444444444444444"): ":KUBE-SVC-4444444444444444 - [0:0]", + utiliptables.Chain("KUBE-SVC-5555555555555555"): ":KUBE-SVC-5555555555555555 - [0:0]", + utiliptables.Chain("KUBE-SVC-6666666666666666"): ":KUBE-SVC-6666666666666666 - [0:0]", + } + checkAllLines(t, utiliptables.TableNAT, []byte(iptables_save), expected) +} + +// TODO(thockin): add a test for syncProxyRules() or break it down further and test the pieces. diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/types.go b/vendor/k8s.io/kubernetes/pkg/proxy/types.go new file mode 100644 index 000000000..15b227f1c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/types.go @@ -0,0 +1,49 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 proxy + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/types" +) + +// ProxyProvider is the interface provided by proxier implementations. +type ProxyProvider interface { + // OnServiceUpdate manages the active set of service proxies. + // Active service proxies are reinitialized if found in the update set or + // removed if missing from the update set. + OnServiceUpdate(services []api.Service) + // Sync immediately synchronizes the ProxyProvider's current state to iptables. + Sync() + // SyncLoop runs periodic work. + // This is expected to run as a goroutine or as the main loop of the app. + // It does not return. + SyncLoop() +} + +// ServicePortName carries a namespace + name + portname. This is the unique +// identfier for a load-balanced service. +type ServicePortName struct { + types.NamespacedName + Port string +} + +func (spn ServicePortName) String() string { + return fmt.Sprintf("%s:%s", spn.NamespacedName.String(), spn.Port) +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/userspace/loadbalancer.go b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/loadbalancer.go new file mode 100644 index 000000000..f32f05fc8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/loadbalancer.go @@ -0,0 +1,33 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 userspace + +import ( + "net" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/proxy" +) + +// LoadBalancer is an interface for distributing incoming requests to service endpoints. +type LoadBalancer interface { + // NextEndpoint returns the endpoint to handle a request for the given + // service-port and source address. + NextEndpoint(service proxy.ServicePortName, srcAddr net.Addr) (string, error) + NewService(service proxy.ServicePortName, sessionAffinityType api.ServiceAffinity, stickyMaxAgeMinutes int) error + CleanupStaleStickySessions(service proxy.ServicePortName) +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/userspace/port_allocator.go b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/port_allocator.go new file mode 100644 index 000000000..22182f9c9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/port_allocator.go @@ -0,0 +1,153 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 userspace + +import ( + "errors" + "math/big" + "math/rand" + "sync" + "time" + + "k8s.io/kubernetes/pkg/util/net" + "k8s.io/kubernetes/pkg/util/wait" +) + +var ( + errPortRangeNoPortsRemaining = errors.New("port allocation failed; there are no remaining ports left to allocate in the accepted range") +) + +type PortAllocator interface { + AllocateNext() (int, error) + Release(int) +} + +// randomAllocator is a PortAllocator implementation that allocates random ports, yielding +// a port value of 0 for every call to AllocateNext(). +type randomAllocator struct{} + +// AllocateNext always returns 0 +func (r *randomAllocator) AllocateNext() (int, error) { + return 0, nil +} + +// Release is a noop +func (r *randomAllocator) Release(_ int) { + // noop +} + +// newPortAllocator builds PortAllocator for a given PortRange. If the PortRange is empty +// then a random port allocator is returned; otherwise, a new range-based allocator +// is returned. +func newPortAllocator(r net.PortRange) PortAllocator { + if r.Base == 0 { + return &randomAllocator{} + } + return newPortRangeAllocator(r) +} + +const ( + portsBufSize = 16 + nextFreePortCooldown = 500 * time.Millisecond + allocateNextTimeout = 1 * time.Second +) + +type rangeAllocator struct { + net.PortRange + ports chan int + used big.Int + lock sync.Mutex + rand *rand.Rand +} + +func newPortRangeAllocator(r net.PortRange) PortAllocator { + if r.Base == 0 || r.Size == 0 { + panic("illegal argument: may not specify an empty port range") + } + ra := &rangeAllocator{ + PortRange: r, + ports: make(chan int, portsBufSize), + rand: rand.New(rand.NewSource(time.Now().UnixNano())), + } + go wait.Until(func() { ra.fillPorts(wait.NeverStop) }, nextFreePortCooldown, wait.NeverStop) + return ra +} + +// fillPorts loops, always searching for the next free port and, if found, fills the ports buffer with it. +// this func blocks until either there are no remaining free ports, or else the stopCh chan is closed. +func (r *rangeAllocator) fillPorts(stopCh <-chan struct{}) { + for { + port := r.nextFreePort() + if port == -1 { + return + } + select { + case <-stopCh: + return + case r.ports <- port: + } + } +} + +// nextFreePort finds a free port, first picking a random port. if that port is already in use +// then the port range is scanned sequentially until either a port is found or the scan completes +// unsuccessfully. an unsuccessful scan returns a port of -1. +func (r *rangeAllocator) nextFreePort() int { + r.lock.Lock() + defer r.lock.Unlock() + + // choose random port + j := r.rand.Intn(r.Size) + if b := r.used.Bit(j); b == 0 { + r.used.SetBit(&r.used, j, 1) + return j + r.Base + } + + // search sequentially + for i := j + 1; i < r.Size; i++ { + if b := r.used.Bit(i); b == 0 { + r.used.SetBit(&r.used, i, 1) + return i + r.Base + } + } + for i := 0; i < j; i++ { + if b := r.used.Bit(i); b == 0 { + r.used.SetBit(&r.used, i, 1) + return i + r.Base + } + } + return -1 +} + +func (r *rangeAllocator) AllocateNext() (port int, err error) { + select { + case port = <-r.ports: + case <-time.After(allocateNextTimeout): + err = errPortRangeNoPortsRemaining + } + return +} + +func (r *rangeAllocator) Release(port int) { + port -= r.Base + if port < 0 || port >= r.Size { + return + } + r.lock.Lock() + defer r.lock.Unlock() + r.used.SetBit(&r.used, port, 0) +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/userspace/port_allocator_test.go b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/port_allocator_test.go new file mode 100644 index 000000000..b2c2e5a67 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/port_allocator_test.go @@ -0,0 +1,101 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 userspace + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/util/net" +) + +func TestRangeAllocatorEmpty(t *testing.T) { + r := &net.PortRange{} + r.Set("0-0") + defer func() { + if rv := recover(); rv == nil { + t.Fatalf("expected panic because of empty port range: %+v", r) + } + }() + _ = newPortRangeAllocator(*r) +} + +func TestRangeAllocatorFullyAllocated(t *testing.T) { + r := &net.PortRange{} + r.Set("1-1") + a := newPortRangeAllocator(*r) + p, err := a.AllocateNext() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if p != 1 { + t.Fatalf("unexpected allocated port: %d", p) + } + + _, err = a.AllocateNext() + if err == nil { + t.Fatalf("expected error because of fully-allocated range") + } + + a.Release(p) + p, err = a.AllocateNext() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if p != 1 { + t.Fatalf("unexpected allocated port: %d", p) + } + + _, err = a.AllocateNext() + if err == nil { + t.Fatalf("expected error because of fully-allocated range") + } +} + +func TestRangeAllocator_RandomishAllocation(t *testing.T) { + r := &net.PortRange{} + r.Set("1-100") + a := newPortRangeAllocator(*r) + + // allocate all the ports + var err error + ports := make([]int, 100, 100) + for i := 0; i < 100; i++ { + ports[i], err = a.AllocateNext() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } + + // release them all + for i := 0; i < 100; i++ { + a.Release(ports[i]) + } + + // allocate the ports again + rports := make([]int, 100, 100) + for i := 0; i < 100; i++ { + rports[i], err = a.AllocateNext() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } + + if reflect.DeepEqual(ports, rports) { + t.Fatalf("expected re-allocated ports to be in a somewhat random order") + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/userspace/proxier.go b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/proxier.go new file mode 100644 index 000000000..76ba79481 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/proxier.go @@ -0,0 +1,1050 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 userspace + +import ( + "fmt" + "net" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/proxy" + "k8s.io/kubernetes/pkg/types" + utilnet "k8s.io/kubernetes/pkg/util/net" + + utilerrors "k8s.io/kubernetes/pkg/util/errors" + "k8s.io/kubernetes/pkg/util/iptables" + "k8s.io/kubernetes/pkg/util/runtime" +) + +type portal struct { + ip net.IP + port int + isExternal bool +} + +type serviceInfo struct { + isAliveAtomic int32 // Only access this with atomic ops + portal portal + protocol api.Protocol + proxyPort int + socket proxySocket + timeout time.Duration + activeClients *clientCache + nodePort int + loadBalancerStatus api.LoadBalancerStatus + sessionAffinityType api.ServiceAffinity + stickyMaxAgeMinutes int + // Deprecated, but required for back-compat (including e2e) + externalIPs []string +} + +func (info *serviceInfo) setAlive(b bool) { + var i int32 + if b { + i = 1 + } + atomic.StoreInt32(&info.isAliveAtomic, i) +} + +func (info *serviceInfo) isAlive() bool { + return atomic.LoadInt32(&info.isAliveAtomic) != 0 +} + +func logTimeout(err error) bool { + if e, ok := err.(net.Error); ok { + if e.Timeout() { + glog.V(3).Infof("connection to endpoint closed due to inactivity") + return true + } + } + return false +} + +// Proxier is a simple proxy for TCP connections between a localhost:lport +// and services that provide the actual implementations. +type Proxier struct { + loadBalancer LoadBalancer + mu sync.Mutex // protects serviceMap + serviceMap map[proxy.ServicePortName]*serviceInfo + syncPeriod time.Duration + udpIdleTimeout time.Duration + portMapMutex sync.Mutex + portMap map[portMapKey]*portMapValue + numProxyLoops int32 // use atomic ops to access this; mostly for testing + listenIP net.IP + iptables iptables.Interface + hostIP net.IP + proxyPorts PortAllocator +} + +// assert Proxier is a ProxyProvider +var _ proxy.ProxyProvider = &Proxier{} + +// A key for the portMap. The ip has to be a string because slices can't be map +// keys. +type portMapKey struct { + ip string + port int + protocol api.Protocol +} + +func (k *portMapKey) String() string { + return fmt.Sprintf("%s:%d/%s", k.ip, k.port, k.protocol) +} + +// A value for the portMap +type portMapValue struct { + owner proxy.ServicePortName + socket interface { + Close() error + } +} + +var ( + // ErrProxyOnLocalhost is returned by NewProxier if the user requests a proxier on + // the loopback address. May be checked for by callers of NewProxier to know whether + // the caller provided invalid input. + ErrProxyOnLocalhost = fmt.Errorf("cannot proxy on localhost") +) + +// IsProxyLocked returns true if the proxy could not acquire the lock on iptables. +func IsProxyLocked(err error) bool { + return strings.Contains(err.Error(), "holding the xtables lock") +} + +// NewProxier returns a new Proxier given a LoadBalancer and an address on +// which to listen. Because of the iptables logic, It is assumed that there +// is only a single Proxier active on a machine. An error will be returned if +// the proxier cannot be started due to an invalid ListenIP (loopback) or +// if iptables fails to update or acquire the initial lock. Once a proxier is +// created, it will keep iptables up to date in the background and will not +// terminate if a particular iptables call fails. +func NewProxier(loadBalancer LoadBalancer, listenIP net.IP, iptables iptables.Interface, pr utilnet.PortRange, syncPeriod, udpIdleTimeout time.Duration) (*Proxier, error) { + if listenIP.Equal(localhostIPv4) || listenIP.Equal(localhostIPv6) { + return nil, ErrProxyOnLocalhost + } + + hostIP, err := utilnet.ChooseHostInterface() + if err != nil { + return nil, fmt.Errorf("failed to select a host interface: %v", err) + } + + err = setRLimit(64 * 1000) + if err != nil { + return nil, fmt.Errorf("failed to set open file handler limit: %v", err) + } + + proxyPorts := newPortAllocator(pr) + + glog.V(2).Infof("Setting proxy IP to %v and initializing iptables", hostIP) + return createProxier(loadBalancer, listenIP, iptables, hostIP, proxyPorts, syncPeriod, udpIdleTimeout) +} + +func createProxier(loadBalancer LoadBalancer, listenIP net.IP, iptables iptables.Interface, hostIP net.IP, proxyPorts PortAllocator, syncPeriod, udpIdleTimeout time.Duration) (*Proxier, error) { + // convenient to pass nil for tests.. + if proxyPorts == nil { + proxyPorts = newPortAllocator(utilnet.PortRange{}) + } + // Set up the iptables foundations we need. + if err := iptablesInit(iptables); err != nil { + return nil, fmt.Errorf("failed to initialize iptables: %v", err) + } + // Flush old iptables rules (since the bound ports will be invalid after a restart). + // When OnUpdate() is first called, the rules will be recreated. + if err := iptablesFlush(iptables); err != nil { + return nil, fmt.Errorf("failed to flush iptables: %v", err) + } + return &Proxier{ + loadBalancer: loadBalancer, + serviceMap: make(map[proxy.ServicePortName]*serviceInfo), + portMap: make(map[portMapKey]*portMapValue), + syncPeriod: syncPeriod, + udpIdleTimeout: udpIdleTimeout, + listenIP: listenIP, + iptables: iptables, + hostIP: hostIP, + proxyPorts: proxyPorts, + }, nil +} + +// CleanupLeftovers removes all iptables rules and chains created by the Proxier +// It returns true if an error was encountered. Errors are logged. +func CleanupLeftovers(ipt iptables.Interface) (encounteredError bool) { + // NOTE: Warning, this needs to be kept in sync with the userspace Proxier, + // we want to ensure we remove all of the iptables rules it creates. + // Currently they are all in iptablesInit() + // Delete Rules first, then Flush and Delete Chains + args := []string{"-m", "comment", "--comment", "handle ClusterIPs; NOTE: this must be before the NodePort rules"} + if err := ipt.DeleteRule(iptables.TableNAT, iptables.ChainOutput, append(args, "-j", string(iptablesHostPortalChain))...); err != nil { + if !iptables.IsNotFoundError(err) { + glog.Errorf("Error removing userspace rule: %v", err) + encounteredError = true + } + } + if err := ipt.DeleteRule(iptables.TableNAT, iptables.ChainPrerouting, append(args, "-j", string(iptablesContainerPortalChain))...); err != nil { + if !iptables.IsNotFoundError(err) { + glog.Errorf("Error removing userspace rule: %v", err) + encounteredError = true + } + } + args = []string{"-m", "addrtype", "--dst-type", "LOCAL"} + args = append(args, "-m", "comment", "--comment", "handle service NodePorts; NOTE: this must be the last rule in the chain") + if err := ipt.DeleteRule(iptables.TableNAT, iptables.ChainOutput, append(args, "-j", string(iptablesHostNodePortChain))...); err != nil { + if !iptables.IsNotFoundError(err) { + glog.Errorf("Error removing userspace rule: %v", err) + encounteredError = true + } + } + if err := ipt.DeleteRule(iptables.TableNAT, iptables.ChainPrerouting, append(args, "-j", string(iptablesContainerNodePortChain))...); err != nil { + if !iptables.IsNotFoundError(err) { + glog.Errorf("Error removing userspace rule: %v", err) + encounteredError = true + } + } + args = []string{"-m", "comment", "--comment", "Ensure that non-local NodePort traffic can flow"} + if err := ipt.DeleteRule(iptables.TableFilter, iptables.ChainInput, append(args, "-j", string(iptablesNonLocalNodePortChain))...); err != nil { + if !iptables.IsNotFoundError(err) { + glog.Errorf("Error removing userspace rule: %v", err) + encounteredError = true + } + } + + // flush and delete chains. + tableChains := map[iptables.Table][]iptables.Chain{ + iptables.TableNAT: {iptablesContainerPortalChain, iptablesHostPortalChain, iptablesHostNodePortChain, iptablesContainerNodePortChain}, + iptables.TableFilter: {iptablesNonLocalNodePortChain}, + } + for table, chains := range tableChains { + for _, c := range chains { + // flush chain, then if successful delete, delete will fail if flush fails. + if err := ipt.FlushChain(table, c); err != nil { + if !iptables.IsNotFoundError(err) { + glog.Errorf("Error flushing userspace chain: %v", err) + encounteredError = true + } + } else { + if err = ipt.DeleteChain(table, c); err != nil { + if !iptables.IsNotFoundError(err) { + glog.Errorf("Error deleting userspace chain: %v", err) + encounteredError = true + } + } + } + } + } + return encounteredError +} + +// Sync is called to immediately synchronize the proxier state to iptables +func (proxier *Proxier) Sync() { + if err := iptablesInit(proxier.iptables); err != nil { + glog.Errorf("Failed to ensure iptables: %v", err) + } + proxier.ensurePortals() + proxier.cleanupStaleStickySessions() +} + +// SyncLoop runs periodic work. This is expected to run as a goroutine or as the main loop of the app. It does not return. +func (proxier *Proxier) SyncLoop() { + t := time.NewTicker(proxier.syncPeriod) + defer t.Stop() + for { + <-t.C + glog.V(6).Infof("Periodic sync") + proxier.Sync() + } +} + +// Ensure that portals exist for all services. +func (proxier *Proxier) ensurePortals() { + proxier.mu.Lock() + defer proxier.mu.Unlock() + // NB: This does not remove rules that should not be present. + for name, info := range proxier.serviceMap { + err := proxier.openPortal(name, info) + if err != nil { + glog.Errorf("Failed to ensure portal for %q: %v", name, err) + } + } +} + +// clean up any stale sticky session records in the hash map. +func (proxier *Proxier) cleanupStaleStickySessions() { + proxier.mu.Lock() + defer proxier.mu.Unlock() + for name := range proxier.serviceMap { + proxier.loadBalancer.CleanupStaleStickySessions(name) + } +} + +// This assumes proxier.mu is not locked. +func (proxier *Proxier) stopProxy(service proxy.ServicePortName, info *serviceInfo) error { + proxier.mu.Lock() + defer proxier.mu.Unlock() + return proxier.stopProxyInternal(service, info) +} + +// This assumes proxier.mu is locked. +func (proxier *Proxier) stopProxyInternal(service proxy.ServicePortName, info *serviceInfo) error { + delete(proxier.serviceMap, service) + info.setAlive(false) + err := info.socket.Close() + port := info.socket.ListenPort() + proxier.proxyPorts.Release(port) + return err +} + +func (proxier *Proxier) getServiceInfo(service proxy.ServicePortName) (*serviceInfo, bool) { + proxier.mu.Lock() + defer proxier.mu.Unlock() + info, ok := proxier.serviceMap[service] + return info, ok +} + +func (proxier *Proxier) setServiceInfo(service proxy.ServicePortName, info *serviceInfo) { + proxier.mu.Lock() + defer proxier.mu.Unlock() + proxier.serviceMap[service] = info +} + +// addServiceOnPort starts listening for a new service, returning the serviceInfo. +// Pass proxyPort=0 to allocate a random port. The timeout only applies to UDP +// connections, for now. +func (proxier *Proxier) addServiceOnPort(service proxy.ServicePortName, protocol api.Protocol, proxyPort int, timeout time.Duration) (*serviceInfo, error) { + sock, err := newProxySocket(protocol, proxier.listenIP, proxyPort) + if err != nil { + return nil, err + } + _, portStr, err := net.SplitHostPort(sock.Addr().String()) + if err != nil { + sock.Close() + return nil, err + } + portNum, err := strconv.Atoi(portStr) + if err != nil { + sock.Close() + return nil, err + } + si := &serviceInfo{ + isAliveAtomic: 1, + proxyPort: portNum, + protocol: protocol, + socket: sock, + timeout: timeout, + activeClients: newClientCache(), + sessionAffinityType: api.ServiceAffinityNone, // default + stickyMaxAgeMinutes: 180, // TODO: parameterize this in the API. + } + proxier.setServiceInfo(service, si) + + glog.V(2).Infof("Proxying for service %q on %s port %d", service, protocol, portNum) + go func(service proxy.ServicePortName, proxier *Proxier) { + defer runtime.HandleCrash() + atomic.AddInt32(&proxier.numProxyLoops, 1) + sock.ProxyLoop(service, si, proxier) + atomic.AddInt32(&proxier.numProxyLoops, -1) + }(service, proxier) + + return si, nil +} + +// OnServiceUpdate manages the active set of service proxies. +// Active service proxies are reinitialized if found in the update set or +// shutdown if missing from the update set. +func (proxier *Proxier) OnServiceUpdate(services []api.Service) { + glog.V(4).Infof("Received update notice: %+v", services) + activeServices := make(map[proxy.ServicePortName]bool) // use a map as a set + for i := range services { + service := &services[i] + + // if ClusterIP is "None" or empty, skip proxying + if !api.IsServiceIPSet(service) { + glog.V(3).Infof("Skipping service %s due to clusterIP = %q", types.NamespacedName{Namespace: service.Namespace, Name: service.Name}, service.Spec.ClusterIP) + continue + } + + for i := range service.Spec.Ports { + servicePort := &service.Spec.Ports[i] + serviceName := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: service.Namespace, Name: service.Name}, Port: servicePort.Name} + activeServices[serviceName] = true + serviceIP := net.ParseIP(service.Spec.ClusterIP) + info, exists := proxier.getServiceInfo(serviceName) + // TODO: check health of the socket? What if ProxyLoop exited? + if exists && sameConfig(info, service, servicePort) { + // Nothing changed. + continue + } + if exists { + glog.V(4).Infof("Something changed for service %q: stopping it", serviceName) + err := proxier.closePortal(serviceName, info) + if err != nil { + glog.Errorf("Failed to close portal for %q: %v", serviceName, err) + } + err = proxier.stopProxy(serviceName, info) + if err != nil { + glog.Errorf("Failed to stop service %q: %v", serviceName, err) + } + } + + proxyPort, err := proxier.proxyPorts.AllocateNext() + if err != nil { + glog.Errorf("failed to allocate proxy port for service %q: %v", serviceName, err) + continue + } + + glog.V(1).Infof("Adding new service %q at %s:%d/%s", serviceName, serviceIP, servicePort.Port, servicePort.Protocol) + info, err = proxier.addServiceOnPort(serviceName, servicePort.Protocol, proxyPort, proxier.udpIdleTimeout) + if err != nil { + glog.Errorf("Failed to start proxy for %q: %v", serviceName, err) + continue + } + info.portal.ip = serviceIP + info.portal.port = servicePort.Port + info.externalIPs = service.Spec.ExternalIPs + // Deep-copy in case the service instance changes + info.loadBalancerStatus = *api.LoadBalancerStatusDeepCopy(&service.Status.LoadBalancer) + info.nodePort = servicePort.NodePort + info.sessionAffinityType = service.Spec.SessionAffinity + glog.V(4).Infof("info: %+v", info) + + err = proxier.openPortal(serviceName, info) + if err != nil { + glog.Errorf("Failed to open portal for %q: %v", serviceName, err) + } + proxier.loadBalancer.NewService(serviceName, info.sessionAffinityType, info.stickyMaxAgeMinutes) + } + } + proxier.mu.Lock() + defer proxier.mu.Unlock() + for name, info := range proxier.serviceMap { + if !activeServices[name] { + glog.V(1).Infof("Stopping service %q", name) + err := proxier.closePortal(name, info) + if err != nil { + glog.Errorf("Failed to close portal for %q: %v", name, err) + } + err = proxier.stopProxyInternal(name, info) + if err != nil { + glog.Errorf("Failed to stop service %q: %v", name, err) + } + } + } +} + +func sameConfig(info *serviceInfo, service *api.Service, port *api.ServicePort) bool { + if info.protocol != port.Protocol || info.portal.port != port.Port || info.nodePort != port.NodePort { + return false + } + if !info.portal.ip.Equal(net.ParseIP(service.Spec.ClusterIP)) { + return false + } + if !ipsEqual(info.externalIPs, service.Spec.ExternalIPs) { + return false + } + if !api.LoadBalancerStatusEqual(&info.loadBalancerStatus, &service.Status.LoadBalancer) { + return false + } + if info.sessionAffinityType != service.Spec.SessionAffinity { + return false + } + return true +} + +func ipsEqual(lhs, rhs []string) bool { + if len(lhs) != len(rhs) { + return false + } + for i := range lhs { + if lhs[i] != rhs[i] { + return false + } + } + return true +} + +func (proxier *Proxier) openPortal(service proxy.ServicePortName, info *serviceInfo) error { + err := proxier.openOnePortal(info.portal, info.protocol, proxier.listenIP, info.proxyPort, service) + if err != nil { + return err + } + for _, publicIP := range info.externalIPs { + err = proxier.openOnePortal(portal{net.ParseIP(publicIP), info.portal.port, true}, info.protocol, proxier.listenIP, info.proxyPort, service) + if err != nil { + return err + } + } + for _, ingress := range info.loadBalancerStatus.Ingress { + if ingress.IP != "" { + err = proxier.openOnePortal(portal{net.ParseIP(ingress.IP), info.portal.port, false}, info.protocol, proxier.listenIP, info.proxyPort, service) + if err != nil { + return err + } + } + } + if info.nodePort != 0 { + err = proxier.openNodePort(info.nodePort, info.protocol, proxier.listenIP, info.proxyPort, service) + if err != nil { + return err + } + } + return nil +} + +func (proxier *Proxier) openOnePortal(portal portal, protocol api.Protocol, proxyIP net.IP, proxyPort int, name proxy.ServicePortName) error { + if local, err := isLocalIP(portal.ip); err != nil { + return fmt.Errorf("can't determine if IP is local, assuming not: %v", err) + } else if local { + err := proxier.claimNodePort(portal.ip, portal.port, protocol, name) + if err != nil { + return err + } + } + + // Handle traffic from containers. + args := proxier.iptablesContainerPortalArgs(portal.ip, portal.isExternal, false, portal.port, protocol, proxyIP, proxyPort, name) + existed, err := proxier.iptables.EnsureRule(iptables.Append, iptables.TableNAT, iptablesContainerPortalChain, args...) + if err != nil { + glog.Errorf("Failed to install iptables %s rule for service %q, args:%v", iptablesContainerPortalChain, name, args) + return err + } + if !existed { + glog.V(3).Infof("Opened iptables from-containers portal for service %q on %s %s:%d", name, protocol, portal.ip, portal.port) + } + if portal.isExternal { + args := proxier.iptablesContainerPortalArgs(portal.ip, false, true, portal.port, protocol, proxyIP, proxyPort, name) + existed, err := proxier.iptables.EnsureRule(iptables.Append, iptables.TableNAT, iptablesContainerPortalChain, args...) + if err != nil { + glog.Errorf("Failed to install iptables %s rule that opens service %q for local traffic, args:%v", iptablesContainerPortalChain, name, args) + return err + } + if !existed { + glog.V(3).Infof("Opened iptables from-containers portal for service %q on %s %s:%d for local traffic", name, protocol, portal.ip, portal.port) + } + + args = proxier.iptablesHostPortalArgs(portal.ip, true, portal.port, protocol, proxyIP, proxyPort, name) + existed, err = proxier.iptables.EnsureRule(iptables.Append, iptables.TableNAT, iptablesHostPortalChain, args...) + if err != nil { + glog.Errorf("Failed to install iptables %s rule for service %q for dst-local traffic", iptablesHostPortalChain, name) + return err + } + if !existed { + glog.V(3).Infof("Opened iptables from-host portal for service %q on %s %s:%d for dst-local traffic", name, protocol, portal.ip, portal.port) + } + return nil + } + + // Handle traffic from the host. + args = proxier.iptablesHostPortalArgs(portal.ip, false, portal.port, protocol, proxyIP, proxyPort, name) + existed, err = proxier.iptables.EnsureRule(iptables.Append, iptables.TableNAT, iptablesHostPortalChain, args...) + if err != nil { + glog.Errorf("Failed to install iptables %s rule for service %q", iptablesHostPortalChain, name) + return err + } + if !existed { + glog.V(3).Infof("Opened iptables from-host portal for service %q on %s %s:%d", name, protocol, portal.ip, portal.port) + } + return nil +} + +// Marks a port as being owned by a particular service, or returns error if already claimed. +// Idempotent: reclaiming with the same owner is not an error +func (proxier *Proxier) claimNodePort(ip net.IP, port int, protocol api.Protocol, owner proxy.ServicePortName) error { + proxier.portMapMutex.Lock() + defer proxier.portMapMutex.Unlock() + + // TODO: We could pre-populate some reserved ports into portMap and/or blacklist some well-known ports + + key := portMapKey{ip: ip.String(), port: port, protocol: protocol} + existing, found := proxier.portMap[key] + if !found { + // Hold the actual port open, even though we use iptables to redirect + // it. This ensures that a) it's safe to take and b) that stays true. + // NOTE: We should not need to have a real listen()ing socket - bind() + // should be enough, but I can't figure out a way to e2e test without + // it. Tools like 'ss' and 'netstat' do not show sockets that are + // bind()ed but not listen()ed, and at least the default debian netcat + // has no way to avoid about 10 seconds of retries. + socket, err := newProxySocket(protocol, ip, port) + if err != nil { + return fmt.Errorf("can't open node port for %s: %v", key.String(), err) + } + proxier.portMap[key] = &portMapValue{owner: owner, socket: socket} + glog.V(2).Infof("Claimed local port %s", key.String()) + return nil + } + if existing.owner == owner { + // We are idempotent + return nil + } + return fmt.Errorf("Port conflict detected on port %s. %v vs %v", key.String(), owner, existing) +} + +// Release a claim on a port. Returns an error if the owner does not match the claim. +// Tolerates release on an unclaimed port, to simplify . +func (proxier *Proxier) releaseNodePort(ip net.IP, port int, protocol api.Protocol, owner proxy.ServicePortName) error { + proxier.portMapMutex.Lock() + defer proxier.portMapMutex.Unlock() + + key := portMapKey{ip: ip.String(), port: port, protocol: protocol} + existing, found := proxier.portMap[key] + if !found { + // We tolerate this, it happens if we are cleaning up a failed allocation + glog.Infof("Ignoring release on unowned port: %v", key) + return nil + } + if existing.owner != owner { + return fmt.Errorf("Port conflict detected on port %v (unowned unlock). %v vs %v", key, owner, existing) + } + delete(proxier.portMap, key) + existing.socket.Close() + return nil +} + +func (proxier *Proxier) openNodePort(nodePort int, protocol api.Protocol, proxyIP net.IP, proxyPort int, name proxy.ServicePortName) error { + // TODO: Do we want to allow containers to access public services? Probably yes. + // TODO: We could refactor this to be the same code as portal, but with IP == nil + + err := proxier.claimNodePort(nil, nodePort, protocol, name) + if err != nil { + return err + } + + // Handle traffic from containers. + args := proxier.iptablesContainerNodePortArgs(nodePort, protocol, proxyIP, proxyPort, name) + existed, err := proxier.iptables.EnsureRule(iptables.Append, iptables.TableNAT, iptablesContainerNodePortChain, args...) + if err != nil { + glog.Errorf("Failed to install iptables %s rule for service %q", iptablesContainerNodePortChain, name) + return err + } + if !existed { + glog.Infof("Opened iptables from-containers public port for service %q on %s port %d", name, protocol, nodePort) + } + + // Handle traffic from the host. + args = proxier.iptablesHostNodePortArgs(nodePort, protocol, proxyIP, proxyPort, name) + existed, err = proxier.iptables.EnsureRule(iptables.Append, iptables.TableNAT, iptablesHostNodePortChain, args...) + if err != nil { + glog.Errorf("Failed to install iptables %s rule for service %q", iptablesHostNodePortChain, name) + return err + } + if !existed { + glog.Infof("Opened iptables from-host public port for service %q on %s port %d", name, protocol, nodePort) + } + + args = proxier.iptablesNonLocalNodePortArgs(nodePort, protocol, proxyIP, proxyPort, name) + existed, err = proxier.iptables.EnsureRule(iptables.Append, iptables.TableFilter, iptablesNonLocalNodePortChain, args...) + if err != nil { + glog.Errorf("Failed to install iptables %s rule for service %q", iptablesNonLocalNodePortChain, name) + return err + } + if !existed { + glog.Infof("Opened iptables from-non-local public port for service %q on %s port %d", name, protocol, nodePort) + } + + return nil +} + +func (proxier *Proxier) closePortal(service proxy.ServicePortName, info *serviceInfo) error { + // Collect errors and report them all at the end. + el := proxier.closeOnePortal(info.portal, info.protocol, proxier.listenIP, info.proxyPort, service) + for _, publicIP := range info.externalIPs { + el = append(el, proxier.closeOnePortal(portal{net.ParseIP(publicIP), info.portal.port, true}, info.protocol, proxier.listenIP, info.proxyPort, service)...) + } + for _, ingress := range info.loadBalancerStatus.Ingress { + if ingress.IP != "" { + el = append(el, proxier.closeOnePortal(portal{net.ParseIP(ingress.IP), info.portal.port, false}, info.protocol, proxier.listenIP, info.proxyPort, service)...) + } + } + if info.nodePort != 0 { + el = append(el, proxier.closeNodePort(info.nodePort, info.protocol, proxier.listenIP, info.proxyPort, service)...) + } + if len(el) == 0 { + glog.V(3).Infof("Closed iptables portals for service %q", service) + } else { + glog.Errorf("Some errors closing iptables portals for service %q", service) + } + return utilerrors.NewAggregate(el) +} + +func (proxier *Proxier) closeOnePortal(portal portal, protocol api.Protocol, proxyIP net.IP, proxyPort int, name proxy.ServicePortName) []error { + el := []error{} + + if local, err := isLocalIP(portal.ip); err != nil { + el = append(el, fmt.Errorf("can't determine if IP is local, assuming not: %v", err)) + } else if local { + if err := proxier.releaseNodePort(portal.ip, portal.port, protocol, name); err != nil { + el = append(el, err) + } + } + + // Handle traffic from containers. + args := proxier.iptablesContainerPortalArgs(portal.ip, portal.isExternal, false, portal.port, protocol, proxyIP, proxyPort, name) + if err := proxier.iptables.DeleteRule(iptables.TableNAT, iptablesContainerPortalChain, args...); err != nil { + glog.Errorf("Failed to delete iptables %s rule for service %q", iptablesContainerPortalChain, name) + el = append(el, err) + } + + if portal.isExternal { + args := proxier.iptablesContainerPortalArgs(portal.ip, false, true, portal.port, protocol, proxyIP, proxyPort, name) + if err := proxier.iptables.DeleteRule(iptables.TableNAT, iptablesContainerPortalChain, args...); err != nil { + glog.Errorf("Failed to delete iptables %s rule for service %q", iptablesContainerPortalChain, name) + el = append(el, err) + } + + args = proxier.iptablesHostPortalArgs(portal.ip, true, portal.port, protocol, proxyIP, proxyPort, name) + if err := proxier.iptables.DeleteRule(iptables.TableNAT, iptablesHostPortalChain, args...); err != nil { + glog.Errorf("Failed to delete iptables %s rule for service %q", iptablesHostPortalChain, name) + el = append(el, err) + } + return el + } + + // Handle traffic from the host (portalIP is not external). + args = proxier.iptablesHostPortalArgs(portal.ip, false, portal.port, protocol, proxyIP, proxyPort, name) + if err := proxier.iptables.DeleteRule(iptables.TableNAT, iptablesHostPortalChain, args...); err != nil { + glog.Errorf("Failed to delete iptables %s rule for service %q", iptablesHostPortalChain, name) + el = append(el, err) + } + + return el +} + +func (proxier *Proxier) closeNodePort(nodePort int, protocol api.Protocol, proxyIP net.IP, proxyPort int, name proxy.ServicePortName) []error { + el := []error{} + + // Handle traffic from containers. + args := proxier.iptablesContainerNodePortArgs(nodePort, protocol, proxyIP, proxyPort, name) + if err := proxier.iptables.DeleteRule(iptables.TableNAT, iptablesContainerNodePortChain, args...); err != nil { + glog.Errorf("Failed to delete iptables %s rule for service %q", iptablesContainerNodePortChain, name) + el = append(el, err) + } + + // Handle traffic from the host. + args = proxier.iptablesHostNodePortArgs(nodePort, protocol, proxyIP, proxyPort, name) + if err := proxier.iptables.DeleteRule(iptables.TableNAT, iptablesHostNodePortChain, args...); err != nil { + glog.Errorf("Failed to delete iptables %s rule for service %q", iptablesHostNodePortChain, name) + el = append(el, err) + } + + // Handle traffic not local to the host + args = proxier.iptablesNonLocalNodePortArgs(nodePort, protocol, proxyIP, proxyPort, name) + if err := proxier.iptables.DeleteRule(iptables.TableFilter, iptablesNonLocalNodePortChain, args...); err != nil { + glog.Errorf("Failed to delete iptables %s rule for service %q", iptablesNonLocalNodePortChain, name) + el = append(el, err) + } + + if err := proxier.releaseNodePort(nil, nodePort, protocol, name); err != nil { + el = append(el, err) + } + + return el +} + +func isLocalIP(ip net.IP) (bool, error) { + addrs, err := net.InterfaceAddrs() + if err != nil { + return false, err + } + for i := range addrs { + intf, _, err := net.ParseCIDR(addrs[i].String()) + if err != nil { + return false, err + } + if ip.Equal(intf) { + return true, nil + } + } + return false, nil +} + +// See comments in the *PortalArgs() functions for some details about why we +// use two chains for portals. +var iptablesContainerPortalChain iptables.Chain = "KUBE-PORTALS-CONTAINER" +var iptablesHostPortalChain iptables.Chain = "KUBE-PORTALS-HOST" + +// Chains for NodePort services +var iptablesContainerNodePortChain iptables.Chain = "KUBE-NODEPORT-CONTAINER" +var iptablesHostNodePortChain iptables.Chain = "KUBE-NODEPORT-HOST" +var iptablesNonLocalNodePortChain iptables.Chain = "KUBE-NODEPORT-NON-LOCAL" + +// Ensure that the iptables infrastructure we use is set up. This can safely be called periodically. +func iptablesInit(ipt iptables.Interface) error { + // TODO: There is almost certainly room for optimization here. E.g. If + // we knew the service-cluster-ip-range CIDR we could fast-track outbound packets not + // destined for a service. There's probably more, help wanted. + + // Danger - order of these rules matters here: + // + // We match portal rules first, then NodePort rules. For NodePort rules, we filter primarily on --dst-type LOCAL, + // because we want to listen on all local addresses, but don't match internet traffic with the same dst port number. + // + // There is one complication (per thockin): + // -m addrtype --dst-type LOCAL is what we want except that it is broken (by intent without foresight to our usecase) + // on at least GCE. Specifically, GCE machines have a daemon which learns what external IPs are forwarded to that + // machine, and configure a local route for that IP, making a match for --dst-type LOCAL when we don't want it to. + // Removing the route gives correct behavior until the daemon recreates it. + // Killing the daemon is an option, but means that any non-kubernetes use of the machine with external IP will be broken. + // + // This applies to IPs on GCE that are actually from a load-balancer; they will be categorized as LOCAL. + // _If_ the chains were in the wrong order, and the LB traffic had dst-port == a NodePort on some other service, + // the NodePort would take priority (incorrectly). + // This is unlikely (and would only affect outgoing traffic from the cluster to the load balancer, which seems + // doubly-unlikely), but we need to be careful to keep the rules in the right order. + args := []string{ /* service-cluster-ip-range matching could go here */ } + args = append(args, "-m", "comment", "--comment", "handle ClusterIPs; NOTE: this must be before the NodePort rules") + if _, err := ipt.EnsureChain(iptables.TableNAT, iptablesContainerPortalChain); err != nil { + return err + } + if _, err := ipt.EnsureRule(iptables.Prepend, iptables.TableNAT, iptables.ChainPrerouting, append(args, "-j", string(iptablesContainerPortalChain))...); err != nil { + return err + } + if _, err := ipt.EnsureChain(iptables.TableNAT, iptablesHostPortalChain); err != nil { + return err + } + if _, err := ipt.EnsureRule(iptables.Prepend, iptables.TableNAT, iptables.ChainOutput, append(args, "-j", string(iptablesHostPortalChain))...); err != nil { + return err + } + + // This set of rules matches broadly (addrtype & destination port), and therefore must come after the portal rules + args = []string{"-m", "addrtype", "--dst-type", "LOCAL"} + args = append(args, "-m", "comment", "--comment", "handle service NodePorts; NOTE: this must be the last rule in the chain") + if _, err := ipt.EnsureChain(iptables.TableNAT, iptablesContainerNodePortChain); err != nil { + return err + } + if _, err := ipt.EnsureRule(iptables.Append, iptables.TableNAT, iptables.ChainPrerouting, append(args, "-j", string(iptablesContainerNodePortChain))...); err != nil { + return err + } + if _, err := ipt.EnsureChain(iptables.TableNAT, iptablesHostNodePortChain); err != nil { + return err + } + if _, err := ipt.EnsureRule(iptables.Append, iptables.TableNAT, iptables.ChainOutput, append(args, "-j", string(iptablesHostNodePortChain))...); err != nil { + return err + } + + // Create a chain intended to explicitly allow non-local NodePort + // traffic to work around default-deny iptables configurations + // that would otherwise reject such traffic. + args = []string{"-m", "comment", "--comment", "Ensure that non-local NodePort traffic can flow"} + if _, err := ipt.EnsureChain(iptables.TableFilter, iptablesNonLocalNodePortChain); err != nil { + return err + } + if _, err := ipt.EnsureRule(iptables.Prepend, iptables.TableFilter, iptables.ChainInput, append(args, "-j", string(iptablesNonLocalNodePortChain))...); err != nil { + return err + } + + // TODO: Verify order of rules. + return nil +} + +// Flush all of our custom iptables rules. +func iptablesFlush(ipt iptables.Interface) error { + el := []error{} + if err := ipt.FlushChain(iptables.TableNAT, iptablesContainerPortalChain); err != nil { + el = append(el, err) + } + if err := ipt.FlushChain(iptables.TableNAT, iptablesHostPortalChain); err != nil { + el = append(el, err) + } + if err := ipt.FlushChain(iptables.TableNAT, iptablesContainerNodePortChain); err != nil { + el = append(el, err) + } + if err := ipt.FlushChain(iptables.TableNAT, iptablesHostNodePortChain); err != nil { + el = append(el, err) + } + if err := ipt.FlushChain(iptables.TableFilter, iptablesNonLocalNodePortChain); err != nil { + el = append(el, err) + } + if len(el) != 0 { + glog.Errorf("Some errors flushing old iptables portals: %v", el) + } + return utilerrors.NewAggregate(el) +} + +// Used below. +var zeroIPv4 = net.ParseIP("0.0.0.0") +var localhostIPv4 = net.ParseIP("127.0.0.1") + +var zeroIPv6 = net.ParseIP("::0") +var localhostIPv6 = net.ParseIP("::1") + +// Build a slice of iptables args that are common to from-container and from-host portal rules. +func iptablesCommonPortalArgs(destIP net.IP, addPhysicalInterfaceMatch bool, addDstLocalMatch bool, destPort int, protocol api.Protocol, service proxy.ServicePortName) []string { + // This list needs to include all fields as they are eventually spit out + // by iptables-save. This is because some systems do not support the + // 'iptables -C' arg, and so fall back on parsing iptables-save output. + // If this does not match, it will not pass the check. For example: + // adding the /32 on the destination IP arg is not strictly required, + // but causes this list to not match the final iptables-save output. + // This is fragile and I hope one day we can stop supporting such old + // iptables versions. + args := []string{ + "-m", "comment", + "--comment", service.String(), + "-p", strings.ToLower(string(protocol)), + "-m", strings.ToLower(string(protocol)), + "--dport", fmt.Sprintf("%d", destPort), + } + + if destIP != nil { + args = append(args, "-d", fmt.Sprintf("%s/32", destIP.String())) + } + + if addPhysicalInterfaceMatch { + args = append(args, "-m", "physdev", "!", "--physdev-is-in") + } + + if addDstLocalMatch { + args = append(args, "-m", "addrtype", "--dst-type", "LOCAL") + } + + return args +} + +// Build a slice of iptables args for a from-container portal rule. +func (proxier *Proxier) iptablesContainerPortalArgs(destIP net.IP, addPhysicalInterfaceMatch bool, addDstLocalMatch bool, destPort int, protocol api.Protocol, proxyIP net.IP, proxyPort int, service proxy.ServicePortName) []string { + args := iptablesCommonPortalArgs(destIP, addPhysicalInterfaceMatch, addDstLocalMatch, destPort, protocol, service) + + // This is tricky. + // + // If the proxy is bound (see Proxier.listenIP) to 0.0.0.0 ("any + // interface") we want to use REDIRECT, which sends traffic to the + // "primary address of the incoming interface" which means the container + // bridge, if there is one. When the response comes, it comes from that + // same interface, so the NAT matches and the response packet is + // correct. This matters for UDP, since there is no per-connection port + // number. + // + // The alternative would be to use DNAT, except that it doesn't work + // (empirically): + // * DNAT to 127.0.0.1 = Packets just disappear - this seems to be a + // well-known limitation of iptables. + // * DNAT to eth0's IP = Response packets come from the bridge, which + // breaks the NAT, and makes things like DNS not accept them. If + // this could be resolved, it would simplify all of this code. + // + // If the proxy is bound to a specific IP, then we have to use DNAT to + // that IP. Unlike the previous case, this works because the proxy is + // ONLY listening on that IP, not the bridge. + // + // Why would anyone bind to an address that is not inclusive of + // localhost? Apparently some cloud environments have their public IP + // exposed as a real network interface AND do not have firewalling. We + // don't want to expose everything out to the world. + // + // Unfortunately, I don't know of any way to listen on some (N > 1) + // interfaces but not ALL interfaces, short of doing it manually, and + // this is simpler than that. + // + // If the proxy is bound to localhost only, all of this is broken. Not + // allowed. + if proxyIP.Equal(zeroIPv4) || proxyIP.Equal(zeroIPv6) { + // TODO: Can we REDIRECT with IPv6? + args = append(args, "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", proxyPort)) + } else { + // TODO: Can we DNAT with IPv6? + args = append(args, "-j", "DNAT", "--to-destination", net.JoinHostPort(proxyIP.String(), strconv.Itoa(proxyPort))) + } + return args +} + +// Build a slice of iptables args for a from-host portal rule. +func (proxier *Proxier) iptablesHostPortalArgs(destIP net.IP, addDstLocalMatch bool, destPort int, protocol api.Protocol, proxyIP net.IP, proxyPort int, service proxy.ServicePortName) []string { + args := iptablesCommonPortalArgs(destIP, false, addDstLocalMatch, destPort, protocol, service) + + // This is tricky. + // + // If the proxy is bound (see Proxier.listenIP) to 0.0.0.0 ("any + // interface") we want to do the same as from-container traffic and use + // REDIRECT. Except that it doesn't work (empirically). REDIRECT on + // local packets sends the traffic to localhost (special case, but it is + // documented) but the response comes from the eth0 IP (not sure why, + // truthfully), which makes DNS unhappy. + // + // So we have to use DNAT. DNAT to 127.0.0.1 can't work for the same + // reason. + // + // So we do our best to find an interface that is not a loopback and + // DNAT to that. This works (again, empirically). + // + // If the proxy is bound to a specific IP, then we have to use DNAT to + // that IP. Unlike the previous case, this works because the proxy is + // ONLY listening on that IP, not the bridge. + // + // If the proxy is bound to localhost only, this should work, but we + // don't allow it for now. + if proxyIP.Equal(zeroIPv4) || proxyIP.Equal(zeroIPv6) { + proxyIP = proxier.hostIP + } + // TODO: Can we DNAT with IPv6? + args = append(args, "-j", "DNAT", "--to-destination", net.JoinHostPort(proxyIP.String(), strconv.Itoa(proxyPort))) + return args +} + +// Build a slice of iptables args for a from-container public-port rule. +// See iptablesContainerPortalArgs +// TODO: Should we just reuse iptablesContainerPortalArgs? +func (proxier *Proxier) iptablesContainerNodePortArgs(nodePort int, protocol api.Protocol, proxyIP net.IP, proxyPort int, service proxy.ServicePortName) []string { + args := iptablesCommonPortalArgs(nil, false, false, nodePort, protocol, service) + + if proxyIP.Equal(zeroIPv4) || proxyIP.Equal(zeroIPv6) { + // TODO: Can we REDIRECT with IPv6? + args = append(args, "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", proxyPort)) + } else { + // TODO: Can we DNAT with IPv6? + args = append(args, "-j", "DNAT", "--to-destination", net.JoinHostPort(proxyIP.String(), strconv.Itoa(proxyPort))) + } + + return args +} + +// Build a slice of iptables args for a from-host public-port rule. +// See iptablesHostPortalArgs +// TODO: Should we just reuse iptablesHostPortalArgs? +func (proxier *Proxier) iptablesHostNodePortArgs(nodePort int, protocol api.Protocol, proxyIP net.IP, proxyPort int, service proxy.ServicePortName) []string { + args := iptablesCommonPortalArgs(nil, false, false, nodePort, protocol, service) + + if proxyIP.Equal(zeroIPv4) || proxyIP.Equal(zeroIPv6) { + proxyIP = proxier.hostIP + } + // TODO: Can we DNAT with IPv6? + args = append(args, "-j", "DNAT", "--to-destination", net.JoinHostPort(proxyIP.String(), strconv.Itoa(proxyPort))) + return args +} + +// Build a slice of iptables args for an from-non-local public-port rule. +func (proxier *Proxier) iptablesNonLocalNodePortArgs(nodePort int, protocol api.Protocol, proxyIP net.IP, proxyPort int, service proxy.ServicePortName) []string { + args := iptablesCommonPortalArgs(nil, false, false, proxyPort, protocol, service) + args = append(args, "-m", "comment", "--comment", service.String(), "-m", "state", "--state", "NEW", "-j", "ACCEPT") + return args +} + +func isTooManyFDsError(err error) bool { + return strings.Contains(err.Error(), "too many open files") +} + +func isClosedError(err error) bool { + // A brief discussion about handling closed error here: + // https://code.google.com/p/go/issues/detail?id=4373#c14 + // TODO: maybe create a stoppable TCP listener that returns a StoppedError + return strings.HasSuffix(err.Error(), "use of closed network connection") +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/userspace/proxier_test.go b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/proxier_test.go new file mode 100644 index 000000000..34b01a9fc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/proxier_test.go @@ -0,0 +1,844 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 userspace + +import ( + "fmt" + "io/ioutil" + "net" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "sync/atomic" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/proxy" + "k8s.io/kubernetes/pkg/types" + ipttest "k8s.io/kubernetes/pkg/util/iptables/testing" + "k8s.io/kubernetes/pkg/util/runtime" +) + +const ( + udpIdleTimeoutForTest = 250 * time.Millisecond +) + +func joinHostPort(host string, port int) string { + return net.JoinHostPort(host, fmt.Sprintf("%d", port)) +} + +func waitForClosedPortTCP(p *Proxier, proxyPort int) error { + for i := 0; i < 50; i++ { + conn, err := net.Dial("tcp", joinHostPort("", proxyPort)) + if err != nil { + return nil + } + conn.Close() + time.Sleep(1 * time.Millisecond) + } + return fmt.Errorf("port %d still open", proxyPort) +} + +func waitForClosedPortUDP(p *Proxier, proxyPort int) error { + for i := 0; i < 50; i++ { + conn, err := net.Dial("udp", joinHostPort("", proxyPort)) + if err != nil { + return nil + } + conn.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) + // To detect a closed UDP port write, then read. + _, err = conn.Write([]byte("x")) + if err != nil { + if e, ok := err.(net.Error); ok && !e.Timeout() { + return nil + } + } + var buf [4]byte + _, err = conn.Read(buf[0:]) + if err != nil { + if e, ok := err.(net.Error); ok && !e.Timeout() { + return nil + } + } + conn.Close() + time.Sleep(1 * time.Millisecond) + } + return fmt.Errorf("port %d still open", proxyPort) +} + +var tcpServerPort int +var udpServerPort int + +func init() { + // Don't handle panics + runtime.ReallyCrash = true + + // TCP setup. + // TODO: Close() this when fix #19254 + tcp := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(r.URL.Path[1:])) + })) + u, err := url.Parse(tcp.URL) + if err != nil { + panic(fmt.Sprintf("failed to parse: %v", err)) + } + _, port, err := net.SplitHostPort(u.Host) + if err != nil { + panic(fmt.Sprintf("failed to parse: %v", err)) + } + tcpServerPort, err = strconv.Atoi(port) + if err != nil { + panic(fmt.Sprintf("failed to atoi(%s): %v", port, err)) + } + + // UDP setup. + udp, err := newUDPEchoServer() + if err != nil { + panic(fmt.Sprintf("failed to make a UDP server: %v", err)) + } + _, port, err = net.SplitHostPort(udp.LocalAddr().String()) + if err != nil { + panic(fmt.Sprintf("failed to parse: %v", err)) + } + udpServerPort, err = strconv.Atoi(port) + if err != nil { + panic(fmt.Sprintf("failed to atoi(%s): %v", port, err)) + } + go udp.Loop() +} + +func testEchoTCP(t *testing.T, address string, port int) { + path := "aaaaa" + res, err := http.Get("http://" + address + ":" + fmt.Sprintf("%d", port) + "/" + path) + if err != nil { + t.Fatalf("error connecting to server: %v", err) + } + defer res.Body.Close() + data, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Errorf("error reading data: %v %v", err, string(data)) + } + if string(data) != path { + t.Errorf("expected: %s, got %s", path, string(data)) + } +} + +func testEchoUDP(t *testing.T, address string, port int) { + data := "abc123" + + conn, err := net.Dial("udp", joinHostPort(address, port)) + if err != nil { + t.Fatalf("error connecting to server: %v", err) + } + if _, err := conn.Write([]byte(data)); err != nil { + t.Fatalf("error sending to server: %v", err) + } + var resp [1024]byte + n, err := conn.Read(resp[0:]) + if err != nil { + t.Errorf("error receiving data: %v", err) + } + if string(resp[0:n]) != data { + t.Errorf("expected: %s, got %s", data, string(resp[0:n])) + } +} + +func waitForNumProxyLoops(t *testing.T, p *Proxier, want int32) { + var got int32 + for i := 0; i < 600; i++ { + got = atomic.LoadInt32(&p.numProxyLoops) + if got == want { + return + } + time.Sleep(100 * time.Millisecond) + } + t.Errorf("expected %d ProxyLoops running, got %d", want, got) +} + +func waitForNumProxyClients(t *testing.T, s *serviceInfo, want int, timeout time.Duration) { + var got int + now := time.Now() + deadline := now.Add(timeout) + for time.Now().Before(deadline) { + s.activeClients.mu.Lock() + got = len(s.activeClients.clients) + s.activeClients.mu.Unlock() + if got == want { + return + } + time.Sleep(500 * time.Millisecond) + } + t.Errorf("expected %d ProxyClients live, got %d", want, got) +} + +func TestTCPProxy(t *testing.T) { + lb := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "p"} + lb.OnEndpointsUpdate([]api.Endpoints{ + { + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: tcpServerPort}}, + }}, + }, + }) + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + svcInfo, err := p.addServiceOnPort(service, "TCP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + testEchoTCP(t, "127.0.0.1", svcInfo.proxyPort) + waitForNumProxyLoops(t, p, 1) +} + +func TestUDPProxy(t *testing.T) { + lb := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "p"} + lb.OnEndpointsUpdate([]api.Endpoints{ + { + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: udpServerPort}}, + }}, + }, + }) + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + svcInfo, err := p.addServiceOnPort(service, "UDP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + testEchoUDP(t, "127.0.0.1", svcInfo.proxyPort) + waitForNumProxyLoops(t, p, 1) +} + +func TestUDPProxyTimeout(t *testing.T) { + lb := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "p"} + lb.OnEndpointsUpdate([]api.Endpoints{ + { + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: udpServerPort}}, + }}, + }, + }) + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + svcInfo, err := p.addServiceOnPort(service, "UDP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + waitForNumProxyLoops(t, p, 1) + testEchoUDP(t, "127.0.0.1", svcInfo.proxyPort) + // When connecting to a UDP service endpoint, there shoule be a Conn for proxy. + waitForNumProxyClients(t, svcInfo, 1, time.Second) + // If conn has no activity for serviceInfo.timeout since last Read/Write, it shoule be closed because of timeout. + waitForNumProxyClients(t, svcInfo, 0, 2*time.Second) +} + +func TestMultiPortProxy(t *testing.T) { + lb := NewLoadBalancerRR() + serviceP := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo-p"}, Port: "p"} + serviceQ := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo-q"}, Port: "q"} + lb.OnEndpointsUpdate([]api.Endpoints{{ + ObjectMeta: api.ObjectMeta{Name: serviceP.Name, Namespace: serviceP.Namespace}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "p", Protocol: "TCP", Port: tcpServerPort}}, + }}, + }, { + ObjectMeta: api.ObjectMeta{Name: serviceQ.Name, Namespace: serviceQ.Namespace}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "q", Protocol: "UDP", Port: udpServerPort}}, + }}, + }}) + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + svcInfoP, err := p.addServiceOnPort(serviceP, "TCP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + testEchoTCP(t, "127.0.0.1", svcInfoP.proxyPort) + waitForNumProxyLoops(t, p, 1) + + svcInfoQ, err := p.addServiceOnPort(serviceQ, "UDP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + testEchoUDP(t, "127.0.0.1", svcInfoQ.proxyPort) + waitForNumProxyLoops(t, p, 2) +} + +func TestMultiPortOnServiceUpdate(t *testing.T) { + lb := NewLoadBalancerRR() + serviceP := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "p"} + serviceQ := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "q"} + serviceX := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "x"} + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + p.OnServiceUpdate([]api.Service{{ + ObjectMeta: api.ObjectMeta{Name: serviceP.Name, Namespace: serviceP.Namespace}, + Spec: api.ServiceSpec{ClusterIP: "1.2.3.4", Ports: []api.ServicePort{{ + Name: "p", + Port: 80, + Protocol: "TCP", + }, { + Name: "q", + Port: 81, + Protocol: "UDP", + }}}, + }}) + waitForNumProxyLoops(t, p, 2) + svcInfo, exists := p.getServiceInfo(serviceP) + if !exists { + t.Fatalf("can't find serviceInfo for %s", serviceP) + } + if svcInfo.portal.ip.String() != "1.2.3.4" || svcInfo.portal.port != 80 || svcInfo.protocol != "TCP" { + t.Errorf("unexpected serviceInfo for %s: %#v", serviceP, svcInfo) + } + + svcInfo, exists = p.getServiceInfo(serviceQ) + if !exists { + t.Fatalf("can't find serviceInfo for %s", serviceQ) + } + if svcInfo.portal.ip.String() != "1.2.3.4" || svcInfo.portal.port != 81 || svcInfo.protocol != "UDP" { + t.Errorf("unexpected serviceInfo for %s: %#v", serviceQ, svcInfo) + } + + svcInfo, exists = p.getServiceInfo(serviceX) + if exists { + t.Fatalf("found unwanted serviceInfo for %s: %#v", serviceX, svcInfo) + } +} + +// Helper: Stops the proxy for the named service. +func stopProxyByName(proxier *Proxier, service proxy.ServicePortName) error { + info, found := proxier.getServiceInfo(service) + if !found { + return fmt.Errorf("unknown service: %s", service) + } + return proxier.stopProxy(service, info) +} + +func TestTCPProxyStop(t *testing.T) { + lb := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "p"} + lb.OnEndpointsUpdate([]api.Endpoints{ + { + ObjectMeta: api.ObjectMeta{Namespace: service.Namespace, Name: service.Name}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: tcpServerPort}}, + }}, + }, + }) + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + svcInfo, err := p.addServiceOnPort(service, "TCP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + if !svcInfo.isAlive() { + t.Fatalf("wrong value for isAlive(): expected true") + } + conn, err := net.Dial("tcp", joinHostPort("", svcInfo.proxyPort)) + if err != nil { + t.Fatalf("error connecting to proxy: %v", err) + } + conn.Close() + waitForNumProxyLoops(t, p, 1) + + stopProxyByName(p, service) + if svcInfo.isAlive() { + t.Fatalf("wrong value for isAlive(): expected false") + } + // Wait for the port to really close. + if err := waitForClosedPortTCP(p, svcInfo.proxyPort); err != nil { + t.Fatalf(err.Error()) + } + waitForNumProxyLoops(t, p, 0) +} + +func TestUDPProxyStop(t *testing.T) { + lb := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "p"} + lb.OnEndpointsUpdate([]api.Endpoints{ + { + ObjectMeta: api.ObjectMeta{Namespace: service.Namespace, Name: service.Name}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: udpServerPort}}, + }}, + }, + }) + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + svcInfo, err := p.addServiceOnPort(service, "UDP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + conn, err := net.Dial("udp", joinHostPort("", svcInfo.proxyPort)) + if err != nil { + t.Fatalf("error connecting to proxy: %v", err) + } + conn.Close() + waitForNumProxyLoops(t, p, 1) + + stopProxyByName(p, service) + // Wait for the port to really close. + if err := waitForClosedPortUDP(p, svcInfo.proxyPort); err != nil { + t.Fatalf(err.Error()) + } + waitForNumProxyLoops(t, p, 0) +} + +func TestTCPProxyUpdateDelete(t *testing.T) { + lb := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "p"} + lb.OnEndpointsUpdate([]api.Endpoints{ + { + ObjectMeta: api.ObjectMeta{Namespace: service.Namespace, Name: service.Name}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: tcpServerPort}}, + }}, + }, + }) + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + svcInfo, err := p.addServiceOnPort(service, "TCP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + conn, err := net.Dial("tcp", joinHostPort("", svcInfo.proxyPort)) + if err != nil { + t.Fatalf("error connecting to proxy: %v", err) + } + conn.Close() + waitForNumProxyLoops(t, p, 1) + + p.OnServiceUpdate([]api.Service{}) + if err := waitForClosedPortTCP(p, svcInfo.proxyPort); err != nil { + t.Fatalf(err.Error()) + } + waitForNumProxyLoops(t, p, 0) +} + +func TestUDPProxyUpdateDelete(t *testing.T) { + lb := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "p"} + lb.OnEndpointsUpdate([]api.Endpoints{ + { + ObjectMeta: api.ObjectMeta{Namespace: service.Namespace, Name: service.Name}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: udpServerPort}}, + }}, + }, + }) + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + svcInfo, err := p.addServiceOnPort(service, "UDP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + conn, err := net.Dial("udp", joinHostPort("", svcInfo.proxyPort)) + if err != nil { + t.Fatalf("error connecting to proxy: %v", err) + } + conn.Close() + waitForNumProxyLoops(t, p, 1) + + p.OnServiceUpdate([]api.Service{}) + if err := waitForClosedPortUDP(p, svcInfo.proxyPort); err != nil { + t.Fatalf(err.Error()) + } + waitForNumProxyLoops(t, p, 0) +} + +func TestTCPProxyUpdateDeleteUpdate(t *testing.T) { + lb := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "p"} + lb.OnEndpointsUpdate([]api.Endpoints{ + { + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: tcpServerPort}}, + }}, + }, + }) + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + svcInfo, err := p.addServiceOnPort(service, "TCP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + conn, err := net.Dial("tcp", joinHostPort("", svcInfo.proxyPort)) + if err != nil { + t.Fatalf("error connecting to proxy: %v", err) + } + conn.Close() + waitForNumProxyLoops(t, p, 1) + + p.OnServiceUpdate([]api.Service{}) + if err := waitForClosedPortTCP(p, svcInfo.proxyPort); err != nil { + t.Fatalf(err.Error()) + } + waitForNumProxyLoops(t, p, 0) + + p.OnServiceUpdate([]api.Service{{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Spec: api.ServiceSpec{ClusterIP: "1.2.3.4", Ports: []api.ServicePort{{ + Name: "p", + Port: svcInfo.proxyPort, + Protocol: "TCP", + }}}, + }}) + svcInfo, exists := p.getServiceInfo(service) + if !exists { + t.Fatalf("can't find serviceInfo for %s", service) + } + testEchoTCP(t, "127.0.0.1", svcInfo.proxyPort) + waitForNumProxyLoops(t, p, 1) +} + +func TestUDPProxyUpdateDeleteUpdate(t *testing.T) { + lb := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "p"} + lb.OnEndpointsUpdate([]api.Endpoints{ + { + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: udpServerPort}}, + }}, + }, + }) + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + svcInfo, err := p.addServiceOnPort(service, "UDP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + conn, err := net.Dial("udp", joinHostPort("", svcInfo.proxyPort)) + if err != nil { + t.Fatalf("error connecting to proxy: %v", err) + } + conn.Close() + waitForNumProxyLoops(t, p, 1) + + p.OnServiceUpdate([]api.Service{}) + if err := waitForClosedPortUDP(p, svcInfo.proxyPort); err != nil { + t.Fatalf(err.Error()) + } + waitForNumProxyLoops(t, p, 0) + + p.OnServiceUpdate([]api.Service{{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Spec: api.ServiceSpec{ClusterIP: "1.2.3.4", Ports: []api.ServicePort{{ + Name: "p", + Port: svcInfo.proxyPort, + Protocol: "UDP", + }}}, + }}) + svcInfo, exists := p.getServiceInfo(service) + if !exists { + t.Fatalf("can't find serviceInfo") + } + testEchoUDP(t, "127.0.0.1", svcInfo.proxyPort) + waitForNumProxyLoops(t, p, 1) +} + +func TestTCPProxyUpdatePort(t *testing.T) { + lb := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "p"} + lb.OnEndpointsUpdate([]api.Endpoints{ + { + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: tcpServerPort}}, + }}, + }, + }) + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + svcInfo, err := p.addServiceOnPort(service, "TCP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + testEchoTCP(t, "127.0.0.1", svcInfo.proxyPort) + waitForNumProxyLoops(t, p, 1) + + p.OnServiceUpdate([]api.Service{{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Spec: api.ServiceSpec{ClusterIP: "1.2.3.4", Ports: []api.ServicePort{{ + Name: "p", + Port: 99, + Protocol: "TCP", + }}}, + }}) + // Wait for the socket to actually get free. + if err := waitForClosedPortTCP(p, svcInfo.proxyPort); err != nil { + t.Fatalf(err.Error()) + } + svcInfo, exists := p.getServiceInfo(service) + if !exists { + t.Fatalf("can't find serviceInfo") + } + testEchoTCP(t, "127.0.0.1", svcInfo.proxyPort) + // This is a bit async, but this should be sufficient. + time.Sleep(500 * time.Millisecond) + waitForNumProxyLoops(t, p, 1) +} + +func TestUDPProxyUpdatePort(t *testing.T) { + lb := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "p"} + lb.OnEndpointsUpdate([]api.Endpoints{ + { + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: udpServerPort}}, + }}, + }, + }) + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + svcInfo, err := p.addServiceOnPort(service, "UDP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + waitForNumProxyLoops(t, p, 1) + + p.OnServiceUpdate([]api.Service{{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Spec: api.ServiceSpec{ClusterIP: "1.2.3.4", Ports: []api.ServicePort{{ + Name: "p", + Port: 99, + Protocol: "UDP", + }}}, + }}) + // Wait for the socket to actually get free. + if err := waitForClosedPortUDP(p, svcInfo.proxyPort); err != nil { + t.Fatalf(err.Error()) + } + svcInfo, exists := p.getServiceInfo(service) + if !exists { + t.Fatalf("can't find serviceInfo") + } + testEchoUDP(t, "127.0.0.1", svcInfo.proxyPort) + waitForNumProxyLoops(t, p, 1) +} + +func TestProxyUpdatePublicIPs(t *testing.T) { + lb := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "p"} + lb.OnEndpointsUpdate([]api.Endpoints{ + { + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: tcpServerPort}}, + }}, + }, + }) + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + svcInfo, err := p.addServiceOnPort(service, "TCP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + testEchoTCP(t, "127.0.0.1", svcInfo.proxyPort) + waitForNumProxyLoops(t, p, 1) + + p.OnServiceUpdate([]api.Service{{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Spec: api.ServiceSpec{ + Ports: []api.ServicePort{{ + Name: "p", + Port: svcInfo.portal.port, + Protocol: "TCP", + }}, + ClusterIP: svcInfo.portal.ip.String(), + ExternalIPs: []string{"4.3.2.1"}, + }, + }}) + // Wait for the socket to actually get free. + if err := waitForClosedPortTCP(p, svcInfo.proxyPort); err != nil { + t.Fatalf(err.Error()) + } + svcInfo, exists := p.getServiceInfo(service) + if !exists { + t.Fatalf("can't find serviceInfo") + } + testEchoTCP(t, "127.0.0.1", svcInfo.proxyPort) + // This is a bit async, but this should be sufficient. + time.Sleep(500 * time.Millisecond) + waitForNumProxyLoops(t, p, 1) +} + +func TestProxyUpdatePortal(t *testing.T) { + lb := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "echo"}, Port: "p"} + lb.OnEndpointsUpdate([]api.Endpoints{ + { + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "127.0.0.1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: tcpServerPort}}, + }}, + }, + }) + + p, err := createProxier(lb, net.ParseIP("0.0.0.0"), ipttest.NewFake(), net.ParseIP("127.0.0.1"), nil, time.Minute, udpIdleTimeoutForTest) + if err != nil { + t.Fatal(err) + } + waitForNumProxyLoops(t, p, 0) + + svcInfo, err := p.addServiceOnPort(service, "TCP", 0, time.Second) + if err != nil { + t.Fatalf("error adding new service: %#v", err) + } + testEchoTCP(t, "127.0.0.1", svcInfo.proxyPort) + waitForNumProxyLoops(t, p, 1) + + p.OnServiceUpdate([]api.Service{{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Spec: api.ServiceSpec{ClusterIP: "", Ports: []api.ServicePort{{ + Name: "p", + Port: svcInfo.proxyPort, + Protocol: "TCP", + }}}, + }}) + _, exists := p.getServiceInfo(service) + if exists { + t.Fatalf("service with empty ClusterIP should not be included in the proxy") + } + + p.OnServiceUpdate([]api.Service{{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Spec: api.ServiceSpec{ClusterIP: "None", Ports: []api.ServicePort{{ + Name: "p", + Port: svcInfo.proxyPort, + Protocol: "TCP", + }}}, + }}) + _, exists = p.getServiceInfo(service) + if exists { + t.Fatalf("service with 'None' as ClusterIP should not be included in the proxy") + } + + p.OnServiceUpdate([]api.Service{{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Spec: api.ServiceSpec{ClusterIP: "1.2.3.4", Ports: []api.ServicePort{{ + Name: "p", + Port: svcInfo.proxyPort, + Protocol: "TCP", + }}}, + }}) + svcInfo, exists = p.getServiceInfo(service) + if !exists { + t.Fatalf("service with ClusterIP set not found in the proxy") + } + testEchoTCP(t, "127.0.0.1", svcInfo.proxyPort) + waitForNumProxyLoops(t, p, 1) +} + +// TODO(justinsb): Add test for nodePort conflict detection, once we have nodePort wired in diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/userspace/proxysocket.go b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/proxysocket.go new file mode 100644 index 000000000..97f42cc73 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/proxysocket.go @@ -0,0 +1,298 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 userspace + +import ( + "fmt" + "io" + "net" + "strconv" + "strings" + "sync" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/proxy" + "k8s.io/kubernetes/pkg/util/runtime" +) + +// Abstraction over TCP/UDP sockets which are proxied. +type proxySocket interface { + // Addr gets the net.Addr for a proxySocket. + Addr() net.Addr + // Close stops the proxySocket from accepting incoming connections. + // Each implementation should comment on the impact of calling Close + // while sessions are active. + Close() error + // ProxyLoop proxies incoming connections for the specified service to the service endpoints. + ProxyLoop(service proxy.ServicePortName, info *serviceInfo, proxier *Proxier) + // ListenPort returns the host port that the proxySocket is listening on + ListenPort() int +} + +func newProxySocket(protocol api.Protocol, ip net.IP, port int) (proxySocket, error) { + host := "" + if ip != nil { + host = ip.String() + } + + switch strings.ToUpper(string(protocol)) { + case "TCP": + listener, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port))) + if err != nil { + return nil, err + } + return &tcpProxySocket{Listener: listener, port: port}, nil + case "UDP": + addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(host, strconv.Itoa(port))) + if err != nil { + return nil, err + } + conn, err := net.ListenUDP("udp", addr) + if err != nil { + return nil, err + } + return &udpProxySocket{UDPConn: conn, port: port}, nil + } + return nil, fmt.Errorf("unknown protocol %q", protocol) +} + +// How long we wait for a connection to a backend in seconds +var endpointDialTimeout = []time.Duration{250 * time.Millisecond, 500 * time.Millisecond, 1 * time.Second, 2 * time.Second} + +// tcpProxySocket implements proxySocket. Close() is implemented by net.Listener. When Close() is called, +// no new connections are allowed but existing connections are left untouched. +type tcpProxySocket struct { + net.Listener + port int +} + +func (tcp *tcpProxySocket) ListenPort() int { + return tcp.port +} + +func tryConnect(service proxy.ServicePortName, srcAddr net.Addr, protocol string, proxier *Proxier) (out net.Conn, err error) { + for _, dialTimeout := range endpointDialTimeout { + endpoint, err := proxier.loadBalancer.NextEndpoint(service, srcAddr) + if err != nil { + glog.Errorf("Couldn't find an endpoint for %s: %v", service, err) + return nil, err + } + glog.V(3).Infof("Mapped service %q to endpoint %s", service, endpoint) + // TODO: This could spin up a new goroutine to make the outbound connection, + // and keep accepting inbound traffic. + outConn, err := net.DialTimeout(protocol, endpoint, dialTimeout) + if err != nil { + if isTooManyFDsError(err) { + panic("Dial failed: " + err.Error()) + } + glog.Errorf("Dial failed: %v", err) + continue + } + return outConn, nil + } + return nil, fmt.Errorf("failed to connect to an endpoint.") +} + +func (tcp *tcpProxySocket) ProxyLoop(service proxy.ServicePortName, myInfo *serviceInfo, proxier *Proxier) { + for { + if !myInfo.isAlive() { + // The service port was closed or replaced. + return + } + // Block until a connection is made. + inConn, err := tcp.Accept() + if err != nil { + if isTooManyFDsError(err) { + panic("Accept failed: " + err.Error()) + } + + if isClosedError(err) { + return + } + if !myInfo.isAlive() { + // Then the service port was just closed so the accept failure is to be expected. + return + } + glog.Errorf("Accept failed: %v", err) + continue + } + glog.V(3).Infof("Accepted TCP connection from %v to %v", inConn.RemoteAddr(), inConn.LocalAddr()) + outConn, err := tryConnect(service, inConn.(*net.TCPConn).RemoteAddr(), "tcp", proxier) + if err != nil { + glog.Errorf("Failed to connect to balancer: %v", err) + inConn.Close() + continue + } + // Spin up an async copy loop. + go proxyTCP(inConn.(*net.TCPConn), outConn.(*net.TCPConn)) + } +} + +// proxyTCP proxies data bi-directionally between in and out. +func proxyTCP(in, out *net.TCPConn) { + var wg sync.WaitGroup + wg.Add(2) + glog.V(4).Infof("Creating proxy between %v <-> %v <-> %v <-> %v", + in.RemoteAddr(), in.LocalAddr(), out.LocalAddr(), out.RemoteAddr()) + go copyBytes("from backend", in, out, &wg) + go copyBytes("to backend", out, in, &wg) + wg.Wait() +} + +func copyBytes(direction string, dest, src *net.TCPConn, wg *sync.WaitGroup) { + defer wg.Done() + glog.V(4).Infof("Copying %s: %s -> %s", direction, src.RemoteAddr(), dest.RemoteAddr()) + n, err := io.Copy(dest, src) + if err != nil { + if !isClosedError(err) { + glog.Errorf("I/O error: %v", err) + } + } + glog.V(4).Infof("Copied %d bytes %s: %s -> %s", n, direction, src.RemoteAddr(), dest.RemoteAddr()) + dest.Close() + src.Close() +} + +// udpProxySocket implements proxySocket. Close() is implemented by net.UDPConn. When Close() is called, +// no new connections are allowed and existing connections are broken. +// TODO: We could lame-duck this ourselves, if it becomes important. +type udpProxySocket struct { + *net.UDPConn + port int +} + +func (udp *udpProxySocket) ListenPort() int { + return udp.port +} + +func (udp *udpProxySocket) Addr() net.Addr { + return udp.LocalAddr() +} + +// Holds all the known UDP clients that have not timed out. +type clientCache struct { + mu sync.Mutex + clients map[string]net.Conn // addr string -> connection +} + +func newClientCache() *clientCache { + return &clientCache{clients: map[string]net.Conn{}} +} + +func (udp *udpProxySocket) ProxyLoop(service proxy.ServicePortName, myInfo *serviceInfo, proxier *Proxier) { + var buffer [4096]byte // 4KiB should be enough for most whole-packets + for { + if !myInfo.isAlive() { + // The service port was closed or replaced. + break + } + + // Block until data arrives. + // TODO: Accumulate a histogram of n or something, to fine tune the buffer size. + n, cliAddr, err := udp.ReadFrom(buffer[0:]) + if err != nil { + if e, ok := err.(net.Error); ok { + if e.Temporary() { + glog.V(1).Infof("ReadFrom had a temporary failure: %v", err) + continue + } + } + glog.Errorf("ReadFrom failed, exiting ProxyLoop: %v", err) + break + } + // If this is a client we know already, reuse the connection and goroutine. + svrConn, err := udp.getBackendConn(myInfo.activeClients, cliAddr, proxier, service, myInfo.timeout) + if err != nil { + continue + } + // TODO: It would be nice to let the goroutine handle this write, but we don't + // really want to copy the buffer. We could do a pool of buffers or something. + _, err = svrConn.Write(buffer[0:n]) + if err != nil { + if !logTimeout(err) { + glog.Errorf("Write failed: %v", err) + // TODO: Maybe tear down the goroutine for this client/server pair? + } + continue + } + err = svrConn.SetDeadline(time.Now().Add(myInfo.timeout)) + if err != nil { + glog.Errorf("SetDeadline failed: %v", err) + continue + } + } +} + +func (udp *udpProxySocket) getBackendConn(activeClients *clientCache, cliAddr net.Addr, proxier *Proxier, service proxy.ServicePortName, timeout time.Duration) (net.Conn, error) { + activeClients.mu.Lock() + defer activeClients.mu.Unlock() + + svrConn, found := activeClients.clients[cliAddr.String()] + if !found { + // TODO: This could spin up a new goroutine to make the outbound connection, + // and keep accepting inbound traffic. + glog.V(3).Infof("New UDP connection from %s", cliAddr) + var err error + svrConn, err = tryConnect(service, cliAddr, "udp", proxier) + if err != nil { + return nil, err + } + if err = svrConn.SetDeadline(time.Now().Add(timeout)); err != nil { + glog.Errorf("SetDeadline failed: %v", err) + return nil, err + } + activeClients.clients[cliAddr.String()] = svrConn + go func(cliAddr net.Addr, svrConn net.Conn, activeClients *clientCache, timeout time.Duration) { + defer runtime.HandleCrash() + udp.proxyClient(cliAddr, svrConn, activeClients, timeout) + }(cliAddr, svrConn, activeClients, timeout) + } + return svrConn, nil +} + +// This function is expected to be called as a goroutine. +// TODO: Track and log bytes copied, like TCP +func (udp *udpProxySocket) proxyClient(cliAddr net.Addr, svrConn net.Conn, activeClients *clientCache, timeout time.Duration) { + defer svrConn.Close() + var buffer [4096]byte + for { + n, err := svrConn.Read(buffer[0:]) + if err != nil { + if !logTimeout(err) { + glog.Errorf("Read failed: %v", err) + } + break + } + err = svrConn.SetDeadline(time.Now().Add(timeout)) + if err != nil { + glog.Errorf("SetDeadline failed: %v", err) + break + } + n, err = udp.WriteTo(buffer[0:n], cliAddr) + if err != nil { + if !logTimeout(err) { + glog.Errorf("WriteTo failed: %v", err) + } + break + } + } + activeClients.mu.Lock() + delete(activeClients.clients, cliAddr.String()) + activeClients.mu.Unlock() +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/userspace/rlimit.go b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/rlimit.go new file mode 100644 index 000000000..0f634e051 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/rlimit.go @@ -0,0 +1,25 @@ +// +build !windows + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 userspace + +import "syscall" + +func setRLimit(limit uint64) error { + return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &syscall.Rlimit{Max: limit, Cur: limit}) +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/userspace/rlimit_windows.go b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/rlimit_windows.go new file mode 100644 index 000000000..346ee18bb --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/rlimit_windows.go @@ -0,0 +1,23 @@ +// +build windows + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 userspace + +func setRLimit(limit uint64) error { + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/userspace/roundrobin.go b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/roundrobin.go new file mode 100644 index 000000000..6c28fc016 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/roundrobin.go @@ -0,0 +1,312 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 userspace + +import ( + "errors" + "fmt" + "net" + "reflect" + "strconv" + "sync" + "time" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/proxy" + "k8s.io/kubernetes/pkg/types" + "k8s.io/kubernetes/pkg/util/slice" +) + +var ( + ErrMissingServiceEntry = errors.New("missing service entry") + ErrMissingEndpoints = errors.New("missing endpoints") +) + +type affinityState struct { + clientIP string + //clientProtocol api.Protocol //not yet used + //sessionCookie string //not yet used + endpoint string + lastUsed time.Time +} + +type affinityPolicy struct { + affinityType api.ServiceAffinity + affinityMap map[string]*affinityState // map client IP -> affinity info + ttlMinutes int +} + +// LoadBalancerRR is a round-robin load balancer. +type LoadBalancerRR struct { + lock sync.RWMutex + services map[proxy.ServicePortName]*balancerState +} + +// Ensure this implements LoadBalancer. +var _ LoadBalancer = &LoadBalancerRR{} + +type balancerState struct { + endpoints []string // a list of "ip:port" style strings + index int // current index into endpoints + affinity affinityPolicy +} + +func newAffinityPolicy(affinityType api.ServiceAffinity, ttlMinutes int) *affinityPolicy { + return &affinityPolicy{ + affinityType: affinityType, + affinityMap: make(map[string]*affinityState), + ttlMinutes: ttlMinutes, + } +} + +// NewLoadBalancerRR returns a new LoadBalancerRR. +func NewLoadBalancerRR() *LoadBalancerRR { + return &LoadBalancerRR{ + services: map[proxy.ServicePortName]*balancerState{}, + } +} + +func (lb *LoadBalancerRR) NewService(svcPort proxy.ServicePortName, affinityType api.ServiceAffinity, ttlMinutes int) error { + lb.lock.Lock() + defer lb.lock.Unlock() + lb.newServiceInternal(svcPort, affinityType, ttlMinutes) + return nil +} + +// This assumes that lb.lock is already held. +func (lb *LoadBalancerRR) newServiceInternal(svcPort proxy.ServicePortName, affinityType api.ServiceAffinity, ttlMinutes int) *balancerState { + if ttlMinutes == 0 { + ttlMinutes = 180 //default to 3 hours if not specified. Should 0 be unlimited instead???? + } + + if _, exists := lb.services[svcPort]; !exists { + lb.services[svcPort] = &balancerState{affinity: *newAffinityPolicy(affinityType, ttlMinutes)} + glog.V(4).Infof("LoadBalancerRR service %q did not exist, created", svcPort) + } else if affinityType != "" { + lb.services[svcPort].affinity.affinityType = affinityType + } + return lb.services[svcPort] +} + +// return true if this service is using some form of session affinity. +func isSessionAffinity(affinity *affinityPolicy) bool { + // Should never be empty string, but checking for it to be safe. + if affinity.affinityType == "" || affinity.affinityType == api.ServiceAffinityNone { + return false + } + return true +} + +// NextEndpoint returns a service endpoint. +// The service endpoint is chosen using the round-robin algorithm. +func (lb *LoadBalancerRR) NextEndpoint(svcPort proxy.ServicePortName, srcAddr net.Addr) (string, error) { + // Coarse locking is simple. We can get more fine-grained if/when we + // can prove it matters. + lb.lock.Lock() + defer lb.lock.Unlock() + + state, exists := lb.services[svcPort] + if !exists || state == nil { + return "", ErrMissingServiceEntry + } + if len(state.endpoints) == 0 { + return "", ErrMissingEndpoints + } + glog.V(4).Infof("NextEndpoint for service %q, srcAddr=%v: endpoints: %+v", svcPort, srcAddr, state.endpoints) + + sessionAffinityEnabled := isSessionAffinity(&state.affinity) + + var ipaddr string + if sessionAffinityEnabled { + // Caution: don't shadow ipaddr + var err error + ipaddr, _, err = net.SplitHostPort(srcAddr.String()) + if err != nil { + return "", fmt.Errorf("malformed source address %q: %v", srcAddr.String(), err) + } + sessionAffinity, exists := state.affinity.affinityMap[ipaddr] + if exists && int(time.Now().Sub(sessionAffinity.lastUsed).Minutes()) < state.affinity.ttlMinutes { + // Affinity wins. + endpoint := sessionAffinity.endpoint + sessionAffinity.lastUsed = time.Now() + glog.V(4).Infof("NextEndpoint for service %q from IP %s with sessionAffinity %+v: %s", svcPort, ipaddr, sessionAffinity, endpoint) + return endpoint, nil + } + } + // Take the next endpoint. + endpoint := state.endpoints[state.index] + state.index = (state.index + 1) % len(state.endpoints) + + if sessionAffinityEnabled { + var affinity *affinityState + affinity = state.affinity.affinityMap[ipaddr] + if affinity == nil { + affinity = new(affinityState) //&affinityState{ipaddr, "TCP", "", endpoint, time.Now()} + state.affinity.affinityMap[ipaddr] = affinity + } + affinity.lastUsed = time.Now() + affinity.endpoint = endpoint + affinity.clientIP = ipaddr + glog.V(4).Infof("Updated affinity key %s: %+v", ipaddr, state.affinity.affinityMap[ipaddr]) + } + + return endpoint, nil +} + +type hostPortPair struct { + host string + port int +} + +func isValidEndpoint(hpp *hostPortPair) bool { + return hpp.host != "" && hpp.port > 0 +} + +func flattenValidEndpoints(endpoints []hostPortPair) []string { + // Convert Endpoint objects into strings for easier use later. Ignore + // the protocol field - we'll get that from the Service objects. + var result []string + for i := range endpoints { + hpp := &endpoints[i] + if isValidEndpoint(hpp) { + result = append(result, net.JoinHostPort(hpp.host, strconv.Itoa(hpp.port))) + } + } + return result +} + +// Remove any session affinity records associated to a particular endpoint (for example when a pod goes down). +func removeSessionAffinityByEndpoint(state *balancerState, svcPort proxy.ServicePortName, endpoint string) { + for _, affinity := range state.affinity.affinityMap { + if affinity.endpoint == endpoint { + glog.V(4).Infof("Removing client: %s from affinityMap for service %q", affinity.endpoint, svcPort) + delete(state.affinity.affinityMap, affinity.clientIP) + } + } +} + +// Loop through the valid endpoints and then the endpoints associated with the Load Balancer. +// Then remove any session affinity records that are not in both lists. +// This assumes the lb.lock is held. +func (lb *LoadBalancerRR) updateAffinityMap(svcPort proxy.ServicePortName, newEndpoints []string) { + allEndpoints := map[string]int{} + for _, newEndpoint := range newEndpoints { + allEndpoints[newEndpoint] = 1 + } + state, exists := lb.services[svcPort] + if !exists { + return + } + for _, existingEndpoint := range state.endpoints { + allEndpoints[existingEndpoint] = allEndpoints[existingEndpoint] + 1 + } + for mKey, mVal := range allEndpoints { + if mVal == 1 { + glog.V(2).Infof("Delete endpoint %s for service %q", mKey, svcPort) + removeSessionAffinityByEndpoint(state, svcPort, mKey) + } + } +} + +// OnEndpointsUpdate manages the registered service endpoints. +// Registered endpoints are updated if found in the update set or +// unregistered if missing from the update set. +func (lb *LoadBalancerRR) OnEndpointsUpdate(allEndpoints []api.Endpoints) { + registeredEndpoints := make(map[proxy.ServicePortName]bool) + lb.lock.Lock() + defer lb.lock.Unlock() + + // Update endpoints for services. + for i := range allEndpoints { + svcEndpoints := &allEndpoints[i] + + // We need to build a map of portname -> all ip:ports for that + // portname. Explode Endpoints.Subsets[*] into this structure. + portsToEndpoints := map[string][]hostPortPair{} + for i := range svcEndpoints.Subsets { + ss := &svcEndpoints.Subsets[i] + for i := range ss.Ports { + port := &ss.Ports[i] + for i := range ss.Addresses { + addr := &ss.Addresses[i] + portsToEndpoints[port.Name] = append(portsToEndpoints[port.Name], hostPortPair{addr.IP, port.Port}) + // Ignore the protocol field - we'll get that from the Service objects. + } + } + } + + for portname := range portsToEndpoints { + svcPort := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: svcEndpoints.Namespace, Name: svcEndpoints.Name}, Port: portname} + state, exists := lb.services[svcPort] + curEndpoints := []string{} + if state != nil { + curEndpoints = state.endpoints + } + newEndpoints := flattenValidEndpoints(portsToEndpoints[portname]) + + if !exists || state == nil || len(curEndpoints) != len(newEndpoints) || !slicesEquiv(slice.CopyStrings(curEndpoints), newEndpoints) { + glog.V(1).Infof("LoadBalancerRR: Setting endpoints for %s to %+v", svcPort, newEndpoints) + lb.updateAffinityMap(svcPort, newEndpoints) + // OnEndpointsUpdate can be called without NewService being called externally. + // To be safe we will call it here. A new service will only be created + // if one does not already exist. The affinity will be updated + // later, once NewService is called. + state = lb.newServiceInternal(svcPort, api.ServiceAffinity(""), 0) + state.endpoints = slice.ShuffleStrings(newEndpoints) + + // Reset the round-robin index. + state.index = 0 + } + registeredEndpoints[svcPort] = true + } + } + // Remove endpoints missing from the update. + for k := range lb.services { + if _, exists := registeredEndpoints[k]; !exists { + glog.V(2).Infof("LoadBalancerRR: Removing endpoints for %s", k) + delete(lb.services, k) + } + } +} + +// Tests whether two slices are equivalent. This sorts both slices in-place. +func slicesEquiv(lhs, rhs []string) bool { + if len(lhs) != len(rhs) { + return false + } + if reflect.DeepEqual(slice.SortStrings(lhs), slice.SortStrings(rhs)) { + return true + } + return false +} + +func (lb *LoadBalancerRR) CleanupStaleStickySessions(svcPort proxy.ServicePortName) { + lb.lock.Lock() + defer lb.lock.Unlock() + + state, exists := lb.services[svcPort] + if !exists { + return + } + for ip, affinity := range state.affinity.affinityMap { + if int(time.Now().Sub(affinity.lastUsed).Minutes()) >= state.affinity.ttlMinutes { + glog.V(4).Infof("Removing client %s from affinityMap for service %q", affinity.clientIP, svcPort) + delete(state.affinity.affinityMap, ip) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/userspace/roundrobin_test.go b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/roundrobin_test.go new file mode 100644 index 000000000..d04bb73af --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/roundrobin_test.go @@ -0,0 +1,665 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 userspace + +import ( + "net" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/proxy" + "k8s.io/kubernetes/pkg/types" +) + +func TestValidateWorks(t *testing.T) { + if isValidEndpoint(&hostPortPair{}) { + t.Errorf("Didn't fail for empty set") + } + if isValidEndpoint(&hostPortPair{host: "foobar"}) { + t.Errorf("Didn't fail with invalid port") + } + if isValidEndpoint(&hostPortPair{host: "foobar", port: -1}) { + t.Errorf("Didn't fail with a negative port") + } + if !isValidEndpoint(&hostPortPair{host: "foobar", port: 8080}) { + t.Errorf("Failed a valid config.") + } +} + +func TestFilterWorks(t *testing.T) { + endpoints := []hostPortPair{ + {host: "foobar", port: 1}, + {host: "foobar", port: 2}, + {host: "foobar", port: -1}, + {host: "foobar", port: 3}, + {host: "foobar", port: -2}, + } + filtered := flattenValidEndpoints(endpoints) + + if len(filtered) != 3 { + t.Errorf("Failed to filter to the correct size") + } + if filtered[0] != "foobar:1" { + t.Errorf("Index zero is not foobar:1") + } + if filtered[1] != "foobar:2" { + t.Errorf("Index one is not foobar:2") + } + if filtered[2] != "foobar:3" { + t.Errorf("Index two is not foobar:3") + } +} + +func TestLoadBalanceFailsWithNoEndpoints(t *testing.T) { + loadBalancer := NewLoadBalancerRR() + var endpoints []api.Endpoints + loadBalancer.OnEndpointsUpdate(endpoints) + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "foo"}, Port: "does-not-exist"} + endpoint, err := loadBalancer.NextEndpoint(service, nil) + if err == nil { + t.Errorf("Didn't fail with non-existent service") + } + if len(endpoint) != 0 { + t.Errorf("Got an endpoint") + } +} + +func expectEndpoint(t *testing.T, loadBalancer *LoadBalancerRR, service proxy.ServicePortName, expected string, netaddr net.Addr) { + endpoint, err := loadBalancer.NextEndpoint(service, netaddr) + if err != nil { + t.Errorf("Didn't find a service for %s, expected %s, failed with: %v", service, expected, err) + } + if endpoint != expected { + t.Errorf("Didn't get expected endpoint for service %s client %v, expected %s, got: %s", service, netaddr, expected, endpoint) + } +} + +func TestLoadBalanceWorksWithSingleEndpoint(t *testing.T) { + loadBalancer := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "foo"}, Port: "p"} + endpoint, err := loadBalancer.NextEndpoint(service, nil) + if err == nil || len(endpoint) != 0 { + t.Errorf("Didn't fail with non-existent service") + } + endpoints := make([]api.Endpoints, 1) + endpoints[0] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "endpoint1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: 40}}, + }}, + } + loadBalancer.OnEndpointsUpdate(endpoints) + expectEndpoint(t, loadBalancer, service, "endpoint1:40", nil) + expectEndpoint(t, loadBalancer, service, "endpoint1:40", nil) + expectEndpoint(t, loadBalancer, service, "endpoint1:40", nil) + expectEndpoint(t, loadBalancer, service, "endpoint1:40", nil) +} + +func stringsInSlice(haystack []string, needles ...string) bool { + for _, needle := range needles { + found := false + for i := range haystack { + if haystack[i] == needle { + found = true + break + } + } + if found == false { + return false + } + } + return true +} + +func TestLoadBalanceWorksWithMultipleEndpoints(t *testing.T) { + loadBalancer := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "foo"}, Port: "p"} + endpoint, err := loadBalancer.NextEndpoint(service, nil) + if err == nil || len(endpoint) != 0 { + t.Errorf("Didn't fail with non-existent service") + } + endpoints := make([]api.Endpoints, 1) + endpoints[0] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "endpoint"}}, + Ports: []api.EndpointPort{{Name: "p", Port: 1}, {Name: "p", Port: 2}, {Name: "p", Port: 3}}, + }}, + } + loadBalancer.OnEndpointsUpdate(endpoints) + + shuffledEndpoints := loadBalancer.services[service].endpoints + if !stringsInSlice(shuffledEndpoints, "endpoint:1", "endpoint:2", "endpoint:3") { + t.Errorf("did not find expected endpoints: %v", shuffledEndpoints) + } + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[0], nil) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[1], nil) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[2], nil) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[0], nil) +} + +func TestLoadBalanceWorksWithMultipleEndpointsMultiplePorts(t *testing.T) { + loadBalancer := NewLoadBalancerRR() + serviceP := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "foo"}, Port: "p"} + serviceQ := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "foo"}, Port: "q"} + endpoint, err := loadBalancer.NextEndpoint(serviceP, nil) + if err == nil || len(endpoint) != 0 { + t.Errorf("Didn't fail with non-existent service") + } + endpoints := make([]api.Endpoints, 1) + endpoints[0] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: serviceP.Name, Namespace: serviceP.Namespace}, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{{IP: "endpoint1"}, {IP: "endpoint2"}}, + Ports: []api.EndpointPort{{Name: "p", Port: 1}, {Name: "q", Port: 2}}, + }, + { + Addresses: []api.EndpointAddress{{IP: "endpoint3"}}, + Ports: []api.EndpointPort{{Name: "p", Port: 3}, {Name: "q", Port: 4}}, + }, + }, + } + loadBalancer.OnEndpointsUpdate(endpoints) + + shuffledEndpoints := loadBalancer.services[serviceP].endpoints + if !stringsInSlice(shuffledEndpoints, "endpoint1:1", "endpoint2:1", "endpoint3:3") { + t.Errorf("did not find expected endpoints: %v", shuffledEndpoints) + } + expectEndpoint(t, loadBalancer, serviceP, shuffledEndpoints[0], nil) + expectEndpoint(t, loadBalancer, serviceP, shuffledEndpoints[1], nil) + expectEndpoint(t, loadBalancer, serviceP, shuffledEndpoints[2], nil) + expectEndpoint(t, loadBalancer, serviceP, shuffledEndpoints[0], nil) + + shuffledEndpoints = loadBalancer.services[serviceQ].endpoints + if !stringsInSlice(shuffledEndpoints, "endpoint1:2", "endpoint2:2", "endpoint3:4") { + t.Errorf("did not find expected endpoints: %v", shuffledEndpoints) + } + expectEndpoint(t, loadBalancer, serviceQ, shuffledEndpoints[0], nil) + expectEndpoint(t, loadBalancer, serviceQ, shuffledEndpoints[1], nil) + expectEndpoint(t, loadBalancer, serviceQ, shuffledEndpoints[2], nil) + expectEndpoint(t, loadBalancer, serviceQ, shuffledEndpoints[0], nil) +} + +func TestLoadBalanceWorksWithMultipleEndpointsAndUpdates(t *testing.T) { + loadBalancer := NewLoadBalancerRR() + serviceP := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "foo"}, Port: "p"} + serviceQ := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "foo"}, Port: "q"} + endpoint, err := loadBalancer.NextEndpoint(serviceP, nil) + if err == nil || len(endpoint) != 0 { + t.Errorf("Didn't fail with non-existent service") + } + endpoints := make([]api.Endpoints, 1) + endpoints[0] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: serviceP.Name, Namespace: serviceP.Namespace}, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{{IP: "endpoint1"}}, + Ports: []api.EndpointPort{{Name: "p", Port: 1}, {Name: "q", Port: 10}}, + }, + { + Addresses: []api.EndpointAddress{{IP: "endpoint2"}}, + Ports: []api.EndpointPort{{Name: "p", Port: 2}, {Name: "q", Port: 20}}, + }, + { + Addresses: []api.EndpointAddress{{IP: "endpoint3"}}, + Ports: []api.EndpointPort{{Name: "p", Port: 3}, {Name: "q", Port: 30}}, + }, + }, + } + loadBalancer.OnEndpointsUpdate(endpoints) + + shuffledEndpoints := loadBalancer.services[serviceP].endpoints + if !stringsInSlice(shuffledEndpoints, "endpoint1:1", "endpoint2:2", "endpoint3:3") { + t.Errorf("did not find expected endpoints: %v", shuffledEndpoints) + } + expectEndpoint(t, loadBalancer, serviceP, shuffledEndpoints[0], nil) + expectEndpoint(t, loadBalancer, serviceP, shuffledEndpoints[1], nil) + expectEndpoint(t, loadBalancer, serviceP, shuffledEndpoints[2], nil) + expectEndpoint(t, loadBalancer, serviceP, shuffledEndpoints[0], nil) + + shuffledEndpoints = loadBalancer.services[serviceQ].endpoints + if !stringsInSlice(shuffledEndpoints, "endpoint1:10", "endpoint2:20", "endpoint3:30") { + t.Errorf("did not find expected endpoints: %v", shuffledEndpoints) + } + expectEndpoint(t, loadBalancer, serviceQ, shuffledEndpoints[0], nil) + expectEndpoint(t, loadBalancer, serviceQ, shuffledEndpoints[1], nil) + expectEndpoint(t, loadBalancer, serviceQ, shuffledEndpoints[2], nil) + expectEndpoint(t, loadBalancer, serviceQ, shuffledEndpoints[0], nil) + + // Then update the configuration with one fewer endpoints, make sure + // we start in the beginning again + endpoints[0] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: serviceP.Name, Namespace: serviceP.Namespace}, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{{IP: "endpoint4"}}, + Ports: []api.EndpointPort{{Name: "p", Port: 4}, {Name: "q", Port: 40}}, + }, + { + Addresses: []api.EndpointAddress{{IP: "endpoint5"}}, + Ports: []api.EndpointPort{{Name: "p", Port: 5}, {Name: "q", Port: 50}}, + }, + }, + } + loadBalancer.OnEndpointsUpdate(endpoints) + + shuffledEndpoints = loadBalancer.services[serviceP].endpoints + if !stringsInSlice(shuffledEndpoints, "endpoint4:4", "endpoint5:5") { + t.Errorf("did not find expected endpoints: %v", shuffledEndpoints) + } + expectEndpoint(t, loadBalancer, serviceP, shuffledEndpoints[0], nil) + expectEndpoint(t, loadBalancer, serviceP, shuffledEndpoints[1], nil) + expectEndpoint(t, loadBalancer, serviceP, shuffledEndpoints[0], nil) + expectEndpoint(t, loadBalancer, serviceP, shuffledEndpoints[1], nil) + + shuffledEndpoints = loadBalancer.services[serviceQ].endpoints + if !stringsInSlice(shuffledEndpoints, "endpoint4:40", "endpoint5:50") { + t.Errorf("did not find expected endpoints: %v", shuffledEndpoints) + } + expectEndpoint(t, loadBalancer, serviceQ, shuffledEndpoints[0], nil) + expectEndpoint(t, loadBalancer, serviceQ, shuffledEndpoints[1], nil) + expectEndpoint(t, loadBalancer, serviceQ, shuffledEndpoints[0], nil) + expectEndpoint(t, loadBalancer, serviceQ, shuffledEndpoints[1], nil) + + // Clear endpoints + endpoints[0] = api.Endpoints{ObjectMeta: api.ObjectMeta{Name: serviceP.Name, Namespace: serviceP.Namespace}, Subsets: nil} + loadBalancer.OnEndpointsUpdate(endpoints) + + endpoint, err = loadBalancer.NextEndpoint(serviceP, nil) + if err == nil || len(endpoint) != 0 { + t.Errorf("Didn't fail with non-existent service") + } +} + +func TestLoadBalanceWorksWithServiceRemoval(t *testing.T) { + loadBalancer := NewLoadBalancerRR() + fooServiceP := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "foo"}, Port: "p"} + barServiceP := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "bar"}, Port: "p"} + endpoint, err := loadBalancer.NextEndpoint(fooServiceP, nil) + if err == nil || len(endpoint) != 0 { + t.Errorf("Didn't fail with non-existent service") + } + endpoints := make([]api.Endpoints, 2) + endpoints[0] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: fooServiceP.Name, Namespace: fooServiceP.Namespace}, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{{IP: "endpoint1"}, {IP: "endpoint2"}, {IP: "endpoint3"}}, + Ports: []api.EndpointPort{{Name: "p", Port: 123}}, + }, + }, + } + endpoints[1] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: barServiceP.Name, Namespace: barServiceP.Namespace}, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{{IP: "endpoint4"}, {IP: "endpoint5"}, {IP: "endpoint6"}}, + Ports: []api.EndpointPort{{Name: "p", Port: 456}}, + }, + }, + } + loadBalancer.OnEndpointsUpdate(endpoints) + shuffledFooEndpoints := loadBalancer.services[fooServiceP].endpoints + expectEndpoint(t, loadBalancer, fooServiceP, shuffledFooEndpoints[0], nil) + expectEndpoint(t, loadBalancer, fooServiceP, shuffledFooEndpoints[1], nil) + expectEndpoint(t, loadBalancer, fooServiceP, shuffledFooEndpoints[2], nil) + expectEndpoint(t, loadBalancer, fooServiceP, shuffledFooEndpoints[0], nil) + expectEndpoint(t, loadBalancer, fooServiceP, shuffledFooEndpoints[1], nil) + + shuffledBarEndpoints := loadBalancer.services[barServiceP].endpoints + expectEndpoint(t, loadBalancer, barServiceP, shuffledBarEndpoints[0], nil) + expectEndpoint(t, loadBalancer, barServiceP, shuffledBarEndpoints[1], nil) + expectEndpoint(t, loadBalancer, barServiceP, shuffledBarEndpoints[2], nil) + expectEndpoint(t, loadBalancer, barServiceP, shuffledBarEndpoints[0], nil) + expectEndpoint(t, loadBalancer, barServiceP, shuffledBarEndpoints[1], nil) + + // Then update the configuration by removing foo + loadBalancer.OnEndpointsUpdate(endpoints[1:]) + endpoint, err = loadBalancer.NextEndpoint(fooServiceP, nil) + if err == nil || len(endpoint) != 0 { + t.Errorf("Didn't fail with non-existent service") + } + + // but bar is still there, and we continue RR from where we left off. + expectEndpoint(t, loadBalancer, barServiceP, shuffledBarEndpoints[2], nil) + expectEndpoint(t, loadBalancer, barServiceP, shuffledBarEndpoints[0], nil) + expectEndpoint(t, loadBalancer, barServiceP, shuffledBarEndpoints[1], nil) + expectEndpoint(t, loadBalancer, barServiceP, shuffledBarEndpoints[2], nil) +} + +func TestStickyLoadBalanceWorksWithNewServiceCalledFirst(t *testing.T) { + loadBalancer := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "foo"}, Port: ""} + endpoint, err := loadBalancer.NextEndpoint(service, nil) + if err == nil || len(endpoint) != 0 { + t.Errorf("Didn't fail with non-existent service") + } + + // Call NewService() before OnEndpointsUpdate() + loadBalancer.NewService(service, api.ServiceAffinityClientIP, 0) + endpoints := make([]api.Endpoints, 1) + endpoints[0] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{ + {Addresses: []api.EndpointAddress{{IP: "endpoint1"}}, Ports: []api.EndpointPort{{Port: 1}}}, + {Addresses: []api.EndpointAddress{{IP: "endpoint2"}}, Ports: []api.EndpointPort{{Port: 2}}}, + {Addresses: []api.EndpointAddress{{IP: "endpoint3"}}, Ports: []api.EndpointPort{{Port: 3}}}, + }, + } + loadBalancer.OnEndpointsUpdate(endpoints) + + client1 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} + client2 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 2), Port: 0} + client3 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 3), Port: 0} + + ep1, err := loadBalancer.NextEndpoint(service, client1) + if err != nil { + t.Errorf("Didn't find a service for %s: %v", service, err) + } + expectEndpoint(t, loadBalancer, service, ep1, client1) + expectEndpoint(t, loadBalancer, service, ep1, client1) + expectEndpoint(t, loadBalancer, service, ep1, client1) + + ep2, err := loadBalancer.NextEndpoint(service, client2) + if err != nil { + t.Errorf("Didn't find a service for %s: %v", service, err) + } + expectEndpoint(t, loadBalancer, service, ep2, client2) + expectEndpoint(t, loadBalancer, service, ep2, client2) + expectEndpoint(t, loadBalancer, service, ep2, client2) + + ep3, err := loadBalancer.NextEndpoint(service, client3) + if err != nil { + t.Errorf("Didn't find a service for %s: %v", service, err) + } + expectEndpoint(t, loadBalancer, service, ep3, client3) + expectEndpoint(t, loadBalancer, service, ep3, client3) + expectEndpoint(t, loadBalancer, service, ep3, client3) + + expectEndpoint(t, loadBalancer, service, ep1, client1) + expectEndpoint(t, loadBalancer, service, ep2, client2) + expectEndpoint(t, loadBalancer, service, ep3, client3) + expectEndpoint(t, loadBalancer, service, ep1, client1) + expectEndpoint(t, loadBalancer, service, ep2, client2) + expectEndpoint(t, loadBalancer, service, ep3, client3) +} + +func TestStickyLoadBalanceWorksWithNewServiceCalledSecond(t *testing.T) { + loadBalancer := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "foo"}, Port: ""} + endpoint, err := loadBalancer.NextEndpoint(service, nil) + if err == nil || len(endpoint) != 0 { + t.Errorf("Didn't fail with non-existent service") + } + + // Call OnEndpointsUpdate() before NewService() + endpoints := make([]api.Endpoints, 1) + endpoints[0] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{ + {Addresses: []api.EndpointAddress{{IP: "endpoint1"}}, Ports: []api.EndpointPort{{Port: 1}}}, + {Addresses: []api.EndpointAddress{{IP: "endpoint2"}}, Ports: []api.EndpointPort{{Port: 2}}}, + }, + } + loadBalancer.OnEndpointsUpdate(endpoints) + loadBalancer.NewService(service, api.ServiceAffinityClientIP, 0) + + client1 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} + client2 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 2), Port: 0} + client3 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 3), Port: 0} + + ep1, err := loadBalancer.NextEndpoint(service, client1) + if err != nil { + t.Errorf("Didn't find a service for %s: %v", service, err) + } + expectEndpoint(t, loadBalancer, service, ep1, client1) + expectEndpoint(t, loadBalancer, service, ep1, client1) + expectEndpoint(t, loadBalancer, service, ep1, client1) + + ep2, err := loadBalancer.NextEndpoint(service, client2) + if err != nil { + t.Errorf("Didn't find a service for %s: %v", service, err) + } + expectEndpoint(t, loadBalancer, service, ep2, client2) + expectEndpoint(t, loadBalancer, service, ep2, client2) + expectEndpoint(t, loadBalancer, service, ep2, client2) + + ep3, err := loadBalancer.NextEndpoint(service, client3) + if err != nil { + t.Errorf("Didn't find a service for %s: %v", service, err) + } + expectEndpoint(t, loadBalancer, service, ep3, client3) + expectEndpoint(t, loadBalancer, service, ep3, client3) + expectEndpoint(t, loadBalancer, service, ep3, client3) + + expectEndpoint(t, loadBalancer, service, ep1, client1) + expectEndpoint(t, loadBalancer, service, ep2, client2) + expectEndpoint(t, loadBalancer, service, ep3, client3) + expectEndpoint(t, loadBalancer, service, ep1, client1) + expectEndpoint(t, loadBalancer, service, ep2, client2) + expectEndpoint(t, loadBalancer, service, ep3, client3) +} + +func TestStickyLoadBalanaceWorksWithMultipleEndpointsRemoveOne(t *testing.T) { + client1 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} + client2 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 2), Port: 0} + client3 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 3), Port: 0} + client4 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 4), Port: 0} + client5 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 5), Port: 0} + client6 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 6), Port: 0} + loadBalancer := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "foo"}, Port: ""} + endpoint, err := loadBalancer.NextEndpoint(service, nil) + if err == nil || len(endpoint) != 0 { + t.Errorf("Didn't fail with non-existent service") + } + + loadBalancer.NewService(service, api.ServiceAffinityClientIP, 0) + endpoints := make([]api.Endpoints, 1) + endpoints[0] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{{IP: "endpoint"}}, + Ports: []api.EndpointPort{{Port: 1}, {Port: 2}, {Port: 3}}, + }, + }, + } + loadBalancer.OnEndpointsUpdate(endpoints) + shuffledEndpoints := loadBalancer.services[service].endpoints + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[0], client1) + client1Endpoint := shuffledEndpoints[0] + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[0], client1) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[1], client2) + client2Endpoint := shuffledEndpoints[1] + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[1], client2) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[2], client3) + client3Endpoint := shuffledEndpoints[2] + + endpoints[0] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{{IP: "endpoint"}}, + Ports: []api.EndpointPort{{Port: 1}, {Port: 2}}, + }, + }, + } + loadBalancer.OnEndpointsUpdate(endpoints) + shuffledEndpoints = loadBalancer.services[service].endpoints + if client1Endpoint == "endpoint:3" { + client1Endpoint = shuffledEndpoints[0] + } else if client2Endpoint == "endpoint:3" { + client2Endpoint = shuffledEndpoints[0] + } else if client3Endpoint == "endpoint:3" { + client3Endpoint = shuffledEndpoints[0] + } + expectEndpoint(t, loadBalancer, service, client1Endpoint, client1) + expectEndpoint(t, loadBalancer, service, client2Endpoint, client2) + expectEndpoint(t, loadBalancer, service, client3Endpoint, client3) + + endpoints[0] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{{IP: "endpoint"}}, + Ports: []api.EndpointPort{{Port: 1}, {Port: 2}, {Port: 4}}, + }, + }, + } + loadBalancer.OnEndpointsUpdate(endpoints) + shuffledEndpoints = loadBalancer.services[service].endpoints + expectEndpoint(t, loadBalancer, service, client1Endpoint, client1) + expectEndpoint(t, loadBalancer, service, client2Endpoint, client2) + expectEndpoint(t, loadBalancer, service, client3Endpoint, client3) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[0], client4) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[1], client5) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[2], client6) +} + +func TestStickyLoadBalanceWorksWithMultipleEndpointsAndUpdates(t *testing.T) { + client1 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} + client2 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 2), Port: 0} + client3 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 3), Port: 0} + loadBalancer := NewLoadBalancerRR() + service := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "foo"}, Port: ""} + endpoint, err := loadBalancer.NextEndpoint(service, nil) + if err == nil || len(endpoint) != 0 { + t.Errorf("Didn't fail with non-existent service") + } + + loadBalancer.NewService(service, api.ServiceAffinityClientIP, 0) + endpoints := make([]api.Endpoints, 1) + endpoints[0] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{{IP: "endpoint"}}, + Ports: []api.EndpointPort{{Port: 1}, {Port: 2}, {Port: 3}}, + }, + }, + } + loadBalancer.OnEndpointsUpdate(endpoints) + shuffledEndpoints := loadBalancer.services[service].endpoints + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[0], client1) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[0], client1) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[1], client2) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[1], client2) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[2], client3) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[0], client1) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[1], client2) + // Then update the configuration with one fewer endpoints, make sure + // we start in the beginning again + endpoints[0] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{{IP: "endpoint"}}, + Ports: []api.EndpointPort{{Port: 4}, {Port: 5}}, + }, + }, + } + loadBalancer.OnEndpointsUpdate(endpoints) + shuffledEndpoints = loadBalancer.services[service].endpoints + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[0], client1) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[1], client2) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[0], client1) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[0], client1) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[1], client2) + expectEndpoint(t, loadBalancer, service, shuffledEndpoints[1], client2) + + // Clear endpoints + endpoints[0] = api.Endpoints{ObjectMeta: api.ObjectMeta{Name: service.Name, Namespace: service.Namespace}, Subsets: nil} + loadBalancer.OnEndpointsUpdate(endpoints) + + endpoint, err = loadBalancer.NextEndpoint(service, nil) + if err == nil || len(endpoint) != 0 { + t.Errorf("Didn't fail with non-existent service") + } +} + +func TestStickyLoadBalanceWorksWithServiceRemoval(t *testing.T) { + client1 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} + client2 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 2), Port: 0} + client3 := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 3), Port: 0} + loadBalancer := NewLoadBalancerRR() + fooService := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "foo"}, Port: ""} + endpoint, err := loadBalancer.NextEndpoint(fooService, nil) + if err == nil || len(endpoint) != 0 { + t.Errorf("Didn't fail with non-existent service") + } + loadBalancer.NewService(fooService, api.ServiceAffinityClientIP, 0) + endpoints := make([]api.Endpoints, 2) + endpoints[0] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: fooService.Name, Namespace: fooService.Namespace}, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{{IP: "endpoint"}}, + Ports: []api.EndpointPort{{Port: 1}, {Port: 2}, {Port: 3}}, + }, + }, + } + barService := proxy.ServicePortName{NamespacedName: types.NamespacedName{Namespace: "testnamespace", Name: "bar"}, Port: ""} + loadBalancer.NewService(barService, api.ServiceAffinityClientIP, 0) + endpoints[1] = api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: barService.Name, Namespace: barService.Namespace}, + Subsets: []api.EndpointSubset{ + { + Addresses: []api.EndpointAddress{{IP: "endpoint"}}, + Ports: []api.EndpointPort{{Port: 4}, {Port: 5}}, + }, + }, + } + loadBalancer.OnEndpointsUpdate(endpoints) + + shuffledFooEndpoints := loadBalancer.services[fooService].endpoints + expectEndpoint(t, loadBalancer, fooService, shuffledFooEndpoints[0], client1) + expectEndpoint(t, loadBalancer, fooService, shuffledFooEndpoints[1], client2) + expectEndpoint(t, loadBalancer, fooService, shuffledFooEndpoints[2], client3) + expectEndpoint(t, loadBalancer, fooService, shuffledFooEndpoints[0], client1) + expectEndpoint(t, loadBalancer, fooService, shuffledFooEndpoints[0], client1) + expectEndpoint(t, loadBalancer, fooService, shuffledFooEndpoints[1], client2) + expectEndpoint(t, loadBalancer, fooService, shuffledFooEndpoints[1], client2) + expectEndpoint(t, loadBalancer, fooService, shuffledFooEndpoints[2], client3) + expectEndpoint(t, loadBalancer, fooService, shuffledFooEndpoints[2], client3) + + shuffledBarEndpoints := loadBalancer.services[barService].endpoints + expectEndpoint(t, loadBalancer, barService, shuffledBarEndpoints[0], client1) + expectEndpoint(t, loadBalancer, barService, shuffledBarEndpoints[1], client2) + expectEndpoint(t, loadBalancer, barService, shuffledBarEndpoints[0], client1) + expectEndpoint(t, loadBalancer, barService, shuffledBarEndpoints[0], client1) + expectEndpoint(t, loadBalancer, barService, shuffledBarEndpoints[1], client2) + expectEndpoint(t, loadBalancer, barService, shuffledBarEndpoints[1], client2) + + // Then update the configuration by removing foo + loadBalancer.OnEndpointsUpdate(endpoints[1:]) + endpoint, err = loadBalancer.NextEndpoint(fooService, nil) + if err == nil || len(endpoint) != 0 { + t.Errorf("Didn't fail with non-existent service") + } + + // but bar is still there, and we continue RR from where we left off. + shuffledBarEndpoints = loadBalancer.services[barService].endpoints + expectEndpoint(t, loadBalancer, barService, shuffledBarEndpoints[0], client1) + expectEndpoint(t, loadBalancer, barService, shuffledBarEndpoints[1], client2) + expectEndpoint(t, loadBalancer, barService, shuffledBarEndpoints[0], client1) + expectEndpoint(t, loadBalancer, barService, shuffledBarEndpoints[1], client2) + expectEndpoint(t, loadBalancer, barService, shuffledBarEndpoints[0], client1) + expectEndpoint(t, loadBalancer, barService, shuffledBarEndpoints[0], client1) +} diff --git a/vendor/k8s.io/kubernetes/pkg/proxy/userspace/udp_server.go b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/udp_server.go new file mode 100644 index 000000000..fdc85b47d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/proxy/userspace/udp_server.go @@ -0,0 +1,54 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 userspace + +import ( + "fmt" + "net" +) + +// udpEchoServer is a simple echo server in UDP, intended for testing the proxy. +type udpEchoServer struct { + net.PacketConn +} + +func (r *udpEchoServer) Loop() { + var buffer [4096]byte + for { + n, cliAddr, err := r.ReadFrom(buffer[0:]) + if err != nil { + fmt.Printf("ReadFrom failed: %v\n", err) + continue + } + r.WriteTo(buffer[0:n], cliAddr) + } +} + +func newUDPEchoServer() (*udpEchoServer, error) { + packetconn, err := net.ListenPacket("udp", ":0") + if err != nil { + return nil, err + } + return &udpEchoServer{packetconn}, nil +} + +/* +func main() { + r,_ := newUDPEchoServer() + r.Loop() +} +*/ diff --git a/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/configmap.go b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/configmap.go new file mode 100644 index 000000000..879beb1b9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/configmap.go @@ -0,0 +1,45 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 core + +import ( + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/generic" + "k8s.io/kubernetes/pkg/runtime" +) + +// NewConfigMapEvaluator returns an evaluator that can evaluate configMaps +func NewConfigMapEvaluator(kubeClient clientset.Interface) quota.Evaluator { + allResources := []api.ResourceName{api.ResourceConfigMaps} + return &generic.GenericEvaluator{ + Name: "Evaluator.ConfigMap", + InternalGroupKind: api.Kind("ConfigMap"), + InternalOperationResources: map[admission.Operation][]api.ResourceName{ + admission.Create: allResources, + }, + MatchedResourceNames: allResources, + MatchesScopeFunc: generic.MatchesNoScopeFunc, + ConstraintsFunc: generic.ObjectCountConstraintsFunc(api.ResourceConfigMaps), + UsageFunc: generic.ObjectCountUsageFunc(api.ResourceConfigMaps), + ListFuncByNamespace: func(namespace string, options api.ListOptions) (runtime.Object, error) { + return kubeClient.Core().ConfigMaps(namespace).List(options) + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/doc.go b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/doc.go new file mode 100644 index 000000000..3fdfaa773 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// core contains modules that interface with the core api group +package core diff --git a/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/persistent_volume_claims.go b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/persistent_volume_claims.go new file mode 100644 index 000000000..4edfffdd0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/persistent_volume_claims.go @@ -0,0 +1,45 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 core + +import ( + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/generic" + "k8s.io/kubernetes/pkg/runtime" +) + +// NewPersistentVolumeClaimEvaluator returns an evaluator that can evaluate persistent volume claims +func NewPersistentVolumeClaimEvaluator(kubeClient clientset.Interface) quota.Evaluator { + allResources := []api.ResourceName{api.ResourcePersistentVolumeClaims} + return &generic.GenericEvaluator{ + Name: "Evaluator.PersistentVolumeClaim", + InternalGroupKind: api.Kind("PersistentVolumeClaim"), + InternalOperationResources: map[admission.Operation][]api.ResourceName{ + admission.Create: allResources, + }, + MatchedResourceNames: allResources, + MatchesScopeFunc: generic.MatchesNoScopeFunc, + ConstraintsFunc: generic.ObjectCountConstraintsFunc(api.ResourcePersistentVolumeClaims), + UsageFunc: generic.ObjectCountUsageFunc(api.ResourcePersistentVolumeClaims), + ListFuncByNamespace: func(namespace string, options api.ListOptions) (runtime.Object, error) { + return kubeClient.Core().PersistentVolumeClaims(namespace).List(options) + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/pods.go b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/pods.go new file mode 100644 index 000000000..eedc86927 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/pods.go @@ -0,0 +1,183 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 core + +import ( + "fmt" + "strings" + + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/kubelet/qos/util" + "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/sets" +) + +// NewPodEvaluator returns an evaluator that can evaluate pods +func NewPodEvaluator(kubeClient clientset.Interface) quota.Evaluator { + computeResources := []api.ResourceName{ + api.ResourceCPU, + api.ResourceMemory, + api.ResourceRequestsCPU, + api.ResourceRequestsMemory, + api.ResourceLimitsCPU, + api.ResourceLimitsMemory, + } + allResources := append(computeResources, api.ResourcePods) + return &generic.GenericEvaluator{ + Name: "Evaluator.Pod", + InternalGroupKind: api.Kind("Pod"), + InternalOperationResources: map[admission.Operation][]api.ResourceName{ + admission.Create: allResources, + admission.Update: computeResources, + }, + GetFuncByNamespace: func(namespace, name string) (runtime.Object, error) { + return kubeClient.Core().Pods(namespace).Get(name) + }, + ConstraintsFunc: PodConstraintsFunc, + MatchedResourceNames: allResources, + MatchesScopeFunc: PodMatchesScopeFunc, + UsageFunc: PodUsageFunc, + ListFuncByNamespace: func(namespace string, options api.ListOptions) (runtime.Object, error) { + return kubeClient.Core().Pods(namespace).List(options) + }, + } +} + +// PodConstraintsFunc verifies that all required resources are present on the pod +func PodConstraintsFunc(required []api.ResourceName, object runtime.Object) error { + pod, ok := object.(*api.Pod) + if !ok { + return fmt.Errorf("Unexpected input object %v", object) + } + + // TODO: fix this when we have pod level cgroups + // since we do not yet pod level requests/limits, we need to ensure each + // container makes an explict request or limit for a quota tracked resource + requiredSet := quota.ToSet(required) + missingSet := sets.NewString() + for i := range pod.Spec.Containers { + requests := pod.Spec.Containers[i].Resources.Requests + limits := pod.Spec.Containers[i].Resources.Limits + containerUsage := podUsageHelper(requests, limits) + containerSet := quota.ToSet(quota.ResourceNames(containerUsage)) + if !containerSet.Equal(requiredSet) { + difference := requiredSet.Difference(containerSet) + missingSet.Insert(difference.List()...) + } + } + if len(missingSet) == 0 { + return nil + } + return fmt.Errorf("must specify %s", strings.Join(missingSet.List(), ",")) +} + +// podUsageHelper can summarize the pod quota usage based on requests and limits +func podUsageHelper(requests api.ResourceList, limits api.ResourceList) api.ResourceList { + result := api.ResourceList{} + result[api.ResourcePods] = resource.MustParse("1") + if request, found := requests[api.ResourceCPU]; found { + result[api.ResourceCPU] = request + result[api.ResourceRequestsCPU] = request + } + if limit, found := limits[api.ResourceCPU]; found { + result[api.ResourceLimitsCPU] = limit + } + if request, found := requests[api.ResourceMemory]; found { + result[api.ResourceMemory] = request + result[api.ResourceRequestsMemory] = request + } + if limit, found := limits[api.ResourceMemory]; found { + result[api.ResourceLimitsMemory] = limit + } + return result +} + +// PodUsageFunc knows how to measure usage associated with pods +func PodUsageFunc(object runtime.Object) api.ResourceList { + pod, ok := object.(*api.Pod) + if !ok { + return api.ResourceList{} + } + + // by convention, we do not quota pods that have reached an end-of-life state + if !QuotaPod(pod) { + return api.ResourceList{} + } + + // TODO: fix this when we have pod level cgroups + // when we have pod level cgroups, we can just read pod level requests/limits + requests := api.ResourceList{} + limits := api.ResourceList{} + for i := range pod.Spec.Containers { + requests = quota.Add(requests, pod.Spec.Containers[i].Resources.Requests) + limits = quota.Add(limits, pod.Spec.Containers[i].Resources.Limits) + } + + return podUsageHelper(requests, limits) +} + +// PodMatchesScopeFunc is a function that knows how to evaluate if a pod matches a scope +func PodMatchesScopeFunc(scope api.ResourceQuotaScope, object runtime.Object) bool { + pod, ok := object.(*api.Pod) + if !ok { + return false + } + switch scope { + case api.ResourceQuotaScopeTerminating: + return isTerminating(pod) + case api.ResourceQuotaScopeNotTerminating: + return !isTerminating(pod) + case api.ResourceQuotaScopeBestEffort: + return isBestEffort(pod) + case api.ResourceQuotaScopeNotBestEffort: + return !isBestEffort(pod) + } + return false +} + +func isBestEffort(pod *api.Pod) bool { + // TODO: when we have request/limits on a pod scope, we need to revisit this + for _, container := range pod.Spec.Containers { + qosPerResource := util.GetQoS(&container) + for _, qos := range qosPerResource { + if util.BestEffort == qos { + return true + } + } + } + return false +} + +func isTerminating(pod *api.Pod) bool { + if pod.Spec.ActiveDeadlineSeconds != nil && *pod.Spec.ActiveDeadlineSeconds >= int64(0) { + return true + } + return false +} + +// QuotaPod returns true if the pod is eligible to track against a quota +// if it's not in a terminal state according to its phase. +func QuotaPod(pod *api.Pod) bool { + // see GetPhase in kubelet.go for details on how it covers all restart policy conditions + // https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/kubelet.go#L3001 + return !(api.PodFailed == pod.Status.Phase || api.PodSucceeded == pod.Status.Phase) +} diff --git a/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/registry.go b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/registry.go new file mode 100644 index 000000000..69d148455 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/registry.go @@ -0,0 +1,46 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 core + +import ( + "k8s.io/kubernetes/pkg/api/unversioned" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/generic" +) + +// NewRegistry returns a registry that knows how to deal with core kubernetes resources +func NewRegistry(kubeClient clientset.Interface) quota.Registry { + pod := NewPodEvaluator(kubeClient) + service := NewServiceEvaluator(kubeClient) + replicationController := NewReplicationControllerEvaluator(kubeClient) + resourceQuota := NewResourceQuotaEvaluator(kubeClient) + secret := NewSecretEvaluator(kubeClient) + configMap := NewConfigMapEvaluator(kubeClient) + persistentVolumeClaim := NewPersistentVolumeClaimEvaluator(kubeClient) + return &generic.GenericRegistry{ + InternalEvaluators: map[unversioned.GroupKind]quota.Evaluator{ + pod.GroupKind(): pod, + service.GroupKind(): service, + replicationController.GroupKind(): replicationController, + secret.GroupKind(): secret, + configMap.GroupKind(): configMap, + resourceQuota.GroupKind(): resourceQuota, + persistentVolumeClaim.GroupKind(): persistentVolumeClaim, + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/replication_controllers.go b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/replication_controllers.go new file mode 100644 index 000000000..7d4b44337 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/replication_controllers.go @@ -0,0 +1,45 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 core + +import ( + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/generic" + "k8s.io/kubernetes/pkg/runtime" +) + +// NewReplicationControllerEvaluator returns an evaluator that can evaluate replication controllers +func NewReplicationControllerEvaluator(kubeClient clientset.Interface) quota.Evaluator { + allResources := []api.ResourceName{api.ResourceReplicationControllers} + return &generic.GenericEvaluator{ + Name: "Evaluator.ReplicationController", + InternalGroupKind: api.Kind("ReplicationController"), + InternalOperationResources: map[admission.Operation][]api.ResourceName{ + admission.Create: allResources, + }, + MatchedResourceNames: allResources, + MatchesScopeFunc: generic.MatchesNoScopeFunc, + ConstraintsFunc: generic.ObjectCountConstraintsFunc(api.ResourceReplicationControllers), + UsageFunc: generic.ObjectCountUsageFunc(api.ResourceReplicationControllers), + ListFuncByNamespace: func(namespace string, options api.ListOptions) (runtime.Object, error) { + return kubeClient.Core().ReplicationControllers(namespace).List(options) + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/resource_quotas.go b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/resource_quotas.go new file mode 100644 index 000000000..6d52e70e1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/resource_quotas.go @@ -0,0 +1,45 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 core + +import ( + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/generic" + "k8s.io/kubernetes/pkg/runtime" +) + +// NewResourceQuotaEvaluator returns an evaluator that can evaluate resource quotas +func NewResourceQuotaEvaluator(kubeClient clientset.Interface) quota.Evaluator { + allResources := []api.ResourceName{api.ResourceQuotas} + return &generic.GenericEvaluator{ + Name: "Evaluator.ResourceQuota", + InternalGroupKind: api.Kind("ResourceQuota"), + InternalOperationResources: map[admission.Operation][]api.ResourceName{ + admission.Create: allResources, + }, + MatchedResourceNames: allResources, + MatchesScopeFunc: generic.MatchesNoScopeFunc, + ConstraintsFunc: generic.ObjectCountConstraintsFunc(api.ResourceQuotas), + UsageFunc: generic.ObjectCountUsageFunc(api.ResourceQuotas), + ListFuncByNamespace: func(namespace string, options api.ListOptions) (runtime.Object, error) { + return kubeClient.Core().ResourceQuotas(namespace).List(options) + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/secrets.go b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/secrets.go new file mode 100644 index 000000000..d3d79f293 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/secrets.go @@ -0,0 +1,45 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 core + +import ( + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/generic" + "k8s.io/kubernetes/pkg/runtime" +) + +// NewSecretEvaluator returns an evaluator that can evaluate secrets +func NewSecretEvaluator(kubeClient clientset.Interface) quota.Evaluator { + allResources := []api.ResourceName{api.ResourceSecrets} + return &generic.GenericEvaluator{ + Name: "Evaluator.Secret", + InternalGroupKind: api.Kind("Secret"), + InternalOperationResources: map[admission.Operation][]api.ResourceName{ + admission.Create: allResources, + }, + MatchedResourceNames: allResources, + MatchesScopeFunc: generic.MatchesNoScopeFunc, + ConstraintsFunc: generic.ObjectCountConstraintsFunc(api.ResourceSecrets), + UsageFunc: generic.ObjectCountUsageFunc(api.ResourceSecrets), + ListFuncByNamespace: func(namespace string, options api.ListOptions) (runtime.Object, error) { + return kubeClient.Core().Secrets(namespace).List(options) + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/services.go b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/services.go new file mode 100644 index 000000000..98812b673 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/evaluator/core/services.go @@ -0,0 +1,71 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 core + +import ( + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/generic" + "k8s.io/kubernetes/pkg/runtime" +) + +// NewServiceEvaluator returns an evaluator that can evaluate service quotas +func NewServiceEvaluator(kubeClient clientset.Interface) quota.Evaluator { + allResources := []api.ResourceName{ + api.ResourceServices, + api.ResourceServicesNodePorts, + } + return &generic.GenericEvaluator{ + Name: "Evaluator.Service", + InternalGroupKind: api.Kind("Service"), + InternalOperationResources: map[admission.Operation][]api.ResourceName{ + admission.Create: allResources, + }, + MatchedResourceNames: allResources, + MatchesScopeFunc: generic.MatchesNoScopeFunc, + ConstraintsFunc: generic.ObjectCountConstraintsFunc(api.ResourceServices), + UsageFunc: ServiceUsageFunc, + ListFuncByNamespace: func(namespace string, options api.ListOptions) (runtime.Object, error) { + return kubeClient.Core().Services(namespace).List(options) + }, + } +} + +// ServiceUsageFunc knows how to measure usage associated with services +func ServiceUsageFunc(object runtime.Object) api.ResourceList { + result := api.ResourceList{} + if service, ok := object.(*api.Service); ok { + result[api.ResourceServices] = resource.MustParse("1") + switch service.Spec.Type { + case api.ServiceTypeNodePort: + result[api.ResourceServicesNodePorts] = resource.MustParse("1") + } + } + return result +} + +// QuotaServiceType returns true if the service type is eligible to track against a quota +func QuotaServiceType(service *api.Service) bool { + switch service.Spec.Type { + case api.ServiceTypeNodePort: + return true + } + return false +} diff --git a/vendor/k8s.io/kubernetes/pkg/quota/generic/evaluator.go b/vendor/k8s.io/kubernetes/pkg/quota/generic/evaluator.go new file mode 100644 index 000000000..905590e4a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/generic/evaluator.go @@ -0,0 +1,199 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 generic + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/runtime" +) + +// ConstraintsFunc takes a list of required resources that must match on the input item +type ConstraintsFunc func(required []api.ResourceName, item runtime.Object) error + +// GetFuncByNamespace knows how to get a resource with specified namespace and name +type GetFuncByNamespace func(namespace, name string) (runtime.Object, error) + +// ListFuncByNamespace knows how to list resources in a namespace +type ListFuncByNamespace func(namespace string, options api.ListOptions) (runtime.Object, error) + +// MatchesScopeFunc knows how to evaluate if an object matches a scope +type MatchesScopeFunc func(scope api.ResourceQuotaScope, object runtime.Object) bool + +// UsageFunc knows how to measure usage associated with an object +type UsageFunc func(object runtime.Object) api.ResourceList + +// MatchesNoScopeFunc returns false on all match checks +func MatchesNoScopeFunc(scope api.ResourceQuotaScope, object runtime.Object) bool { + return false +} + +// ObjectCountConstraintsFunc returns true if the specified resource name is in +// the required set of resource names +func ObjectCountConstraintsFunc(resourceName api.ResourceName) ConstraintsFunc { + return func(required []api.ResourceName, item runtime.Object) error { + if !quota.Contains(required, resourceName) { + return fmt.Errorf("missing %s", resourceName) + } + return nil + } +} + +// ObjectCountUsageFunc is useful if you are only counting your object +// It always returns 1 as the usage for the named resource +func ObjectCountUsageFunc(resourceName api.ResourceName) UsageFunc { + return func(object runtime.Object) api.ResourceList { + return api.ResourceList{ + resourceName: resource.MustParse("1"), + } + } +} + +// GenericEvaluator provides an implementation for quota.Evaluator +type GenericEvaluator struct { + // Name used for logging + Name string + // The GroupKind that this evaluator tracks + InternalGroupKind unversioned.GroupKind + // The set of resources that are pertinent to the mapped operation + InternalOperationResources map[admission.Operation][]api.ResourceName + // The set of resource names this evaluator matches + MatchedResourceNames []api.ResourceName + // A function that knows how to evaluate a matches scope request + MatchesScopeFunc MatchesScopeFunc + // A function that knows how to return usage for an object + UsageFunc UsageFunc + // A function that knows how to list resources by namespace + ListFuncByNamespace ListFuncByNamespace + // A function that knows how to get resource in a namespace + // This function must be specified if the evaluator needs to handle UPDATE + GetFuncByNamespace GetFuncByNamespace + // A function that checks required constraints are satisfied + ConstraintsFunc ConstraintsFunc +} + +// Ensure that GenericEvaluator implements quota.Evaluator +var _ quota.Evaluator = &GenericEvaluator{} + +// Constraints checks required constraints are satisfied on the input object +func (g *GenericEvaluator) Constraints(required []api.ResourceName, item runtime.Object) error { + return g.ConstraintsFunc(required, item) +} + +// Get returns the object by namespace and name +func (g *GenericEvaluator) Get(namespace, name string) (runtime.Object, error) { + return g.GetFuncByNamespace(namespace, name) +} + +// OperationResources returns the set of resources that could be updated for the +// specified operation for this kind. If empty, admission control will ignore +// quota processing for the operation. +func (g *GenericEvaluator) OperationResources(operation admission.Operation) []api.ResourceName { + return g.InternalOperationResources[operation] +} + +// GroupKind that this evaluator tracks +func (g *GenericEvaluator) GroupKind() unversioned.GroupKind { + return g.InternalGroupKind +} + +// MatchesResources is the list of resources that this evaluator matches +func (g *GenericEvaluator) MatchesResources() []api.ResourceName { + return g.MatchedResourceNames +} + +// Matches returns true if the evaluator matches the specified quota with the provided input item +func (g *GenericEvaluator) Matches(resourceQuota *api.ResourceQuota, item runtime.Object) bool { + if resourceQuota == nil { + return false + } + + // verify the quota matches on resource, by default its false + matchResource := false + for resourceName := range resourceQuota.Status.Hard { + if g.MatchesResource(resourceName) { + matchResource = true + } + } + // by default, no scopes matches all + matchScope := true + for _, scope := range resourceQuota.Spec.Scopes { + matchScope = matchScope && g.MatchesScope(scope, item) + } + return matchResource && matchScope +} + +// MatchesResource returns true if this evaluator can match on the specified resource +func (g *GenericEvaluator) MatchesResource(resourceName api.ResourceName) bool { + for _, matchedResourceName := range g.MatchedResourceNames { + if resourceName == matchedResourceName { + return true + } + } + return false +} + +// MatchesScope returns true if the input object matches the specified scope +func (g *GenericEvaluator) MatchesScope(scope api.ResourceQuotaScope, object runtime.Object) bool { + return g.MatchesScopeFunc(scope, object) +} + +// Usage returns the resource usage for the specified object +func (g *GenericEvaluator) Usage(object runtime.Object) api.ResourceList { + return g.UsageFunc(object) +} + +// UsageStats calculates latest observed usage stats for all objects +func (g *GenericEvaluator) UsageStats(options quota.UsageStatsOptions) (quota.UsageStats, error) { + // default each tracked resource to zero + result := quota.UsageStats{Used: api.ResourceList{}} + for _, resourceName := range g.MatchedResourceNames { + result.Used[resourceName] = resource.MustParse("0") + } + list, err := g.ListFuncByNamespace(options.Namespace, api.ListOptions{}) + if err != nil { + return result, fmt.Errorf("%s: Failed to list %v: %v", g.Name, g.GroupKind(), err) + } + _, err = meta.Accessor(list) + if err != nil { + return result, fmt.Errorf("%s: Unable to understand list result %#v", g.Name, list) + } + items, err := meta.ExtractList(list) + if err != nil { + return result, fmt.Errorf("%s: Unable to understand list result %#v (%v)", g.Name, list, err) + } + for _, item := range items { + // need to verify that the item matches the set of scopes + matchesScopes := true + for _, scope := range options.Scopes { + if !g.MatchesScope(scope, item) { + matchesScopes = false + } + } + // only count usage if there was a match + if matchesScopes { + result.Used = quota.Add(result.Used, g.Usage(item)) + } + } + return result, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/quota/generic/registry.go b/vendor/k8s.io/kubernetes/pkg/quota/generic/registry.go new file mode 100644 index 000000000..0609d73cf --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/generic/registry.go @@ -0,0 +1,36 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 generic + +import ( + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/quota" +) + +// Ensure it implements the required interface +var _ quota.Registry = &GenericRegistry{} + +// GenericRegistry implements Registry +type GenericRegistry struct { + // internal evaluators by group kind + InternalEvaluators map[unversioned.GroupKind]quota.Evaluator +} + +// Evaluators returns the map of evaluators by groupKind +func (r *GenericRegistry) Evaluators() map[unversioned.GroupKind]quota.Evaluator { + return r.InternalEvaluators +} diff --git a/vendor/k8s.io/kubernetes/pkg/quota/install/registry.go b/vendor/k8s.io/kubernetes/pkg/quota/install/registry.go new file mode 100644 index 000000000..109b57484 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/install/registry.go @@ -0,0 +1,30 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 install + +import ( + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/quota" + "k8s.io/kubernetes/pkg/quota/evaluator/core" +) + +// NewRegistry returns a registry that knows how to deal kubernetes resources +// across API groups +func NewRegistry(kubeClient clientset.Interface) quota.Registry { + // TODO: when quota supports resources in other api groups, we will need to merge + return core.NewRegistry(kubeClient) +} diff --git a/vendor/k8s.io/kubernetes/pkg/quota/interfaces.go b/vendor/k8s.io/kubernetes/pkg/quota/interfaces.go new file mode 100644 index 000000000..da7e4a18f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/interfaces.go @@ -0,0 +1,66 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 quota + +import ( + "k8s.io/kubernetes/pkg/admission" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/runtime" +) + +// UsageStatsOptions is an options structs that describes how stats should be calculated +type UsageStatsOptions struct { + // Namespace where stats should be calculate + Namespace string + // Scopes that must match counted objects + Scopes []api.ResourceQuotaScope +} + +// UsageStats is result of measuring observed resource use in the system +type UsageStats struct { + // Used maps resource to quantity used + Used api.ResourceList +} + +// Evaluator knows how to evaluate quota usage for a particular group kind +type Evaluator interface { + // Constraints ensures that each required resource is present on item + Constraints(required []api.ResourceName, item runtime.Object) error + // Get returns the object with specified namespace and name + Get(namespace, name string) (runtime.Object, error) + // GroupKind returns the groupKind that this object knows how to evaluate + GroupKind() unversioned.GroupKind + // MatchesResources is the list of resources that this evaluator matches + MatchesResources() []api.ResourceName + // Matches returns true if the specified quota matches the input item + Matches(resourceQuota *api.ResourceQuota, item runtime.Object) bool + // OperationResources returns the set of resources that could be updated for the + // specified operation for this kind. If empty, admission control will ignore + // quota processing for the operation. + OperationResources(operation admission.Operation) []api.ResourceName + // Usage returns the resource usage for the specified object + Usage(object runtime.Object) api.ResourceList + // UsageStats calculates latest observed usage stats for all objects + UsageStats(options UsageStatsOptions) (UsageStats, error) +} + +// Registry holds the list of evaluators associated to a particular group kind +type Registry interface { + // Evaluators returns the set Evaluator objects registered to a groupKind + Evaluators() map[unversioned.GroupKind]Evaluator +} diff --git a/vendor/k8s.io/kubernetes/pkg/quota/resources.go b/vendor/k8s.io/kubernetes/pkg/quota/resources.go new file mode 100644 index 000000000..0cb03a84a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/resources.go @@ -0,0 +1,159 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 quota + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/util/sets" +) + +// Equals returns true if the two lists are equivalent +func Equals(a api.ResourceList, b api.ResourceList) bool { + for key, value1 := range a { + value2, found := b[key] + if !found { + return false + } + if value1.Cmp(value2) != 0 { + return false + } + } + for key, value1 := range b { + value2, found := a[key] + if !found { + return false + } + if value1.Cmp(value2) != 0 { + return false + } + } + return true +} + +// LessThanOrEqual returns true if a < b for each key in b +// If false, it returns the keys in a that exceeded b +func LessThanOrEqual(a api.ResourceList, b api.ResourceList) (bool, []api.ResourceName) { + result := true + resourceNames := []api.ResourceName{} + for key, value := range b { + if other, found := a[key]; found { + if other.Cmp(value) > 0 { + result = false + resourceNames = append(resourceNames, key) + } + } + } + return result, resourceNames +} + +// Add returns the result of a + b for each named resource +func Add(a api.ResourceList, b api.ResourceList) api.ResourceList { + result := api.ResourceList{} + for key, value := range a { + quantity := *value.Copy() + if other, found := b[key]; found { + quantity.Add(other) + } + result[key] = quantity + } + for key, value := range b { + if _, found := result[key]; !found { + quantity := *value.Copy() + result[key] = quantity + } + } + return result +} + +// Subtract returns the result of a - b for each named resource +func Subtract(a api.ResourceList, b api.ResourceList) api.ResourceList { + result := api.ResourceList{} + for key, value := range a { + quantity := *value.Copy() + if other, found := b[key]; found { + quantity.Sub(other) + } + result[key] = quantity + } + for key, value := range b { + if _, found := result[key]; !found { + quantity := *value.Copy() + quantity.Neg(value) + result[key] = quantity + } + } + return result +} + +// Mask returns a new resource list that only has the values with the specified names +func Mask(resources api.ResourceList, names []api.ResourceName) api.ResourceList { + nameSet := ToSet(names) + result := api.ResourceList{} + for key, value := range resources { + if nameSet.Has(string(key)) { + result[key] = *value.Copy() + } + } + return result +} + +// ResourceNames returns a list of all resource names in the ResourceList +func ResourceNames(resources api.ResourceList) []api.ResourceName { + result := []api.ResourceName{} + for resourceName := range resources { + result = append(result, resourceName) + } + return result +} + +// Contains returns true if the specified item is in the list of items +func Contains(items []api.ResourceName, item api.ResourceName) bool { + return ToSet(items).Has(string(item)) +} + +// Intersection returns the intersection of both list of resources +func Intersection(a []api.ResourceName, b []api.ResourceName) []api.ResourceName { + setA := ToSet(a) + setB := ToSet(b) + setC := setA.Intersection(setB) + result := []api.ResourceName{} + for _, resourceName := range setC.List() { + result = append(result, api.ResourceName(resourceName)) + } + return result +} + +// IsZero returns true if each key maps to the quantity value 0 +func IsZero(a api.ResourceList) bool { + zero := resource.MustParse("0") + for _, v := range a { + if v.Cmp(zero) != 0 { + return false + } + } + return true +} + +// ToSet takes a list of resource names and converts to a string set +func ToSet(resourceNames []api.ResourceName) sets.String { + result := sets.NewString() + for _, resourceName := range resourceNames { + result.Insert(string(resourceName)) + } + return result +} diff --git a/vendor/k8s.io/kubernetes/pkg/quota/resources_test.go b/vendor/k8s.io/kubernetes/pkg/quota/resources_test.go new file mode 100644 index 000000000..79a3184f0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/quota/resources_test.go @@ -0,0 +1,223 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 quota + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" +) + +func TestEquals(t *testing.T) { + testCases := map[string]struct { + a api.ResourceList + b api.ResourceList + expected bool + }{ + "isEqual": { + a: api.ResourceList{}, + b: api.ResourceList{}, + expected: true, + }, + "isEqualWithKeys": { + a: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100m"), + api.ResourceMemory: resource.MustParse("1Gi"), + }, + b: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100m"), + api.ResourceMemory: resource.MustParse("1Gi"), + }, + expected: true, + }, + "isNotEqualSameKeys": { + a: api.ResourceList{ + api.ResourceCPU: resource.MustParse("200m"), + api.ResourceMemory: resource.MustParse("1Gi"), + }, + b: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100m"), + api.ResourceMemory: resource.MustParse("1Gi"), + }, + expected: false, + }, + "isNotEqualDiffKeys": { + a: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100m"), + api.ResourceMemory: resource.MustParse("1Gi"), + }, + b: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100m"), + api.ResourceMemory: resource.MustParse("1Gi"), + api.ResourcePods: resource.MustParse("1"), + }, + expected: false, + }, + } + for testName, testCase := range testCases { + if result := Equals(testCase.a, testCase.b); result != testCase.expected { + t.Errorf("%s expected: %v, actual: %v, a=%v, b=%v", testName, testCase.expected, result, testCase.a, testCase.b) + } + } +} + +func TestAdd(t *testing.T) { + testCases := map[string]struct { + a api.ResourceList + b api.ResourceList + expected api.ResourceList + }{ + "noKeys": { + a: api.ResourceList{}, + b: api.ResourceList{}, + expected: api.ResourceList{}, + }, + "toEmpty": { + a: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, + b: api.ResourceList{}, + expected: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, + }, + "matching": { + a: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, + b: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, + expected: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, + }, + } + for testName, testCase := range testCases { + sum := Add(testCase.a, testCase.b) + if result := Equals(testCase.expected, sum); !result { + t.Errorf("%s expected: %v, actual: %v", testName, testCase.expected, sum) + } + } +} + +func TestSubtract(t *testing.T) { + testCases := map[string]struct { + a api.ResourceList + b api.ResourceList + expected api.ResourceList + }{ + "noKeys": { + a: api.ResourceList{}, + b: api.ResourceList{}, + expected: api.ResourceList{}, + }, + "value-empty": { + a: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, + b: api.ResourceList{}, + expected: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, + }, + "empty-value": { + a: api.ResourceList{}, + b: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, + expected: api.ResourceList{api.ResourceCPU: resource.MustParse("-100m")}, + }, + "value-value": { + a: api.ResourceList{api.ResourceCPU: resource.MustParse("200m")}, + b: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, + expected: api.ResourceList{api.ResourceCPU: resource.MustParse("100m")}, + }, + } + for testName, testCase := range testCases { + sub := Subtract(testCase.a, testCase.b) + if result := Equals(testCase.expected, sub); !result { + t.Errorf("%s expected: %v, actual: %v", testName, testCase.expected, sub) + } + } +} + +func TestResourceNames(t *testing.T) { + testCases := map[string]struct { + a api.ResourceList + expected []api.ResourceName + }{ + "empty": { + a: api.ResourceList{}, + expected: []api.ResourceName{}, + }, + "values": { + a: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100m"), + api.ResourceMemory: resource.MustParse("1Gi"), + }, + expected: []api.ResourceName{api.ResourceMemory, api.ResourceCPU}, + }, + } + for testName, testCase := range testCases { + actualSet := ToSet(ResourceNames(testCase.a)) + expectedSet := ToSet(testCase.expected) + if !actualSet.Equal(expectedSet) { + t.Errorf("%s expected: %v, actual: %v", testName, expectedSet, actualSet) + } + } +} + +func TestContains(t *testing.T) { + testCases := map[string]struct { + a []api.ResourceName + b api.ResourceName + expected bool + }{ + "does-not-contain": { + a: []api.ResourceName{api.ResourceMemory}, + b: api.ResourceCPU, + expected: false, + }, + "does-contain": { + a: []api.ResourceName{api.ResourceMemory, api.ResourceCPU}, + b: api.ResourceCPU, + expected: true, + }, + } + for testName, testCase := range testCases { + if actual := Contains(testCase.a, testCase.b); actual != testCase.expected { + t.Errorf("%s expected: %v, actual: %v", testName, testCase.expected, actual) + } + } +} + +func TestIsZero(t *testing.T) { + testCases := map[string]struct { + a api.ResourceList + expected bool + }{ + "empty": { + a: api.ResourceList{}, + expected: true, + }, + "zero": { + a: api.ResourceList{ + api.ResourceCPU: resource.MustParse("0"), + api.ResourceMemory: resource.MustParse("0"), + }, + expected: true, + }, + "non-zero": { + a: api.ResourceList{ + api.ResourceCPU: resource.MustParse("200m"), + api.ResourceMemory: resource.MustParse("1Gi"), + }, + expected: false, + }, + } + for testName, testCase := range testCases { + if result := IsZero(testCase.a); result != testCase.expected { + t.Errorf("%s expected: %v, actual: %v", testName, testCase.expected, result) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/OWNERS b/vendor/k8s.io/kubernetes/pkg/registry/OWNERS new file mode 100644 index 000000000..f9b7bd6a8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/OWNERS @@ -0,0 +1,4 @@ +assignees: + - lavalamp + - smarterclayton + - wojtek-t diff --git a/vendor/k8s.io/kubernetes/pkg/registry/cachesize/cachesize.go b/vendor/k8s.io/kubernetes/pkg/registry/cachesize/cachesize.go new file mode 100644 index 000000000..708a4441d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/cachesize/cachesize.go @@ -0,0 +1,97 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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. +*/ + +//use for --watch-cache-sizes param of kube-apiserver +//make watch cache size of resources configurable +package cachesize + +import ( + "strconv" + "strings" + + "github.com/golang/glog" +) + +type Resource string + +const ( + Controllers Resource = "controllers" + Daemonsets Resource = "daemonsets" + Deployments Resource = "deployments" + Endpoints Resource = "endpoints" + HorizontalPodAutoscalers Resource = "horizontalpodautoscalers" + Ingress Resource = "ingress" + Jobs Resource = "jobs" + LimitRanges Resource = "limitranges" + Namespaces Resource = "namespaces" + Nodes Resource = "nodes" + PersistentVolumes Resource = "persistentvolumes" + PersistentVolumeClaims Resource = "persistentvolumeclaims" + Pods Resource = "pods" + PodTemplates Resource = "podtemplates" + Replicasets Resource = "replicasets" + ResourceQuotas Resource = "resourcequotas" + Secrets Resource = "secrets" + ServiceAccounts Resource = "serviceaccounts" + Services Resource = "services" +) + +var watchCacheSizes map[Resource]int + +func init() { + watchCacheSizes = make(map[Resource]int) + watchCacheSizes[Controllers] = 100 + watchCacheSizes[Daemonsets] = 100 + watchCacheSizes[Deployments] = 100 + watchCacheSizes[Endpoints] = 1000 + watchCacheSizes[HorizontalPodAutoscalers] = 100 + watchCacheSizes[Ingress] = 100 + watchCacheSizes[Jobs] = 100 + watchCacheSizes[LimitRanges] = 100 + watchCacheSizes[Namespaces] = 100 + watchCacheSizes[Nodes] = 1000 + watchCacheSizes[PersistentVolumes] = 100 + watchCacheSizes[PersistentVolumeClaims] = 100 + watchCacheSizes[Pods] = 1000 + watchCacheSizes[PodTemplates] = 100 + watchCacheSizes[Replicasets] = 100 + watchCacheSizes[ResourceQuotas] = 100 + watchCacheSizes[Secrets] = 100 + watchCacheSizes[ServiceAccounts] = 100 + watchCacheSizes[Services] = 100 +} + +func SetWatchCacheSizes(cacheSizes []string) { + for _, c := range cacheSizes { + tokens := strings.Split(c, "#") + if len(tokens) != 2 { + glog.Errorf("invalid value of watch cache capabilities: %s", c) + continue + } + + size, err := strconv.Atoi(tokens[1]) + if err != nil { + glog.Errorf("invalid size of watch cache capabilities: %s", c) + continue + } + + watchCacheSizes[Resource(strings.ToLower(tokens[0]))] = size + } +} + +func GetWatchCacheSizeByResource(resource Resource) int { + return watchCacheSizes[resource] +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/componentstatus/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/componentstatus/doc.go new file mode 100644 index 000000000..85248b33a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/componentstatus/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 componentstatus provides interfaces and implementation for retrieving cluster +// component status. +package componentstatus diff --git a/vendor/k8s.io/kubernetes/pkg/registry/componentstatus/rest.go b/vendor/k8s.io/kubernetes/pkg/registry/componentstatus/rest.go new file mode 100644 index 000000000..968b90bbb --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/componentstatus/rest.go @@ -0,0 +1,117 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 componentstatus + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apiserver" + "k8s.io/kubernetes/pkg/probe" + httpprober "k8s.io/kubernetes/pkg/probe/http" + "k8s.io/kubernetes/pkg/runtime" + "sync" +) + +type REST struct { + GetServersToValidate func() map[string]apiserver.Server + prober httpprober.HTTPProber +} + +// NewStorage returns a new REST. +func NewStorage(serverRetriever func() map[string]apiserver.Server) *REST { + return &REST{ + GetServersToValidate: serverRetriever, + prober: httpprober.New(), + } +} + +func (rs *REST) New() runtime.Object { + return &api.ComponentStatus{} +} + +func (rs *REST) NewList() runtime.Object { + return &api.ComponentStatusList{} +} + +// Returns the list of component status. Note that the label and field are both ignored. +// Note that this call doesn't support labels or selectors. +func (rs *REST) List(ctx api.Context, options *api.ListOptions) (runtime.Object, error) { + servers := rs.GetServersToValidate() + + wait := sync.WaitGroup{} + wait.Add(len(servers)) + statuses := make(chan api.ComponentStatus, len(servers)) + for k, v := range servers { + go func(name string, server apiserver.Server) { + defer wait.Done() + status := rs.getComponentStatus(name, server) + statuses <- *status + }(k, v) + } + wait.Wait() + close(statuses) + + reply := []api.ComponentStatus{} + for status := range statuses { + reply = append(reply, status) + } + return &api.ComponentStatusList{Items: reply}, nil +} + +func (rs *REST) Get(ctx api.Context, name string) (runtime.Object, error) { + servers := rs.GetServersToValidate() + + if server, ok := servers[name]; !ok { + return nil, fmt.Errorf("Component not found: %s", name) + } else { + return rs.getComponentStatus(name, server), nil + } +} + +func ToConditionStatus(s probe.Result) api.ConditionStatus { + switch s { + case probe.Success: + return api.ConditionTrue + case probe.Failure: + return api.ConditionFalse + default: + return api.ConditionUnknown + } +} + +func (rs *REST) getComponentStatus(name string, server apiserver.Server) *api.ComponentStatus { + status, msg, err := server.DoServerCheck(rs.prober) + errorMsg := "" + if err != nil { + errorMsg = err.Error() + } + + c := &api.ComponentCondition{ + Type: api.ComponentHealthy, + Status: ToConditionStatus(status), + Message: msg, + Error: errorMsg, + } + + retVal := &api.ComponentStatus{ + Conditions: []api.ComponentCondition{*c}, + } + retVal.Name = name + + return retVal +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/componentstatus/rest_test.go b/vendor/k8s.io/kubernetes/pkg/registry/componentstatus/rest_test.go new file mode 100644 index 000000000..526f1a079 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/componentstatus/rest_test.go @@ -0,0 +1,140 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 componentstatus + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apiserver" + "k8s.io/kubernetes/pkg/probe" + "k8s.io/kubernetes/pkg/util/diff" + "net/http" + "net/url" + "time" +) + +type fakeHttpProber struct { + result probe.Result + body string + err error +} + +func (f *fakeHttpProber) Probe(*url.URL, http.Header, time.Duration) (probe.Result, string, error) { + return f.result, f.body, f.err +} + +type testResponse struct { + result probe.Result + data string + err error +} + +func NewTestREST(resp testResponse) *REST { + return &REST{ + GetServersToValidate: func() map[string]apiserver.Server { + return map[string]apiserver.Server{ + "test1": {Addr: "testserver1", Port: 8000, Path: "/healthz"}, + } + }, + prober: &fakeHttpProber{ + result: resp.result, + body: resp.data, + err: resp.err, + }, + } +} + +func createTestStatus(name string, status api.ConditionStatus, msg string, err string) *api.ComponentStatus { + retVal := &api.ComponentStatus{ + Conditions: []api.ComponentCondition{ + {Type: api.ComponentHealthy, Status: status, Message: msg, Error: err}, + }, + } + retVal.Name = name + return retVal +} + +func TestList_NoError(t *testing.T) { + r := NewTestREST(testResponse{result: probe.Success, data: "ok"}) + got, err := r.List(api.NewContext(), nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + expect := &api.ComponentStatusList{ + Items: []api.ComponentStatus{*(createTestStatus("test1", api.ConditionTrue, "ok", ""))}, + } + if e, a := expect, got; !reflect.DeepEqual(e, a) { + t.Errorf("Got unexpected object. Diff: %s", diff.ObjectDiff(e, a)) + } +} + +func TestList_FailedCheck(t *testing.T) { + r := NewTestREST(testResponse{result: probe.Failure, data: ""}) + got, err := r.List(api.NewContext(), nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + expect := &api.ComponentStatusList{ + Items: []api.ComponentStatus{ + *(createTestStatus("test1", api.ConditionFalse, "", ""))}, + } + if e, a := expect, got; !reflect.DeepEqual(e, a) { + t.Errorf("Got unexpected object. Diff: %s", diff.ObjectDiff(e, a)) + } +} + +func TestList_UnknownError(t *testing.T) { + r := NewTestREST(testResponse{result: probe.Unknown, data: "", err: fmt.Errorf("fizzbuzz error")}) + got, err := r.List(api.NewContext(), nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + expect := &api.ComponentStatusList{ + Items: []api.ComponentStatus{ + *(createTestStatus("test1", api.ConditionUnknown, "", "fizzbuzz error"))}, + } + if e, a := expect, got; !reflect.DeepEqual(e, a) { + t.Errorf("Got unexpected object. Diff: %s", diff.ObjectDiff(e, a)) + } +} + +func TestGet_NoError(t *testing.T) { + r := NewTestREST(testResponse{result: probe.Success, data: "ok"}) + got, err := r.Get(api.NewContext(), "test1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + expect := createTestStatus("test1", api.ConditionTrue, "ok", "") + if e, a := expect, got; !reflect.DeepEqual(e, a) { + t.Errorf("Got unexpected object. Diff: %s", diff.ObjectDiff(e, a)) + } +} + +func TestGet_BadName(t *testing.T) { + r := NewTestREST(testResponse{result: probe.Success, data: "ok"}) + _, err := r.Get(api.NewContext(), "invalidname") + if err == nil { + t.Fatalf("Expected error, but did not get one") + } + if !strings.Contains(err.Error(), "Component not found: invalidname") { + t.Fatalf("Got unexpected error: %v", err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/configmap/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/configmap/doc.go new file mode 100644 index 000000000..ec8cc087d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/configmap/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 configmap provides Registry interface +// and its REST implementation for storing +// ConfigMap API objects. +package configmap diff --git a/vendor/k8s.io/kubernetes/pkg/registry/configmap/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/configmap/etcd/etcd.go new file mode 100644 index 000000000..2e0702c25 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/configmap/etcd/etcd.go @@ -0,0 +1,80 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/registry/configmap" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" +) + +// REST implements a RESTStorage for ConfigMap against etcd +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work with ConfigMap objects. +func NewREST(opts generic.RESTOptions) *REST { + prefix := "/configmaps" + + newListFunc := func() runtime.Object { return &api.ConfigMapList{} } + storageInterface := opts.Decorator( + opts.Storage, 100, &api.ConfigMap{}, prefix, configmap.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { + return &api.ConfigMap{} + }, + + // NewListFunc returns an object to store results of an etcd list. + NewListFunc: newListFunc, + + // Produces a path that etcd understands, to the root of the resource + // by combining the namespace in the context with the given prefix. + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + + // Produces a path that etcd understands, to the resource by combining + // the namespace in the context with the given prefix + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + + // Retrieves the name field of a ConfigMap object. + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.ConfigMap).Name, nil + }, + + // Matches objects based on labels/fields for list and watch + PredicateFunc: configmap.MatchConfigMap, + + QualifiedResource: api.Resource("configmaps"), + + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: configmap.Strategy, + UpdateStrategy: configmap.Strategy, + DeleteStrategy: configmap.Strategy, + + Storage: storageInterface, + } + return &REST{store} +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/configmap/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/configmap/etcd/etcd_test.go new file mode 100644 index 000000000..d2a81c281 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/configmap/etcd/etcd_test.go @@ -0,0 +1,149 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + return NewREST(restOptions), server +} + +func validNewConfigMap() *api.ConfigMap { + return &api.ConfigMap{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "default", + Labels: map[string]string{ + "label-1": "value-1", + "label-2": "value-2", + }, + }, + Data: map[string]string{ + "test": "data", + }, + } +} + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + + validConfigMap := validNewConfigMap() + validConfigMap.ObjectMeta = api.ObjectMeta{ + GenerateName: "foo-", + } + + test.TestCreate( + validConfigMap, + &api.ConfigMap{ + ObjectMeta: api.ObjectMeta{Name: "badName"}, + Data: map[string]string{ + "key": "value", + }, + }, + &api.ConfigMap{ + ObjectMeta: api.ObjectMeta{Name: "name-2"}, + Data: map[string]string{ + "..dotfile": "do: nothing\n", + }, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestUpdate( + // valid + validNewConfigMap(), + // updateFunc + func(obj runtime.Object) runtime.Object { + cfg := obj.(*api.ConfigMap) + cfg.Data["update-test"] = "value" + return cfg + }, + // invalid updateFunc + func(obj runtime.Object) runtime.Object { + cfg := obj.(*api.ConfigMap) + cfg.Data["badKey"] = "value" + return cfg + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestDelete(validNewConfigMap()) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(validNewConfigMap()) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(validNewConfigMap()) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validNewConfigMap(), + // matching labels + []labels.Set{ + {"label-1": "value-1"}, + {"label-2": "value-2"}, + }, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.namespace": "default"}, + {"metadata.name": "foo"}, + }, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + {"name": "foo"}, + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/configmap/registry.go b/vendor/k8s.io/kubernetes/pkg/registry/configmap/registry.go new file mode 100644 index 000000000..f94def472 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/configmap/registry.go @@ -0,0 +1,90 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 configmap + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface for things that know how to store ConfigMaps. +type Registry interface { + ListConfigMaps(ctx api.Context, options *api.ListOptions) (*api.ConfigMapList, error) + WatchConfigMaps(ctx api.Context, options *api.ListOptions) (watch.Interface, error) + GetConfigMap(ctx api.Context, name string) (*api.ConfigMap, error) + CreateConfigMap(ctx api.Context, cfg *api.ConfigMap) (*api.ConfigMap, error) + UpdateConfigMap(ctx api.Context, cfg *api.ConfigMap) (*api.ConfigMap, error) + DeleteConfigMap(ctx api.Context, name string) error +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListConfigMaps(ctx api.Context, options *api.ListOptions) (*api.ConfigMapList, error) { + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + + return obj.(*api.ConfigMapList), err +} + +func (s *storage) WatchConfigMaps(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetConfigMap(ctx api.Context, name string) (*api.ConfigMap, error) { + obj, err := s.Get(ctx, name) + if err != nil { + return nil, err + } + + return obj.(*api.ConfigMap), nil +} + +func (s *storage) CreateConfigMap(ctx api.Context, cfg *api.ConfigMap) (*api.ConfigMap, error) { + obj, err := s.Create(ctx, cfg) + if err != nil { + return nil, err + } + + return obj.(*api.ConfigMap), nil +} + +func (s *storage) UpdateConfigMap(ctx api.Context, cfg *api.ConfigMap) (*api.ConfigMap, error) { + obj, _, err := s.Update(ctx, cfg) + if err != nil { + return nil, err + } + + return obj.(*api.ConfigMap), nil +} + +func (s *storage) DeleteConfigMap(ctx api.Context, name string) error { + _, err := s.Delete(ctx, name, nil) + + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/configmap/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/configmap/strategy.go new file mode 100644 index 000000000..a07a7b917 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/configmap/strategy.go @@ -0,0 +1,104 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 configmap + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// strategy implements behavior for ConfigMap objects +type strategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating ConfigMap +// objects via the REST API. +var Strategy = strategy{api.Scheme, api.SimpleNameGenerator} + +// Strategy should implement rest.RESTCreateStrategy +var _ rest.RESTCreateStrategy = Strategy + +// Strategy should implement rest.RESTUpdateStrategy +var _ rest.RESTUpdateStrategy = Strategy + +func (strategy) NamespaceScoped() bool { + return true +} + +func (strategy) PrepareForCreate(obj runtime.Object) { + _ = obj.(*api.ConfigMap) +} + +func (strategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + cfg := obj.(*api.ConfigMap) + + return validation.ValidateConfigMap(cfg) +} + +// Canonicalize normalizes the object after validation. +func (strategy) Canonicalize(obj runtime.Object) { +} + +func (strategy) AllowCreateOnUpdate() bool { + return false +} + +func (strategy) PrepareForUpdate(newObj, oldObj runtime.Object) { + _ = oldObj.(*api.ConfigMap) + _ = newObj.(*api.ConfigMap) +} + +func (strategy) AllowUnconditionalUpdate() bool { + return true +} + +func (strategy) ValidateUpdate(ctx api.Context, newObj, oldObj runtime.Object) field.ErrorList { + oldCfg, newCfg := oldObj.(*api.ConfigMap), newObj.(*api.ConfigMap) + + return validation.ValidateConfigMapUpdate(newCfg, oldCfg) +} + +// ConfigMapToSelectableFields returns a field set that represents the object for matching purposes. +func ConfigMapToSelectableFields(cfg *api.ConfigMap) fields.Set { + return generic.ObjectMetaFieldsSet(cfg.ObjectMeta, true) +} + +// MatchConfigMap returns a generic matcher for a given label and field selector. +func MatchConfigMap(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + cfg, ok := obj.(*api.ConfigMap) + if !ok { + return nil, nil, fmt.Errorf("given object is not of type ConfigMap") + } + + return labels.Set(cfg.ObjectMeta.Labels), ConfigMapToSelectableFields(cfg), nil + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/configmap/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/configmap/strategy_test.go new file mode 100644 index 000000000..e2b06790d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/configmap/strategy_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 configmap + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/labels" +) + +func TestConfigMapStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if !Strategy.NamespaceScoped() { + t.Errorf("ConfigMap must be namespace scoped") + } + if Strategy.AllowCreateOnUpdate() { + t.Errorf("ConfigMap should not allow create on update") + } + + cfg := &api.ConfigMap{ + ObjectMeta: api.ObjectMeta{ + Name: "valid-config-data", + Namespace: api.NamespaceDefault, + }, + Data: map[string]string{ + "foo": "bar", + }, + } + + Strategy.PrepareForCreate(cfg) + + errs := Strategy.Validate(ctx, cfg) + if len(errs) != 0 { + t.Errorf("unexpected error validating %v", errs) + } + + newCfg := &api.ConfigMap{ + ObjectMeta: api.ObjectMeta{ + Name: "valid-config-data-2", + Namespace: api.NamespaceDefault, + ResourceVersion: "4", + }, + Data: map[string]string{ + "invalidKey": "updatedValue", + }, + } + + Strategy.PrepareForUpdate(newCfg, cfg) + + errs = Strategy.ValidateUpdate(ctx, newCfg, cfg) + if len(errs) == 0 { + t.Errorf("Expected a validation error") + } +} + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "ConfigMap", + labels.Set(ConfigMapToSelectableFields(&api.ConfigMap{})), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/controller/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/controller/doc.go new file mode 100644 index 000000000..4c0d14fab --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/controller/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 controller provides Registry interface and it's RESTStorage +// implementation for storing ReplicationController api objects. +package controller diff --git a/vendor/k8s.io/kubernetes/pkg/registry/controller/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/controller/etcd/etcd.go new file mode 100644 index 000000000..6886e973b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/controller/etcd/etcd.go @@ -0,0 +1,188 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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. +*/ + +// If you make changes to this file, you should also make the corresponding change in ReplicaSet. + +package etcd + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/autoscaling" + "k8s.io/kubernetes/pkg/apis/autoscaling/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/controller" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/runtime" +) + +// ControllerStorage includes dummy storage for Replication Controllers and for Scale subresource. +type ControllerStorage struct { + Controller *REST + Status *StatusREST + Scale *ScaleREST +} + +func NewStorage(opts generic.RESTOptions) ControllerStorage { + controllerREST, statusREST := NewREST(opts) + controllerRegistry := controller.NewRegistry(controllerREST) + + return ControllerStorage{ + Controller: controllerREST, + Status: statusREST, + Scale: &ScaleREST{registry: controllerRegistry}, + } +} + +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against replication controllers. +func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { + prefix := "/controllers" + + newListFunc := func() runtime.Object { return &api.ReplicationControllerList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.Controllers), &api.ReplicationController{}, prefix, controller.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.ReplicationController{} }, + + // NewListFunc returns an object capable of storing results of an etcd list. + NewListFunc: newListFunc, + // Produces a path that etcd understands, to the root of the resource + // by combining the namespace in the context with the given prefix + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + // Produces a path that etcd understands, to the resource by combining + // the namespace in the context with the given prefix + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + // Retrieve the name field of a replication controller + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.ReplicationController).Name, nil + }, + // Used to match objects based on labels/fields for list and watch + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return controller.MatchController(label, field) + }, + QualifiedResource: api.Resource("replicationcontrollers"), + + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + // Used to validate controller creation + CreateStrategy: controller.Strategy, + + // Used to validate controller updates + UpdateStrategy: controller.Strategy, + DeleteStrategy: controller.Strategy, + + Storage: storageInterface, + } + statusStore := *store + statusStore.UpdateStrategy = controller.StatusStrategy + + return &REST{store}, &StatusREST{store: &statusStore} +} + +// StatusREST implements the REST endpoint for changing the status of a replication controller +type StatusREST struct { + store *etcdgeneric.Etcd +} + +func (r *StatusREST) New() runtime.Object { + return &api.ReplicationController{} +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} + +type ScaleREST struct { + registry controller.Registry +} + +// ScaleREST implements Patcher +var _ = rest.Patcher(&ScaleREST{}) + +// New creates a new Scale object +func (r *ScaleREST) New() runtime.Object { + return &autoscaling.Scale{} +} + +func (r *ScaleREST) Get(ctx api.Context, name string) (runtime.Object, error) { + rc, err := r.registry.GetController(ctx, name) + if err != nil { + return nil, errors.NewNotFound(autoscaling.Resource("replicationcontrollers/scale"), name) + } + return scaleFromRC(rc), nil +} + +func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + if obj == nil { + return nil, false, errors.NewBadRequest("nil update passed to Scale") + } + scale, ok := obj.(*autoscaling.Scale) + if !ok { + return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj)) + } + + if errs := validation.ValidateScale(scale); len(errs) > 0 { + return nil, false, errors.NewInvalid(autoscaling.Kind("Scale"), scale.Name, errs) + } + + rc, err := r.registry.GetController(ctx, scale.Name) + if err != nil { + return nil, false, errors.NewNotFound(autoscaling.Resource("replicationcontrollers/scale"), scale.Name) + } + rc.Spec.Replicas = scale.Spec.Replicas + rc.ResourceVersion = scale.ResourceVersion + rc, err = r.registry.UpdateController(ctx, rc) + if err != nil { + return nil, false, err + } + return scaleFromRC(rc), false, nil +} + +// scaleFromRC returns a scale subresource for a replication controller. +func scaleFromRC(rc *api.ReplicationController) *autoscaling.Scale { + return &autoscaling.Scale{ + ObjectMeta: api.ObjectMeta{ + Name: rc.Name, + Namespace: rc.Namespace, + UID: rc.UID, + ResourceVersion: rc.ResourceVersion, + CreationTimestamp: rc.CreationTimestamp, + }, + Spec: autoscaling.ScaleSpec{ + Replicas: rc.Spec.Replicas, + }, + Status: autoscaling.ScaleStatus{ + Replicas: rc.Status.Replicas, + Selector: labels.SelectorFromSet(rc.Spec.Selector).String(), + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/controller/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/controller/etcd/etcd_test.go new file mode 100644 index 000000000..164550564 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/controller/etcd/etcd_test.go @@ -0,0 +1,310 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/apis/autoscaling" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + "k8s.io/kubernetes/pkg/util/diff" +) + +const ( + namespace = api.NamespaceDefault + name = "foo" +) + +func newStorage(t *testing.T) (ControllerStorage, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + storage := NewStorage(restOptions) + return storage, server +} + +// createController is a helper function that returns a controller with the updated resource version. +func createController(storage *REST, rc api.ReplicationController, t *testing.T) (api.ReplicationController, error) { + ctx := api.WithNamespace(api.NewContext(), rc.Namespace) + obj, err := storage.Create(ctx, &rc) + if err != nil { + t.Errorf("Failed to create controller, %v", err) + } + newRc := obj.(*api.ReplicationController) + return *newRc, nil +} + +func validNewController() *api.ReplicationController { + return &api.ReplicationController{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: api.ReplicationControllerSpec{ + Selector: map[string]string{"a": "b"}, + Template: &api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"a": "b"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "test", + Image: "test_image", + ImagePullPolicy: api.PullIfNotPresent, + }, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, + }, + } +} + +var validController = validNewController() + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Controller.Etcd) + controller := validNewController() + controller.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + controller, + // invalid (invalid selector) + &api.ReplicationController{ + Spec: api.ReplicationControllerSpec{ + Replicas: 2, + Selector: map[string]string{}, + Template: validController.Spec.Template, + }, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Controller.Etcd) + test.TestUpdate( + // valid + validNewController(), + // valid updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*api.ReplicationController) + object.Spec.Replicas = object.Spec.Replicas + 1 + return object + }, + // invalid updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*api.ReplicationController) + object.Name = "" + return object + }, + func(obj runtime.Object) runtime.Object { + object := obj.(*api.ReplicationController) + object.Spec.Selector = map[string]string{} + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Controller.Etcd) + test.TestDelete(validNewController()) +} + +func TestGenerationNumber(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + modifiedSno := *validNewController() + modifiedSno.Generation = 100 + modifiedSno.Status.ObservedGeneration = 10 + ctx := api.NewDefaultContext() + rc, err := createController(storage.Controller, modifiedSno, t) + ctrl, err := storage.Controller.Get(ctx, rc.Name) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + controller, _ := ctrl.(*api.ReplicationController) + + // Generation initialization + if controller.Generation != 1 && controller.Status.ObservedGeneration != 0 { + t.Fatalf("Unexpected generation number %v, status generation %v", controller.Generation, controller.Status.ObservedGeneration) + } + + // Updates to spec should increment the generation number + controller.Spec.Replicas += 1 + storage.Controller.Update(ctx, controller) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + ctrl, err = storage.Controller.Get(ctx, rc.Name) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + controller, _ = ctrl.(*api.ReplicationController) + if controller.Generation != 2 || controller.Status.ObservedGeneration != 0 { + t.Fatalf("Unexpected generation, spec: %v, status: %v", controller.Generation, controller.Status.ObservedGeneration) + } + + // Updates to status should not increment either spec or status generation numbers + controller.Status.Replicas += 1 + storage.Controller.Update(ctx, controller) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + ctrl, err = storage.Controller.Get(ctx, rc.Name) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + controller, _ = ctrl.(*api.ReplicationController) + if controller.Generation != 2 || controller.Status.ObservedGeneration != 0 { + t.Fatalf("Unexpected generation number, spec: %v, status: %v", controller.Generation, controller.Status.ObservedGeneration) + } +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Controller.Etcd) + test.TestGet(validNewController()) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Controller.Etcd) + test.TestList(validNewController()) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Controller.Etcd) + test.TestWatch( + validController, + // matching labels + []labels.Set{ + {"a": "b"}, + }, + // not matching labels + []labels.Set{ + {"a": "c"}, + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"status.replicas": "0"}, + {"metadata.name": "foo"}, + {"status.replicas": "0", "metadata.name": "foo"}, + }, + // not matchin fields + []fields.Set{ + {"status.replicas": "10"}, + {"metadata.name": "bar"}, + {"name": "foo"}, + {"status.replicas": "10", "metadata.name": "foo"}, + {"status.replicas": "0", "metadata.name": "bar"}, + }, + ) +} + +//TODO TestUpdateStatus + +func TestScaleGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + + ctx := api.WithNamespace(api.NewContext(), namespace) + rc, err := createController(storage.Controller, *validController, t) + if err != nil { + t.Fatalf("error setting new replication controller %v: %v", *validController, err) + } + + want := &autoscaling.Scale{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: namespace, + UID: rc.UID, + ResourceVersion: rc.ResourceVersion, + CreationTimestamp: rc.CreationTimestamp, + }, + Spec: autoscaling.ScaleSpec{ + Replicas: validController.Spec.Replicas, + }, + Status: autoscaling.ScaleStatus{ + Replicas: validController.Status.Replicas, + Selector: labels.SelectorFromSet(validController.Spec.Template.Labels).String(), + }, + } + obj, err := storage.Scale.Get(ctx, name) + if err != nil { + t.Fatalf("error fetching scale for %s: %v", name, err) + } + got := obj.(*autoscaling.Scale) + if !api.Semantic.DeepEqual(want, got) { + t.Errorf("unexpected scale: %s", diff.ObjectDiff(want, got)) + } +} + +func TestScaleUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + + ctx := api.WithNamespace(api.NewContext(), namespace) + rc, err := createController(storage.Controller, *validController, t) + if err != nil { + t.Fatalf("error setting new replication controller %v: %v", *validController, err) + } + replicas := 12 + update := autoscaling.Scale{ + ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace}, + Spec: autoscaling.ScaleSpec{ + Replicas: replicas, + }, + } + + if _, _, err := storage.Scale.Update(ctx, &update); err != nil { + t.Fatalf("error updating scale %v: %v", update, err) + } + obj, err := storage.Scale.Get(ctx, name) + if err != nil { + t.Fatalf("error fetching scale for %s: %v", name, err) + } + scale := obj.(*autoscaling.Scale) + if scale.Spec.Replicas != replicas { + t.Errorf("wrong replicas count expected: %d got: %d", replicas, rc.Spec.Replicas) + } + + update.ResourceVersion = rc.ResourceVersion + update.Spec.Replicas = 15 + + if _, _, err = storage.Scale.Update(ctx, &update); err != nil && !errors.IsConflict(err) { + t.Fatalf("unexpected error, expecting an update conflict but got %v", err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/controller/registry.go b/vendor/k8s.io/kubernetes/pkg/registry/controller/registry.go new file mode 100644 index 000000000..e3ecfa6a6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/controller/registry.go @@ -0,0 +1,92 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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. +*/ + +// If you make changes to this file, you should also make the corresponding change in ReplicaSet. + +package controller + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface for things that know how to store ReplicationControllers. +type Registry interface { + ListControllers(ctx api.Context, options *api.ListOptions) (*api.ReplicationControllerList, error) + WatchControllers(ctx api.Context, options *api.ListOptions) (watch.Interface, error) + GetController(ctx api.Context, controllerID string) (*api.ReplicationController, error) + CreateController(ctx api.Context, controller *api.ReplicationController) (*api.ReplicationController, error) + UpdateController(ctx api.Context, controller *api.ReplicationController) (*api.ReplicationController, error) + DeleteController(ctx api.Context, controllerID string) error +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListControllers(ctx api.Context, options *api.ListOptions) (*api.ReplicationControllerList, error) { + if options != nil && options.FieldSelector != nil && !options.FieldSelector.Empty() { + return nil, fmt.Errorf("field selector not supported yet") + } + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + return obj.(*api.ReplicationControllerList), err +} + +func (s *storage) WatchControllers(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetController(ctx api.Context, controllerID string) (*api.ReplicationController, error) { + obj, err := s.Get(ctx, controllerID) + if err != nil { + return nil, err + } + return obj.(*api.ReplicationController), nil +} + +func (s *storage) CreateController(ctx api.Context, controller *api.ReplicationController) (*api.ReplicationController, error) { + obj, err := s.Create(ctx, controller) + if err != nil { + return nil, err + } + return obj.(*api.ReplicationController), nil +} + +func (s *storage) UpdateController(ctx api.Context, controller *api.ReplicationController) (*api.ReplicationController, error) { + obj, _, err := s.Update(ctx, controller) + if err != nil { + return nil, err + } + return obj.(*api.ReplicationController), nil +} + +func (s *storage) DeleteController(ctx api.Context, controllerID string) error { + _, err := s.Delete(ctx, controllerID, nil) + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/controller/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/controller/strategy.go new file mode 100644 index 000000000..f2c19391b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/controller/strategy.go @@ -0,0 +1,145 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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. +*/ + +// If you make changes to this file, you should also make the corresponding change in ReplicaSet. + +package controller + +import ( + "fmt" + "reflect" + "strconv" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// rcStrategy implements verification logic for Replication Controllers. +type rcStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Replication Controller objects. +var Strategy = rcStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped returns true because all Replication Controllers need to be within a namespace. +func (rcStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears the status of a replication controller before creation. +func (rcStrategy) PrepareForCreate(obj runtime.Object) { + controller := obj.(*api.ReplicationController) + controller.Status = api.ReplicationControllerStatus{} + + controller.Generation = 1 +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (rcStrategy) PrepareForUpdate(obj, old runtime.Object) { + newController := obj.(*api.ReplicationController) + oldController := old.(*api.ReplicationController) + // update is not allowed to set status + newController.Status = oldController.Status + + // Any changes to the spec increment the generation number, any changes to the + // status should reflect the generation number of the corresponding object. We push + // the burden of managing the status onto the clients because we can't (in general) + // know here what version of spec the writer of the status has seen. It may seem like + // we can at first -- since obj contains spec -- but in the future we will probably make + // status its own object, and even if we don't, writes may be the result of a + // read-update-write loop, so the contents of spec may not actually be the spec that + // the controller has *seen*. + if !reflect.DeepEqual(oldController.Spec, newController.Spec) { + newController.Generation = oldController.Generation + 1 + } +} + +// Validate validates a new replication controller. +func (rcStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + controller := obj.(*api.ReplicationController) + return validation.ValidateReplicationController(controller) +} + +// Canonicalize normalizes the object after validation. +func (rcStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is false for replication controllers; this means a POST is +// needed to create one. +func (rcStrategy) AllowCreateOnUpdate() bool { + return false +} + +// ValidateUpdate is the default update validation for an end user. +func (rcStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + validationErrorList := validation.ValidateReplicationController(obj.(*api.ReplicationController)) + updateErrorList := validation.ValidateReplicationControllerUpdate(obj.(*api.ReplicationController), old.(*api.ReplicationController)) + return append(validationErrorList, updateErrorList...) +} + +func (rcStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// ControllerToSelectableFields returns a field set that represents the object. +func ControllerToSelectableFields(controller *api.ReplicationController) fields.Set { + objectMetaFieldsSet := generic.ObjectMetaFieldsSet(controller.ObjectMeta, true) + controllerSpecificFieldsSet := fields.Set{ + "status.replicas": strconv.Itoa(controller.Status.Replicas), + } + return generic.MergeFieldsSets(objectMetaFieldsSet, controllerSpecificFieldsSet) +} + +// MatchController is the filter used by the generic etcd backend to route +// watch events from etcd to clients of the apiserver only interested in specific +// labels/fields. +func MatchController(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + rc, ok := obj.(*api.ReplicationController) + if !ok { + return nil, nil, fmt.Errorf("Given object is not a replication controller.") + } + return labels.Set(rc.ObjectMeta.Labels), ControllerToSelectableFields(rc), nil + }, + } +} + +type rcStatusStrategy struct { + rcStrategy +} + +var StatusStrategy = rcStatusStrategy{Strategy} + +func (rcStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newRc := obj.(*api.ReplicationController) + oldRc := old.(*api.ReplicationController) + // update is not allowed to set spec + newRc.Spec = oldRc.Spec +} + +func (rcStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateReplicationControllerStatusUpdate(obj.(*api.ReplicationController), old.(*api.ReplicationController)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/controller/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/controller/strategy_test.go new file mode 100644 index 000000000..bf530b584 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/controller/strategy_test.go @@ -0,0 +1,152 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 controller + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/labels" +) + +func TestControllerStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if !Strategy.NamespaceScoped() { + t.Errorf("ReplicationController must be namespace scoped") + } + if Strategy.AllowCreateOnUpdate() { + t.Errorf("ReplicationController should not allow create on update") + } + + validSelector := map[string]string{"a": "b"} + validPodTemplate := api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validSelector, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + }, + } + rc := &api.ReplicationController{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: api.ReplicationControllerSpec{ + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + Status: api.ReplicationControllerStatus{ + Replicas: 1, + ObservedGeneration: int64(10), + }, + } + + Strategy.PrepareForCreate(rc) + if rc.Status.Replicas != 0 { + t.Error("ReplicationController should not allow setting status.replicas on create") + } + if rc.Status.ObservedGeneration != int64(0) { + t.Error("ReplicationController should not allow setting status.observedGeneration on create") + } + errs := Strategy.Validate(ctx, rc) + if len(errs) != 0 { + t.Errorf("Unexpected error validating %v", errs) + } + + invalidRc := &api.ReplicationController{ + ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "4"}, + } + Strategy.PrepareForUpdate(invalidRc, rc) + errs = Strategy.ValidateUpdate(ctx, invalidRc, rc) + if len(errs) == 0 { + t.Errorf("Expected a validation error") + } + if invalidRc.ResourceVersion != "4" { + t.Errorf("Incoming resource version on update should not be mutated") + } +} + +func TestControllerStatusStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if !StatusStrategy.NamespaceScoped() { + t.Errorf("ReplicationController must be namespace scoped") + } + if StatusStrategy.AllowCreateOnUpdate() { + t.Errorf("ReplicationController should not allow create on update") + } + validSelector := map[string]string{"a": "b"} + validPodTemplate := api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validSelector, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + }, + } + oldController := &api.ReplicationController{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault, ResourceVersion: "10"}, + Spec: api.ReplicationControllerSpec{ + Replicas: 3, + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + Status: api.ReplicationControllerStatus{ + Replicas: 1, + ObservedGeneration: int64(10), + }, + } + newController := &api.ReplicationController{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault, ResourceVersion: "9"}, + Spec: api.ReplicationControllerSpec{ + Replicas: 1, + Selector: validSelector, + Template: &validPodTemplate.Template, + }, + Status: api.ReplicationControllerStatus{ + Replicas: 3, + ObservedGeneration: int64(11), + }, + } + StatusStrategy.PrepareForUpdate(newController, oldController) + if newController.Status.Replicas != 3 { + t.Errorf("Replication controller status updates should allow change of replicas: %v", newController.Status.Replicas) + } + if newController.Spec.Replicas != 3 { + t.Errorf("PrepareForUpdate should have preferred spec") + } + errs := StatusStrategy.ValidateUpdate(ctx, newController, oldController) + if len(errs) != 0 { + t.Errorf("Unexpected error %v", errs) + } +} + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "ReplicationController", + labels.Set(ControllerToSelectableFields(&api.ReplicationController{})), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/daemonset/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/daemonset/doc.go new file mode 100644 index 000000000..435b0fe61 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/daemonset/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 daemonset provides Registry interface and its RESTStorage +// implementation for storing DaemonSet api objects. +package daemonset diff --git a/vendor/k8s.io/kubernetes/pkg/registry/daemonset/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/daemonset/etcd/etcd.go new file mode 100644 index 000000000..a24951f5b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/daemonset/etcd/etcd.go @@ -0,0 +1,97 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/daemonset" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/runtime" +) + +// rest implements a RESTStorage for DaemonSets against etcd +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against DaemonSets. +func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { + prefix := "/daemonsets" + + newListFunc := func() runtime.Object { return &extensions.DaemonSetList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.Daemonsets), &extensions.DaemonSet{}, prefix, daemonset.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &extensions.DaemonSet{} }, + + // NewListFunc returns an object capable of storing results of an etcd list. + NewListFunc: newListFunc, + // Produces a path that etcd understands, to the root of the resource + // by combining the namespace in the context with the given prefix + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + // Produces a path that etcd understands, to the resource by combining + // the namespace in the context with the given prefix + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + // Retrieve the name field of a daemon set + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*extensions.DaemonSet).Name, nil + }, + // Used to match objects based on labels/fields for list and watch + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return daemonset.MatchDaemonSet(label, field) + }, + QualifiedResource: extensions.Resource("daemonsets"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + // Used to validate daemon set creation + CreateStrategy: daemonset.Strategy, + + // Used to validate daemon set updates + UpdateStrategy: daemonset.Strategy, + DeleteStrategy: daemonset.Strategy, + + Storage: storageInterface, + } + statusStore := *store + statusStore.UpdateStrategy = daemonset.StatusStrategy + + return &REST{store}, &StatusREST{store: &statusStore} +} + +// StatusREST implements the REST endpoint for changing the status of a daemonset +type StatusREST struct { + store *etcdgeneric.Etcd +} + +func (r *StatusREST) New() runtime.Object { + return &extensions.DaemonSet{} +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/daemonset/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/daemonset/etcd/etcd_test.go new file mode 100644 index 000000000..b29870837 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/daemonset/etcd/etcd_test.go @@ -0,0 +1,172 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, extensions.GroupName) + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + daemonSetStorage, statusStorage := NewREST(restOptions) + return daemonSetStorage, statusStorage, server +} + +func newValidDaemonSet() *extensions.DaemonSet { + return &extensions.DaemonSet{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: extensions.DaemonSetSpec{ + Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"a": "b"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "test", + Image: "test_image", + ImagePullPolicy: api.PullIfNotPresent, + }, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, + }, + } +} + +var validDaemonSet = newValidDaemonSet() + +func TestCreate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + ds := newValidDaemonSet() + ds.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + ds, + // invalid (invalid selector) + &extensions.DaemonSet{ + Spec: extensions.DaemonSetSpec{ + Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{}}, + Template: validDaemonSet.Spec.Template, + }, + }, + // invalid update strategy + &extensions.DaemonSet{ + Spec: extensions.DaemonSetSpec{ + Selector: validDaemonSet.Spec.Selector, + Template: validDaemonSet.Spec.Template, + }, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestUpdate( + // valid + newValidDaemonSet(), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.DaemonSet) + object.Spec.Template.Spec.NodeSelector = map[string]string{"c": "d"} + object.Spec.Template.Spec.DNSPolicy = api.DNSDefault + return object + }, + // invalid updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.DaemonSet) + object.Name = "" + return object + }, + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.DaemonSet) + object.Spec.Template.Spec.RestartPolicy = api.RestartPolicyOnFailure + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestDelete(newValidDaemonSet()) +} + +func TestGet(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(newValidDaemonSet()) +} + +func TestList(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(newValidDaemonSet()) +} + +func TestWatch(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validDaemonSet, + // matching labels + []labels.Set{ + {"a": "b"}, + }, + // not matching labels + []labels.Set{ + {"a": "c"}, + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": "foo"}, + }, + // notmatching fields + []fields.Set{ + {"metadata.name": "bar"}, + {"name": "foo"}, + }, + ) +} + +// TODO TestUpdateStatus diff --git a/vendor/k8s.io/kubernetes/pkg/registry/daemonset/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/daemonset/strategy.go new file mode 100644 index 000000000..044d779b4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/daemonset/strategy.go @@ -0,0 +1,143 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 daemonset + +import ( + "fmt" + "reflect" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/extensions/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// daemonSetStrategy implements verification logic for daemon sets. +type daemonSetStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating DaemonSet objects. +var Strategy = daemonSetStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped returns true because all DaemonSets need to be within a namespace. +func (daemonSetStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears the status of a daemon set before creation. +func (daemonSetStrategy) PrepareForCreate(obj runtime.Object) { + daemonSet := obj.(*extensions.DaemonSet) + daemonSet.Status = extensions.DaemonSetStatus{} + + daemonSet.Generation = 1 +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (daemonSetStrategy) PrepareForUpdate(obj, old runtime.Object) { + newDaemonSet := obj.(*extensions.DaemonSet) + oldDaemonSet := old.(*extensions.DaemonSet) + + // update is not allowed to set status + newDaemonSet.Status = oldDaemonSet.Status + + // Any changes to the spec increment the generation number, any changes to the + // status should reflect the generation number of the corresponding object. We push + // the burden of managing the status onto the clients because we can't (in general) + // know here what version of spec the writer of the status has seen. It may seem like + // we can at first -- since obj contains spec -- but in the future we will probably make + // status its own object, and even if we don't, writes may be the result of a + // read-update-write loop, so the contents of spec may not actually be the spec that + // the manager has *seen*. + // + // TODO: Any changes to a part of the object that represents desired state (labels, + // annotations etc) should also increment the generation. + if !reflect.DeepEqual(oldDaemonSet.Spec, newDaemonSet.Spec) { + newDaemonSet.Generation = oldDaemonSet.Generation + 1 + } +} + +// Validate validates a new daemon set. +func (daemonSetStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + daemonSet := obj.(*extensions.DaemonSet) + return validation.ValidateDaemonSet(daemonSet) +} + +// Canonicalize normalizes the object after validation. +func (daemonSetStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is false for daemon set; this means a POST is +// needed to create one +func (daemonSetStrategy) AllowCreateOnUpdate() bool { + return false +} + +// ValidateUpdate is the default update validation for an end user. +func (daemonSetStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + validationErrorList := validation.ValidateDaemonSet(obj.(*extensions.DaemonSet)) + updateErrorList := validation.ValidateDaemonSetUpdate(obj.(*extensions.DaemonSet), old.(*extensions.DaemonSet)) + return append(validationErrorList, updateErrorList...) +} + +// AllowUnconditionalUpdate is the default update policy for daemon set objects. +func (daemonSetStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// DaemonSetToSelectableFields returns a field set that represents the object. +func DaemonSetToSelectableFields(daemon *extensions.DaemonSet) fields.Set { + return generic.ObjectMetaFieldsSet(daemon.ObjectMeta, true) +} + +// MatchSetDaemon is the filter used by the generic etcd backend to route +// watch events from etcd to clients of the apiserver only interested in specific +// labels/fields. +func MatchDaemonSet(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + ds, ok := obj.(*extensions.DaemonSet) + if !ok { + return nil, nil, fmt.Errorf("given object is not a ds.") + } + return labels.Set(ds.ObjectMeta.Labels), DaemonSetToSelectableFields(ds), nil + }, + } +} + +type daemonSetStatusStrategy struct { + daemonSetStrategy +} + +var StatusStrategy = daemonSetStatusStrategy{Strategy} + +func (daemonSetStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newDaemonSet := obj.(*extensions.DaemonSet) + oldDaemonSet := old.(*extensions.DaemonSet) + newDaemonSet.Spec = oldDaemonSet.Spec +} + +func (daemonSetStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateDaemonSetStatusUpdate(obj.(*extensions.DaemonSet), old.(*extensions.DaemonSet)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/daemonset/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/daemonset/strategy_test.go new file mode 100644 index 000000000..dd5ef836d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/daemonset/strategy_test.go @@ -0,0 +1,36 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 daemonset + +import ( + "testing" + + _ "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/labels" +) + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Extensions.GroupVersion().String(), + "DaemonSet", + labels.Set(DaemonSetToSelectableFields(&extensions.DaemonSet{})), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/deployment/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/deployment/doc.go new file mode 100644 index 000000000..184fa30d3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/deployment/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 deployment diff --git a/vendor/k8s.io/kubernetes/pkg/registry/deployment/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/deployment/etcd/etcd.go new file mode 100644 index 000000000..5335e0fec --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/deployment/etcd/etcd.go @@ -0,0 +1,257 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + storeerr "k8s.io/kubernetes/pkg/api/errors/storage" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/extensions" + extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/deployment" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" +) + +// DeploymentStorage includes dummy storage for Deployments and for Scale subresource. +type DeploymentStorage struct { + Deployment *REST + Status *StatusREST + Scale *ScaleREST + Rollback *RollbackREST +} + +func NewStorage(opts generic.RESTOptions) DeploymentStorage { + deploymentRest, deploymentStatusRest, deploymentRollbackRest := NewREST(opts) + deploymentRegistry := deployment.NewRegistry(deploymentRest) + + return DeploymentStorage{ + Deployment: deploymentRest, + Status: deploymentStatusRest, + Scale: &ScaleREST{registry: deploymentRegistry}, + Rollback: deploymentRollbackRest, + } +} + +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against deployments. +func NewREST(opts generic.RESTOptions) (*REST, *StatusREST, *RollbackREST) { + prefix := "/deployments" + + newListFunc := func() runtime.Object { return &extensions.DeploymentList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.Deployments), &extensions.Deployment{}, prefix, deployment.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &extensions.Deployment{} }, + // NewListFunc returns an object capable of storing results of an etcd list. + NewListFunc: newListFunc, + // Produces a path that etcd understands, to the root of the resource + // by combining the namespace in the context with the given prefix. + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + // Produces a path that etcd understands, to the resource by combining + // the namespace in the context with the given prefix. + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + // Retrieve the name field of a deployment. + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*extensions.Deployment).Name, nil + }, + // Used to match objects based on labels/fields for list. + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return deployment.MatchDeployment(label, field) + }, + QualifiedResource: extensions.Resource("deployments"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + // Used to validate deployment creation. + CreateStrategy: deployment.Strategy, + + // Used to validate deployment updates. + UpdateStrategy: deployment.Strategy, + DeleteStrategy: deployment.Strategy, + + Storage: storageInterface, + } + statusStore := *store + statusStore.UpdateStrategy = deployment.StatusStrategy + return &REST{store}, &StatusREST{store: &statusStore}, &RollbackREST{store: store} +} + +// StatusREST implements the REST endpoint for changing the status of a deployment +type StatusREST struct { + store *etcdgeneric.Etcd +} + +func (r *StatusREST) New() runtime.Object { + return &extensions.Deployment{} +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} + +// RollbackREST implements the REST endpoint for initiating the rollback of a deployment +type RollbackREST struct { + store *etcdgeneric.Etcd +} + +// New creates a rollback +func (r *RollbackREST) New() runtime.Object { + return &extensions.DeploymentRollback{} +} + +var _ = rest.Creater(&RollbackREST{}) + +func (r *RollbackREST) Create(ctx api.Context, obj runtime.Object) (out runtime.Object, err error) { + rollback, ok := obj.(*extensions.DeploymentRollback) + if !ok { + return nil, fmt.Errorf("expected input object type to be DeploymentRollback, but %T", obj) + } + + if errs := extvalidation.ValidateDeploymentRollback(rollback); len(errs) != 0 { + return nil, errors.NewInvalid(extensions.Kind("DeploymentRollback"), rollback.Name, errs) + } + + // Update the Deployment with information in DeploymentRollback to trigger rollback + err = r.rollbackDeployment(ctx, rollback.Name, &rollback.RollbackTo, rollback.UpdatedAnnotations) + return +} + +func (r *RollbackREST) rollbackDeployment(ctx api.Context, deploymentID string, config *extensions.RollbackConfig, annotations map[string]string) (err error) { + if _, err = r.setDeploymentRollback(ctx, deploymentID, config, annotations); err != nil { + err = storeerr.InterpretGetError(err, extensions.Resource("deployments"), deploymentID) + err = storeerr.InterpretUpdateError(err, extensions.Resource("deployments"), deploymentID) + if _, ok := err.(*errors.StatusError); !ok { + err = errors.NewConflict(extensions.Resource("deployments/rollback"), deploymentID, err) + } + } + return +} + +func (r *RollbackREST) setDeploymentRollback(ctx api.Context, deploymentID string, config *extensions.RollbackConfig, annotations map[string]string) (finalDeployment *extensions.Deployment, err error) { + dKey, err := r.store.KeyFunc(ctx, deploymentID) + if err != nil { + return nil, err + } + err = r.store.Storage.GuaranteedUpdate(ctx, dKey, &extensions.Deployment{}, false, nil, storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { + d, ok := obj.(*extensions.Deployment) + if !ok { + return nil, fmt.Errorf("unexpected object: %#v", obj) + } + if d.Annotations == nil { + d.Annotations = make(map[string]string) + } + for k, v := range annotations { + d.Annotations[k] = v + } + d.Spec.RollbackTo = config + finalDeployment = d + return d, nil + })) + return finalDeployment, err +} + +type ScaleREST struct { + registry deployment.Registry +} + +// ScaleREST implements Patcher +var _ = rest.Patcher(&ScaleREST{}) + +// New creates a new Scale object +func (r *ScaleREST) New() runtime.Object { + return &extensions.Scale{} +} + +func (r *ScaleREST) Get(ctx api.Context, name string) (runtime.Object, error) { + deployment, err := r.registry.GetDeployment(ctx, name) + if err != nil { + return nil, errors.NewNotFound(extensions.Resource("deployments/scale"), name) + } + scale, err := scaleFromDeployment(deployment) + if err != nil { + return nil, errors.NewBadRequest(fmt.Sprintf("%v", err)) + } + return scale, nil +} + +func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + if obj == nil { + return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) + } + scale, ok := obj.(*extensions.Scale) + if !ok { + return nil, false, errors.NewBadRequest(fmt.Sprintf("expected input object type to be Scale, but %T", obj)) + } + + if errs := extvalidation.ValidateScale(scale); len(errs) > 0 { + return nil, false, errors.NewInvalid(extensions.Kind("Scale"), scale.Name, errs) + } + + deployment, err := r.registry.GetDeployment(ctx, scale.Name) + if err != nil { + return nil, false, errors.NewNotFound(extensions.Resource("deployments/scale"), scale.Name) + } + deployment.Spec.Replicas = scale.Spec.Replicas + deployment.ResourceVersion = scale.ResourceVersion + deployment, err = r.registry.UpdateDeployment(ctx, deployment) + if err != nil { + return nil, false, err + } + newScale, err := scaleFromDeployment(deployment) + if err != nil { + return nil, false, errors.NewBadRequest(fmt.Sprintf("%v", err)) + } + return newScale, false, nil +} + +// scaleFromDeployment returns a scale subresource for a deployment. +func scaleFromDeployment(deployment *extensions.Deployment) (*extensions.Scale, error) { + return &extensions.Scale{ + // TODO: Create a variant of ObjectMeta type that only contains the fields below. + ObjectMeta: api.ObjectMeta{ + Name: deployment.Name, + Namespace: deployment.Namespace, + UID: deployment.UID, + ResourceVersion: deployment.ResourceVersion, + CreationTimestamp: deployment.CreationTimestamp, + }, + Spec: extensions.ScaleSpec{ + Replicas: deployment.Spec.Replicas, + }, + Status: extensions.ScaleStatus{ + Replicas: deployment.Status.Replicas, + Selector: deployment.Spec.Selector, + }, + }, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/deployment/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/deployment/etcd/etcd_test.go new file mode 100644 index 000000000..8201168d4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/deployment/etcd/etcd_test.go @@ -0,0 +1,375 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + storeerr "k8s.io/kubernetes/pkg/api/errors/storage" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage/etcd/etcdtest" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + "k8s.io/kubernetes/pkg/util/diff" +) + +const defaultReplicas = 100 + +func newStorage(t *testing.T) (*DeploymentStorage, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, extensions.GroupName) + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + deploymentStorage := NewStorage(restOptions) + return &deploymentStorage, server +} + +var namespace = "foo-namespace" +var name = "foo-deployment" + +func validNewDeployment() *extensions.Deployment { + return &extensions.Deployment{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: extensions.DeploymentSpec{ + Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"a": "b"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "test", + Image: "test_image", + ImagePullPolicy: api.PullIfNotPresent, + }, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, + Replicas: 7, + }, + Status: extensions.DeploymentStatus{ + Replicas: 5, + }, + } +} + +var validDeployment = *validNewDeployment() + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Deployment.Etcd) + deployment := validNewDeployment() + deployment.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + deployment, + // invalid (invalid selector) + &extensions.Deployment{ + Spec: extensions.DeploymentSpec{ + Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{}}, + Template: validDeployment.Spec.Template, + }, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Deployment.Etcd) + test.TestUpdate( + // valid + validNewDeployment(), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.Deployment) + object.Spec.Template.Spec.NodeSelector = map[string]string{"c": "d"} + return object + }, + // invalid updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.Deployment) + object.Name = "" + return object + }, + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.Deployment) + object.Spec.Template.Spec.RestartPolicy = api.RestartPolicyOnFailure + return object + }, + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.Deployment) + object.Spec.Selector = &unversioned.LabelSelector{MatchLabels: map[string]string{}} + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Deployment.Etcd) + test.TestDelete(validNewDeployment()) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Deployment.Etcd) + test.TestGet(validNewDeployment()) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Deployment.Etcd) + test.TestList(validNewDeployment()) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Deployment.Etcd) + test.TestWatch( + validNewDeployment(), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"a": "c"}, + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": name}, + }, + // not matchin fields + []fields.Set{ + {"metadata.name": "bar"}, + {"name": name}, + }, + ) +} + +func TestScaleGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + + var deployment extensions.Deployment + ctx := api.WithNamespace(api.NewContext(), namespace) + key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name) + if err := storage.Deployment.Storage.Set(ctx, key, &validDeployment, &deployment, 0); err != nil { + t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err) + } + + want := &extensions.Scale{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: namespace, + UID: deployment.UID, + ResourceVersion: deployment.ResourceVersion, + CreationTimestamp: deployment.CreationTimestamp, + }, + Spec: extensions.ScaleSpec{ + Replicas: validDeployment.Spec.Replicas, + }, + Status: extensions.ScaleStatus{ + Replicas: validDeployment.Status.Replicas, + Selector: validDeployment.Spec.Selector, + }, + } + obj, err := storage.Scale.Get(ctx, name) + if err != nil { + t.Fatalf("error fetching scale for %s: %v", name, err) + } + got := obj.(*extensions.Scale) + if !api.Semantic.DeepEqual(want, got) { + t.Errorf("unexpected scale: %s", diff.ObjectDiff(want, got)) + } +} + +func TestScaleUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + + var deployment extensions.Deployment + ctx := api.WithNamespace(api.NewContext(), namespace) + key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name) + if err := storage.Deployment.Storage.Set(ctx, key, &validDeployment, &deployment, 0); err != nil { + t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err) + } + replicas := 12 + update := extensions.Scale{ + ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace}, + Spec: extensions.ScaleSpec{ + Replicas: replicas, + }, + } + + if _, _, err := storage.Scale.Update(ctx, &update); err != nil { + t.Fatalf("error updating scale %v: %v", update, err) + } + obj, err := storage.Scale.Get(ctx, name) + if err != nil { + t.Fatalf("error fetching scale for %s: %v", name, err) + } + scale := obj.(*extensions.Scale) + if scale.Spec.Replicas != replicas { + t.Errorf("wrong replicas count expected: %d got: %d", replicas, deployment.Spec.Replicas) + } + + update.ResourceVersion = deployment.ResourceVersion + update.Spec.Replicas = 15 + + if _, _, err = storage.Scale.Update(ctx, &update); err != nil && !errors.IsConflict(err) { + t.Fatalf("unexpected error, expecting an update conflict but got %v", err) + } +} + +func TestStatusUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + + ctx := api.WithNamespace(api.NewContext(), namespace) + key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name) + if err := storage.Deployment.Storage.Set(ctx, key, &validDeployment, nil, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + update := extensions.Deployment{ + ObjectMeta: validDeployment.ObjectMeta, + Spec: extensions.DeploymentSpec{ + Replicas: defaultReplicas, + }, + Status: extensions.DeploymentStatus{ + Replicas: defaultReplicas, + }, + } + + if _, _, err := storage.Status.Update(ctx, &update); err != nil { + t.Fatalf("unexpected error: %v", err) + } + obj, err := storage.Deployment.Get(ctx, name) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + deployment := obj.(*extensions.Deployment) + if deployment.Spec.Replicas != 7 { + t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", deployment.Spec.Replicas) + } + if deployment.Status.Replicas != defaultReplicas { + t.Errorf("we expected .status.replicas to be updated to %d but it was %v", defaultReplicas, deployment.Status.Replicas) + } +} + +func TestEtcdCreateDeploymentRollback(t *testing.T) { + ctx := api.WithNamespace(api.NewContext(), namespace) + + testCases := map[string]struct { + rollback extensions.DeploymentRollback + errOK func(error) bool + }{ + "normal": { + rollback: extensions.DeploymentRollback{ + Name: name, + UpdatedAnnotations: map[string]string{}, + RollbackTo: extensions.RollbackConfig{Revision: 1}, + }, + errOK: func(err error) bool { return err == nil }, + }, + "noAnnotation": { + rollback: extensions.DeploymentRollback{ + Name: name, + RollbackTo: extensions.RollbackConfig{Revision: 1}, + }, + errOK: func(err error) bool { return err == nil }, + }, + "noName": { + rollback: extensions.DeploymentRollback{ + UpdatedAnnotations: map[string]string{}, + RollbackTo: extensions.RollbackConfig{Revision: 1}, + }, + errOK: func(err error) bool { return err != nil }, + }, + } + for k, test := range testCases { + storage, server := newStorage(t) + rollbackStorage := storage.Rollback + key, _ := storage.Deployment.KeyFunc(ctx, name) + key = etcdtest.AddPrefix(key) + + if _, err := storage.Deployment.Create(ctx, validNewDeployment()); err != nil { + t.Fatalf("%s: unexpected error: %v", k, err) + } + if _, err := rollbackStorage.Create(ctx, &test.rollback); !test.errOK(err) { + t.Errorf("%s: unexpected error: %v", k, err) + } else if err == nil { + // If rollback succeeded, verify Rollback field of deployment + d, err := storage.Deployment.Get(ctx, validNewDeployment().ObjectMeta.Name) + if err != nil { + t.Errorf("%s: unexpected error: %v", k, err) + } else if !reflect.DeepEqual(*d.(*extensions.Deployment).Spec.RollbackTo, test.rollback.RollbackTo) { + t.Errorf("%s: expected: %v, got: %v", k, *d.(*extensions.Deployment).Spec.RollbackTo, test.rollback.RollbackTo) + } + } + server.Terminate(t) + } +} + +// Ensure that when a deploymentRollback is created for a deployment that has already been deleted +// by the API server, API server returns not-found error. +func TestEtcdCreateDeploymentRollbackNoDeployment(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + rollbackStorage := storage.Rollback + ctx := api.WithNamespace(api.NewContext(), namespace) + + key, _ := storage.Deployment.KeyFunc(ctx, name) + key = etcdtest.AddPrefix(key) + _, err := rollbackStorage.Create(ctx, &extensions.DeploymentRollback{ + Name: name, + UpdatedAnnotations: map[string]string{}, + RollbackTo: extensions.RollbackConfig{Revision: 1}, + }) + if err == nil { + t.Fatalf("Expected not-found-error but got nothing") + } + if !errors.IsNotFound(storeerr.InterpretGetError(err, extensions.Resource("deployments"), name)) { + t.Fatalf("Unexpected error returned: %#v", err) + } + + _, err = storage.Deployment.Get(ctx, name) + if err == nil { + t.Fatalf("Expected not-found-error but got nothing") + } + if !errors.IsNotFound(storeerr.InterpretGetError(err, extensions.Resource("deployments"), name)) { + t.Fatalf("Unexpected error: %v", err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/deployment/registry.go b/vendor/k8s.io/kubernetes/pkg/registry/deployment/registry.go new file mode 100644 index 000000000..ec967e93d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/deployment/registry.go @@ -0,0 +1,84 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 deployment + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +// Registry is an interface for things that know how to store Deployments. +type Registry interface { + ListDeployments(ctx api.Context, options *api.ListOptions) (*extensions.DeploymentList, error) + GetDeployment(ctx api.Context, deploymentID string) (*extensions.Deployment, error) + CreateDeployment(ctx api.Context, deployment *extensions.Deployment) (*extensions.Deployment, error) + UpdateDeployment(ctx api.Context, deployment *extensions.Deployment) (*extensions.Deployment, error) + DeleteDeployment(ctx api.Context, deploymentID string) error +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListDeployments(ctx api.Context, options *api.ListOptions) (*extensions.DeploymentList, error) { + if options != nil && options.FieldSelector != nil && !options.FieldSelector.Empty() { + return nil, fmt.Errorf("field selector not supported yet") + } + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + return obj.(*extensions.DeploymentList), err +} + +func (s *storage) GetDeployment(ctx api.Context, deploymentID string) (*extensions.Deployment, error) { + obj, err := s.Get(ctx, deploymentID) + if err != nil { + return nil, err + } + return obj.(*extensions.Deployment), nil +} + +func (s *storage) CreateDeployment(ctx api.Context, deployment *extensions.Deployment) (*extensions.Deployment, error) { + obj, err := s.Create(ctx, deployment) + if err != nil { + return nil, err + } + return obj.(*extensions.Deployment), nil +} + +func (s *storage) UpdateDeployment(ctx api.Context, deployment *extensions.Deployment) (*extensions.Deployment, error) { + obj, _, err := s.Update(ctx, deployment) + if err != nil { + return nil, err + } + return obj.(*extensions.Deployment), nil +} + +func (s *storage) DeleteDeployment(ctx api.Context, deploymentID string) error { + _, err := s.Delete(ctx, deploymentID, nil) + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/deployment/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/deployment/strategy.go new file mode 100644 index 000000000..574cc0583 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/deployment/strategy.go @@ -0,0 +1,133 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 deployment + +import ( + "fmt" + "reflect" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/extensions/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// deploymentStrategy implements behavior for Deployments. +type deploymentStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Deployment +// objects via the REST API. +var Strategy = deploymentStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped is true for deployment. +func (deploymentStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears fields that are not allowed to be set by end users on creation. +func (deploymentStrategy) PrepareForCreate(obj runtime.Object) { + deployment := obj.(*extensions.Deployment) + deployment.Status = extensions.DeploymentStatus{} + deployment.Generation = 1 +} + +// Validate validates a new deployment. +func (deploymentStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + deployment := obj.(*extensions.Deployment) + return validation.ValidateDeployment(deployment) +} + +// Canonicalize normalizes the object after validation. +func (deploymentStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is false for deployments. +func (deploymentStrategy) AllowCreateOnUpdate() bool { + return false +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (deploymentStrategy) PrepareForUpdate(obj, old runtime.Object) { + newDeployment := obj.(*extensions.Deployment) + oldDeployment := old.(*extensions.Deployment) + newDeployment.Status = oldDeployment.Status + + // Spec updates bump the generation so that we can distinguish between + // scaling events and template changes, annotation updates bump the generation + // because annotations are copied from deployments to their replica sets. + if !reflect.DeepEqual(newDeployment.Spec, oldDeployment.Spec) || + !reflect.DeepEqual(newDeployment.Annotations, oldDeployment.Annotations) { + newDeployment.Generation = oldDeployment.Generation + 1 + } +} + +// ValidateUpdate is the default update validation for an end user. +func (deploymentStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateDeploymentUpdate(obj.(*extensions.Deployment), old.(*extensions.Deployment)) +} + +func (deploymentStrategy) AllowUnconditionalUpdate() bool { + return true +} + +type deploymentStatusStrategy struct { + deploymentStrategy +} + +var StatusStrategy = deploymentStatusStrategy{Strategy} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update of status +func (deploymentStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newDeployment := obj.(*extensions.Deployment) + oldDeployment := old.(*extensions.Deployment) + newDeployment.Spec = oldDeployment.Spec + newDeployment.Labels = oldDeployment.Labels +} + +// ValidateUpdate is the default update validation for an end user updating status +func (deploymentStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateDeploymentStatusUpdate(obj.(*extensions.Deployment), old.(*extensions.Deployment)) +} + +// DeploymentToSelectableFields returns a field set that represents the object. +func DeploymentToSelectableFields(deployment *extensions.Deployment) fields.Set { + return generic.ObjectMetaFieldsSet(deployment.ObjectMeta, true) +} + +// MatchDeployment is the filter used by the generic etcd backend to route +// watch events from etcd to clients of the apiserver only interested in specific +// labels/fields. +func MatchDeployment(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + deployment, ok := obj.(*extensions.Deployment) + if !ok { + return nil, nil, fmt.Errorf("given object is not a deployment.") + } + return labels.Set(deployment.ObjectMeta.Labels), DeploymentToSelectableFields(deployment), nil + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/deployment/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/deployment/strategy_test.go new file mode 100644 index 000000000..ca4e59489 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/deployment/strategy_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 deployment + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" +) + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Extensions.GroupVersion().String(), + "Deployment", + labels.Set(DeploymentToSelectableFields(&extensions.Deployment{})), + nil, + ) +} + +func TestStatusUpdates(t *testing.T) { + tests := []struct { + old runtime.Object + obj runtime.Object + expected runtime.Object + }{ + { + old: newDeployment(map[string]string{"test": "label"}, map[string]string{"test": "annotation"}), + obj: newDeployment(map[string]string{"test": "label", "sneaky": "label"}, map[string]string{"test": "annotation"}), + expected: newDeployment(map[string]string{"test": "label"}, map[string]string{"test": "annotation"}), + }, + { + old: newDeployment(map[string]string{"test": "label"}, map[string]string{"test": "annotation"}), + obj: newDeployment(map[string]string{"test": "label"}, map[string]string{"test": "annotation", "sneaky": "annotation"}), + expected: newDeployment(map[string]string{"test": "label"}, map[string]string{"test": "annotation", "sneaky": "annotation"}), + }, + } + + for _, test := range tests { + deploymentStatusStrategy{}.PrepareForUpdate(test.obj, test.old) + if !reflect.DeepEqual(test.expected, test.obj) { + t.Errorf("Unexpected object mismatch! Expected:\n%#v\ngot:\n%#v", test.expected, test.obj) + } + } +} + +func newDeployment(labels, annotations map[string]string) *extensions.Deployment { + return &extensions.Deployment{ + ObjectMeta: api.ObjectMeta{ + Name: "test", + Labels: labels, + Annotations: annotations, + }, + Spec: extensions.DeploymentSpec{ + Replicas: 1, + Strategy: extensions.DeploymentStrategy{ + Type: extensions.RecreateDeploymentStrategyType, + }, + Template: api.PodTemplateSpec{ + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "test", + Image: "test", + }, + }, + }, + }, + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/doc.go new file mode 100644 index 000000000..0cfe6ff81 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 registry implements the storage and system logic for the core of the api server. +package registry diff --git a/vendor/k8s.io/kubernetes/pkg/registry/endpoint/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/endpoint/doc.go new file mode 100644 index 000000000..05141506c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/endpoint/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 endpoint provides Registry interface and it's RESTStorage +// implementation for storing Endpoint api objects. +package endpoint diff --git a/vendor/k8s.io/kubernetes/pkg/registry/endpoint/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/endpoint/etcd/etcd.go new file mode 100644 index 000000000..8528ece0b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/endpoint/etcd/etcd.go @@ -0,0 +1,67 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/endpoint" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/runtime" +) + +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against endpoints. +func NewREST(opts generic.RESTOptions) *REST { + prefix := "/services/endpoints" + + newListFunc := func() runtime.Object { return &api.EndpointsList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.Endpoints), &api.Endpoints{}, prefix, endpoint.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.Endpoints{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.Endpoints).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return endpoint.MatchEndpoints(label, field) + }, + QualifiedResource: api.Resource("endpoints"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: endpoint.Strategy, + UpdateStrategy: endpoint.Strategy, + DeleteStrategy: endpoint.Strategy, + + Storage: storageInterface, + } + return &REST{store} +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/endpoint/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/endpoint/etcd/etcd_test.go new file mode 100644 index 000000000..a00ff7d61 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/endpoint/etcd/etcd_test.go @@ -0,0 +1,138 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + return NewREST(restOptions), server +} + +func validNewEndpoints() *api.Endpoints { + return &api.Endpoints{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Port: 80, Protocol: "TCP"}}, + }}, + } +} + +func validChangedEndpoints() *api.Endpoints { + endpoints := validNewEndpoints() + endpoints.ResourceVersion = "1" + endpoints.Subsets = []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}, {IP: "5.6.7.8"}}, + Ports: []api.EndpointPort{{Port: 80, Protocol: "TCP"}}, + }} + return endpoints +} + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + endpoints := validNewEndpoints() + endpoints.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + endpoints, + // invalid + &api.Endpoints{ + ObjectMeta: api.ObjectMeta{Name: "_-a123-a_"}, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate() + test.TestUpdate( + // valid + validNewEndpoints(), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*api.Endpoints) + object.Subsets = []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}, {IP: "5.6.7.8"}}, + Ports: []api.EndpointPort{{Port: 80, Protocol: "TCP"}}, + }} + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestDelete(validNewEndpoints()) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(validNewEndpoints()) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(validNewEndpoints()) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validNewEndpoints(), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": "foo"}, + }, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + {"name": "foo"}, + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/endpoint/registry.go b/vendor/k8s.io/kubernetes/pkg/registry/endpoint/registry.go new file mode 100644 index 000000000..a034852fb --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/endpoint/registry.go @@ -0,0 +1,73 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 endpoint + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface for things that know how to store endpoints. +type Registry interface { + ListEndpoints(ctx api.Context, options *api.ListOptions) (*api.EndpointsList, error) + GetEndpoints(ctx api.Context, name string) (*api.Endpoints, error) + WatchEndpoints(ctx api.Context, options *api.ListOptions) (watch.Interface, error) + UpdateEndpoints(ctx api.Context, e *api.Endpoints) error + DeleteEndpoints(ctx api.Context, name string) error +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListEndpoints(ctx api.Context, options *api.ListOptions) (*api.EndpointsList, error) { + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + return obj.(*api.EndpointsList), nil +} + +func (s *storage) WatchEndpoints(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetEndpoints(ctx api.Context, name string) (*api.Endpoints, error) { + obj, err := s.Get(ctx, name) + if err != nil { + return nil, err + } + return obj.(*api.Endpoints), nil +} + +func (s *storage) UpdateEndpoints(ctx api.Context, endpoints *api.Endpoints) error { + _, _, err := s.Update(ctx, endpoints) + return err +} + +func (s *storage) DeleteEndpoints(ctx api.Context, name string) error { + _, err := s.Delete(ctx, name, nil) + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/endpoint/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/endpoint/strategy.go new file mode 100644 index 000000000..f6f6634a3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/endpoint/strategy.go @@ -0,0 +1,94 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 endpoint + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + endptspkg "k8s.io/kubernetes/pkg/api/endpoints" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// endpointsStrategy implements behavior for Endpoints +type endpointsStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Endpoint +// objects via the REST API. +var Strategy = endpointsStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped is true for endpoints. +func (endpointsStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears fields that are not allowed to be set by end users on creation. +func (endpointsStrategy) PrepareForCreate(obj runtime.Object) { +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (endpointsStrategy) PrepareForUpdate(obj, old runtime.Object) { +} + +// Validate validates a new endpoints. +func (endpointsStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + return validation.ValidateEndpoints(obj.(*api.Endpoints)) +} + +// Canonicalize normalizes the object after validation. +func (endpointsStrategy) Canonicalize(obj runtime.Object) { + endpoints := obj.(*api.Endpoints) + endpoints.Subsets = endptspkg.RepackSubsets(endpoints.Subsets) +} + +// AllowCreateOnUpdate is true for endpoints. +func (endpointsStrategy) AllowCreateOnUpdate() bool { + return true +} + +// ValidateUpdate is the default update validation for an end user. +func (endpointsStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + errorList := validation.ValidateEndpoints(obj.(*api.Endpoints)) + return append(errorList, validation.ValidateEndpointsUpdate(obj.(*api.Endpoints), old.(*api.Endpoints))...) +} + +func (endpointsStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// MatchEndpoints returns a generic matcher for a given label and field selector. +func MatchEndpoints(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{Label: label, Field: field, GetAttrs: EndpointsAttributes} +} + +// EndpointsAttributes returns the attributes of an endpoint such that a +// generic.SelectionPredicate can match appropriately. +func EndpointsAttributes(obj runtime.Object) (objLabels labels.Set, objFields fields.Set, err error) { + endpoints, ok := obj.(*api.Endpoints) + if !ok { + return nil, nil, fmt.Errorf("invalid object type %#v", obj) + } + return endpoints.Labels, generic.ObjectMetaFieldsSet(endpoints.ObjectMeta, true), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/endpoint/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/endpoint/strategy_test.go new file mode 100644 index 000000000..915e5e693 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/endpoint/strategy_test.go @@ -0,0 +1,39 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 endpoint + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/labels" +) + +func TestSelectableFieldLabelConversions(t *testing.T) { + _, fieldsSet, err := EndpointsAttributes(&api.Endpoints{}) + if err != nil { + t.Fatal(err) + } + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "Endpoints", + labels.Set(fieldsSet), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/event/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/event/doc.go new file mode 100644 index 000000000..67633f235 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/event/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 event provides Registry interface and it's REST +// implementation for storing Event api objects. +package event diff --git a/vendor/k8s.io/kubernetes/pkg/registry/event/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/event/etcd/etcd.go new file mode 100644 index 000000000..ac84e2ef9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/event/etcd/etcd.go @@ -0,0 +1,69 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/event" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/runtime" +) + +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against events. +func NewREST(opts generic.RESTOptions, ttl uint64) *REST { + prefix := "/events" + + // We explicitly do NOT do any decoration here - switching on Cacher + // for events will lead to too high memory consumption. + storageInterface := opts.Storage + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.Event{} }, + NewListFunc: func() runtime.Object { return &api.EventList{} }, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, id string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, id) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.Event).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return event.MatchEvent(label, field) + }, + TTLFunc: func(runtime.Object, uint64, bool) (uint64, error) { + return ttl, nil + }, + QualifiedResource: api.Resource("events"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: event.Strategy, + UpdateStrategy: event.Strategy, + DeleteStrategy: event.Strategy, + + Storage: storageInterface, + } + return &REST{store} +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/event/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/event/etcd/etcd_test.go new file mode 100644 index 000000000..db14f2291 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/event/etcd/etcd_test.go @@ -0,0 +1,92 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +var testTTL uint64 = 60 + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + return NewREST(restOptions, testTTL), server +} + +func validNewEvent(namespace string) *api.Event { + return &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: namespace, + }, + Reason: "forTesting", + InvolvedObject: api.ObjectReference{ + Name: "bar", + Namespace: namespace, + }, + } +} + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + event := validNewEvent(test.TestNamespace()) + event.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + event, + // invalid + &api.Event{}, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate() + test.TestUpdate( + // valid + validNewEvent(test.TestNamespace()), + // valid updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*api.Event) + object.Reason = "forDifferentTesting" + return object + }, + // invalid updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*api.Event) + object.InvolvedObject.Namespace = "different-namespace" + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestDelete(validNewEvent(test.TestNamespace())) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/event/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/event/strategy.go new file mode 100644 index 000000000..d9f628e76 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/event/strategy.go @@ -0,0 +1,101 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 event + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +type eventStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that pplies when creating and updating +// Event objects via the REST API. +var Strategy = eventStrategy{api.Scheme, api.SimpleNameGenerator} + +func (eventStrategy) NamespaceScoped() bool { + return true +} + +func (eventStrategy) PrepareForCreate(obj runtime.Object) { +} + +func (eventStrategy) PrepareForUpdate(obj, old runtime.Object) { +} + +func (eventStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + event := obj.(*api.Event) + return validation.ValidateEvent(event) +} + +// Canonicalize normalizes the object after validation. +func (eventStrategy) Canonicalize(obj runtime.Object) { +} + +func (eventStrategy) AllowCreateOnUpdate() bool { + return true +} + +func (eventStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + event := obj.(*api.Event) + return validation.ValidateEvent(event) +} + +func (eventStrategy) AllowUnconditionalUpdate() bool { + return true +} + +func MatchEvent(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{Label: label, Field: field, GetAttrs: getAttrs} +} + +func getAttrs(obj runtime.Object) (objLabels labels.Set, objFields fields.Set, err error) { + event, ok := obj.(*api.Event) + if !ok { + return nil, nil, errors.NewInternalError(fmt.Errorf("object is not of type event: %#v", obj)) + } + l := event.Labels + if l == nil { + l = labels.Set{} + } + + objectMetaFieldsSet := generic.ObjectMetaFieldsSet(event.ObjectMeta, true) + specificFieldsSet := fields.Set{ + "involvedObject.kind": event.InvolvedObject.Kind, + "involvedObject.namespace": event.InvolvedObject.Namespace, + "involvedObject.name": event.InvolvedObject.Name, + "involvedObject.uid": string(event.InvolvedObject.UID), + "involvedObject.apiVersion": event.InvolvedObject.APIVersion, + "involvedObject.resourceVersion": event.InvolvedObject.ResourceVersion, + "involvedObject.fieldPath": event.InvolvedObject.FieldPath, + "reason": event.Reason, + "source": event.Source.Component, + "type": event.Type, + } + return l, generic.MergeFieldsSets(objectMetaFieldsSet, specificFieldsSet), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/event/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/event/strategy_test.go new file mode 100644 index 000000000..c5a685b24 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/event/strategy_test.go @@ -0,0 +1,100 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 event + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/util/diff" +) + +func testEvent(name string) *api.Event { + return &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: "default", + }, + InvolvedObject: api.ObjectReference{ + Namespace: "default", + }, + Reason: "forTesting", + } +} + +func TestGetAttrs(t *testing.T) { + eventA := &api.Event{ + ObjectMeta: api.ObjectMeta{ + Name: "f0118", + Namespace: "default", + }, + InvolvedObject: api.ObjectReference{ + Kind: "Pod", + Name: "foo", + Namespace: "baz", + UID: "long uid string", + APIVersion: testapi.Default.GroupVersion().String(), + ResourceVersion: "0", + FieldPath: "", + }, + Reason: "ForTesting", + Source: api.EventSource{Component: "test"}, + Type: api.EventTypeNormal, + } + label, field, err := getAttrs(eventA) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + if e, a := label, (labels.Set{}); !reflect.DeepEqual(e, a) { + t.Errorf("diff: %s", diff.ObjectDiff(e, a)) + } + expect := fields.Set{ + "metadata.name": "f0118", + "metadata.namespace": "default", + "involvedObject.kind": "Pod", + "involvedObject.name": "foo", + "involvedObject.namespace": "baz", + "involvedObject.uid": "long uid string", + "involvedObject.apiVersion": testapi.Default.GroupVersion().String(), + "involvedObject.resourceVersion": "0", + "involvedObject.fieldPath": "", + "reason": "ForTesting", + "source": "test", + "type": api.EventTypeNormal, + } + if e, a := expect, field; !reflect.DeepEqual(e, a) { + t.Errorf("diff: %s", diff.ObjectDiff(e, a)) + } +} + +func TestSelectableFieldLabelConversions(t *testing.T) { + _, fset, err := getAttrs(&api.Event{}) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "Event", + labels.Set(fset), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/experimental/controller/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/experimental/controller/etcd/etcd.go new file mode 100644 index 000000000..7bf63be2e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/experimental/controller/etcd/etcd.go @@ -0,0 +1,126 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/registry/controller" + "k8s.io/kubernetes/pkg/registry/controller/etcd" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + + "k8s.io/kubernetes/pkg/apis/extensions" + + extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation" +) + +// Container includes dummy storage for RC pods and experimental storage for Scale. +type ContainerStorage struct { + ReplicationController *RcREST + Scale *ScaleREST +} + +func NewStorage(opts generic.RESTOptions) ContainerStorage { + // scale does not set status, only updates spec so we ignore the status + controllerREST, _ := etcd.NewREST(opts) + rcRegistry := controller.NewRegistry(controllerREST) + + return ContainerStorage{ + ReplicationController: &RcREST{}, + Scale: &ScaleREST{registry: &rcRegistry}, + } +} + +type ScaleREST struct { + registry *controller.Registry +} + +// ScaleREST implements Patcher +var _ = rest.Patcher(&ScaleREST{}) + +// New creates a new Scale object +func (r *ScaleREST) New() runtime.Object { + return &extensions.Scale{} +} + +func (r *ScaleREST) Get(ctx api.Context, name string) (runtime.Object, error) { + rc, err := (*r.registry).GetController(ctx, name) + if err != nil { + return nil, errors.NewNotFound(extensions.Resource("replicationcontrollers/scale"), name) + } + return scaleFromRC(rc), nil +} + +func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + if obj == nil { + return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) + } + scale, ok := obj.(*extensions.Scale) + if !ok { + return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj)) + } + + if errs := extvalidation.ValidateScale(scale); len(errs) > 0 { + return nil, false, errors.NewInvalid(extensions.Kind("Scale"), scale.Name, errs) + } + + rc, err := (*r.registry).GetController(ctx, scale.Name) + if err != nil { + return nil, false, errors.NewNotFound(extensions.Resource("replicationcontrollers/scale"), scale.Name) + } + rc.Spec.Replicas = scale.Spec.Replicas + rc.ResourceVersion = scale.ResourceVersion + rc, err = (*r.registry).UpdateController(ctx, rc) + if err != nil { + return nil, false, errors.NewConflict(extensions.Resource("replicationcontrollers/scale"), scale.Name, err) + } + return scaleFromRC(rc), false, nil +} + +// scaleFromRC returns a scale subresource for a replication controller. +func scaleFromRC(rc *api.ReplicationController) *extensions.Scale { + return &extensions.Scale{ + ObjectMeta: api.ObjectMeta{ + Name: rc.Name, + Namespace: rc.Namespace, + UID: rc.UID, + ResourceVersion: rc.ResourceVersion, + CreationTimestamp: rc.CreationTimestamp, + }, + Spec: extensions.ScaleSpec{ + Replicas: rc.Spec.Replicas, + }, + Status: extensions.ScaleStatus{ + Replicas: rc.Status.Replicas, + Selector: &unversioned.LabelSelector{ + MatchLabels: rc.Spec.Selector, + }, + }, + } +} + +// Dummy implementation +type RcREST struct{} + +func (r *RcREST) New() runtime.Object { + return &extensions.ReplicationControllerDummy{} +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/experimental/controller/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/experimental/controller/etcd/etcd_test.go new file mode 100644 index 000000000..6d2706863 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/experimental/controller/etcd/etcd_test.go @@ -0,0 +1,131 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/storage" + "k8s.io/kubernetes/pkg/storage/etcd/etcdtest" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*ScaleREST, *etcdtesting.EtcdTestServer, storage.Interface) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + return NewStorage(restOptions).Scale, server, etcdStorage +} + +var validPodTemplate = api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"a": "b"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "test", + Image: "test_image", + ImagePullPolicy: api.PullIfNotPresent, + }, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, +} + +var validReplicas = 8 + +var validControllerSpec = api.ReplicationControllerSpec{ + Replicas: validReplicas, + Selector: validPodTemplate.Template.Labels, + Template: &validPodTemplate.Template, +} + +var validController = api.ReplicationController{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"}, + Spec: validControllerSpec, +} + +var validScale = extensions.Scale{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"}, + Spec: extensions.ScaleSpec{ + Replicas: validReplicas, + }, + Status: extensions.ScaleStatus{ + Replicas: 0, + Selector: &unversioned.LabelSelector{ + MatchLabels: validPodTemplate.Template.Labels, + }, + }, +} + +func TestGet(t *testing.T) { + storage, server, si := newStorage(t) + defer server.Terminate(t) + + ctx := api.WithNamespace(api.NewContext(), "test") + key := etcdtest.AddPrefix("/controllers/test/foo") + if err := si.Set(ctx, key, &validController, nil, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + obj, err := storage.Get(ctx, "foo") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + scale := obj.(*extensions.Scale) + if scale.Spec.Replicas != validReplicas { + t.Errorf("wrong replicas count expected: %d got: %d", validReplicas, scale.Spec.Replicas) + } +} + +func TestUpdate(t *testing.T) { + storage, server, si := newStorage(t) + defer server.Terminate(t) + + ctx := api.WithNamespace(api.NewContext(), "test") + key := etcdtest.AddPrefix("/controllers/test/foo") + if err := si.Set(ctx, key, &validController, nil, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + replicas := 12 + update := extensions.Scale{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"}, + Spec: extensions.ScaleSpec{ + Replicas: replicas, + }, + } + + if _, _, err := storage.Update(ctx, &update); err != nil { + t.Fatalf("unexpected error: %v", err) + } + obj, err := storage.Get(ctx, "foo") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + updated := obj.(*extensions.Scale) + if updated.Spec.Replicas != replicas { + t.Errorf("wrong replicas count expected: %d got: %d", replicas, updated.Spec.Replicas) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/doc.go new file mode 100644 index 000000000..2486e9b74 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 generic provides a generic object store interface and a +// generic label/field matching type. +package generic diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/etcd/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/etcd/doc.go new file mode 100644 index 000000000..9bf7a1968 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/etcd/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd has a generic implementation of a registry that +// stores things in etcd. +package etcd diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/etcd/etcd.go new file mode 100644 index 000000000..16114b738 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/etcd/etcd.go @@ -0,0 +1,680 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "fmt" + "reflect" + "sync" + + "k8s.io/kubernetes/pkg/api" + kubeerr "k8s.io/kubernetes/pkg/api/errors" + storeerr "k8s.io/kubernetes/pkg/api/errors/storage" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/watch" + + "github.com/golang/glog" +) + +// Etcd implements generic.Registry, backing it with etcd storage. +// It's intended to be embeddable, so that you can implement any +// non-generic functions if needed. +// You must supply a value for every field below before use; these are +// left public as it's meant to be overridable if need be. +// This object is intended to be copyable so that it can be used in +// different ways but share the same underlying behavior. +// +// The intended use of this type is embedding within a Kind specific +// RESTStorage implementation. This type provides CRUD semantics on +// a Kubelike resource, handling details like conflict detection with +// ResourceVersion and semantics. The RESTCreateStrategy and +// RESTUpdateStrategy are generic across all backends, and encapsulate +// logic specific to the API. +// +// TODO: make the default exposed methods exactly match a generic RESTStorage +// TODO: because all aspects of etcd have been removed it should really +// just be called a registry implementation. +type Etcd struct { + // Called to make a new object, should return e.g., &api.Pod{} + NewFunc func() runtime.Object + + // Called to make a new listing object, should return e.g., &api.PodList{} + NewListFunc func() runtime.Object + + // Used for error reporting + QualifiedResource unversioned.GroupResource + + // Used for listing/watching; should not include trailing "/" + KeyRootFunc func(ctx api.Context) string + + // Called for Create/Update/Get/Delete. Note that 'namespace' can be + // gotten from ctx. + KeyFunc func(ctx api.Context, name string) (string, error) + + // Called to get the name of an object + ObjectNameFunc func(obj runtime.Object) (string, error) + + // Return the TTL objects should be persisted with. Update is true if this + // is an operation against an existing object. Existing is the current TTL + // or the default for this operation. + TTLFunc func(obj runtime.Object, existing uint64, update bool) (uint64, error) + + // Returns a matcher corresponding to the provided labels and fields. + PredicateFunc func(label labels.Selector, field fields.Selector) generic.Matcher + + // DeleteCollectionWorkers is the maximum number of workers in a single + // DeleteCollection call. + DeleteCollectionWorkers int + + // Called on all objects returned from the underlying store, after + // the exit hooks are invoked. Decorators are intended for integrations + // that are above etcd and should only be used for specific cases where + // storage of the value in etcd is not appropriate, since they cannot + // be watched. + Decorator rest.ObjectFunc + // Allows extended behavior during creation, required + CreateStrategy rest.RESTCreateStrategy + // On create of an object, attempt to run a further operation. + AfterCreate rest.ObjectFunc + // Allows extended behavior during updates, required + UpdateStrategy rest.RESTUpdateStrategy + // On update of an object, attempt to run a further operation. + AfterUpdate rest.ObjectFunc + // Allows extended behavior during updates, optional + DeleteStrategy rest.RESTDeleteStrategy + // On deletion of an object, attempt to run a further operation. + AfterDelete rest.ObjectFunc + // If true, return the object that was deleted. Otherwise, return a generic + // success status response. + ReturnDeletedObject bool + // Allows extended behavior during export, optional + ExportStrategy rest.RESTExportStrategy + + // Used for all etcd access functions + Storage storage.Interface +} + +// NamespaceKeyRootFunc is the default function for constructing etcd paths to resource directories enforcing namespace rules. +func NamespaceKeyRootFunc(ctx api.Context, prefix string) string { + key := prefix + ns, ok := api.NamespaceFrom(ctx) + if ok && len(ns) > 0 { + key = key + "/" + ns + } + return key +} + +// NamespaceKeyFunc is the default function for constructing etcd paths to a resource relative to prefix enforcing namespace rules. +// If no namespace is on context, it errors. +func NamespaceKeyFunc(ctx api.Context, prefix string, name string) (string, error) { + key := NamespaceKeyRootFunc(ctx, prefix) + ns, ok := api.NamespaceFrom(ctx) + if !ok || len(ns) == 0 { + return "", kubeerr.NewBadRequest("Namespace parameter required.") + } + if len(name) == 0 { + return "", kubeerr.NewBadRequest("Name parameter required.") + } + if ok, msg := validation.IsValidPathSegmentName(name); !ok { + return "", kubeerr.NewBadRequest(fmt.Sprintf("Name parameter invalid: %v.", msg)) + } + key = key + "/" + name + return key, nil +} + +// NoNamespaceKeyFunc is the default function for constructing etcd paths to a resource relative to prefix without a namespace +func NoNamespaceKeyFunc(ctx api.Context, prefix string, name string) (string, error) { + if len(name) == 0 { + return "", kubeerr.NewBadRequest("Name parameter required.") + } + if ok, msg := validation.IsValidPathSegmentName(name); !ok { + return "", kubeerr.NewBadRequest(fmt.Sprintf("Name parameter invalid: %v.", msg)) + } + key := prefix + "/" + name + return key, nil +} + +// New implements RESTStorage +func (e *Etcd) New() runtime.Object { + return e.NewFunc() +} + +// NewList implements RESTLister +func (e *Etcd) NewList() runtime.Object { + return e.NewListFunc() +} + +// List returns a list of items matching labels and field +func (e *Etcd) List(ctx api.Context, options *api.ListOptions) (runtime.Object, error) { + label := labels.Everything() + if options != nil && options.LabelSelector != nil { + label = options.LabelSelector + } + field := fields.Everything() + if options != nil && options.FieldSelector != nil { + field = options.FieldSelector + } + return e.ListPredicate(ctx, e.PredicateFunc(label, field), options) +} + +// ListPredicate returns a list of all the items matching m. +func (e *Etcd) ListPredicate(ctx api.Context, m generic.Matcher, options *api.ListOptions) (runtime.Object, error) { + list := e.NewListFunc() + filterFunc := e.filterAndDecorateFunction(m) + if name, ok := m.MatchesSingle(); ok { + if key, err := e.KeyFunc(ctx, name); err == nil { + err := e.Storage.GetToList(ctx, key, filterFunc, list) + return list, storeerr.InterpretListError(err, e.QualifiedResource) + } + // if we cannot extract a key based on the current context, the optimization is skipped + } + + if options == nil { + options = &api.ListOptions{ResourceVersion: "0"} + } + err := e.Storage.List(ctx, e.KeyRootFunc(ctx), options.ResourceVersion, filterFunc, list) + return list, storeerr.InterpretListError(err, e.QualifiedResource) +} + +// Create inserts a new item according to the unique key from the object. +func (e *Etcd) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { + if err := rest.BeforeCreate(e.CreateStrategy, ctx, obj); err != nil { + return nil, err + } + name, err := e.ObjectNameFunc(obj) + if err != nil { + return nil, err + } + key, err := e.KeyFunc(ctx, name) + if err != nil { + return nil, err + } + ttl, err := e.calculateTTL(obj, 0, false) + if err != nil { + return nil, err + } + out := e.NewFunc() + if err := e.Storage.Create(ctx, key, obj, out, ttl); err != nil { + err = storeerr.InterpretCreateError(err, e.QualifiedResource, name) + err = rest.CheckGeneratedNameError(e.CreateStrategy, err, obj) + return nil, err + } + if e.AfterCreate != nil { + if err := e.AfterCreate(out); err != nil { + return nil, err + } + } + if e.Decorator != nil { + if err := e.Decorator(obj); err != nil { + return nil, err + } + } + return out, nil +} + +// Update performs an atomic update and set of the object. Returns the result of the update +// or an error. If the registry allows create-on-update, the create flow will be executed. +// A bool is returned along with the object and any errors, to indicate object creation. +func (e *Etcd) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + name, err := e.ObjectNameFunc(obj) + if err != nil { + return nil, false, err + } + key, err := e.KeyFunc(ctx, name) + if err != nil { + return nil, false, err + } + // If AllowUnconditionalUpdate() is true and the object specified by the user does not have a resource version, + // then we populate it with the latest version. + // Else, we check that the version specified by the user matches the version of latest etcd object. + resourceVersion, err := e.Storage.Versioner().ObjectResourceVersion(obj) + if err != nil { + return nil, false, err + } + doUnconditionalUpdate := resourceVersion == 0 && e.UpdateStrategy.AllowUnconditionalUpdate() + // TODO: expose TTL + creating := false + out := e.NewFunc() + meta, err := api.ObjectMetaFor(obj) + if err != nil { + return nil, false, kubeerr.NewInternalError(err) + } + var preconditions *storage.Preconditions + // If the UID of the new object is specified, we use it as an Update precondition. + if len(meta.UID) != 0 { + UIDCopy := meta.UID + preconditions = &storage.Preconditions{UID: &UIDCopy} + } + err = e.Storage.GuaranteedUpdate(ctx, key, out, true, preconditions, func(existing runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { + // Since we return 'obj' from this function and it can be modified outside this + // function, we are resetting resourceVersion to the initial value here. + // + // TODO: In fact, we should probably return a DeepCopy of obj in all places. + err := e.Storage.Versioner().UpdateObject(obj, nil, resourceVersion) + if err != nil { + return nil, nil, err + } + + version, err := e.Storage.Versioner().ObjectResourceVersion(existing) + if err != nil { + return nil, nil, err + } + if version == 0 { + if !e.UpdateStrategy.AllowCreateOnUpdate() { + return nil, nil, kubeerr.NewNotFound(e.QualifiedResource, name) + } + creating = true + if err := rest.BeforeCreate(e.CreateStrategy, ctx, obj); err != nil { + return nil, nil, err + } + ttl, err := e.calculateTTL(obj, 0, false) + if err != nil { + return nil, nil, err + } + return obj, &ttl, nil + } + + creating = false + if doUnconditionalUpdate { + // Update the object's resource version to match the latest etcd object's resource version. + err = e.Storage.Versioner().UpdateObject(obj, res.Expiration, res.ResourceVersion) + if err != nil { + return nil, nil, err + } + } else { + // Check if the object's resource version matches the latest resource version. + newVersion, err := e.Storage.Versioner().ObjectResourceVersion(obj) + if err != nil { + return nil, nil, err + } + if newVersion == 0 { + // TODO: The Invalid error should has a field for Resource. + // After that field is added, we should fill the Resource and + // leave the Kind field empty. See the discussion in #18526. + qualifiedKind := unversioned.GroupKind{Group: e.QualifiedResource.Group, Kind: e.QualifiedResource.Resource} + fieldErrList := field.ErrorList{field.Invalid(field.NewPath("metadata").Child("resourceVersion"), newVersion, "must be specified for an update")} + return nil, nil, kubeerr.NewInvalid(qualifiedKind, name, fieldErrList) + } + if newVersion != version { + return nil, nil, kubeerr.NewConflict(e.QualifiedResource, name, fmt.Errorf("the object has been modified; please apply your changes to the latest version and try again")) + } + } + if err := rest.BeforeUpdate(e.UpdateStrategy, ctx, obj, existing); err != nil { + return nil, nil, err + } + ttl, err := e.calculateTTL(obj, res.TTL, true) + if err != nil { + return nil, nil, err + } + if int64(ttl) != res.TTL { + return obj, &ttl, nil + } + return obj, nil, nil + }) + + if err != nil { + if creating { + err = storeerr.InterpretCreateError(err, e.QualifiedResource, name) + err = rest.CheckGeneratedNameError(e.CreateStrategy, err, obj) + } else { + err = storeerr.InterpretUpdateError(err, e.QualifiedResource, name) + } + return nil, false, err + } + if creating { + if e.AfterCreate != nil { + if err := e.AfterCreate(out); err != nil { + return nil, false, err + } + } + } else { + if e.AfterUpdate != nil { + if err := e.AfterUpdate(out); err != nil { + return nil, false, err + } + } + } + if e.Decorator != nil { + if err := e.Decorator(obj); err != nil { + return nil, false, err + } + } + return out, creating, nil +} + +// Get retrieves the item from etcd. +func (e *Etcd) Get(ctx api.Context, name string) (runtime.Object, error) { + obj := e.NewFunc() + key, err := e.KeyFunc(ctx, name) + if err != nil { + return nil, err + } + if err := e.Storage.Get(ctx, key, obj, false); err != nil { + return nil, storeerr.InterpretGetError(err, e.QualifiedResource, name) + } + if e.Decorator != nil { + if err := e.Decorator(obj); err != nil { + return nil, err + } + } + return obj, nil +} + +var ( + errAlreadyDeleting = fmt.Errorf("abort delete") + errDeleteNow = fmt.Errorf("delete now") +) + +// Delete removes the item from etcd. +func (e *Etcd) Delete(ctx api.Context, name string, options *api.DeleteOptions) (runtime.Object, error) { + key, err := e.KeyFunc(ctx, name) + if err != nil { + return nil, err + } + + obj := e.NewFunc() + if err := e.Storage.Get(ctx, key, obj, false); err != nil { + return nil, storeerr.InterpretDeleteError(err, e.QualifiedResource, name) + } + + // support older consumers of delete by treating "nil" as delete immediately + if options == nil { + options = api.NewDeleteOptions(0) + } + var preconditions storage.Preconditions + if options.Preconditions != nil { + preconditions.UID = options.Preconditions.UID + } + graceful, pendingGraceful, err := rest.BeforeDelete(e.DeleteStrategy, ctx, obj, options) + if err != nil { + return nil, err + } + if pendingGraceful { + return e.finalizeDelete(obj, false) + } + var ignoreNotFound bool = false + var lastExisting runtime.Object = nil + if graceful { + out := e.NewFunc() + lastGraceful := int64(0) + err := e.Storage.GuaranteedUpdate( + ctx, key, out, false, &preconditions, + storage.SimpleUpdate(func(existing runtime.Object) (runtime.Object, error) { + graceful, pendingGraceful, err := rest.BeforeDelete(e.DeleteStrategy, ctx, existing, options) + if err != nil { + return nil, err + } + if pendingGraceful { + return nil, errAlreadyDeleting + } + if !graceful { + return nil, errDeleteNow + } + lastGraceful = *options.GracePeriodSeconds + lastExisting = existing + return existing, nil + }), + ) + switch err { + case nil: + if lastGraceful > 0 { + return out, nil + } + // If we are here, the registry supports grace period mechanism and + // we are intentionally delete gracelessly. In this case, we may + // enter a race with other k8s components. If other component wins + // the race, the object will not be found, and we should tolerate + // the NotFound error. See + // https://github.com/kubernetes/kubernetes/issues/19403 for + // details. + ignoreNotFound = true + // exit the switch and delete immediately + case errDeleteNow: + // we've updated the object to have a zero grace period, or it's already at 0, so + // we should fall through and truly delete the object. + case errAlreadyDeleting: + return e.finalizeDelete(obj, true) + default: + return nil, storeerr.InterpretUpdateError(err, e.QualifiedResource, name) + } + } + + // delete immediately, or no graceful deletion supported + out := e.NewFunc() + if err := e.Storage.Delete(ctx, key, out, &preconditions); err != nil { + // Please refer to the place where we set ignoreNotFound for the reason + // why we ignore the NotFound error . + if storage.IsNotFound(err) && ignoreNotFound && lastExisting != nil { + // The lastExisting object may not be the last state of the object + // before its deletion, but it's the best approximation. + return e.finalizeDelete(lastExisting, true) + } + return nil, storeerr.InterpretDeleteError(err, e.QualifiedResource, name) + } + return e.finalizeDelete(out, true) +} + +// DeleteCollection remove all items returned by List with a given ListOptions from etcd. +// +// DeleteCollection is currently NOT atomic. It can happen that only subset of objects +// will be deleted from etcd, and then an error will be returned. +// In case of success, the list of deleted objects will be returned. +// +// TODO: Currently, there is no easy way to remove 'directory' entry from etcd (if we +// are removing all objects of a given type) with the current API (it's technically +// possibly with etcd API, but watch is not delivered correctly then). +// It will be possible to fix it with v3 etcd API. +func (e *Etcd) DeleteCollection(ctx api.Context, options *api.DeleteOptions, listOptions *api.ListOptions) (runtime.Object, error) { + listObj, err := e.List(ctx, listOptions) + if err != nil { + return nil, err + } + items, err := meta.ExtractList(listObj) + if err != nil { + return nil, err + } + // Spawn a number of goroutines, so that we can issue requests to etcd + // in parallel to speed up deletion. + // TODO: Make this proportional to the number of items to delete, up to + // DeleteCollectionWorkers (it doesn't make much sense to spawn 16 + // workers to delete 10 items). + workersNumber := e.DeleteCollectionWorkers + if workersNumber < 1 { + workersNumber = 1 + } + wg := sync.WaitGroup{} + toProcess := make(chan int, 2*workersNumber) + errs := make(chan error, workersNumber+1) + + go func() { + defer utilruntime.HandleCrash(func(panicReason interface{}) { + errs <- fmt.Errorf("DeleteCollection distributor panicked: %v", panicReason) + }) + for i := 0; i < len(items); i++ { + toProcess <- i + } + close(toProcess) + }() + + wg.Add(workersNumber) + for i := 0; i < workersNumber; i++ { + go func() { + // panics don't cross goroutine boundaries + defer utilruntime.HandleCrash(func(panicReason interface{}) { + errs <- fmt.Errorf("DeleteCollection goroutine panicked: %v", panicReason) + }) + defer wg.Done() + + for { + index, ok := <-toProcess + if !ok { + return + } + accessor, err := meta.Accessor(items[index]) + if err != nil { + errs <- err + return + } + if _, err := e.Delete(ctx, accessor.GetName(), options); err != nil && !kubeerr.IsNotFound(err) { + glog.V(4).Infof("Delete %s in DeleteCollection failed: %v", accessor.GetName(), err) + errs <- err + return + } + } + }() + } + wg.Wait() + select { + case err := <-errs: + return nil, err + default: + return listObj, nil + } +} + +func (e *Etcd) finalizeDelete(obj runtime.Object, runHooks bool) (runtime.Object, error) { + if runHooks && e.AfterDelete != nil { + if err := e.AfterDelete(obj); err != nil { + return nil, err + } + } + if e.ReturnDeletedObject { + if e.Decorator != nil { + if err := e.Decorator(obj); err != nil { + return nil, err + } + } + return obj, nil + } + return &unversioned.Status{Status: unversioned.StatusSuccess}, nil +} + +// Watch makes a matcher for the given label and field, and calls +// WatchPredicate. If possible, you should customize PredicateFunc to produre a +// matcher that matches by key. generic.SelectionPredicate does this for you +// automatically. +func (e *Etcd) Watch(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + label := labels.Everything() + if options != nil && options.LabelSelector != nil { + label = options.LabelSelector + } + field := fields.Everything() + if options != nil && options.FieldSelector != nil { + field = options.FieldSelector + } + resourceVersion := "" + if options != nil { + resourceVersion = options.ResourceVersion + } + return e.WatchPredicate(ctx, e.PredicateFunc(label, field), resourceVersion) +} + +// WatchPredicate starts a watch for the items that m matches. +func (e *Etcd) WatchPredicate(ctx api.Context, m generic.Matcher, resourceVersion string) (watch.Interface, error) { + filterFunc := e.filterAndDecorateFunction(m) + + if name, ok := m.MatchesSingle(); ok { + if key, err := e.KeyFunc(ctx, name); err == nil { + if err != nil { + return nil, err + } + return e.Storage.Watch(ctx, key, resourceVersion, filterFunc) + } + // if we cannot extract a key based on the current context, the optimization is skipped + } + + return e.Storage.WatchList(ctx, e.KeyRootFunc(ctx), resourceVersion, filterFunc) +} + +func (e *Etcd) filterAndDecorateFunction(m generic.Matcher) func(runtime.Object) bool { + return func(obj runtime.Object) bool { + matches, err := m.Matches(obj) + if err != nil { + glog.Errorf("unable to match watch: %v", err) + return false + } + if matches && e.Decorator != nil { + if err := e.Decorator(obj); err != nil { + glog.Errorf("unable to decorate watch: %v", err) + return false + } + } + return matches + } +} + +// calculateTTL is a helper for retrieving the updated TTL for an object or returning an error +// if the TTL cannot be calculated. The defaultTTL is changed to 1 if less than zero. Zero means +// no TTL, not expire immediately. +func (e *Etcd) calculateTTL(obj runtime.Object, defaultTTL int64, update bool) (ttl uint64, err error) { + // etcd may return a negative TTL for a node if the expiration has not occurred due + // to server lag - we will ensure that the value is at least set. + if defaultTTL < 0 { + defaultTTL = 1 + } + ttl = uint64(defaultTTL) + if e.TTLFunc != nil { + ttl, err = e.TTLFunc(obj, ttl, update) + } + return ttl, err +} + +func exportObjectMeta(accessor meta.Object, exact bool) { + accessor.SetUID("") + if !exact { + accessor.SetNamespace("") + } + accessor.SetCreationTimestamp(unversioned.Time{}) + accessor.SetDeletionTimestamp(nil) + accessor.SetResourceVersion("") + accessor.SetSelfLink("") + if len(accessor.GetGenerateName()) > 0 && !exact { + accessor.SetName("") + } +} + +// Implements the rest.Exporter interface +func (e *Etcd) Export(ctx api.Context, name string, opts unversioned.ExportOptions) (runtime.Object, error) { + obj, err := e.Get(ctx, name) + if err != nil { + return nil, err + } + if accessor, err := meta.Accessor(obj); err == nil { + exportObjectMeta(accessor, opts.Exact) + } else { + glog.V(4).Infof("Object of type %v does not have ObjectMeta: %v", reflect.TypeOf(obj), err) + } + + if e.ExportStrategy != nil { + if err = e.ExportStrategy.Export(obj, opts.Exact); err != nil { + return nil, err + } + } else { + e.CreateStrategy.PrepareForCreate(obj) + } + return obj, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/etcd/etcd_test.go new file mode 100644 index 000000000..f3d6508a1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/etcd/etcd_test.go @@ -0,0 +1,717 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "fmt" + "path" + "reflect" + "strconv" + "testing" + + "sync" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + etcdstorage "k8s.io/kubernetes/pkg/storage/etcd" + "k8s.io/kubernetes/pkg/storage/etcd/etcdtest" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + storagetesting "k8s.io/kubernetes/pkg/storage/testing" + "k8s.io/kubernetes/pkg/util/sets" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +type testRESTStrategy struct { + runtime.ObjectTyper + api.NameGenerator + namespaceScoped bool + allowCreateOnUpdate bool + allowUnconditionalUpdate bool +} + +func (t *testRESTStrategy) NamespaceScoped() bool { return t.namespaceScoped } +func (t *testRESTStrategy) AllowCreateOnUpdate() bool { return t.allowCreateOnUpdate } +func (t *testRESTStrategy) AllowUnconditionalUpdate() bool { return t.allowUnconditionalUpdate } + +func (t *testRESTStrategy) PrepareForCreate(obj runtime.Object) { + metaObj, err := meta.Accessor(obj) + if err != nil { + panic(err.Error()) + } + labels := metaObj.GetLabels() + if labels == nil { + labels = map[string]string{} + } + labels["prepare_create"] = "true" + metaObj.SetLabels(labels) +} + +func (t *testRESTStrategy) PrepareForUpdate(obj, old runtime.Object) {} +func (t *testRESTStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + return nil +} +func (t *testRESTStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return nil +} +func (t *testRESTStrategy) Canonicalize(obj runtime.Object) {} + +func hasCreated(t *testing.T, pod *api.Pod) func(runtime.Object) bool { + return func(obj runtime.Object) bool { + actualPod := obj.(*api.Pod) + if !api.Semantic.DeepDerivative(pod.Status, actualPod.Status) { + t.Errorf("not a deep derivative %#v", actualPod) + return false + } + return api.HasObjectMetaSystemFieldValues(&actualPod.ObjectMeta) + } +} + +func NewTestGenericEtcdRegistry(t *testing.T) (*etcdtesting.EtcdTestServer, *Etcd) { + podPrefix := "/pods" + server := etcdtesting.NewEtcdTestClientServer(t) + s := etcdstorage.NewEtcdStorage(server.Client, testapi.Default.Codec(), etcdtest.PathPrefix(), false) + strategy := &testRESTStrategy{api.Scheme, api.SimpleNameGenerator, true, false, true} + + return server, &Etcd{ + NewFunc: func() runtime.Object { return &api.Pod{} }, + NewListFunc: func() runtime.Object { return &api.PodList{} }, + QualifiedResource: api.Resource("pods"), + CreateStrategy: strategy, + UpdateStrategy: strategy, + DeleteStrategy: strategy, + KeyRootFunc: func(ctx api.Context) string { + return podPrefix + }, + KeyFunc: func(ctx api.Context, id string) (string, error) { + if _, ok := api.NamespaceFrom(ctx); !ok { + return "", fmt.Errorf("namespace is required") + } + return path.Join(podPrefix, id), nil + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Pod).Name, nil }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + pod, ok := obj.(*api.Pod) + if !ok { + return nil, nil, fmt.Errorf("not a pod") + } + return labels.Set(pod.ObjectMeta.Labels), generic.ObjectMetaFieldsSet(pod.ObjectMeta, true), nil + }, + } + }, + Storage: s, + } +} + +// setMatcher is a matcher that matches any pod with id in the set. +// Makes testing simpler. +type setMatcher struct { + sets.String +} + +func (sm setMatcher) Matches(obj runtime.Object) (bool, error) { + pod, ok := obj.(*api.Pod) + if !ok { + return false, fmt.Errorf("wrong object") + } + return sm.Has(pod.Name), nil +} + +func (sm setMatcher) MatchesSingle() (string, bool) { + if sm.Len() == 1 { + // Since pod name is its key, we can optimize this case. + return sm.List()[0], true + } + return "", false +} + +// everythingMatcher matches everything +type everythingMatcher struct{} + +func (everythingMatcher) Matches(obj runtime.Object) (bool, error) { + return true, nil +} + +func (everythingMatcher) MatchesSingle() (string, bool) { + return "", false +} + +func TestEtcdList(t *testing.T) { + podA := &api.Pod{ + ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "bar"}, + Spec: api.PodSpec{NodeName: "machine"}, + } + podB := &api.Pod{ + ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "foo"}, + Spec: api.PodSpec{NodeName: "machine"}, + } + + testContext := api.WithNamespace(api.NewContext(), "test") + noNamespaceContext := api.NewContext() + + table := map[string]struct { + in *api.PodList + m generic.Matcher + out runtime.Object + context api.Context + }{ + "notFound": { + in: nil, + m: everythingMatcher{}, + out: &api.PodList{Items: []api.Pod{}}, + }, + "normal": { + in: &api.PodList{Items: []api.Pod{*podA, *podB}}, + m: everythingMatcher{}, + out: &api.PodList{Items: []api.Pod{*podA, *podB}}, + }, + "normalFiltered": { + in: &api.PodList{Items: []api.Pod{*podA, *podB}}, + m: setMatcher{sets.NewString("foo")}, + out: &api.PodList{Items: []api.Pod{*podB}}, + }, + "normalFilteredNoNamespace": { + in: &api.PodList{Items: []api.Pod{*podA, *podB}}, + m: setMatcher{sets.NewString("foo")}, + out: &api.PodList{Items: []api.Pod{*podB}}, + context: noNamespaceContext, + }, + "normalFilteredMatchMultiple": { + in: &api.PodList{Items: []api.Pod{*podA, *podB}}, + m: setMatcher{sets.NewString("foo", "makeMatchSingleReturnFalse")}, + out: &api.PodList{Items: []api.Pod{*podB}}, + }, + } + + for name, item := range table { + ctx := testContext + if item.context != nil { + ctx = item.context + } + server, registry := NewTestGenericEtcdRegistry(t) + + if item.in != nil { + if err := storagetesting.CreateList("/pods", registry.Storage, item.in); err != nil { + t.Errorf("Unexpected error %v", err) + } + } + + list, err := registry.ListPredicate(ctx, item.m, nil) + if err != nil { + t.Errorf("Unexpected error %v", err) + continue + } + + // DeepDerivative e,a is needed here b/c the storage layer sets ResourceVersion + if e, a := item.out, list; !api.Semantic.DeepDerivative(e, a) { + t.Errorf("%v: Expected %#v, got %#v", name, e, a) + } + server.Terminate(t) + } +} + +func TestEtcdCreate(t *testing.T) { + podA := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"}, + Spec: api.PodSpec{NodeName: "machine"}, + } + podB := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"}, + Spec: api.PodSpec{NodeName: "machine2"}, + } + + testContext := api.WithNamespace(api.NewContext(), "test") + server, registry := NewTestGenericEtcdRegistry(t) + defer server.Terminate(t) + + // create the object + objA, err := registry.Create(testContext, podA) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // get the object + checkobj, err := registry.Get(testContext, podA.Name) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // verify objects are equal + if e, a := objA, checkobj; !reflect.DeepEqual(e, a) { + t.Errorf("Expected %#v, got %#v", e, a) + } + + // now try to create the second pod + _, err = registry.Create(testContext, podB) + if !errors.IsAlreadyExists(err) { + t.Errorf("Unexpected error: %v", err) + } +} + +func updateAndVerify(t *testing.T, ctx api.Context, registry *Etcd, pod *api.Pod) bool { + obj, _, err := registry.Update(ctx, pod) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return false + } + checkObj, err := registry.Get(ctx, pod.Name) + if err != nil { + t.Errorf("Unexpected error: %v", err) + return false + } + if e, a := obj, checkObj; !reflect.DeepEqual(e, a) { + t.Errorf("Expected %#v, got %#v", e, a) + return false + } + return true +} + +func TestEtcdUpdate(t *testing.T) { + podA := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"}, + Spec: api.PodSpec{NodeName: "machine"}, + } + podB := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"}, + Spec: api.PodSpec{NodeName: "machine2"}, + } + podAWithResourceVersion := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "7"}, + Spec: api.PodSpec{NodeName: "machine"}, + } + + testContext := api.WithNamespace(api.NewContext(), "test") + server, registry := NewTestGenericEtcdRegistry(t) + defer server.Terminate(t) + + // Test1 try to update a non-existing node + _, _, err := registry.Update(testContext, podA) + if !errors.IsNotFound(err) { + t.Errorf("Unexpected error: %v", err) + } + + // Test2 createIfNotFound and verify + registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = true + if !updateAndVerify(t, testContext, registry, podA) { + t.Errorf("Unexpected error updating podA") + } + registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = false + + // Test3 outofDate + _, _, err = registry.Update(testContext, podAWithResourceVersion) + if !errors.IsConflict(err) { + t.Errorf("Unexpected error updating podAWithResourceVersion: %v", err) + } + + // Test4 normal update and verify + if !updateAndVerify(t, testContext, registry, podB) { + t.Errorf("Unexpected error updating podB") + } + + // Test5 unconditional update + // NOTE: The logic for unconditional updates doesn't make sense to me, and imho should be removed. + // doUnconditionalUpdate := resourceVersion == 0 && e.UpdateStrategy.AllowUnconditionalUpdate() + // ^^ That condition can *never be true due to the creation of root objects. + // + // registry.UpdateStrategy.(*testRESTStrategy).allowUnconditionalUpdate = true + // updateAndVerify(t, testContext, registry, podAWithResourceVersion) + +} + +func TestNoOpUpdates(t *testing.T) { + server, registry := NewTestGenericEtcdRegistry(t) + defer server.Terminate(t) + + newPod := func() *api.Pod { + return &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Namespace: api.NamespaceDefault, + Name: "foo", + Labels: map[string]string{"prepare_create": "true"}, + }, + Spec: api.PodSpec{NodeName: "machine"}, + } + } + + var err error + var createResult runtime.Object + if createResult, err = registry.Create(api.NewDefaultContext(), newPod()); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + createdPod, err := registry.Get(api.NewDefaultContext(), "foo") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + var updateResult runtime.Object + if updateResult, _, err = registry.Update(api.NewDefaultContext(), newPod()); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Check whether we do not return empty result on no-op update. + if !reflect.DeepEqual(createResult, updateResult) { + t.Errorf("no-op update should return a correct value, got: %#v", updateResult) + } + + updatedPod, err := registry.Get(api.NewDefaultContext(), "foo") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + createdMeta, err := meta.Accessor(createdPod) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + updatedMeta, err := meta.Accessor(updatedPod) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if createdMeta.GetResourceVersion() != updatedMeta.GetResourceVersion() { + t.Errorf("no-op update should be ignored and not written to etcd") + } +} + +// TODO: Add a test to check no-op update if we have object with ResourceVersion +// already stored in etcd. Currently there is no easy way to store object with +// ResourceVersion in etcd. + +type testPodExport struct{} + +func (t testPodExport) Export(obj runtime.Object, exact bool) error { + pod := obj.(*api.Pod) + if pod.Labels == nil { + pod.Labels = map[string]string{} + } + pod.Labels["exported"] = "true" + pod.Labels["exact"] = strconv.FormatBool(exact) + + return nil +} + +func TestEtcdCustomExport(t *testing.T) { + podA := api.Pod{ + ObjectMeta: api.ObjectMeta{ + Namespace: "test", + Name: "foo", + Labels: map[string]string{}, + }, + Spec: api.PodSpec{NodeName: "machine"}, + } + + server, registry := NewTestGenericEtcdRegistry(t) + defer server.Terminate(t) + + registry.ExportStrategy = testPodExport{} + + testContext := api.WithNamespace(api.NewContext(), "test") + registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = true + if !updateAndVerify(t, testContext, registry, &podA) { + t.Errorf("Unexpected error updating podA") + } + + obj, err := registry.Export(testContext, podA.Name, unversioned.ExportOptions{}) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + exportedPod := obj.(*api.Pod) + if exportedPod.Labels["exported"] != "true" { + t.Errorf("expected: exported->true, found: %s", exportedPod.Labels["exported"]) + } + if exportedPod.Labels["exact"] != "false" { + t.Errorf("expected: exact->false, found: %s", exportedPod.Labels["exact"]) + } + delete(exportedPod.Labels, "exported") + delete(exportedPod.Labels, "exact") + exportObjectMeta(&podA.ObjectMeta, false) + podA.Spec = exportedPod.Spec + if !reflect.DeepEqual(&podA, exportedPod) { + t.Errorf("expected:\n%v\nsaw:\n%v\n", &podA, exportedPod) + } +} + +func TestEtcdBasicExport(t *testing.T) { + podA := api.Pod{ + ObjectMeta: api.ObjectMeta{ + Namespace: "test", + Name: "foo", + Labels: map[string]string{}, + }, + Spec: api.PodSpec{NodeName: "machine"}, + Status: api.PodStatus{HostIP: "1.2.3.4"}, + } + + server, registry := NewTestGenericEtcdRegistry(t) + defer server.Terminate(t) + + testContext := api.WithNamespace(api.NewContext(), "test") + registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = true + if !updateAndVerify(t, testContext, registry, &podA) { + t.Errorf("Unexpected error updating podA") + } + + obj, err := registry.Export(testContext, podA.Name, unversioned.ExportOptions{}) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + exportedPod := obj.(*api.Pod) + if exportedPod.Labels["prepare_create"] != "true" { + t.Errorf("expected: prepare_create->true, found: %s", exportedPod.Labels["prepare_create"]) + } + exportObjectMeta(&podA.ObjectMeta, false) + podA.Spec = exportedPod.Spec + if !reflect.DeepEqual(&podA, exportedPod) { + t.Errorf("expected:\n%v\nsaw:\n%v\n", &podA, exportedPod) + } +} + +func TestEtcdGet(t *testing.T) { + podA := &api.Pod{ + ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "foo"}, + Spec: api.PodSpec{NodeName: "machine"}, + } + + testContext := api.WithNamespace(api.NewContext(), "test") + server, registry := NewTestGenericEtcdRegistry(t) + defer server.Terminate(t) + + _, err := registry.Get(testContext, podA.Name) + if !errors.IsNotFound(err) { + t.Errorf("Unexpected error: %v", err) + } + + registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = true + if !updateAndVerify(t, testContext, registry, podA) { + t.Errorf("Unexpected error updating podA") + } +} + +func TestEtcdDelete(t *testing.T) { + podA := &api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{NodeName: "machine"}, + } + + testContext := api.WithNamespace(api.NewContext(), "test") + server, registry := NewTestGenericEtcdRegistry(t) + defer server.Terminate(t) + + // test failure condition + _, err := registry.Delete(testContext, podA.Name, nil) + if !errors.IsNotFound(err) { + t.Errorf("Unexpected error: %v", err) + } + + // create pod + _, err = registry.Create(testContext, podA) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // delete object + _, err = registry.Delete(testContext, podA.Name, nil) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // try to get a item which should be deleted + _, err = registry.Get(testContext, podA.Name) + if !errors.IsNotFound(err) { + t.Errorf("Unexpected error: %v", err) + } +} + +func TestEtcdDeleteCollection(t *testing.T) { + podA := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}} + podB := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "bar"}} + + testContext := api.WithNamespace(api.NewContext(), "test") + server, registry := NewTestGenericEtcdRegistry(t) + defer server.Terminate(t) + + if _, err := registry.Create(testContext, podA); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if _, err := registry.Create(testContext, podB); err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // Delete all pods. + deleted, err := registry.DeleteCollection(testContext, nil, &api.ListOptions{}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + deletedPods := deleted.(*api.PodList) + if len(deletedPods.Items) != 2 { + t.Errorf("Unexpected number of pods deleted: %d, expected: 2", len(deletedPods.Items)) + } + + if _, err := registry.Get(testContext, podA.Name); !errors.IsNotFound(err) { + t.Errorf("Unexpected error: %v", err) + } + if _, err := registry.Get(testContext, podB.Name); !errors.IsNotFound(err) { + t.Errorf("Unexpected error: %v", err) + } +} + +func TestEtcdDeleteCollectionNotFound(t *testing.T) { + server, registry := NewTestGenericEtcdRegistry(t) + defer server.Terminate(t) + + testContext := api.WithNamespace(api.NewContext(), "test") + + podA := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}} + podB := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "bar"}} + + for i := 0; i < 10; i++ { + // Setup + if _, err := registry.Create(testContext, podA); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if _, err := registry.Create(testContext, podB); err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // Kick off multiple delete collection calls to test notfound behavior + wg := &sync.WaitGroup{} + for j := 0; j < 2; j++ { + wg.Add(1) + go func() { + defer wg.Done() + _, err := registry.DeleteCollection(testContext, nil, &api.ListOptions{}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + }() + } + wg.Wait() + + if _, err := registry.Get(testContext, podA.Name); !errors.IsNotFound(err) { + t.Errorf("Unexpected error: %v", err) + } + if _, err := registry.Get(testContext, podB.Name); !errors.IsNotFound(err) { + t.Errorf("Unexpected error: %v", err) + } + } +} + +// Test whether objects deleted with DeleteCollection are correctly delivered +// to watchers. +func TestEtcdDeleteCollectionWithWatch(t *testing.T) { + podA := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}} + + testContext := api.WithNamespace(api.NewContext(), "test") + server, registry := NewTestGenericEtcdRegistry(t) + defer server.Terminate(t) + + objCreated, err := registry.Create(testContext, podA) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + podCreated := objCreated.(*api.Pod) + + watcher, err := registry.WatchPredicate(testContext, setMatcher{sets.NewString("foo")}, podCreated.ResourceVersion) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + defer watcher.Stop() + + if _, err := registry.DeleteCollection(testContext, nil, &api.ListOptions{}); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + got, open := <-watcher.ResultChan() + if !open { + t.Errorf("Unexpected channel close") + } else { + if got.Type != "DELETED" { + t.Errorf("Unexpected event type: %s", got.Type) + } + gotObject := got.Object.(*api.Pod) + gotObject.ResourceVersion = podCreated.ResourceVersion + if e, a := podCreated, gotObject; !reflect.DeepEqual(e, a) { + t.Errorf("Expected: %#v, got: %#v", e, a) + } + } +} + +func TestEtcdWatch(t *testing.T) { + testContext := api.WithNamespace(api.NewContext(), "test") + noNamespaceContext := api.NewContext() + + table := map[string]struct { + generic.Matcher + context api.Context + }{ + "single": { + Matcher: setMatcher{sets.NewString("foo")}, + }, + "multi": { + Matcher: setMatcher{sets.NewString("foo", "bar")}, + }, + "singleNoNamespace": { + Matcher: setMatcher{sets.NewString("foo")}, + context: noNamespaceContext, + }, + } + + for name, m := range table { + ctx := testContext + if m.context != nil { + ctx = m.context + } + podA := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "test", + }, + Spec: api.PodSpec{NodeName: "machine"}, + } + + server, registry := NewTestGenericEtcdRegistry(t) + wi, err := registry.WatchPredicate(ctx, m, "0") + if err != nil { + t.Errorf("%v: unexpected error: %v", name, err) + } else { + obj, err := registry.Create(testContext, podA) + if err != nil { + got, open := <-wi.ResultChan() + if !open { + t.Errorf("%v: unexpected channel close", name) + } else { + if e, a := obj, got.Object; !reflect.DeepEqual(e, a) { + t.Errorf("Expected %#v, got %#v", e, a) + } + } + } + wi.Stop() + } + + server.Terminate(t) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/etcd/storage_factory.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/etcd/storage_factory.go new file mode 100644 index 000000000..9aff3e8c3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/etcd/storage_factory.go @@ -0,0 +1,37 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" + etcdstorage "k8s.io/kubernetes/pkg/storage/etcd" +) + +// Creates a cacher on top of the given 'storageInterface'. +func StorageWithCacher( + storageInterface storage.Interface, + capacity int, + objectType runtime.Object, + resourcePrefix string, + scopeStrategy rest.NamespaceScopedStrategy, + newListFunc func() runtime.Object) storage.Interface { + return storage.NewCacher( + storageInterface, capacity, etcdstorage.APIObjectVersioner{}, + objectType, resourcePrefix, scopeStrategy, newListFunc) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/matcher.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/matcher.go new file mode 100644 index 000000000..08e2df7b4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/matcher.go @@ -0,0 +1,142 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 generic + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" +) + +// AttrFunc returns label and field sets for List or Watch to compare against, or an error. +type AttrFunc func(obj runtime.Object) (label labels.Set, field fields.Set, err error) + +// ObjectMetaFieldsSet returns a fields set that represents the ObjectMeta. +func ObjectMetaFieldsSet(objectMeta api.ObjectMeta, hasNamespaceField bool) fields.Set { + if !hasNamespaceField { + return fields.Set{ + "metadata.name": objectMeta.Name, + } + } + return fields.Set{ + "metadata.name": objectMeta.Name, + "metadata.namespace": objectMeta.Namespace, + } +} + +// MergeFieldsSets merges a fields'set from fragment into the source. +func MergeFieldsSets(source fields.Set, fragment fields.Set) fields.Set { + for k, value := range fragment { + source[k] = value + } + return source +} + +// SelectionPredicate implements a generic predicate that can be passed to +// GenericRegistry's List or Watch methods. Implements the Matcher interface. +type SelectionPredicate struct { + Label labels.Selector + Field fields.Selector + GetAttrs AttrFunc +} + +// Matches returns true if the given object's labels and fields (as +// returned by s.GetAttrs) match s.Label and s.Field. An error is +// returned if s.GetAttrs fails. +func (s *SelectionPredicate) Matches(obj runtime.Object) (bool, error) { + if s.Label.Empty() && s.Field.Empty() { + return true, nil + } + labels, fields, err := s.GetAttrs(obj) + if err != nil { + return false, err + } + return s.Label.Matches(labels) && s.Field.Matches(fields), nil +} + +// MatchesSingle will return (name, true) if and only if s.Field matches on the object's +// name. +func (s *SelectionPredicate) MatchesSingle() (string, bool) { + // TODO: should be namespace.name + if name, ok := s.Field.RequiresExactMatch("metadata.name"); ok { + return name, true + } + return "", false +} + +// Matcher can return true if an object matches the Matcher's selection +// criteria. If it is known that the matcher will match only a single object +// then MatchesSingle should return the key of that object and true. This is an +// optimization only--Matches() should continue to work. +type Matcher interface { + // Matches should return true if obj matches this matcher's requirements. + Matches(obj runtime.Object) (matchesThisObject bool, err error) + + // If this matcher matches a single object, return the key for that + // object and true here. This will greatly increase efficiency. You + // must still implement Matches(). Note that key does NOT need to + // include the object's namespace. + MatchesSingle() (key string, matchesSingleObject bool) + + // TODO: when we start indexing objects, add something like the below: + // MatchesIndices() (indexName []string, indexValue []string) + // where indexName/indexValue are the same length. +} + +// MatcherFunc makes a matcher from the provided function. For easy definition +// of matchers for testing. Note: use SelectionPredicate above for real code! +func MatcherFunc(f func(obj runtime.Object) (bool, error)) Matcher { + return matcherFunc(f) +} + +type matcherFunc func(obj runtime.Object) (bool, error) + +// Matches calls the embedded function. +func (m matcherFunc) Matches(obj runtime.Object) (bool, error) { + return m(obj) +} + +// MatchesSingle always returns "", false-- because this is a predicate +// implementation of Matcher. +func (m matcherFunc) MatchesSingle() (string, bool) { + return "", false +} + +// MatchOnKey returns a matcher that will send only the object matching key +// through the matching function f. For testing! +// Note: use SelectionPredicate above for real code! +func MatchOnKey(key string, f func(obj runtime.Object) (bool, error)) Matcher { + return matchKey{key, f} +} + +type matchKey struct { + key string + matcherFunc +} + +// MatchesSingle always returns its key, true. +func (m matchKey) MatchesSingle() (string, bool) { + return m.key, true +} + +var ( + // Assert implementations match the interface. + _ = Matcher(matchKey{}) + _ = Matcher(&SelectionPredicate{}) + _ = Matcher(matcherFunc(nil)) +) diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/matcher_test.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/matcher_test.go new file mode 100644 index 000000000..17c7fb363 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/matcher_test.go @@ -0,0 +1,130 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 generic + +import ( + "errors" + "testing" + + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" +) + +type Ignored struct { + ID string +} + +type IgnoredList struct { + Items []Ignored +} + +func (obj *Ignored) GetObjectKind() unversioned.ObjectKind { return unversioned.EmptyObjectKind } +func (obj *IgnoredList) GetObjectKind() unversioned.ObjectKind { return unversioned.EmptyObjectKind } + +func TestSelectionPredicate(t *testing.T) { + table := map[string]struct { + labelSelector, fieldSelector string + labels labels.Set + fields fields.Set + err error + shouldMatch bool + matchSingleKey string + }{ + "A": { + labelSelector: "name=foo", + fieldSelector: "uid=12345", + labels: labels.Set{"name": "foo"}, + fields: fields.Set{"uid": "12345"}, + shouldMatch: true, + }, + "B": { + labelSelector: "name=foo", + fieldSelector: "uid=12345", + labels: labels.Set{"name": "foo"}, + fields: fields.Set{}, + shouldMatch: false, + }, + "C": { + labelSelector: "name=foo", + fieldSelector: "uid=12345", + labels: labels.Set{}, + fields: fields.Set{"uid": "12345"}, + shouldMatch: false, + }, + "D": { + fieldSelector: "metadata.name=12345", + labels: labels.Set{}, + fields: fields.Set{"metadata.name": "12345"}, + shouldMatch: true, + matchSingleKey: "12345", + }, + "error": { + labelSelector: "name=foo", + fieldSelector: "uid=12345", + err: errors.New("maybe this is a 'wrong object type' error"), + shouldMatch: false, + }, + } + + for name, item := range table { + parsedLabel, err := labels.Parse(item.labelSelector) + if err != nil { + panic(err) + } + parsedField, err := fields.ParseSelector(item.fieldSelector) + if err != nil { + panic(err) + } + sp := &SelectionPredicate{ + Label: parsedLabel, + Field: parsedField, + GetAttrs: func(runtime.Object) (label labels.Set, field fields.Set, err error) { + return item.labels, item.fields, item.err + }, + } + got, err := sp.Matches(&Ignored{}) + if e, a := item.err, err; e != a { + t.Errorf("%v: expected %v, got %v", name, e, a) + continue + } + if e, a := item.shouldMatch, got; e != a { + t.Errorf("%v: expected %v, got %v", name, e, a) + } + if key := item.matchSingleKey; key != "" { + got, ok := sp.MatchesSingle() + if !ok { + t.Errorf("%v: expected single match", name) + } + if e, a := key, got; e != a { + t.Errorf("%v: expected %v, got %v", name, e, a) + } + } + } +} + +func TestSingleMatch(t *testing.T) { + m := MatchOnKey("pod-name-here", func(obj runtime.Object) (bool, error) { return true, nil }) + got, ok := m.MatchesSingle() + if !ok { + t.Errorf("Expected MatchesSingle to return true") + } + if e, a := "pod-name-here", got; e != a { + t.Errorf("Expected %#v, got %#v", e, a) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/options.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/options.go new file mode 100644 index 000000000..eea52c995 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/options.go @@ -0,0 +1,28 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 generic + +import ( + pkgstorage "k8s.io/kubernetes/pkg/storage" +) + +// RESTOptions is set of configuration options to generic registries. +type RESTOptions struct { + Storage pkgstorage.Interface + Decorator StorageDecorator + DeleteCollectionWorkers int +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/doc.go new file mode 100644 index 000000000..fef461387 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 rest has generic implementations of resources used for +// REST responses +package rest diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/proxy.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/proxy.go new file mode 100644 index 000000000..ca28831c8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/proxy.go @@ -0,0 +1,242 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 rest + +import ( + "io" + "net/http" + "net/http/httputil" + "net/url" + "strings" + "sync" + "time" + + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/util/httpstream" + "k8s.io/kubernetes/pkg/util/net" + "k8s.io/kubernetes/pkg/util/proxy" + + "github.com/golang/glog" + "github.com/mxk/go-flowrate/flowrate" +) + +// UpgradeAwareProxyHandler is a handler for proxy requests that may require an upgrade +type UpgradeAwareProxyHandler struct { + UpgradeRequired bool + Location *url.URL + // Transport provides an optional round tripper to use to proxy. If nil, the default proxy transport is used + Transport http.RoundTripper + // WrapTransport indicates whether the provided Transport should be wrapped with default proxy transport behavior (URL rewriting, X-Forwarded-* header setting) + WrapTransport bool + FlushInterval time.Duration + MaxBytesPerSec int64 + Responder ErrorResponder +} + +const defaultFlushInterval = 200 * time.Millisecond + +// ErrorResponder abstracts error reporting to the proxy handler to remove the need to hardcode a particular +// error format. +type ErrorResponder interface { + Error(err error) +} + +// NewUpgradeAwareProxyHandler creates a new proxy handler with a default flush interval. Responder is required for returning +// errors to the caller. +func NewUpgradeAwareProxyHandler(location *url.URL, transport http.RoundTripper, wrapTransport, upgradeRequired bool, responder ErrorResponder) *UpgradeAwareProxyHandler { + return &UpgradeAwareProxyHandler{ + Location: location, + Transport: transport, + WrapTransport: wrapTransport, + UpgradeRequired: upgradeRequired, + FlushInterval: defaultFlushInterval, + Responder: responder, + } +} + +// ServeHTTP handles the proxy request +func (h *UpgradeAwareProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if len(h.Location.Scheme) == 0 { + h.Location.Scheme = "http" + } + if h.tryUpgrade(w, req) { + return + } + if h.UpgradeRequired { + h.Responder.Error(errors.NewBadRequest("Upgrade request required")) + return + } + + loc := *h.Location + loc.RawQuery = req.URL.RawQuery + + // If original request URL ended in '/', append a '/' at the end of the + // of the proxy URL + if !strings.HasSuffix(loc.Path, "/") && strings.HasSuffix(req.URL.Path, "/") { + loc.Path += "/" + } + + // From pkg/apiserver/proxy.go#ServeHTTP: + // Redirect requests with an empty path to a location that ends with a '/' + // This is essentially a hack for http://issue.k8s.io/4958. + // Note: Keep this code after tryUpgrade to not break that flow. + if len(loc.Path) == 0 { + var queryPart string + if len(req.URL.RawQuery) > 0 { + queryPart = "?" + req.URL.RawQuery + } + w.Header().Set("Location", req.URL.Path+"/"+queryPart) + w.WriteHeader(http.StatusMovedPermanently) + return + } + + if h.Transport == nil || h.WrapTransport { + h.Transport = h.defaultProxyTransport(req.URL, h.Transport) + } + + newReq, err := http.NewRequest(req.Method, loc.String(), req.Body) + if err != nil { + h.Responder.Error(err) + return + } + newReq.Header = req.Header + newReq.ContentLength = req.ContentLength + // Copy the TransferEncoding is for future-proofing. Currently Go only supports "chunked" and + // it can determine the TransferEncoding based on ContentLength and the Body. + newReq.TransferEncoding = req.TransferEncoding + + proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: h.Location.Scheme, Host: h.Location.Host}) + proxy.Transport = h.Transport + proxy.FlushInterval = h.FlushInterval + proxy.ServeHTTP(w, newReq) +} + +// tryUpgrade returns true if the request was handled. +func (h *UpgradeAwareProxyHandler) tryUpgrade(w http.ResponseWriter, req *http.Request) bool { + if !httpstream.IsUpgradeRequest(req) { + return false + } + + backendConn, err := proxy.DialURL(h.Location, h.Transport) + if err != nil { + h.Responder.Error(err) + return true + } + defer backendConn.Close() + + requestHijackedConn, _, err := w.(http.Hijacker).Hijack() + if err != nil { + h.Responder.Error(err) + return true + } + defer requestHijackedConn.Close() + + newReq, err := http.NewRequest(req.Method, h.Location.String(), req.Body) + if err != nil { + h.Responder.Error(err) + return true + } + newReq.Header = req.Header + + if err = newReq.Write(backendConn); err != nil { + h.Responder.Error(err) + return true + } + + wg := &sync.WaitGroup{} + wg.Add(2) + + go func() { + var writer io.WriteCloser + if h.MaxBytesPerSec > 0 { + writer = flowrate.NewWriter(backendConn, h.MaxBytesPerSec) + } else { + writer = backendConn + } + _, err := io.Copy(writer, requestHijackedConn) + if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { + glog.Errorf("Error proxying data from client to backend: %v", err) + } + wg.Done() + }() + + go func() { + var reader io.ReadCloser + if h.MaxBytesPerSec > 0 { + reader = flowrate.NewReader(backendConn, h.MaxBytesPerSec) + } else { + reader = backendConn + } + _, err := io.Copy(requestHijackedConn, reader) + if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { + glog.Errorf("Error proxying data from backend to client: %v", err) + } + wg.Done() + }() + + wg.Wait() + return true +} + +func (h *UpgradeAwareProxyHandler) defaultProxyTransport(url *url.URL, internalTransport http.RoundTripper) http.RoundTripper { + scheme := url.Scheme + host := url.Host + suffix := h.Location.Path + if strings.HasSuffix(url.Path, "/") && !strings.HasSuffix(suffix, "/") { + suffix += "/" + } + pathPrepend := strings.TrimSuffix(url.Path, suffix) + rewritingTransport := &proxy.Transport{ + Scheme: scheme, + Host: host, + PathPrepend: pathPrepend, + RoundTripper: internalTransport, + } + return &corsRemovingTransport{ + RoundTripper: rewritingTransport, + } +} + +// corsRemovingTransport is a wrapper for an internal transport. It removes CORS headers +// from the internal response. +type corsRemovingTransport struct { + http.RoundTripper +} + +func (p *corsRemovingTransport) RoundTrip(req *http.Request) (*http.Response, error) { + resp, err := p.RoundTripper.RoundTrip(req) + if err != nil { + return nil, err + } + removeCORSHeaders(resp) + return resp, nil +} + +var _ = net.RoundTripperWrapper(&corsRemovingTransport{}) + +func (rt *corsRemovingTransport) WrappedRoundTripper() http.RoundTripper { + return rt.RoundTripper +} + +// removeCORSHeaders strip CORS headers sent from the backend +// This should be called on all responses before returning +func removeCORSHeaders(resp *http.Response) { + resp.Header.Del("Access-Control-Allow-Credentials") + resp.Header.Del("Access-Control-Allow-Headers") + resp.Header.Del("Access-Control-Allow-Methods") + resp.Header.Del("Access-Control-Allow-Origin") +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/proxy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/proxy_test.go new file mode 100644 index 000000000..7e4c27915 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/proxy_test.go @@ -0,0 +1,731 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 rest + +import ( + "bytes" + "compress/gzip" + "crypto/tls" + "crypto/x509" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/httptest" + "net/http/httputil" + "net/url" + "reflect" + "strconv" + "strings" + "testing" + + "golang.org/x/net/websocket" + + utilnet "k8s.io/kubernetes/pkg/util/net" + "k8s.io/kubernetes/pkg/util/proxy" +) + +type fakeResponder struct { + called bool + err error +} + +func (r *fakeResponder) Error(err error) { + if r.called { + panic("called twice") + } + r.called = true + r.err = err +} + +type SimpleBackendHandler struct { + requestURL url.URL + requestHeader http.Header + requestBody []byte + requestMethod string + responseBody string + responseHeader map[string]string + t *testing.T +} + +func (s *SimpleBackendHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + s.requestURL = *req.URL + s.requestHeader = req.Header + s.requestMethod = req.Method + var err error + s.requestBody, err = ioutil.ReadAll(req.Body) + if err != nil { + s.t.Errorf("Unexpected error: %v", err) + return + } + + if s.responseHeader != nil { + for k, v := range s.responseHeader { + w.Header().Add(k, v) + } + } + w.Write([]byte(s.responseBody)) +} + +func validateParameters(t *testing.T, name string, actual url.Values, expected map[string]string) { + for k, v := range expected { + actualValue, ok := actual[k] + if !ok { + t.Errorf("%s: Expected parameter %s not received", name, k) + continue + } + if actualValue[0] != v { + t.Errorf("%s: Parameter %s values don't match. Actual: %#v, Expected: %s", + name, k, actualValue, v) + } + } +} + +func validateHeaders(t *testing.T, name string, actual http.Header, expected map[string]string, notExpected []string) { + for k, v := range expected { + actualValue, ok := actual[k] + if !ok { + t.Errorf("%s: Expected header %s not received", name, k) + continue + } + if actualValue[0] != v { + t.Errorf("%s: Header %s values don't match. Actual: %s, Expected: %s", + name, k, actualValue, v) + } + } + if notExpected == nil { + return + } + for _, h := range notExpected { + if _, present := actual[h]; present { + t.Errorf("%s: unexpected header: %s", name, h) + } + } +} + +func TestServeHTTP(t *testing.T) { + tests := []struct { + name string + method string + requestPath string + expectedPath string + requestBody string + requestParams map[string]string + requestHeader map[string]string + responseHeader map[string]string + expectedRespHeader map[string]string + notExpectedRespHeader []string + upgradeRequired bool + expectError func(err error) bool + }{ + { + name: "root path, simple get", + method: "GET", + requestPath: "/", + expectedPath: "/", + }, + { + name: "no upgrade header sent", + method: "GET", + requestPath: "/", + upgradeRequired: true, + expectError: func(err error) bool { + return err != nil && strings.Contains(err.Error(), "Upgrade request required") + }, + }, + { + name: "simple path, get", + method: "GET", + requestPath: "/path/to/test", + expectedPath: "/path/to/test", + }, + { + name: "request params", + method: "POST", + requestPath: "/some/path/", + expectedPath: "/some/path/", + requestParams: map[string]string{"param1": "value/1", "param2": "value%2"}, + requestBody: "test request body", + }, + { + name: "request headers", + method: "PUT", + requestPath: "/some/path", + expectedPath: "/some/path", + requestHeader: map[string]string{"Header1": "value1", "Header2": "value2"}, + }, + { + name: "empty path - slash should be added", + method: "GET", + requestPath: "", + expectedPath: "/", + }, + { + name: "remove CORS headers", + method: "GET", + requestPath: "/some/path", + expectedPath: "/some/path", + responseHeader: map[string]string{ + "Header1": "value1", + "Access-Control-Allow-Origin": "some.server", + "Access-Control-Allow-Methods": "GET"}, + expectedRespHeader: map[string]string{ + "Header1": "value1", + }, + notExpectedRespHeader: []string{ + "Access-Control-Allow-Origin", + "Access-Control-Allow-Methods", + }, + }, + } + + for i, test := range tests { + func() { + backendResponse := "Hello" + backendResponseHeader := test.responseHeader + // Test a simple header if not specified in the test + if backendResponseHeader == nil && test.expectedRespHeader == nil { + backendResponseHeader = map[string]string{"Content-Type": "text/html"} + test.expectedRespHeader = map[string]string{"Content-Type": "text/html"} + } + backendHandler := &SimpleBackendHandler{ + responseBody: backendResponse, + responseHeader: backendResponseHeader, + } + backendServer := httptest.NewServer(backendHandler) + // TODO: Uncomment when fix #19254 + // defer backendServer.Close() + + responder := &fakeResponder{} + backendURL, _ := url.Parse(backendServer.URL) + backendURL.Path = test.requestPath + proxyHandler := &UpgradeAwareProxyHandler{ + Location: backendURL, + Responder: responder, + UpgradeRequired: test.upgradeRequired, + } + proxyServer := httptest.NewServer(proxyHandler) + // TODO: Uncomment when fix #19254 + // defer proxyServer.Close() + proxyURL, _ := url.Parse(proxyServer.URL) + proxyURL.Path = test.requestPath + paramValues := url.Values{} + for k, v := range test.requestParams { + paramValues[k] = []string{v} + } + proxyURL.RawQuery = paramValues.Encode() + var requestBody io.Reader + if test.requestBody != "" { + requestBody = bytes.NewBufferString(test.requestBody) + } + req, err := http.NewRequest(test.method, proxyURL.String(), requestBody) + if test.requestHeader != nil { + header := http.Header{} + for k, v := range test.requestHeader { + header.Add(k, v) + } + req.Header = header + } + if err != nil { + t.Errorf("Error creating client request: %v", err) + } + client := &http.Client{} + res, err := client.Do(req) + if err != nil { + t.Errorf("Error from proxy request: %v", err) + } + + if test.expectError != nil { + if !responder.called { + t.Errorf("%d: responder was not invoked", i) + return + } + if !test.expectError(responder.err) { + t.Errorf("%d: unexpected error: %v", i, responder.err) + } + return + } + + // Validate backend request + // Method + if backendHandler.requestMethod != test.method { + t.Errorf("Unexpected request method: %s. Expected: %s", + backendHandler.requestMethod, test.method) + } + + // Body + if string(backendHandler.requestBody) != test.requestBody { + t.Errorf("Unexpected request body: %s. Expected: %s", + string(backendHandler.requestBody), test.requestBody) + } + + // Path + if backendHandler.requestURL.Path != test.expectedPath { + t.Errorf("Unexpected request path: %s", backendHandler.requestURL.Path) + } + // Parameters + validateParameters(t, test.name, backendHandler.requestURL.Query(), test.requestParams) + + // Headers + validateHeaders(t, test.name+" backend request", backendHandler.requestHeader, + test.requestHeader, nil) + + // Validate proxy response + + // Response Headers + validateHeaders(t, test.name+" backend headers", res.Header, test.expectedRespHeader, test.notExpectedRespHeader) + + // Validate Body + responseBody, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Errorf("Unexpected error reading response body: %v", err) + } + if rb := string(responseBody); rb != backendResponse { + t.Errorf("Did not get expected response body: %s. Expected: %s", rb, backendResponse) + } + + // Error + if responder.called { + t.Errorf("Unexpected proxy handler error: %v", responder.err) + } + }() + } +} + +func TestProxyUpgrade(t *testing.T) { + + localhostPool := x509.NewCertPool() + if !localhostPool.AppendCertsFromPEM(localhostCert) { + t.Errorf("error setting up localhostCert pool") + } + + testcases := map[string]struct { + ServerFunc func(http.Handler) *httptest.Server + ProxyTransport http.RoundTripper + }{ + "http": { + ServerFunc: httptest.NewServer, + ProxyTransport: nil, + }, + "https (invalid hostname + InsecureSkipVerify)": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(exampleCert, exampleKey) + if err != nil { + t.Errorf("https (invalid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}), + }, + "https (valid hostname + RootCAs)": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(localhostCert, localhostKey) + if err != nil { + t.Errorf("https (valid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{TLSClientConfig: &tls.Config{RootCAs: localhostPool}}), + }, + "https (valid hostname + RootCAs + custom dialer)": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(localhostCert, localhostKey) + if err != nil { + t.Errorf("https (valid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: utilnet.SetTransportDefaults(&http.Transport{Dial: net.Dial, TLSClientConfig: &tls.Config{RootCAs: localhostPool}}), + }, + } + + for k, tc := range testcases { + + backendServer := tc.ServerFunc(websocket.Handler(func(ws *websocket.Conn) { + defer ws.Close() + body := make([]byte, 5) + ws.Read(body) + ws.Write([]byte("hello " + string(body))) + })) + // TODO: Uncomment when fix #19254 + // defer backendServer.Close() + + serverURL, _ := url.Parse(backendServer.URL) + proxyHandler := &UpgradeAwareProxyHandler{ + Location: serverURL, + Transport: tc.ProxyTransport, + } + proxy := httptest.NewServer(proxyHandler) + // TODO: Uncomment when fix #19254 + // defer proxy.Close() + + ws, err := websocket.Dial("ws://"+proxy.Listener.Addr().String()+"/some/path", "", "http://127.0.0.1/") + if err != nil { + t.Fatalf("%s: websocket dial err: %s", k, err) + } + defer ws.Close() + + if _, err := ws.Write([]byte("world")); err != nil { + t.Fatalf("%s: write err: %s", k, err) + } + + response := make([]byte, 20) + n, err := ws.Read(response) + if err != nil { + t.Fatalf("%s: read err: %s", k, err) + } + if e, a := "hello world", string(response[0:n]); e != a { + t.Fatalf("%s: expected '%#v', got '%#v'", k, e, a) + } + } +} + +func TestDefaultProxyTransport(t *testing.T) { + tests := []struct { + name, + url, + location, + expectedScheme, + expectedHost, + expectedPathPrepend string + }{ + + { + name: "simple path", + url: "http://test.server:8080/a/test/location", + location: "http://localhost/location", + expectedScheme: "http", + expectedHost: "test.server:8080", + expectedPathPrepend: "/a/test", + }, + { + name: "empty path", + url: "http://test.server:8080/a/test/", + location: "http://localhost", + expectedScheme: "http", + expectedHost: "test.server:8080", + expectedPathPrepend: "/a/test", + }, + { + name: "location ending in slash", + url: "http://test.server:8080/a/test/", + location: "http://localhost/", + expectedScheme: "http", + expectedHost: "test.server:8080", + expectedPathPrepend: "/a/test", + }, + } + + for _, test := range tests { + locURL, _ := url.Parse(test.location) + URL, _ := url.Parse(test.url) + h := UpgradeAwareProxyHandler{ + Location: locURL, + } + result := h.defaultProxyTransport(URL, nil) + transport := result.(*corsRemovingTransport).RoundTripper.(*proxy.Transport) + if transport.Scheme != test.expectedScheme { + t.Errorf("%s: unexpected scheme. Actual: %s, Expected: %s", test.name, transport.Scheme, test.expectedScheme) + } + if transport.Host != test.expectedHost { + t.Errorf("%s: unexpected host. Actual: %s, Expected: %s", test.name, transport.Host, test.expectedHost) + } + if transport.PathPrepend != test.expectedPathPrepend { + t.Errorf("%s: unexpected path prepend. Actual: %s, Expected: %s", test.name, transport.PathPrepend, test.expectedPathPrepend) + } + } +} + +func TestProxyRequestContentLengthAndTransferEncoding(t *testing.T) { + chunk := func(data []byte) []byte { + out := &bytes.Buffer{} + chunker := httputil.NewChunkedWriter(out) + for _, b := range data { + if _, err := chunker.Write([]byte{b}); err != nil { + panic(err) + } + } + chunker.Close() + out.Write([]byte("\r\n")) + return out.Bytes() + } + + zip := func(data []byte) []byte { + out := &bytes.Buffer{} + zipper := gzip.NewWriter(out) + if _, err := zipper.Write(data); err != nil { + panic(err) + } + zipper.Close() + return out.Bytes() + } + + sampleData := []byte("abcde") + + table := map[string]struct { + reqHeaders http.Header + reqBody []byte + + expectedHeaders http.Header + expectedBody []byte + }{ + "content-length": { + reqHeaders: http.Header{ + "Content-Length": []string{"5"}, + }, + reqBody: sampleData, + + expectedHeaders: http.Header{ + "Content-Length": []string{"5"}, + "Content-Encoding": nil, // none set + "Transfer-Encoding": nil, // none set + }, + expectedBody: sampleData, + }, + + "content-length + identity transfer-encoding": { + reqHeaders: http.Header{ + "Content-Length": []string{"5"}, + "Transfer-Encoding": []string{"identity"}, + }, + reqBody: sampleData, + + expectedHeaders: http.Header{ + "Content-Length": []string{"5"}, + "Content-Encoding": nil, // none set + "Transfer-Encoding": nil, // gets removed + }, + expectedBody: sampleData, + }, + + "content-length + gzip content-encoding": { + reqHeaders: http.Header{ + "Content-Length": []string{strconv.Itoa(len(zip(sampleData)))}, + "Content-Encoding": []string{"gzip"}, + }, + reqBody: zip(sampleData), + + expectedHeaders: http.Header{ + "Content-Length": []string{strconv.Itoa(len(zip(sampleData)))}, + "Content-Encoding": []string{"gzip"}, + "Transfer-Encoding": nil, // none set + }, + expectedBody: zip(sampleData), + }, + + "chunked transfer-encoding": { + reqHeaders: http.Header{ + "Transfer-Encoding": []string{"chunked"}, + }, + reqBody: chunk(sampleData), + + expectedHeaders: http.Header{ + "Content-Length": nil, // none set + "Content-Encoding": nil, // none set + "Transfer-Encoding": nil, // Transfer-Encoding gets removed + }, + expectedBody: sampleData, // sample data is unchunked + }, + + "chunked transfer-encoding + gzip content-encoding": { + reqHeaders: http.Header{ + "Content-Encoding": []string{"gzip"}, + "Transfer-Encoding": []string{"chunked"}, + }, + reqBody: chunk(zip(sampleData)), + + expectedHeaders: http.Header{ + "Content-Length": nil, // none set + "Content-Encoding": []string{"gzip"}, + "Transfer-Encoding": nil, // gets removed + }, + expectedBody: zip(sampleData), // sample data is unchunked, but content-encoding is preserved + }, + + // "Transfer-Encoding: gzip" is not supported by go + // See http/transfer.go#fixTransferEncoding (https://golang.org/src/net/http/transfer.go#L427) + // Once it is supported, this test case should succeed + // + // "gzip+chunked transfer-encoding": { + // reqHeaders: http.Header{ + // "Transfer-Encoding": []string{"chunked,gzip"}, + // }, + // reqBody: chunk(zip(sampleData)), + // + // expectedHeaders: http.Header{ + // "Content-Length": nil, // no content-length headers + // "Transfer-Encoding": nil, // Transfer-Encoding gets removed + // }, + // expectedBody: sampleData, + // }, + } + + successfulResponse := "backend passed tests" + for k, item := range table { + // Start the downstream server + downstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // Verify headers + for header, v := range item.expectedHeaders { + if !reflect.DeepEqual(v, req.Header[header]) { + t.Errorf("%s: Expected headers for %s to be %v, got %v", k, header, v, req.Header[header]) + } + } + + // Read body + body, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Errorf("%s: unexpected error %v", k, err) + } + req.Body.Close() + + // Verify length + if req.ContentLength > 0 && req.ContentLength != int64(len(body)) { + t.Errorf("%s: ContentLength was %d, len(data) was %d", k, req.ContentLength, len(body)) + } + + // Verify content + if !bytes.Equal(item.expectedBody, body) { + t.Errorf("%s: Expected %q, got %q", k, string(item.expectedBody), string(body)) + } + + // Write successful response + w.Write([]byte(successfulResponse)) + })) + // TODO: Uncomment when fix #19254 + // defer downstreamServer.Close() + + responder := &fakeResponder{} + backendURL, _ := url.Parse(downstreamServer.URL) + proxyHandler := &UpgradeAwareProxyHandler{ + Location: backendURL, + Responder: responder, + UpgradeRequired: false, + } + proxyServer := httptest.NewServer(proxyHandler) + // TODO: Uncomment when fix #19254 + // defer proxyServer.Close() + + // Dial the proxy server + conn, err := net.Dial(proxyServer.Listener.Addr().Network(), proxyServer.Listener.Addr().String()) + if err != nil { + t.Errorf("unexpected error %v", err) + continue + } + defer conn.Close() + + // Add standard http 1.1 headers + if item.reqHeaders == nil { + item.reqHeaders = http.Header{} + } + item.reqHeaders.Add("Connection", "close") + item.reqHeaders.Add("Host", proxyServer.Listener.Addr().String()) + + // Write the request headers + if _, err := fmt.Fprint(conn, "POST / HTTP/1.1\r\n"); err != nil { + t.Fatalf("%s unexpected error %v", k, err) + } + for header, values := range item.reqHeaders { + for _, value := range values { + if _, err := fmt.Fprintf(conn, "%s: %s\r\n", header, value); err != nil { + t.Fatalf("%s: unexpected error %v", k, err) + } + } + } + // Header separator + if _, err := fmt.Fprint(conn, "\r\n"); err != nil { + t.Fatalf("%s: unexpected error %v", k, err) + } + // Body + if _, err := conn.Write(item.reqBody); err != nil { + t.Fatalf("%s: unexpected error %v", k, err) + } + + // Read response + response, err := ioutil.ReadAll(conn) + if err != nil { + t.Errorf("%s: unexpected error %v", k, err) + continue + } + if !strings.HasSuffix(string(response), successfulResponse) { + t.Errorf("%s: Did not get successful response: %s", k, string(response)) + continue + } + } +} + +// exampleCert was generated from crypto/tls/generate_cert.go with the following command: +// go run generate_cert.go --rsa-bits 512 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var exampleCert = []byte(`-----BEGIN CERTIFICATE----- +MIIBcjCCAR6gAwIBAgIQBOUTYowZaENkZi0faI9DgTALBgkqhkiG9w0BAQswEjEQ +MA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2MDAw +MFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCZ +xfR3sgeHBraGFfF/24tTn4PRVAHOf2UOOxSQRs+aYjNqimFqf/SRIblQgeXdBJDR +gVK5F1Js2zwlehw0bHxRAgMBAAGjUDBOMA4GA1UdDwEB/wQEAwIApDATBgNVHSUE +DDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MBYGA1UdEQQPMA2CC2V4YW1w +bGUuY29tMAsGCSqGSIb3DQEBCwNBAI/mfBB8dm33IpUl+acSyWfL6gX5Wc0FFyVj +dKeesE1XBuPX1My/rzU6Oy/YwX7LOL4FaeNUS6bbL4axSLPKYSs= +-----END CERTIFICATE-----`) + +var exampleKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAJnF9HeyB4cGtoYV8X/bi1Ofg9FUAc5/ZQ47FJBGz5piM2qKYWp/ +9JEhuVCB5d0EkNGBUrkXUmzbPCV6HDRsfFECAwEAAQJBAJLH9yPuButniACTn5L5 +IJQw1mWQt6zBw9eCo41YWkA0866EgjC53aPZaRjXMp0uNJGdIsys2V5rCOOLWN2C +ODECIQDICHsi8QQQ9wpuJy8X5l8MAfxHL+DIqI84wQTeVM91FQIhAMTME8A18/7h +1Ad6drdnxAkuC0tX6Sx0LDozrmen+HFNAiAlcEDrt0RVkIcpOrg7tuhPLQf0oudl +Zvb3Xlj069awSQIgcT15E/43w2+RASifzVNhQ2MCTr1sSA8lL+xzK+REmnUCIBhQ +j4139pf8Re1J50zBxS/JlQfgDQi9sO9pYeiHIxNs +-----END RSA PRIVATE KEY-----`) + +// localhostCert was generated from crypto/tls/generate_cert.go with the following command: +// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var localhostCert = []byte(`-----BEGIN CERTIFICATE----- +MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD +bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj +bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa +IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA +AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud +EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA +AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk +Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA== +-----END CERTIFICATE-----`) + +// localhostKey is the private key for localhostCert. +var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0 +0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV +NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d +AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW +MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD +EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA +1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE= +-----END RSA PRIVATE KEY-----`) diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/response_checker.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/response_checker.go new file mode 100644 index 000000000..b0c61075c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/response_checker.go @@ -0,0 +1,71 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 rest + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" + + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/unversioned" +) + +// Check the http error status from a location URL. +// And convert an error into a structured API object. +// Finally ensure we close the body before returning the error +type HttpResponseChecker interface { + Check(resp *http.Response) error +} + +// Max length read from the response body of a location which returns error status +const ( + maxReadLength = 50000 +) + +// A generic http response checker to transform the error. +type GenericHttpResponseChecker struct { + QualifiedResource unversioned.GroupResource + Name string +} + +func (checker GenericHttpResponseChecker) Check(resp *http.Response) error { + if resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent { + defer resp.Body.Close() + bodyBytes, err := ioutil.ReadAll(io.LimitReader(resp.Body, maxReadLength)) + if err != nil { + return errors.NewInternalError(err) + } + bodyText := string(bodyBytes) + + switch { + case resp.StatusCode == http.StatusInternalServerError: + return errors.NewInternalError(fmt.Errorf("%s", bodyText)) + case resp.StatusCode == http.StatusBadRequest: + return errors.NewBadRequest(bodyText) + case resp.StatusCode == http.StatusNotFound: + return errors.NewGenericServerResponse(resp.StatusCode, "", checker.QualifiedResource, checker.Name, bodyText, 0, false) + } + return errors.NewGenericServerResponse(resp.StatusCode, "", checker.QualifiedResource, checker.Name, bodyText, 0, false) + } + return nil +} + +func NewGenericHttpResponseChecker(qualifiedResource unversioned.GroupResource, name string) GenericHttpResponseChecker { + return GenericHttpResponseChecker{QualifiedResource: qualifiedResource, Name: name} +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/response_checker_test.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/response_checker_test.go new file mode 100644 index 000000000..f1ad62020 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/response_checker_test.go @@ -0,0 +1,95 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 rest + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "reflect" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" +) + +func TestGenericHttpResponseChecker(t *testing.T) { + responseChecker := NewGenericHttpResponseChecker(api.Resource("pods"), "foo") + tests := []struct { + resp *http.Response + expectError bool + expected error + name string + }{ + { + resp: &http.Response{ + Body: ioutil.NopCloser(bytes.NewBufferString("Success")), + StatusCode: http.StatusOK, + }, + expectError: false, + name: "ok", + }, + { + resp: &http.Response{ + Body: ioutil.NopCloser(bytes.NewBufferString("Invalid request.")), + StatusCode: http.StatusBadRequest, + }, + expectError: true, + expected: errors.NewBadRequest("Invalid request."), + name: "bad request", + }, + { + resp: &http.Response{ + Body: ioutil.NopCloser(bytes.NewBufferString("Pod does not exist.")), + StatusCode: http.StatusInternalServerError, + }, + expectError: true, + expected: errors.NewInternalError(fmt.Errorf("%s", "Pod does not exist.")), + name: "internal server error", + }, + } + for _, test := range tests { + err := responseChecker.Check(test.resp) + if test.expectError && err == nil { + t.Error("unexpected non-error") + } + if !test.expectError && err != nil { + t.Errorf("unexpected error: %v", err) + } + if test.expectError && !reflect.DeepEqual(err, test.expected) { + t.Errorf("expected: %s, saw: %s", test.expected, err) + } + } +} + +func TestGenericHttpResponseCheckerLimitReader(t *testing.T) { + responseChecker := NewGenericHttpResponseChecker(api.Resource("pods"), "foo") + excessedString := strings.Repeat("a", (maxReadLength + 10000)) + resp := &http.Response{ + Body: ioutil.NopCloser(bytes.NewBufferString(excessedString)), + StatusCode: http.StatusBadRequest, + } + err := responseChecker.Check(resp) + if err == nil { + t.Error("unexpected non-error") + } + if len(err.Error()) != maxReadLength { + t.Errorf("expected lenth of error message: %d, saw: %d", maxReadLength, len(err.Error())) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/streamer.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/streamer.go new file mode 100644 index 000000000..afa9eb5b5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/streamer.go @@ -0,0 +1,79 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 rest + +import ( + "io" + "net/http" + "net/url" + "strings" + + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/unversioned" +) + +// LocationStreamer is a resource that streams the contents of a particular +// location URL +type LocationStreamer struct { + Location *url.URL + Transport http.RoundTripper + ContentType string + Flush bool + ResponseChecker HttpResponseChecker +} + +// a LocationStreamer must implement a rest.ResourceStreamer +var _ rest.ResourceStreamer = &LocationStreamer{} + +func (obj *LocationStreamer) GetObjectKind() unversioned.ObjectKind { + return unversioned.EmptyObjectKind +} + +// InputStream returns a stream with the contents of the URL location. If no location is provided, +// a null stream is returned. +func (s *LocationStreamer) InputStream(apiVersion, acceptHeader string) (stream io.ReadCloser, flush bool, contentType string, err error) { + if s.Location == nil { + // If no location was provided, return a null stream + return nil, false, "", nil + } + transport := s.Transport + if transport == nil { + transport = http.DefaultTransport + } + client := &http.Client{Transport: transport} + resp, err := client.Get(s.Location.String()) + if err != nil { + return nil, false, "", err + } + + if s.ResponseChecker != nil { + if err = s.ResponseChecker.Check(resp); err != nil { + return nil, false, "", err + } + } + + contentType = s.ContentType + if len(contentType) == 0 { + contentType = resp.Header.Get("Content-Type") + if len(contentType) > 0 { + contentType = strings.TrimSpace(strings.SplitN(contentType, ";", 2)[0]) + } + } + flush = s.Flush + stream = resp.Body + return +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/streamer_test.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/streamer_test.go new file mode 100644 index 000000000..d941d3b23 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/rest/streamer_test.go @@ -0,0 +1,149 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 rest + +import ( + "bufio" + "bytes" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" +) + +func TestInputStreamReader(t *testing.T) { + resultString := "Test output" + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Write([]byte(resultString)) + })) + // TODO: Uncomment when fix #19254 + // defer s.Close() + u, err := url.Parse(s.URL) + if err != nil { + t.Errorf("Error parsing server URL: %v", err) + return + } + streamer := &LocationStreamer{ + Location: u, + } + readCloser, _, _, err := streamer.InputStream("", "") + if err != nil { + t.Errorf("Unexpected error when getting stream: %v", err) + return + } + defer readCloser.Close() + result, err := ioutil.ReadAll(readCloser) + if string(result) != resultString { + t.Errorf("Stream content does not match. Got: %s. Expected: %s.", string(result), resultString) + } +} + +func TestInputStreamNullLocation(t *testing.T) { + streamer := &LocationStreamer{ + Location: nil, + } + readCloser, _, _, err := streamer.InputStream("", "") + if err != nil { + t.Errorf("Unexpected error when getting stream with null location: %v", err) + } + if readCloser != nil { + t.Errorf("Expected stream to be nil. Got: %#v", readCloser) + } +} + +type testTransport struct { + body string + err error +} + +func (tt *testTransport) RoundTrip(req *http.Request) (*http.Response, error) { + r := bufio.NewReader(bytes.NewBufferString(tt.body)) + return http.ReadResponse(r, req) +} + +func fakeTransport(mime, message string) http.RoundTripper { + content := fmt.Sprintf("HTTP/1.1 200 OK\nContent-Type: %s\n\n%s", mime, message) + return &testTransport{body: content} +} + +func TestInputStreamContentType(t *testing.T) { + location, _ := url.Parse("http://www.example.com") + streamer := &LocationStreamer{ + Location: location, + Transport: fakeTransport("application/json", "hello world"), + } + readCloser, _, contentType, err := streamer.InputStream("", "") + if err != nil { + t.Errorf("Unexpected error when getting stream: %v", err) + return + } + defer readCloser.Close() + if contentType != "application/json" { + t.Errorf("Unexpected content type. Got: %s. Expected: application/json", contentType) + } +} + +func TestInputStreamTransport(t *testing.T) { + message := "hello world" + location, _ := url.Parse("http://www.example.com") + streamer := &LocationStreamer{ + Location: location, + Transport: fakeTransport("text/plain", message), + } + readCloser, _, _, err := streamer.InputStream("", "") + if err != nil { + t.Errorf("Unexpected error when getting stream: %v", err) + return + } + defer readCloser.Close() + result, err := ioutil.ReadAll(readCloser) + if string(result) != message { + t.Errorf("Stream content does not match. Got: %s. Expected: %s.", string(result), message) + } +} + +func fakeInternalServerErrorTransport(mime, message string) http.RoundTripper { + content := fmt.Sprintf("HTTP/1.1 500 \"Internal Server Error\"\nContent-Type: %s\n\n%s", mime, message) + return &testTransport{body: content} +} + +func TestInputStreamInternalServerErrorTransport(t *testing.T) { + message := "Pod is in PodPending" + location, _ := url.Parse("http://www.example.com") + streamer := &LocationStreamer{ + Location: location, + Transport: fakeInternalServerErrorTransport("text/plain", message), + ResponseChecker: NewGenericHttpResponseChecker(api.Resource(""), ""), + } + expectedError := errors.NewInternalError(fmt.Errorf("%s", message)) + + _, _, _, err := streamer.InputStream("", "") + if err == nil { + t.Errorf("unexpected non-error") + return + } + + if !reflect.DeepEqual(err, expectedError) { + t.Errorf("StreamInternalServerError does not match. Got: %s. Expected: %s.", err, expectedError) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/generic/storage_decorator.go b/vendor/k8s.io/kubernetes/pkg/registry/generic/storage_decorator.go new file mode 100644 index 000000000..70109efe3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/generic/storage_decorator.go @@ -0,0 +1,44 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 generic + +import ( + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" +) + +// StorageDecorator is a function signature for producing +// a storage.Interface from given parameters. +type StorageDecorator func( + storageInterface storage.Interface, + capacity int, + objectType runtime.Object, + resourcePrefix string, + scopeStrategy rest.NamespaceScopedStrategy, + newListFunc func() runtime.Object) storage.Interface + +// Returns given 'storageInterface' without any decoration. +func UndecoratedStorage( + storageInterface storage.Interface, + capacity int, + objectType runtime.Object, + resourcePrefix string, + scopeStrategy rest.NamespaceScopedStrategy, + newListFunc func() runtime.Object) storage.Interface { + return storageInterface +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/doc.go new file mode 100644 index 000000000..a628ee1b9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 horizontalpodautoscaler diff --git a/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/etcd/etcd.go new file mode 100644 index 000000000..143d47f9f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/etcd/etcd.go @@ -0,0 +1,94 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler" + "k8s.io/kubernetes/pkg/runtime" +) + +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against horizontal pod autoscalers. +func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { + prefix := "/horizontalpodautoscalers" + + newListFunc := func() runtime.Object { return &extensions.HorizontalPodAutoscalerList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.HorizontalPodAutoscalers), &extensions.HorizontalPodAutoscaler{}, prefix, horizontalpodautoscaler.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &extensions.HorizontalPodAutoscaler{} }, + // NewListFunc returns an object capable of storing results of an etcd list. + NewListFunc: newListFunc, + // Produces a path that etcd understands, to the root of the resource + // by combining the namespace in the context with the given prefix + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + // Produces a path that etcd understands, to the resource by combining + // the namespace in the context with the given prefix + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + // Retrieve the name field of an autoscaler + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*extensions.HorizontalPodAutoscaler).Name, nil + }, + // Used to match objects based on labels/fields for list + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return horizontalpodautoscaler.MatchAutoscaler(label, field) + }, + QualifiedResource: extensions.Resource("horizontalpodautoscalers"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + // Used to validate autoscaler creation + CreateStrategy: horizontalpodautoscaler.Strategy, + + // Used to validate autoscaler updates + UpdateStrategy: horizontalpodautoscaler.Strategy, + DeleteStrategy: horizontalpodautoscaler.Strategy, + + Storage: storageInterface, + } + statusStore := *store + statusStore.UpdateStrategy = horizontalpodautoscaler.StatusStrategy + return &REST{store}, &StatusREST{store: &statusStore} +} + +// StatusREST implements the REST endpoint for changing the status of a daemonset +type StatusREST struct { + store *etcdgeneric.Etcd +} + +func (r *StatusREST) New() runtime.Object { + return &extensions.HorizontalPodAutoscaler{} +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/etcd/etcd_test.go new file mode 100644 index 000000000..0f6b20371 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/etcd/etcd_test.go @@ -0,0 +1,132 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + // Ensure that extensions/v1beta1 package is initialized. + _ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, extensions.GroupName) + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + horizontalPodAutoscalerStorage, statusStorage := NewREST(restOptions) + return horizontalPodAutoscalerStorage, statusStorage, server +} + +func validNewHorizontalPodAutoscaler(name string) *extensions.HorizontalPodAutoscaler { + return &extensions.HorizontalPodAutoscaler{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: api.NamespaceDefault, + }, + Spec: extensions.HorizontalPodAutoscalerSpec{ + ScaleRef: extensions.SubresourceReference{ + Kind: "ReplicationController", + Name: "myrc", + Subresource: "scale", + }, + MaxReplicas: 5, + CPUUtilization: &extensions.CPUTargetUtilization{TargetPercentage: 70}, + }, + } +} + +func TestCreate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + autoscaler := validNewHorizontalPodAutoscaler("foo") + autoscaler.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + autoscaler, + // invalid + &extensions.HorizontalPodAutoscaler{}, + ) +} + +func TestUpdate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestUpdate( + // valid + validNewHorizontalPodAutoscaler("foo"), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.HorizontalPodAutoscaler) + object.Spec.MaxReplicas = object.Spec.MaxReplicas + 1 + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestDelete(validNewHorizontalPodAutoscaler("foo")) +} + +func TestGet(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(validNewHorizontalPodAutoscaler("foo")) +} + +func TestList(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(validNewHorizontalPodAutoscaler("foo")) +} + +func TestWatch(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validNewHorizontalPodAutoscaler("foo"), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{}, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + {"name": "foo"}, + }, + ) +} + +// TODO TestUpdateStatus diff --git a/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/strategy.go new file mode 100644 index 000000000..623cf2224 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/strategy.go @@ -0,0 +1,120 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 horizontalpodautoscaler + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/extensions/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// autoscalerStrategy implements behavior for HorizontalPodAutoscalers +type autoscalerStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating HorizontalPodAutoscaler +// objects via the REST API. +var Strategy = autoscalerStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped is true for autoscaler. +func (autoscalerStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears fields that are not allowed to be set by end users on creation. +func (autoscalerStrategy) PrepareForCreate(obj runtime.Object) { + newHPA := obj.(*extensions.HorizontalPodAutoscaler) + + // create cannot set status + newHPA.Status = extensions.HorizontalPodAutoscalerStatus{} +} + +// Validate validates a new autoscaler. +func (autoscalerStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + autoscaler := obj.(*extensions.HorizontalPodAutoscaler) + return validation.ValidateHorizontalPodAutoscaler(autoscaler) +} + +// Canonicalize normalizes the object after validation. +func (autoscalerStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is false for autoscalers. +func (autoscalerStrategy) AllowCreateOnUpdate() bool { + return false +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (autoscalerStrategy) PrepareForUpdate(obj, old runtime.Object) { + newHPA := obj.(*extensions.HorizontalPodAutoscaler) + oldHPA := obj.(*extensions.HorizontalPodAutoscaler) + // Update is not allowed to set status + newHPA.Status = oldHPA.Status +} + +// ValidateUpdate is the default update validation for an end user. +func (autoscalerStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateHorizontalPodAutoscalerUpdate(obj.(*extensions.HorizontalPodAutoscaler), old.(*extensions.HorizontalPodAutoscaler)) +} + +func (autoscalerStrategy) AllowUnconditionalUpdate() bool { + return true +} + +func AutoscalerToSelectableFields(limitRange *extensions.HorizontalPodAutoscaler) fields.Set { + return fields.Set{} +} + +func MatchAutoscaler(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + hpa, ok := obj.(*extensions.HorizontalPodAutoscaler) + if !ok { + return nil, nil, fmt.Errorf("given object is not a horizontal pod autoscaler.") + } + return labels.Set(hpa.ObjectMeta.Labels), AutoscalerToSelectableFields(hpa), nil + }, + } +} + +type autoscalerStatusStrategy struct { + autoscalerStrategy +} + +var StatusStrategy = autoscalerStatusStrategy{Strategy} + +func (autoscalerStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newAutoscaler := obj.(*extensions.HorizontalPodAutoscaler) + oldAutoscaler := old.(*extensions.HorizontalPodAutoscaler) + // status changes are not allowed to update spec + newAutoscaler.Spec = oldAutoscaler.Spec +} + +func (autoscalerStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateHorizontalPodAutoscalerStatusUpdate(obj.(*extensions.HorizontalPodAutoscaler), old.(*extensions.HorizontalPodAutoscaler)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/strategy_test.go new file mode 100644 index 000000000..992238bae --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/horizontalpodautoscaler/strategy_test.go @@ -0,0 +1,36 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 horizontalpodautoscaler + +import ( + "testing" + + _ "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/labels" +) + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Extensions.GroupVersion().String(), + "Autoscaler", + labels.Set(AutoscalerToSelectableFields(&extensions.HorizontalPodAutoscaler{})), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/ingress/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/ingress/doc.go new file mode 100644 index 000000000..5a6272cc5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/ingress/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 ingress diff --git a/vendor/k8s.io/kubernetes/pkg/registry/ingress/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/ingress/etcd/etcd.go new file mode 100644 index 000000000..21cb89808 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/ingress/etcd/etcd.go @@ -0,0 +1,96 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + ingress "k8s.io/kubernetes/pkg/registry/ingress" + "k8s.io/kubernetes/pkg/runtime" +) + +// rest implements a RESTStorage for replication controllers against etcd +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against replication controllers. +func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { + prefix := "/ingress" + + newListFunc := func() runtime.Object { return &extensions.IngressList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.Ingress), &extensions.Ingress{}, prefix, ingress.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &extensions.Ingress{} }, + + // NewListFunc returns an object capable of storing results of an etcd list. + NewListFunc: newListFunc, + // Produces a ingress that etcd understands, to the root of the resource + // by combining the namespace in the context with the given prefix + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + // Produces a ingress that etcd understands, to the resource by combining + // the namespace in the context with the given prefix + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + // Retrieve the name field of a replication controller + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*extensions.Ingress).Name, nil + }, + // Used to match objects based on labels/fields for list and watch + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return ingress.MatchIngress(label, field) + }, + QualifiedResource: extensions.Resource("ingresses"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + // Used to validate controller creation + CreateStrategy: ingress.Strategy, + + // Used to validate controller updates + UpdateStrategy: ingress.Strategy, + DeleteStrategy: ingress.Strategy, + + Storage: storageInterface, + } + statusStore := *store + statusStore.UpdateStrategy = ingress.StatusStrategy + return &REST{store}, &StatusREST{store: &statusStore} +} + +// StatusREST implements the REST endpoint for changing the status of an ingress +type StatusREST struct { + store *etcdgeneric.Etcd +} + +func (r *StatusREST) New() runtime.Object { + return &extensions.Ingress{} +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/ingress/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/ingress/etcd/etcd_test.go new file mode 100644 index 000000000..14b0ce705 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/ingress/etcd/etcd_test.go @@ -0,0 +1,215 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + "k8s.io/kubernetes/pkg/util/intstr" +) + +func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, extensions.GroupName) + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + ingressStorage, statusStorage := NewREST(restOptions) + return ingressStorage, statusStorage, server +} + +var ( + namespace = api.NamespaceNone + name = "foo-ingress" + defaultHostname = "foo.bar.com" + defaultBackendName = "default-backend" + defaultBackendPort = intstr.FromInt(80) + defaultLoadBalancer = "127.0.0.1" + defaultPath = "/foo" + defaultPathMap = map[string]string{defaultPath: defaultBackendName} + defaultTLS = []extensions.IngressTLS{ + {Hosts: []string{"foo.bar.com", "*.bar.com"}, SecretName: "fooSecret"}, + } +) + +type IngressRuleValues map[string]string + +func toHTTPIngressPaths(pathMap map[string]string) []extensions.HTTPIngressPath { + httpPaths := []extensions.HTTPIngressPath{} + for path, backend := range pathMap { + httpPaths = append(httpPaths, extensions.HTTPIngressPath{ + Path: path, + Backend: extensions.IngressBackend{ + ServiceName: backend, + ServicePort: defaultBackendPort, + }, + }) + } + return httpPaths +} + +func toIngressRules(hostRules map[string]IngressRuleValues) []extensions.IngressRule { + rules := []extensions.IngressRule{} + for host, pathMap := range hostRules { + rules = append(rules, extensions.IngressRule{ + Host: host, + IngressRuleValue: extensions.IngressRuleValue{ + HTTP: &extensions.HTTPIngressRuleValue{ + Paths: toHTTPIngressPaths(pathMap), + }, + }, + }) + } + return rules +} + +func newIngress(pathMap map[string]string) *extensions.Ingress { + return &extensions.Ingress{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: extensions.IngressSpec{ + Backend: &extensions.IngressBackend{ + ServiceName: defaultBackendName, + ServicePort: defaultBackendPort, + }, + Rules: toIngressRules(map[string]IngressRuleValues{ + defaultHostname: pathMap, + }), + TLS: defaultTLS, + }, + Status: extensions.IngressStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{ + {IP: defaultLoadBalancer}, + }, + }, + }, + } +} + +func validIngress() *extensions.Ingress { + return newIngress(defaultPathMap) +} + +func TestCreate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + ingress := validIngress() + noDefaultBackendAndRules := validIngress() + noDefaultBackendAndRules.Spec.Backend = &extensions.IngressBackend{} + noDefaultBackendAndRules.Spec.Rules = []extensions.IngressRule{} + badPath := validIngress() + badPath.Spec.Rules = toIngressRules(map[string]IngressRuleValues{ + "foo.bar.com": {"/invalid[": "svc"}}) + test.TestCreate( + // valid + ingress, + noDefaultBackendAndRules, + badPath, + ) +} + +func TestUpdate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestUpdate( + // valid + validIngress(), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.Ingress) + object.Spec.Rules = toIngressRules(map[string]IngressRuleValues{ + "bar.foo.com": {"/bar": defaultBackendName}, + }) + object.Spec.TLS = append(object.Spec.TLS, extensions.IngressTLS{ + Hosts: []string{"*.google.com"}, + SecretName: "googleSecret", + }) + return object + }, + // invalid updateFunc: ObjeceMeta is not to be tampered with. + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.Ingress) + object.Name = "" + return object + }, + + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.Ingress) + object.Spec.Rules = toIngressRules(map[string]IngressRuleValues{ + "foo.bar.com": {"/invalid[": "svc"}}) + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestDelete(validIngress()) +} + +func TestGet(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(validIngress()) +} + +func TestList(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(validIngress()) +} + +func TestWatch(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validIngress(), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"a": "c"}, + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": name}, + }, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + {"name": name}, + }, + ) +} + +// TODO TestUpdateStatus diff --git a/vendor/k8s.io/kubernetes/pkg/registry/ingress/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/ingress/strategy.go new file mode 100644 index 000000000..92a596a5e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/ingress/strategy.go @@ -0,0 +1,139 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 ingress + +import ( + "fmt" + "reflect" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/extensions/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// ingressStrategy implements verification logic for Replication Ingresss. +type ingressStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Replication Ingress objects. +var Strategy = ingressStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped returns true because all Ingress' need to be within a namespace. +func (ingressStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears the status of an Ingress before creation. +func (ingressStrategy) PrepareForCreate(obj runtime.Object) { + ingress := obj.(*extensions.Ingress) + // create cannot set status + ingress.Status = extensions.IngressStatus{} + + ingress.Generation = 1 +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (ingressStrategy) PrepareForUpdate(obj, old runtime.Object) { + newIngress := obj.(*extensions.Ingress) + oldIngress := old.(*extensions.Ingress) + // Update is not allowed to set status + newIngress.Status = oldIngress.Status + + // Any changes to the spec increment the generation number, any changes to the + // status should reflect the generation number of the corresponding object. + // See api.ObjectMeta description for more information on Generation. + if !reflect.DeepEqual(oldIngress.Spec, newIngress.Spec) { + newIngress.Generation = oldIngress.Generation + 1 + } + +} + +// Validate validates a new Ingress. +func (ingressStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + ingress := obj.(*extensions.Ingress) + err := validation.ValidateIngress(ingress) + return err +} + +// Canonicalize normalizes the object after validation. +func (ingressStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is false for Ingress; this means POST is needed to create one. +func (ingressStrategy) AllowCreateOnUpdate() bool { + return false +} + +// ValidateUpdate is the default update validation for an end user. +func (ingressStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + validationErrorList := validation.ValidateIngress(obj.(*extensions.Ingress)) + updateErrorList := validation.ValidateIngressUpdate(obj.(*extensions.Ingress), old.(*extensions.Ingress)) + return append(validationErrorList, updateErrorList...) +} + +// AllowUnconditionalUpdate is the default update policy for Ingress objects. +func (ingressStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// IngressToSelectableFields returns a field set that represents the object. +func IngressToSelectableFields(ingress *extensions.Ingress) fields.Set { + return generic.ObjectMetaFieldsSet(ingress.ObjectMeta, true) +} + +// MatchIngress is the filter used by the generic etcd backend to ingress +// watch events from etcd to clients of the apiserver only interested in specific +// labels/fields. +func MatchIngress(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + ingress, ok := obj.(*extensions.Ingress) + if !ok { + return nil, nil, fmt.Errorf("Given object is not an Ingress.") + } + return labels.Set(ingress.ObjectMeta.Labels), IngressToSelectableFields(ingress), nil + }, + } +} + +type ingressStatusStrategy struct { + ingressStrategy +} + +var StatusStrategy = ingressStatusStrategy{Strategy} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update of status +func (ingressStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newIngress := obj.(*extensions.Ingress) + oldIngress := old.(*extensions.Ingress) + // status changes are not allowed to update spec + newIngress.Spec = oldIngress.Spec +} + +// ValidateUpdate is the default update validation for an end user updating status +func (ingressStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateIngressStatusUpdate(obj.(*extensions.Ingress), old.(*extensions.Ingress)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/ingress/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/ingress/strategy_test.go new file mode 100644 index 000000000..88cbeb3e5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/ingress/strategy_test.go @@ -0,0 +1,142 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 ingress + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/util/intstr" +) + +func newIngress() extensions.Ingress { + defaultBackend := extensions.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + } + return extensions.Ingress{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: extensions.IngressSpec{ + Backend: &extensions.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + }, + Rules: []extensions.IngressRule{ + { + Host: "foo.bar.com", + IngressRuleValue: extensions.IngressRuleValue{ + HTTP: &extensions.HTTPIngressRuleValue{ + Paths: []extensions.HTTPIngressPath{ + { + Path: "/foo", + Backend: defaultBackend, + }, + }, + }, + }, + }, + }, + }, + Status: extensions.IngressStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{ + {IP: "127.0.0.1"}, + }, + }, + }, + } +} + +func TestIngressStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if !Strategy.NamespaceScoped() { + t.Errorf("Ingress must be namespace scoped") + } + if Strategy.AllowCreateOnUpdate() { + t.Errorf("Ingress should not allow create on update") + } + + ingress := newIngress() + Strategy.PrepareForCreate(&ingress) + if len(ingress.Status.LoadBalancer.Ingress) != 0 { + t.Error("Ingress should not allow setting status on create") + } + errs := Strategy.Validate(ctx, &ingress) + if len(errs) != 0 { + t.Errorf("Unexpected error validating %v", errs) + } + invalidIngress := newIngress() + invalidIngress.ResourceVersion = "4" + invalidIngress.Spec = extensions.IngressSpec{} + Strategy.PrepareForUpdate(&invalidIngress, &ingress) + errs = Strategy.ValidateUpdate(ctx, &invalidIngress, &ingress) + if len(errs) == 0 { + t.Errorf("Expected a validation error") + } + if invalidIngress.ResourceVersion != "4" { + t.Errorf("Incoming resource version on update should not be mutated") + } +} + +func TestIngressStatusStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if !StatusStrategy.NamespaceScoped() { + t.Errorf("Ingress must be namespace scoped") + } + if StatusStrategy.AllowCreateOnUpdate() { + t.Errorf("Ingress should not allow create on update") + } + oldIngress := newIngress() + newIngress := newIngress() + oldIngress.ResourceVersion = "4" + newIngress.ResourceVersion = "4" + newIngress.Spec.Backend.ServiceName = "ignore" + newIngress.Status = extensions.IngressStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{ + {IP: "127.0.0.2"}, + }, + }, + } + StatusStrategy.PrepareForUpdate(&newIngress, &oldIngress) + if newIngress.Status.LoadBalancer.Ingress[0].IP != "127.0.0.2" { + t.Errorf("Ingress status updates should allow change of status fields") + } + if newIngress.Spec.Backend.ServiceName != "default-backend" { + t.Errorf("PrepareForUpdate should have preserved old spec") + } + errs := StatusStrategy.ValidateUpdate(ctx, &newIngress, &oldIngress) + if len(errs) != 0 { + t.Errorf("Unexpected error %v", errs) + } +} + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Extensions.GroupVersion().String(), + "Ingress", + labels.Set(IngressToSelectableFields(&extensions.Ingress{})), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/job/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/job/doc.go new file mode 100644 index 000000000..d6351371c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/job/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 job provides Registry interface and it's RESTStorage +// implementation for storing Job api objects. +package job diff --git a/vendor/k8s.io/kubernetes/pkg/registry/job/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/job/etcd/etcd.go new file mode 100644 index 000000000..8f8f6a556 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/job/etcd/etcd.go @@ -0,0 +1,98 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/job" + "k8s.io/kubernetes/pkg/runtime" +) + +// REST implements a RESTStorage for jobs against etcd +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against Jobs. +func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { + prefix := "/jobs" + + newListFunc := func() runtime.Object { return &extensions.JobList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.Jobs), &extensions.Job{}, prefix, job.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &extensions.Job{} }, + + // NewListFunc returns an object capable of storing results of an etcd list. + NewListFunc: newListFunc, + // Produces a path that etcd understands, to the root of the resource + // by combining the namespace in the context with the given prefix + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + // Produces a path that etcd understands, to the resource by combining + // the namespace in the context with the given prefix + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + // Retrieve the name field of a job + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*extensions.Job).Name, nil + }, + // Used to match objects based on labels/fields for list and watch + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return job.MatchJob(label, field) + }, + QualifiedResource: extensions.Resource("jobs"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + // Used to validate job creation + CreateStrategy: job.Strategy, + + // Used to validate job updates + UpdateStrategy: job.Strategy, + DeleteStrategy: job.Strategy, + + Storage: storageInterface, + } + + statusStore := *store + statusStore.UpdateStrategy = job.StatusStrategy + + return &REST{store}, &StatusREST{store: &statusStore} +} + +// StatusREST implements the REST endpoint for changing the status of a resourcequota. +type StatusREST struct { + store *etcdgeneric.Etcd +} + +func (r *StatusREST) New() runtime.Object { + return &extensions.Job{} +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/job/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/job/etcd/etcd_test.go new file mode 100644 index 000000000..fae531dc5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/job/etcd/etcd_test.go @@ -0,0 +1,174 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + // Ensure that extensions/v1beta1 package is initialized. + _ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, extensions.GroupName) + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + jobStorage, statusStorage := NewREST(restOptions) + return jobStorage, statusStorage, server +} + +func validNewJob() *extensions.Job { + completions := 1 + parallelism := 1 + return &extensions.Job{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "default", + }, + Spec: extensions.JobSpec{ + Completions: &completions, + Parallelism: ¶llelism, + Selector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + ManualSelector: newBool(true), + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"a": "b"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "test", + Image: "test_image", + ImagePullPolicy: api.PullIfNotPresent, + }, + }, + RestartPolicy: api.RestartPolicyOnFailure, + DNSPolicy: api.DNSClusterFirst, + }, + }, + }, + } +} + +func TestCreate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + validJob := validNewJob() + validJob.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + validJob, + // invalid (empty selector) + &extensions.Job{ + Spec: extensions.JobSpec{ + Completions: validJob.Spec.Completions, + Selector: &unversioned.LabelSelector{}, + Template: validJob.Spec.Template, + }, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + two := 2 + test.TestUpdate( + // valid + validNewJob(), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.Job) + object.Spec.Parallelism = &two + return object + }, + // invalid updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.Job) + object.Spec.Selector = &unversioned.LabelSelector{} + return object + }, + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.Job) + object.Spec.Completions = &two + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestDelete(validNewJob()) +} + +func TestGet(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(validNewJob()) +} + +func TestList(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(validNewJob()) +} + +func TestWatch(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validNewJob(), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"x": "y"}, + }, + // matching fields + []fields.Set{}, + // not matching fields + []fields.Set{ + {"metadata.name": "xyz"}, + {"name": "foo"}, + }, + ) +} + +// TODO: test update /status + +func newBool(val bool) *bool { + p := new(bool) + *p = val + return p +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/job/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/job/strategy.go new file mode 100644 index 000000000..aef9eebf2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/job/strategy.go @@ -0,0 +1,181 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 job + +import ( + "fmt" + "strconv" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/extensions/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// jobStrategy implements verification logic for Replication Controllers. +type jobStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Replication Controller objects. +var Strategy = jobStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped returns true because all jobs need to be within a namespace. +func (jobStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears the status of a job before creation. +func (jobStrategy) PrepareForCreate(obj runtime.Object) { + job := obj.(*extensions.Job) + job.Status = extensions.JobStatus{} +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (jobStrategy) PrepareForUpdate(obj, old runtime.Object) { + newJob := obj.(*extensions.Job) + oldJob := old.(*extensions.Job) + newJob.Status = oldJob.Status +} + +// Validate validates a new job. +func (jobStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + job := obj.(*extensions.Job) + // TODO: move UID generation earlier and do this in defaulting logic? + if job.Spec.ManualSelector == nil || *job.Spec.ManualSelector == false { + generateSelector(job) + } + return validation.ValidateJob(job) +} + +// generateSelector adds a selector to a job and labels to its template +// which can be used to uniquely identify the pods created by that job, +// if the user has requested this behavior. +func generateSelector(obj *extensions.Job) { + if obj.Spec.Template.Labels == nil { + obj.Spec.Template.Labels = make(map[string]string) + } + // The job-name label is unique except in cases that are expected to be + // quite uncommon, and is more user friendly than uid. So, we add it as + // a label. + _, found := obj.Spec.Template.Labels["job-name"] + if found { + // User asked us to not automatically generate a selector and labels, + // but set a possibly conflicting value. If there is a conflict, + // we will reject in validation. + } else { + obj.Spec.Template.Labels["job-name"] = string(obj.ObjectMeta.Name) + } + // The controller-uid label makes the pods that belong to this job + // only match this job. + _, found = obj.Spec.Template.Labels["controller-uid"] + if found { + // User asked us to automatically generate a selector and labels, + // but set a possibly conflicting value. If there is a conflict, + // we will reject in validation. + } else { + obj.Spec.Template.Labels["controller-uid"] = string(obj.ObjectMeta.UID) + } + // Select the controller-uid label. This is sufficient for uniqueness. + if obj.Spec.Selector == nil { + obj.Spec.Selector = &unversioned.LabelSelector{} + } + if obj.Spec.Selector.MatchLabels == nil { + obj.Spec.Selector.MatchLabels = make(map[string]string) + } + if _, found := obj.Spec.Selector.MatchLabels["controller-uid"]; !found { + obj.Spec.Selector.MatchLabels["controller-uid"] = string(obj.ObjectMeta.UID) + } + // If the user specified matchLabel controller-uid=$WRONGUID, then it should fail + // in validation, either because the selector does not match the pod template + // (controller-uid=$WRONGUID does not match controller-uid=$UID, which we applied + // above, or we will reject in validation because the template has the wrong + // labels. +} + +// TODO: generalize generateSelector so it can work for other controller +// objects such as ReplicaSet. Can use pkg/api/meta to generically get the +// UID, but need some way to generically access the selector and pod labels +// fields. + +// Canonicalize normalizes the object after validation. +func (jobStrategy) Canonicalize(obj runtime.Object) { +} + +func (jobStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// AllowCreateOnUpdate is false for jobs; this means a POST is needed to create one. +func (jobStrategy) AllowCreateOnUpdate() bool { + return false +} + +// ValidateUpdate is the default update validation for an end user. +func (jobStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + validationErrorList := validation.ValidateJob(obj.(*extensions.Job)) + updateErrorList := validation.ValidateJobUpdate(obj.(*extensions.Job), old.(*extensions.Job)) + return append(validationErrorList, updateErrorList...) +} + +type jobStatusStrategy struct { + jobStrategy +} + +var StatusStrategy = jobStatusStrategy{Strategy} + +func (jobStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newJob := obj.(*extensions.Job) + oldJob := old.(*extensions.Job) + newJob.Spec = oldJob.Spec +} + +func (jobStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateJobUpdateStatus(obj.(*extensions.Job), old.(*extensions.Job)) +} + +// JobSelectableFields returns a field set that represents the object for matching purposes. +func JobToSelectableFields(job *extensions.Job) fields.Set { + objectMetaFieldsSet := generic.ObjectMetaFieldsSet(job.ObjectMeta, true) + specificFieldsSet := fields.Set{ + "status.successful": strconv.Itoa(job.Status.Succeeded), + } + return generic.MergeFieldsSets(objectMetaFieldsSet, specificFieldsSet) +} + +// MatchJob is the filter used by the generic etcd backend to route +// watch events from etcd to clients of the apiserver only interested in specific +// labels/fields. +func MatchJob(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + job, ok := obj.(*extensions.Job) + if !ok { + return nil, nil, fmt.Errorf("Given object is not a job.") + } + return labels.Set(job.ObjectMeta.Labels), JobToSelectableFields(job), nil + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/job/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/job/strategy_test.go new file mode 100644 index 000000000..13eb6c582 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/job/strategy_test.go @@ -0,0 +1,232 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 job + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/types" +) + +func newBool(a bool) *bool { + r := new(bool) + *r = a + return r +} + +func TestJobStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if !Strategy.NamespaceScoped() { + t.Errorf("Job must be namespace scoped") + } + if Strategy.AllowCreateOnUpdate() { + t.Errorf("Job should not allow create on update") + } + + validSelector := &unversioned.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + } + validPodTemplateSpec := api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validSelector.MatchLabels, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyOnFailure, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + } + job := &extensions.Job{ + ObjectMeta: api.ObjectMeta{ + Name: "myjob", + Namespace: api.NamespaceDefault, + }, + Spec: extensions.JobSpec{ + Selector: validSelector, + Template: validPodTemplateSpec, + ManualSelector: newBool(true), + }, + Status: extensions.JobStatus{ + Active: 11, + }, + } + + Strategy.PrepareForCreate(job) + if job.Status.Active != 0 { + t.Errorf("Job does not allow setting status on create") + } + errs := Strategy.Validate(ctx, job) + if len(errs) != 0 { + t.Errorf("Unexpected error validating %v", errs) + } + parallelism := 10 + updatedJob := &extensions.Job{ + ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "4"}, + Spec: extensions.JobSpec{ + Parallelism: ¶llelism, + }, + Status: extensions.JobStatus{ + Active: 11, + }, + } + // ensure we do not change status + job.Status.Active = 10 + Strategy.PrepareForUpdate(updatedJob, job) + if updatedJob.Status.Active != 10 { + t.Errorf("PrepareForUpdate should have preserved prior version status") + } + errs = Strategy.ValidateUpdate(ctx, updatedJob, job) + if len(errs) == 0 { + t.Errorf("Expected a validation error") + } +} + +func TestJobStrategyWithGeneration(t *testing.T) { + ctx := api.NewDefaultContext() + + theUID := types.UID("1a2b3c4d5e6f7g8h9i0k") + + validPodTemplateSpec := api.PodTemplateSpec{ + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyOnFailure, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + } + job := &extensions.Job{ + ObjectMeta: api.ObjectMeta{ + Name: "myjob2", + Namespace: api.NamespaceDefault, + UID: theUID, + }, + Spec: extensions.JobSpec{ + Selector: nil, + Template: validPodTemplateSpec, + }, + } + + Strategy.PrepareForCreate(job) + errs := Strategy.Validate(ctx, job) + if len(errs) != 0 { + t.Errorf("Unexpected error validating %v", errs) + } + + // Validate the stuff that validation should have validated. + if job.Spec.Selector == nil { + t.Errorf("Selector not generated") + } + expectedLabels := make(map[string]string) + expectedLabels["controller-uid"] = string(theUID) + if !reflect.DeepEqual(job.Spec.Selector.MatchLabels, expectedLabels) { + t.Errorf("Expected label selector not generated") + } + if job.Spec.Template.ObjectMeta.Labels == nil { + t.Errorf("Expected template labels not generated") + } + if v, ok := job.Spec.Template.ObjectMeta.Labels["job-name"]; !ok || v != "myjob2" { + t.Errorf("Expected template labels not present") + } + if v, ok := job.Spec.Template.ObjectMeta.Labels["controller-uid"]; !ok || v != string(theUID) { + t.Errorf("Expected template labels not present: ok: %v, v: %v", ok, v) + } +} + +func TestJobStatusStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if !StatusStrategy.NamespaceScoped() { + t.Errorf("Job must be namespace scoped") + } + if StatusStrategy.AllowCreateOnUpdate() { + t.Errorf("Job should not allow create on update") + } + validSelector := &unversioned.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + } + validPodTemplateSpec := api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validSelector.MatchLabels, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyOnFailure, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + } + oldParallelism := 10 + newParallelism := 11 + oldJob := &extensions.Job{ + ObjectMeta: api.ObjectMeta{ + Name: "myjob", + Namespace: api.NamespaceDefault, + ResourceVersion: "10", + }, + Spec: extensions.JobSpec{ + Selector: validSelector, + Template: validPodTemplateSpec, + Parallelism: &oldParallelism, + }, + Status: extensions.JobStatus{ + Active: 11, + }, + } + newJob := &extensions.Job{ + ObjectMeta: api.ObjectMeta{ + Name: "myjob", + Namespace: api.NamespaceDefault, + ResourceVersion: "9", + }, + Spec: extensions.JobSpec{ + Selector: validSelector, + Template: validPodTemplateSpec, + Parallelism: &newParallelism, + }, + Status: extensions.JobStatus{ + Active: 12, + }, + } + + StatusStrategy.PrepareForUpdate(newJob, oldJob) + if newJob.Status.Active != 12 { + t.Errorf("Job status updates must allow changes to job status") + } + if *newJob.Spec.Parallelism != 10 { + t.Errorf("Job status updates must now allow changes to job spec") + } + errs := StatusStrategy.ValidateUpdate(ctx, newJob, oldJob) + if len(errs) != 0 { + t.Errorf("Unexpected error %v", errs) + } + if newJob.ResourceVersion != "9" { + t.Errorf("Incoming resource version on update should not be mutated") + } +} + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Extensions.GroupVersion().String(), + "Job", + labels.Set(JobToSelectableFields(&extensions.Job{})), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/limitrange/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/limitrange/doc.go new file mode 100644 index 000000000..6c4214a04 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/limitrange/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 limitrange provides Registry interface and it's REST +// implementation for storing LimitRange api objects. +package limitrange diff --git a/vendor/k8s.io/kubernetes/pkg/registry/limitrange/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/limitrange/etcd/etcd.go new file mode 100644 index 000000000..1fb7ecbe9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/limitrange/etcd/etcd.go @@ -0,0 +1,68 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/limitrange" + "k8s.io/kubernetes/pkg/runtime" +) + +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against horizontal pod autoscalers. +func NewREST(opts generic.RESTOptions) *REST { + prefix := "/limitranges" + + newListFunc := func() runtime.Object { return &api.LimitRangeList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.LimitRanges), &api.LimitRange{}, prefix, limitrange.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.LimitRange{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, id string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, id) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.LimitRange).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return limitrange.MatchLimitRange(label, field) + }, + QualifiedResource: api.Resource("limitranges"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: limitrange.Strategy, + UpdateStrategy: limitrange.Strategy, + DeleteStrategy: limitrange.Strategy, + ExportStrategy: limitrange.Strategy, + + Storage: storageInterface, + } + return &REST{store} +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/limitrange/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/limitrange/etcd/etcd_test.go new file mode 100644 index 000000000..95c06b137 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/limitrange/etcd/etcd_test.go @@ -0,0 +1,147 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + return NewREST(restOptions), server +} + +func validNewLimitRange() *api.LimitRange { + return &api.LimitRange{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: api.LimitRangeSpec{ + Limits: []api.LimitRangeItem{ + { + Type: api.LimitTypePod, + Max: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100"), + api.ResourceMemory: resource.MustParse("10000"), + }, + Min: api.ResourceList{ + api.ResourceCPU: resource.MustParse("0"), + api.ResourceMemory: resource.MustParse("100"), + }, + }, + }, + }, + } +} + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).GeneratesName() + validLimitRange := validNewLimitRange() + validLimitRange.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + validLimitRange, + // invalid + &api.LimitRange{ + ObjectMeta: api.ObjectMeta{Name: "_-a123-a_"}, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate() + test.TestUpdate( + // valid + validNewLimitRange(), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*api.LimitRange) + object.Spec.Limits = []api.LimitRangeItem{ + { + Type: api.LimitTypePod, + Max: api.ResourceList{ + api.ResourceCPU: resource.MustParse("1000"), + api.ResourceMemory: resource.MustParse("100000"), + }, + Min: api.ResourceList{ + api.ResourceCPU: resource.MustParse("10"), + api.ResourceMemory: resource.MustParse("1000"), + }, + }, + } + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestDelete(validNewLimitRange()) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(validNewLimitRange()) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(validNewLimitRange()) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validNewLimitRange(), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{}, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + {"name": "foo"}, + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/limitrange/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/limitrange/strategy.go new file mode 100644 index 000000000..ff36fe68d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/limitrange/strategy.go @@ -0,0 +1,100 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 limitrange + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +type limitrangeStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating +// LimitRange objects via the REST API. +var Strategy = limitrangeStrategy{api.Scheme, api.SimpleNameGenerator} + +func (limitrangeStrategy) NamespaceScoped() bool { + return true +} + +func (limitrangeStrategy) PrepareForCreate(obj runtime.Object) { + limitRange := obj.(*api.LimitRange) + if len(limitRange.Name) == 0 { + limitRange.Name = string(util.NewUUID()) + } +} + +func (limitrangeStrategy) PrepareForUpdate(obj, old runtime.Object) { +} + +func (limitrangeStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + limitRange := obj.(*api.LimitRange) + return validation.ValidateLimitRange(limitRange) +} + +// Canonicalize normalizes the object after validation. +func (limitrangeStrategy) Canonicalize(obj runtime.Object) { +} + +func (limitrangeStrategy) AllowCreateOnUpdate() bool { + return true +} + +func (limitrangeStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + limitRange := obj.(*api.LimitRange) + return validation.ValidateLimitRange(limitRange) +} + +func (limitrangeStrategy) AllowUnconditionalUpdate() bool { + return true +} + +func LimitRangeToSelectableFields(limitRange *api.LimitRange) fields.Set { + return fields.Set{} +} + +func (limitrangeStrategy) Export(runtime.Object, bool) error { + // Copied from OpenShift exporter + // TODO: this needs to be fixed + // limitrange.Strategy.PrepareForCreate(obj) + return nil +} + +func MatchLimitRange(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + lr, ok := obj.(*api.LimitRange) + if !ok { + return nil, nil, fmt.Errorf("given object is not a limit range.") + } + return labels.Set(lr.ObjectMeta.Labels), LimitRangeToSelectableFields(lr), nil + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/limitrange/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/limitrange/strategy_test.go new file mode 100644 index 000000000..38ceb8c23 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/limitrange/strategy_test.go @@ -0,0 +1,35 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 limitrange + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/labels" +) + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "LimitRange", + labels.Set(LimitRangeToSelectableFields(&api.LimitRange{})), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/namespace/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/namespace/doc.go new file mode 100644 index 000000000..206290037 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/namespace/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 namespace provides Registry interface and it's REST +// implementation for storing Namespace api objects. +package namespace diff --git a/vendor/k8s.io/kubernetes/pkg/registry/namespace/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/namespace/etcd/etcd.go new file mode 100644 index 000000000..8955cbbdd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/namespace/etcd/etcd.go @@ -0,0 +1,135 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + apierrors "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/namespace" + "k8s.io/kubernetes/pkg/runtime" +) + +// rest implements a RESTStorage for namespaces against etcd +type REST struct { + *etcdgeneric.Etcd + status *etcdgeneric.Etcd +} + +// StatusREST implements the REST endpoint for changing the status of a namespace. +type StatusREST struct { + store *etcdgeneric.Etcd +} + +// FinalizeREST implements the REST endpoint for finalizing a namespace. +type FinalizeREST struct { + store *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against namespaces. +func NewREST(opts generic.RESTOptions) (*REST, *StatusREST, *FinalizeREST) { + prefix := "/namespaces" + + newListFunc := func() runtime.Object { return &api.NamespaceList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.Namespaces), &api.Namespace{}, prefix, namespace.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.Namespace{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return prefix + }, + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NoNamespaceKeyFunc(ctx, prefix, name) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.Namespace).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return namespace.MatchNamespace(label, field) + }, + QualifiedResource: api.Resource("namespaces"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: namespace.Strategy, + UpdateStrategy: namespace.Strategy, + DeleteStrategy: namespace.Strategy, + ReturnDeletedObject: true, + + Storage: storageInterface, + } + + statusStore := *store + statusStore.UpdateStrategy = namespace.StatusStrategy + + finalizeStore := *store + finalizeStore.UpdateStrategy = namespace.FinalizeStrategy + + return &REST{Etcd: store, status: &statusStore}, &StatusREST{store: &statusStore}, &FinalizeREST{store: &finalizeStore} +} + +// Delete enforces life-cycle rules for namespace termination +func (r *REST) Delete(ctx api.Context, name string, options *api.DeleteOptions) (runtime.Object, error) { + nsObj, err := r.Get(ctx, name) + if err != nil { + return nil, err + } + + namespace := nsObj.(*api.Namespace) + + // upon first request to delete, we switch the phase to start namespace termination + if namespace.DeletionTimestamp.IsZero() { + now := unversioned.Now() + namespace.DeletionTimestamp = &now + namespace.Status.Phase = api.NamespaceTerminating + result, _, err := r.status.Update(ctx, namespace) + return result, err + } + + // prior to final deletion, we must ensure that finalizers is empty + if len(namespace.Spec.Finalizers) != 0 { + err = apierrors.NewConflict(api.Resource("namespaces"), namespace.Name, fmt.Errorf("The system is ensuring all content is removed from this namespace. Upon completion, this namespace will automatically be purged by the system.")) + return nil, err + } + return r.Etcd.Delete(ctx, name, nil) +} + +func (r *StatusREST) New() runtime.Object { + return r.store.New() +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} + +func (r *FinalizeREST) New() runtime.Object { + return r.store.New() +} + +// Update alters the status finalizers subset of an object. +func (r *FinalizeREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/namespace/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/namespace/etcd/etcd_test.go new file mode 100644 index 000000000..862697f48 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/namespace/etcd/etcd_test.go @@ -0,0 +1,180 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/storage/etcd/etcdtest" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + namespaceStorage, _, _ := NewREST(restOptions) + return namespaceStorage, server +} + +func validNewNamespace() *api.Namespace { + return &api.Namespace{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + }, + } +} + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + namespace := validNewNamespace() + namespace.ObjectMeta = api.ObjectMeta{GenerateName: "foo"} + test.TestCreate( + // valid + namespace, + // invalid + &api.Namespace{ + ObjectMeta: api.ObjectMeta{Name: "bad value"}, + }, + ) +} + +func TestCreateSetsFields(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + namespace := validNewNamespace() + ctx := api.NewContext() + _, err := storage.Create(ctx, namespace) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + object, err := storage.Get(ctx, "foo") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + actual := object.(*api.Namespace) + if actual.Name != namespace.Name { + t.Errorf("unexpected namespace: %#v", actual) + } + if len(actual.UID) == 0 { + t.Errorf("expected namespace UID to be set: %#v", actual) + } + if actual.Status.Phase != api.NamespaceActive { + t.Errorf("expected namespace phase to be set to active, but %v", actual.Status.Phase) + } +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope().ReturnDeletedObject() + test.TestDelete(validNewNamespace()) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestGet(validNewNamespace()) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestList(validNewNamespace()) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestWatch( + validNewNamespace(), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": "foo"}, + {"name": "foo"}, + }, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + }, + ) +} + +func TestDeleteNamespaceWithIncompleteFinalizers(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + key := etcdtest.AddPrefix("namespaces/foo") + ctx := api.NewContext() + now := unversioned.Now() + namespace := &api.Namespace{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + DeletionTimestamp: &now, + }, + Spec: api.NamespaceSpec{ + Finalizers: []api.FinalizerName{api.FinalizerKubernetes}, + }, + Status: api.NamespaceStatus{Phase: api.NamespaceActive}, + } + if err := storage.Storage.Set(ctx, key, namespace, nil, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if _, err := storage.Delete(ctx, "foo", nil); err == nil { + t.Errorf("unexpected error: %v", err) + } +} + +func TestDeleteNamespaceWithCompleteFinalizers(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + key := etcdtest.AddPrefix("namespaces/foo") + ctx := api.NewContext() + now := unversioned.Now() + namespace := &api.Namespace{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + DeletionTimestamp: &now, + }, + Spec: api.NamespaceSpec{ + Finalizers: []api.FinalizerName{}, + }, + Status: api.NamespaceStatus{Phase: api.NamespaceActive}, + } + if err := storage.Storage.Set(ctx, key, namespace, nil, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if _, err := storage.Delete(ctx, "foo", nil); err != nil { + t.Errorf("unexpected error: %v", err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/namespace/registry.go b/vendor/k8s.io/kubernetes/pkg/registry/namespace/registry.go new file mode 100644 index 000000000..33b50d32e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/namespace/registry.go @@ -0,0 +1,79 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 namespace + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface implemented by things that know how to store Namespace objects. +type Registry interface { + ListNamespaces(ctx api.Context, options *api.ListOptions) (*api.NamespaceList, error) + WatchNamespaces(ctx api.Context, options *api.ListOptions) (watch.Interface, error) + GetNamespace(ctx api.Context, namespaceID string) (*api.Namespace, error) + CreateNamespace(ctx api.Context, namespace *api.Namespace) error + UpdateNamespace(ctx api.Context, namespace *api.Namespace) error + DeleteNamespace(ctx api.Context, namespaceID string) error +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListNamespaces(ctx api.Context, options *api.ListOptions) (*api.NamespaceList, error) { + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + return obj.(*api.NamespaceList), nil +} + +func (s *storage) WatchNamespaces(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetNamespace(ctx api.Context, namespaceName string) (*api.Namespace, error) { + obj, err := s.Get(ctx, namespaceName) + if err != nil { + return nil, err + } + return obj.(*api.Namespace), nil +} + +func (s *storage) CreateNamespace(ctx api.Context, namespace *api.Namespace) error { + _, err := s.Create(ctx, namespace) + return err +} + +func (s *storage) UpdateNamespace(ctx api.Context, namespace *api.Namespace) error { + _, _, err := s.Update(ctx, namespace) + return err +} + +func (s *storage) DeleteNamespace(ctx api.Context, namespaceID string) error { + _, err := s.Delete(ctx, namespaceID, nil) + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/namespace/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/namespace/strategy.go new file mode 100644 index 000000000..3b069847c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/namespace/strategy.go @@ -0,0 +1,158 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 namespace + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// namespaceStrategy implements behavior for Namespaces +type namespaceStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Namespace +// objects via the REST API. +var Strategy = namespaceStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped is false for namespaces. +func (namespaceStrategy) NamespaceScoped() bool { + return false +} + +// PrepareForCreate clears fields that are not allowed to be set by end users on creation. +func (namespaceStrategy) PrepareForCreate(obj runtime.Object) { + // on create, status is active + namespace := obj.(*api.Namespace) + namespace.Status = api.NamespaceStatus{ + Phase: api.NamespaceActive, + } + // on create, we require the kubernetes value + // we cannot use this in defaults conversion because we let it get removed over life of object + hasKubeFinalizer := false + for i := range namespace.Spec.Finalizers { + if namespace.Spec.Finalizers[i] == api.FinalizerKubernetes { + hasKubeFinalizer = true + break + } + } + if !hasKubeFinalizer { + if len(namespace.Spec.Finalizers) == 0 { + namespace.Spec.Finalizers = []api.FinalizerName{api.FinalizerKubernetes} + } else { + namespace.Spec.Finalizers = append(namespace.Spec.Finalizers, api.FinalizerKubernetes) + } + } +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (namespaceStrategy) PrepareForUpdate(obj, old runtime.Object) { + newNamespace := obj.(*api.Namespace) + oldNamespace := old.(*api.Namespace) + newNamespace.Spec.Finalizers = oldNamespace.Spec.Finalizers + newNamespace.Status = oldNamespace.Status +} + +// Validate validates a new namespace. +func (namespaceStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + namespace := obj.(*api.Namespace) + return validation.ValidateNamespace(namespace) +} + +// Canonicalize normalizes the object after validation. +func (namespaceStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is false for namespaces. +func (namespaceStrategy) AllowCreateOnUpdate() bool { + return false +} + +// ValidateUpdate is the default update validation for an end user. +func (namespaceStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + errorList := validation.ValidateNamespace(obj.(*api.Namespace)) + return append(errorList, validation.ValidateNamespaceUpdate(obj.(*api.Namespace), old.(*api.Namespace))...) +} + +func (namespaceStrategy) AllowUnconditionalUpdate() bool { + return true +} + +type namespaceStatusStrategy struct { + namespaceStrategy +} + +var StatusStrategy = namespaceStatusStrategy{Strategy} + +func (namespaceStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newNamespace := obj.(*api.Namespace) + oldNamespace := old.(*api.Namespace) + newNamespace.Spec = oldNamespace.Spec +} + +func (namespaceStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateNamespaceStatusUpdate(obj.(*api.Namespace), old.(*api.Namespace)) +} + +type namespaceFinalizeStrategy struct { + namespaceStrategy +} + +var FinalizeStrategy = namespaceFinalizeStrategy{Strategy} + +func (namespaceFinalizeStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateNamespaceFinalizeUpdate(obj.(*api.Namespace), old.(*api.Namespace)) +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (namespaceFinalizeStrategy) PrepareForUpdate(obj, old runtime.Object) { + newNamespace := obj.(*api.Namespace) + oldNamespace := old.(*api.Namespace) + newNamespace.Status = oldNamespace.Status +} + +// MatchNamespace returns a generic matcher for a given label and field selector. +func MatchNamespace(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + namespaceObj, ok := obj.(*api.Namespace) + if !ok { + return false, fmt.Errorf("not a namespace") + } + fields := NamespaceToSelectableFields(namespaceObj) + return label.Matches(labels.Set(namespaceObj.Labels)) && field.Matches(fields), nil + }) +} + +// NamespaceToSelectableFields returns a label set that represents the object +func NamespaceToSelectableFields(namespace *api.Namespace) labels.Set { + objectMetaFieldsSet := generic.ObjectMetaFieldsSet(namespace.ObjectMeta, false) + specificFieldsSet := fields.Set{ + "status.phase": string(namespace.Status.Phase), + // This is a bug, but we need to support it for backward compatibility. + "name": namespace.Name, + } + return labels.Set(generic.MergeFieldsSets(objectMetaFieldsSet, specificFieldsSet)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/namespace/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/namespace/strategy_test.go new file mode 100644 index 000000000..fb06a302f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/namespace/strategy_test.go @@ -0,0 +1,143 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 namespace + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/api/unversioned" +) + +func TestNamespaceStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if Strategy.NamespaceScoped() { + t.Errorf("Namespaces should not be namespace scoped") + } + if Strategy.AllowCreateOnUpdate() { + t.Errorf("Namespaces should not allow create on update") + } + namespace := &api.Namespace{ + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}, + Status: api.NamespaceStatus{Phase: api.NamespaceTerminating}, + } + Strategy.PrepareForCreate(namespace) + if namespace.Status.Phase != api.NamespaceActive { + t.Errorf("Namespaces do not allow setting phase on create") + } + if len(namespace.Spec.Finalizers) != 1 || namespace.Spec.Finalizers[0] != api.FinalizerKubernetes { + t.Errorf("Prepare For Create should have added kubernetes finalizer") + } + errs := Strategy.Validate(ctx, namespace) + if len(errs) != 0 { + t.Errorf("Unexpected error validating %v", errs) + } + invalidNamespace := &api.Namespace{ + ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "4"}, + } + // ensure we copy spec.finalizers from old to new + Strategy.PrepareForUpdate(invalidNamespace, namespace) + if len(invalidNamespace.Spec.Finalizers) != 1 || invalidNamespace.Spec.Finalizers[0] != api.FinalizerKubernetes { + t.Errorf("PrepareForUpdate should have preserved old.spec.finalizers") + } + errs = Strategy.ValidateUpdate(ctx, invalidNamespace, namespace) + if len(errs) == 0 { + t.Errorf("Expected a validation error") + } + if invalidNamespace.ResourceVersion != "4" { + t.Errorf("Incoming resource version on update should not be mutated") + } +} + +func TestNamespaceStatusStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if StatusStrategy.NamespaceScoped() { + t.Errorf("Namespaces should not be namespace scoped") + } + if StatusStrategy.AllowCreateOnUpdate() { + t.Errorf("Namespaces should not allow create on update") + } + now := unversioned.Now() + oldNamespace := &api.Namespace{ + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}, + Spec: api.NamespaceSpec{Finalizers: []api.FinalizerName{"kubernetes"}}, + Status: api.NamespaceStatus{Phase: api.NamespaceActive}, + } + namespace := &api.Namespace{ + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "9", DeletionTimestamp: &now}, + Status: api.NamespaceStatus{Phase: api.NamespaceTerminating}, + } + StatusStrategy.PrepareForUpdate(namespace, oldNamespace) + if namespace.Status.Phase != api.NamespaceTerminating { + t.Errorf("Namespace status updates should allow change of phase: %v", namespace.Status.Phase) + } + if len(namespace.Spec.Finalizers) != 1 || namespace.Spec.Finalizers[0] != api.FinalizerKubernetes { + t.Errorf("PrepareForUpdate should have preserved old finalizers") + } + errs := StatusStrategy.ValidateUpdate(ctx, namespace, oldNamespace) + if len(errs) != 0 { + t.Errorf("Unexpected error %v", errs) + } + if namespace.ResourceVersion != "9" { + t.Errorf("Incoming resource version on update should not be mutated") + } +} + +func TestNamespaceFinalizeStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if FinalizeStrategy.NamespaceScoped() { + t.Errorf("Namespaces should not be namespace scoped") + } + if FinalizeStrategy.AllowCreateOnUpdate() { + t.Errorf("Namespaces should not allow create on update") + } + oldNamespace := &api.Namespace{ + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}, + Spec: api.NamespaceSpec{Finalizers: []api.FinalizerName{"kubernetes", "example.com/org"}}, + Status: api.NamespaceStatus{Phase: api.NamespaceActive}, + } + namespace := &api.Namespace{ + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "9"}, + Spec: api.NamespaceSpec{Finalizers: []api.FinalizerName{"example.com/foo"}}, + Status: api.NamespaceStatus{Phase: api.NamespaceTerminating}, + } + FinalizeStrategy.PrepareForUpdate(namespace, oldNamespace) + if namespace.Status.Phase != api.NamespaceActive { + t.Errorf("finalize updates should not allow change of phase: %v", namespace.Status.Phase) + } + if len(namespace.Spec.Finalizers) != 1 || string(namespace.Spec.Finalizers[0]) != "example.com/foo" { + t.Errorf("PrepareForUpdate should have modified finalizers") + } + errs := StatusStrategy.ValidateUpdate(ctx, namespace, oldNamespace) + if len(errs) != 0 { + t.Errorf("Unexpected error %v", errs) + } + if namespace.ResourceVersion != "9" { + t.Errorf("Incoming resource version on update should not be mutated") + } +} + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "Namespace", + NamespaceToSelectableFields(&api.Namespace{}), + map[string]string{"name": "metadata.name"}, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/node/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/node/doc.go new file mode 100644 index 000000000..cd604b4ab --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/node/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 provides Registry interface and implementation for storing Nodes. +package node diff --git a/vendor/k8s.io/kubernetes/pkg/registry/node/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/node/etcd/etcd.go new file mode 100644 index 000000000..835fcc122 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/node/etcd/etcd.go @@ -0,0 +1,142 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "fmt" + "net/http" + "net/url" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/kubelet/client" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/node" + noderest "k8s.io/kubernetes/pkg/registry/node/rest" + "k8s.io/kubernetes/pkg/runtime" +) + +// NodeStorage includes storage for nodes and all sub resources +type NodeStorage struct { + Node *REST + Status *StatusREST + Proxy *noderest.ProxyREST +} + +type REST struct { + *etcdgeneric.Etcd + connection client.ConnectionInfoGetter + proxyTransport http.RoundTripper +} + +// StatusREST implements the REST endpoint for changing the status of a pod. +type StatusREST struct { + store *etcdgeneric.Etcd +} + +func (r *StatusREST) New() runtime.Object { + return &api.Node{} +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} + +// NewREST returns a RESTStorage object that will work against nodes. +func NewStorage(opts generic.RESTOptions, connection client.ConnectionInfoGetter, proxyTransport http.RoundTripper) NodeStorage { + prefix := "/minions" + + newListFunc := func() runtime.Object { return &api.NodeList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.Nodes), &api.Node{}, prefix, node.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.Node{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return prefix + }, + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NoNamespaceKeyFunc(ctx, prefix, name) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.Node).Name, nil + }, + PredicateFunc: node.MatchNode, + QualifiedResource: api.Resource("nodes"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: node.Strategy, + UpdateStrategy: node.Strategy, + DeleteStrategy: node.Strategy, + ExportStrategy: node.Strategy, + + Storage: storageInterface, + } + + statusStore := *store + statusStore.UpdateStrategy = node.StatusStrategy + + nodeREST := &REST{store, connection, proxyTransport} + + return NodeStorage{ + Node: nodeREST, + Status: &StatusREST{store: &statusStore}, + Proxy: &noderest.ProxyREST{Store: store, Connection: client.ConnectionInfoGetter(nodeREST), ProxyTransport: proxyTransport}, + } +} + +// Implement Redirector. +var _ = rest.Redirector(&REST{}) + +// ResourceLocation returns a URL to which one can send traffic for the specified node. +func (r *REST) ResourceLocation(ctx api.Context, id string) (*url.URL, http.RoundTripper, error) { + return node.ResourceLocation(r, r, r.proxyTransport, ctx, id) +} + +var _ = client.ConnectionInfoGetter(&REST{}) + +func (r *REST) getKubeletPort(ctx api.Context, nodeName string) (int, error) { + // We probably shouldn't care about context when looking for Node object. + obj, err := r.Get(ctx, nodeName) + if err != nil { + return 0, err + } + node, ok := obj.(*api.Node) + if !ok { + return 0, fmt.Errorf("Unexpected object type: %#v", node) + } + return node.Status.DaemonEndpoints.KubeletEndpoint.Port, nil +} + +func (c *REST) GetConnectionInfo(ctx api.Context, nodeName string) (string, uint, http.RoundTripper, error) { + scheme, port, transport, err := c.connection.GetConnectionInfo(ctx, nodeName) + if err != nil { + return "", 0, nil, err + } + daemonPort, err := c.getKubeletPort(ctx, nodeName) + if err != nil { + return "", 0, nil, err + } + if daemonPort > 0 { + return scheme, uint(daemonPort), transport, nil + } + return scheme, port, transport, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/node/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/node/etcd/etcd_test.go new file mode 100644 index 000000000..0f5ac76ec --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/node/etcd/etcd_test.go @@ -0,0 +1,144 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "net/http" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +type fakeConnectionInfoGetter struct { +} + +func (fakeConnectionInfoGetter) GetConnectionInfo(ctx api.Context, nodeName string) (string, uint, http.RoundTripper, error) { + return "http", 12345, nil, nil +} + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + storage := NewStorage(restOptions, fakeConnectionInfoGetter{}, nil) + return storage.Node, server +} + +func validNewNode() *api.Node { + return &api.Node{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + "name": "foo", + }, + }, + Spec: api.NodeSpec{ + ExternalID: "external", + }, + Status: api.NodeStatus{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceCPU): resource.MustParse("10"), + api.ResourceName(api.ResourceMemory): resource.MustParse("0"), + }, + }, + } +} + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + node := validNewNode() + node.ObjectMeta = api.ObjectMeta{GenerateName: "foo"} + test.TestCreate( + // valid + node, + // invalid + &api.Node{ + ObjectMeta: api.ObjectMeta{Name: "_-a123-a_"}, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestUpdate( + // valid + validNewNode(), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*api.Node) + object.Spec.Unschedulable = !object.Spec.Unschedulable + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestDelete(validNewNode()) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestGet(validNewNode()) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestList(validNewNode()) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestWatch( + validNewNode(), + // matching labels + []labels.Set{ + {"name": "foo"}, + }, + // not matching labels + []labels.Set{ + {"name": "bar"}, + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": "foo"}, + }, + // not matchin fields + []fields.Set{ + {"metadata.name": "bar"}, + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/node/registry.go b/vendor/k8s.io/kubernetes/pkg/registry/node/registry.go new file mode 100644 index 000000000..3d6f3bf07 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/node/registry.go @@ -0,0 +1,80 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface for things that know how to store node. +type Registry interface { + ListNodes(ctx api.Context, options *api.ListOptions) (*api.NodeList, error) + CreateNode(ctx api.Context, node *api.Node) error + UpdateNode(ctx api.Context, node *api.Node) error + GetNode(ctx api.Context, nodeID string) (*api.Node, error) + DeleteNode(ctx api.Context, nodeID string) error + WatchNodes(ctx api.Context, options *api.ListOptions) (watch.Interface, error) +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListNodes(ctx api.Context, options *api.ListOptions) (*api.NodeList, error) { + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + + return obj.(*api.NodeList), nil +} + +func (s *storage) CreateNode(ctx api.Context, node *api.Node) error { + _, err := s.Create(ctx, node) + return err +} + +func (s *storage) UpdateNode(ctx api.Context, node *api.Node) error { + _, _, err := s.Update(ctx, node) + return err +} + +func (s *storage) WatchNodes(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetNode(ctx api.Context, name string) (*api.Node, error) { + obj, err := s.Get(ctx, name) + if err != nil { + return nil, err + } + return obj.(*api.Node), nil +} + +func (s *storage) DeleteNode(ctx api.Context, name string) error { + _, err := s.Delete(ctx, name, nil) + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/node/rest/proxy.go b/vendor/k8s.io/kubernetes/pkg/registry/node/rest/proxy.go new file mode 100644 index 000000000..5b0f3d6e3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/node/rest/proxy.go @@ -0,0 +1,81 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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" + "net/http" + "net/url" + "path" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/capabilities" + "k8s.io/kubernetes/pkg/kubelet/client" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + genericrest "k8s.io/kubernetes/pkg/registry/generic/rest" + "k8s.io/kubernetes/pkg/registry/node" + "k8s.io/kubernetes/pkg/runtime" +) + +// ProxyREST implements the proxy subresource for a Node +type ProxyREST struct { + Store *etcdgeneric.Etcd + Connection client.ConnectionInfoGetter + ProxyTransport http.RoundTripper +} + +// Implement Connecter +var _ = rest.Connecter(&ProxyREST{}) + +var proxyMethods = []string{"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"} + +// New returns an empty service resource +func (r *ProxyREST) New() runtime.Object { + return &api.Node{} +} + +// ConnectMethods returns the list of HTTP methods that can be proxied +func (r *ProxyREST) ConnectMethods() []string { + return proxyMethods +} + +// NewConnectOptions returns versioned resource that represents proxy parameters +func (r *ProxyREST) NewConnectOptions() (runtime.Object, bool, string) { + return &api.NodeProxyOptions{}, true, "path" +} + +// Connect returns a handler for the node proxy +func (r *ProxyREST) Connect(ctx api.Context, id string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { + proxyOpts, ok := opts.(*api.NodeProxyOptions) + if !ok { + return nil, fmt.Errorf("Invalid options object: %#v", opts) + } + location, transport, err := node.ResourceLocation(r.Store, r.Connection, r.ProxyTransport, ctx, id) + if err != nil { + return nil, err + } + location.Path = path.Join(location.Path, proxyOpts.Path) + // Return a proxy handler that uses the desired transport, wrapped with additional proxy handling (to get URL rewriting, X-Forwarded-* headers, etc) + return newThrottledUpgradeAwareProxyHandler(location, transport, true, false, responder), nil +} + +func newThrottledUpgradeAwareProxyHandler(location *url.URL, transport http.RoundTripper, wrapTransport, upgradeRequired bool, responder rest.Responder) *genericrest.UpgradeAwareProxyHandler { + handler := genericrest.NewUpgradeAwareProxyHandler(location, transport, wrapTransport, upgradeRequired, responder) + handler.MaxBytesPerSec = capabilities.Get().PerConnectionBandwidthLimitBytesPerSec + return handler +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/node/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/node/strategy.go new file mode 100644 index 000000000..fc00988ba --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/node/strategy.go @@ -0,0 +1,205 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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" + "net" + "net/http" + "net/url" + "strconv" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/kubelet/client" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/master/ports" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + utilnet "k8s.io/kubernetes/pkg/util/net" + nodeutil "k8s.io/kubernetes/pkg/util/node" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// nodeStrategy implements behavior for nodes +type nodeStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Nodes is the default logic that applies when creating and updating Node +// objects. +var Strategy = nodeStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped is false for nodes. +func (nodeStrategy) NamespaceScoped() bool { + return false +} + +// AllowCreateOnUpdate is false for nodes. +func (nodeStrategy) AllowCreateOnUpdate() bool { + return false +} + +// PrepareForCreate clears fields that are not allowed to be set by end users on creation. +func (nodeStrategy) PrepareForCreate(obj runtime.Object) { + _ = obj.(*api.Node) + // Nodes allow *all* fields, including status, to be set on create. +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (nodeStrategy) PrepareForUpdate(obj, old runtime.Object) { + newNode := obj.(*api.Node) + oldNode := old.(*api.Node) + newNode.Status = oldNode.Status +} + +// Validate validates a new node. +func (nodeStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + node := obj.(*api.Node) + return validation.ValidateNode(node) +} + +// Canonicalize normalizes the object after validation. +func (nodeStrategy) Canonicalize(obj runtime.Object) { +} + +// ValidateUpdate is the default update validation for an end user. +func (nodeStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + errorList := validation.ValidateNode(obj.(*api.Node)) + return append(errorList, validation.ValidateNodeUpdate(obj.(*api.Node), old.(*api.Node))...) +} + +func (nodeStrategy) AllowUnconditionalUpdate() bool { + return true +} + +func (ns nodeStrategy) Export(obj runtime.Object, exact bool) error { + n, ok := obj.(*api.Node) + if !ok { + // unexpected programmer error + return fmt.Errorf("unexpected object: %v", obj) + } + ns.PrepareForCreate(obj) + if exact { + return nil + } + // Nodes are the only resources that allow direct status edits, therefore + // we clear that without exact so that the node value can be reused. + n.Status = api.NodeStatus{} + return nil +} + +type nodeStatusStrategy struct { + nodeStrategy +} + +var StatusStrategy = nodeStatusStrategy{Strategy} + +func (nodeStatusStrategy) PrepareForCreate(obj runtime.Object) { + _ = obj.(*api.Node) + // Nodes allow *all* fields, including status, to be set on create. +} + +func (nodeStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newNode := obj.(*api.Node) + oldNode := old.(*api.Node) + newNode.Spec = oldNode.Spec +} + +func (nodeStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateNodeUpdate(obj.(*api.Node), old.(*api.Node)) +} + +// Canonicalize normalizes the object after validation. +func (nodeStatusStrategy) Canonicalize(obj runtime.Object) { +} + +// ResourceGetter is an interface for retrieving resources by ResourceLocation. +type ResourceGetter interface { + Get(api.Context, string) (runtime.Object, error) +} + +// NodeToSelectableFields returns a field set that represents the object. +func NodeToSelectableFields(node *api.Node) fields.Set { + objectMetaFieldsSet := generic.ObjectMetaFieldsSet(node.ObjectMeta, false) + specificFieldsSet := fields.Set{ + "spec.unschedulable": fmt.Sprint(node.Spec.Unschedulable), + } + return generic.MergeFieldsSets(objectMetaFieldsSet, specificFieldsSet) +} + +// MatchNode returns a generic matcher for a given label and field selector. +func MatchNode(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + nodeObj, ok := obj.(*api.Node) + if !ok { + return nil, nil, fmt.Errorf("not a node") + } + return labels.Set(nodeObj.ObjectMeta.Labels), NodeToSelectableFields(nodeObj), nil + }, + } +} + +// ResourceLocation returns an URL and transport which one can use to send traffic for the specified node. +func ResourceLocation(getter ResourceGetter, connection client.ConnectionInfoGetter, proxyTransport http.RoundTripper, ctx api.Context, id string) (*url.URL, http.RoundTripper, error) { + schemeReq, name, portReq, valid := utilnet.SplitSchemeNamePort(id) + if !valid { + return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid node request %q", id)) + } + + nodeObj, err := getter.Get(ctx, name) + if err != nil { + return nil, nil, err + } + node := nodeObj.(*api.Node) + hostIP, err := nodeutil.GetNodeHostIP(node) + if err != nil { + return nil, nil, err + } + host := hostIP.String() + + // We check if we want to get a default Kubelet's transport. It happens if either: + // - no port is specified in request (Kubelet's port is default), + // - we're using Port stored as a DaemonEndpoint and requested port is a Kubelet's port stored in the DaemonEndpoint, + // - there's no information in the API about DaemonEnpoint (legacy cluster) and requested port is equal to ports.KubeletPort (cluster-wide config) + kubeletPort := node.Status.DaemonEndpoints.KubeletEndpoint.Port + if kubeletPort == 0 { + kubeletPort = ports.KubeletPort + } + if portReq == "" || strconv.Itoa(kubeletPort) == portReq { + scheme, port, kubeletTransport, err := connection.GetConnectionInfo(ctx, node.Name) + if err != nil { + return nil, nil, err + } + return &url.URL{ + Scheme: scheme, + Host: net.JoinHostPort( + host, + strconv.FormatUint(uint64(port), 10), + ), + }, + kubeletTransport, + nil + } + return &url.URL{Scheme: schemeReq, Host: net.JoinHostPort(host, portReq)}, proxyTransport, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/node/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/node/strategy_test.go new file mode 100644 index 000000000..3a8552d2c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/node/strategy_test.go @@ -0,0 +1,57 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" +) + +func TestMatchNode(t *testing.T) { + testFieldMap := map[bool][]fields.Set{ + true: { + {"metadata.name": "foo"}, + }, + false: { + {"foo": "bar"}, + }, + } + + for expectedResult, fieldSet := range testFieldMap { + for _, field := range fieldSet { + m := MatchNode(labels.Everything(), field.AsSelector()) + _, matchesSingle := m.MatchesSingle() + if e, a := expectedResult, matchesSingle; e != a { + t.Errorf("%+v: expected %v, got %v", fieldSet, e, a) + } + } + } +} + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "Node", + labels.Set(NodeToSelectableFields(&api.Node{})), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/doc.go new file mode 100644 index 000000000..0f0cd6c91 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 persistentvolume diff --git a/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/etcd/etcd.go new file mode 100644 index 000000000..7f30cc20b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/etcd/etcd.go @@ -0,0 +1,86 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/persistentvolume" + "k8s.io/kubernetes/pkg/runtime" +) + +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against persistent volumes. +func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { + prefix := "/persistentvolumes" + + newListFunc := func() runtime.Object { return &api.PersistentVolumeList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.PersistentVolumes), &api.PersistentVolume{}, prefix, persistentvolume.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.PersistentVolume{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return prefix + }, + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NoNamespaceKeyFunc(ctx, prefix, name) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.PersistentVolume).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return persistentvolume.MatchPersistentVolumes(label, field) + }, + QualifiedResource: api.Resource("persistentvolumes"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: persistentvolume.Strategy, + UpdateStrategy: persistentvolume.Strategy, + DeleteStrategy: persistentvolume.Strategy, + ReturnDeletedObject: true, + + Storage: storageInterface, + } + + statusStore := *store + statusStore.UpdateStrategy = persistentvolume.StatusStrategy + + return &REST{store}, &StatusREST{store: &statusStore} +} + +// StatusREST implements the REST endpoint for changing the status of a persistentvolume. +type StatusREST struct { + store *etcdgeneric.Etcd +} + +func (r *StatusREST) New() runtime.Object { + return &api.PersistentVolume{} +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/etcd/etcd_test.go new file mode 100644 index 000000000..c9fd6ad83 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/etcd/etcd_test.go @@ -0,0 +1,183 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage/etcd/etcdtest" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + "k8s.io/kubernetes/pkg/util/diff" +) + +func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + persistentVolumeStorage, statusStorage := NewREST(restOptions) + return persistentVolumeStorage, statusStorage, server +} + +func validNewPersistentVolume(name string) *api.PersistentVolume { + pv := &api.PersistentVolume{ + ObjectMeta: api.ObjectMeta{ + Name: name, + }, + Spec: api.PersistentVolumeSpec{ + Capacity: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), + }, + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + PersistentVolumeSource: api.PersistentVolumeSource{ + HostPath: &api.HostPathVolumeSource{Path: "/foo"}, + }, + PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRetain, + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumePending, + Message: "bar", + Reason: "foo", + }, + } + return pv +} + +func validChangedPersistentVolume() *api.PersistentVolume { + pv := validNewPersistentVolume("foo") + return pv +} + +func TestCreate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + pv := validNewPersistentVolume("foo") + pv.ObjectMeta = api.ObjectMeta{GenerateName: "foo"} + test.TestCreate( + // valid + pv, + // invalid + &api.PersistentVolume{ + ObjectMeta: api.ObjectMeta{Name: "*BadName!"}, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestUpdate( + // valid + validNewPersistentVolume("foo"), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*api.PersistentVolume) + object.Spec.Capacity = api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("20G"), + } + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope().ReturnDeletedObject() + test.TestDelete(validNewPersistentVolume("foo")) +} + +func TestGet(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestGet(validNewPersistentVolume("foo")) +} + +func TestList(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestList(validNewPersistentVolume("foo")) +} + +func TestWatch(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestWatch( + validNewPersistentVolume("foo"), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": "foo"}, + {"name": "foo"}, + }, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + }, + ) +} + +func TestUpdateStatus(t *testing.T) { + storage, statusStorage, server := newStorage(t) + defer server.Terminate(t) + ctx := api.NewContext() + key, _ := storage.KeyFunc(ctx, "foo") + key = etcdtest.AddPrefix(key) + pvStart := validNewPersistentVolume("foo") + err := storage.Storage.Set(ctx, key, pvStart, nil, 0) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + pvIn := &api.PersistentVolume{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + }, + Status: api.PersistentVolumeStatus{ + Phase: api.VolumeBound, + }, + } + + _, _, err = statusStorage.Update(ctx, pvIn) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + obj, err := storage.Get(ctx, "foo") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + pvOut := obj.(*api.PersistentVolume) + // only compare the relevant change b/c metadata will differ + if !api.Semantic.DeepEqual(pvIn.Status, pvOut.Status) { + t.Errorf("unexpected object: %s", diff.ObjectDiff(pvIn.Status, pvOut.Status)) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/strategy.go new file mode 100644 index 000000000..12eb72ce9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/strategy.go @@ -0,0 +1,117 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 persistentvolume + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// persistentvolumeStrategy implements behavior for PersistentVolume objects +type persistentvolumeStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating PersistentVolume +// objects via the REST API. +var Strategy = persistentvolumeStrategy{api.Scheme, api.SimpleNameGenerator} + +func (persistentvolumeStrategy) NamespaceScoped() bool { + return false +} + +// ResetBeforeCreate clears the Status field which is not allowed to be set by end users on creation. +func (persistentvolumeStrategy) PrepareForCreate(obj runtime.Object) { + pv := obj.(*api.PersistentVolume) + pv.Status = api.PersistentVolumeStatus{} +} + +func (persistentvolumeStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + persistentvolume := obj.(*api.PersistentVolume) + return validation.ValidatePersistentVolume(persistentvolume) +} + +// Canonicalize normalizes the object after validation. +func (persistentvolumeStrategy) Canonicalize(obj runtime.Object) { +} + +func (persistentvolumeStrategy) AllowCreateOnUpdate() bool { + return false +} + +// PrepareForUpdate sets the Status fields which is not allowed to be set by an end user updating a PV +func (persistentvolumeStrategy) PrepareForUpdate(obj, old runtime.Object) { + newPv := obj.(*api.PersistentVolume) + oldPv := obj.(*api.PersistentVolume) + newPv.Status = oldPv.Status +} + +func (persistentvolumeStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + errorList := validation.ValidatePersistentVolume(obj.(*api.PersistentVolume)) + return append(errorList, validation.ValidatePersistentVolumeUpdate(obj.(*api.PersistentVolume), old.(*api.PersistentVolume))...) +} + +func (persistentvolumeStrategy) AllowUnconditionalUpdate() bool { + return true +} + +type persistentvolumeStatusStrategy struct { + persistentvolumeStrategy +} + +var StatusStrategy = persistentvolumeStatusStrategy{Strategy} + +// PrepareForUpdate sets the Spec field which is not allowed to be changed when updating a PV's Status +func (persistentvolumeStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newPv := obj.(*api.PersistentVolume) + oldPv := obj.(*api.PersistentVolume) + newPv.Spec = oldPv.Spec +} + +func (persistentvolumeStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidatePersistentVolumeStatusUpdate(obj.(*api.PersistentVolume), old.(*api.PersistentVolume)) +} + +// MatchPersistentVolume returns a generic matcher for a given label and field selector. +func MatchPersistentVolumes(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + persistentvolumeObj, ok := obj.(*api.PersistentVolume) + if !ok { + return false, fmt.Errorf("not a persistentvolume") + } + fields := PersistentVolumeToSelectableFields(persistentvolumeObj) + return label.Matches(labels.Set(persistentvolumeObj.Labels)) && field.Matches(fields), nil + }) +} + +// PersistentVolumeToSelectableFields returns a label set that represents the object +func PersistentVolumeToSelectableFields(persistentvolume *api.PersistentVolume) labels.Set { + objectMetaFieldsSet := generic.ObjectMetaFieldsSet(persistentvolume.ObjectMeta, false) + specificFieldsSet := fields.Set{ + // This is a bug, but we need to support it for backward compatibility. + "name": persistentvolume.Name, + } + return labels.Set(generic.MergeFieldsSets(objectMetaFieldsSet, specificFieldsSet)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/strategy_test.go new file mode 100644 index 000000000..423aa7364 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolume/strategy_test.go @@ -0,0 +1,34 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 persistentvolume + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" +) + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "PersistentVolume", + PersistentVolumeToSelectableFields(&api.PersistentVolume{}), + map[string]string{"name": "metadata.name"}, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/doc.go new file mode 100644 index 000000000..f58b1682c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 persistentvolumeclaim diff --git a/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/etcd/etcd.go new file mode 100644 index 000000000..e81c825cd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/etcd/etcd.go @@ -0,0 +1,86 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/persistentvolumeclaim" + "k8s.io/kubernetes/pkg/runtime" +) + +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against persistent volume claims. +func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { + prefix := "/persistentvolumeclaims" + + newListFunc := func() runtime.Object { return &api.PersistentVolumeClaimList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.PersistentVolumeClaims), &api.PersistentVolumeClaim{}, prefix, persistentvolumeclaim.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.PersistentVolumeClaim{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.PersistentVolumeClaim).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return persistentvolumeclaim.MatchPersistentVolumeClaim(label, field) + }, + QualifiedResource: api.Resource("persistentvolumeclaims"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: persistentvolumeclaim.Strategy, + UpdateStrategy: persistentvolumeclaim.Strategy, + DeleteStrategy: persistentvolumeclaim.Strategy, + ReturnDeletedObject: true, + + Storage: storageInterface, + } + + statusStore := *store + statusStore.UpdateStrategy = persistentvolumeclaim.StatusStrategy + + return &REST{store}, &StatusREST{store: &statusStore} +} + +// StatusREST implements the REST endpoint for changing the status of a persistentvolumeclaim. +type StatusREST struct { + store *etcdgeneric.Etcd +} + +func (r *StatusREST) New() runtime.Object { + return &api.PersistentVolumeClaim{} +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/etcd/etcd_test.go new file mode 100644 index 000000000..04ccda042 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/etcd/etcd_test.go @@ -0,0 +1,184 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage/etcd/etcdtest" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + "k8s.io/kubernetes/pkg/util/diff" +) + +func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + persistentVolumeClaimStorage, statusStorage := NewREST(restOptions) + return persistentVolumeClaimStorage, statusStorage, server +} + +func validNewPersistentVolumeClaim(name, ns string) *api.PersistentVolumeClaim { + pv := &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("10G"), + }, + }, + }, + Status: api.PersistentVolumeClaimStatus{ + Phase: api.ClaimPending, + }, + } + return pv +} + +func TestCreate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + pv := validNewPersistentVolumeClaim("foo", api.NamespaceDefault) + pv.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + pv, + // invalid + &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{Name: "*BadName!"}, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestUpdate( + // valid + validNewPersistentVolumeClaim("foo", api.NamespaceDefault), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*api.PersistentVolumeClaim) + object.Spec.Resources = api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("20G"), + }, + } + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ReturnDeletedObject() + test.TestDelete(validNewPersistentVolumeClaim("foo", api.NamespaceDefault)) +} + +func TestGet(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(validNewPersistentVolumeClaim("foo", api.NamespaceDefault)) +} + +func TestList(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(validNewPersistentVolumeClaim("foo", api.NamespaceDefault)) +} + +func TestWatch(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validNewPersistentVolumeClaim("foo", api.NamespaceDefault), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": "foo"}, + {"name": "foo"}, + }, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + }, + ) +} + +func TestUpdateStatus(t *testing.T) { + storage, statusStorage, server := newStorage(t) + defer server.Terminate(t) + ctx := api.NewDefaultContext() + + key, _ := storage.KeyFunc(ctx, "foo") + key = etcdtest.AddPrefix(key) + pvcStart := validNewPersistentVolumeClaim("foo", api.NamespaceDefault) + err := storage.Storage.Set(ctx, key, pvcStart, nil, 0) + + pvc := &api.PersistentVolumeClaim{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: api.PersistentVolumeClaimSpec{ + AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce}, + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceName(api.ResourceStorage): resource.MustParse("3Gi"), + }, + }, + }, + Status: api.PersistentVolumeClaimStatus{ + Phase: api.ClaimBound, + }, + } + + _, _, err = statusStorage.Update(ctx, pvc) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + obj, err := storage.Get(ctx, "foo") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + pvcOut := obj.(*api.PersistentVolumeClaim) + // only compare relevant changes b/c of difference in metadata + if !api.Semantic.DeepEqual(pvc.Status, pvcOut.Status) { + t.Errorf("unexpected object: %s", diff.ObjectDiff(pvc.Status, pvcOut.Status)) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/strategy.go new file mode 100644 index 000000000..8580c53b1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/strategy.go @@ -0,0 +1,117 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 persistentvolumeclaim + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// persistentvolumeclaimStrategy implements behavior for PersistentVolumeClaim objects +type persistentvolumeclaimStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating PersistentVolumeClaim +// objects via the REST API. +var Strategy = persistentvolumeclaimStrategy{api.Scheme, api.SimpleNameGenerator} + +func (persistentvolumeclaimStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears the Status field which is not allowed to be set by end users on creation. +func (persistentvolumeclaimStrategy) PrepareForCreate(obj runtime.Object) { + pv := obj.(*api.PersistentVolumeClaim) + pv.Status = api.PersistentVolumeClaimStatus{} +} + +func (persistentvolumeclaimStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + pvc := obj.(*api.PersistentVolumeClaim) + return validation.ValidatePersistentVolumeClaim(pvc) +} + +// Canonicalize normalizes the object after validation. +func (persistentvolumeclaimStrategy) Canonicalize(obj runtime.Object) { +} + +func (persistentvolumeclaimStrategy) AllowCreateOnUpdate() bool { + return false +} + +// PrepareForUpdate sets the Status field which is not allowed to be set by end users on update +func (persistentvolumeclaimStrategy) PrepareForUpdate(obj, old runtime.Object) { + newPvc := obj.(*api.PersistentVolumeClaim) + oldPvc := obj.(*api.PersistentVolumeClaim) + newPvc.Status = oldPvc.Status +} + +func (persistentvolumeclaimStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + errorList := validation.ValidatePersistentVolumeClaim(obj.(*api.PersistentVolumeClaim)) + return append(errorList, validation.ValidatePersistentVolumeClaimUpdate(obj.(*api.PersistentVolumeClaim), old.(*api.PersistentVolumeClaim))...) +} + +func (persistentvolumeclaimStrategy) AllowUnconditionalUpdate() bool { + return true +} + +type persistentvolumeclaimStatusStrategy struct { + persistentvolumeclaimStrategy +} + +var StatusStrategy = persistentvolumeclaimStatusStrategy{Strategy} + +// PrepareForUpdate sets the Spec field which is not allowed to be changed when updating a PV's Status +func (persistentvolumeclaimStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newPv := obj.(*api.PersistentVolumeClaim) + oldPv := obj.(*api.PersistentVolumeClaim) + newPv.Spec = oldPv.Spec +} + +func (persistentvolumeclaimStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidatePersistentVolumeClaimStatusUpdate(obj.(*api.PersistentVolumeClaim), old.(*api.PersistentVolumeClaim)) +} + +// MatchPersistentVolumeClaim returns a generic matcher for a given label and field selector. +func MatchPersistentVolumeClaim(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + persistentvolumeclaimObj, ok := obj.(*api.PersistentVolumeClaim) + if !ok { + return false, fmt.Errorf("not a persistentvolumeclaim") + } + fields := PersistentVolumeClaimToSelectableFields(persistentvolumeclaimObj) + return label.Matches(labels.Set(persistentvolumeclaimObj.Labels)) && field.Matches(fields), nil + }) +} + +// PersistentVolumeClaimToSelectableFields returns a label set that represents the object +func PersistentVolumeClaimToSelectableFields(persistentvolumeclaim *api.PersistentVolumeClaim) labels.Set { + objectMetaFieldsSet := generic.ObjectMetaFieldsSet(persistentvolumeclaim.ObjectMeta, true) + specificFieldsSet := fields.Set{ + // This is a bug, but we need to support it for backward compatibility. + "name": persistentvolumeclaim.Name, + } + return labels.Set(generic.MergeFieldsSets(objectMetaFieldsSet, specificFieldsSet)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/strategy_test.go new file mode 100644 index 000000000..ecbdba3bd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/strategy_test.go @@ -0,0 +1,34 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 persistentvolumeclaim + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" +) + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "PersistentVolumeClaim", + PersistentVolumeClaimToSelectableFields(&api.PersistentVolumeClaim{}), + map[string]string{"name": "metadata.name"}, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/pod/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/pod/doc.go new file mode 100644 index 000000000..3c6d2f3c1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/pod/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 pod provides Registry interface and it's RESTStorage +// implementation for storing Pod api objects. +package pod diff --git a/vendor/k8s.io/kubernetes/pkg/registry/pod/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/pod/etcd/etcd.go new file mode 100644 index 000000000..33dfb949b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/pod/etcd/etcd.go @@ -0,0 +1,200 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "fmt" + "net/http" + "net/url" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + storeerr "k8s.io/kubernetes/pkg/api/errors/storage" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/kubelet/client" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/pod" + podrest "k8s.io/kubernetes/pkg/registry/pod/rest" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" +) + +// PodStorage includes storage for pods and all sub resources +type PodStorage struct { + Pod *REST + Binding *BindingREST + Status *StatusREST + Log *podrest.LogREST + Proxy *podrest.ProxyREST + Exec *podrest.ExecREST + Attach *podrest.AttachREST + PortForward *podrest.PortForwardREST +} + +// REST implements a RESTStorage for pods against etcd +type REST struct { + *etcdgeneric.Etcd + proxyTransport http.RoundTripper +} + +// NewStorage returns a RESTStorage object that will work against pods. +func NewStorage(opts generic.RESTOptions, k client.ConnectionInfoGetter, proxyTransport http.RoundTripper) PodStorage { + prefix := "/pods" + + newListFunc := func() runtime.Object { return &api.PodList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.Pods), &api.Pod{}, prefix, pod.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.Pod{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.Pod).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return pod.MatchPod(label, field) + }, + QualifiedResource: api.Resource("pods"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: pod.Strategy, + UpdateStrategy: pod.Strategy, + DeleteStrategy: pod.Strategy, + ReturnDeletedObject: true, + + Storage: storageInterface, + } + + statusStore := *store + statusStore.UpdateStrategy = pod.StatusStrategy + + return PodStorage{ + Pod: &REST{store, proxyTransport}, + Binding: &BindingREST{store: store}, + Status: &StatusREST{store: &statusStore}, + Log: &podrest.LogREST{Store: store, KubeletConn: k}, + Proxy: &podrest.ProxyREST{Store: store, ProxyTransport: proxyTransport}, + Exec: &podrest.ExecREST{Store: store, KubeletConn: k}, + Attach: &podrest.AttachREST{Store: store, KubeletConn: k}, + PortForward: &podrest.PortForwardREST{Store: store, KubeletConn: k}, + } +} + +// Implement Redirector. +var _ = rest.Redirector(&REST{}) + +// ResourceLocation returns a pods location from its HostIP +func (r *REST) ResourceLocation(ctx api.Context, name string) (*url.URL, http.RoundTripper, error) { + return pod.ResourceLocation(r, r.proxyTransport, ctx, name) +} + +// BindingREST implements the REST endpoint for binding pods to nodes when etcd is in use. +type BindingREST struct { + store *etcdgeneric.Etcd +} + +// New creates a new binding resource +func (r *BindingREST) New() runtime.Object { + return &api.Binding{} +} + +var _ = rest.Creater(&BindingREST{}) + +// Create ensures a pod is bound to a specific host. +func (r *BindingREST) Create(ctx api.Context, obj runtime.Object) (out runtime.Object, err error) { + binding := obj.(*api.Binding) + + // TODO: move me to a binding strategy + if errs := validation.ValidatePodBinding(binding); len(errs) != 0 { + return nil, errs.ToAggregate() + } + + err = r.assignPod(ctx, binding.Name, binding.Target.Name, binding.Annotations) + out = &unversioned.Status{Status: unversioned.StatusSuccess} + return +} + +// setPodHostAndAnnotations sets the given pod's host to 'machine' if and only if it was +// previously 'oldMachine' and merges the provided annotations with those of the pod. +// Returns the current state of the pod, or an error. +func (r *BindingREST) setPodHostAndAnnotations(ctx api.Context, podID, oldMachine, machine string, annotations map[string]string) (finalPod *api.Pod, err error) { + podKey, err := r.store.KeyFunc(ctx, podID) + if err != nil { + return nil, err + } + err = r.store.Storage.GuaranteedUpdate(ctx, podKey, &api.Pod{}, false, nil, storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { + pod, ok := obj.(*api.Pod) + if !ok { + return nil, fmt.Errorf("unexpected object: %#v", obj) + } + if pod.DeletionTimestamp != nil { + return nil, fmt.Errorf("pod %s is being deleted, cannot be assigned to a host", pod.Name) + } + if pod.Spec.NodeName != oldMachine { + return nil, fmt.Errorf("pod %v is already assigned to node %q", pod.Name, pod.Spec.NodeName) + } + pod.Spec.NodeName = machine + if pod.Annotations == nil { + pod.Annotations = make(map[string]string) + } + for k, v := range annotations { + pod.Annotations[k] = v + } + finalPod = pod + return pod, nil + })) + return finalPod, err +} + +// assignPod assigns the given pod to the given machine. +func (r *BindingREST) assignPod(ctx api.Context, podID string, machine string, annotations map[string]string) (err error) { + if _, err = r.setPodHostAndAnnotations(ctx, podID, "", machine, annotations); err != nil { + err = storeerr.InterpretGetError(err, api.Resource("pods"), podID) + err = storeerr.InterpretUpdateError(err, api.Resource("pods"), podID) + if _, ok := err.(*errors.StatusError); !ok { + err = errors.NewConflict(api.Resource("pods/binding"), podID, err) + } + } + return +} + +// StatusREST implements the REST endpoint for changing the status of a pod. +type StatusREST struct { + store *etcdgeneric.Etcd +} + +// New creates a new pod resource +func (r *StatusREST) New() runtime.Object { + return &api.Pod{} +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/pod/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/pod/etcd/etcd_test.go new file mode 100644 index 000000000..0b46a7c6b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/pod/etcd/etcd_test.go @@ -0,0 +1,774 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "strings" + "testing" + + "golang.org/x/net/context" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + storeerr "k8s.io/kubernetes/pkg/api/errors/storage" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/securitycontext" + "k8s.io/kubernetes/pkg/storage" + "k8s.io/kubernetes/pkg/storage/etcd/etcdtest" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + "k8s.io/kubernetes/pkg/util/diff" +) + +func newStorage(t *testing.T) (*REST, *BindingREST, *StatusREST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 3} + storage := NewStorage(restOptions, nil, nil) + return storage.Pod, storage.Binding, storage.Status, server +} + +func validNewPod() *api.Pod { + grace := int64(30) + return &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + + TerminationGracePeriodSeconds: &grace, + Containers: []api.Container{ + { + Name: "foo", + Image: "test", + ImagePullPolicy: api.PullAlways, + + TerminationMessagePath: api.TerminationMessagePathDefault, + SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), + }, + }, + SecurityContext: &api.PodSecurityContext{}, + }, + } +} + +func validChangedPod() *api.Pod { + pod := validNewPod() + pod.Labels = map[string]string{ + "foo": "bar", + } + return pod +} + +func TestCreate(t *testing.T) { + storage, _, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + pod := validNewPod() + pod.ObjectMeta = api.ObjectMeta{} + // Make an invalid pod with an an incorrect label. + invalidPod := validNewPod() + invalidPod.Namespace = test.TestNamespace() + invalidPod.Labels = map[string]string{ + "invalid/label/to/cause/validation/failure": "bar", + } + test.TestCreate( + // valid + pod, + // invalid (empty contains list) + &api.Pod{ + Spec: api.PodSpec{ + Containers: []api.Container{}, + }, + }, + // invalid (invalid labels) + invalidPod, + ) +} + +func TestUpdate(t *testing.T) { + storage, _, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestUpdate( + // valid + validNewPod(), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*api.Pod) + object.Labels = map[string]string{"a": "b"} + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, _, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ReturnDeletedObject() + test.TestDelete(validNewPod()) + + scheduledPod := validNewPod() + scheduledPod.Spec.NodeName = "some-node" + test.TestDeleteGraceful(scheduledPod, 30) +} + +type FailDeletionStorage struct { + storage.Interface + Called *bool +} + +func (f FailDeletionStorage) Delete(ctx context.Context, key string, out runtime.Object, precondition *storage.Preconditions) error { + *f.Called = true + return storage.NewKeyNotFoundError(key, 0) +} + +func newFailDeleteStorage(t *testing.T, called *bool) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + failDeleteStorage := FailDeletionStorage{etcdStorage, called} + restOptions := generic.RESTOptions{Storage: failDeleteStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 3} + storage := NewStorage(restOptions, nil, nil) + return storage.Pod, server +} + +func TestIgnoreDeleteNotFound(t *testing.T) { + pod := validNewPod() + testContext := api.WithNamespace(api.NewContext(), api.NamespaceDefault) + called := false + registry, server := newFailDeleteStorage(t, &called) + defer server.Terminate(t) + + // should fail if pod A is not created yet. + _, err := registry.Delete(testContext, pod.Name, nil) + if !errors.IsNotFound(err) { + t.Errorf("Unexpected error: %v", err) + } + + // create pod + _, err = registry.Create(testContext, pod) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // delete object with grace period 0, storage will return NotFound, but the + // registry shouldn't get any error since we ignore the NotFound error. + zero := int64(0) + opt := &api.DeleteOptions{GracePeriodSeconds: &zero} + obj, err := registry.Delete(testContext, pod.Name, opt) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if !called { + t.Fatalf("expect the overriding Delete method to be called") + } + deletedPod, ok := obj.(*api.Pod) + if !ok { + t.Fatalf("expect a pod is returned") + } + if deletedPod.DeletionTimestamp == nil { + t.Errorf("expect the DeletionTimestamp to be set") + } + if deletedPod.DeletionGracePeriodSeconds == nil { + t.Fatalf("expect the DeletionGracePeriodSeconds to be set") + } + if *deletedPod.DeletionGracePeriodSeconds != 0 { + t.Errorf("expect the DeletionGracePeriodSeconds to be 0, got %d", *deletedPod.DeletionTimestamp) + } +} + +func TestCreateSetsFields(t *testing.T) { + storage, _, _, server := newStorage(t) + defer server.Terminate(t) + pod := validNewPod() + _, err := storage.Create(api.NewDefaultContext(), pod) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + ctx := api.NewDefaultContext() + object, err := storage.Get(ctx, "foo") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + actual := object.(*api.Pod) + if actual.Name != pod.Name { + t.Errorf("unexpected pod: %#v", actual) + } + if len(actual.UID) == 0 { + t.Errorf("expected pod UID to be set: %#v", actual) + } +} + +func TestResourceLocation(t *testing.T) { + expectedIP := "1.2.3.4" + testCases := []struct { + pod api.Pod + query string + location string + }{ + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Status: api.PodStatus{PodIP: expectedIP}, + }, + query: "foo", + location: expectedIP, + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Status: api.PodStatus{PodIP: expectedIP}, + }, + query: "foo:12345", + location: expectedIP + ":12345", + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "ctr"}, + }, + }, + Status: api.PodStatus{PodIP: expectedIP}, + }, + query: "foo", + location: expectedIP, + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "ctr", Ports: []api.ContainerPort{{ContainerPort: 9376}}}, + }, + }, + Status: api.PodStatus{PodIP: expectedIP}, + }, + query: "foo", + location: expectedIP + ":9376", + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "ctr", Ports: []api.ContainerPort{{ContainerPort: 9376}}}, + }, + }, + Status: api.PodStatus{PodIP: expectedIP}, + }, + query: "foo:12345", + location: expectedIP + ":12345", + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "ctr1"}, + {Name: "ctr2", Ports: []api.ContainerPort{{ContainerPort: 9376}}}, + }, + }, + Status: api.PodStatus{PodIP: expectedIP}, + }, + query: "foo", + location: expectedIP + ":9376", + }, + { + pod: api.Pod{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "ctr1", Ports: []api.ContainerPort{{ContainerPort: 9376}}}, + {Name: "ctr2", Ports: []api.ContainerPort{{ContainerPort: 1234}}}, + }, + }, + Status: api.PodStatus{PodIP: expectedIP}, + }, + query: "foo", + location: expectedIP + ":9376", + }, + } + + ctx := api.NewDefaultContext() + for _, tc := range testCases { + storage, _, _, server := newStorage(t) + key, _ := storage.KeyFunc(ctx, tc.pod.Name) + key = etcdtest.AddPrefix(key) + if err := storage.Storage.Create(ctx, key, &tc.pod, nil, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + redirector := rest.Redirector(storage) + location, _, err := redirector.ResourceLocation(api.NewDefaultContext(), tc.query) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if location == nil { + t.Errorf("Unexpected nil: %v", location) + } + + if location.Scheme != "" { + t.Errorf("Expected '%v', but got '%v'", "", location.Scheme) + } + if location.Host != tc.location { + t.Errorf("Expected %v, but got %v", tc.location, location.Host) + } + server.Terminate(t) + } +} + +func TestGet(t *testing.T) { + storage, _, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(validNewPod()) +} + +func TestList(t *testing.T) { + storage, _, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(validNewPod()) +} + +func TestWatch(t *testing.T) { + storage, _, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validNewPod(), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": "foo"}, + }, + // not matchin fields + []fields.Set{ + {"metadata.name": "bar"}, + }, + ) +} + +func TestEtcdCreate(t *testing.T) { + storage, bindingStorage, _, server := newStorage(t) + defer server.Terminate(t) + ctx := api.NewDefaultContext() + key, _ := storage.KeyFunc(ctx, "foo") + key = etcdtest.AddPrefix(key) + _, err := storage.Create(ctx, validNewPod()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Suddenly, a wild scheduler appears: + _, err = bindingStorage.Create(ctx, &api.Binding{ + ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"}, + Target: api.ObjectReference{Name: "machine"}, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + _, err = storage.Get(ctx, "foo") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } +} + +// Ensure that when scheduler creates a binding for a pod that has already been deleted +// by the API server, API server returns not-found error. +func TestEtcdCreateBindingNoPod(t *testing.T) { + storage, bindingStorage, _, server := newStorage(t) + defer server.Terminate(t) + ctx := api.NewDefaultContext() + + key, _ := storage.KeyFunc(ctx, "foo") + key = etcdtest.AddPrefix(key) + // Assume that a pod has undergone the following: + // - Create (apiserver) + // - Schedule (scheduler) + // - Delete (apiserver) + _, err := bindingStorage.Create(ctx, &api.Binding{ + ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"}, + Target: api.ObjectReference{Name: "machine"}, + }) + if err == nil { + t.Fatalf("Expected not-found-error but got nothing") + } + if !errors.IsNotFound(storeerr.InterpretGetError(err, api.Resource("pods"), "foo")) { + t.Fatalf("Unexpected error returned: %#v", err) + } + + _, err = storage.Get(ctx, "foo") + if err == nil { + t.Fatalf("Expected not-found-error but got nothing") + } + if !errors.IsNotFound(storeerr.InterpretGetError(err, api.Resource("pods"), "foo")) { + t.Fatalf("Unexpected error: %v", err) + } +} + +func TestEtcdCreateFailsWithoutNamespace(t *testing.T) { + storage, _, _, server := newStorage(t) + defer server.Terminate(t) + pod := validNewPod() + pod.Namespace = "" + _, err := storage.Create(api.NewContext(), pod) + // Accept "namespace" or "Namespace". + if err == nil || !strings.Contains(err.Error(), "amespace") { + t.Fatalf("expected error that namespace was missing from context, got: %v", err) + } +} + +func TestEtcdCreateWithContainersNotFound(t *testing.T) { + storage, bindingStorage, _, server := newStorage(t) + defer server.Terminate(t) + ctx := api.NewDefaultContext() + key, _ := storage.KeyFunc(ctx, "foo") + key = etcdtest.AddPrefix(key) + _, err := storage.Create(ctx, validNewPod()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Suddenly, a wild scheduler appears: + _, err = bindingStorage.Create(ctx, &api.Binding{ + ObjectMeta: api.ObjectMeta{ + Namespace: api.NamespaceDefault, + Name: "foo", + Annotations: map[string]string{"label1": "value1"}, + }, + Target: api.ObjectReference{Name: "machine"}, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + obj, err := storage.Get(ctx, "foo") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + pod := obj.(*api.Pod) + + if !(pod.Annotations != nil && pod.Annotations["label1"] == "value1") { + t.Fatalf("Pod annotations don't match the expected: %v", pod.Annotations) + } +} + +func TestEtcdCreateWithConflict(t *testing.T) { + storage, bindingStorage, _, server := newStorage(t) + defer server.Terminate(t) + ctx := api.NewDefaultContext() + + _, err := storage.Create(ctx, validNewPod()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Suddenly, a wild scheduler appears: + binding := api.Binding{ + ObjectMeta: api.ObjectMeta{ + Namespace: api.NamespaceDefault, + Name: "foo", + Annotations: map[string]string{"label1": "value1"}, + }, + Target: api.ObjectReference{Name: "machine"}, + } + _, err = bindingStorage.Create(ctx, &binding) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + _, err = bindingStorage.Create(ctx, &binding) + if err == nil || !errors.IsConflict(err) { + t.Fatalf("expected resource conflict error, not: %v", err) + } +} + +func TestEtcdCreateWithExistingContainers(t *testing.T) { + storage, bindingStorage, _, server := newStorage(t) + defer server.Terminate(t) + ctx := api.NewDefaultContext() + key, _ := storage.KeyFunc(ctx, "foo") + key = etcdtest.AddPrefix(key) + _, err := storage.Create(ctx, validNewPod()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Suddenly, a wild scheduler appears: + _, err = bindingStorage.Create(ctx, &api.Binding{ + ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"}, + Target: api.ObjectReference{Name: "machine"}, + }) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + _, err = storage.Get(ctx, "foo") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } +} + +func TestEtcdCreateBinding(t *testing.T) { + ctx := api.NewDefaultContext() + + testCases := map[string]struct { + binding api.Binding + errOK func(error) bool + }{ + "noName": { + binding: api.Binding{ + ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"}, + Target: api.ObjectReference{}, + }, + errOK: func(err error) bool { return err != nil }, + }, + "badKind": { + binding: api.Binding{ + ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"}, + Target: api.ObjectReference{Name: "machine1", Kind: "unknown"}, + }, + errOK: func(err error) bool { return err != nil }, + }, + "emptyKind": { + binding: api.Binding{ + ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"}, + Target: api.ObjectReference{Name: "machine2"}, + }, + errOK: func(err error) bool { return err == nil }, + }, + "kindNode": { + binding: api.Binding{ + ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault, Name: "foo"}, + Target: api.ObjectReference{Name: "machine3", Kind: "Node"}, + }, + errOK: func(err error) bool { return err == nil }, + }, + } + for k, test := range testCases { + storage, bindingStorage, _, server := newStorage(t) + key, _ := storage.KeyFunc(ctx, "foo") + key = etcdtest.AddPrefix(key) + + if _, err := storage.Create(ctx, validNewPod()); err != nil { + t.Fatalf("%s: unexpected error: %v", k, err) + } + if _, err := bindingStorage.Create(ctx, &test.binding); !test.errOK(err) { + t.Errorf("%s: unexpected error: %v", k, err) + } else if err == nil { + // If bind succeeded, verify Host field in pod's Spec. + pod, err := storage.Get(ctx, validNewPod().ObjectMeta.Name) + if err != nil { + t.Errorf("%s: unexpected error: %v", k, err) + } else if pod.(*api.Pod).Spec.NodeName != test.binding.Target.Name { + t.Errorf("%s: expected: %v, got: %v", k, pod.(*api.Pod).Spec.NodeName, test.binding.Target.Name) + } + } + server.Terminate(t) + } +} + +func TestEtcdUpdateNotScheduled(t *testing.T) { + storage, _, _, server := newStorage(t) + defer server.Terminate(t) + ctx := api.NewDefaultContext() + + key, _ := storage.KeyFunc(ctx, "foo") + key = etcdtest.AddPrefix(key) + if _, err := storage.Create(ctx, validNewPod()); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + podIn := validChangedPod() + _, _, err := storage.Update(ctx, podIn) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + obj, err := storage.Get(ctx, validNewPod().ObjectMeta.Name) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + podOut := obj.(*api.Pod) + // validChangedPod only changes the Labels, so were checking the update was valid + if !api.Semantic.DeepEqual(podIn.Labels, podOut.Labels) { + t.Errorf("objects differ: %v", diff.ObjectDiff(podOut, podIn)) + } +} + +func TestEtcdUpdateScheduled(t *testing.T) { + storage, _, _, server := newStorage(t) + defer server.Terminate(t) + ctx := api.NewDefaultContext() + + key, _ := storage.KeyFunc(ctx, "foo") + key = etcdtest.AddPrefix(key) + err := storage.Storage.Create(ctx, key, &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: api.PodSpec{ + NodeName: "machine", + Containers: []api.Container{ + { + Name: "foobar", + Image: "foo:v1", + SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), + }, + }, + SecurityContext: &api.PodSecurityContext{}, + }, + }, nil, 1) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + grace := int64(30) + podIn := api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + "foo": "bar", + }, + }, + Spec: api.PodSpec{ + NodeName: "machine", + Containers: []api.Container{{ + Name: "foobar", + Image: "foo:v2", + ImagePullPolicy: api.PullIfNotPresent, + TerminationMessagePath: api.TerminationMessagePathDefault, + SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), + }}, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + + TerminationGracePeriodSeconds: &grace, + SecurityContext: &api.PodSecurityContext{}, + }, + } + _, _, err = storage.Update(ctx, &podIn) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + obj, err := storage.Get(ctx, "foo") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + podOut := obj.(*api.Pod) + // Check to verify the Spec and Label updates match from change above. Those are the fields changed. + if !api.Semantic.DeepEqual(podOut.Spec, podIn.Spec) || !api.Semantic.DeepEqual(podOut.Labels, podIn.Labels) { + t.Errorf("objects differ: %v", diff.ObjectDiff(podOut, podIn)) + } + +} + +func TestEtcdUpdateStatus(t *testing.T) { + storage, _, statusStorage, server := newStorage(t) + defer server.Terminate(t) + ctx := api.NewDefaultContext() + + key, _ := storage.KeyFunc(ctx, "foo") + key = etcdtest.AddPrefix(key) + podStart := api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: api.PodSpec{ + NodeName: "machine", + Containers: []api.Container{ + { + Image: "foo:v1", + SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), + }, + }, + SecurityContext: &api.PodSecurityContext{}, + }, + } + err := storage.Storage.Create(ctx, key, &podStart, nil, 0) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + podIn := api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + "foo": "bar", + }, + }, + Spec: api.PodSpec{ + NodeName: "machine", + Containers: []api.Container{ + { + Image: "foo:v2", + ImagePullPolicy: api.PullIfNotPresent, + TerminationMessagePath: api.TerminationMessagePathDefault, + }, + }, + SecurityContext: &api.PodSecurityContext{}, + }, + Status: api.PodStatus{ + Phase: api.PodRunning, + PodIP: "127.0.0.1", + Message: "is now scheduled", + }, + } + + expected := podStart + expected.ResourceVersion = "2" + grace := int64(30) + expected.Spec.TerminationGracePeriodSeconds = &grace + expected.Spec.RestartPolicy = api.RestartPolicyAlways + expected.Spec.DNSPolicy = api.DNSClusterFirst + expected.Spec.Containers[0].ImagePullPolicy = api.PullIfNotPresent + expected.Spec.Containers[0].TerminationMessagePath = api.TerminationMessagePathDefault + expected.Labels = podIn.Labels + expected.Status = podIn.Status + + _, _, err = statusStorage.Update(ctx, &podIn) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + obj, err := storage.Get(ctx, "foo") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + podOut := obj.(*api.Pod) + // Check to verify the Spec, Label, and Status updates match from change above. Those are the fields changed. + if !api.Semantic.DeepEqual(podOut.Spec, podIn.Spec) || + !api.Semantic.DeepEqual(podOut.Labels, podIn.Labels) || + !api.Semantic.DeepEqual(podOut.Status, podIn.Status) { + t.Errorf("objects differ: %v", diff.ObjectDiff(podOut, podIn)) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/pod/rest/log.go b/vendor/k8s.io/kubernetes/pkg/registry/pod/rest/log.go new file mode 100644 index 000000000..602fcefee --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/pod/rest/log.go @@ -0,0 +1,73 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 rest + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/kubelet/client" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + genericrest "k8s.io/kubernetes/pkg/registry/generic/rest" + "k8s.io/kubernetes/pkg/registry/pod" + "k8s.io/kubernetes/pkg/runtime" +) + +// LogREST implements the log endpoint for a Pod +type LogREST struct { + KubeletConn client.ConnectionInfoGetter + Store *etcdgeneric.Etcd +} + +// LogREST implements GetterWithOptions +var _ = rest.GetterWithOptions(&LogREST{}) + +// New creates a new Pod log options object +func (r *LogREST) New() runtime.Object { + // TODO - return a resource that represents a log + return &api.Pod{} +} + +// Get retrieves a runtime.Object that will stream the contents of the pod log +func (r *LogREST) Get(ctx api.Context, name string, opts runtime.Object) (runtime.Object, error) { + logOpts, ok := opts.(*api.PodLogOptions) + if !ok { + return nil, fmt.Errorf("invalid options object: %#v", opts) + } + if errs := validation.ValidatePodLogOptions(logOpts); len(errs) > 0 { + return nil, errors.NewInvalid(api.Kind("PodLogOptions"), name, errs) + } + location, transport, err := pod.LogLocation(r.Store, r.KubeletConn, ctx, name, logOpts) + if err != nil { + return nil, err + } + return &genericrest.LocationStreamer{ + Location: location, + Transport: transport, + ContentType: "text/plain", + Flush: logOpts.Follow, + ResponseChecker: genericrest.NewGenericHttpResponseChecker(api.Resource("pods/log"), name), + }, nil +} + +// NewGetOptions creates a new options object +func (r *LogREST) NewGetOptions() (runtime.Object, bool, string) { + return &api.PodLogOptions{}, false, "" +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/pod/rest/log_test.go b/vendor/k8s.io/kubernetes/pkg/registry/pod/rest/log_test.go new file mode 100644 index 000000000..013514ed5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/pod/rest/log_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 rest + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/registrytest" +) + +func TestPodLogValidates(t *testing.T) { + etcdStorage, _ := registrytest.NewEtcdStorage(t, "") + store := &etcdgeneric.Etcd{ + Storage: etcdStorage, + } + logRest := &LogREST{Store: store, KubeletConn: nil} + + negativeOne := int64(-1) + testCases := []*api.PodLogOptions{ + {SinceSeconds: &negativeOne}, + {TailLines: &negativeOne}, + } + + for _, tc := range testCases { + _, err := logRest.Get(api.NewDefaultContext(), "test", tc) + if !errors.IsInvalid(err) { + t.Fatalf("unexpected error: %v", err) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/pod/rest/subresources.go b/vendor/k8s.io/kubernetes/pkg/registry/pod/rest/subresources.go new file mode 100644 index 000000000..fd0659549 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/pod/rest/subresources.go @@ -0,0 +1,190 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 rest + +import ( + "fmt" + "net/http" + "net/url" + "path" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/capabilities" + "k8s.io/kubernetes/pkg/kubelet/client" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + genericrest "k8s.io/kubernetes/pkg/registry/generic/rest" + "k8s.io/kubernetes/pkg/registry/pod" + "k8s.io/kubernetes/pkg/runtime" +) + +// ProxyREST implements the proxy subresource for a Pod +type ProxyREST struct { + Store *etcdgeneric.Etcd + ProxyTransport http.RoundTripper +} + +// Implement Connecter +var _ = rest.Connecter(&ProxyREST{}) + +var proxyMethods = []string{"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"} + +// New returns an empty pod resource +func (r *ProxyREST) New() runtime.Object { + return &api.Pod{} +} + +// ConnectMethods returns the list of HTTP methods that can be proxied +func (r *ProxyREST) ConnectMethods() []string { + return proxyMethods +} + +// NewConnectOptions returns versioned resource that represents proxy parameters +func (r *ProxyREST) NewConnectOptions() (runtime.Object, bool, string) { + return &api.PodProxyOptions{}, true, "path" +} + +// Connect returns a handler for the pod proxy +func (r *ProxyREST) Connect(ctx api.Context, id string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { + proxyOpts, ok := opts.(*api.PodProxyOptions) + if !ok { + return nil, fmt.Errorf("Invalid options object: %#v", opts) + } + location, transport, err := pod.ResourceLocation(r.Store, r.ProxyTransport, ctx, id) + if err != nil { + return nil, err + } + location.Path = path.Join(location.Path, proxyOpts.Path) + // Return a proxy handler that uses the desired transport, wrapped with additional proxy handling (to get URL rewriting, X-Forwarded-* headers, etc) + return newThrottledUpgradeAwareProxyHandler(location, transport, true, false, responder), nil +} + +// Support both GET and POST methods. We must support GET for browsers that want to use WebSockets. +var upgradeableMethods = []string{"GET", "POST"} + +// AttachREST implements the attach subresource for a Pod +type AttachREST struct { + Store *etcdgeneric.Etcd + KubeletConn client.ConnectionInfoGetter +} + +// Implement Connecter +var _ = rest.Connecter(&AttachREST{}) + +// New creates a new Pod object +func (r *AttachREST) New() runtime.Object { + return &api.Pod{} +} + +// Connect returns a handler for the pod exec proxy +func (r *AttachREST) Connect(ctx api.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { + attachOpts, ok := opts.(*api.PodAttachOptions) + if !ok { + return nil, fmt.Errorf("Invalid options object: %#v", opts) + } + location, transport, err := pod.AttachLocation(r.Store, r.KubeletConn, ctx, name, attachOpts) + if err != nil { + return nil, err + } + return newThrottledUpgradeAwareProxyHandler(location, transport, false, true, responder), nil +} + +// NewConnectOptions returns the versioned object that represents exec parameters +func (r *AttachREST) NewConnectOptions() (runtime.Object, bool, string) { + return &api.PodAttachOptions{}, false, "" +} + +// ConnectMethods returns the methods supported by exec +func (r *AttachREST) ConnectMethods() []string { + return upgradeableMethods +} + +// ExecREST implements the exec subresource for a Pod +type ExecREST struct { + Store *etcdgeneric.Etcd + KubeletConn client.ConnectionInfoGetter +} + +// Implement Connecter +var _ = rest.Connecter(&ExecREST{}) + +// New creates a new Pod object +func (r *ExecREST) New() runtime.Object { + return &api.Pod{} +} + +// Connect returns a handler for the pod exec proxy +func (r *ExecREST) Connect(ctx api.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { + execOpts, ok := opts.(*api.PodExecOptions) + if !ok { + return nil, fmt.Errorf("invalid options object: %#v", opts) + } + location, transport, err := pod.ExecLocation(r.Store, r.KubeletConn, ctx, name, execOpts) + if err != nil { + return nil, err + } + return newThrottledUpgradeAwareProxyHandler(location, transport, false, true, responder), nil +} + +// NewConnectOptions returns the versioned object that represents exec parameters +func (r *ExecREST) NewConnectOptions() (runtime.Object, bool, string) { + return &api.PodExecOptions{}, false, "" +} + +// ConnectMethods returns the methods supported by exec +func (r *ExecREST) ConnectMethods() []string { + return upgradeableMethods +} + +// PortForwardREST implements the portforward subresource for a Pod +type PortForwardREST struct { + Store *etcdgeneric.Etcd + KubeletConn client.ConnectionInfoGetter +} + +// Implement Connecter +var _ = rest.Connecter(&PortForwardREST{}) + +// New returns an empty pod object +func (r *PortForwardREST) New() runtime.Object { + return &api.Pod{} +} + +// NewConnectOptions returns nil since portforward doesn't take additional parameters +func (r *PortForwardREST) NewConnectOptions() (runtime.Object, bool, string) { + return nil, false, "" +} + +// ConnectMethods returns the methods supported by portforward +func (r *PortForwardREST) ConnectMethods() []string { + return upgradeableMethods +} + +// Connect returns a handler for the pod portforward proxy +func (r *PortForwardREST) Connect(ctx api.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { + location, transport, err := pod.PortForwardLocation(r.Store, r.KubeletConn, ctx, name) + if err != nil { + return nil, err + } + return newThrottledUpgradeAwareProxyHandler(location, transport, false, true, responder), nil +} + +func newThrottledUpgradeAwareProxyHandler(location *url.URL, transport http.RoundTripper, wrapTransport, upgradeRequired bool, responder rest.Responder) *genericrest.UpgradeAwareProxyHandler { + handler := genericrest.NewUpgradeAwareProxyHandler(location, transport, wrapTransport, upgradeRequired, responder) + handler.MaxBytesPerSec = capabilities.Get().PerConnectionBandwidthLimitBytesPerSec + return handler +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/pod/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/pod/strategy.go new file mode 100644 index 000000000..d739f92a8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/pod/strategy.go @@ -0,0 +1,467 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 pod + +import ( + "fmt" + "net" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/kubelet/client" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + utilnet "k8s.io/kubernetes/pkg/util/net" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// podStrategy implements behavior for Pods +type podStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Pod +// objects via the REST API. +var Strategy = podStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped is true for pods. +func (podStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears fields that are not allowed to be set by end users on creation. +func (podStrategy) PrepareForCreate(obj runtime.Object) { + pod := obj.(*api.Pod) + pod.Status = api.PodStatus{ + Phase: api.PodPending, + } +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (podStrategy) PrepareForUpdate(obj, old runtime.Object) { + newPod := obj.(*api.Pod) + oldPod := old.(*api.Pod) + newPod.Status = oldPod.Status +} + +// Validate validates a new pod. +func (podStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + pod := obj.(*api.Pod) + return validation.ValidatePod(pod) +} + +// Canonicalize normalizes the object after validation. +func (podStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is false for pods. +func (podStrategy) AllowCreateOnUpdate() bool { + return false +} + +// ValidateUpdate is the default update validation for an end user. +func (podStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + errorList := validation.ValidatePod(obj.(*api.Pod)) + return append(errorList, validation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod))...) +} + +// AllowUnconditionalUpdate allows pods to be overwritten +func (podStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// CheckGracefulDelete allows a pod to be gracefully deleted. It updates the DeleteOptions to +// reflect the desired grace value. +func (podStrategy) CheckGracefulDelete(obj runtime.Object, options *api.DeleteOptions) bool { + if options == nil { + return false + } + pod := obj.(*api.Pod) + period := int64(0) + // user has specified a value + if options.GracePeriodSeconds != nil { + period = *options.GracePeriodSeconds + } else { + // use the default value if set, or deletes the pod immediately (0) + if pod.Spec.TerminationGracePeriodSeconds != nil { + period = *pod.Spec.TerminationGracePeriodSeconds + } + } + // if the pod is not scheduled, delete immediately + if len(pod.Spec.NodeName) == 0 { + period = 0 + } + // if the pod is already terminated, delete immediately + if pod.Status.Phase == api.PodFailed || pod.Status.Phase == api.PodSucceeded { + period = 0 + } + // ensure the options and the pod are in sync + options.GracePeriodSeconds = &period + return true +} + +type podStrategyWithoutGraceful struct { + podStrategy +} + +// CheckGracefulDelete prohibits graceful deletion. +func (podStrategyWithoutGraceful) CheckGracefulDelete(obj runtime.Object, options *api.DeleteOptions) bool { + return false +} + +// StrategyWithoutGraceful implements the legacy instant delele behavior. +var StrategyWithoutGraceful = podStrategyWithoutGraceful{Strategy} + +type podStatusStrategy struct { + podStrategy +} + +var StatusStrategy = podStatusStrategy{Strategy} + +func (podStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newPod := obj.(*api.Pod) + oldPod := old.(*api.Pod) + newPod.Spec = oldPod.Spec + newPod.DeletionTimestamp = nil +} + +func (podStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + // TODO: merge valid fields after update + return validation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod)) +} + +// MatchPod returns a generic matcher for a given label and field selector. +func MatchPod(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + pod, ok := obj.(*api.Pod) + if !ok { + return nil, nil, fmt.Errorf("not a pod") + } + return labels.Set(pod.ObjectMeta.Labels), PodToSelectableFields(pod), nil + }, + } +} + +// PodToSelectableFields returns a field set that represents the object +// TODO: fields are not labels, and the validation rules for them do not apply. +func PodToSelectableFields(pod *api.Pod) fields.Set { + objectMetaFieldsSet := generic.ObjectMetaFieldsSet(pod.ObjectMeta, true) + podSpecificFieldsSet := fields.Set{ + "spec.nodeName": pod.Spec.NodeName, + "spec.restartPolicy": string(pod.Spec.RestartPolicy), + "status.phase": string(pod.Status.Phase), + } + return generic.MergeFieldsSets(objectMetaFieldsSet, podSpecificFieldsSet) +} + +// ResourceGetter is an interface for retrieving resources by ResourceLocation. +type ResourceGetter interface { + Get(api.Context, string) (runtime.Object, error) +} + +func getPod(getter ResourceGetter, ctx api.Context, name string) (*api.Pod, error) { + obj, err := getter.Get(ctx, name) + if err != nil { + return nil, err + } + pod := obj.(*api.Pod) + if pod == nil { + return nil, fmt.Errorf("Unexpected object type: %#v", pod) + } + return pod, nil +} + +// ResourceLocation returns a URL to which one can send traffic for the specified pod. +func ResourceLocation(getter ResourceGetter, rt http.RoundTripper, ctx api.Context, id string) (*url.URL, http.RoundTripper, error) { + // Allow ID as "podname" or "podname:port" or "scheme:podname:port". + // If port is not specified, try to use the first defined port on the pod. + scheme, name, port, valid := utilnet.SplitSchemeNamePort(id) + if !valid { + return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid pod request %q", id)) + } + // TODO: if port is not a number but a "(container)/(portname)", do a name lookup. + + pod, err := getPod(getter, ctx, name) + if err != nil { + return nil, nil, err + } + + // Try to figure out a port. + if port == "" { + for i := range pod.Spec.Containers { + if len(pod.Spec.Containers[i].Ports) > 0 { + port = fmt.Sprintf("%d", pod.Spec.Containers[i].Ports[0].ContainerPort) + break + } + } + } + + loc := &url.URL{ + Scheme: scheme, + } + if port == "" { + loc.Host = pod.Status.PodIP + } else { + loc.Host = net.JoinHostPort(pod.Status.PodIP, port) + } + return loc, rt, nil +} + +// getContainerNames returns a formatted string containing the container names +func getContainerNames(pod *api.Pod) string { + names := []string{} + for _, c := range pod.Spec.Containers { + names = append(names, c.Name) + } + return strings.Join(names, " ") +} + +// LogLocation returns the log URL for a pod container. If opts.Container is blank +// and only one container is present in the pod, that container is used. +func LogLocation( + getter ResourceGetter, + connInfo client.ConnectionInfoGetter, + ctx api.Context, + name string, + opts *api.PodLogOptions, +) (*url.URL, http.RoundTripper, error) { + pod, err := getPod(getter, ctx, name) + if err != nil { + return nil, nil, err + } + + // Try to figure out a container + // If a container was provided, it must be valid + container := opts.Container + if len(container) == 0 { + switch len(pod.Spec.Containers) { + case 1: + container = pod.Spec.Containers[0].Name + case 0: + return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name)) + default: + containerNames := getContainerNames(pod) + return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s, choose one of: [%s]", name, containerNames)) + } + } else { + if !podHasContainerWithName(pod, container) { + return nil, nil, errors.NewBadRequest(fmt.Sprintf("container %s is not valid for pod %s", container, name)) + } + } + nodeHost := pod.Spec.NodeName + if len(nodeHost) == 0 { + // If pod has not been assigned a host, return an empty location + return nil, nil, nil + } + nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(ctx, nodeHost) + if err != nil { + return nil, nil, err + } + params := url.Values{} + if opts.Follow { + params.Add("follow", "true") + } + if opts.Previous { + params.Add("previous", "true") + } + if opts.Timestamps { + params.Add("timestamps", "true") + } + if opts.SinceSeconds != nil { + params.Add("sinceSeconds", strconv.FormatInt(*opts.SinceSeconds, 10)) + } + if opts.SinceTime != nil { + params.Add("sinceTime", opts.SinceTime.Format(time.RFC3339)) + } + if opts.TailLines != nil { + params.Add("tailLines", strconv.FormatInt(*opts.TailLines, 10)) + } + if opts.LimitBytes != nil { + params.Add("limitBytes", strconv.FormatInt(*opts.LimitBytes, 10)) + } + loc := &url.URL{ + Scheme: nodeScheme, + Host: fmt.Sprintf("%s:%d", nodeHost, nodePort), + Path: fmt.Sprintf("/containerLogs/%s/%s/%s", pod.Namespace, pod.Name, container), + RawQuery: params.Encode(), + } + return loc, nodeTransport, nil +} + +func podHasContainerWithName(pod *api.Pod, containerName string) bool { + for _, c := range pod.Spec.Containers { + if c.Name == containerName { + return true + } + } + return false +} + +func streamParams(params url.Values, opts runtime.Object) error { + switch opts := opts.(type) { + case *api.PodExecOptions: + if opts.Stdin { + params.Add(api.ExecStdinParam, "1") + } + if opts.Stdout { + params.Add(api.ExecStdoutParam, "1") + } + if opts.Stderr { + params.Add(api.ExecStderrParam, "1") + } + if opts.TTY { + params.Add(api.ExecTTYParam, "1") + } + for _, c := range opts.Command { + params.Add("command", c) + } + case *api.PodAttachOptions: + if opts.Stdin { + params.Add(api.ExecStdinParam, "1") + } + if opts.Stdout { + params.Add(api.ExecStdoutParam, "1") + } + if opts.Stderr { + params.Add(api.ExecStderrParam, "1") + } + if opts.TTY { + params.Add(api.ExecTTYParam, "1") + } + default: + return fmt.Errorf("Unknown object for streaming: %v", opts) + } + return nil +} + +// AttachLocation returns the attach URL for a pod container. If opts.Container is blank +// and only one container is present in the pod, that container is used. +func AttachLocation( + getter ResourceGetter, + connInfo client.ConnectionInfoGetter, + ctx api.Context, + name string, + opts *api.PodAttachOptions, +) (*url.URL, http.RoundTripper, error) { + return streamLocation(getter, connInfo, ctx, name, opts, opts.Container, "attach") +} + +// ExecLocation returns the exec URL for a pod container. If opts.Container is blank +// and only one container is present in the pod, that container is used. +func ExecLocation( + getter ResourceGetter, + connInfo client.ConnectionInfoGetter, + ctx api.Context, + name string, + opts *api.PodExecOptions, +) (*url.URL, http.RoundTripper, error) { + return streamLocation(getter, connInfo, ctx, name, opts, opts.Container, "exec") +} + +func streamLocation( + getter ResourceGetter, + connInfo client.ConnectionInfoGetter, + ctx api.Context, + name string, + opts runtime.Object, + container, + path string, +) (*url.URL, http.RoundTripper, error) { + pod, err := getPod(getter, ctx, name) + if err != nil { + return nil, nil, err + } + + // Try to figure out a container + // If a container was provided, it must be valid + if container == "" { + switch len(pod.Spec.Containers) { + case 1: + container = pod.Spec.Containers[0].Name + case 0: + return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name)) + default: + containerNames := getContainerNames(pod) + return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s, choose one of: [%s]", name, containerNames)) + } + } else { + if !podHasContainerWithName(pod, container) { + return nil, nil, errors.NewBadRequest(fmt.Sprintf("container %s is not valid for pod %s", container, name)) + } + } + nodeHost := pod.Spec.NodeName + if len(nodeHost) == 0 { + // If pod has not been assigned a host, return an empty location + return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name)) + } + nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(ctx, nodeHost) + if err != nil { + return nil, nil, err + } + params := url.Values{} + if err := streamParams(params, opts); err != nil { + return nil, nil, err + } + loc := &url.URL{ + Scheme: nodeScheme, + Host: fmt.Sprintf("%s:%d", nodeHost, nodePort), + Path: fmt.Sprintf("/%s/%s/%s/%s", path, pod.Namespace, pod.Name, container), + RawQuery: params.Encode(), + } + return loc, nodeTransport, nil +} + +// PortForwardLocation returns the port-forward URL for a pod. +func PortForwardLocation( + getter ResourceGetter, + connInfo client.ConnectionInfoGetter, + ctx api.Context, + name string, +) (*url.URL, http.RoundTripper, error) { + pod, err := getPod(getter, ctx, name) + if err != nil { + return nil, nil, err + } + + nodeHost := pod.Spec.NodeName + if len(nodeHost) == 0 { + // If pod has not been assigned a host, return an empty location + return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name)) + } + nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(ctx, nodeHost) + if err != nil { + return nil, nil, err + } + loc := &url.URL{ + Scheme: nodeScheme, + Host: fmt.Sprintf("%s:%d", nodeHost, nodePort), + Path: fmt.Sprintf("/portForward/%s/%s", pod.Namespace, pod.Name), + } + return loc, nodeTransport, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/pod/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/pod/strategy_test.go new file mode 100644 index 000000000..77d343b52 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/pod/strategy_test.go @@ -0,0 +1,242 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 pod + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" +) + +func TestMatchPod(t *testing.T) { + testCases := []struct { + in *api.Pod + fieldSelector fields.Selector + expectMatch bool + }{ + { + in: &api.Pod{ + Spec: api.PodSpec{NodeName: "nodeA"}, + }, + fieldSelector: fields.ParseSelectorOrDie("spec.nodeName=nodeA"), + expectMatch: true, + }, + { + in: &api.Pod{ + Spec: api.PodSpec{NodeName: "nodeB"}, + }, + fieldSelector: fields.ParseSelectorOrDie("spec.nodeName=nodeA"), + expectMatch: false, + }, + { + in: &api.Pod{ + Spec: api.PodSpec{RestartPolicy: api.RestartPolicyAlways}, + }, + fieldSelector: fields.ParseSelectorOrDie("spec.restartPolicy=Always"), + expectMatch: true, + }, + { + in: &api.Pod{ + Spec: api.PodSpec{RestartPolicy: api.RestartPolicyAlways}, + }, + fieldSelector: fields.ParseSelectorOrDie("spec.restartPolicy=Never"), + expectMatch: false, + }, + { + in: &api.Pod{ + Status: api.PodStatus{Phase: api.PodRunning}, + }, + fieldSelector: fields.ParseSelectorOrDie("status.phase=Running"), + expectMatch: true, + }, + { + in: &api.Pod{ + Status: api.PodStatus{Phase: api.PodRunning}, + }, + fieldSelector: fields.ParseSelectorOrDie("status.phase=Pending"), + expectMatch: false, + }, + } + for _, testCase := range testCases { + result, err := MatchPod(labels.Everything(), testCase.fieldSelector).Matches(testCase.in) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + if result != testCase.expectMatch { + t.Errorf("Result %v, Expected %v, Selector: %v, Pod: %v", result, testCase.expectMatch, testCase.fieldSelector.String(), testCase.in) + } + } +} + +func TestCheckGracefulDelete(t *testing.T) { + defaultGracePeriod := int64(30) + tcs := []struct { + in *api.Pod + gracePeriod int64 + }{ + { + in: &api.Pod{ + Spec: api.PodSpec{NodeName: "something"}, + Status: api.PodStatus{Phase: api.PodPending}, + }, + + gracePeriod: defaultGracePeriod, + }, + { + in: &api.Pod{ + Spec: api.PodSpec{NodeName: "something"}, + Status: api.PodStatus{Phase: api.PodFailed}, + }, + gracePeriod: 0, + }, + { + in: &api.Pod{ + Spec: api.PodSpec{}, + Status: api.PodStatus{Phase: api.PodPending}, + }, + gracePeriod: 0, + }, + { + in: &api.Pod{ + Spec: api.PodSpec{}, + Status: api.PodStatus{Phase: api.PodSucceeded}, + }, + gracePeriod: 0, + }, + { + in: &api.Pod{ + Spec: api.PodSpec{}, + Status: api.PodStatus{}, + }, + gracePeriod: 0, + }, + } + for _, tc := range tcs { + out := &api.DeleteOptions{GracePeriodSeconds: &defaultGracePeriod} + Strategy.CheckGracefulDelete(tc.in, out) + if out.GracePeriodSeconds == nil { + t.Errorf("out grace period was nil but supposed to be %v", tc.gracePeriod) + } + if *(out.GracePeriodSeconds) != tc.gracePeriod { + t.Errorf("out grace period was %v but was expected to be %v", *out, tc.gracePeriod) + } + } +} + +type mockPodGetter struct { + pod *api.Pod +} + +func (g mockPodGetter) Get(api.Context, string) (runtime.Object, error) { + return g.pod, nil +} + +func TestCheckLogLocation(t *testing.T) { + ctx := api.NewDefaultContext() + tcs := []struct { + in *api.Pod + opts *api.PodLogOptions + expectedErr error + }{ + { + in: &api.Pod{ + Spec: api.PodSpec{}, + Status: api.PodStatus{}, + }, + opts: &api.PodLogOptions{}, + expectedErr: errors.NewBadRequest("a container name must be specified for pod test"), + }, + { + in: &api.Pod{ + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "mycontainer"}, + }, + }, + Status: api.PodStatus{}, + }, + opts: &api.PodLogOptions{}, + expectedErr: nil, + }, + { + in: &api.Pod{ + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "container1"}, + {Name: "container2"}, + }, + }, + Status: api.PodStatus{}, + }, + opts: &api.PodLogOptions{}, + expectedErr: errors.NewBadRequest("a container name must be specified for pod test, choose one of: [container1 container2]"), + }, + { + in: &api.Pod{ + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "container1"}, + {Name: "container2"}, + }, + }, + Status: api.PodStatus{}, + }, + opts: &api.PodLogOptions{ + Container: "unknown", + }, + expectedErr: errors.NewBadRequest("container unknown is not valid for pod test"), + }, + { + in: &api.Pod{ + Spec: api.PodSpec{ + Containers: []api.Container{ + {Name: "container1"}, + {Name: "container2"}, + }, + }, + Status: api.PodStatus{}, + }, + opts: &api.PodLogOptions{ + Container: "container2", + }, + expectedErr: nil, + }, + } + for _, tc := range tcs { + getter := &mockPodGetter{tc.in} + _, _, err := LogLocation(getter, nil, ctx, "test", tc.opts) + if !reflect.DeepEqual(err, tc.expectedErr) { + t.Errorf("expected %v, got %v", tc.expectedErr, err) + } + } +} + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "Pod", + labels.Set(PodToSelectableFields(&api.Pod{})), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/podsecuritypolicy/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/podsecuritypolicy/doc.go new file mode 100644 index 000000000..fa50db8e4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/podsecuritypolicy/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 podsecuritypolicy provides Registry interface and its REST +// implementation for storing PodSecurityPolicy api objects. +package podsecuritypolicy diff --git a/vendor/k8s.io/kubernetes/pkg/registry/podsecuritypolicy/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/podsecuritypolicy/etcd/etcd.go new file mode 100644 index 000000000..34839dd48 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/podsecuritypolicy/etcd/etcd.go @@ -0,0 +1,68 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/podsecuritypolicy" + "k8s.io/kubernetes/pkg/runtime" +) + +// REST implements a RESTStorage for PodSecurityPolicies against etcd. +type REST struct { + *etcdgeneric.Etcd +} + +const Prefix = "/podsecuritypolicies" + +// NewREST returns a RESTStorage object that will work against PodSecurityPolicy objects. +func NewREST(opts generic.RESTOptions) *REST { + newListFunc := func() runtime.Object { return &extensions.PodSecurityPolicyList{} } + storageInterface := opts.Decorator( + opts.Storage, 100, &extensions.PodSecurityPolicy{}, Prefix, podsecuritypolicy.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &extensions.PodSecurityPolicy{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return Prefix + }, + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NoNamespaceKeyFunc(ctx, Prefix, name) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*extensions.PodSecurityPolicy).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return podsecuritypolicy.MatchPodSecurityPolicy(label, field) + }, + QualifiedResource: extensions.Resource("podsecuritypolicies"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: podsecuritypolicy.Strategy, + UpdateStrategy: podsecuritypolicy.Strategy, + DeleteStrategy: podsecuritypolicy.Strategy, + ReturnDeletedObject: true, + Storage: storageInterface, + } + return &REST{store} +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/podsecuritypolicy/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/podsecuritypolicy/etcd/etcd_test.go new file mode 100644 index 000000000..1e7c8ca7f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/podsecuritypolicy/etcd/etcd_test.go @@ -0,0 +1,131 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + // Ensure that extensions/v1beta1 package is initialized. + _ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "extensions") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + return NewREST(restOptions), server +} + +func validNewPodSecurityPolicy() *extensions.PodSecurityPolicy { + return &extensions.PodSecurityPolicy{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + }, + Spec: extensions.PodSecurityPolicySpec{ + SELinux: extensions.SELinuxStrategyOptions{ + Rule: extensions.SELinuxStrategyRunAsAny, + }, + RunAsUser: extensions.RunAsUserStrategyOptions{ + Rule: extensions.RunAsUserStrategyRunAsAny, + }, + }, + } +} + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + scc := validNewPodSecurityPolicy() + scc.ObjectMeta = api.ObjectMeta{GenerateName: "foo-"} + test.TestCreate( + // valid + scc, + // invalid + &extensions.PodSecurityPolicy{ + ObjectMeta: api.ObjectMeta{Name: "name with spaces"}, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestUpdate( + // valid + validNewPodSecurityPolicy(), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.PodSecurityPolicy) + object.Labels = map[string]string{"a": "b"} + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope().ReturnDeletedObject() + test.TestDelete(validNewPodSecurityPolicy()) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestGet(validNewPodSecurityPolicy()) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestList(validNewPodSecurityPolicy()) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ClusterScope() + test.TestWatch( + validNewPodSecurityPolicy(), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": "foo"}, + }, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + {"name": "foo"}, + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/podsecuritypolicy/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/podsecuritypolicy/strategy.go new file mode 100644 index 000000000..905be4dc6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/podsecuritypolicy/strategy.go @@ -0,0 +1,93 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 podsecuritypolicy + +import ( + "fmt" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/extensions/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// strategy implements behavior for PodSecurityPolicy objects +type strategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating PodSecurityPolicy +// objects via the REST API. +var Strategy = strategy{api.Scheme, api.SimpleNameGenerator} + +var _ = rest.RESTCreateStrategy(Strategy) + +var _ = rest.RESTUpdateStrategy(Strategy) + +func (strategy) NamespaceScoped() bool { + return false +} + +func (strategy) AllowCreateOnUpdate() bool { + return false +} + +func (strategy) AllowUnconditionalUpdate() bool { + return true +} + +func (strategy) PrepareForCreate(obj runtime.Object) { +} + +func (strategy) PrepareForUpdate(obj, old runtime.Object) { +} + +func (strategy) Canonicalize(obj runtime.Object) { +} + +func (strategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + return validation.ValidatePodSecurityPolicy(obj.(*extensions.PodSecurityPolicy)) +} + +func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidatePodSecurityPolicyUpdate(old.(*extensions.PodSecurityPolicy), obj.(*extensions.PodSecurityPolicy)) +} + +// Matcher returns a generic matcher for a given label and field selector. +func MatchPodSecurityPolicy(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + psp, ok := obj.(*extensions.PodSecurityPolicy) + if !ok { + return nil, nil, fmt.Errorf("given object is not a pod security policy.") + } + return labels.Set(psp.ObjectMeta.Labels), PodSecurityPolicyToSelectableFields(psp), nil + }, + } +} + +// PodSecurityPolicyToSelectableFields returns a label set that represents the object +func PodSecurityPolicyToSelectableFields(obj *extensions.PodSecurityPolicy) fields.Set { + return generic.ObjectMetaFieldsSet(obj.ObjectMeta, false) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/doc.go new file mode 100644 index 000000000..a622a2ecf --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 podtemplate provides RESTStorage implementations for storing PodTemplate API objects. +package podtemplate diff --git a/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/etcd/etcd.go new file mode 100644 index 000000000..88203ece0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/etcd/etcd.go @@ -0,0 +1,70 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/podtemplate" + "k8s.io/kubernetes/pkg/runtime" +) + +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against pod templates. +func NewREST(opts generic.RESTOptions) *REST { + prefix := "/podtemplates" + + newListFunc := func() runtime.Object { return &api.PodTemplateList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.PodTemplates), &api.PodTemplate{}, prefix, podtemplate.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.PodTemplate{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.PodTemplate).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return podtemplate.MatchPodTemplate(label, field) + }, + QualifiedResource: api.Resource("podtemplates"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: podtemplate.Strategy, + UpdateStrategy: podtemplate.Strategy, + DeleteStrategy: podtemplate.Strategy, + ExportStrategy: podtemplate.Strategy, + + ReturnDeletedObject: true, + + Storage: storageInterface, + } + return &REST{store} +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/etcd/etcd_test.go new file mode 100644 index 000000000..6240e71ec --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/etcd/etcd_test.go @@ -0,0 +1,137 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + return NewREST(restOptions), server +} + +func validNewPodTemplate(name string) *api.PodTemplate { + return &api.PodTemplate{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: api.NamespaceDefault, + }, + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"test": "foo"}, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{ + { + Name: "foo", + Image: "test", + ImagePullPolicy: api.PullAlways, + + TerminationMessagePath: api.TerminationMessagePathDefault, + }, + }, + }, + }, + } +} + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + pod := validNewPodTemplate("foo") + pod.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + pod, + // invalid + &api.PodTemplate{ + Template: api.PodTemplateSpec{}, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestUpdate( + //valid + validNewPodTemplate("foo"), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*api.PodTemplate) + object.Template.Spec.NodeSelector = map[string]string{"a": "b"} + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ReturnDeletedObject() + test.TestDelete(validNewPodTemplate("foo")) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(validNewPodTemplate("foo")) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(validNewPodTemplate("foo")) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validNewPodTemplate("foo"), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{}, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + {"name": "foo"}, + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/strategy.go new file mode 100644 index 000000000..03ce85186 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/strategy.go @@ -0,0 +1,101 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 podtemplate + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// podTemplateStrategy implements behavior for PodTemplates +type podTemplateStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating PodTemplate +// objects via the REST API. +var Strategy = podTemplateStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped is true for pod templates. +func (podTemplateStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears fields that are not allowed to be set by end users on creation. +func (podTemplateStrategy) PrepareForCreate(obj runtime.Object) { + _ = obj.(*api.PodTemplate) +} + +// Validate validates a new pod template. +func (podTemplateStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + pod := obj.(*api.PodTemplate) + return validation.ValidatePodTemplate(pod) +} + +// Canonicalize normalizes the object after validation. +func (podTemplateStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is false for pod templates. +func (podTemplateStrategy) AllowCreateOnUpdate() bool { + return false +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (podTemplateStrategy) PrepareForUpdate(obj, old runtime.Object) { + _ = obj.(*api.PodTemplate) +} + +// ValidateUpdate is the default update validation for an end user. +func (podTemplateStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidatePodTemplateUpdate(obj.(*api.PodTemplate), old.(*api.PodTemplate)) +} + +func (podTemplateStrategy) AllowUnconditionalUpdate() bool { + return true +} + +func (podTemplateStrategy) Export(obj runtime.Object, exact bool) error { + // Do nothing + return nil +} + +func PodTemplateToSelectableFields(podTemplate *api.PodTemplate) fields.Set { + return fields.Set{} +} + +func MatchPodTemplate(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + pt, ok := obj.(*api.PodTemplate) + if !ok { + return nil, nil, fmt.Errorf("given object is not a pod template.") + } + return labels.Set(pt.ObjectMeta.Labels), PodTemplateToSelectableFields(pt), nil + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/strategy_test.go new file mode 100644 index 000000000..e26c13b86 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/podtemplate/strategy_test.go @@ -0,0 +1,35 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 podtemplate + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/labels" +) + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "PodTemplate", + labels.Set(PodTemplateToSelectableFields(&api.PodTemplate{})), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/registrytest/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/registrytest/doc.go new file mode 100644 index 000000000..81c460706 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/registrytest/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 registrytest provides tests for Registry implementations +// for storing Minions, Pods, Schedulers and Services. +package registrytest diff --git a/vendor/k8s.io/kubernetes/pkg/registry/registrytest/endpoint.go b/vendor/k8s.io/kubernetes/pkg/registry/registrytest/endpoint.go new file mode 100644 index 000000000..d11dafa67 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/registrytest/endpoint.go @@ -0,0 +1,110 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 registrytest + +import ( + "fmt" + "sync" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface for things that know how to store endpoints. +type EndpointRegistry struct { + Endpoints *api.EndpointsList + Updates []api.Endpoints + Err error + + lock sync.Mutex +} + +func (e *EndpointRegistry) ListEndpoints(ctx api.Context, options *api.ListOptions) (*api.EndpointsList, error) { + // TODO: support namespaces in this mock + e.lock.Lock() + defer e.lock.Unlock() + + return e.Endpoints, e.Err +} + +func (e *EndpointRegistry) GetEndpoints(ctx api.Context, name string) (*api.Endpoints, error) { + // TODO: support namespaces in this mock + e.lock.Lock() + defer e.lock.Unlock() + if e.Err != nil { + return nil, e.Err + } + if e.Endpoints != nil { + for _, endpoint := range e.Endpoints.Items { + if endpoint.Name == name { + return &endpoint, nil + } + } + } + return nil, errors.NewNotFound(api.Resource("endpoints"), name) +} + +func (e *EndpointRegistry) WatchEndpoints(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return nil, fmt.Errorf("unimplemented!") +} + +func (e *EndpointRegistry) UpdateEndpoints(ctx api.Context, endpoints *api.Endpoints) error { + // TODO: support namespaces in this mock + e.lock.Lock() + defer e.lock.Unlock() + + e.Updates = append(e.Updates, *endpoints) + + if e.Err != nil { + return e.Err + } + if e.Endpoints == nil { + e.Endpoints = &api.EndpointsList{ + Items: []api.Endpoints{ + *endpoints, + }, + } + return nil + } + for ix := range e.Endpoints.Items { + if e.Endpoints.Items[ix].Name == endpoints.Name { + e.Endpoints.Items[ix] = *endpoints + } + } + e.Endpoints.Items = append(e.Endpoints.Items, *endpoints) + return nil +} + +func (e *EndpointRegistry) DeleteEndpoints(ctx api.Context, name string) error { + // TODO: support namespaces in this mock + e.lock.Lock() + defer e.lock.Unlock() + if e.Err != nil { + return e.Err + } + if e.Endpoints != nil { + var newList []api.Endpoints + for _, endpoint := range e.Endpoints.Items { + if endpoint.Name != name { + newList = append(newList, endpoint) + } + } + e.Endpoints.Items = newList + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/registrytest/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/registrytest/etcd.go new file mode 100644 index 000000000..84ae66707 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/registrytest/etcd.go @@ -0,0 +1,223 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 registrytest + +import ( + "fmt" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/rest/resttest" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" + etcdstorage "k8s.io/kubernetes/pkg/storage/etcd" + "k8s.io/kubernetes/pkg/storage/etcd/etcdtest" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + storagetesting "k8s.io/kubernetes/pkg/storage/testing" +) + +func NewEtcdStorage(t *testing.T, group string) (storage.Interface, *etcdtesting.EtcdTestServer) { + server := etcdtesting.NewEtcdTestClientServer(t) + storage := etcdstorage.NewEtcdStorage(server.Client, testapi.Groups[group].Codec(), etcdtest.PathPrefix(), false) + return storage, server +} + +type Tester struct { + tester *resttest.Tester + storage *etcdgeneric.Etcd +} +type UpdateFunc func(runtime.Object) runtime.Object + +func New(t *testing.T, storage *etcdgeneric.Etcd) *Tester { + return &Tester{ + tester: resttest.New(t, storage), + storage: storage, + } +} + +func (t *Tester) TestNamespace() string { + return t.tester.TestNamespace() +} + +func (t *Tester) ClusterScope() *Tester { + t.tester = t.tester.ClusterScope() + return t +} + +func (t *Tester) AllowCreateOnUpdate() *Tester { + t.tester = t.tester.AllowCreateOnUpdate() + return t +} + +func (t *Tester) GeneratesName() *Tester { + t.tester = t.tester.GeneratesName() + return t +} + +func (t *Tester) ReturnDeletedObject() *Tester { + t.tester = t.tester.ReturnDeletedObject() + return t +} + +func (t *Tester) TestCreate(valid runtime.Object, invalid ...runtime.Object) { + t.tester.TestCreate( + valid, + t.setObject, + t.getObject, + invalid..., + ) +} + +func (t *Tester) TestUpdate(valid runtime.Object, validUpdateFunc UpdateFunc, invalidUpdateFunc ...UpdateFunc) { + var invalidFuncs []resttest.UpdateFunc + for _, f := range invalidUpdateFunc { + invalidFuncs = append(invalidFuncs, resttest.UpdateFunc(f)) + } + t.tester.TestUpdate( + valid, + t.setObject, + t.getObject, + resttest.UpdateFunc(validUpdateFunc), + invalidFuncs..., + ) +} + +func (t *Tester) TestDelete(valid runtime.Object) { + t.tester.TestDelete( + valid, + t.setObject, + t.getObject, + errors.IsNotFound, + ) +} + +func (t *Tester) TestDeleteGraceful(valid runtime.Object, expectedGrace int64) { + t.tester.TestDeleteGraceful( + valid, + t.setObject, + t.getObject, + expectedGrace, + ) +} + +func (t *Tester) TestGet(valid runtime.Object) { + t.tester.TestGet(valid) +} + +func (t *Tester) TestList(valid runtime.Object) { + t.tester.TestList( + valid, + t.setObjectsForList, + ) +} + +func (t *Tester) TestWatch(valid runtime.Object, labelsPass, labelsFail []labels.Set, fieldsPass, fieldsFail []fields.Set) { + t.tester.TestWatch( + valid, + t.emitObject, + labelsPass, + labelsFail, + fieldsPass, + fieldsFail, + // TODO: This should be filtered, the registry should not be aware of this level of detail + []string{etcdstorage.EtcdCreate, etcdstorage.EtcdDelete}, + ) +} + +// ============================================================================= +// get codec based on runtime.Object +func getCodec(obj runtime.Object) (runtime.Codec, error) { + fqKind, err := api.Scheme.ObjectKind(obj) + if err != nil { + return nil, fmt.Errorf("unexpected encoding error: %v", err) + } + // TODO: caesarxuchao: we should detect which group an object belongs to + // by using the version returned by Schem.ObjectVersionAndKind() once we + // split the schemes for internal objects. + // TODO: caesarxuchao: we should add a map from kind to group in Scheme. + var codec runtime.Codec + if api.Scheme.Recognizes(testapi.Default.GroupVersion().WithKind(fqKind.Kind)) { + codec = testapi.Default.Codec() + } else if api.Scheme.Recognizes(testapi.Extensions.GroupVersion().WithKind(fqKind.Kind)) { + codec = testapi.Extensions.Codec() + } else { + return nil, fmt.Errorf("unexpected kind: %v", fqKind) + } + return codec, nil +} + +// Helper functions + +func (t *Tester) getObject(ctx api.Context, obj runtime.Object) (runtime.Object, error) { + accessor, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + + result, err := t.storage.Get(ctx, accessor.GetName()) + if err != nil { + return nil, err + } + return result, nil +} + +func (t *Tester) setObject(ctx api.Context, obj runtime.Object) error { + accessor, err := meta.Accessor(obj) + if err != nil { + return err + } + key, err := t.storage.KeyFunc(ctx, accessor.GetName()) + if err != nil { + return err + } + return t.storage.Storage.Set(ctx, key, obj, nil, 0) +} + +func (t *Tester) setObjectsForList(objects []runtime.Object) []runtime.Object { + key := t.storage.KeyRootFunc(t.tester.TestContext()) + if err := storagetesting.CreateObjList(key, t.storage.Storage, objects); err != nil { + t.tester.Errorf("unexpected error: %v", err) + return nil + } + return objects +} + +func (t *Tester) emitObject(obj runtime.Object, action string) error { + ctx := t.tester.TestContext() + var err error + + switch action { + case etcdstorage.EtcdCreate: + err = t.setObject(ctx, obj) + case etcdstorage.EtcdDelete: + accessor, err := meta.Accessor(obj) + if err != nil { + return err + } + _, err = t.storage.Delete(ctx, accessor.GetName(), nil) + default: + err = fmt.Errorf("unexpected action: %v", action) + } + + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/registrytest/node.go b/vendor/k8s.io/kubernetes/pkg/registry/registrytest/node.go new file mode 100644 index 000000000..8f917b9bc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/registrytest/node.go @@ -0,0 +1,115 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 registrytest + +import ( + "sync" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/watch" +) + +// NodeRegistry implements node.Registry interface. +type NodeRegistry struct { + Err error + Node string + Nodes api.NodeList + + sync.Mutex +} + +// MakeNodeList constructs api.NodeList from list of node names and a NodeResource. +func MakeNodeList(nodes []string, nodeResources api.NodeResources) *api.NodeList { + list := api.NodeList{ + Items: make([]api.Node, len(nodes)), + } + for i := range nodes { + list.Items[i].Name = nodes[i] + list.Items[i].Status.Capacity = nodeResources.Capacity + } + return &list +} + +func NewNodeRegistry(nodes []string, nodeResources api.NodeResources) *NodeRegistry { + return &NodeRegistry{ + Nodes: *MakeNodeList(nodes, nodeResources), + } +} + +func (r *NodeRegistry) SetError(err error) { + r.Lock() + defer r.Unlock() + r.Err = err +} + +func (r *NodeRegistry) ListNodes(ctx api.Context, options *api.ListOptions) (*api.NodeList, error) { + r.Lock() + defer r.Unlock() + return &r.Nodes, r.Err +} + +func (r *NodeRegistry) CreateNode(ctx api.Context, node *api.Node) error { + r.Lock() + defer r.Unlock() + r.Node = node.Name + r.Nodes.Items = append(r.Nodes.Items, *node) + return r.Err +} + +func (r *NodeRegistry) UpdateNode(ctx api.Context, node *api.Node) error { + r.Lock() + defer r.Unlock() + for i, item := range r.Nodes.Items { + if item.Name == node.Name { + r.Nodes.Items[i] = *node + return r.Err + } + } + return r.Err +} + +func (r *NodeRegistry) GetNode(ctx api.Context, nodeID string) (*api.Node, error) { + r.Lock() + defer r.Unlock() + if r.Err != nil { + return nil, r.Err + } + for _, node := range r.Nodes.Items { + if node.Name == nodeID { + return &node, nil + } + } + return nil, errors.NewNotFound(api.Resource("nodes"), nodeID) +} + +func (r *NodeRegistry) DeleteNode(ctx api.Context, nodeID string) error { + r.Lock() + defer r.Unlock() + var newList []api.Node + for _, node := range r.Nodes.Items { + if node.Name != nodeID { + newList = append(newList, api.Node{ObjectMeta: api.ObjectMeta{Name: node.Name}}) + } + } + r.Nodes.Items = newList + return r.Err +} + +func (r *NodeRegistry) WatchNodes(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return nil, r.Err +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/registrytest/service.go b/vendor/k8s.io/kubernetes/pkg/registry/registrytest/service.go new file mode 100644 index 000000000..f634d7ffc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/registrytest/service.go @@ -0,0 +1,122 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 registrytest + +import ( + "sync" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/watch" +) + +func NewServiceRegistry() *ServiceRegistry { + return &ServiceRegistry{} +} + +type ServiceRegistry struct { + mu sync.Mutex + List api.ServiceList + Service *api.Service + Updates []api.Service + Err error + + DeletedID string + GottenID string + UpdatedID string +} + +func (r *ServiceRegistry) SetError(err error) { + r.mu.Lock() + defer r.mu.Unlock() + r.Err = err +} + +func (r *ServiceRegistry) ListServices(ctx api.Context, options *api.ListOptions) (*api.ServiceList, error) { + r.mu.Lock() + defer r.mu.Unlock() + + ns, _ := api.NamespaceFrom(ctx) + + // Copy metadata from internal list into result + res := new(api.ServiceList) + res.TypeMeta = r.List.TypeMeta + res.ListMeta = r.List.ListMeta + + if ns != api.NamespaceAll { + for _, service := range r.List.Items { + if ns == service.Namespace { + res.Items = append(res.Items, service) + } + } + } else { + res.Items = append([]api.Service{}, r.List.Items...) + } + + return res, r.Err +} + +func (r *ServiceRegistry) CreateService(ctx api.Context, svc *api.Service) (*api.Service, error) { + r.mu.Lock() + defer r.mu.Unlock() + + r.Service = new(api.Service) + *r.Service = *svc + r.List.Items = append(r.List.Items, *svc) + return svc, r.Err +} + +func (r *ServiceRegistry) GetService(ctx api.Context, id string) (*api.Service, error) { + r.mu.Lock() + defer r.mu.Unlock() + + r.GottenID = id + return r.Service, r.Err +} + +func (r *ServiceRegistry) DeleteService(ctx api.Context, id string) error { + r.mu.Lock() + defer r.mu.Unlock() + + r.DeletedID = id + r.Service = nil + return r.Err +} + +func (r *ServiceRegistry) UpdateService(ctx api.Context, svc *api.Service) (*api.Service, error) { + r.mu.Lock() + defer r.mu.Unlock() + + r.UpdatedID = svc.Name + *r.Service = *svc + r.Updates = append(r.Updates, *svc) + return svc, r.Err +} + +func (r *ServiceRegistry) WatchServices(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + r.mu.Lock() + defer r.mu.Unlock() + + return nil, r.Err +} + +func (r *ServiceRegistry) ExportService(ctx api.Context, name string, options unversioned.ExportOptions) (*api.Service, error) { + r.mu.Lock() + defer r.mu.Lock() + + return r.Service, r.Err +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/replicaset/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/replicaset/doc.go new file mode 100644 index 000000000..ee349fae7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/replicaset/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 replicaset provides Registry interface and it's RESTStorage +// implementation for storing ReplicaSet api objects. +package replicaset diff --git a/vendor/k8s.io/kubernetes/pkg/registry/replicaset/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/replicaset/etcd/etcd.go new file mode 100644 index 000000000..caa254066 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/replicaset/etcd/etcd.go @@ -0,0 +1,196 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// If you make changes to this file, you should also make the corresponding change in ReplicationController. + +package etcd + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/extensions" + extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/replicaset" + "k8s.io/kubernetes/pkg/runtime" +) + +// ReplicaSetStorage includes dummy storage for ReplicaSets and for Scale subresource. +type ReplicaSetStorage struct { + ReplicaSet *REST + Status *StatusREST + Scale *ScaleREST +} + +func NewStorage(opts generic.RESTOptions) ReplicaSetStorage { + replicaSetRest, replicaSetStatusRest := NewREST(opts) + replicaSetRegistry := replicaset.NewRegistry(replicaSetRest) + + return ReplicaSetStorage{ + ReplicaSet: replicaSetRest, + Status: replicaSetStatusRest, + Scale: &ScaleREST{registry: replicaSetRegistry}, + } +} + +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against ReplicaSet. +func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { + prefix := "/replicasets" + + newListFunc := func() runtime.Object { return &extensions.ReplicaSetList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.Replicasets), &extensions.ReplicaSet{}, prefix, replicaset.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &extensions.ReplicaSet{} }, + + // NewListFunc returns an object capable of storing results of an etcd list. + NewListFunc: newListFunc, + // Produces a path that etcd understands, to the root of the resource + // by combining the namespace in the context with the given prefix + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + // Produces a path that etcd understands, to the resource by combining + // the namespace in the context with the given prefix + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + // Retrieve the name field of a ReplicaSet + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*extensions.ReplicaSet).Name, nil + }, + // Used to match objects based on labels/fields for list and watch + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return replicaset.MatchReplicaSet(label, field) + }, + QualifiedResource: api.Resource("replicasets"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + // Used to validate ReplicaSet creation + CreateStrategy: replicaset.Strategy, + + // Used to validate ReplicaSet updates + UpdateStrategy: replicaset.Strategy, + DeleteStrategy: replicaset.Strategy, + + Storage: storageInterface, + } + statusStore := *store + statusStore.UpdateStrategy = replicaset.StatusStrategy + + return &REST{store}, &StatusREST{store: &statusStore} +} + +// StatusREST implements the REST endpoint for changing the status of a ReplicaSet +type StatusREST struct { + store *etcdgeneric.Etcd +} + +func (r *StatusREST) New() runtime.Object { + return &extensions.ReplicaSet{} +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} + +type ScaleREST struct { + registry replicaset.Registry +} + +// ScaleREST implements Patcher +var _ = rest.Patcher(&ScaleREST{}) + +// New creates a new Scale object +func (r *ScaleREST) New() runtime.Object { + return &extensions.Scale{} +} + +func (r *ScaleREST) Get(ctx api.Context, name string) (runtime.Object, error) { + rs, err := r.registry.GetReplicaSet(ctx, name) + if err != nil { + return nil, errors.NewNotFound(extensions.Resource("replicasets/scale"), name) + } + scale, err := scaleFromReplicaSet(rs) + if err != nil { + return nil, errors.NewBadRequest(fmt.Sprintf("%v", err)) + } + return scale, err +} + +func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + if obj == nil { + return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) + } + scale, ok := obj.(*extensions.Scale) + if !ok { + return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj)) + } + + if errs := extvalidation.ValidateScale(scale); len(errs) > 0 { + return nil, false, errors.NewInvalid(extensions.Kind("Scale"), scale.Name, errs) + } + + rs, err := r.registry.GetReplicaSet(ctx, scale.Name) + if err != nil { + return nil, false, errors.NewNotFound(extensions.Resource("replicasets/scale"), scale.Name) + } + rs.Spec.Replicas = scale.Spec.Replicas + rs.ResourceVersion = scale.ResourceVersion + rs, err = r.registry.UpdateReplicaSet(ctx, rs) + if err != nil { + return nil, false, err + } + newScale, err := scaleFromReplicaSet(rs) + if err != nil { + return nil, false, errors.NewBadRequest(fmt.Sprintf("%v", err)) + } + return newScale, false, err +} + +// scaleFromReplicaSet returns a scale subresource for a replica set. +func scaleFromReplicaSet(rs *extensions.ReplicaSet) (*extensions.Scale, error) { + return &extensions.Scale{ + // TODO: Create a variant of ObjectMeta type that only contains the fields below. + ObjectMeta: api.ObjectMeta{ + Name: rs.Name, + Namespace: rs.Namespace, + UID: rs.UID, + ResourceVersion: rs.ResourceVersion, + CreationTimestamp: rs.CreationTimestamp, + }, + Spec: extensions.ScaleSpec{ + Replicas: rs.Spec.Replicas, + }, + Status: extensions.ScaleStatus{ + Replicas: rs.Status.Replicas, + Selector: rs.Spec.Selector, + }, + }, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/replicaset/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/replicaset/etcd/etcd_test.go new file mode 100644 index 000000000..77cb949df --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/replicaset/etcd/etcd_test.go @@ -0,0 +1,357 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage/etcd/etcdtest" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + "k8s.io/kubernetes/pkg/util/diff" +) + +const defaultReplicas = 100 + +func newStorage(t *testing.T) (*ReplicaSetStorage, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "extensions") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + replicaSetStorage := NewStorage(restOptions) + return &replicaSetStorage, server +} + +// createReplicaSet is a helper function that returns a ReplicaSet with the updated resource version. +func createReplicaSet(storage *REST, rs extensions.ReplicaSet, t *testing.T) (extensions.ReplicaSet, error) { + ctx := api.WithNamespace(api.NewContext(), rs.Namespace) + obj, err := storage.Create(ctx, &rs) + if err != nil { + t.Errorf("Failed to create ReplicaSet, %v", err) + } + newRS := obj.(*extensions.ReplicaSet) + return *newRS, nil +} + +func validNewReplicaSet() *extensions.ReplicaSet { + return &extensions.ReplicaSet{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: extensions.ReplicaSetSpec{ + Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"a": "b"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "test", + Image: "test_image", + ImagePullPolicy: api.PullIfNotPresent, + }, + }, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, + }, + Replicas: 7, + }, + Status: extensions.ReplicaSetStatus{ + Replicas: 5, + }, + } +} + +var validReplicaSet = *validNewReplicaSet() + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.ReplicaSet.Etcd) + rs := validNewReplicaSet() + rs.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + rs, + // invalid (invalid selector) + &extensions.ReplicaSet{ + Spec: extensions.ReplicaSetSpec{ + Replicas: 2, + Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{}}, + Template: validReplicaSet.Spec.Template, + }, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.ReplicaSet.Etcd) + test.TestUpdate( + // valid + validNewReplicaSet(), + // valid updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.ReplicaSet) + object.Spec.Replicas = object.Spec.Replicas + 1 + return object + }, + // invalid updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.ReplicaSet) + object.Name = "" + return object + }, + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.ReplicaSet) + object.Spec.Selector = &unversioned.LabelSelector{MatchLabels: map[string]string{}} + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.ReplicaSet.Etcd) + test.TestDelete(validNewReplicaSet()) +} + +func TestGenerationNumber(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + modifiedSno := *validNewReplicaSet() + modifiedSno.Generation = 100 + modifiedSno.Status.ObservedGeneration = 10 + ctx := api.NewDefaultContext() + rs, err := createReplicaSet(storage.ReplicaSet, modifiedSno, t) + etcdRS, err := storage.ReplicaSet.Get(ctx, rs.Name) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + storedRS, _ := etcdRS.(*extensions.ReplicaSet) + + // Generation initialization + if storedRS.Generation != 1 && storedRS.Status.ObservedGeneration != 0 { + t.Fatalf("Unexpected generation number %v, status generation %v", storedRS.Generation, storedRS.Status.ObservedGeneration) + } + + // Updates to spec should increment the generation number + storedRS.Spec.Replicas += 1 + storage.ReplicaSet.Update(ctx, storedRS) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + etcdRS, err = storage.ReplicaSet.Get(ctx, rs.Name) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + storedRS, _ = etcdRS.(*extensions.ReplicaSet) + if storedRS.Generation != 2 || storedRS.Status.ObservedGeneration != 0 { + t.Fatalf("Unexpected generation, spec: %v, status: %v", storedRS.Generation, storedRS.Status.ObservedGeneration) + } + + // Updates to status should not increment either spec or status generation numbers + storedRS.Status.Replicas += 1 + storage.ReplicaSet.Update(ctx, storedRS) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + etcdRS, err = storage.ReplicaSet.Get(ctx, rs.Name) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + storedRS, _ = etcdRS.(*extensions.ReplicaSet) + if storedRS.Generation != 2 || storedRS.Status.ObservedGeneration != 0 { + t.Fatalf("Unexpected generation number, spec: %v, status: %v", storedRS.Generation, storedRS.Status.ObservedGeneration) + } +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.ReplicaSet.Etcd) + test.TestGet(validNewReplicaSet()) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.ReplicaSet.Etcd) + test.TestList(validNewReplicaSet()) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.ReplicaSet.Etcd) + test.TestWatch( + validNewReplicaSet(), + // matching labels + []labels.Set{ + {"a": "b"}, + }, + // not matching labels + []labels.Set{ + {"a": "c"}, + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"status.replicas": "5"}, + {"metadata.name": "foo"}, + {"status.replicas": "5", "metadata.name": "foo"}, + }, + // not matchin fields + []fields.Set{ + {"status.replicas": "10"}, + {"metadata.name": "bar"}, + {"name": "foo"}, + {"status.replicas": "10", "metadata.name": "foo"}, + {"status.replicas": "0", "metadata.name": "bar"}, + }, + ) +} + +func TestScaleGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + + name := "foo" + + var rs extensions.ReplicaSet + ctx := api.WithNamespace(api.NewContext(), api.NamespaceDefault) + key := etcdtest.AddPrefix("/replicasets/" + api.NamespaceDefault + "/" + name) + if err := storage.ReplicaSet.Storage.Set(ctx, key, &validReplicaSet, &rs, 0); err != nil { + t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err) + } + + want := &extensions.Scale{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: api.NamespaceDefault, + UID: rs.UID, + ResourceVersion: rs.ResourceVersion, + CreationTimestamp: rs.CreationTimestamp, + }, + Spec: extensions.ScaleSpec{ + Replicas: validReplicaSet.Spec.Replicas, + }, + Status: extensions.ScaleStatus{ + Replicas: validReplicaSet.Status.Replicas, + Selector: validReplicaSet.Spec.Selector, + }, + } + obj, err := storage.Scale.Get(ctx, name) + got := obj.(*extensions.Scale) + if err != nil { + t.Fatalf("error fetching scale for %s: %v", name, err) + } + if !api.Semantic.DeepEqual(got, want) { + t.Errorf("unexpected scale: %s", diff.ObjectDiff(got, want)) + } +} + +func TestScaleUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + + name := "foo" + + var rs extensions.ReplicaSet + ctx := api.WithNamespace(api.NewContext(), api.NamespaceDefault) + key := etcdtest.AddPrefix("/replicasets/" + api.NamespaceDefault + "/" + name) + if err := storage.ReplicaSet.Storage.Set(ctx, key, &validReplicaSet, &rs, 0); err != nil { + t.Fatalf("error setting new replica set (key: %s) %v: %v", key, validReplicaSet, err) + } + replicas := 12 + update := extensions.Scale{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: api.NamespaceDefault, + }, + Spec: extensions.ScaleSpec{ + Replicas: replicas, + }, + } + + if _, _, err := storage.Scale.Update(ctx, &update); err != nil { + t.Fatalf("error updating scale %v: %v", update, err) + } + + obj, err := storage.Scale.Get(ctx, name) + if err != nil { + t.Fatalf("error fetching scale for %s: %v", name, err) + } + scale := obj.(*extensions.Scale) + if scale.Spec.Replicas != replicas { + t.Errorf("wrong replicas count expected: %d got: %d", replicas, scale.Spec.Replicas) + } + + update.ResourceVersion = rs.ResourceVersion + update.Spec.Replicas = 15 + + if _, _, err = storage.Scale.Update(ctx, &update); err != nil && !errors.IsConflict(err) { + t.Fatalf("unexpected error, expecting an update conflict but got %v", err) + } +} + +func TestStatusUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + + ctx := api.WithNamespace(api.NewContext(), api.NamespaceDefault) + key := etcdtest.AddPrefix("/replicasets/" + api.NamespaceDefault + "/foo") + if err := storage.ReplicaSet.Storage.Set(ctx, key, &validReplicaSet, nil, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + update := extensions.ReplicaSet{ + ObjectMeta: validReplicaSet.ObjectMeta, + Spec: extensions.ReplicaSetSpec{ + Replicas: defaultReplicas, + }, + Status: extensions.ReplicaSetStatus{ + Replicas: defaultReplicas, + }, + } + + if _, _, err := storage.Status.Update(ctx, &update); err != nil { + t.Fatalf("unexpected error: %v", err) + } + obj, err := storage.ReplicaSet.Get(ctx, "foo") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + rs := obj.(*extensions.ReplicaSet) + if rs.Spec.Replicas != 7 { + t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", rs.Spec.Replicas) + } + if rs.Status.Replicas != defaultReplicas { + t.Errorf("we expected .status.replicas to be updated to %d but it was %v", defaultReplicas, rs.Status.Replicas) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/replicaset/registry.go b/vendor/k8s.io/kubernetes/pkg/registry/replicaset/registry.go new file mode 100644 index 000000000..d1279179a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/replicaset/registry.go @@ -0,0 +1,93 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// If you make changes to this file, you should also make the corresponding change in ReplicationController. + +package replicaset + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface for things that know how to store ReplicaSets. +type Registry interface { + ListReplicaSets(ctx api.Context, options *api.ListOptions) (*extensions.ReplicaSetList, error) + WatchReplicaSets(ctx api.Context, options *api.ListOptions) (watch.Interface, error) + GetReplicaSet(ctx api.Context, replicaSetID string) (*extensions.ReplicaSet, error) + CreateReplicaSet(ctx api.Context, replicaSet *extensions.ReplicaSet) (*extensions.ReplicaSet, error) + UpdateReplicaSet(ctx api.Context, replicaSet *extensions.ReplicaSet) (*extensions.ReplicaSet, error) + DeleteReplicaSet(ctx api.Context, replicaSetID string) error +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListReplicaSets(ctx api.Context, options *api.ListOptions) (*extensions.ReplicaSetList, error) { + if options != nil && options.FieldSelector != nil && !options.FieldSelector.Empty() { + return nil, fmt.Errorf("field selector not supported yet") + } + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + return obj.(*extensions.ReplicaSetList), err +} + +func (s *storage) WatchReplicaSets(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetReplicaSet(ctx api.Context, replicaSetID string) (*extensions.ReplicaSet, error) { + obj, err := s.Get(ctx, replicaSetID) + if err != nil { + return nil, err + } + return obj.(*extensions.ReplicaSet), nil +} + +func (s *storage) CreateReplicaSet(ctx api.Context, replicaSet *extensions.ReplicaSet) (*extensions.ReplicaSet, error) { + obj, err := s.Create(ctx, replicaSet) + if err != nil { + return nil, err + } + return obj.(*extensions.ReplicaSet), nil +} + +func (s *storage) UpdateReplicaSet(ctx api.Context, replicaSet *extensions.ReplicaSet) (*extensions.ReplicaSet, error) { + obj, _, err := s.Update(ctx, replicaSet) + if err != nil { + return nil, err + } + return obj.(*extensions.ReplicaSet), nil +} + +func (s *storage) DeleteReplicaSet(ctx api.Context, replicaSetID string) error { + _, err := s.Delete(ctx, replicaSetID, nil) + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/replicaset/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/replicaset/strategy.go new file mode 100644 index 000000000..b7f7f54f7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/replicaset/strategy.go @@ -0,0 +1,146 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// If you make changes to this file, you should also make the corresponding change in ReplicationController. + +package replicaset + +import ( + "fmt" + "reflect" + "strconv" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/extensions/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// rsStrategy implements verification logic for ReplicaSets. +type rsStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating ReplicaSet objects. +var Strategy = rsStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped returns true because all ReplicaSets need to be within a namespace. +func (rsStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears the status of a ReplicaSet before creation. +func (rsStrategy) PrepareForCreate(obj runtime.Object) { + rs := obj.(*extensions.ReplicaSet) + rs.Status = extensions.ReplicaSetStatus{} + + rs.Generation = 1 +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (rsStrategy) PrepareForUpdate(obj, old runtime.Object) { + newRS := obj.(*extensions.ReplicaSet) + oldRS := old.(*extensions.ReplicaSet) + // update is not allowed to set status + newRS.Status = oldRS.Status + + // Any changes to the spec increment the generation number, any changes to the + // status should reflect the generation number of the corresponding object. We push + // the burden of managing the status onto the clients because we can't (in general) + // know here what version of spec the writer of the status has seen. It may seem like + // we can at first -- since obj contains spec -- but in the future we will probably make + // status its own object, and even if we don't, writes may be the result of a + // read-update-write loop, so the contents of spec may not actually be the spec that + // the ReplicaSet has *seen*. + if !reflect.DeepEqual(oldRS.Spec, newRS.Spec) { + newRS.Generation = oldRS.Generation + 1 + } +} + +// Validate validates a new ReplicaSet. +func (rsStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + rs := obj.(*extensions.ReplicaSet) + return validation.ValidateReplicaSet(rs) +} + +// Canonicalize normalizes the object after validation. +func (rsStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is false for ReplicaSets; this means a POST is +// needed to create one. +func (rsStrategy) AllowCreateOnUpdate() bool { + return false +} + +// ValidateUpdate is the default update validation for an end user. +func (rsStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + validationErrorList := validation.ValidateReplicaSet(obj.(*extensions.ReplicaSet)) + updateErrorList := validation.ValidateReplicaSetUpdate(obj.(*extensions.ReplicaSet), old.(*extensions.ReplicaSet)) + return append(validationErrorList, updateErrorList...) +} + +func (rsStrategy) AllowUnconditionalUpdate() bool { + return true +} + +// ReplicaSetToSelectableFields returns a field set that represents the object. +func ReplicaSetToSelectableFields(rs *extensions.ReplicaSet) fields.Set { + objectMetaFieldsSet := generic.ObjectMetaFieldsSet(rs.ObjectMeta, true) + rsSpecificFieldsSet := fields.Set{ + "status.replicas": strconv.Itoa(rs.Status.Replicas), + } + return generic.MergeFieldsSets(objectMetaFieldsSet, rsSpecificFieldsSet) +} + +// MatchReplicaSet is the filter used by the generic etcd backend to route +// watch events from etcd to clients of the apiserver only interested in specific +// labels/fields. +func MatchReplicaSet(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + rs, ok := obj.(*extensions.ReplicaSet) + if !ok { + return nil, nil, fmt.Errorf("Given object is not a ReplicaSet.") + } + return labels.Set(rs.ObjectMeta.Labels), ReplicaSetToSelectableFields(rs), nil + }, + } +} + +type rsStatusStrategy struct { + rsStrategy +} + +var StatusStrategy = rsStatusStrategy{Strategy} + +func (rsStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newRS := obj.(*extensions.ReplicaSet) + oldRS := old.(*extensions.ReplicaSet) + // update is not allowed to set spec + newRS.Spec = oldRS.Spec +} + +func (rsStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateReplicaSetStatusUpdate(obj.(*extensions.ReplicaSet), old.(*extensions.ReplicaSet)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/replicaset/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/replicaset/strategy_test.go new file mode 100644 index 000000000..e4fea3c39 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/replicaset/strategy_test.go @@ -0,0 +1,142 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 replicaset + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestReplicaSetStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if !Strategy.NamespaceScoped() { + t.Errorf("ReplicaSet must be namespace scoped") + } + if Strategy.AllowCreateOnUpdate() { + t.Errorf("ReplicaSet should not allow create on update") + } + + validSelector := map[string]string{"a": "b"} + validPodTemplate := api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validSelector, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + }, + } + rs := &extensions.ReplicaSet{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: extensions.ReplicaSetSpec{ + Selector: &unversioned.LabelSelector{MatchLabels: validSelector}, + Template: validPodTemplate.Template, + }, + Status: extensions.ReplicaSetStatus{ + Replicas: 1, + ObservedGeneration: int64(10), + }, + } + + Strategy.PrepareForCreate(rs) + if rs.Status.Replicas != 0 { + t.Error("ReplicaSet should not allow setting status.replicas on create") + } + if rs.Status.ObservedGeneration != int64(0) { + t.Error("ReplicaSet should not allow setting status.observedGeneration on create") + } + errs := Strategy.Validate(ctx, rs) + if len(errs) != 0 { + t.Errorf("Unexpected error validating %v", errs) + } + + invalidRc := &extensions.ReplicaSet{ + ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "4"}, + } + Strategy.PrepareForUpdate(invalidRc, rs) + errs = Strategy.ValidateUpdate(ctx, invalidRc, rs) + if len(errs) == 0 { + t.Errorf("Expected a validation error") + } + if invalidRc.ResourceVersion != "4" { + t.Errorf("Incoming resource version on update should not be mutated") + } +} + +func TestReplicaSetStatusStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if !StatusStrategy.NamespaceScoped() { + t.Errorf("ReplicaSet must be namespace scoped") + } + if StatusStrategy.AllowCreateOnUpdate() { + t.Errorf("ReplicaSet should not allow create on update") + } + validSelector := map[string]string{"a": "b"} + validPodTemplate := api.PodTemplate{ + Template: api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: validSelector, + }, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}}, + }, + }, + } + oldRS := &extensions.ReplicaSet{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault, ResourceVersion: "10"}, + Spec: extensions.ReplicaSetSpec{ + Replicas: 3, + Selector: &unversioned.LabelSelector{MatchLabels: validSelector}, + Template: validPodTemplate.Template, + }, + Status: extensions.ReplicaSetStatus{ + Replicas: 1, + ObservedGeneration: int64(10), + }, + } + newRS := &extensions.ReplicaSet{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault, ResourceVersion: "9"}, + Spec: extensions.ReplicaSetSpec{ + Replicas: 1, + Selector: &unversioned.LabelSelector{MatchLabels: validSelector}, + Template: validPodTemplate.Template, + }, + Status: extensions.ReplicaSetStatus{ + Replicas: 3, + ObservedGeneration: int64(11), + }, + } + StatusStrategy.PrepareForUpdate(newRS, oldRS) + if newRS.Status.Replicas != 3 { + t.Errorf("ReplicaSet status updates should allow change of replicas: %v", newRS.Status.Replicas) + } + if newRS.Spec.Replicas != 3 { + t.Errorf("PrepareForUpdate should have preferred spec") + } + errs := StatusStrategy.ValidateUpdate(ctx, newRS, oldRS) + if len(errs) != 0 { + t.Errorf("Unexpected error %v", errs) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/doc.go new file mode 100644 index 000000000..91ec69fcc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 resourcequota provides Registry interface and it's REST +// implementation for storing ResourceQuota api objects. +package resourcequota diff --git a/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/etcd/etcd.go new file mode 100644 index 000000000..413228197 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/etcd/etcd.go @@ -0,0 +1,86 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/resourcequota" + "k8s.io/kubernetes/pkg/runtime" +) + +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against resource quotas. +func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { + prefix := "/resourcequotas" + + newListFunc := func() runtime.Object { return &api.ResourceQuotaList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.ResourceQuotas), &api.ResourceQuota{}, prefix, resourcequota.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.ResourceQuota{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.ResourceQuota).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return resourcequota.MatchResourceQuota(label, field) + }, + QualifiedResource: api.Resource("resourcequotas"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: resourcequota.Strategy, + UpdateStrategy: resourcequota.Strategy, + DeleteStrategy: resourcequota.Strategy, + ReturnDeletedObject: true, + + Storage: storageInterface, + } + + statusStore := *store + statusStore.UpdateStrategy = resourcequota.StatusStrategy + + return &REST{store}, &StatusREST{store: &statusStore} +} + +// StatusREST implements the REST endpoint for changing the status of a resourcequota. +type StatusREST struct { + store *etcdgeneric.Etcd +} + +func (r *StatusREST) New() runtime.Object { + return &api.ResourceQuota{} +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/etcd/etcd_test.go new file mode 100644 index 000000000..6b9e5c10a --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/etcd/etcd_test.go @@ -0,0 +1,190 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/storage/etcd/etcdtest" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + "k8s.io/kubernetes/pkg/util/diff" +) + +func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + resourceQuotaStorage, statusStorage := NewREST(restOptions) + return resourceQuotaStorage, statusStorage, server +} + +func validNewResourceQuota() *api.ResourceQuota { + return &api.ResourceQuota{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: api.ResourceQuotaSpec{ + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100"), + api.ResourceMemory: resource.MustParse("4Gi"), + api.ResourcePods: resource.MustParse("10"), + api.ResourceServices: resource.MustParse("10"), + api.ResourceReplicationControllers: resource.MustParse("10"), + api.ResourceQuotas: resource.MustParse("1"), + }, + }, + } +} + +func TestCreate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + resourcequota := validNewResourceQuota() + resourcequota.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + resourcequota, + // invalid + &api.ResourceQuota{ + ObjectMeta: api.ObjectMeta{Name: "_-a123-a_"}, + }, + ) +} + +func TestCreateSetsFields(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + ctx := api.NewDefaultContext() + resourcequota := validNewResourceQuota() + _, err := storage.Create(api.NewDefaultContext(), resourcequota) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + object, err := storage.Get(ctx, "foo") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + actual := object.(*api.ResourceQuota) + if actual.Name != resourcequota.Name { + t.Errorf("unexpected resourcequota: %#v", actual) + } + if len(actual.UID) == 0 { + t.Errorf("expected resourcequota UID to be set: %#v", actual) + } +} + +func TestDelete(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ReturnDeletedObject() + test.TestDelete(validNewResourceQuota()) +} + +func TestGet(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(validNewResourceQuota()) +} + +func TestList(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(validNewResourceQuota()) +} + +func TestWatch(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validNewResourceQuota(), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": "foo"}, + }, + // not matchin fields + []fields.Set{ + {"metadata.name": "bar"}, + }, + ) +} + +func TestUpdateStatus(t *testing.T) { + storage, status, server := newStorage(t) + defer server.Terminate(t) + ctx := api.NewDefaultContext() + + key, _ := storage.KeyFunc(ctx, "foo") + key = etcdtest.AddPrefix(key) + resourcequotaStart := validNewResourceQuota() + err := storage.Storage.Set(ctx, key, resourcequotaStart, nil, 0) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + resourcequotaIn := &api.ResourceQuota{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Status: api.ResourceQuotaStatus{ + Used: api.ResourceList{ + api.ResourceCPU: resource.MustParse("1"), + api.ResourceMemory: resource.MustParse("1Gi"), + api.ResourcePods: resource.MustParse("1"), + api.ResourceServices: resource.MustParse("1"), + api.ResourceReplicationControllers: resource.MustParse("1"), + api.ResourceQuotas: resource.MustParse("1"), + }, + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100"), + api.ResourceMemory: resource.MustParse("4Gi"), + api.ResourcePods: resource.MustParse("10"), + api.ResourceServices: resource.MustParse("10"), + api.ResourceReplicationControllers: resource.MustParse("10"), + api.ResourceQuotas: resource.MustParse("1"), + }, + }, + } + + _, _, err = status.Update(ctx, resourcequotaIn) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + obj, err := storage.Get(ctx, "foo") + rqOut := obj.(*api.ResourceQuota) + // only compare the meaningful update b/c we can't compare due to metadata + if !api.Semantic.DeepEqual(resourcequotaIn.Status, rqOut.Status) { + t.Errorf("unexpected object: %s", diff.ObjectDiff(resourcequotaIn, rqOut)) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/strategy.go new file mode 100644 index 000000000..656d5cecb --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/strategy.go @@ -0,0 +1,115 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 resourcequota + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// resourcequotaStrategy implements behavior for ResourceQuota objects +type resourcequotaStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating ResourceQuota +// objects via the REST API. +var Strategy = resourcequotaStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped is true for resourcequotas. +func (resourcequotaStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears fields that are not allowed to be set by end users on creation. +func (resourcequotaStrategy) PrepareForCreate(obj runtime.Object) { + resourcequota := obj.(*api.ResourceQuota) + resourcequota.Status = api.ResourceQuotaStatus{} +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (resourcequotaStrategy) PrepareForUpdate(obj, old runtime.Object) { + newResourcequota := obj.(*api.ResourceQuota) + oldResourcequota := old.(*api.ResourceQuota) + newResourcequota.Status = oldResourcequota.Status +} + +// Validate validates a new resourcequota. +func (resourcequotaStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + resourcequota := obj.(*api.ResourceQuota) + return validation.ValidateResourceQuota(resourcequota) +} + +// Canonicalize normalizes the object after validation. +func (resourcequotaStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is false for resourcequotas. +func (resourcequotaStrategy) AllowCreateOnUpdate() bool { + return false +} + +// ValidateUpdate is the default update validation for an end user. +func (resourcequotaStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + errorList := validation.ValidateResourceQuota(obj.(*api.ResourceQuota)) + return append(errorList, validation.ValidateResourceQuotaUpdate(obj.(*api.ResourceQuota), old.(*api.ResourceQuota))...) +} + +func (resourcequotaStrategy) AllowUnconditionalUpdate() bool { + return true +} + +type resourcequotaStatusStrategy struct { + resourcequotaStrategy +} + +var StatusStrategy = resourcequotaStatusStrategy{Strategy} + +func (resourcequotaStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newResourcequota := obj.(*api.ResourceQuota) + oldResourcequota := old.(*api.ResourceQuota) + newResourcequota.Spec = oldResourcequota.Spec +} + +func (resourcequotaStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateResourceQuotaStatusUpdate(obj.(*api.ResourceQuota), old.(*api.ResourceQuota)) +} + +// MatchResourceQuota returns a generic matcher for a given label and field selector. +func MatchResourceQuota(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + resourcequotaObj, ok := obj.(*api.ResourceQuota) + if !ok { + return false, fmt.Errorf("not a resourcequota") + } + fields := ResourceQuotaToSelectableFields(resourcequotaObj) + return label.Matches(labels.Set(resourcequotaObj.Labels)) && field.Matches(fields), nil + }) +} + +// ResourceQuotaToSelectableFields returns a label set that represents the object +func ResourceQuotaToSelectableFields(resourcequota *api.ResourceQuota) labels.Set { + return labels.Set(generic.ObjectMetaFieldsSet(resourcequota.ObjectMeta, true)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/strategy_test.go new file mode 100644 index 000000000..9bc8a70ad --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/resourcequota/strategy_test.go @@ -0,0 +1,69 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 resourcequota + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" +) + +func TestResourceQuotaStrategy(t *testing.T) { + if !Strategy.NamespaceScoped() { + t.Errorf("ResourceQuota should be namespace scoped") + } + if Strategy.AllowCreateOnUpdate() { + t.Errorf("ResourceQuota should not allow create on update") + } + resourceQuota := &api.ResourceQuota{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Status: api.ResourceQuotaStatus{ + Used: api.ResourceList{ + api.ResourceCPU: resource.MustParse("1"), + api.ResourceMemory: resource.MustParse("1Gi"), + api.ResourcePods: resource.MustParse("1"), + api.ResourceServices: resource.MustParse("1"), + api.ResourceReplicationControllers: resource.MustParse("1"), + api.ResourceQuotas: resource.MustParse("1"), + }, + Hard: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100"), + api.ResourceMemory: resource.MustParse("4Gi"), + api.ResourcePods: resource.MustParse("10"), + api.ResourceServices: resource.MustParse("10"), + api.ResourceReplicationControllers: resource.MustParse("10"), + api.ResourceQuotas: resource.MustParse("1"), + }, + }, + } + Strategy.PrepareForCreate(resourceQuota) + if resourceQuota.Status.Used != nil { + t.Errorf("ResourceQuota does not allow setting status on create") + } +} + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "ResourceQuota", + ResourceQuotaToSelectableFields(&api.ResourceQuota{}), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/secret/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/secret/doc.go new file mode 100644 index 000000000..0f3c2c720 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/secret/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 secrets provides Registry interface and its REST +// implementation for storing Secret api objects. +package secret diff --git a/vendor/k8s.io/kubernetes/pkg/registry/secret/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/secret/etcd/etcd.go new file mode 100644 index 000000000..36cc252be --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/secret/etcd/etcd.go @@ -0,0 +1,67 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/secret" + "k8s.io/kubernetes/pkg/runtime" +) + +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against secrets. +func NewREST(opts generic.RESTOptions) *REST { + prefix := "/secrets" + + newListFunc := func() runtime.Object { return &api.SecretList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.Secrets), &api.Secret{}, prefix, secret.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.Secret{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, id string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, id) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.Secret).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return secret.Matcher(label, field) + }, + QualifiedResource: api.Resource("secrets"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: secret.Strategy, + UpdateStrategy: secret.Strategy, + DeleteStrategy: secret.Strategy, + + Storage: storageInterface, + } + return &REST{store} +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/secret/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/secret/etcd/etcd_test.go new file mode 100644 index 000000000..9015b2bf3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/secret/etcd/etcd_test.go @@ -0,0 +1,128 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + return NewREST(restOptions), server +} + +func validNewSecret(name string) *api.Secret { + return &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: api.NamespaceDefault, + }, + Data: map[string][]byte{ + "test": []byte("data"), + }, + } +} + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + secret := validNewSecret("foo") + secret.ObjectMeta = api.ObjectMeta{GenerateName: "foo-"} + test.TestCreate( + // valid + secret, + // invalid + &api.Secret{}, + &api.Secret{ + ObjectMeta: api.ObjectMeta{Name: "name"}, + Data: map[string][]byte{"name with spaces": []byte("")}, + }, + &api.Secret{ + ObjectMeta: api.ObjectMeta{Name: "name"}, + Data: map[string][]byte{"~.dotfile": []byte("")}, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestUpdate( + // valid + validNewSecret("foo"), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*api.Secret) + object.Data["othertest"] = []byte("otherdata") + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestDelete(validNewSecret("foo")) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(validNewSecret("foo")) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(validNewSecret("foo")) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validNewSecret("foo"), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{}, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + {"name": "foo"}, + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/secret/registry.go b/vendor/k8s.io/kubernetes/pkg/registry/secret/registry.go new file mode 100644 index 000000000..ef989f006 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/secret/registry.go @@ -0,0 +1,79 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 secret + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface implemented by things that know how to store Secret objects. +type Registry interface { + ListSecrets(ctx api.Context, options *api.ListOptions) (*api.SecretList, error) + WatchSecrets(ctx api.Context, options *api.ListOptions) (watch.Interface, error) + GetSecret(ctx api.Context, name string) (*api.Secret, error) + CreateSecret(ctx api.Context, Secret *api.Secret) (*api.Secret, error) + UpdateSecret(ctx api.Context, Secret *api.Secret) (*api.Secret, error) + DeleteSecret(ctx api.Context, name string) error +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListSecrets(ctx api.Context, options *api.ListOptions) (*api.SecretList, error) { + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + return obj.(*api.SecretList), nil +} + +func (s *storage) WatchSecrets(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetSecret(ctx api.Context, name string) (*api.Secret, error) { + obj, err := s.Get(ctx, name) + if err != nil { + return nil, err + } + return obj.(*api.Secret), nil +} + +func (s *storage) CreateSecret(ctx api.Context, secret *api.Secret) (*api.Secret, error) { + obj, err := s.Create(ctx, secret) + return obj.(*api.Secret), err +} + +func (s *storage) UpdateSecret(ctx api.Context, secret *api.Secret) (*api.Secret, error) { + obj, _, err := s.Update(ctx, secret) + return obj.(*api.Secret), err +} + +func (s *storage) DeleteSecret(ctx api.Context, name string) error { + _, err := s.Delete(ctx, name, nil) + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/secret/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/secret/strategy.go new file mode 100644 index 000000000..d06c86b33 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/secret/strategy.go @@ -0,0 +1,115 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 secret + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// strategy implements behavior for Secret objects +type strategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating Secret +// objects via the REST API. +var Strategy = strategy{api.Scheme, api.SimpleNameGenerator} + +var _ = rest.RESTCreateStrategy(Strategy) + +var _ = rest.RESTUpdateStrategy(Strategy) + +func (strategy) NamespaceScoped() bool { + return true +} + +func (strategy) PrepareForCreate(obj runtime.Object) { +} + +func (strategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + return validation.ValidateSecret(obj.(*api.Secret)) +} + +func (strategy) Canonicalize(obj runtime.Object) { +} + +func (strategy) AllowCreateOnUpdate() bool { + return false +} + +func (strategy) PrepareForUpdate(obj, old runtime.Object) { +} + +func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateSecretUpdate(obj.(*api.Secret), old.(*api.Secret)) +} + +func (strategy) AllowUnconditionalUpdate() bool { + return true +} + +func (s strategy) Export(obj runtime.Object, exact bool) error { + t, ok := obj.(*api.Secret) + if !ok { + // unexpected programmer error + return fmt.Errorf("unexpected object: %v", obj) + } + s.PrepareForCreate(obj) + if exact { + return nil + } + // secrets that are tied to the UID of a service account cannot be exported anyway + if t.Type == api.SecretTypeServiceAccountToken || len(t.Annotations[api.ServiceAccountUIDKey]) > 0 { + errs := []*field.Error{ + field.Invalid(field.NewPath("type"), t, "can not export service account secrets"), + } + return errors.NewInvalid(api.Kind("Secret"), t.Name, errs) + } + return nil +} + +// Matcher returns a generic matcher for a given label and field selector. +func Matcher(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + sa, ok := obj.(*api.Secret) + if !ok { + return false, fmt.Errorf("not a secret") + } + fields := SelectableFields(sa) + return label.Matches(labels.Set(sa.Labels)) && field.Matches(fields), nil + }) +} + +// SelectableFields returns a label set that can be used for filter selection +func SelectableFields(obj *api.Secret) labels.Set { + objectMetaFieldsSet := generic.ObjectMetaFieldsSet(obj.ObjectMeta, true) + secretSpecificFieldsSet := fields.Set{ + "type": string(obj.Type), + } + return labels.Set(generic.MergeFieldsSets(objectMetaFieldsSet, secretSpecificFieldsSet)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/secret/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/secret/strategy_test.go new file mode 100644 index 000000000..c9f2afdf6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/secret/strategy_test.go @@ -0,0 +1,110 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 secret + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/runtime" +) + +func TestExportSecret(t *testing.T) { + tests := []struct { + objIn runtime.Object + objOut runtime.Object + exact bool + expectErr bool + }{ + { + objIn: &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "bar", + }, + Data: map[string][]byte{ + "foo": []byte("bar"), + }, + }, + objOut: &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "bar", + }, + Data: map[string][]byte{ + "foo": []byte("bar"), + }, + }, + exact: true, + }, + { + objIn: &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "bar", + }, + Type: api.SecretTypeServiceAccountToken, + }, + expectErr: true, + }, + { + objIn: &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "bar", + Annotations: map[string]string{ + api.ServiceAccountUIDKey: "true", + }, + }, + }, + expectErr: true, + }, + { + objIn: &api.Pod{}, + expectErr: true, + }, + } + + for _, test := range tests { + err := Strategy.Export(test.objIn, test.exact) + if err != nil { + if !test.expectErr { + t.Errorf("unexpected error: %v", err) + } + continue + } + if test.expectErr { + t.Error("unexpected non-error") + continue + } + if !reflect.DeepEqual(test.objIn, test.objOut) { + t.Errorf("expected:\n%v\nsaw:\n%v\n", test.objOut, test.objIn) + } + } +} + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "Secret", + SelectableFields(&api.Secret{}), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/bitmap.go b/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/bitmap.go new file mode 100644 index 000000000..e90f396a6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/bitmap.go @@ -0,0 +1,194 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 allocator + +import ( + "errors" + "math/big" + "math/rand" + "sync" +) + +// AllocationBitmap is a contiguous block of resources that can be allocated atomically. +// +// Each resource has an offset. The internal structure is a bitmap, with a bit for each offset. +// +// If a resource is taken, the bit at that offset is set to one. +// r.count is always equal to the number of set bits and can be recalculated at any time +// by counting the set bits in r.allocated. +// +// TODO: use RLE and compact the allocator to minimize space. +type AllocationBitmap struct { + // strategy is the strategy for choosing the next available item out of the range + strategy allocateStrategy + // max is the maximum size of the usable items in the range + max int + // rangeSpec is the range specifier, matching RangeAllocation.Range + rangeSpec string + + // lock guards the following members + lock sync.Mutex + // count is the number of currently allocated elements in the range + count int + // allocated is a bit array of the allocated items in the range + allocated *big.Int +} + +// AllocationBitmap implements Interface and Snapshottable +var _ Interface = &AllocationBitmap{} +var _ Snapshottable = &AllocationBitmap{} + +// allocateStrategy is a search strategy in the allocation map for a valid item. +type allocateStrategy func(allocated *big.Int, max, count int) (int, bool) + +// NewAllocationMap creates an allocation bitmap using the random scan strategy. +func NewAllocationMap(max int, rangeSpec string) *AllocationBitmap { + a := AllocationBitmap{ + strategy: randomScanStrategy, + allocated: big.NewInt(0), + count: 0, + max: max, + rangeSpec: rangeSpec, + } + return &a +} + +// NewContiguousAllocationMap creates an allocation bitmap using the contiguous scan strategy. +func NewContiguousAllocationMap(max int, rangeSpec string) *AllocationBitmap { + a := AllocationBitmap{ + strategy: contiguousScanStrategy, + allocated: big.NewInt(0), + count: 0, + max: max, + rangeSpec: rangeSpec, + } + return &a +} + +// Allocate attempts to reserve the provided item. +// Returns true if it was allocated, false if it was already in use +func (r *AllocationBitmap) Allocate(offset int) (bool, error) { + r.lock.Lock() + defer r.lock.Unlock() + + if r.allocated.Bit(offset) == 1 { + return false, nil + } + r.allocated = r.allocated.SetBit(r.allocated, offset, 1) + r.count++ + return true, nil +} + +// AllocateNext reserves one of the items from the pool. +// (0, false, nil) may be returned if there are no items left. +func (r *AllocationBitmap) AllocateNext() (int, bool, error) { + r.lock.Lock() + defer r.lock.Unlock() + + next, ok := r.strategy(r.allocated, r.max, r.count) + if !ok { + return 0, false, nil + } + r.count++ + r.allocated = r.allocated.SetBit(r.allocated, next, 1) + return next, true, nil +} + +// Release releases the item back to the pool. Releasing an +// unallocated item or an item out of the range is a no-op and +// returns no error. +func (r *AllocationBitmap) Release(offset int) error { + r.lock.Lock() + defer r.lock.Unlock() + + if r.allocated.Bit(offset) == 0 { + return nil + } + + r.allocated = r.allocated.SetBit(r.allocated, offset, 0) + r.count-- + return nil +} + +// Has returns true if the provided item is already allocated and a call +// to Allocate(offset) would fail. +func (r *AllocationBitmap) Has(offset int) bool { + r.lock.Lock() + defer r.lock.Unlock() + + return r.allocated.Bit(offset) == 1 +} + +// Free returns the count of items left in the range. +func (r *AllocationBitmap) Free() int { + r.lock.Lock() + defer r.lock.Unlock() + return r.max - r.count +} + +// Snapshot saves the current state of the pool. +func (r *AllocationBitmap) Snapshot() (string, []byte) { + r.lock.Lock() + defer r.lock.Unlock() + + return r.rangeSpec, r.allocated.Bytes() +} + +// Restore restores the pool to the previously captured state. +func (r *AllocationBitmap) Restore(rangeSpec string, data []byte) error { + r.lock.Lock() + defer r.lock.Unlock() + + if r.rangeSpec != rangeSpec { + return errors.New("the provided range does not match the current range") + } + + r.allocated = big.NewInt(0).SetBytes(data) + r.count = countBits(r.allocated) + + return nil +} + +// randomScanStrategy chooses a random address from the provided big.Int, and then +// scans forward looking for the next available address (it will wrap the range if +// necessary). +func randomScanStrategy(allocated *big.Int, max, count int) (int, bool) { + if count >= max { + return 0, false + } + offset := rand.Intn(max) + for i := 0; i < max; i++ { + at := (offset + i) % max + if allocated.Bit(at) == 0 { + return at, true + } + } + return 0, false +} + +// contiguousScanStrategy tries to allocate starting at 0 and filling in any gaps +func contiguousScanStrategy(allocated *big.Int, max, count int) (int, bool) { + if count >= max { + return 0, false + } + for i := 0; i < max; i++ { + if allocated.Bit(i) == 0 { + return i, true + } + } + return 0, false +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/bitmap_test.go b/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/bitmap_test.go new file mode 100644 index 000000000..87e145e94 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/bitmap_test.go @@ -0,0 +1,124 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 allocator + +import ( + "testing" +) + +func TestAllocate(t *testing.T) { + max := 10 + m := NewAllocationMap(max, "test") + + if _, ok, _ := m.AllocateNext(); !ok { + t.Fatalf("unexpected error") + } + if m.count != 1 { + t.Errorf("expect to get %d, but got %d", 1, m.count) + } + if f := m.Free(); f != max-1 { + t.Errorf("expect to get %d, but got %d", max-1, f) + } +} + +func TestAllocateMax(t *testing.T) { + max := 10 + m := NewAllocationMap(max, "test") + for i := 0; i < max; i++ { + if _, ok, _ := m.AllocateNext(); !ok { + t.Fatalf("unexpected error") + } + } + + if _, ok, _ := m.AllocateNext(); ok { + t.Errorf("unexpected success") + } + if f := m.Free(); f != 0 { + t.Errorf("expect to get %d, but got %d", 0, f) + } +} + +func TestAllocateError(t *testing.T) { + m := NewAllocationMap(10, "test") + if ok, _ := m.Allocate(3); !ok { + t.Errorf("error allocate offset %v", 3) + } + if ok, _ := m.Allocate(3); ok { + t.Errorf("unexpected success") + } +} + +func TestRelease(t *testing.T) { + offset := 3 + m := NewAllocationMap(10, "test") + if ok, _ := m.Allocate(offset); !ok { + t.Errorf("error allocate offset %v", offset) + } + + if !m.Has(offset) { + t.Errorf("expect offset %v allocated", offset) + } + + if err := m.Release(offset); err != nil { + t.Errorf("unexpected error: %v", err) + } + + if m.Has(offset) { + t.Errorf("expect offset %v not allocated", offset) + } +} + +func TestSnapshotAndRestore(t *testing.T) { + offset := 3 + m := NewAllocationMap(10, "test") + if ok, _ := m.Allocate(offset); !ok { + t.Errorf("error allocate offset %v", offset) + } + spec, bytes := m.Snapshot() + + m2 := NewAllocationMap(10, "test") + err := m2.Restore(spec, bytes) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if m2.count != 1 { + t.Errorf("expect count to %d, but got %d", 0, m.count) + } + if !m2.Has(offset) { + t.Errorf("expect offset %v allocated", offset) + } +} + +func TestContiguousAllocation(t *testing.T) { + max := 10 + m := NewContiguousAllocationMap(max, "test") + + for i := 0; i < max; i++ { + next, ok, _ := m.AllocateNext() + if !ok { + t.Fatalf("unexpected error") + } + if next != i { + t.Fatalf("expect next to %d, but got %d", i, next) + } + } + + if _, ok, _ := m.AllocateNext(); ok { + t.Errorf("unexpected success") + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/etcd/etcd.go new file mode 100644 index 000000000..2c1c74984 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/etcd/etcd.go @@ -0,0 +1,242 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "errors" + "fmt" + "sync" + + "k8s.io/kubernetes/pkg/api" + k8serr "k8s.io/kubernetes/pkg/api/errors" + storeerr "k8s.io/kubernetes/pkg/api/errors/storage" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/registry/service" + "k8s.io/kubernetes/pkg/registry/service/allocator" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" + + "golang.org/x/net/context" +) + +var ( + errorUnableToAllocate = errors.New("unable to allocate") +) + +// Etcd exposes a service.Allocator that is backed by etcd. +// TODO: allow multiple allocations to be tried at once +// TODO: subdivide the keyspace to reduce conflicts +// TODO: investigate issuing a CAS without reading first +type Etcd struct { + lock sync.Mutex + + alloc allocator.Snapshottable + storage storage.Interface + last string + + baseKey string + resource unversioned.GroupResource +} + +// Etcd implements allocator.Interface and service.RangeRegistry +var _ allocator.Interface = &Etcd{} +var _ service.RangeRegistry = &Etcd{} + +// NewEtcd returns an allocator that is backed by Etcd and can manage +// persisting the snapshot state of allocation after each allocation is made. +func NewEtcd(alloc allocator.Snapshottable, baseKey string, resource unversioned.GroupResource, storage storage.Interface) *Etcd { + return &Etcd{ + alloc: alloc, + storage: storage, + baseKey: baseKey, + resource: resource, + } +} + +// Allocate attempts to allocate the item locally and then in etcd. +func (e *Etcd) Allocate(offset int) (bool, error) { + e.lock.Lock() + defer e.lock.Unlock() + + ok, err := e.alloc.Allocate(offset) + if !ok || err != nil { + return ok, err + } + + err = e.tryUpdate(func() error { + ok, err := e.alloc.Allocate(offset) + if err != nil { + return err + } + if !ok { + return errorUnableToAllocate + } + return nil + }) + if err != nil { + if err == errorUnableToAllocate { + return false, nil + } + return false, err + } + return true, nil +} + +// AllocateNext attempts to allocate the next item locally and then in etcd. +func (e *Etcd) AllocateNext() (int, bool, error) { + e.lock.Lock() + defer e.lock.Unlock() + + offset, ok, err := e.alloc.AllocateNext() + if !ok || err != nil { + return offset, ok, err + } + + err = e.tryUpdate(func() error { + ok, err := e.alloc.Allocate(offset) + if err != nil { + return err + } + if !ok { + // update the offset here + offset, ok, err = e.alloc.AllocateNext() + if err != nil { + return err + } + if !ok { + return errorUnableToAllocate + } + return nil + } + return nil + }) + return offset, ok, err +} + +// Release attempts to release the provided item locally and then in etcd. +func (e *Etcd) Release(item int) error { + e.lock.Lock() + defer e.lock.Unlock() + + if err := e.alloc.Release(item); err != nil { + return err + } + + return e.tryUpdate(func() error { + return e.alloc.Release(item) + }) +} + +// tryUpdate performs a read-update to persist the latest snapshot state of allocation. +func (e *Etcd) tryUpdate(fn func() error) error { + err := e.storage.GuaranteedUpdate(context.TODO(), e.baseKey, &api.RangeAllocation{}, true, nil, + storage.SimpleUpdate(func(input runtime.Object) (output runtime.Object, err error) { + existing := input.(*api.RangeAllocation) + if len(existing.ResourceVersion) == 0 { + return nil, fmt.Errorf("cannot allocate resources of type %s at this time", e.resource.String()) + } + if existing.ResourceVersion != e.last { + if err := e.alloc.Restore(existing.Range, existing.Data); err != nil { + return nil, err + } + if err := fn(); err != nil { + return nil, err + } + } + e.last = existing.ResourceVersion + rangeSpec, data := e.alloc.Snapshot() + existing.Range = rangeSpec + existing.Data = data + return existing, nil + }), + ) + return storeerr.InterpretUpdateError(err, e.resource, "") +} + +// Refresh reloads the RangeAllocation from etcd. +func (e *Etcd) Refresh() (*api.RangeAllocation, error) { + e.lock.Lock() + defer e.lock.Unlock() + + existing := &api.RangeAllocation{} + if err := e.storage.Get(context.TODO(), e.baseKey, existing, false); err != nil { + if storage.IsNotFound(err) { + return nil, nil + } + return nil, storeerr.InterpretGetError(err, e.resource, "") + } + + return existing, nil +} + +// Get returns an api.RangeAllocation that represents the current state in +// etcd. If the key does not exist, the object will have an empty ResourceVersion. +func (e *Etcd) Get() (*api.RangeAllocation, error) { + existing := &api.RangeAllocation{} + if err := e.storage.Get(context.TODO(), e.baseKey, existing, true); err != nil { + return nil, storeerr.InterpretGetError(err, e.resource, "") + } + return existing, nil +} + +// CreateOrUpdate attempts to update the current etcd state with the provided +// allocation. +func (e *Etcd) CreateOrUpdate(snapshot *api.RangeAllocation) error { + e.lock.Lock() + defer e.lock.Unlock() + + last := "" + err := e.storage.GuaranteedUpdate(context.TODO(), e.baseKey, &api.RangeAllocation{}, true, nil, + storage.SimpleUpdate(func(input runtime.Object) (output runtime.Object, err error) { + existing := input.(*api.RangeAllocation) + switch { + case len(snapshot.ResourceVersion) != 0 && len(existing.ResourceVersion) != 0: + if snapshot.ResourceVersion != existing.ResourceVersion { + return nil, k8serr.NewConflict(e.resource, "", fmt.Errorf("the provided resource version does not match")) + } + case len(existing.ResourceVersion) != 0: + return nil, k8serr.NewConflict(e.resource, "", fmt.Errorf("another caller has already initialized the resource")) + } + last = snapshot.ResourceVersion + return snapshot, nil + }), + ) + if err != nil { + return storeerr.InterpretUpdateError(err, e.resource, "") + } + err = e.alloc.Restore(snapshot.Range, snapshot.Data) + if err == nil { + e.last = last + } + return err +} + +// Implements allocator.Interface::Has +func (e *Etcd) Has(item int) bool { + e.lock.Lock() + defer e.lock.Unlock() + + return e.alloc.Has(item) +} + +// Implements allocator.Interface::Free +func (e *Etcd) Free() int { + e.lock.Lock() + defer e.lock.Unlock() + + return e.alloc.Free() +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/etcd/etcd_test.go new file mode 100644 index 000000000..d40cc0f1b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/etcd/etcd_test.go @@ -0,0 +1,100 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/registry/service/allocator" + "k8s.io/kubernetes/pkg/storage/etcd/etcdtest" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + + "golang.org/x/net/context" +) + +func newStorage(t *testing.T) (*Etcd, *etcdtesting.EtcdTestServer, allocator.Interface) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + mem := allocator.NewAllocationMap(100, "rangeSpecValue") + etcd := NewEtcd(mem, "/ranges/serviceips", api.Resource("serviceipallocations"), etcdStorage) + return etcd, server, mem +} + +func validNewRangeAllocation() *api.RangeAllocation { + return &api.RangeAllocation{ + Range: "rangeSpecValue", + } +} + +func key() string { + s := "/ranges/serviceips" + return etcdtest.AddPrefix(s) +} + +func TestEmpty(t *testing.T) { + storage, server, _ := newStorage(t) + defer server.Terminate(t) + if _, err := storage.Allocate(1); !strings.Contains(err.Error(), "cannot allocate resources of type serviceipallocations at this time") { + t.Fatal(err) + } +} + +func TestStore(t *testing.T) { + storage, server, backing := newStorage(t) + defer server.Terminate(t) + if err := storage.storage.Set(context.TODO(), key(), validNewRangeAllocation(), nil, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if _, err := storage.Allocate(2); err != nil { + t.Fatal(err) + } + ok, err := backing.Allocate(2) + if err != nil { + t.Fatal(err) + } + if ok { + t.Fatal("Expected backing allocation to fail") + } + if ok, err := storage.Allocate(2); ok || err != nil { + t.Fatal("Expected allocation to fail") + } + + other := allocator.NewAllocationMap(100, "rangeSpecValue") + + allocation := &api.RangeAllocation{} + if err := storage.storage.Get(context.TODO(), key(), allocation, false); err != nil { + t.Fatal(err) + } + if allocation.Range != "rangeSpecValue" { + t.Errorf("unexpected stored Range: %s", allocation.Range) + } + if err := other.Restore("rangeSpecValue", allocation.Data); err != nil { + t.Fatal(err) + } + if !other.Has(2) { + t.Fatalf("could not restore allocated IP: %#v", other) + } + + other = allocator.NewAllocationMap(100, "rangeSpecValue") + otherStorage := NewEtcd(other, "/ranges/serviceips", api.Resource("serviceipallocations"), storage.storage) + if ok, err := otherStorage.Allocate(2); ok || err != nil { + t.Fatal(err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/interfaces.go b/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/interfaces.go new file mode 100644 index 000000000..9d4409098 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/interfaces.go @@ -0,0 +1,41 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 allocator + +// Interface manages the allocation of items out of a range. Interface +// should be threadsafe. +type Interface interface { + Allocate(int) (bool, error) + AllocateNext() (int, bool, error) + Release(int) error + + // For testing + Has(int) bool + + // For testing + Free() int +} + +// Snapshottable is an Interface that can be snapshotted and restored. Snapshottable +// should be threadsafe. +type Snapshottable interface { + Interface + Snapshot() (string, []byte) + Restore(string, []byte) error +} + +type AllocatorFactory func(max int, rangeSpec string) Interface diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/utils.go b/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/utils.go new file mode 100644 index 000000000..fc7cff70e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/utils.go @@ -0,0 +1,64 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 allocator + +import "math/big" + +// countBits returns the number of set bits in n +func countBits(n *big.Int) int { + var count int = 0 + for _, b := range n.Bytes() { + count += int(bitCounts[b]) + } + return count +} + +// bitCounts is all of the bits counted for each number between 0-255 +var bitCounts = []int8{ + 0, 1, 1, 2, 1, 2, 2, 3, + 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, + 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, + 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, + 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, + 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, + 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, + 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, + 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, + 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, + 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, + 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, + 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, + 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, + 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, + 4, 5, 5, 6, 5, 6, 6, 7, + 4, 5, 5, 6, 5, 6, 6, 7, + 5, 6, 6, 7, 6, 7, 7, 8, +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/utils_test.go b/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/utils_test.go new file mode 100644 index 000000000..fcd59f016 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/allocator/utils_test.go @@ -0,0 +1,52 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 allocator + +import ( + "math/big" + "testing" +) + +func TestBitCount(t *testing.T) { + for i, c := range bitCounts { + actual := 0 + for j := 0; j < 8; j++ { + if ((1 << uint(j)) & i) != 0 { + actual++ + } + } + if actual != int(c) { + t.Errorf("%d should have %d bits but recorded as %d", i, actual, c) + } + } +} + +func TestCountBits(t *testing.T) { + tests := []struct { + n *big.Int + expected int + }{ + {n: big.NewInt(int64(0)), expected: 0}, + {n: big.NewInt(int64(0xffffffffff)), expected: 40}, + } + for _, test := range tests { + actual := countBits(test.n) + if test.expected != actual { + t.Errorf("%d should have %d bits but recorded as %d", test.n, test.expected, actual) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/service/doc.go new file mode 100644 index 000000000..64d927e80 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 service provides the Registry interface and its RESTStorage +// implementation for storing Service api objects. +package service diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/service/etcd/etcd.go new file mode 100644 index 000000000..866ef1c6b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/etcd/etcd.go @@ -0,0 +1,84 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/service" + "k8s.io/kubernetes/pkg/runtime" +) + +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against services. +func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) { + prefix := "/services/specs" + + newListFunc := func() runtime.Object { return &api.ServiceList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.Services), &api.Service{}, prefix, service.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.Service{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.Service).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return service.MatchServices(label, field) + }, + QualifiedResource: api.Resource("services"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: service.Strategy, + UpdateStrategy: service.Strategy, + DeleteStrategy: service.Strategy, + ExportStrategy: service.Strategy, + + Storage: storageInterface, + } + statusStore := *store + statusStore.UpdateStrategy = service.StatusStrategy + return &REST{store}, &StatusREST{store: &statusStore} +} + +// StatusREST implements the REST endpoint for changing the status of a service. +type StatusREST struct { + store *etcdgeneric.Etcd +} + +func (r *StatusREST) New() runtime.Object { + return &api.Service{} +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + return r.store.Update(ctx, obj) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/service/etcd/etcd_test.go new file mode 100644 index 000000000..0dd8db66f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/etcd/etcd_test.go @@ -0,0 +1,156 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + "k8s.io/kubernetes/pkg/util/intstr" +) + +func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + serviceStorage, statusStorage := NewREST(restOptions) + return serviceStorage, statusStorage, server +} + +func validService() *api.Service { + return &api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + ClusterIP: "None", + SessionAffinity: "None", + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + } +} + +func TestCreate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + validService := validService() + validService.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + validService, + // invalid + &api.Service{ + Spec: api.ServiceSpec{}, + }, + // invalid + &api.Service{ + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + ClusterIP: "invalid", + SessionAffinity: "None", + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate() + test.TestUpdate( + // valid + validService(), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*api.Service) + object.Spec = api.ServiceSpec{ + Selector: map[string]string{"bar": "baz2"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + } + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate() + test.TestDelete(validService()) +} + +func TestGet(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate() + test.TestGet(validService()) +} + +func TestList(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).AllowCreateOnUpdate() + test.TestList(validService()) +} + +func TestWatch(t *testing.T) { + storage, _, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validService(), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": "foo"}, + }, + // not matchin fields + []fields.Set{ + {"metadata.name": "bar"}, + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/allocator.go b/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/allocator.go new file mode 100644 index 000000000..3aa5e58f6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/allocator.go @@ -0,0 +1,238 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 ipallocator + +import ( + "errors" + "fmt" + "math/big" + "net" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/registry/service/allocator" +) + +// Interface manages the allocation of IP addresses out of a range. Interface +// should be threadsafe. +type Interface interface { + Allocate(net.IP) error + AllocateNext() (net.IP, error) + Release(net.IP) error +} + +var ( + ErrFull = errors.New("range is full") + ErrNotInRange = errors.New("provided IP is not in the valid range") + ErrAllocated = errors.New("provided IP is already allocated") + ErrMismatchedNetwork = errors.New("the provided network does not match the current range") +) + +// Range is a contiguous block of IPs that can be allocated atomically. +// +// The internal structure of the range is: +// +// For CIDR 10.0.0.0/24 +// 254 addresses usable out of 256 total (minus base and broadcast IPs) +// The number of usable addresses is r.max +// +// CIDR base IP CIDR broadcast IP +// 10.0.0.0 10.0.0.255 +// | | +// 0 1 2 3 4 5 ... ... 253 254 255 +// | | +// r.base r.base + r.max +// | | +// offset #0 of r.allocated last offset of r.allocated +type Range struct { + net *net.IPNet + // base is a cached version of the start IP in the CIDR range as a *big.Int + base *big.Int + // max is the maximum size of the usable addresses in the range + max int + + alloc allocator.Interface +} + +// NewAllocatorCIDRRange creates a Range over a net.IPNet, calling allocatorFactory to construct the backing store. +func NewAllocatorCIDRRange(cidr *net.IPNet, allocatorFactory allocator.AllocatorFactory) *Range { + max := RangeSize(cidr) + base := bigForIP(cidr.IP) + rangeSpec := cidr.String() + + r := Range{ + net: cidr, + base: base.Add(base, big.NewInt(1)), // don't use the network base + max: maximum(0, int(max-2)), // don't use the network broadcast, + } + r.alloc = allocatorFactory(r.max, rangeSpec) + return &r +} + +// Helper that wraps NewAllocatorCIDRRange, for creating a range backed by an in-memory store. +func NewCIDRRange(cidr *net.IPNet) *Range { + return NewAllocatorCIDRRange(cidr, func(max int, rangeSpec string) allocator.Interface { + return allocator.NewAllocationMap(max, rangeSpec) + }) +} + +func maximum(a, b int) int { + if a > b { + return a + } + return b +} + +// Free returns the count of IP addresses left in the range. +func (r *Range) Free() int { + return r.alloc.Free() +} + +// Allocate attempts to reserve the provided IP. ErrNotInRange or +// ErrAllocated will be returned if the IP is not valid for this range +// or has already been reserved. ErrFull will be returned if there +// are no addresses left. +func (r *Range) Allocate(ip net.IP) error { + ok, offset := r.contains(ip) + if !ok { + return ErrNotInRange + } + + allocated, err := r.alloc.Allocate(offset) + if err != nil { + return err + } + if !allocated { + return ErrAllocated + } + return nil +} + +// AllocateNext reserves one of the IPs from the pool. ErrFull may +// be returned if there are no addresses left. +func (r *Range) AllocateNext() (net.IP, error) { + offset, ok, err := r.alloc.AllocateNext() + if err != nil { + return nil, err + } + if !ok { + return nil, ErrFull + } + return addIPOffset(r.base, offset), nil +} + +// Release releases the IP back to the pool. Releasing an +// unallocated IP or an IP out of the range is a no-op and +// returns no error. +func (r *Range) Release(ip net.IP) error { + ok, offset := r.contains(ip) + if !ok { + return nil + } + + return r.alloc.Release(offset) +} + +// Has returns true if the provided IP is already allocated and a call +// to Allocate(ip) would fail with ErrAllocated. +func (r *Range) Has(ip net.IP) bool { + ok, offset := r.contains(ip) + if !ok { + return false + } + + return r.alloc.Has(offset) +} + +// Snapshot saves the current state of the pool. +func (r *Range) Snapshot(dst *api.RangeAllocation) error { + snapshottable, ok := r.alloc.(allocator.Snapshottable) + if !ok { + return fmt.Errorf("not a snapshottable allocator") + } + rangeString, data := snapshottable.Snapshot() + dst.Range = rangeString + dst.Data = data + return nil +} + +// Restore restores the pool to the previously captured state. ErrMismatchedNetwork +// is returned if the provided IPNet range doesn't exactly match the previous range. +func (r *Range) Restore(net *net.IPNet, data []byte) error { + if !net.IP.Equal(r.net.IP) || net.Mask.String() != r.net.Mask.String() { + return ErrMismatchedNetwork + } + snapshottable, ok := r.alloc.(allocator.Snapshottable) + if !ok { + return fmt.Errorf("not a snapshottable allocator") + } + snapshottable.Restore(net.String(), data) + return nil +} + +// contains returns true and the offset if the ip is in the range, and false +// and nil otherwise. The first and last addresses of the CIDR are omitted. +func (r *Range) contains(ip net.IP) (bool, int) { + if !r.net.Contains(ip) { + return false, 0 + } + + offset := calculateIPOffset(r.base, ip) + if offset < 0 || offset >= r.max { + return false, 0 + } + return true, offset +} + +// bigForIP creates a big.Int based on the provided net.IP +func bigForIP(ip net.IP) *big.Int { + b := ip.To4() + if b == nil { + b = ip.To16() + } + return big.NewInt(0).SetBytes(b) +} + +// addIPOffset adds the provided integer offset to a base big.Int representing a +// net.IP +func addIPOffset(base *big.Int, offset int) net.IP { + return net.IP(big.NewInt(0).Add(base, big.NewInt(int64(offset))).Bytes()) +} + +// calculateIPOffset calculates the integer offset of ip from base such that +// base + offset = ip. It requires ip >= base. +func calculateIPOffset(base *big.Int, ip net.IP) int { + return int(big.NewInt(0).Sub(bigForIP(ip), base).Int64()) +} + +// RangeSize returns the size of a range in valid addresses. +func RangeSize(subnet *net.IPNet) int64 { + ones, bits := subnet.Mask.Size() + if (bits - ones) >= 31 { + panic("masks greater than 31 bits are not supported") + } + max := int64(1) << uint(bits-ones) + return max +} + +// GetIndexedIP returns a net.IP that is subnet.IP + index in the contiguous IP space. +func GetIndexedIP(subnet *net.IPNet, index int) (net.IP, error) { + ip := addIPOffset(bigForIP(subnet.IP), index) + if !subnet.Contains(ip) { + return nil, fmt.Errorf("can't generate IP with index %d from subnet. subnet too small. subnet: %q", index, subnet) + } + return ip, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/allocator_test.go b/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/allocator_test.go new file mode 100644 index 000000000..c853ebab2 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/allocator_test.go @@ -0,0 +1,221 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 ipallocator + +import ( + "net" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/util/sets" +) + +func TestAllocate(t *testing.T) { + _, cidr, err := net.ParseCIDR("192.168.1.0/24") + if err != nil { + t.Fatal(err) + } + r := NewCIDRRange(cidr) + t.Logf("base: %v", r.base.Bytes()) + if f := r.Free(); f != 254 { + t.Errorf("unexpected free %d", f) + } + found := sets.NewString() + count := 0 + for r.Free() > 0 { + ip, err := r.AllocateNext() + if err != nil { + t.Fatalf("error @ %d: %v", count, err) + } + count++ + if !cidr.Contains(ip) { + t.Fatalf("allocated %s which is outside of %s", ip, cidr) + } + if found.Has(ip.String()) { + t.Fatalf("allocated %s twice @ %d", ip, count) + } + found.Insert(ip.String()) + } + if _, err := r.AllocateNext(); err != ErrFull { + t.Fatal(err) + } + + released := net.ParseIP("192.168.1.5") + if err := r.Release(released); err != nil { + t.Fatal(err) + } + if f := r.Free(); f != 1 { + t.Errorf("unexpected free %d", f) + } + ip, err := r.AllocateNext() + if err != nil { + t.Fatal(err) + } + if !released.Equal(ip) { + t.Errorf("unexpected %s : %s", ip, released) + } + + if err := r.Release(released); err != nil { + t.Fatal(err) + } + if err := r.Allocate(net.ParseIP("192.168.0.1")); err != ErrNotInRange { + t.Fatal(err) + } + if err := r.Allocate(net.ParseIP("192.168.1.1")); err != ErrAllocated { + t.Fatal(err) + } + if err := r.Allocate(net.ParseIP("192.168.1.0")); err != ErrNotInRange { + t.Fatal(err) + } + if err := r.Allocate(net.ParseIP("192.168.1.255")); err != ErrNotInRange { + t.Fatal(err) + } + if f := r.Free(); f != 1 { + t.Errorf("unexpected free %d", f) + } + if err := r.Allocate(released); err != nil { + t.Fatal(err) + } + if f := r.Free(); f != 0 { + t.Errorf("unexpected free %d", f) + } +} + +func TestAllocateTiny(t *testing.T) { + _, cidr, err := net.ParseCIDR("192.168.1.0/32") + if err != nil { + t.Fatal(err) + } + r := NewCIDRRange(cidr) + if f := r.Free(); f != 0 { + t.Errorf("free: %d", f) + } + if _, err := r.AllocateNext(); err != ErrFull { + t.Error(err) + } +} + +func TestAllocateSmall(t *testing.T) { + _, cidr, err := net.ParseCIDR("192.168.1.240/30") + if err != nil { + t.Fatal(err) + } + r := NewCIDRRange(cidr) + if f := r.Free(); f != 2 { + t.Errorf("free: %d", f) + } + found := sets.NewString() + for i := 0; i < 2; i++ { + ip, err := r.AllocateNext() + if err != nil { + t.Fatal(err) + } + if found.Has(ip.String()) { + t.Fatalf("already reserved: %s", ip) + } + found.Insert(ip.String()) + } + for s := range found { + if !r.Has(net.ParseIP(s)) { + t.Fatalf("missing: %s", s) + } + if err := r.Allocate(net.ParseIP(s)); err != ErrAllocated { + t.Fatal(err) + } + } + for i := 0; i < 100; i++ { + if _, err := r.AllocateNext(); err != ErrFull { + t.Fatalf("suddenly became not-full: %#v", r) + } + } + + if r.Free() != 0 && r.max != 2 { + t.Fatalf("unexpected range: %v", r) + } + + t.Logf("allocated: %v", found) +} + +func TestRangeSize(t *testing.T) { + testCases := map[string]int64{ + "192.168.1.0/24": 256, + "192.168.1.0/32": 1, + "192.168.1.0/31": 2, + } + for k, v := range testCases { + _, cidr, err := net.ParseCIDR(k) + if err != nil { + t.Fatal(err) + } + if size := RangeSize(cidr); size != v { + t.Errorf("%s should have a range size of %d, got %d", k, v, size) + } + } +} + +func TestSnapshot(t *testing.T) { + _, cidr, err := net.ParseCIDR("192.168.1.0/24") + if err != nil { + t.Fatal(err) + } + r := NewCIDRRange(cidr) + ip := []net.IP{} + for i := 0; i < 10; i++ { + n, err := r.AllocateNext() + if err != nil { + t.Fatal(err) + } + ip = append(ip, n) + } + + var dst api.RangeAllocation + err = r.Snapshot(&dst) + if err != nil { + t.Fatal(err) + } + + _, network, err := net.ParseCIDR(dst.Range) + if err != nil { + t.Fatal(err) + } + + if !network.IP.Equal(cidr.IP) || network.Mask.String() != cidr.Mask.String() { + t.Fatalf("mismatched networks: %s : %s", network, cidr) + } + + _, otherCidr, err := net.ParseCIDR("192.168.2.0/24") + if err != nil { + t.Fatal(err) + } + other := NewCIDRRange(otherCidr) + if err := r.Restore(otherCidr, dst.Data); err != ErrMismatchedNetwork { + t.Fatal(err) + } + other = NewCIDRRange(network) + if err := other.Restore(network, dst.Data); err != nil { + t.Fatal(err) + } + + for _, n := range ip { + if !other.Has(n) { + t.Errorf("restored range does not have %s", n) + } + } + if other.Free() != r.Free() { + t.Errorf("counts do not match: %d", other.Free()) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/controller/repair.go b/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/controller/repair.go new file mode 100644 index 000000000..b0a2b90a5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/controller/repair.go @@ -0,0 +1,152 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 controller + +import ( + "fmt" + "net" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/registry/service" + "k8s.io/kubernetes/pkg/registry/service/ipallocator" + "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" +) + +// Repair is a controller loop that periodically examines all service ClusterIP allocations +// and logs any errors, and then sets the compacted and accurate list of all allocated IPs. +// +// Handles: +// * Duplicate ClusterIP assignments caused by operator action or undetected race conditions +// * ClusterIPs that do not match the currently configured range +// * Allocations to services that were not actually created due to a crash or powerloss +// * Migrates old versions of Kubernetes services into the atomic ipallocator model automatically +// +// Can be run at infrequent intervals, and is best performed on startup of the master. +// Is level driven and idempotent - all valid ClusterIPs will be updated into the ipallocator +// map at the end of a single execution loop if no race is encountered. +// +// TODO: allocate new IPs if necessary +// TODO: perform repair? +type Repair struct { + interval time.Duration + registry service.Registry + network *net.IPNet + alloc service.RangeRegistry +} + +// NewRepair creates a controller that periodically ensures that all clusterIPs are uniquely allocated across the cluster +// and generates informational warnings for a cluster that is not in sync. +func NewRepair(interval time.Duration, registry service.Registry, network *net.IPNet, alloc service.RangeRegistry) *Repair { + return &Repair{ + interval: interval, + registry: registry, + network: network, + alloc: alloc, + } +} + +// RunUntil starts the controller until the provided ch is closed. +func (c *Repair) RunUntil(ch chan struct{}) { + wait.Until(func() { + if err := c.RunOnce(); err != nil { + runtime.HandleError(err) + } + }, c.interval, ch) +} + +// RunOnce verifies the state of the cluster IP allocations and returns an error if an unrecoverable problem occurs. +func (c *Repair) RunOnce() error { + return client.RetryOnConflict(client.DefaultBackoff, c.runOnce) +} + +// runOnce verifies the state of the cluster IP allocations and returns an error if an unrecoverable problem occurs. +func (c *Repair) runOnce() error { + // TODO: (per smarterclayton) if Get() or ListServices() is a weak consistency read, + // or if they are executed against different leaders, + // the ordering guarantee required to ensure no IP is allocated twice is violated. + // ListServices must return a ResourceVersion higher than the etcd index Get triggers, + // and the release code must not release services that have had IPs allocated but not yet been created + // See #8295 + + // If etcd server is not running we should wait for some time and fail only then. This is particularly + // important when we start apiserver and etcd at the same time. + var latest *api.RangeAllocation + var err error + err = wait.PollImmediate(time.Second, 10*time.Second, func() (bool, error) { + latest, err = c.alloc.Get() + return err == nil, err + }) + if err != nil { + return fmt.Errorf("unable to refresh the service IP block: %v", err) + } + + ctx := api.WithNamespace(api.NewDefaultContext(), api.NamespaceAll) + // We explicitly send no resource version, since the resource version + // of 'latest' is from a different collection, it's not comparable to + // the service collection. The caching layer keeps per-collection RVs, + // and this is proper, since in theory the collections could be hosted + // in separate etcd (or even non-etcd) instances. + list, err := c.registry.ListServices(ctx, nil) + if err != nil { + return fmt.Errorf("unable to refresh the service IP block: %v", err) + } + + r := ipallocator.NewCIDRRange(c.network) + for _, svc := range list.Items { + if !api.IsServiceIPSet(&svc) { + continue + } + ip := net.ParseIP(svc.Spec.ClusterIP) + if ip == nil { + // cluster IP is broken, reallocate + runtime.HandleError(fmt.Errorf("the cluster IP %s for service %s/%s is not a valid IP; please recreate", svc.Spec.ClusterIP, svc.Name, svc.Namespace)) + continue + } + switch err := r.Allocate(ip); err { + case nil: + case ipallocator.ErrAllocated: + // TODO: send event + // cluster IP is broken, reallocate + runtime.HandleError(fmt.Errorf("the cluster IP %s for service %s/%s was assigned to multiple services; please recreate", ip, svc.Name, svc.Namespace)) + case ipallocator.ErrNotInRange: + // TODO: send event + // cluster IP is broken, reallocate + runtime.HandleError(fmt.Errorf("the cluster IP %s for service %s/%s is not within the service CIDR %s; please recreate", ip, svc.Name, svc.Namespace, c.network)) + case ipallocator.ErrFull: + // TODO: send event + return fmt.Errorf("the service CIDR %v is full; you must widen the CIDR in order to create new services", r) + default: + return fmt.Errorf("unable to allocate cluster IP %s for service %s/%s due to an unknown error, exiting: %v", ip, svc.Name, svc.Namespace, err) + } + } + + if err := r.Snapshot(latest); err != nil { + return fmt.Errorf("unable to snapshot the updated service IP allocations: %v", err) + } + + if err := c.alloc.CreateOrUpdate(latest); err != nil { + if errors.IsConflict(err) { + return err + } + return fmt.Errorf("unable to persist the updated service IP allocations: %v", err) + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/controller/repair_test.go b/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/controller/repair_test.go new file mode 100644 index 000000000..4d80f10b5 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/controller/repair_test.go @@ -0,0 +1,167 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 controller + +import ( + "fmt" + "net" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/registry/service/ipallocator" +) + +type mockRangeRegistry struct { + getCalled bool + item *api.RangeAllocation + err error + + updateCalled bool + updated *api.RangeAllocation + updateErr error +} + +func (r *mockRangeRegistry) Get() (*api.RangeAllocation, error) { + r.getCalled = true + return r.item, r.err +} + +func (r *mockRangeRegistry) CreateOrUpdate(alloc *api.RangeAllocation) error { + r.updateCalled = true + r.updated = alloc + return r.updateErr +} + +func TestRepair(t *testing.T) { + registry := registrytest.NewServiceRegistry() + _, cidr, _ := net.ParseCIDR("192.168.1.0/24") + ipregistry := &mockRangeRegistry{ + item: &api.RangeAllocation{}, + } + r := NewRepair(0, registry, cidr, ipregistry) + + if err := r.RunOnce(); err != nil { + t.Fatal(err) + } + if !ipregistry.updateCalled || ipregistry.updated == nil || ipregistry.updated.Range != cidr.String() || ipregistry.updated != ipregistry.item { + t.Errorf("unexpected ipregistry: %#v", ipregistry) + } + + ipregistry = &mockRangeRegistry{ + item: &api.RangeAllocation{}, + updateErr: fmt.Errorf("test error"), + } + r = NewRepair(0, registry, cidr, ipregistry) + if err := r.RunOnce(); !strings.Contains(err.Error(), ": test error") { + t.Fatal(err) + } +} + +func TestRepairEmpty(t *testing.T) { + _, cidr, _ := net.ParseCIDR("192.168.1.0/24") + previous := ipallocator.NewCIDRRange(cidr) + previous.Allocate(net.ParseIP("192.168.1.10")) + + var dst api.RangeAllocation + err := previous.Snapshot(&dst) + if err != nil { + t.Fatal(err) + } + + registry := registrytest.NewServiceRegistry() + ipregistry := &mockRangeRegistry{ + item: &api.RangeAllocation{ + ObjectMeta: api.ObjectMeta{ + ResourceVersion: "1", + }, + Range: dst.Range, + Data: dst.Data, + }, + } + r := NewRepair(0, registry, cidr, ipregistry) + if err := r.RunOnce(); err != nil { + t.Fatal(err) + } + after := ipallocator.NewCIDRRange(cidr) + if err := after.Restore(cidr, ipregistry.updated.Data); err != nil { + t.Fatal(err) + } + if after.Has(net.ParseIP("192.168.1.10")) { + t.Errorf("unexpected ipallocator state: %#v", after) + } +} + +func TestRepairWithExisting(t *testing.T) { + _, cidr, _ := net.ParseCIDR("192.168.1.0/24") + previous := ipallocator.NewCIDRRange(cidr) + + var dst api.RangeAllocation + err := previous.Snapshot(&dst) + if err != nil { + t.Fatal(err) + } + + registry := registrytest.NewServiceRegistry() + registry.List = api.ServiceList{ + Items: []api.Service{ + { + Spec: api.ServiceSpec{ClusterIP: "192.168.1.1"}, + }, + { + Spec: api.ServiceSpec{ClusterIP: "192.168.1.100"}, + }, + { // outside CIDR, will be dropped + Spec: api.ServiceSpec{ClusterIP: "192.168.0.1"}, + }, + { // empty, ignored + Spec: api.ServiceSpec{ClusterIP: ""}, + }, + { // duplicate, dropped + Spec: api.ServiceSpec{ClusterIP: "192.168.1.1"}, + }, + { // headless + Spec: api.ServiceSpec{ClusterIP: "None"}, + }, + }, + } + + ipregistry := &mockRangeRegistry{ + item: &api.RangeAllocation{ + ObjectMeta: api.ObjectMeta{ + ResourceVersion: "1", + }, + Range: dst.Range, + Data: dst.Data, + }, + } + r := NewRepair(0, registry, cidr, ipregistry) + if err := r.RunOnce(); err != nil { + t.Fatal(err) + } + after := ipallocator.NewCIDRRange(cidr) + if err := after.Restore(cidr, ipregistry.updated.Data); err != nil { + t.Fatal(err) + } + if !after.Has(net.ParseIP("192.168.1.1")) || !after.Has(net.ParseIP("192.168.1.100")) { + t.Errorf("unexpected ipallocator state: %#v", after) + } + if after.Free() != 252 { + t.Errorf("unexpected ipallocator state: %#v", after) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/etcd/etcd.go new file mode 100644 index 000000000..35118afbd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/etcd/etcd.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +// Keep CI happy; it is unhappy if a directory only contains tests diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/etcd/etcd_test.go new file mode 100644 index 000000000..ae0c8b90d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/ipallocator/etcd/etcd_test.go @@ -0,0 +1,102 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "net" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/registry/service/allocator" + allocatoretcd "k8s.io/kubernetes/pkg/registry/service/allocator/etcd" + "k8s.io/kubernetes/pkg/registry/service/ipallocator" + "k8s.io/kubernetes/pkg/storage" + "k8s.io/kubernetes/pkg/storage/etcd/etcdtest" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + + "golang.org/x/net/context" +) + +func newStorage(t *testing.T) (*etcdtesting.EtcdTestServer, ipallocator.Interface, allocator.Interface, storage.Interface) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + _, cidr, err := net.ParseCIDR("192.168.1.0/24") + if err != nil { + t.Fatal(err) + } + + var backing allocator.Interface + storage := ipallocator.NewAllocatorCIDRRange(cidr, func(max int, rangeSpec string) allocator.Interface { + mem := allocator.NewAllocationMap(max, rangeSpec) + backing = mem + etcd := allocatoretcd.NewEtcd(mem, "/ranges/serviceips", api.Resource("serviceipallocations"), etcdStorage) + return etcd + }) + + return server, storage, backing, etcdStorage +} + +func validNewRangeAllocation() *api.RangeAllocation { + _, cidr, _ := net.ParseCIDR("192.168.1.0/24") + return &api.RangeAllocation{ + Range: cidr.String(), + } +} + +func key() string { + s := "/ranges/serviceips" + return etcdtest.AddPrefix(s) +} + +func TestEmpty(t *testing.T) { + server, storage, _, _ := newStorage(t) + defer server.Terminate(t) + if err := storage.Allocate(net.ParseIP("192.168.1.2")); !strings.Contains(err.Error(), "cannot allocate resources of type serviceipallocations at this time") { + t.Fatal(err) + } +} + +func TestErrors(t *testing.T) { + server, storage, _, _ := newStorage(t) + defer server.Terminate(t) + if err := storage.Allocate(net.ParseIP("192.168.0.0")); err != ipallocator.ErrNotInRange { + t.Fatal(err) + } +} + +func TestStore(t *testing.T) { + server, storage, backing, si := newStorage(t) + defer server.Terminate(t) + if err := si.Set(context.TODO(), key(), validNewRangeAllocation(), nil, 0); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if err := storage.Allocate(net.ParseIP("192.168.1.2")); err != nil { + t.Fatal(err) + } + ok, err := backing.Allocate(1) + if err != nil { + t.Fatal(err) + } + if ok { + t.Fatal("Expected allocation to fail") + } + if err := storage.Allocate(net.ParseIP("192.168.1.2")); err != ipallocator.ErrAllocated { + t.Fatal(err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/portallocator/allocator.go b/vendor/k8s.io/kubernetes/pkg/registry/service/portallocator/allocator.go new file mode 100644 index 000000000..765ac8f3c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/portallocator/allocator.go @@ -0,0 +1,169 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 portallocator + +import ( + "errors" + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/registry/service/allocator" + "k8s.io/kubernetes/pkg/util/net" + + "github.com/golang/glog" +) + +// Interface manages the allocation of ports out of a range. Interface +// should be threadsafe. +type Interface interface { + Allocate(int) error + AllocateNext() (int, error) + Release(int) error +} + +var ( + ErrFull = errors.New("range is full") + ErrNotInRange = errors.New("provided port is not in the valid range") + ErrAllocated = errors.New("provided port is already allocated") + ErrMismatchedNetwork = errors.New("the provided port range does not match the current port range") +) + +type PortAllocator struct { + portRange net.PortRange + + alloc allocator.Interface +} + +// PortAllocator implements Interface and Snapshottable +var _ Interface = &PortAllocator{} + +// NewPortAllocatorCustom creates a PortAllocator over a net.PortRange, calling allocatorFactory to construct the backing store. +func NewPortAllocatorCustom(pr net.PortRange, allocatorFactory allocator.AllocatorFactory) *PortAllocator { + max := pr.Size + rangeSpec := pr.String() + + a := &PortAllocator{ + portRange: pr, + } + a.alloc = allocatorFactory(max, rangeSpec) + return a +} + +// Helper that wraps NewAllocatorCIDRRange, for creating a range backed by an in-memory store. +func NewPortAllocator(pr net.PortRange) *PortAllocator { + return NewPortAllocatorCustom(pr, func(max int, rangeSpec string) allocator.Interface { + return allocator.NewAllocationMap(max, rangeSpec) + }) +} + +// Free returns the count of port left in the range. +func (r *PortAllocator) Free() int { + return r.alloc.Free() +} + +// Allocate attempts to reserve the provided port. ErrNotInRange or +// ErrAllocated will be returned if the port is not valid for this range +// or has already been reserved. ErrFull will be returned if there +// are no ports left. +func (r *PortAllocator) Allocate(port int) error { + ok, offset := r.contains(port) + if !ok { + return ErrNotInRange + } + + allocated, err := r.alloc.Allocate(offset) + if err != nil { + return err + } + if !allocated { + return ErrAllocated + } + return nil +} + +// AllocateNext reserves one of the ports from the pool. ErrFull may +// be returned if there are no ports left. +func (r *PortAllocator) AllocateNext() (int, error) { + offset, ok, err := r.alloc.AllocateNext() + if err != nil { + return 0, err + } + if !ok { + return 0, ErrFull + } + return r.portRange.Base + offset, nil +} + +// Release releases the port back to the pool. Releasing an +// unallocated port or a port out of the range is a no-op and +// returns no error. +func (r *PortAllocator) Release(port int) error { + ok, offset := r.contains(port) + if !ok { + glog.Warningf("port is not in the range when release it. port: %v", port) + return nil + } + + return r.alloc.Release(offset) +} + +// Has returns true if the provided port is already allocated and a call +// to Allocate(port) would fail with ErrAllocated. +func (r *PortAllocator) Has(port int) bool { + ok, offset := r.contains(port) + if !ok { + return false + } + + return r.alloc.Has(offset) +} + +// Snapshot saves the current state of the pool. +func (r *PortAllocator) Snapshot(dst *api.RangeAllocation) error { + snapshottable, ok := r.alloc.(allocator.Snapshottable) + if !ok { + return fmt.Errorf("not a snapshottable allocator") + } + rangeString, data := snapshottable.Snapshot() + dst.Range = rangeString + dst.Data = data + return nil +} + +// Restore restores the pool to the previously captured state. ErrMismatchedNetwork +// is returned if the provided port range doesn't exactly match the previous range. +func (r *PortAllocator) Restore(pr net.PortRange, data []byte) error { + if pr.String() != r.portRange.String() { + return ErrMismatchedNetwork + } + snapshottable, ok := r.alloc.(allocator.Snapshottable) + if !ok { + return fmt.Errorf("not a snapshottable allocator") + } + return snapshottable.Restore(pr.String(), data) +} + +// contains returns true and the offset if the port is in the range, and false +// and nil otherwise. +func (r *PortAllocator) contains(port int) (bool, int) { + if !r.portRange.Contains(port) { + return false, 0 + } + + offset := port - r.portRange.Base + return true, offset +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/portallocator/allocator_test.go b/vendor/k8s.io/kubernetes/pkg/registry/service/portallocator/allocator_test.go new file mode 100644 index 000000000..38904cb86 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/portallocator/allocator_test.go @@ -0,0 +1,150 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 portallocator + +import ( + "testing" + + "strconv" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/util/net" + "k8s.io/kubernetes/pkg/util/sets" +) + +func TestAllocate(t *testing.T) { + pr, err := net.ParsePortRange("10000-10200") + if err != nil { + t.Fatal(err) + } + r := NewPortAllocator(*pr) + if f := r.Free(); f != 201 { + t.Errorf("unexpected free %d", f) + } + found := sets.NewString() + count := 0 + for r.Free() > 0 { + p, err := r.AllocateNext() + if err != nil { + t.Fatalf("error @ %d: %v", count, err) + } + count++ + if !pr.Contains(p) { + t.Fatalf("allocated %d which is outside of %v", p, pr) + } + if found.Has(strconv.Itoa(p)) { + t.Fatalf("allocated %d twice @ %d", p, count) + } + found.Insert(strconv.Itoa(p)) + } + if _, err := r.AllocateNext(); err != ErrFull { + t.Fatal(err) + } + + released := 10005 + if err := r.Release(released); err != nil { + t.Fatal(err) + } + if f := r.Free(); f != 1 { + t.Errorf("unexpected free %d", f) + } + p, err := r.AllocateNext() + if err != nil { + t.Fatal(err) + } + if released != p { + t.Errorf("unexpected %d : %d", p, released) + } + + if err := r.Release(released); err != nil { + t.Fatal(err) + } + if err := r.Allocate(1); err != ErrNotInRange { + t.Fatal(err) + } + if err := r.Allocate(10001); err != ErrAllocated { + t.Fatal(err) + } + if err := r.Allocate(20000); err != ErrNotInRange { + t.Fatal(err) + } + if err := r.Allocate(10201); err != ErrNotInRange { + t.Fatal(err) + } + if f := r.Free(); f != 1 { + t.Errorf("unexpected free %d", f) + } + if err := r.Allocate(released); err != nil { + t.Fatal(err) + } + if f := r.Free(); f != 0 { + t.Errorf("unexpected free %d", f) + } +} + +func TestSnapshot(t *testing.T) { + pr, err := net.ParsePortRange("10000-10200") + if err != nil { + t.Fatal(err) + } + r := NewPortAllocator(*pr) + ports := []int{} + for i := 0; i < 10; i++ { + port, err := r.AllocateNext() + if err != nil { + t.Fatal(err) + } + ports = append(ports, port) + } + + var dst api.RangeAllocation + err = r.Snapshot(&dst) + if err != nil { + t.Fatal(err) + } + + pr2, err := net.ParsePortRange(dst.Range) + if err != nil { + t.Fatal(err) + } + + if pr.String() != pr2.String() { + t.Fatalf("mismatched networks: %s : %s", pr, pr2) + } + + otherPr, err := net.ParsePortRange("200-300") + if err != nil { + t.Fatal(err) + } + other := NewPortAllocator(*otherPr) + if err := r.Restore(*otherPr, dst.Data); err != ErrMismatchedNetwork { + t.Fatal(err) + } + other = NewPortAllocator(*pr2) + if err := other.Restore(*pr2, dst.Data); err != nil { + t.Fatal(err) + } + + for _, n := range ports { + if !other.Has(n) { + t.Errorf("restored range does not have %d", n) + } + } + if other.Free() != r.Free() { + t.Errorf("counts do not match: %d", other.Free()) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/portallocator/controller/repair.go b/vendor/k8s.io/kubernetes/pkg/registry/service/portallocator/controller/repair.go new file mode 100644 index 000000000..cff3f5f3e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/portallocator/controller/repair.go @@ -0,0 +1,141 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 controller + +import ( + "fmt" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/registry/service" + "k8s.io/kubernetes/pkg/registry/service/portallocator" + "k8s.io/kubernetes/pkg/util/net" + "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" +) + +// See ipallocator/controller/repair.go; this is a copy for ports. +type Repair struct { + interval time.Duration + registry service.Registry + portRange net.PortRange + alloc service.RangeRegistry +} + +// NewRepair creates a controller that periodically ensures that all ports are uniquely allocated across the cluster +// and generates informational warnings for a cluster that is not in sync. +func NewRepair(interval time.Duration, registry service.Registry, portRange net.PortRange, alloc service.RangeRegistry) *Repair { + return &Repair{ + interval: interval, + registry: registry, + portRange: portRange, + alloc: alloc, + } +} + +// RunUntil starts the controller until the provided ch is closed. +func (c *Repair) RunUntil(ch chan struct{}) { + wait.Until(func() { + if err := c.RunOnce(); err != nil { + runtime.HandleError(err) + } + }, c.interval, ch) +} + +// RunOnce verifies the state of the port allocations and returns an error if an unrecoverable problem occurs. +func (c *Repair) RunOnce() error { + return client.RetryOnConflict(client.DefaultBackoff, c.runOnce) +} + +// runOnce verifies the state of the port allocations and returns an error if an unrecoverable problem occurs. +func (c *Repair) runOnce() error { + // TODO: (per smarterclayton) if Get() or ListServices() is a weak consistency read, + // or if they are executed against different leaders, + // the ordering guarantee required to ensure no port is allocated twice is violated. + // ListServices must return a ResourceVersion higher than the etcd index Get triggers, + // and the release code must not release services that have had ports allocated but not yet been created + // See #8295 + + // If etcd server is not running we should wait for some time and fail only then. This is particularly + // important when we start apiserver and etcd at the same time. + var latest *api.RangeAllocation + var err error + for i := 0; i < 10; i++ { + if latest, err = c.alloc.Get(); err != nil { + time.Sleep(time.Second) + } else { + break + } + } + if err != nil { + return fmt.Errorf("unable to refresh the port block: %v", err) + } + + ctx := api.WithNamespace(api.NewDefaultContext(), api.NamespaceAll) + // We explicitly send no resource version, since the resource version + // of 'latest' is from a different collection, it's not comparable to + // the service collection. The caching layer keeps per-collection RVs, + // and this is proper, since in theory the collections could be hosted + // in separate etcd (or even non-etcd) instances. + list, err := c.registry.ListServices(ctx, nil) + if err != nil { + return fmt.Errorf("unable to refresh the port block: %v", err) + } + + r := portallocator.NewPortAllocator(c.portRange) + for i := range list.Items { + svc := &list.Items[i] + ports := service.CollectServiceNodePorts(svc) + if len(ports) == 0 { + continue + } + + for _, port := range ports { + switch err := r.Allocate(port); err { + case nil: + case portallocator.ErrAllocated: + // TODO: send event + // port is broken, reallocate + runtime.HandleError(fmt.Errorf("the port %d for service %s/%s was assigned to multiple services; please recreate", port, svc.Name, svc.Namespace)) + case portallocator.ErrNotInRange: + // TODO: send event + // port is broken, reallocate + runtime.HandleError(fmt.Errorf("the port %d for service %s/%s is not within the port range %v; please recreate", port, svc.Name, svc.Namespace, c.portRange)) + case portallocator.ErrFull: + // TODO: send event + return fmt.Errorf("the port range %v is full; you must widen the port range in order to create new services", c.portRange) + default: + return fmt.Errorf("unable to allocate port %d for service %s/%s due to an unknown error, exiting: %v", port, svc.Name, svc.Namespace, err) + } + } + } + + err = r.Snapshot(latest) + if err != nil { + return fmt.Errorf("unable to snapshot the updated port allocations: %v", err) + } + + if err := c.alloc.CreateOrUpdate(latest); err != nil { + if errors.IsConflict(err) { + return err + } + return fmt.Errorf("unable to persist the updated port allocations: %v", err) + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/portallocator/operation.go b/vendor/k8s.io/kubernetes/pkg/registry/service/portallocator/operation.go new file mode 100644 index 000000000..a43501043 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/portallocator/operation.go @@ -0,0 +1,117 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 portallocator + +// Encapsulates the semantics of a port allocation 'transaction': +// It is better to leak ports than to double-allocate them, +// so we allocate immediately, but defer release. +// On commit we best-effort release the deferred releases. +// On rollback we best-effort release any allocations we did. +// +// Pattern for use: +// op := StartPortAllocationOperation(...) +// defer op.Finish +// ... +// write(updatedOwner) +/// op.Commit() +type portAllocationOperation struct { + pa Interface + allocated []int + releaseDeferred []int + shouldRollback bool +} + +// Creates a portAllocationOperation, tracking a set of allocations & releases +func StartOperation(pa Interface) *portAllocationOperation { + op := &portAllocationOperation{} + op.pa = pa + op.allocated = []int{} + op.releaseDeferred = []int{} + op.shouldRollback = true + return op +} + +// Will rollback unless marked as shouldRollback = false by a Commit(). Call from a defer block +func (op *portAllocationOperation) Finish() { + if op.shouldRollback { + op.Rollback() + } +} + +// (Try to) undo any operations we did +func (op *portAllocationOperation) Rollback() []error { + errors := []error{} + + for _, allocated := range op.allocated { + err := op.pa.Release(allocated) + if err != nil { + errors = append(errors, err) + } + } + + if len(errors) == 0 { + return nil + } + return errors +} + +// (Try to) perform any deferred operations. +// Note that even if this fails, we don't rollback; we always want to err on the side of over-allocation, +// and Commit should be called _after_ the owner is written +func (op *portAllocationOperation) Commit() []error { + errors := []error{} + + for _, release := range op.releaseDeferred { + err := op.pa.Release(release) + if err != nil { + errors = append(errors, err) + } + } + + // Even on error, we don't rollback + // Problems should be fixed by an eventual reconciliation / restart + op.shouldRollback = false + + if len(errors) == 0 { + return nil + } + + return errors +} + +// Allocates a port, and record it for future rollback +func (op *portAllocationOperation) Allocate(port int) error { + err := op.pa.Allocate(port) + if err == nil { + op.allocated = append(op.allocated, port) + } + return err +} + +// Allocates a port, and record it for future rollback +func (op *portAllocationOperation) AllocateNext() (int, error) { + port, err := op.pa.AllocateNext() + if err == nil { + op.allocated = append(op.allocated, port) + } + return port, err +} + +// Marks a port so that it will be released if this operation Commits +func (op *portAllocationOperation) ReleaseDeferred(port int) { + op.releaseDeferred = append(op.releaseDeferred, port) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/proxy.go b/vendor/k8s.io/kubernetes/pkg/registry/service/proxy.go new file mode 100644 index 000000000..77ff95331 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/proxy.go @@ -0,0 +1,77 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 service + +import ( + "fmt" + "net/http" + "net/url" + "path" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/capabilities" + genericrest "k8s.io/kubernetes/pkg/registry/generic/rest" + "k8s.io/kubernetes/pkg/runtime" +) + +// ProxyREST implements the proxy subresource for a Service +type ProxyREST struct { + ServiceRest *REST + ProxyTransport http.RoundTripper +} + +// Implement Connecter +var _ = rest.Connecter(&ProxyREST{}) + +var proxyMethods = []string{"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"} + +// New returns an empty service resource +func (r *ProxyREST) New() runtime.Object { + return &api.Service{} +} + +// ConnectMethods returns the list of HTTP methods that can be proxied +func (r *ProxyREST) ConnectMethods() []string { + return proxyMethods +} + +// NewConnectOptions returns versioned resource that represents proxy parameters +func (r *ProxyREST) NewConnectOptions() (runtime.Object, bool, string) { + return &api.ServiceProxyOptions{}, true, "path" +} + +// Connect returns a handler for the service proxy +func (r *ProxyREST) Connect(ctx api.Context, id string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { + proxyOpts, ok := opts.(*api.ServiceProxyOptions) + if !ok { + return nil, fmt.Errorf("Invalid options object: %#v", opts) + } + location, transport, err := r.ServiceRest.ResourceLocation(ctx, id) + if err != nil { + return nil, err + } + location.Path = path.Join(location.Path, proxyOpts.Path) + // Return a proxy handler that uses the desired transport, wrapped with additional proxy handling (to get URL rewriting, X-Forwarded-* headers, etc) + return newThrottledUpgradeAwareProxyHandler(location, transport, true, false, responder), nil +} + +func newThrottledUpgradeAwareProxyHandler(location *url.URL, transport http.RoundTripper, wrapTransport, upgradeRequired bool, responder rest.Responder) *genericrest.UpgradeAwareProxyHandler { + handler := genericrest.NewUpgradeAwareProxyHandler(location, transport, wrapTransport, upgradeRequired, responder) + handler.MaxBytesPerSec = capabilities.Get().PerConnectionBandwidthLimitBytesPerSec + return handler +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/registry.go b/vendor/k8s.io/kubernetes/pkg/registry/service/registry.go new file mode 100644 index 000000000..8fdbabab6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/registry.go @@ -0,0 +1,114 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 service + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface for things that know how to store services. +type Registry interface { + ListServices(ctx api.Context, options *api.ListOptions) (*api.ServiceList, error) + CreateService(ctx api.Context, svc *api.Service) (*api.Service, error) + GetService(ctx api.Context, name string) (*api.Service, error) + DeleteService(ctx api.Context, name string) error + UpdateService(ctx api.Context, svc *api.Service) (*api.Service, error) + WatchServices(ctx api.Context, options *api.ListOptions) (watch.Interface, error) + ExportService(ctx api.Context, name string, options unversioned.ExportOptions) (*api.Service, error) +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListServices(ctx api.Context, options *api.ListOptions) (*api.ServiceList, error) { + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + return obj.(*api.ServiceList), nil +} + +func (s *storage) CreateService(ctx api.Context, svc *api.Service) (*api.Service, error) { + obj, err := s.Create(ctx, svc) + if err != nil { + return nil, err + } + return obj.(*api.Service), nil +} + +func (s *storage) GetService(ctx api.Context, name string) (*api.Service, error) { + obj, err := s.Get(ctx, name) + if err != nil { + return nil, err + } + return obj.(*api.Service), nil +} + +func (s *storage) DeleteService(ctx api.Context, name string) error { + _, err := s.Delete(ctx, name, nil) + return err +} + +func (s *storage) UpdateService(ctx api.Context, svc *api.Service) (*api.Service, error) { + obj, _, err := s.Update(ctx, svc) + if err != nil { + return nil, err + } + return obj.(*api.Service), nil +} + +func (s *storage) WatchServices(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +// If StandardStorage implements rest.Exporter, returns exported service. +// Otherwise export is not supported. +func (s *storage) ExportService(ctx api.Context, name string, options unversioned.ExportOptions) (*api.Service, error) { + exporter, isExporter := s.StandardStorage.(rest.Exporter) + if !isExporter { + return nil, fmt.Errorf("export is not supported") + } + obj, err := exporter.Export(ctx, name, options) + if err != nil { + return nil, err + } + return obj.(*api.Service), nil +} + +// TODO: Move to a general location (as other components may need allocation in future; it's not service specific) +// RangeRegistry is a registry that can retrieve or persist a RangeAllocation object. +type RangeRegistry interface { + // Get returns the latest allocation, an empty object if no allocation has been made, + // or an error if the allocation could not be retrieved. + Get() (*api.RangeAllocation, error) + // CreateOrUpdate should create or update the provide allocation, unless a conflict + // has occurred since the item was last created. + CreateOrUpdate(*api.RangeAllocation) error +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/rest.go b/vendor/k8s.io/kubernetes/pkg/registry/service/rest.go new file mode 100644 index 000000000..af6a591a1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/rest.go @@ -0,0 +1,395 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 service + +import ( + "fmt" + "math/rand" + "net" + "net/http" + "net/url" + "strconv" + + "github.com/golang/glog" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/registry/endpoint" + "k8s.io/kubernetes/pkg/registry/service/ipallocator" + "k8s.io/kubernetes/pkg/registry/service/portallocator" + "k8s.io/kubernetes/pkg/runtime" + utilnet "k8s.io/kubernetes/pkg/util/net" + "k8s.io/kubernetes/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/watch" +) + +// ServiceRest includes storage for services and all sub resources +type ServiceRest struct { + Service *REST + Proxy *ProxyREST +} + +// REST adapts a service registry into apiserver's RESTStorage model. +type REST struct { + registry Registry + endpoints endpoint.Registry + serviceIPs ipallocator.Interface + serviceNodePorts portallocator.Interface + proxyTransport http.RoundTripper +} + +// NewStorage returns a new REST. +func NewStorage(registry Registry, endpoints endpoint.Registry, serviceIPs ipallocator.Interface, + serviceNodePorts portallocator.Interface, proxyTransport http.RoundTripper) *ServiceRest { + rest := &REST{ + registry: registry, + endpoints: endpoints, + serviceIPs: serviceIPs, + serviceNodePorts: serviceNodePorts, + proxyTransport: proxyTransport, + } + return &ServiceRest{ + Service: rest, + Proxy: &ProxyREST{ServiceRest: rest, ProxyTransport: proxyTransport}, + } +} + +func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { + service := obj.(*api.Service) + + if err := rest.BeforeCreate(Strategy, ctx, obj); err != nil { + return nil, err + } + + // TODO: this should probably move to strategy.PrepareForCreate() + releaseServiceIP := false + defer func() { + if releaseServiceIP { + if api.IsServiceIPSet(service) { + rs.serviceIPs.Release(net.ParseIP(service.Spec.ClusterIP)) + } + } + }() + + nodePortOp := portallocator.StartOperation(rs.serviceNodePorts) + defer nodePortOp.Finish() + + if api.IsServiceIPRequested(service) { + // Allocate next available. + ip, err := rs.serviceIPs.AllocateNext() + if err != nil { + // TODO: what error should be returned here? It's not a + // field-level validation failure (the field is valid), and it's + // not really an internal error. + return nil, errors.NewInternalError(fmt.Errorf("failed to allocate a serviceIP: %v", err)) + } + service.Spec.ClusterIP = ip.String() + releaseServiceIP = true + } else if api.IsServiceIPSet(service) { + // Try to respect the requested IP. + if err := rs.serviceIPs.Allocate(net.ParseIP(service.Spec.ClusterIP)); err != nil { + // TODO: when validation becomes versioned, this gets more complicated. + el := field.ErrorList{field.Invalid(field.NewPath("spec", "clusterIP"), service.Spec.ClusterIP, err.Error())} + return nil, errors.NewInvalid(api.Kind("Service"), service.Name, el) + } + releaseServiceIP = true + } + + assignNodePorts := shouldAssignNodePorts(service) + for i := range service.Spec.Ports { + servicePort := &service.Spec.Ports[i] + if servicePort.NodePort != 0 { + err := nodePortOp.Allocate(servicePort.NodePort) + if err != nil { + // TODO: when validation becomes versioned, this gets more complicated. + el := field.ErrorList{field.Invalid(field.NewPath("spec", "ports").Index(i).Child("nodePort"), servicePort.NodePort, err.Error())} + return nil, errors.NewInvalid(api.Kind("Service"), service.Name, el) + } + } else if assignNodePorts { + nodePort, err := nodePortOp.AllocateNext() + if err != nil { + // TODO: what error should be returned here? It's not a + // field-level validation failure (the field is valid), and it's + // not really an internal error. + return nil, errors.NewInternalError(fmt.Errorf("failed to allocate a nodePort: %v", err)) + } + servicePort.NodePort = nodePort + } + } + + out, err := rs.registry.CreateService(ctx, service) + if err != nil { + err = rest.CheckGeneratedNameError(Strategy, err, service) + } + + if err == nil { + el := nodePortOp.Commit() + if el != nil { + // these should be caught by an eventual reconciliation / restart + glog.Errorf("error(s) committing service node-ports changes: %v", el) + } + + releaseServiceIP = false + } + + return out, err +} + +func (rs *REST) Delete(ctx api.Context, id string) (runtime.Object, error) { + service, err := rs.registry.GetService(ctx, id) + if err != nil { + return nil, err + } + + err = rs.registry.DeleteService(ctx, id) + if err != nil { + return nil, err + } + + // TODO: can leave dangling endpoints, and potentially return incorrect + // endpoints if a new service is created with the same name + err = rs.endpoints.DeleteEndpoints(ctx, id) + if err != nil && !errors.IsNotFound(err) { + return nil, err + } + + if api.IsServiceIPSet(service) { + rs.serviceIPs.Release(net.ParseIP(service.Spec.ClusterIP)) + } + + for _, nodePort := range CollectServiceNodePorts(service) { + err := rs.serviceNodePorts.Release(nodePort) + if err != nil { + // these should be caught by an eventual reconciliation / restart + glog.Errorf("Error releasing service %s node port %d: %v", service.Name, nodePort, err) + } + } + + return &unversioned.Status{Status: unversioned.StatusSuccess}, nil +} + +func (rs *REST) Get(ctx api.Context, id string) (runtime.Object, error) { + return rs.registry.GetService(ctx, id) +} + +func (rs *REST) List(ctx api.Context, options *api.ListOptions) (runtime.Object, error) { + return rs.registry.ListServices(ctx, options) +} + +// Watch returns Services events via a watch.Interface. +// It implements rest.Watcher. +func (rs *REST) Watch(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return rs.registry.WatchServices(ctx, options) +} + +// Export returns Service stripped of cluster-specific information. +// It implements rest.Exporter. +func (rs *REST) Export(ctx api.Context, name string, opts unversioned.ExportOptions) (runtime.Object, error) { + return rs.registry.ExportService(ctx, name, opts) +} + +func (*REST) New() runtime.Object { + return &api.Service{} +} + +func (*REST) NewList() runtime.Object { + return &api.ServiceList{} +} + +func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { + service := obj.(*api.Service) + if !api.ValidNamespace(ctx, &service.ObjectMeta) { + return nil, false, errors.NewConflict(api.Resource("services"), service.Namespace, fmt.Errorf("Service.Namespace does not match the provided context")) + } + + oldService, err := rs.registry.GetService(ctx, service.Name) + if err != nil { + return nil, false, err + } + + // Copy over non-user fields + // TODO: make this a merge function + if errs := validation.ValidateServiceUpdate(service, oldService); len(errs) > 0 { + return nil, false, errors.NewInvalid(api.Kind("Service"), service.Name, errs) + } + + nodePortOp := portallocator.StartOperation(rs.serviceNodePorts) + defer nodePortOp.Finish() + + assignNodePorts := shouldAssignNodePorts(service) + + oldNodePorts := CollectServiceNodePorts(oldService) + + newNodePorts := []int{} + if assignNodePorts { + for i := range service.Spec.Ports { + servicePort := &service.Spec.Ports[i] + nodePort := servicePort.NodePort + if nodePort != 0 { + if !contains(oldNodePorts, nodePort) { + err := nodePortOp.Allocate(nodePort) + if err != nil { + el := field.ErrorList{field.Invalid(field.NewPath("spec", "ports").Index(i).Child("nodePort"), nodePort, err.Error())} + return nil, false, errors.NewInvalid(api.Kind("Service"), service.Name, el) + } + } + } else { + nodePort, err = nodePortOp.AllocateNext() + if err != nil { + // TODO: what error should be returned here? It's not a + // field-level validation failure (the field is valid), and it's + // not really an internal error. + return nil, false, errors.NewInternalError(fmt.Errorf("failed to allocate a nodePort: %v", err)) + } + servicePort.NodePort = nodePort + } + // Detect duplicate node ports; this should have been caught by validation, so we panic + if contains(newNodePorts, nodePort) { + panic("duplicate node port") + } + newNodePorts = append(newNodePorts, nodePort) + } + } else { + // Validate should have validated that nodePort == 0 + } + + // The comparison loops are O(N^2), but we don't expect N to be huge + // (there's a hard-limit at 2^16, because they're ports; and even 4 ports would be a lot) + for _, oldNodePort := range oldNodePorts { + if !contains(newNodePorts, oldNodePort) { + continue + } + nodePortOp.ReleaseDeferred(oldNodePort) + } + + // Remove any LoadBalancerStatus now if Type != LoadBalancer; + // although loadbalancer delete is actually asynchronous, we don't need to expose the user to that complexity. + if service.Spec.Type != api.ServiceTypeLoadBalancer { + service.Status.LoadBalancer = api.LoadBalancerStatus{} + } + + out, err := rs.registry.UpdateService(ctx, service) + + if err == nil { + el := nodePortOp.Commit() + if el != nil { + // problems should be fixed by an eventual reconciliation / restart + glog.Errorf("error(s) committing NodePorts changes: %v", el) + } + } + + return out, false, err +} + +// Implement Redirector. +var _ = rest.Redirector(&REST{}) + +// ResourceLocation returns a URL to which one can send traffic for the specified service. +func (rs *REST) ResourceLocation(ctx api.Context, id string) (*url.URL, http.RoundTripper, error) { + // Allow ID as "svcname", "svcname:port", or "scheme:svcname:port". + svcScheme, svcName, portStr, valid := utilnet.SplitSchemeNamePort(id) + if !valid { + return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid service request %q", id)) + } + + // If a port *number* was specified, find the corresponding service port name + if portNum, err := strconv.ParseInt(portStr, 10, 64); err == nil { + svc, err := rs.registry.GetService(ctx, svcName) + if err != nil { + return nil, nil, err + } + found := false + for _, svcPort := range svc.Spec.Ports { + if svcPort.Port == int(portNum) { + // use the declared port's name + portStr = svcPort.Name + found = true + break + } + } + if !found { + return nil, nil, errors.NewServiceUnavailable(fmt.Sprintf("no service port %d found for service %q", portNum, svcName)) + } + } + + eps, err := rs.endpoints.GetEndpoints(ctx, svcName) + if err != nil { + return nil, nil, err + } + if len(eps.Subsets) == 0 { + return nil, nil, errors.NewServiceUnavailable(fmt.Sprintf("no endpoints available for service %q", svcName)) + } + // Pick a random Subset to start searching from. + ssSeed := rand.Intn(len(eps.Subsets)) + // Find a Subset that has the port. + for ssi := 0; ssi < len(eps.Subsets); ssi++ { + ss := &eps.Subsets[(ssSeed+ssi)%len(eps.Subsets)] + if len(ss.Addresses) == 0 { + continue + } + for i := range ss.Ports { + if ss.Ports[i].Name == portStr { + // Pick a random address. + ip := ss.Addresses[rand.Intn(len(ss.Addresses))].IP + port := ss.Ports[i].Port + return &url.URL{ + Scheme: svcScheme, + Host: net.JoinHostPort(ip, strconv.Itoa(port)), + }, rs.proxyTransport, nil + } + } + } + return nil, nil, errors.NewServiceUnavailable(fmt.Sprintf("no endpoints available for service %q", id)) +} + +// This is O(N), but we expect haystack to be small; +// so small that we expect a linear search to be faster +func contains(haystack []int, needle int) bool { + for _, v := range haystack { + if v == needle { + return true + } + } + return false +} + +func CollectServiceNodePorts(service *api.Service) []int { + servicePorts := []int{} + for i := range service.Spec.Ports { + servicePort := &service.Spec.Ports[i] + if servicePort.NodePort != 0 { + servicePorts = append(servicePorts, servicePort.NodePort) + } + } + return servicePorts +} + +func shouldAssignNodePorts(service *api.Service) bool { + switch service.Spec.Type { + case api.ServiceTypeLoadBalancer: + return true + case api.ServiceTypeNodePort: + return true + case api.ServiceTypeClusterIP: + return false + default: + glog.Errorf("Unknown service type: %v", service.Spec.Type) + return false + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/rest_test.go b/vendor/k8s.io/kubernetes/pkg/registry/service/rest_test.go new file mode 100644 index 000000000..c1c7120b1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/rest_test.go @@ -0,0 +1,823 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 service + +import ( + "net" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/registry/service/ipallocator" + "k8s.io/kubernetes/pkg/registry/service/portallocator" + utilnet "k8s.io/kubernetes/pkg/util/net" + + "k8s.io/kubernetes/pkg/util/intstr" +) + +// TODO(wojtek-t): Cleanup this file. +// It is now testing mostly the same things as other resources but +// in a completely different way. We should unify it. + +func NewTestREST(t *testing.T, endpoints *api.EndpointsList) (*REST, *registrytest.ServiceRegistry) { + registry := registrytest.NewServiceRegistry() + endpointRegistry := ®istrytest.EndpointRegistry{ + Endpoints: endpoints, + } + r := ipallocator.NewCIDRRange(makeIPNet(t)) + + portRange := utilnet.PortRange{Base: 30000, Size: 1000} + portAllocator := portallocator.NewPortAllocator(portRange) + + storage := NewStorage(registry, endpointRegistry, r, portAllocator, nil) + + return storage.Service, registry +} + +func makeIPNet(t *testing.T) *net.IPNet { + _, net, err := net.ParseCIDR("1.2.3.0/24") + if err != nil { + t.Error(err) + } + return net +} + +func deepCloneService(svc *api.Service) *api.Service { + value, err := api.Scheme.DeepCopy(svc) + if err != nil { + panic("couldn't copy service") + } + return value.(*api.Service) +} + +func TestServiceRegistryCreate(t *testing.T) { + storage, registry := NewTestREST(t, nil) + + svc := &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + } + ctx := api.NewDefaultContext() + created_svc, err := storage.Create(ctx, svc) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + created_service := created_svc.(*api.Service) + if !api.HasObjectMetaSystemFieldValues(&created_service.ObjectMeta) { + t.Errorf("storage did not populate object meta field values") + } + if created_service.Name != "foo" { + t.Errorf("Expected foo, but got %v", created_service.Name) + } + if created_service.CreationTimestamp.IsZero() { + t.Errorf("Expected timestamp to be set, got: %v", created_service.CreationTimestamp) + } + if !makeIPNet(t).Contains(net.ParseIP(created_service.Spec.ClusterIP)) { + t.Errorf("Unexpected ClusterIP: %s", created_service.Spec.ClusterIP) + } + srv, err := registry.GetService(ctx, svc.Name) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if srv == nil { + t.Errorf("Failed to find service: %s", svc.Name) + } +} + +func TestServiceStorageValidatesCreate(t *testing.T) { + storage, _ := NewTestREST(t, nil) + failureCases := map[string]api.Service{ + "empty ID": { + ObjectMeta: api.ObjectMeta{Name: ""}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + }, + "empty port": { + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Protocol: api.ProtocolTCP, + }}, + }, + }, + "missing targetPort": { + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + }}, + }, + }, + } + ctx := api.NewDefaultContext() + for _, failureCase := range failureCases { + c, err := storage.Create(ctx, &failureCase) + if c != nil { + t.Errorf("Expected nil object") + } + if !errors.IsInvalid(err) { + t.Errorf("Expected to get an invalid resource error, got %v", err) + } + } +} + +func TestServiceRegistryUpdate(t *testing.T) { + ctx := api.NewDefaultContext() + storage, registry := NewTestREST(t, nil) + svc, err := registry.CreateService(ctx, &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1", Namespace: api.NamespaceDefault}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz1"}, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + }) + + if err != nil { + t.Fatalf("Expected no error: %v", err) + } + updated_svc, created, err := storage.Update(ctx, &api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + ResourceVersion: svc.ResourceVersion}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz2"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + }) + if err != nil { + t.Fatalf("Expected no error: %v", err) + } + if updated_svc == nil { + t.Errorf("Expected non-nil object") + } + if created { + t.Errorf("expected not created") + } + updated_service := updated_svc.(*api.Service) + if updated_service.Name != "foo" { + t.Errorf("Expected foo, but got %v", updated_service.Name) + } + if e, a := "foo", registry.UpdatedID; e != a { + t.Errorf("Expected %v, but got %v", e, a) + } +} + +func TestServiceStorageValidatesUpdate(t *testing.T) { + ctx := api.NewDefaultContext() + storage, registry := NewTestREST(t, nil) + registry.CreateService(ctx, &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + }}, + }, + }) + failureCases := map[string]api.Service{ + "empty ID": { + ObjectMeta: api.ObjectMeta{Name: ""}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + }, + "invalid selector": { + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"ThisSelectorFailsValidation": "ok"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + }, + } + for _, failureCase := range failureCases { + c, created, err := storage.Update(ctx, &failureCase) + if c != nil || created { + t.Errorf("Expected nil object or created false") + } + if !errors.IsInvalid(err) { + t.Errorf("Expected to get an invalid resource error, got %v", err) + } + } +} + +func TestServiceRegistryExternalService(t *testing.T) { + ctx := api.NewDefaultContext() + storage, registry := NewTestREST(t, nil) + svc := &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeLoadBalancer, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + } + _, err := storage.Create(ctx, svc) + if err != nil { + t.Errorf("Failed to create service: %#v", err) + } + srv, err := registry.GetService(ctx, svc.Name) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if srv == nil { + t.Errorf("Failed to find service: %s", svc.Name) + } +} + +func TestServiceRegistryDelete(t *testing.T) { + ctx := api.NewDefaultContext() + storage, registry := NewTestREST(t, nil) + svc := &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + }}, + }, + } + registry.CreateService(ctx, svc) + storage.Delete(ctx, svc.Name) + if e, a := "foo", registry.DeletedID; e != a { + t.Errorf("Expected %v, but got %v", e, a) + } +} + +func TestServiceRegistryDeleteExternal(t *testing.T) { + ctx := api.NewDefaultContext() + storage, registry := NewTestREST(t, nil) + svc := &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeLoadBalancer, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + }}, + }, + } + registry.CreateService(ctx, svc) + storage.Delete(ctx, svc.Name) + if e, a := "foo", registry.DeletedID; e != a { + t.Errorf("Expected %v, but got %v", e, a) + } +} + +func TestServiceRegistryUpdateExternalService(t *testing.T) { + ctx := api.NewDefaultContext() + storage, _ := NewTestREST(t, nil) + + // Create non-external load balancer. + svc1 := &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + } + if _, err := storage.Create(ctx, svc1); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Modify load balancer to be external. + svc2 := deepCloneService(svc1) + svc2.Spec.Type = api.ServiceTypeLoadBalancer + if _, _, err := storage.Update(ctx, svc2); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Change port. + svc3 := deepCloneService(svc2) + svc3.Spec.Ports[0].Port = 6504 + if _, _, err := storage.Update(ctx, svc3); err != nil { + t.Fatalf("Unexpected error: %v", err) + } +} + +func TestServiceRegistryUpdateMultiPortExternalService(t *testing.T) { + ctx := api.NewDefaultContext() + storage, _ := NewTestREST(t, nil) + + // Create external load balancer. + svc1 := &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeLoadBalancer, + Ports: []api.ServicePort{{ + Name: "p", + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }, { + Name: "q", + Port: 8086, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(8086), + }}, + }, + } + if _, err := storage.Create(ctx, svc1); err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // Modify ports + svc2 := deepCloneService(svc1) + svc2.Spec.Ports[1].Port = 8088 + if _, _, err := storage.Update(ctx, svc2); err != nil { + t.Fatalf("Unexpected error: %v", err) + } +} + +func TestServiceRegistryGet(t *testing.T) { + ctx := api.NewDefaultContext() + storage, registry := NewTestREST(t, nil) + registry.CreateService(ctx, &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + }, + }) + storage.Get(ctx, "foo") + if e, a := "foo", registry.GottenID; e != a { + t.Errorf("Expected %v, but got %v", e, a) + } +} + +func TestServiceRegistryResourceLocation(t *testing.T) { + ctx := api.NewDefaultContext() + endpoints := &api.EndpointsList{ + Items: []api.Endpoints{ + { + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "", Port: 80}, {Name: "p", Port: 93}}, + }}, + }, + { + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Subsets: []api.EndpointSubset{{ + Addresses: []api.EndpointAddress{}, + Ports: []api.EndpointPort{{Name: "", Port: 80}, {Name: "p", Port: 93}}, + }, { + Addresses: []api.EndpointAddress{{IP: "1.2.3.4"}}, + Ports: []api.EndpointPort{{Name: "", Port: 80}, {Name: "p", Port: 93}}, + }, { + Addresses: []api.EndpointAddress{{IP: "1.2.3.5"}}, + Ports: []api.EndpointPort{}, + }}, + }, + }, + } + storage, registry := NewTestREST(t, endpoints) + registry.CreateService(ctx, &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + Ports: []api.ServicePort{ + // Service port 9393 should route to endpoint port "p", which is port 93 + {Name: "p", Port: 9393, TargetPort: intstr.FromString("p")}, + + // Service port 93 should route to unnamed endpoint port, which is port 80 + // This is to test that the service port definition is used when determining resource location + {Name: "", Port: 93, TargetPort: intstr.FromInt(80)}, + }, + }, + }) + redirector := rest.Redirector(storage) + + // Test a simple id. + location, _, err := redirector.ResourceLocation(ctx, "foo") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if location == nil { + t.Errorf("Unexpected nil: %v", location) + } + if e, a := "//1.2.3.4:80", location.String(); e != a { + t.Errorf("Expected %v, but got %v", e, a) + } + + // Test a name + port. + location, _, err = redirector.ResourceLocation(ctx, "foo:p") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if location == nil { + t.Errorf("Unexpected nil: %v", location) + } + if e, a := "//1.2.3.4:93", location.String(); e != a { + t.Errorf("Expected %v, but got %v", e, a) + } + + // Test a name + port number (service port 93 -> target port 80) + location, _, err = redirector.ResourceLocation(ctx, "foo:93") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if location == nil { + t.Errorf("Unexpected nil: %v", location) + } + if e, a := "//1.2.3.4:80", location.String(); e != a { + t.Errorf("Expected %v, but got %v", e, a) + } + + // Test a name + port number (service port 9393 -> target port "p" -> endpoint port 93) + location, _, err = redirector.ResourceLocation(ctx, "foo:9393") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if location == nil { + t.Errorf("Unexpected nil: %v", location) + } + if e, a := "//1.2.3.4:93", location.String(); e != a { + t.Errorf("Expected %v, but got %v", e, a) + } + + // Test a scheme + name + port. + location, _, err = redirector.ResourceLocation(ctx, "https:foo:p") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if location == nil { + t.Errorf("Unexpected nil: %v", location) + } + if e, a := "https://1.2.3.4:93", location.String(); e != a { + t.Errorf("Expected %v, but got %v", e, a) + } + + // Test a non-existent name + port. + location, _, err = redirector.ResourceLocation(ctx, "foo:q") + if err == nil { + t.Errorf("Unexpected nil error") + } + + // Test error path + if _, _, err = redirector.ResourceLocation(ctx, "bar"); err == nil { + t.Errorf("unexpected nil error") + } +} + +func TestServiceRegistryList(t *testing.T) { + ctx := api.NewDefaultContext() + storage, registry := NewTestREST(t, nil) + registry.CreateService(ctx, &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: api.NamespaceDefault}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + }, + }) + registry.CreateService(ctx, &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo2", Namespace: api.NamespaceDefault}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar2": "baz2"}, + }, + }) + registry.List.ResourceVersion = "1" + s, _ := storage.List(ctx, nil) + sl := s.(*api.ServiceList) + if len(sl.Items) != 2 { + t.Fatalf("Expected 2 services, but got %v", len(sl.Items)) + } + if e, a := "foo", sl.Items[0].Name; e != a { + t.Errorf("Expected %v, but got %v", e, a) + } + if e, a := "foo2", sl.Items[1].Name; e != a { + t.Errorf("Expected %v, but got %v", e, a) + } + if sl.ResourceVersion != "1" { + t.Errorf("Unexpected resource version: %#v", sl) + } +} + +func TestServiceRegistryIPAllocation(t *testing.T) { + rest, _ := NewTestREST(t, nil) + + svc1 := &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + } + ctx := api.NewDefaultContext() + created_svc1, _ := rest.Create(ctx, svc1) + created_service_1 := created_svc1.(*api.Service) + if created_service_1.Name != "foo" { + t.Errorf("Expected foo, but got %v", created_service_1.Name) + } + if !makeIPNet(t).Contains(net.ParseIP(created_service_1.Spec.ClusterIP)) { + t.Errorf("Unexpected ClusterIP: %s", created_service_1.Spec.ClusterIP) + } + + svc2 := &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "bar"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }} + ctx = api.NewDefaultContext() + created_svc2, _ := rest.Create(ctx, svc2) + created_service_2 := created_svc2.(*api.Service) + if created_service_2.Name != "bar" { + t.Errorf("Expected bar, but got %v", created_service_2.Name) + } + if !makeIPNet(t).Contains(net.ParseIP(created_service_2.Spec.ClusterIP)) { + t.Errorf("Unexpected ClusterIP: %s", created_service_2.Spec.ClusterIP) + } + + testIPs := []string{"1.2.3.93", "1.2.3.94", "1.2.3.95", "1.2.3.96"} + testIP := "" + for _, ip := range testIPs { + if !rest.serviceIPs.(*ipallocator.Range).Has(net.ParseIP(ip)) { + testIP = ip + break + } + } + + svc3 := &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "quux"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + ClusterIP: testIP, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + } + ctx = api.NewDefaultContext() + created_svc3, err := rest.Create(ctx, svc3) + if err != nil { + t.Fatal(err) + } + created_service_3 := created_svc3.(*api.Service) + if created_service_3.Spec.ClusterIP != testIP { // specific IP + t.Errorf("Unexpected ClusterIP: %s", created_service_3.Spec.ClusterIP) + } +} + +func TestServiceRegistryIPReallocation(t *testing.T) { + rest, _ := NewTestREST(t, nil) + + svc1 := &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + } + ctx := api.NewDefaultContext() + created_svc1, _ := rest.Create(ctx, svc1) + created_service_1 := created_svc1.(*api.Service) + if created_service_1.Name != "foo" { + t.Errorf("Expected foo, but got %v", created_service_1.Name) + } + if !makeIPNet(t).Contains(net.ParseIP(created_service_1.Spec.ClusterIP)) { + t.Errorf("Unexpected ClusterIP: %s", created_service_1.Spec.ClusterIP) + } + + _, err := rest.Delete(ctx, created_service_1.Name) + if err != nil { + t.Errorf("Unexpected error deleting service: %v", err) + } + + svc2 := &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "bar"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + } + ctx = api.NewDefaultContext() + created_svc2, _ := rest.Create(ctx, svc2) + created_service_2 := created_svc2.(*api.Service) + if created_service_2.Name != "bar" { + t.Errorf("Expected bar, but got %v", created_service_2.Name) + } + if !makeIPNet(t).Contains(net.ParseIP(created_service_2.Spec.ClusterIP)) { + t.Errorf("Unexpected ClusterIP: %s", created_service_2.Spec.ClusterIP) + } +} + +func TestServiceRegistryIPUpdate(t *testing.T) { + rest, _ := NewTestREST(t, nil) + + svc := &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + } + ctx := api.NewDefaultContext() + created_svc, _ := rest.Create(ctx, svc) + created_service := created_svc.(*api.Service) + if created_service.Spec.Ports[0].Port != 6502 { + t.Errorf("Expected port 6502, but got %v", created_service.Spec.Ports[0].Port) + } + if !makeIPNet(t).Contains(net.ParseIP(created_service.Spec.ClusterIP)) { + t.Errorf("Unexpected ClusterIP: %s", created_service.Spec.ClusterIP) + } + + update := deepCloneService(created_service) + update.Spec.Ports[0].Port = 6503 + + updated_svc, _, _ := rest.Update(ctx, update) + updated_service := updated_svc.(*api.Service) + if updated_service.Spec.Ports[0].Port != 6503 { + t.Errorf("Expected port 6503, but got %v", updated_service.Spec.Ports[0].Port) + } + + testIPs := []string{"1.2.3.93", "1.2.3.94", "1.2.3.95", "1.2.3.96"} + testIP := "" + for _, ip := range testIPs { + if !rest.serviceIPs.(*ipallocator.Range).Has(net.ParseIP(ip)) { + testIP = ip + break + } + } + + update = deepCloneService(created_service) + update.Spec.Ports[0].Port = 6503 + update.Spec.ClusterIP = testIP // Error: Cluster IP is immutable + + _, _, err := rest.Update(ctx, update) + if err == nil || !errors.IsInvalid(err) { + t.Errorf("Unexpected error type: %v", err) + } +} + +func TestServiceRegistryIPLoadBalancer(t *testing.T) { + rest, _ := NewTestREST(t, nil) + + svc := &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}, + Spec: api.ServiceSpec{ + Selector: map[string]string{"bar": "baz"}, + SessionAffinity: api.ServiceAffinityNone, + Type: api.ServiceTypeLoadBalancer, + Ports: []api.ServicePort{{ + Port: 6502, + Protocol: api.ProtocolTCP, + TargetPort: intstr.FromInt(6502), + }}, + }, + } + ctx := api.NewDefaultContext() + created_svc, _ := rest.Create(ctx, svc) + created_service := created_svc.(*api.Service) + if created_service.Spec.Ports[0].Port != 6502 { + t.Errorf("Expected port 6502, but got %v", created_service.Spec.Ports[0].Port) + } + if !makeIPNet(t).Contains(net.ParseIP(created_service.Spec.ClusterIP)) { + t.Errorf("Unexpected ClusterIP: %s", created_service.Spec.ClusterIP) + } + + update := deepCloneService(created_service) + + _, _, err := rest.Update(ctx, update) + if err != nil { + t.Errorf("Unexpected error %v", err) + } +} + +func TestUpdateServiceWithConflictingNamespace(t *testing.T) { + storage := REST{} + service := &api.Service{ + ObjectMeta: api.ObjectMeta{Name: "test", Namespace: "not-default"}, + } + + ctx := api.NewDefaultContext() + obj, created, err := storage.Update(ctx, service) + if obj != nil || created { + t.Error("Expected a nil object, but we got a value or created was true") + } + if err == nil { + t.Errorf("Expected an error, but we didn't get one") + } else if strings.Index(err.Error(), "Service.Namespace does not match the provided context") == -1 { + t.Errorf("Expected 'Service.Namespace does not match the provided context' error, got '%s'", err.Error()) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/service/strategy.go new file mode 100644 index 000000000..bc87a8107 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/strategy.go @@ -0,0 +1,139 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 service + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// svcStrategy implements behavior for Services +type svcStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Services is the default logic that applies when creating and updating Service +// objects. +var Strategy = svcStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped is true for services. +func (svcStrategy) NamespaceScoped() bool { + return true +} + +// PrepareForCreate clears fields that are not allowed to be set by end users on creation. +func (svcStrategy) PrepareForCreate(obj runtime.Object) { + service := obj.(*api.Service) + service.Status = api.ServiceStatus{} +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (svcStrategy) PrepareForUpdate(obj, old runtime.Object) { + newService := obj.(*api.Service) + oldService := old.(*api.Service) + newService.Status = oldService.Status +} + +// Validate validates a new service. +func (svcStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + service := obj.(*api.Service) + return validation.ValidateService(service) +} + +// Canonicalize normalizes the object after validation. +func (svcStrategy) Canonicalize(obj runtime.Object) { +} + +func (svcStrategy) AllowCreateOnUpdate() bool { + return true +} + +func (svcStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateServiceUpdate(obj.(*api.Service), old.(*api.Service)) +} + +func (svcStrategy) AllowUnconditionalUpdate() bool { + return true +} + +func (svcStrategy) Export(obj runtime.Object, exact bool) error { + t, ok := obj.(*api.Service) + if !ok { + // unexpected programmer error + return fmt.Errorf("unexpected object: %v", obj) + } + // TODO: service does not yet have a prepare create strategy (see above) + t.Status = api.ServiceStatus{} + if exact { + return nil + } + if t.Spec.ClusterIP != api.ClusterIPNone { + t.Spec.ClusterIP = "" + } + if t.Spec.Type == api.ServiceTypeNodePort { + for i := range t.Spec.Ports { + t.Spec.Ports[i].NodePort = 0 + } + } + return nil +} + +func MatchServices(label labels.Selector, field fields.Selector) generic.Matcher { + return &generic.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + service, ok := obj.(*api.Service) + if !ok { + return nil, nil, fmt.Errorf("Given object is not a service") + } + return labels.Set(service.ObjectMeta.Labels), ServiceToSelectableFields(service), nil + }, + } +} + +func ServiceToSelectableFields(service *api.Service) fields.Set { + return generic.ObjectMetaFieldsSet(service.ObjectMeta, true) +} + +type serviceStatusStrategy struct { + svcStrategy +} + +// StatusStrategy is the default logic invoked when updating service status. +var StatusStrategy = serviceStatusStrategy{Strategy} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update of status +func (serviceStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newService := obj.(*api.Service) + oldService := old.(*api.Service) + // status changes are not allowed to update spec + newService.Spec = oldService.Spec +} + +// ValidateUpdate is the default update validation for an end user updating status +func (serviceStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateServiceStatusUpdate(obj.(*api.Service), old.(*api.Service)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/service/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/service/strategy_test.go new file mode 100644 index 000000000..13b6eeb47 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/service/strategy_test.go @@ -0,0 +1,252 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 service + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/intstr" +) + +func TestExportService(t *testing.T) { + tests := []struct { + objIn runtime.Object + objOut runtime.Object + exact bool + expectErr bool + }{ + { + objIn: &api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "bar", + }, + Status: api.ServiceStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{ + {IP: "1.2.3.4"}, + }, + }, + }, + }, + objOut: &api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "bar", + }, + }, + exact: true, + }, + { + objIn: &api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "bar", + }, + Spec: api.ServiceSpec{ + ClusterIP: "10.0.0.1", + }, + Status: api.ServiceStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{ + {IP: "1.2.3.4"}, + }, + }, + }, + }, + objOut: &api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: "bar", + }, + Spec: api.ServiceSpec{ + ClusterIP: "", + }, + }, + }, + { + objIn: &api.Pod{}, + expectErr: true, + }, + } + + for _, test := range tests { + err := Strategy.Export(test.objIn, test.exact) + if err != nil { + if !test.expectErr { + t.Errorf("unexpected error: %v", err) + } + continue + } + if test.expectErr { + t.Error("unexpected non-error") + continue + } + if !reflect.DeepEqual(test.objIn, test.objOut) { + t.Errorf("expected:\n%v\nsaw:\n%v\n", test.objOut, test.objIn) + } + } +} + +func TestCheckGeneratedNameError(t *testing.T) { + expect := errors.NewNotFound(api.Resource("foos"), "bar") + if err := rest.CheckGeneratedNameError(Strategy, expect, &api.Pod{}); err != expect { + t.Errorf("NotFoundError should be ignored: %v", err) + } + + expect = errors.NewAlreadyExists(api.Resource("foos"), "bar") + if err := rest.CheckGeneratedNameError(Strategy, expect, &api.Pod{}); err != expect { + t.Errorf("AlreadyExists should be returned when no GenerateName field: %v", err) + } + + expect = errors.NewAlreadyExists(api.Resource("foos"), "bar") + if err := rest.CheckGeneratedNameError(Strategy, expect, &api.Pod{ObjectMeta: api.ObjectMeta{GenerateName: "foo"}}); err == nil || !errors.IsServerTimeout(err) { + t.Errorf("expected try again later error: %v", err) + } +} + +func makeValidService() api.Service { + return api.Service{ + ObjectMeta: api.ObjectMeta{ + Name: "valid", + Namespace: "default", + Labels: map[string]string{}, + Annotations: map[string]string{}, + ResourceVersion: "1", + }, + Spec: api.ServiceSpec{ + Selector: map[string]string{"key": "val"}, + SessionAffinity: "None", + Type: api.ServiceTypeClusterIP, + Ports: []api.ServicePort{{Name: "p", Protocol: "TCP", Port: 8675, TargetPort: intstr.FromInt(8675)}}, + }, + } +} + +// TODO: This should be done on types that are not part of our API +func TestBeforeUpdate(t *testing.T) { + testCases := []struct { + name string + tweakSvc func(oldSvc, newSvc *api.Service) // given basic valid services, each test case can customize them + expectErr bool + }{ + { + name: "no change", + tweakSvc: func(oldSvc, newSvc *api.Service) { + // nothing + }, + expectErr: false, + }, + { + name: "change port", + tweakSvc: func(oldSvc, newSvc *api.Service) { + newSvc.Spec.Ports[0].Port++ + }, + expectErr: false, + }, + { + name: "bad namespace", + tweakSvc: func(oldSvc, newSvc *api.Service) { + newSvc.Namespace = "#$%%invalid" + }, + expectErr: true, + }, + { + name: "change name", + tweakSvc: func(oldSvc, newSvc *api.Service) { + newSvc.Name += "2" + }, + expectErr: true, + }, + { + name: "change ClusterIP", + tweakSvc: func(oldSvc, newSvc *api.Service) { + oldSvc.Spec.ClusterIP = "1.2.3.4" + newSvc.Spec.ClusterIP = "4.3.2.1" + }, + expectErr: true, + }, + { + name: "change selectpor", + tweakSvc: func(oldSvc, newSvc *api.Service) { + newSvc.Spec.Selector = map[string]string{"newkey": "newvalue"} + }, + expectErr: false, + }, + } + + for _, tc := range testCases { + oldSvc := makeValidService() + newSvc := makeValidService() + tc.tweakSvc(&oldSvc, &newSvc) + ctx := api.NewDefaultContext() + err := rest.BeforeUpdate(Strategy, ctx, runtime.Object(&oldSvc), runtime.Object(&newSvc)) + if tc.expectErr && err == nil { + t.Errorf("unexpected non-error for %q", tc.name) + } + if !tc.expectErr && err != nil { + t.Errorf("unexpected error for %q: %v", tc.name, err) + } + } +} + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "Service", + labels.Set(ServiceToSelectableFields(&api.Service{})), + nil, + ) +} + +func TestServiceStatusStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if !StatusStrategy.NamespaceScoped() { + t.Errorf("Service must be namespace scoped") + } + oldService := makeValidService() + newService := makeValidService() + oldService.ResourceVersion = "4" + newService.ResourceVersion = "4" + newService.Spec.SessionAffinity = "ClientIP" + newService.Status = api.ServiceStatus{ + LoadBalancer: api.LoadBalancerStatus{ + Ingress: []api.LoadBalancerIngress{ + {IP: "127.0.0.2"}, + }, + }, + } + StatusStrategy.PrepareForUpdate(&newService, &oldService) + if newService.Status.LoadBalancer.Ingress[0].IP != "127.0.0.2" { + t.Errorf("Service status updates should allow change of status fields") + } + if newService.Spec.SessionAffinity != "None" { + t.Errorf("PrepareForUpdate should have preserved old spec") + } + errs := StatusStrategy.ValidateUpdate(ctx, &newService, &oldService) + if len(errs) != 0 { + t.Errorf("Unexpected error %v", errs) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/doc.go new file mode 100644 index 000000000..40606205d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 serviceaccount provides a Registry interface and a strategy +// implementation for storing ServiceAccount API objects. +package serviceaccount diff --git a/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/etcd/etcd.go new file mode 100644 index 000000000..e057104f1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/etcd/etcd.go @@ -0,0 +1,68 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/serviceaccount" + "k8s.io/kubernetes/pkg/runtime" +) + +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a RESTStorage object that will work against service accounts. +func NewREST(opts generic.RESTOptions) *REST { + prefix := "/serviceaccounts" + + newListFunc := func() runtime.Object { return &api.ServiceAccountList{} } + storageInterface := opts.Decorator( + opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.ServiceAccounts), &api.ServiceAccount{}, prefix, serviceaccount.Strategy, newListFunc) + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &api.ServiceAccount{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, name string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*api.ServiceAccount).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return serviceaccount.Matcher(label, field) + }, + QualifiedResource: api.Resource("serviceaccounts"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: serviceaccount.Strategy, + UpdateStrategy: serviceaccount.Strategy, + DeleteStrategy: serviceaccount.Strategy, + ReturnDeletedObject: true, + + Storage: storageInterface, + } + return &REST{store} +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/etcd/etcd_test.go new file mode 100644 index 000000000..5f757aa1e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/etcd/etcd_test.go @@ -0,0 +1,123 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + return NewREST(restOptions), server +} + +func validNewServiceAccount(name string) *api.ServiceAccount { + return &api.ServiceAccount{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: api.NamespaceDefault, + }, + Secrets: []api.ObjectReference{}, + } +} + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + serviceAccount := validNewServiceAccount("foo") + serviceAccount.ObjectMeta = api.ObjectMeta{GenerateName: "foo-"} + test.TestCreate( + // valid + serviceAccount, + // invalid + &api.ServiceAccount{}, + &api.ServiceAccount{ + ObjectMeta: api.ObjectMeta{Name: "name with spaces"}, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestUpdate( + // valid + validNewServiceAccount("foo"), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*api.ServiceAccount) + object.Secrets = []api.ObjectReference{{}} + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd).ReturnDeletedObject() + test.TestDelete(validNewServiceAccount("foo")) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(validNewServiceAccount("foo")) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(validNewServiceAccount("foo")) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validNewServiceAccount("foo"), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": "foo"}, + }, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + {"name": "foo"}, + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/registry.go b/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/registry.go new file mode 100644 index 000000000..4dad500bc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/registry.go @@ -0,0 +1,79 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 serviceaccount + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface implemented by things that know how to store ServiceAccount objects. +type Registry interface { + ListServiceAccounts(ctx api.Context, options *api.ListOptions) (*api.ServiceAccountList, error) + WatchServiceAccounts(ctx api.Context, options *api.ListOptions) (watch.Interface, error) + GetServiceAccount(ctx api.Context, name string) (*api.ServiceAccount, error) + CreateServiceAccount(ctx api.Context, ServiceAccount *api.ServiceAccount) error + UpdateServiceAccount(ctx api.Context, ServiceAccount *api.ServiceAccount) error + DeleteServiceAccount(ctx api.Context, name string) error +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListServiceAccounts(ctx api.Context, options *api.ListOptions) (*api.ServiceAccountList, error) { + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + return obj.(*api.ServiceAccountList), nil +} + +func (s *storage) WatchServiceAccounts(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetServiceAccount(ctx api.Context, name string) (*api.ServiceAccount, error) { + obj, err := s.Get(ctx, name) + if err != nil { + return nil, err + } + return obj.(*api.ServiceAccount), nil +} + +func (s *storage) CreateServiceAccount(ctx api.Context, serviceAccount *api.ServiceAccount) error { + _, err := s.Create(ctx, serviceAccount) + return err +} + +func (s *storage) UpdateServiceAccount(ctx api.Context, serviceAccount *api.ServiceAccount) error { + _, _, err := s.Update(ctx, serviceAccount) + return err +} + +func (s *storage) DeleteServiceAccount(ctx api.Context, name string) error { + _, err := s.Delete(ctx, name, nil) + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/strategy.go new file mode 100644 index 000000000..1d1f208f8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/strategy.go @@ -0,0 +1,94 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 serviceaccount + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// strategy implements behavior for ServiceAccount objects +type strategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating ServiceAccount +// objects via the REST API. +var Strategy = strategy{api.Scheme, api.SimpleNameGenerator} + +func (strategy) NamespaceScoped() bool { + return true +} + +func (strategy) PrepareForCreate(obj runtime.Object) { + cleanSecretReferences(obj.(*api.ServiceAccount)) +} + +func (strategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + return validation.ValidateServiceAccount(obj.(*api.ServiceAccount)) +} + +// Canonicalize normalizes the object after validation. +func (strategy) Canonicalize(obj runtime.Object) { +} + +func (strategy) AllowCreateOnUpdate() bool { + return false +} + +func (strategy) PrepareForUpdate(obj, old runtime.Object) { + cleanSecretReferences(obj.(*api.ServiceAccount)) +} + +func cleanSecretReferences(serviceAccount *api.ServiceAccount) { + for i, secret := range serviceAccount.Secrets { + serviceAccount.Secrets[i] = api.ObjectReference{Name: secret.Name} + } +} + +func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateServiceAccountUpdate(obj.(*api.ServiceAccount), old.(*api.ServiceAccount)) +} + +func (strategy) AllowUnconditionalUpdate() bool { + return true +} + +// Matcher returns a generic matcher for a given label and field selector. +func Matcher(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + sa, ok := obj.(*api.ServiceAccount) + if !ok { + return false, fmt.Errorf("not a serviceaccount") + } + fields := SelectableFields(sa) + return label.Matches(labels.Set(sa.Labels)) && field.Matches(fields), nil + }) +} + +// SelectableFields returns a label set that represents the object +func SelectableFields(obj *api.ServiceAccount) labels.Set { + return labels.Set(generic.ObjectMetaFieldsSet(obj.ObjectMeta, true)) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/strategy_test.go new file mode 100644 index 000000000..8530d24ff --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/serviceaccount/strategy_test.go @@ -0,0 +1,34 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 serviceaccount + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" +) + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Default.GroupVersion().String(), + "ServiceAccount", + SelectableFields(&api.ServiceAccount{}), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/doc.go new file mode 100644 index 000000000..7f52880da --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 thirdpartyresource provides Registry interface and its REST +// implementation for storing ThirdPartyResource api objects. +package thirdpartyresource diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd/etcd.go new file mode 100644 index 000000000..edae2ea45 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd/etcd.go @@ -0,0 +1,67 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/thirdpartyresource" + "k8s.io/kubernetes/pkg/runtime" +) + +// REST implements a RESTStorage for ThirdPartyResources against etcd +type REST struct { + *etcdgeneric.Etcd +} + +// NewREST returns a registry which will store ThirdPartyResource in the given helper +func NewREST(opts generic.RESTOptions) *REST { + prefix := "/thirdpartyresources" + + // We explicitly do NOT do any decoration here yet. + storageInterface := opts.Storage + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &extensions.ThirdPartyResource{} }, + NewListFunc: func() runtime.Object { return &extensions.ThirdPartyResourceList{} }, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, id string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, id) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*extensions.ThirdPartyResource).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return thirdpartyresource.Matcher(label, field) + }, + QualifiedResource: extensions.Resource("thirdpartyresources"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + CreateStrategy: thirdpartyresource.Strategy, + UpdateStrategy: thirdpartyresource.Strategy, + DeleteStrategy: thirdpartyresource.Strategy, + + Storage: storageInterface, + } + + return &REST{store} +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd/etcd_test.go new file mode 100644 index 000000000..9ffcd4d66 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/etcd/etcd_test.go @@ -0,0 +1,125 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + // Ensure that extensions/v1beta1 package is initialized. + _ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, extensions.GroupName) + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + return NewREST(restOptions), server +} + +func validNewThirdPartyResource(name string) *extensions.ThirdPartyResource { + return &extensions.ThirdPartyResource{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: api.NamespaceDefault, + }, + Versions: []extensions.APIVersion{ + { + Name: "stable/v1", + }, + }, + } +} + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + rsrc := validNewThirdPartyResource("foo") + rsrc.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + rsrc, + // invalid + &extensions.ThirdPartyResource{}, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestUpdate( + // valid + validNewThirdPartyResource("foo"), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.ThirdPartyResource) + object.Description = "new description" + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestDelete(validNewThirdPartyResource("foo")) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(validNewThirdPartyResource("foo")) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(validNewThirdPartyResource("foo")) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validNewThirdPartyResource("foo"), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{}, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + {"name": "foo"}, + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/strategy.go new file mode 100644 index 000000000..0f466d297 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/strategy.go @@ -0,0 +1,92 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 thirdpartyresource + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/extensions/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// strategy implements behavior for ThirdPartyResource objects +type strategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating ThirdPartyResource +// objects via the REST API. +var Strategy = strategy{api.Scheme, api.SimpleNameGenerator} + +var _ = rest.RESTCreateStrategy(Strategy) + +var _ = rest.RESTUpdateStrategy(Strategy) + +func (strategy) NamespaceScoped() bool { + return true +} + +func (strategy) PrepareForCreate(obj runtime.Object) { +} + +func (strategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + return validation.ValidateThirdPartyResource(obj.(*extensions.ThirdPartyResource)) +} + +// Canonicalize normalizes the object after validation. +func (strategy) Canonicalize(obj runtime.Object) { +} + +func (strategy) AllowCreateOnUpdate() bool { + return false +} + +func (strategy) PrepareForUpdate(obj, old runtime.Object) { +} + +func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateThirdPartyResourceUpdate(obj.(*extensions.ThirdPartyResource), old.(*extensions.ThirdPartyResource)) +} + +func (strategy) AllowUnconditionalUpdate() bool { + return true +} + +// Matcher returns a generic matcher for a given label and field selector. +func Matcher(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + sa, ok := obj.(*extensions.ThirdPartyResource) + if !ok { + return false, fmt.Errorf("not a ThirdPartyResource") + } + fields := SelectableFields(sa) + return label.Matches(labels.Set(sa.Labels)) && field.Matches(fields), nil + }) +} + +// SelectableFields returns a label set that can be used for filter selection +func SelectableFields(obj *extensions.ThirdPartyResource) labels.Set { + return labels.Set{} +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/strategy_test.go new file mode 100644 index 000000000..cbe8f4390 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresource/strategy_test.go @@ -0,0 +1,35 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 thirdpartyresource + +import ( + "testing" + + _ "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Extensions.GroupVersion().String(), + "ThirdPartyResource", + SelectableFields(&extensions.ThirdPartyResource{}), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/codec.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/codec.go new file mode 100644 index 000000000..8a315ca14 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/codec.go @@ -0,0 +1,462 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 thirdpartyresourcedata + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/url" + "strings" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" + apiutil "k8s.io/kubernetes/pkg/api/util" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/apimachinery/registered" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/runtime" +) + +type thirdPartyObjectConverter struct { + converter runtime.ObjectConvertor +} + +func (t *thirdPartyObjectConverter) ConvertToVersion(in runtime.Object, outVersion string) (out runtime.Object, err error) { + switch in.(type) { + // This seems weird, but in this case the ThirdPartyResourceData is really just a wrapper on the raw 3rd party data. + // The actual thing printed/sent to server is the actual raw third party resource data, which only has one version. + case *extensions.ThirdPartyResourceData: + return in, nil + default: + return t.converter.ConvertToVersion(in, outVersion) + } +} + +func (t *thirdPartyObjectConverter) Convert(in, out interface{}) error { + return t.converter.Convert(in, out) +} + +func (t *thirdPartyObjectConverter) ConvertFieldLabel(version, kind, label, value string) (string, string, error) { + return t.converter.ConvertFieldLabel(version, kind, label, value) +} + +func NewThirdPartyObjectConverter(converter runtime.ObjectConvertor) runtime.ObjectConvertor { + return &thirdPartyObjectConverter{converter} +} + +type thirdPartyResourceDataMapper struct { + mapper meta.RESTMapper + kind string + version string + group string +} + +var _ meta.RESTMapper = &thirdPartyResourceDataMapper{} + +func (t *thirdPartyResourceDataMapper) getResource() unversioned.GroupVersionResource { + plural, _ := meta.KindToResource(t.getKind()) + + return plural +} + +func (t *thirdPartyResourceDataMapper) getKind() unversioned.GroupVersionKind { + return unversioned.GroupVersionKind{Group: t.group, Version: t.version, Kind: t.kind} +} + +func (t *thirdPartyResourceDataMapper) isThirdPartyResource(partialResource unversioned.GroupVersionResource) bool { + actualResource := t.getResource() + if strings.ToLower(partialResource.Resource) != strings.ToLower(actualResource.Resource) { + return false + } + if len(partialResource.Group) != 0 && partialResource.Group != actualResource.Group { + return false + } + if len(partialResource.Version) != 0 && partialResource.Version != actualResource.Version { + return false + } + + return true +} + +func (t *thirdPartyResourceDataMapper) ResourcesFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) { + if t.isThirdPartyResource(resource) { + return []unversioned.GroupVersionResource{t.getResource()}, nil + } + return t.mapper.ResourcesFor(resource) +} + +func (t *thirdPartyResourceDataMapper) KindsFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) { + if t.isThirdPartyResource(resource) { + return []unversioned.GroupVersionKind{t.getKind()}, nil + } + return t.mapper.KindsFor(resource) +} + +func (t *thirdPartyResourceDataMapper) ResourceFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) { + if t.isThirdPartyResource(resource) { + return t.getResource(), nil + } + return t.mapper.ResourceFor(resource) +} + +func (t *thirdPartyResourceDataMapper) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) { + if t.isThirdPartyResource(resource) { + return t.getKind(), nil + } + return t.mapper.KindFor(resource) +} + +func (t *thirdPartyResourceDataMapper) RESTMapping(gk unversioned.GroupKind, versions ...string) (*meta.RESTMapping, error) { + if len(versions) != 1 { + return nil, fmt.Errorf("unexpected set of versions: %v", versions) + } + if gk.Group != t.group { + return nil, fmt.Errorf("unknown group %q expected %s", gk.Group, t.group) + } + if gk.Kind != "ThirdPartyResourceData" { + return nil, fmt.Errorf("unknown kind %s expected %s", gk.Kind, t.kind) + } + if versions[0] != t.version { + return nil, fmt.Errorf("unknown version %q expected %q", versions[0], t.version) + } + + // TODO figure out why we're doing this rewriting + extensionGK := unversioned.GroupKind{Group: extensions.GroupName, Kind: "ThirdPartyResourceData"} + + mapping, err := t.mapper.RESTMapping(extensionGK, registered.GroupOrDie(extensions.GroupName).GroupVersion.Version) + if err != nil { + return nil, err + } + mapping.ObjectConvertor = &thirdPartyObjectConverter{mapping.ObjectConvertor} + return mapping, nil +} + +func (t *thirdPartyResourceDataMapper) AliasesForResource(resource string) ([]string, bool) { + return t.mapper.AliasesForResource(resource) +} + +func (t *thirdPartyResourceDataMapper) ResourceSingularizer(resource string) (singular string, err error) { + return t.mapper.ResourceSingularizer(resource) +} + +func NewMapper(mapper meta.RESTMapper, kind, version, group string) meta.RESTMapper { + return &thirdPartyResourceDataMapper{ + mapper: mapper, + kind: kind, + version: version, + group: group, + } +} + +type thirdPartyResourceDataCodecFactory struct { + runtime.NegotiatedSerializer + kind string + encodeGV unversioned.GroupVersion + decodeGV unversioned.GroupVersion +} + +func NewNegotiatedSerializer(s runtime.NegotiatedSerializer, kind string, encodeGV, decodeGV unversioned.GroupVersion) runtime.NegotiatedSerializer { + return &thirdPartyResourceDataCodecFactory{ + NegotiatedSerializer: s, + + kind: kind, + encodeGV: encodeGV, + decodeGV: decodeGV, + } +} + +func (t *thirdPartyResourceDataCodecFactory) EncoderForVersion(s runtime.Serializer, gv unversioned.GroupVersion) runtime.Encoder { + return NewCodec(runtime.NewCodec( + t.NegotiatedSerializer.EncoderForVersion(s, gv), + t.NegotiatedSerializer.DecoderToVersion(s, t.decodeGV), + ), t.kind) +} + +func (t *thirdPartyResourceDataCodecFactory) DecoderToVersion(s runtime.Serializer, gv unversioned.GroupVersion) runtime.Decoder { + return NewCodec(runtime.NewCodec( + t.NegotiatedSerializer.EncoderForVersion(s, t.encodeGV), + t.NegotiatedSerializer.DecoderToVersion(s, gv), + ), t.kind) +} + +type thirdPartyResourceDataCodec struct { + delegate runtime.Codec + kind string +} + +func NewCodec(codec runtime.Codec, kind string) runtime.Codec { + return &thirdPartyResourceDataCodec{codec, kind} +} + +func parseObject(data []byte) (map[string]interface{}, error) { + var obj interface{} + if err := json.Unmarshal(data, &obj); err != nil { + fmt.Printf("Invalid JSON:\n%s\n", string(data)) + return nil, err + } + mapObj, ok := obj.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("unexpected object: %#v", obj) + } + return mapObj, nil +} + +func (t *thirdPartyResourceDataCodec) populate(data []byte) (runtime.Object, error) { + mapObj, err := parseObject(data) + if err != nil { + return nil, err + } + return t.populateFromObject(mapObj, data) +} + +func (t *thirdPartyResourceDataCodec) populateFromObject(mapObj map[string]interface{}, data []byte) (runtime.Object, error) { + typeMeta := unversioned.TypeMeta{} + if err := json.Unmarshal(data, &typeMeta); err != nil { + return nil, err + } + switch typeMeta.Kind { + case t.kind: + result := &extensions.ThirdPartyResourceData{} + if err := t.populateResource(result, mapObj, data); err != nil { + return nil, err + } + return result, nil + case t.kind + "List": + list := &extensions.ThirdPartyResourceDataList{} + if err := t.populateListResource(list, mapObj); err != nil { + return nil, err + } + return list, nil + default: + return nil, fmt.Errorf("unexpected kind: %s, expected %s", typeMeta.Kind, t.kind) + } +} + +func (t *thirdPartyResourceDataCodec) populateResource(objIn *extensions.ThirdPartyResourceData, mapObj map[string]interface{}, data []byte) error { + metadata, ok := mapObj["metadata"].(map[string]interface{}) + if !ok { + return fmt.Errorf("unexpected object for metadata: %#v", mapObj["metadata"]) + } + + metadataData, err := json.Marshal(metadata) + if err != nil { + return err + } + + if err := json.Unmarshal(metadataData, &objIn.ObjectMeta); err != nil { + return err + } + // Override API Version with the ThirdPartyResourceData value + // TODO: fix this hard code + objIn.APIVersion = v1beta1.SchemeGroupVersion.String() + + objIn.Data = data + return nil +} + +func (t *thirdPartyResourceDataCodec) Decode(data []byte, gvk *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) { + if into == nil { + obj, err := t.populate(data) + if err != nil { + return nil, nil, err + } + return obj, gvk, nil + } + thirdParty, ok := into.(*extensions.ThirdPartyResourceData) + if !ok { + return nil, nil, fmt.Errorf("unexpected object: %#v", into) + } + + var dataObj interface{} + if err := json.Unmarshal(data, &dataObj); err != nil { + return nil, nil, err + } + mapObj, ok := dataObj.(map[string]interface{}) + if !ok { + + return nil, nil, fmt.Errorf("unexpected object: %#v", dataObj) + } + /*if gvk.Kind != "ThirdPartyResourceData" { + return nil, nil, fmt.Errorf("unexpected kind: %s", gvk.Kind) + }*/ + actual := &unversioned.GroupVersionKind{} + if kindObj, found := mapObj["kind"]; !found { + if gvk == nil { + return nil, nil, runtime.NewMissingKindErr(string(data)) + } + mapObj["kind"] = gvk.Kind + actual.Kind = gvk.Kind + } else { + kindStr, ok := kindObj.(string) + if !ok { + return nil, nil, fmt.Errorf("unexpected object for 'kind': %v", kindObj) + } + if kindStr != t.kind { + return nil, nil, fmt.Errorf("kind doesn't match, expecting: %s, got %s", gvk.Kind, kindStr) + } + actual.Kind = t.kind + } + if versionObj, found := mapObj["apiVersion"]; !found { + if gvk == nil { + return nil, nil, runtime.NewMissingVersionErr(string(data)) + } + mapObj["apiVersion"] = gvk.GroupVersion().String() + actual.Group, actual.Version = gvk.Group, gvk.Version + } else { + versionStr, ok := versionObj.(string) + if !ok { + return nil, nil, fmt.Errorf("unexpected object for 'apiVersion': %v", versionObj) + } + if gvk != nil && versionStr != gvk.GroupVersion().String() { + return nil, nil, fmt.Errorf("version doesn't match, expecting: %v, got %s", gvk.GroupVersion(), versionStr) + } + gv, err := unversioned.ParseGroupVersion(versionStr) + if err != nil { + return nil, nil, err + } + actual.Group, actual.Version = gv.Group, gv.Version + } + + mapObj, err := parseObject(data) + if err != nil { + return nil, actual, err + } + if err := t.populateResource(thirdParty, mapObj, data); err != nil { + return nil, actual, err + } + return thirdParty, actual, nil +} + +func (t *thirdPartyResourceDataCodec) populateListResource(objIn *extensions.ThirdPartyResourceDataList, mapObj map[string]interface{}) error { + items, ok := mapObj["items"].([]interface{}) + if !ok { + return fmt.Errorf("unexpected object for items: %#v", mapObj["items"]) + } + objIn.Items = make([]extensions.ThirdPartyResourceData, len(items)) + for ix := range items { + objData, err := json.Marshal(items[ix]) + if err != nil { + return err + } + objMap, err := parseObject(objData) + if err != nil { + return err + } + if err := t.populateResource(&objIn.Items[ix], objMap, objData); err != nil { + return err + } + } + return nil +} + +const template = `{ + "kind": "%s", + "items": [ %s ] +}` + +func encodeToJSON(obj *extensions.ThirdPartyResourceData, stream io.Writer) error { + var objOut interface{} + if err := json.Unmarshal(obj.Data, &objOut); err != nil { + return err + } + objMap, ok := objOut.(map[string]interface{}) + if !ok { + return fmt.Errorf("unexpected type: %v", objOut) + } + objMap["metadata"] = obj.ObjectMeta + encoder := json.NewEncoder(stream) + return encoder.Encode(objMap) +} + +func (t *thirdPartyResourceDataCodec) EncodeToStream(obj runtime.Object, stream io.Writer, overrides ...unversioned.GroupVersion) (err error) { + switch obj := obj.(type) { + case *extensions.ThirdPartyResourceData: + return encodeToJSON(obj, stream) + case *extensions.ThirdPartyResourceDataList: + // TODO: There must be a better way to do this... + dataStrings := make([]string, len(obj.Items)) + for ix := range obj.Items { + buff := &bytes.Buffer{} + err := encodeToJSON(&obj.Items[ix], buff) + if err != nil { + return err + } + dataStrings[ix] = buff.String() + } + fmt.Fprintf(stream, template, t.kind+"List", strings.Join(dataStrings, ",")) + return nil + case *unversioned.Status, *unversioned.APIResourceList: + return t.delegate.EncodeToStream(obj, stream, overrides...) + default: + return fmt.Errorf("unexpected object to encode: %#v", obj) + } +} + +func NewObjectCreator(group, version string, delegate runtime.ObjectCreater) runtime.ObjectCreater { + return &thirdPartyResourceDataCreator{group, version, delegate} +} + +type thirdPartyResourceDataCreator struct { + group string + version string + delegate runtime.ObjectCreater +} + +func (t *thirdPartyResourceDataCreator) New(kind unversioned.GroupVersionKind) (out runtime.Object, err error) { + switch kind.Kind { + case "ThirdPartyResourceData": + if apiutil.GetGroupVersion(t.group, t.version) != kind.GroupVersion().String() { + return nil, fmt.Errorf("unknown kind %v", kind) + } + return &extensions.ThirdPartyResourceData{}, nil + case "ThirdPartyResourceDataList": + if apiutil.GetGroupVersion(t.group, t.version) != kind.GroupVersion().String() { + return nil, fmt.Errorf("unknown kind %v", kind) + } + return &extensions.ThirdPartyResourceDataList{}, nil + // TODO: this list needs to be formalized higher in the chain + case "ListOptions", "WatchEvent": + if apiutil.GetGroupVersion(t.group, t.version) == kind.GroupVersion().String() { + // Translate third party group to external group. + gvk := registered.EnabledVersionsForGroup(api.GroupName)[0].WithKind(kind.Kind) + return t.delegate.New(gvk) + } + return t.delegate.New(kind) + default: + return t.delegate.New(kind) + } +} + +func NewThirdPartyParameterCodec(p runtime.ParameterCodec) runtime.ParameterCodec { + return &thirdPartyParameterCodec{p} +} + +type thirdPartyParameterCodec struct { + delegate runtime.ParameterCodec +} + +func (t *thirdPartyParameterCodec) DecodeParameters(parameters url.Values, from unversioned.GroupVersion, into runtime.Object) error { + return t.delegate.DecodeParameters(parameters, v1.SchemeGroupVersion, into) +} + +func (t *thirdPartyParameterCodec) EncodeParameters(obj runtime.Object, to unversioned.GroupVersion) (url.Values, error) { + return t.delegate.EncodeParameters(obj, v1.SchemeGroupVersion) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/codec_test.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/codec_test.go new file mode 100644 index 000000000..188e44bef --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/codec_test.go @@ -0,0 +1,180 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 thirdpartyresourcedata + +import ( + "encoding/json" + "reflect" + "testing" + "time" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/runtime" +) + +type Foo struct { + unversioned.TypeMeta `json:",inline"` + api.ObjectMeta `json:"metadata,omitempty" description:"standard object metadata"` + + SomeField string `json:"someField"` + OtherField int `json:"otherField"` +} + +type FooList struct { + unversioned.TypeMeta `json:",inline"` + unversioned.ListMeta `json:"metadata,omitempty" description:"standard list metadata; see http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata"` + + Items []Foo `json:"items"` +} + +func TestCodec(t *testing.T) { + tests := []struct { + obj *Foo + expectErr bool + name string + }{ + { + obj: &Foo{ObjectMeta: api.ObjectMeta{Name: "bar"}}, + expectErr: true, + name: "missing kind", + }, + { + obj: &Foo{ObjectMeta: api.ObjectMeta{Name: "bar"}, TypeMeta: unversioned.TypeMeta{Kind: "Foo"}}, + name: "basic", + }, + { + obj: &Foo{ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "baz"}, TypeMeta: unversioned.TypeMeta{Kind: "Foo"}}, + name: "resource version", + }, + { + obj: &Foo{ + ObjectMeta: api.ObjectMeta{ + Name: "bar", + CreationTimestamp: unversioned.Time{Time: time.Unix(100, 0)}, + }, + TypeMeta: unversioned.TypeMeta{Kind: "Foo"}, + }, + name: "creation time", + }, + { + obj: &Foo{ + ObjectMeta: api.ObjectMeta{ + Name: "bar", + ResourceVersion: "baz", + Labels: map[string]string{"foo": "bar", "baz": "blah"}, + }, + TypeMeta: unversioned.TypeMeta{Kind: "Foo"}, + }, + name: "labels", + }, + } + for _, test := range tests { + codec := &thirdPartyResourceDataCodec{kind: "Foo", delegate: testapi.Extensions.Codec()} + data, err := json.Marshal(test.obj) + if err != nil { + t.Errorf("[%s] unexpected error: %v", test.name, err) + continue + } + obj, err := runtime.Decode(codec, data) + if err != nil && !test.expectErr { + t.Errorf("[%s] unexpected error: %v", test.name, err) + continue + } + if test.expectErr { + if err == nil { + t.Errorf("[%s] unexpected non-error", test.name) + } + continue + } + rsrcObj, ok := obj.(*extensions.ThirdPartyResourceData) + if !ok { + t.Errorf("[%s] unexpected object: %v", test.name, obj) + continue + } + if !reflect.DeepEqual(rsrcObj.ObjectMeta, test.obj.ObjectMeta) { + t.Errorf("[%s]\nexpected\n%v\nsaw\n%v\n", test.name, rsrcObj.ObjectMeta, test.obj.ObjectMeta) + } + var output Foo + if err := json.Unmarshal(rsrcObj.Data, &output); err != nil { + t.Errorf("[%s] unexpected error: %v", test.name, err) + continue + } + if !reflect.DeepEqual(&output, test.obj) { + t.Errorf("[%s]\nexpected\n%v\nsaw\n%v\n", test.name, test.obj, &output) + } + + data, err = runtime.Encode(codec, rsrcObj) + if err != nil { + t.Errorf("[%s] unexpected error: %v", test.name, err) + } + + var output2 Foo + if err := json.Unmarshal(data, &output2); err != nil { + t.Errorf("[%s] unexpected error: %v", test.name, err) + continue + } + if !reflect.DeepEqual(&output2, test.obj) { + t.Errorf("[%s]\nexpected\n%v\nsaw\n%v\n", test.name, test.obj, &output2) + } + } +} + +func TestCreater(t *testing.T) { + creater := NewObjectCreator("creater group", "creater version", api.Scheme) + tests := []struct { + name string + kind unversioned.GroupVersionKind + expectedObj runtime.Object + expectErr bool + }{ + { + name: "valid ThirdPartyResourceData creation", + kind: unversioned.GroupVersionKind{Group: "creater group", Version: "creater version", Kind: "ThirdPartyResourceData"}, + expectedObj: &extensions.ThirdPartyResourceData{}, + expectErr: false, + }, + { + name: "invalid ThirdPartyResourceData creation", + kind: unversioned.GroupVersionKind{Version: "invalid version", Kind: "ThirdPartyResourceData"}, + expectedObj: nil, + expectErr: true, + }, + { + name: "valid ListOptions creation", + kind: unversioned.GroupVersionKind{Version: "v1", Kind: "ListOptions"}, + expectedObj: &v1.ListOptions{}, + expectErr: false, + }, + } + for _, test := range tests { + out, err := creater.New(test.kind) + if err != nil && !test.expectErr { + t.Errorf("[%s] unexpected error: %v", test.name, err) + } + if err == nil && test.expectErr { + t.Errorf("[%s] unexpected non-error", test.name) + } + if !reflect.DeepEqual(test.expectedObj, out) { + t.Errorf("[%s] unexpected error: expect: %v, got: %v", test.name, test.expectedObj, out) + } + + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/doc.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/doc.go new file mode 100644 index 000000000..62e2dc1e3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 thirdpartyresourcedata provides Registry interface and its REST +// implementation for storing ThirdPartyResourceData api objects. +package thirdpartyresourcedata diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/etcd/etcd.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/etcd/etcd.go new file mode 100644 index 000000000..4dd4adfb9 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/etcd/etcd.go @@ -0,0 +1,78 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "strings" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd" + "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata" + "k8s.io/kubernetes/pkg/runtime" +) + +// REST implements a RESTStorage for ThirdPartyResourceDatas against etcd +type REST struct { + *etcdgeneric.Etcd + kind string +} + +// NewREST returns a registry which will store ThirdPartyResourceData in the given helper +func NewREST(opts generic.RESTOptions, group, kind string) *REST { + prefix := "/ThirdPartyResourceData/" + group + "/" + strings.ToLower(kind) + "s" + + // We explicitly do NOT do any decoration here yet. + storageInterface := opts.Storage + + store := &etcdgeneric.Etcd{ + NewFunc: func() runtime.Object { return &extensions.ThirdPartyResourceData{} }, + NewListFunc: func() runtime.Object { return &extensions.ThirdPartyResourceDataList{} }, + KeyRootFunc: func(ctx api.Context) string { + return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) + }, + KeyFunc: func(ctx api.Context, id string) (string, error) { + return etcdgeneric.NamespaceKeyFunc(ctx, prefix, id) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*extensions.ThirdPartyResourceData).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return thirdpartyresourcedata.Matcher(label, field) + }, + QualifiedResource: extensions.Resource("thirdpartyresourcedatas"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + CreateStrategy: thirdpartyresourcedata.Strategy, + UpdateStrategy: thirdpartyresourcedata.Strategy, + DeleteStrategy: thirdpartyresourcedata.Strategy, + + Storage: storageInterface, + } + + return &REST{ + Etcd: store, + kind: kind, + } +} + +// Implements the rest.KindProvider interface +func (r *REST) Kind() string { + return r.kind +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/etcd/etcd_test.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/etcd/etcd_test.go new file mode 100644 index 000000000..84d40486f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/etcd/etcd_test.go @@ -0,0 +1,121 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + // Ensure that extensions/v1beta1 package is initialized. + _ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" +) + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, extensions.GroupName) + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + return NewREST(restOptions, "foo", "bar"), server +} + +func validNewThirdPartyResourceData(name string) *extensions.ThirdPartyResourceData { + return &extensions.ThirdPartyResourceData{ + ObjectMeta: api.ObjectMeta{ + Name: name, + Namespace: api.NamespaceDefault, + }, + Data: []byte("foobarbaz"), + } +} + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + rsrc := validNewThirdPartyResourceData("foo") + rsrc.ObjectMeta = api.ObjectMeta{} + test.TestCreate( + // valid + rsrc, + // invalid + &extensions.ThirdPartyResourceData{}, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestUpdate( + // valid + validNewThirdPartyResourceData("foo"), + // updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.ThirdPartyResourceData) + object.Data = []byte("new description") + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestDelete(validNewThirdPartyResourceData("foo")) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestGet(validNewThirdPartyResourceData("foo")) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestList(validNewThirdPartyResourceData("foo")) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Etcd) + test.TestWatch( + validNewThirdPartyResourceData("foo"), + // matching labels + []labels.Set{}, + // not matching labels + []labels.Set{ + {"foo": "bar"}, + }, + // matching fields + []fields.Set{}, + // not matching fields + []fields.Set{ + {"metadata.name": "bar"}, + {"name": "foo"}, + }, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/registry.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/registry.go new file mode 100644 index 000000000..5e560dede --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/registry.go @@ -0,0 +1,80 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 thirdpartyresourcedata + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface implemented by things that know how to store ThirdPartyResourceData objects. +type Registry interface { + ListThirdPartyResourceData(ctx api.Context, options *api.ListOptions) (*extensions.ThirdPartyResourceDataList, error) + WatchThirdPartyResourceData(ctx api.Context, options *api.ListOptions) (watch.Interface, error) + GetThirdPartyResourceData(ctx api.Context, name string) (*extensions.ThirdPartyResourceData, error) + CreateThirdPartyResourceData(ctx api.Context, resource *extensions.ThirdPartyResourceData) (*extensions.ThirdPartyResourceData, error) + UpdateThirdPartyResourceData(ctx api.Context, resource *extensions.ThirdPartyResourceData) (*extensions.ThirdPartyResourceData, error) + DeleteThirdPartyResourceData(ctx api.Context, name string) error +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListThirdPartyResourceData(ctx api.Context, options *api.ListOptions) (*extensions.ThirdPartyResourceDataList, error) { + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + return obj.(*extensions.ThirdPartyResourceDataList), nil +} + +func (s *storage) WatchThirdPartyResourceData(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetThirdPartyResourceData(ctx api.Context, name string) (*extensions.ThirdPartyResourceData, error) { + obj, err := s.Get(ctx, name) + if err != nil { + return nil, err + } + return obj.(*extensions.ThirdPartyResourceData), nil +} + +func (s *storage) CreateThirdPartyResourceData(ctx api.Context, ThirdPartyResourceData *extensions.ThirdPartyResourceData) (*extensions.ThirdPartyResourceData, error) { + obj, err := s.Create(ctx, ThirdPartyResourceData) + return obj.(*extensions.ThirdPartyResourceData), err +} + +func (s *storage) UpdateThirdPartyResourceData(ctx api.Context, ThirdPartyResourceData *extensions.ThirdPartyResourceData) (*extensions.ThirdPartyResourceData, error) { + obj, _, err := s.Update(ctx, ThirdPartyResourceData) + return obj.(*extensions.ThirdPartyResourceData), err +} + +func (s *storage) DeleteThirdPartyResourceData(ctx api.Context, name string) error { + _, err := s.Delete(ctx, name, nil) + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/strategy.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/strategy.go new file mode 100644 index 000000000..9f7673d7c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/strategy.go @@ -0,0 +1,92 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 thirdpartyresourcedata + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/apis/extensions/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// strategy implements behavior for ThirdPartyResource objects +type strategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating ThirdPartyResource +// objects via the REST API. +var Strategy = strategy{api.Scheme, api.SimpleNameGenerator} + +var _ = rest.RESTCreateStrategy(Strategy) + +var _ = rest.RESTUpdateStrategy(Strategy) + +func (strategy) NamespaceScoped() bool { + return true +} + +func (strategy) PrepareForCreate(obj runtime.Object) { +} + +func (strategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + return validation.ValidateThirdPartyResourceData(obj.(*extensions.ThirdPartyResourceData)) +} + +// Canonicalize normalizes the object after validation. +func (strategy) Canonicalize(obj runtime.Object) { +} + +func (strategy) AllowCreateOnUpdate() bool { + return false +} + +func (strategy) PrepareForUpdate(obj, old runtime.Object) { +} + +func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateThirdPartyResourceDataUpdate(obj.(*extensions.ThirdPartyResourceData), old.(*extensions.ThirdPartyResourceData)) +} + +func (strategy) AllowUnconditionalUpdate() bool { + return true +} + +// Matcher returns a generic matcher for a given label and field selector. +func Matcher(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + sa, ok := obj.(*extensions.ThirdPartyResourceData) + if !ok { + return false, fmt.Errorf("not a ThirdPartyResourceData") + } + fields := SelectableFields(sa) + return label.Matches(labels.Set(sa.Labels)) && field.Matches(fields), nil + }) +} + +// SelectableFields returns a label set that can be used for filter selection +func SelectableFields(obj *extensions.ThirdPartyResourceData) labels.Set { + return labels.Set{} +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/strategy_test.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/strategy_test.go new file mode 100644 index 000000000..75e821944 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/strategy_test.go @@ -0,0 +1,35 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 thirdpartyresourcedata + +import ( + "testing" + + _ "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + apitesting "k8s.io/kubernetes/pkg/api/testing" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestSelectableFieldLabelConversions(t *testing.T) { + apitesting.TestSelectableFieldLabelConversionsOfKind(t, + testapi.Extensions.GroupVersion().String(), + "ThirdPartyResourceData", + SelectableFields(&extensions.ThirdPartyResourceData{}), + nil, + ) +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/util.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/util.go new file mode 100644 index 000000000..120981e85 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/util.go @@ -0,0 +1,68 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 thirdpartyresourcedata + +import ( + "fmt" + "strings" + + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func ExtractGroupVersionKind(list *extensions.ThirdPartyResourceList) ([]unversioned.GroupVersion, []unversioned.GroupVersionKind, error) { + gvs := []unversioned.GroupVersion{} + gvks := []unversioned.GroupVersionKind{} + for ix := range list.Items { + rsrc := &list.Items[ix] + kind, group, err := ExtractApiGroupAndKind(rsrc) + if err != nil { + return nil, nil, err + } + for _, version := range rsrc.Versions { + gv := unversioned.GroupVersion{Group: group, Version: version.Name} + gvs = append(gvs, gv) + gvks = append(gvks, unversioned.GroupVersionKind{Group: group, Version: version.Name, Kind: kind}) + } + } + return gvs, gvks, nil +} + +func convertToCamelCase(input string) string { + result := "" + toUpper := true + for ix := range input { + char := input[ix] + if toUpper { + result = result + string([]byte{(char - 32)}) + toUpper = false + } else if char == '-' { + toUpper = true + } else { + result = result + string([]byte{char}) + } + } + return result +} + +func ExtractApiGroupAndKind(rsrc *extensions.ThirdPartyResource) (kind string, group string, err error) { + parts := strings.Split(rsrc.Name, ".") + if len(parts) < 3 { + return "", "", fmt.Errorf("unexpectedly short resource name: %s, expected at least ..", rsrc.Name) + } + return convertToCamelCase(parts[0]), strings.Join(parts[1:], "."), nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/util_test.go b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/util_test.go new file mode 100644 index 000000000..a18722c17 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/util_test.go @@ -0,0 +1,66 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 thirdpartyresourcedata + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestExtractAPIGroupAndKind(t *testing.T) { + tests := []struct { + input string + expectedKind string + expectedGroup string + expectErr bool + }{ + { + input: "foo.company.com", + expectedKind: "Foo", + expectedGroup: "company.com", + }, + { + input: "cron-tab.company.com", + expectedKind: "CronTab", + expectedGroup: "company.com", + }, + { + input: "foo", + expectErr: true, + }, + } + + for _, test := range tests { + kind, group, err := ExtractApiGroupAndKind(&extensions.ThirdPartyResource{ObjectMeta: api.ObjectMeta{Name: test.input}}) + if err != nil && !test.expectErr { + t.Errorf("unexpected error: %v", err) + continue + } + if err == nil && test.expectErr { + t.Errorf("unexpected non-error") + continue + } + if kind != test.expectedKind { + t.Errorf("expected: %s, saw: %s", test.expectedKind, kind) + } + if group != test.expectedGroup { + t.Errorf("expected: %s, saw: %s", test.expectedGroup, group) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/OWNERS b/vendor/k8s.io/kubernetes/pkg/runtime/OWNERS new file mode 100644 index 000000000..d038b5e9b --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/runtime/OWNERS @@ -0,0 +1,5 @@ +assignees: + - caesarxuchao + - deads2k + - lavalamp + - smarterclayton diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/codec_check.go b/vendor/k8s.io/kubernetes/pkg/runtime/codec_check.go new file mode 100644 index 000000000..09e7d51ad --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/runtime/codec_check.go @@ -0,0 +1,50 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 runtime + +import ( + "fmt" + "reflect" + + "k8s.io/kubernetes/pkg/api/unversioned" +) + +// CheckCodec makes sure that the codec can encode objects like internalType, +// decode all of the external types listed, and also decode them into the given +// object. (Will modify internalObject.) (Assumes JSON serialization.) +// TODO: verify that the correct external version is chosen on encode... +func CheckCodec(c Codec, internalType Object, externalTypes ...unversioned.GroupVersionKind) error { + _, err := Encode(c, internalType) + if err != nil { + return fmt.Errorf("Internal type not encodable: %v", err) + } + for _, et := range externalTypes { + exBytes := []byte(fmt.Sprintf(`{"kind":"%v","apiVersion":"%v"}`, et.Kind, et.GroupVersion().String())) + obj, err := Decode(c, exBytes) + if err != nil { + return fmt.Errorf("external type %s not interpretable: %v", et, err) + } + if reflect.TypeOf(obj) != reflect.TypeOf(internalType) { + return fmt.Errorf("decode of external type %s produced: %#v", et, obj) + } + err = DecodeInto(c, exBytes, internalType) + if err != nil { + return fmt.Errorf("external type %s not convertable to internal type: %v", et, err) + } + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/conversion.go b/vendor/k8s.io/kubernetes/pkg/runtime/conversion.go index c13d9d042..69cf00fea 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/conversion.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/conversion.go @@ -40,13 +40,13 @@ func JSONKeyMapper(key string, sourceTag, destTag reflect.StructTag) (string, st // DefaultStringConversions are helpers for converting []string and string to real values. var DefaultStringConversions = []interface{}{ - convertStringSliceToString, - convertStringSliceToInt, - convertStringSliceToBool, - convertStringSliceToInt64, + Convert_Slice_string_To_string, + Convert_Slice_string_To_int, + Convert_Slice_string_To_bool, + Convert_Slice_string_To_int64, } -func convertStringSliceToString(input *[]string, out *string, s conversion.Scope) error { +func Convert_Slice_string_To_string(input *[]string, out *string, s conversion.Scope) error { if len(*input) == 0 { *out = "" } @@ -54,7 +54,7 @@ func convertStringSliceToString(input *[]string, out *string, s conversion.Scope return nil } -func convertStringSliceToInt(input *[]string, out *int, s conversion.Scope) error { +func Convert_Slice_string_To_int(input *[]string, out *int, s conversion.Scope) error { if len(*input) == 0 { *out = 0 } @@ -67,10 +67,10 @@ func convertStringSliceToInt(input *[]string, out *int, s conversion.Scope) erro return nil } -// converStringSliceToBool will convert a string parameter to boolean. +// Conver_Slice_string_To_bool will convert a string parameter to boolean. // Only the absence of a value, a value of "false", or a value of "0" resolve to false. // Any other value (including empty string) resolves to true. -func convertStringSliceToBool(input *[]string, out *bool, s conversion.Scope) error { +func Convert_Slice_string_To_bool(input *[]string, out *bool, s conversion.Scope) error { if len(*input) == 0 { *out = false return nil @@ -84,7 +84,7 @@ func convertStringSliceToBool(input *[]string, out *bool, s conversion.Scope) er return nil } -func convertStringSliceToInt64(input *[]string, out *int64, s conversion.Scope) error { +func Convert_Slice_string_To_int64(input *[]string, out *int64, s conversion.Scope) error { if len(*input) == 0 { *out = 0 } diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/conversion_generator.go b/vendor/k8s.io/kubernetes/pkg/runtime/conversion_generator.go index 9971b7b20..f63c95728 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/conversion_generator.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/conversion_generator.go @@ -302,11 +302,15 @@ func (g *conversionGenerator) generateConversionsForSlice(inType, outType reflec } func (g *conversionGenerator) generateConversionsForStruct(inType, outType reflect.Type) error { + errs := []string{} for i := 0; i < inType.NumField(); i++ { inField := inType.Field(i) outField, found := outType.FieldByName(inField.Name) if !found { - return fmt.Errorf("couldn't find a corresponding field %v in %v", inField.Name, outType) + // aggregate the errors so we can return them at the end but still provide + // best effort for generation for other fields in this type + errs = append(errs, fmt.Sprintf("couldn't find a corresponding field %v in %v", inField.Name, outType)) + continue } if isComplexType(inField.Type) { if err := g.generateConversionsBetween(inField.Type, outField.Type); err != nil { @@ -314,7 +318,11 @@ func (g *conversionGenerator) generateConversionsForStruct(inType, outType refle } } } - return nil + + if len(errs) == 0 { + return nil + } + return fmt.Errorf(strings.Join(errs, ",")) } // A buffer of lines that will be written. diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/conversion_generator_test.go b/vendor/k8s.io/kubernetes/pkg/runtime/conversion_generator_test.go new file mode 100644 index 000000000..f97fbf81e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/runtime/conversion_generator_test.go @@ -0,0 +1,76 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 runtime + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api/unversioned" +) + +type InternalSubtype struct { + String string +} + +type Internal struct { + TypeMeta + Bool bool + Complex InternalSubtype +} + +type ExternalSubtype struct { + String string +} + +type External struct { + TypeMeta + Complex ExternalSubtype +} + +func (obj *Internal) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } +func (obj *External) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } + +func TestGenerateConversionsForStruct(t *testing.T) { + internalGV := unversioned.GroupVersion{Group: "test.group", Version: APIVersionInternal} + externalGV := unversioned.GroupVersion{Group: "test.group", Version: "external"} + + scheme := NewScheme() + scheme.Log(t) + scheme.AddKnownTypeWithName(internalGV.WithKind("Complex"), &Internal{}) + scheme.AddKnownTypeWithName(externalGV.WithKind("Complex"), &External{}) + + generator := NewConversionGenerator(scheme, "foo") + typedGenerator, ok := generator.(*conversionGenerator) + if !ok { + t.Fatalf("error converting to conversionGenerator") + } + + internalType := reflect.TypeOf(Internal{}) + externalType := reflect.TypeOf(External{}) + err := typedGenerator.generateConversionsForStruct(internalType, externalType) + + if err == nil { + t.Errorf("expected error for asymmetrical field") + } + + // we are expecting Convert_runtime_InternalSubtype_To_runtime_ExternalSubtype to be generated + // even though the conversion for the parent type cannot be auto generated + if len(typedGenerator.publicFuncs) != 1 { + t.Errorf("expected to find one public conversion for the Complex type but found: %v", typedGenerator.publicFuncs) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/runtime/deep_copy_generated.go new file mode 100644 index 000000000..f368440a3 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/runtime/deep_copy_generated.go @@ -0,0 +1,120 @@ +// +build !ignore_autogenerated + +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package runtime + +import ( + unversioned "k8s.io/kubernetes/pkg/api/unversioned" + conversion "k8s.io/kubernetes/pkg/conversion" + reflect "reflect" +) + +func DeepCopy_runtime_RawExtension(in RawExtension, out *RawExtension, c *conversion.Cloner) error { + if in.Raw != nil { + in, out := in.Raw, &out.Raw + *out = make([]byte, len(in)) + copy(*out, in) + } else { + out.Raw = nil + } + if in.Object == nil { + out.Object = nil + } else if newVal, err := c.DeepCopy(in.Object); err != nil { + return err + } else { + out.Object = newVal.(Object) + } + return nil +} + +func DeepCopy_runtime_Scheme(in Scheme, out *Scheme, c *conversion.Cloner) error { + if in.gvkToType != nil { + in, out := in.gvkToType, &out.gvkToType + *out = make(map[unversioned.GroupVersionKind]reflect.Type) + for range in { + // FIXME: Copying unassignable keys unsupported unversioned.GroupVersionKind + } + } else { + out.gvkToType = nil + } + if in.typeToGVK != nil { + in, out := in.typeToGVK, &out.typeToGVK + *out = make(map[reflect.Type][]unversioned.GroupVersionKind) + for range in { + // FIXME: Copying unassignable keys unsupported reflect.Type + } + } else { + out.typeToGVK = nil + } + if in.unversionedTypes != nil { + in, out := in.unversionedTypes, &out.unversionedTypes + *out = make(map[reflect.Type]unversioned.GroupVersionKind) + for range in { + // FIXME: Copying unassignable keys unsupported reflect.Type + } + } else { + out.unversionedTypes = nil + } + if in.unversionedKinds != nil { + in, out := in.unversionedKinds, &out.unversionedKinds + *out = make(map[string]reflect.Type) + for key, val := range in { + if newVal, err := c.DeepCopy(val); err != nil { + return err + } else { + (*out)[key] = newVal.(reflect.Type) + } + } + } else { + out.unversionedKinds = nil + } + if in.fieldLabelConversionFuncs != nil { + in, out := in.fieldLabelConversionFuncs, &out.fieldLabelConversionFuncs + *out = make(map[string]map[string]FieldLabelConversionFunc) + for key, val := range in { + if newVal, err := c.DeepCopy(val); err != nil { + return err + } else { + (*out)[key] = newVal.(map[string]FieldLabelConversionFunc) + } + } + } else { + out.fieldLabelConversionFuncs = nil + } + if in.converter != nil { + in, out := in.converter, &out.converter + *out = new(conversion.Converter) + if err := conversion.DeepCopy_conversion_Converter(*in, *out, c); err != nil { + return err + } + } else { + out.converter = nil + } + if in.cloner != nil { + in, out := in.cloner, &out.cloner + *out = new(conversion.Cloner) + if err := conversion.DeepCopy_conversion_Cloner(*in, *out, c); err != nil { + return err + } + } else { + out.cloner = nil + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/deep_copy_generator.go b/vendor/k8s.io/kubernetes/pkg/runtime/deep_copy_generator.go deleted file mode 100644 index 4790969f1..000000000 --- a/vendor/k8s.io/kubernetes/pkg/runtime/deep_copy_generator.go +++ /dev/null @@ -1,609 +0,0 @@ -/* -Copyright 2015 The Kubernetes Authors All rights reserved. - -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 runtime - -import ( - "fmt" - "io" - "path" - "reflect" - "sort" - "strings" - - "k8s.io/kubernetes/pkg/util/sets" -) - -// TODO(wojtek-t): As suggested in #8320, we should consider the strategy -// to first do the shallow copy and then recurse into things that need a -// deep copy (maps, pointers, slices). That sort of copy function would -// need one parameter - a pointer to the thing it's supposed to expand, -// and it would involve a lot less memory copying. -type DeepCopyGenerator interface { - // Adds a type to a generator. - // If the type is non-struct, it will return an error, otherwise deep-copy - // functions for this type and all nested types will be generated. - AddType(inType reflect.Type) error - - // ReplaceType registers a type that should be used instead of the type - // with the provided pkgPath and name. - ReplaceType(pkgPath, name string, in interface{}) - - // AddImport registers a package name with the generator and returns its - // short name. - AddImport(pkgPath string) string - - // RepackImports creates a stable ordering of import short names - RepackImports() - - // Writes all imports that are necessary for deep-copy function and - // their registration. - WriteImports(w io.Writer) error - - // Writes deel-copy functions for all types added via AddType() method - // and their nested types. - WriteDeepCopyFunctions(w io.Writer) error - - // Writes an init() function that registers all the generated deep-copy - // functions. - RegisterDeepCopyFunctions(w io.Writer, pkg string) error - - // When generating code, all references to "pkg" package name will be - // replaced with "overwrite". It is used mainly to replace references - // to name of the package in which the code will be created with empty - // string. - OverwritePackage(pkg, overwrite string) -} - -func NewDeepCopyGenerator(scheme *Scheme, targetPkg string, include sets.String) DeepCopyGenerator { - g := &deepCopyGenerator{ - scheme: scheme, - targetPkg: targetPkg, - copyables: make(map[reflect.Type]bool), - imports: make(map[string]string), - shortImports: make(map[string]string), - pkgOverwrites: make(map[string]string), - replace: make(map[pkgPathNamePair]reflect.Type), - include: include, - } - g.targetPackage(targetPkg) - g.AddImport("k8s.io/kubernetes/pkg/conversion") - return g -} - -type pkgPathNamePair struct { - PkgPath string - Name string -} - -type deepCopyGenerator struct { - scheme *Scheme - targetPkg string - copyables map[reflect.Type]bool - // map of package names to shortname - imports map[string]string - // map of short names to package names - shortImports map[string]string - pkgOverwrites map[string]string - replace map[pkgPathNamePair]reflect.Type - include sets.String -} - -func (g *deepCopyGenerator) addImportByPath(pkg string) string { - if name, ok := g.imports[pkg]; ok { - return name - } - name := path.Base(pkg) - if _, ok := g.shortImports[name]; !ok { - g.imports[pkg] = name - g.shortImports[name] = pkg - return name - } - if dirname := path.Base(path.Dir(pkg)); len(dirname) > 0 { - name = dirname + name - if _, ok := g.shortImports[name]; !ok { - g.imports[pkg] = name - g.shortImports[name] = pkg - return name - } - if subdirname := path.Base(path.Dir(path.Dir(pkg))); len(subdirname) > 0 { - name = subdirname + name - if _, ok := g.shortImports[name]; !ok { - g.imports[pkg] = name - g.shortImports[name] = pkg - return name - } - } - } - for i := 2; i < 100; i++ { - generatedName := fmt.Sprintf("%s%d", name, i) - if _, ok := g.shortImports[generatedName]; !ok { - g.imports[pkg] = generatedName - g.shortImports[generatedName] = pkg - return generatedName - } - } - panic(fmt.Sprintf("unable to find a unique name for the package path %q: %v", pkg, g.shortImports)) -} - -func (g *deepCopyGenerator) targetPackage(pkg string) { - g.imports[pkg] = "" - g.shortImports[""] = pkg -} - -func (g *deepCopyGenerator) addAllRecursiveTypes(inType reflect.Type) error { - if _, found := g.copyables[inType]; found { - return nil - } - switch inType.Kind() { - case reflect.Map: - if err := g.addAllRecursiveTypes(inType.Key()); err != nil { - return err - } - if err := g.addAllRecursiveTypes(inType.Elem()); err != nil { - return err - } - case reflect.Slice, reflect.Ptr: - if err := g.addAllRecursiveTypes(inType.Elem()); err != nil { - return err - } - case reflect.Interface: - g.addImportByPath(inType.PkgPath()) - return nil - case reflect.Struct: - g.addImportByPath(inType.PkgPath()) - found := false - for s := range g.include { - if strings.HasPrefix(inType.PkgPath(), s) { - found = true - break - } - } - if !found { - return nil - } - for i := 0; i < inType.NumField(); i++ { - inField := inType.Field(i) - if err := g.addAllRecursiveTypes(inField.Type); err != nil { - return err - } - } - g.copyables[inType] = true - default: - // Simple types should be copied automatically. - } - return nil -} - -func (g *deepCopyGenerator) AddImport(pkg string) string { - return g.addImportByPath(pkg) -} - -// ReplaceType registers a replacement type to be used instead of the named type -func (g *deepCopyGenerator) ReplaceType(pkgPath, name string, t interface{}) { - g.replace[pkgPathNamePair{pkgPath, name}] = reflect.TypeOf(t) -} - -func (g *deepCopyGenerator) AddType(inType reflect.Type) error { - if inType.Kind() != reflect.Struct { - return fmt.Errorf("non-struct copies are not supported") - } - return g.addAllRecursiveTypes(inType) -} - -func (g *deepCopyGenerator) RepackImports() { - var packages []string - for key := range g.imports { - packages = append(packages, key) - } - sort.Strings(packages) - g.imports = make(map[string]string) - g.shortImports = make(map[string]string) - - g.targetPackage(g.targetPkg) - for _, pkg := range packages { - g.addImportByPath(pkg) - } -} - -func (g *deepCopyGenerator) WriteImports(w io.Writer) error { - var packages []string - for key := range g.imports { - packages = append(packages, key) - } - sort.Strings(packages) - - buffer := newBuffer() - indent := 0 - buffer.addLine("import (\n", indent) - for _, importPkg := range packages { - if len(importPkg) == 0 { - continue - } - if len(g.imports[importPkg]) == 0 { - continue - } - buffer.addLine(fmt.Sprintf("%s \"%s\"\n", g.imports[importPkg], importPkg), indent+1) - } - buffer.addLine(")\n", indent) - buffer.addLine("\n", indent) - if err := buffer.flushLines(w); err != nil { - return err - } - return nil -} - -type byPkgAndName []reflect.Type - -func (s byPkgAndName) Len() int { - return len(s) -} - -func (s byPkgAndName) Less(i, j int) bool { - fullNameI := s[i].PkgPath() + "/" + s[i].Name() - fullNameJ := s[j].PkgPath() + "/" + s[j].Name() - return fullNameI < fullNameJ -} - -func (s byPkgAndName) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -func (g *deepCopyGenerator) nameForType(inType reflect.Type) string { - switch inType.Kind() { - case reflect.Slice: - return fmt.Sprintf("[]%s", g.typeName(inType.Elem())) - case reflect.Ptr: - return fmt.Sprintf("*%s", g.typeName(inType.Elem())) - case reflect.Map: - if len(inType.Name()) == 0 { - return fmt.Sprintf("map[%s]%s", g.typeName(inType.Key()), g.typeName(inType.Elem())) - } - fallthrough - default: - pkg, name := inType.PkgPath(), inType.Name() - if len(name) == 0 && inType.Kind() == reflect.Struct { - return "struct{}" - } - if len(pkg) == 0 { - // Default package. - return name - } - if val, found := g.pkgOverwrites[pkg]; found { - pkg = val - } - if len(pkg) == 0 { - return name - } - short := g.addImportByPath(pkg) - if len(short) > 0 { - return fmt.Sprintf("%s.%s", short, name) - } - return name - } -} - -func (g *deepCopyGenerator) typeName(inType reflect.Type) string { - if t, ok := g.replace[pkgPathNamePair{inType.PkgPath(), inType.Name()}]; ok { - return g.nameForType(t) - } - return g.nameForType(inType) -} - -func (g *deepCopyGenerator) deepCopyFunctionName(inType reflect.Type) string { - funcNameFormat := "deepCopy_%s_%s" - inPkg := packageForName(inType) - funcName := fmt.Sprintf(funcNameFormat, inPkg, inType.Name()) - return funcName -} - -func (g *deepCopyGenerator) writeHeader(b *buffer, inType reflect.Type, indent int) { - format := "func %s(in %s, out *%s, c *conversion.Cloner) error {\n" - stmt := fmt.Sprintf(format, g.deepCopyFunctionName(inType), g.typeName(inType), g.typeName(inType)) - b.addLine(stmt, indent) -} - -func (g *deepCopyGenerator) writeFooter(b *buffer, indent int) { - b.addLine("return nil\n", indent+1) - b.addLine("}\n", indent) -} - -func (g *deepCopyGenerator) WriteDeepCopyFunctions(w io.Writer) error { - var keys []reflect.Type - for key := range g.copyables { - keys = append(keys, key) - } - sort.Sort(byPkgAndName(keys)) - - buffer := newBuffer() - indent := 0 - for _, inType := range keys { - if err := g.writeDeepCopyForType(buffer, inType, indent); err != nil { - return err - } - buffer.addLine("\n", 0) - } - if err := buffer.flushLines(w); err != nil { - return err - } - return nil -} - -func (g *deepCopyGenerator) writeDeepCopyForMap(b *buffer, inField reflect.StructField, indent int) error { - ifFormat := "if in.%s != nil {\n" - ifStmt := fmt.Sprintf(ifFormat, inField.Name) - b.addLine(ifStmt, indent) - newFormat := "out.%s = make(%s)\n" - newStmt := fmt.Sprintf(newFormat, inField.Name, g.typeName(inField.Type)) - b.addLine(newStmt, indent+1) - forFormat := "for key, val := range in.%s {\n" - forStmt := fmt.Sprintf(forFormat, inField.Name) - b.addLine(forStmt, indent+1) - - switch inField.Type.Key().Kind() { - case reflect.Map, reflect.Ptr, reflect.Slice, reflect.Interface, reflect.Struct: - return fmt.Errorf("not supported") - default: - switch inField.Type.Elem().Kind() { - case reflect.Map, reflect.Ptr, reflect.Slice, reflect.Interface, reflect.Struct: - if _, found := g.copyables[inField.Type.Elem()]; found { - newFormat := "newVal := new(%s)\n" - newStmt := fmt.Sprintf(newFormat, g.typeName(inField.Type.Elem())) - b.addLine(newStmt, indent+2) - assignFormat := "if err := %s(val, newVal, c); err != nil {\n" - funcName := g.deepCopyFunctionName(inField.Type.Elem()) - assignStmt := fmt.Sprintf(assignFormat, funcName) - b.addLine(assignStmt, indent+2) - b.addLine("return err\n", indent+3) - b.addLine("}\n", indent+2) - setFormat := "out.%s[key] = *newVal\n" - setStmt := fmt.Sprintf(setFormat, inField.Name) - b.addLine(setStmt, indent+2) - } else { - ifStmt := "if newVal, err := c.DeepCopy(val); err != nil {\n" - b.addLine(ifStmt, indent+2) - b.addLine("return err\n", indent+3) - b.addLine("} else {\n", indent+2) - assignFormat := "out.%s[key] = newVal.(%s)\n" - assignStmt := fmt.Sprintf(assignFormat, inField.Name, g.typeName(inField.Type.Elem())) - b.addLine(assignStmt, indent+3) - b.addLine("}\n", indent+2) - } - default: - assignFormat := "out.%s[key] = val\n" - assignStmt := fmt.Sprintf(assignFormat, inField.Name) - b.addLine(assignStmt, indent+2) - } - } - b.addLine("}\n", indent+1) - b.addLine("} else {\n", indent) - elseFormat := "out.%s = nil\n" - elseStmt := fmt.Sprintf(elseFormat, inField.Name) - b.addLine(elseStmt, indent+1) - b.addLine("}\n", indent) - return nil -} - -func (g *deepCopyGenerator) writeDeepCopyForPtr(b *buffer, inField reflect.StructField, indent int) error { - ifFormat := "if in.%s != nil {\n" - ifStmt := fmt.Sprintf(ifFormat, inField.Name) - b.addLine(ifStmt, indent) - - kind := inField.Type.Elem().Kind() - switch kind { - case reflect.Map, reflect.Ptr, reflect.Slice, reflect.Interface, reflect.Struct: - if _, found := g.copyables[inField.Type.Elem()]; found { - newFormat := "out.%s = new(%s)\n" - newStmt := fmt.Sprintf(newFormat, inField.Name, g.typeName(inField.Type.Elem())) - b.addLine(newStmt, indent+1) - assignFormat := "if err := %s(*in.%s, out.%s, c); err != nil {\n" - funcName := g.deepCopyFunctionName(inField.Type.Elem()) - assignStmt := fmt.Sprintf(assignFormat, funcName, inField.Name, inField.Name) - b.addLine(assignStmt, indent+1) - b.addLine("return err\n", indent+2) - b.addLine("}\n", indent+1) - } else { - ifFormat := "if newVal, err := c.DeepCopy(in.%s); err != nil {\n" - ifStmt := fmt.Sprintf(ifFormat, inField.Name) - b.addLine(ifStmt, indent+1) - b.addLine("return err\n", indent+2) - if kind != reflect.Struct { - b.addLine("} else if newVal == nil {\n", indent+1) - b.addLine(fmt.Sprintf("out.%s = nil\n", inField.Name), indent+2) - } - b.addLine("} else {\n", indent+1) - assignFormat := "out.%s = newVal.(%s)\n" - assignStmt := fmt.Sprintf(assignFormat, inField.Name, g.typeName(inField.Type)) - b.addLine(assignStmt, indent+2) - b.addLine("}\n", indent+1) - } - default: - newFormat := "out.%s = new(%s)\n" - newStmt := fmt.Sprintf(newFormat, inField.Name, g.typeName(inField.Type.Elem())) - b.addLine(newStmt, indent+1) - assignFormat := "*out.%s = *in.%s\n" - assignStmt := fmt.Sprintf(assignFormat, inField.Name, inField.Name) - b.addLine(assignStmt, indent+1) - } - b.addLine("} else {\n", indent) - elseFormat := "out.%s = nil\n" - elseStmt := fmt.Sprintf(elseFormat, inField.Name) - b.addLine(elseStmt, indent+1) - b.addLine("}\n", indent) - return nil -} - -func (g *deepCopyGenerator) writeDeepCopyForSlice(b *buffer, inField reflect.StructField, indent int) error { - ifFormat := "if in.%s != nil {\n" - ifStmt := fmt.Sprintf(ifFormat, inField.Name) - b.addLine(ifStmt, indent) - newFormat := "out.%s = make(%s, len(in.%s))\n" - newStmt := fmt.Sprintf(newFormat, inField.Name, g.typeName(inField.Type), inField.Name) - b.addLine(newStmt, indent+1) - forFormat := "for i := range in.%s {\n" - forStmt := fmt.Sprintf(forFormat, inField.Name) - b.addLine(forStmt, indent+1) - - kind := inField.Type.Elem().Kind() - switch kind { - case reflect.Map, reflect.Ptr, reflect.Slice, reflect.Interface, reflect.Struct: - if _, found := g.copyables[inField.Type.Elem()]; found { - assignFormat := "if err := %s(in.%s[i], &out.%s[i], c); err != nil {\n" - funcName := g.deepCopyFunctionName(inField.Type.Elem()) - assignStmt := fmt.Sprintf(assignFormat, funcName, inField.Name, inField.Name) - b.addLine(assignStmt, indent+2) - b.addLine("return err\n", indent+3) - b.addLine("}\n", indent+2) - } else { - ifFormat := "if newVal, err := c.DeepCopy(in.%s[i]); err != nil {\n" - ifStmt := fmt.Sprintf(ifFormat, inField.Name) - b.addLine(ifStmt, indent+2) - b.addLine("return err\n", indent+3) - if kind != reflect.Struct { - b.addLine("} else if newVal == nil {\n", indent+2) - b.addLine(fmt.Sprintf("out.%s[i] = nil\n", inField.Name), indent+3) - } - b.addLine("} else {\n", indent+2) - assignFormat := "out.%s[i] = newVal.(%s)\n" - assignStmt := fmt.Sprintf(assignFormat, inField.Name, g.typeName(inField.Type.Elem())) - b.addLine(assignStmt, indent+3) - b.addLine("}\n", indent+2) - } - default: - assignFormat := "out.%s[i] = in.%s[i]\n" - assignStmt := fmt.Sprintf(assignFormat, inField.Name, inField.Name) - b.addLine(assignStmt, indent+2) - } - b.addLine("}\n", indent+1) - b.addLine("} else {\n", indent) - elseFormat := "out.%s = nil\n" - elseStmt := fmt.Sprintf(elseFormat, inField.Name) - b.addLine(elseStmt, indent+1) - b.addLine("}\n", indent) - return nil -} - -func (g *deepCopyGenerator) writeDeepCopyForStruct(b *buffer, inType reflect.Type, indent int) error { - for i := 0; i < inType.NumField(); i++ { - inField := inType.Field(i) - switch inField.Type.Kind() { - case reflect.Map: - if err := g.writeDeepCopyForMap(b, inField, indent); err != nil { - return err - } - case reflect.Ptr: - if err := g.writeDeepCopyForPtr(b, inField, indent); err != nil { - return err - } - case reflect.Slice: - if err := g.writeDeepCopyForSlice(b, inField, indent); err != nil { - return err - } - case reflect.Interface: - ifFormat := "if newVal, err := c.DeepCopy(in.%s); err != nil {\n" - ifStmt := fmt.Sprintf(ifFormat, inField.Name) - b.addLine(ifStmt, indent) - b.addLine("return err\n", indent+1) - b.addLine("} else if newVal == nil {\n", indent) - b.addLine(fmt.Sprintf("out.%s = nil\n", inField.Name), indent+1) - b.addLine("} else {\n", indent) - copyFormat := "out.%s = newVal.(%s)\n" - copyStmt := fmt.Sprintf(copyFormat, inField.Name, g.typeName(inField.Type)) - b.addLine(copyStmt, indent+1) - b.addLine("}\n", indent) - case reflect.Struct: - if _, found := g.copyables[inField.Type]; found { - ifFormat := "if err := %s(in.%s, &out.%s, c); err != nil {\n" - funcName := g.deepCopyFunctionName(inField.Type) - ifStmt := fmt.Sprintf(ifFormat, funcName, inField.Name, inField.Name) - b.addLine(ifStmt, indent) - b.addLine("return err\n", indent+1) - b.addLine("}\n", indent) - } else { - ifFormat := "if newVal, err := c.DeepCopy(in.%s); err != nil {\n" - ifStmt := fmt.Sprintf(ifFormat, inField.Name) - b.addLine(ifStmt, indent) - b.addLine("return err\n", indent+1) - b.addLine("} else {\n", indent) - assignFormat := "out.%s = newVal.(%s)\n" - assignStmt := fmt.Sprintf(assignFormat, inField.Name, g.typeName(inField.Type)) - b.addLine(assignStmt, indent+1) - b.addLine("}\n", indent) - } - default: - // This should handle all simple types. - assignFormat := "out.%s = in.%s\n" - assignStmt := fmt.Sprintf(assignFormat, inField.Name, inField.Name) - b.addLine(assignStmt, indent) - } - } - return nil -} - -func (g *deepCopyGenerator) writeDeepCopyForType(b *buffer, inType reflect.Type, indent int) error { - g.writeHeader(b, inType, indent) - switch inType.Kind() { - case reflect.Struct: - if err := g.writeDeepCopyForStruct(b, inType, indent+1); err != nil { - return err - } - default: - return fmt.Errorf("type not supported: %v", inType) - } - g.writeFooter(b, indent) - return nil -} - -func (g *deepCopyGenerator) writeRegisterHeader(b *buffer, pkg string, indent int) { - b.addLine("func init() {\n", indent) - registerFormat := "err := %s.AddGeneratedDeepCopyFuncs(\n" - b.addLine(fmt.Sprintf(registerFormat, pkg), indent+1) -} - -func (g *deepCopyGenerator) writeRegisterFooter(b *buffer, indent int) { - b.addLine(")\n", indent+1) - b.addLine("if err != nil {\n", indent+1) - b.addLine("// if one of the deep copy functions is malformed, detect it immediately.\n", indent+2) - b.addLine("panic(err)\n", indent+2) - b.addLine("}\n", indent+1) - b.addLine("}\n", indent) - b.addLine("\n", indent) -} - -func (g *deepCopyGenerator) RegisterDeepCopyFunctions(w io.Writer, pkg string) error { - var keys []reflect.Type - for key := range g.copyables { - keys = append(keys, key) - } - sort.Sort(byPkgAndName(keys)) - - buffer := newBuffer() - indent := 0 - g.writeRegisterHeader(buffer, pkg, indent) - for _, inType := range keys { - funcStmt := fmt.Sprintf("%s,\n", g.deepCopyFunctionName(inType)) - buffer.addLine(funcStmt, indent+2) - } - g.writeRegisterFooter(buffer, indent) - if err := buffer.flushLines(w); err != nil { - return err - } - return nil -} - -func (g *deepCopyGenerator) OverwritePackage(pkg, overwrite string) { - g.pkgOverwrites[pkg] = overwrite -} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/embedded.go b/vendor/k8s.io/kubernetes/pkg/runtime/embedded.go index 0934d6837..a62080e39 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/embedded.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/embedded.go @@ -24,9 +24,9 @@ import ( ) type encodable struct { - e Encoder `json:"-"` + E Encoder `json:"-"` obj Object - versions []unversioned.GroupVersion `json:"-"` + versions []unversioned.GroupVersion } func (e encodable) GetObjectKind() unversioned.ObjectKind { return e.obj.GetObjectKind() } @@ -47,7 +47,7 @@ func (re encodable) UnmarshalJSON(in []byte) error { // Marshal may get called on pointers or values, so implement MarshalJSON on value. // http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go func (re encodable) MarshalJSON() ([]byte, error) { - return Encode(re.e, re.obj) + return Encode(re.E, re.obj) } // NewEncodableList creates an object that will be encoded with the provided codec on demand. @@ -69,56 +69,68 @@ func (re *Unknown) UnmarshalJSON(in []byte) error { return errors.New("runtime.Unknown: UnmarshalJSON on nil pointer") } re.TypeMeta = TypeMeta{} - re.RawJSON = append(re.RawJSON[0:0], in...) + re.Raw = append(re.Raw[0:0], in...) + re.ContentEncoding = "" + re.ContentType = ContentTypeJSON return nil } // Marshal may get called on pointers or values, so implement MarshalJSON on value. // http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go func (re Unknown) MarshalJSON() ([]byte, error) { - if re.RawJSON == nil { + // If ContentType is unset, we assume this is JSON. + if re.ContentType != "" && re.ContentType != ContentTypeJSON { + return nil, errors.New("runtime.Unknown: MarshalJSON on non-json data") + } + if re.Raw == nil { return []byte("null"), nil } - return re.RawJSON, nil + return re.Raw, nil +} + +func Convert_runtime_Object_To_runtime_RawExtension(in *Object, out *RawExtension, s conversion.Scope) error { + if in == nil { + out.Raw = []byte("null") + return nil + } + obj := *in + if unk, ok := obj.(*Unknown); ok { + if unk.Raw != nil { + out.Raw = unk.Raw + return nil + } + obj = out.Object + } + if obj == nil { + out.Raw = nil + return nil + } + out.Object = obj + return nil +} + +func Convert_runtime_RawExtension_To_runtime_Object(in *RawExtension, out *Object, s conversion.Scope) error { + if in.Object != nil { + *out = in.Object + return nil + } + data := in.Raw + if len(data) == 0 || (len(data) == 4 && string(data) == "null") { + *out = nil + return nil + } + *out = &Unknown{ + Raw: data, + // TODO: Set ContentEncoding and ContentType appropriately. + // Currently we set ContentTypeJSON to make tests passing. + ContentType: ContentTypeJSON, + } + return nil } func DefaultEmbeddedConversions() []interface{} { return []interface{}{ - func(in *Object, out *RawExtension, s conversion.Scope) error { - if in == nil { - out.RawJSON = []byte("null") - return nil - } - obj := *in - if unk, ok := obj.(*Unknown); ok { - if unk.RawJSON != nil { - out.RawJSON = unk.RawJSON - return nil - } - obj = out.Object - } - if obj == nil { - out.RawJSON = nil - return nil - } - out.Object = obj - return nil - }, - - func(in *RawExtension, out *Object, s conversion.Scope) error { - if in.Object != nil { - *out = in.Object - return nil - } - data := in.RawJSON - if len(data) == 0 || (len(data) == 4 && string(data) == "null") { - *out = nil - return nil - } - *out = &Unknown{ - RawJSON: data, - } - return nil - }, + Convert_runtime_Object_To_runtime_RawExtension, + Convert_runtime_RawExtension_To_runtime_Object, } } diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/embedded_test.go b/vendor/k8s.io/kubernetes/pkg/runtime/embedded_test.go index 64d6d74fb..6a143fb08 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/embedded_test.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/embedded_test.go @@ -26,7 +26,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime/serializer" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" ) type EmbeddedTest struct { @@ -78,7 +78,7 @@ func TestDecodeEmptyRawExtensionAsObject(t *testing.T) { t.Fatalf("unexpected error: %v", err) } test := obj.(*ObjectTest) - if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.RawJSON) != "{}" { + if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.Raw) != "{}" || unk.ContentType != runtime.ContentTypeJSON { t.Fatalf("unexpected object: %#v", test.Items[0]) } if *gvk != externalGVK { @@ -90,7 +90,7 @@ func TestDecodeEmptyRawExtensionAsObject(t *testing.T) { t.Fatalf("unexpected error: %v", err) } test = obj.(*ObjectTest) - if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.RawJSON) != `{"kind":"Other","apiVersion":"v1"}` { + if unk, ok := test.Items[0].(*runtime.Unknown); !ok || unk.Kind != "" || unk.APIVersion != "" || string(unk.Raw) != `{"kind":"Other","apiVersion":"v1"}` || unk.ContentType != runtime.ContentTypeJSON { t.Fatalf("unexpected object: %#v", test.Items[0]) } if *gvk != externalGVK { @@ -117,7 +117,10 @@ func TestArrayOfRuntimeObject(t *testing.T) { &EmbeddedTest{ID: "foo"}, &EmbeddedTest{ID: "bar"}, // TODO: until YAML is removed, this JSON must be in ascending key order to ensure consistent roundtrip serialization - &runtime.Unknown{RawJSON: []byte(`{"apiVersion":"unknown.group/unknown","foo":"bar","kind":"OtherTest"}`)}, + &runtime.Unknown{ + Raw: []byte(`{"apiVersion":"unknown.group/unknown","foo":"bar","kind":"OtherTest"}`), + ContentType: runtime.ContentTypeJSON, + }, &ObjectTest{ Items: runtime.NewEncodableList(codec, innerItems), }, @@ -135,7 +138,7 @@ func TestArrayOfRuntimeObject(t *testing.T) { if err := json.Unmarshal(wire, obj); err != nil { t.Fatalf("unexpected error: %v", err) } - t.Logf("exact wire is: %s", string(obj.Items[0].RawJSON)) + t.Logf("exact wire is: %s", string(obj.Items[0].Raw)) items[3] = &ObjectTest{Items: innerItems} internal.Items = items @@ -166,7 +169,7 @@ func TestArrayOfRuntimeObject(t *testing.T) { // we want DecodeList to set type meta if possible, even on runtime.Unknown objects internal.Items[2].(*runtime.Unknown).TypeMeta = runtime.TypeMeta{Kind: "OtherTest", APIVersion: "unknown.group/unknown"} if e, a := internal.Items, list; !reflect.DeepEqual(e, a) { - t.Errorf("mismatched decoded: %s", util.ObjectGoPrintSideBySide(e, a)) + t.Errorf("mismatched decoded: %s", diff.ObjectGoPrintSideBySide(e, a)) } } @@ -208,7 +211,7 @@ func TestNestedObject(t *testing.T) { t.Errorf("Expected unequal %#v %#v", e, a) } - obj, err := runtime.Decode(codec, decoded.(*EmbeddedTest).Object.(*runtime.Unknown).RawJSON) + obj, err := runtime.Decode(codec, decoded.(*EmbeddedTest).Object.(*runtime.Unknown).Raw) if err != nil { t.Fatal(err) } @@ -227,7 +230,7 @@ func TestNestedObject(t *testing.T) { if externalViaJSON.Kind == "" || externalViaJSON.APIVersion == "" || externalViaJSON.ID != "outer" { t.Errorf("Expected objects to have type info set, got %#v", externalViaJSON) } - if !reflect.DeepEqual(externalViaJSON.EmptyObject.RawJSON, []byte("null")) || len(externalViaJSON.Object.RawJSON) == 0 { + if !reflect.DeepEqual(externalViaJSON.EmptyObject.Raw, []byte("null")) || len(externalViaJSON.Object.Raw) == 0 { t.Errorf("Expected deserialization of nested objects into bytes, got %#v", externalViaJSON) } diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/extension.go b/vendor/k8s.io/kubernetes/pkg/runtime/extension.go index 629f675b6..eca82986e 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/extension.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/extension.go @@ -25,14 +25,14 @@ func (re *RawExtension) UnmarshalJSON(in []byte) error { if re == nil { return errors.New("runtime.RawExtension: UnmarshalJSON on nil pointer") } - re.RawJSON = append(re.RawJSON[0:0], in...) + re.Raw = append(re.Raw[0:0], in...) return nil } // Marshal may get called on pointers or values, so implement MarshalJSON on value. // http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go func (re RawExtension) MarshalJSON() ([]byte, error) { - if re.RawJSON == nil { + if re.Raw == nil { // TODO: this is to support legacy behavior of JSONPrinter and YAMLPrinter, which // expect to call json.Marshal on arbitrary versioned objects (even those not in // the scheme). pkg/kubectl/resource#AsVersionedObjects and its interaction with @@ -43,5 +43,6 @@ func (re RawExtension) MarshalJSON() ([]byte, error) { } return []byte("null"), nil } - return re.RawJSON, nil + // TODO: Check whether ContentType is actually JSON before returning it. + return re.Raw, nil } diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/extension_test.go b/vendor/k8s.io/kubernetes/pkg/runtime/extension_test.go index 3d8a087a9..3545284e9 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/extension_test.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/extension_test.go @@ -28,7 +28,7 @@ func TestEmbeddedRawExtensionMarshal(t *testing.T) { Ext runtime.RawExtension } - extension := test{Ext: runtime.RawExtension{RawJSON: []byte(`{"foo":"bar"}`)}} + extension := test{Ext: runtime.RawExtension{Raw: []byte(`{"foo":"bar"}`)}} data, err := json.Marshal(extension) if err != nil { t.Fatalf("unexpected error: %v", err) diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/helper.go b/vendor/k8s.io/kubernetes/pkg/runtime/helper.go index 4a76e81dc..ac23e3a26 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/helper.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/helper.go @@ -80,14 +80,16 @@ func EncodeList(e Encoder, objects []Object, overrides ...unversioned.GroupVersi errs = append(errs, err) continue } - objects[i] = &Unknown{RawJSON: data} + // TODO: Set ContentEncoding and ContentType. + objects[i] = &Unknown{Raw: data} } return errors.NewAggregate(errs) } func decodeListItem(obj *Unknown, decoders []Decoder) (Object, error) { for _, decoder := range decoders { - obj, err := Decode(decoder, obj.RawJSON) + // TODO: Decode based on ContentType. + obj, err := Decode(decoder, obj.Raw) if err != nil { if IsNotRegisteredError(err) { continue @@ -99,7 +101,7 @@ func decodeListItem(obj *Unknown, decoders []Decoder) (Object, error) { // could not decode, so leave the object as Unknown, but give the decoders the // chance to set Unknown.TypeMeta if it is available. for _, decoder := range decoders { - if err := DecodeInto(decoder, obj.RawJSON, obj); err == nil { + if err := DecodeInto(decoder, obj.Raw, obj); err == nil { return obj, nil } } @@ -167,3 +169,13 @@ func (m MultiObjectTyper) IsUnversioned(obj Object) (bool, bool) { } return false, false } + +// SetZeroValue would set the object of objPtr to zero value of its type. +func SetZeroValue(objPtr Object) error { + v, err := conversion.EnforcePtr(objPtr) + if err != nil { + return err + } + v.Set(reflect.Zero(v.Type())) + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/helper_test.go b/vendor/k8s.io/kubernetes/pkg/runtime/helper_test.go index be7f0dedd..a4ab5071c 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/helper_test.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/helper_test.go @@ -28,7 +28,11 @@ func TestDecodeList(t *testing.T) { pl := &api.List{ Items: []runtime.Object{ &api.Pod{ObjectMeta: api.ObjectMeta{Name: "1"}}, - &runtime.Unknown{TypeMeta: runtime.TypeMeta{Kind: "Pod", APIVersion: testapi.Default.GroupVersion().String()}, RawJSON: []byte(`{"kind":"Pod","apiVersion":"` + testapi.Default.GroupVersion().String() + `","metadata":{"name":"test"}}`)}, + &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{Kind: "Pod", APIVersion: testapi.Default.GroupVersion().String()}, + Raw: []byte(`{"kind":"Pod","apiVersion":"` + testapi.Default.GroupVersion().String() + `","metadata":{"name":"test"}}`), + ContentType: runtime.ContentTypeJSON, + }, &runtime.Unstructured{TypeMeta: runtime.TypeMeta{Kind: "Foo", APIVersion: "Bar"}, Object: map[string]interface{}{"test": "value"}}, }, } diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/protobuf/protobuf.go b/vendor/k8s.io/kubernetes/pkg/runtime/protobuf/protobuf.go deleted file mode 100644 index cc050a50b..000000000 --- a/vendor/k8s.io/kubernetes/pkg/runtime/protobuf/protobuf.go +++ /dev/null @@ -1,158 +0,0 @@ -// +build proto - -/* -Copyright 2015 The Kubernetes Authors All rights reserved. - -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 protobuf - -import ( - "fmt" - "io" - "net/url" - "reflect" - - "github.com/gogo/protobuf/proto" - - "k8s.io/kubernetes/pkg/api/unversioned" - "k8s.io/kubernetes/pkg/runtime" -) - -// NewCodec -func NewCodec(version string, creater runtime.ObjectCreater, typer runtime.ObjectTyper, convertor runtime.ObjectConvertor) runtime.Codec { - return &codec{ - version: version, - creater: creater, - typer: typer, - convertor: convertor, - } -} - -// codec decodes protobuf objects -type codec struct { - version string - outputVersion string - creater runtime.ObjectCreater - typer runtime.ObjectTyper - convertor runtime.ObjectConvertor -} - -var _ runtime.Codec = codec{} - -func (c codec) Decode(data []byte) (runtime.Object, error) { - unknown := &runtime.Unknown{} - if err := proto.Unmarshal(data, unknown); err != nil { - return nil, err - } - obj, err := c.creater.New(unknown.APIVersion, unknown.Kind) - if err != nil { - return nil, err - } - pobj, ok := obj.(proto.Message) - if !ok { - return nil, fmt.Errorf("runtime object is not a proto.Message: %v", reflect.TypeOf(obj)) - } - if err := proto.Unmarshal(unknown.RawJSON, pobj); err != nil { - return nil, err - } - if unknown.APIVersion != c.outputVersion { - out, err := c.convertor.ConvertToVersion(obj, c.outputVersion) - if err != nil { - return nil, err - } - obj = out - } - return obj, nil -} - -func (c codec) DecodeToVersion(data []byte, version unversioned.GroupVersion) (runtime.Object, error) { - return nil, fmt.Errorf("unimplemented") -} - -func (c codec) DecodeInto(data []byte, obj runtime.Object) error { - version, kind, err := c.typer.ObjectVersionAndKind(obj) - if err != nil { - return err - } - unknown := &runtime.Unknown{} - if err := proto.Unmarshal(data, unknown); err != nil { - return err - } - if unknown.APIVersion == version && unknown.Kind == kind { - pobj, ok := obj.(proto.Message) - if !ok { - return fmt.Errorf("runtime object is not a proto.Message: %v", reflect.TypeOf(obj)) - } - - return proto.Unmarshal(unknown.RawJSON, pobj) - } - - versioned, err := c.creater.New(unknown.APIVersion, unknown.Kind) - if err != nil { - return err - } - - pobj, ok := versioned.(proto.Message) - if !ok { - return fmt.Errorf("runtime object is not a proto.Message: %v", reflect.TypeOf(obj)) - } - - if err := proto.Unmarshal(unknown.RawJSON, pobj); err != nil { - return err - } - return c.convertor.Convert(versioned, obj) -} - -func (c codec) DecodeIntoWithSpecifiedVersionKind(data []byte, obj runtime.Object, kind unversioned.GroupVersionKind) error { - return fmt.Errorf("unimplemented") -} - -func (c codec) DecodeParametersInto(parameters url.Values, obj runtime.Object) error { - return fmt.Errorf("unimplemented") -} - -func (c codec) Encode(obj runtime.Object) (data []byte, err error) { - version, kind, err := c.typer.ObjectVersionAndKind(obj) - if err != nil { - return nil, err - } - if len(version) == 0 { - version = c.version - converted, err := c.convertor.ConvertToVersion(obj, version) - if err != nil { - return nil, err - } - obj = converted - } - m, ok := obj.(proto.Marshaler) - if !ok { - return nil, fmt.Errorf("object %v (kind: %s in version: %s) does not implement ProtoBuf marshalling", reflect.TypeOf(obj), kind, c.version) - } - b, err := m.Marshal() - if err != nil { - return nil, err - } - return (&runtime.Unknown{ - TypeMeta: runtime.TypeMeta{ - Kind: kind, - APIVersion: version, - }, - RawJSON: b, - }).Marshal() -} - -func (c codec) EncodeToStream(obj runtime.Object, stream io.Writer) error { - return fmt.Errorf("unimplemented") -} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/register.go b/vendor/k8s.io/kubernetes/pkg/runtime/register.go index 95244913c..ec58b345d 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/register.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/register.go @@ -30,8 +30,9 @@ func (obj *TypeMeta) GroupVersionKind() *unversioned.GroupVersionKind { return unversioned.FromAPIVersionAndKind(obj.APIVersion, obj.Kind) } -func (obj *Unknown) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } -func (obj *Unstructured) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } +func (obj *Unknown) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } +func (obj *Unstructured) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } +func (obj *UnstructuredList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } // GetObjectKind implements Object for VersionedObjects, returning an empty ObjectKind // interface if no objects are provided, or the ObjectKind interface of the object in the diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/scheme.go b/vendor/k8s.io/kubernetes/pkg/runtime/scheme.go index 37bd985aa..9a4a70896 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/scheme.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/scheme.go @@ -142,7 +142,7 @@ func (s *Scheme) Converter() *conversion.Converter { // API group and version that would never be updated. // // TODO: there is discussion about removing unversioned and replacing it with objects that are manifest into -// every version with particular schemas. Resolve tihs method at that point. +// every version with particular schemas. Resolve this method at that point. func (s *Scheme) AddUnversionedTypes(version unversioned.GroupVersion, types ...Object) { s.AddKnownTypes(version, types...) for _, obj := range types { @@ -474,7 +474,7 @@ func (s *Scheme) ConvertToVersion(in Object, outVersion string) (Object, error) return nil, err } switch in.(type) { - case *Unknown, *Unstructured: + case *Unknown, *Unstructured, *UnstructuredList: old := in.GetObjectKind().GroupVersionKind() defer in.GetObjectKind().SetGroupVersionKind(old) setTargetVersion(in, s, gv) diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/scheme_test.go b/vendor/k8s.io/kubernetes/pkg/runtime/scheme_test.go index 5b933d84b..52cb796da 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/scheme_test.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/scheme_test.go @@ -27,7 +27,7 @@ import ( "k8s.io/kubernetes/pkg/conversion" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime/serializer" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" ) var fuzzIters = flag.Int("fuzz-iters", 50, "How many fuzzing iterations to do.") @@ -239,7 +239,7 @@ func TestExternalToInternalMapping(t *testing.T) { if err != nil { t.Errorf("unexpected error '%v' (%v)", err, item.encoded) } else if e, a := item.obj, gotDecoded; !reflect.DeepEqual(e, a) { - t.Errorf("%d: unexpected objects:\n%s", i, util.ObjectGoPrintSideBySide(e, a)) + t.Errorf("%d: unexpected objects:\n%s", i, diff.ObjectGoPrintSideBySide(e, a)) } } } @@ -274,7 +274,8 @@ func TestExtensionMapping(t *testing.T) { }, &InternalExtensionType{ Extension: &runtime.Unknown{ - RawJSON: []byte(`{"apiVersion":"test.group/testExternal","kind":"A","testString":"foo"}`), + Raw: []byte(`{"apiVersion":"test.group/testExternal","kind":"A","testString":"foo"}`), + ContentType: runtime.ContentTypeJSON, }, }, // apiVersion is set in the serialized object for easier consumption by clients @@ -284,7 +285,8 @@ func TestExtensionMapping(t *testing.T) { &InternalExtensionType{Extension: runtime.NewEncodable(codec, &ExtensionB{TestString: "bar"})}, &InternalExtensionType{ Extension: &runtime.Unknown{ - RawJSON: []byte(`{"apiVersion":"test.group/testExternal","kind":"B","testString":"bar"}`), + Raw: []byte(`{"apiVersion":"test.group/testExternal","kind":"B","testString":"bar"}`), + ContentType: runtime.ContentTypeJSON, }, }, // apiVersion is set in the serialized object for easier consumption by clients @@ -312,7 +314,7 @@ func TestExtensionMapping(t *testing.T) { if err != nil { t.Errorf("unexpected error '%v' (%v)", err, item.encoded) } else if e, a := item.expected, gotDecoded; !reflect.DeepEqual(e, a) { - t.Errorf("%d: unexpected objects:\n%s", i, util.ObjectGoPrintSideBySide(e, a)) + t.Errorf("%d: unexpected objects:\n%s", i, diff.ObjectGoPrintSideBySide(e, a)) } } } @@ -361,7 +363,7 @@ func TestUnversionedTypes(t *testing.T) { codec := serializer.NewCodecFactory(scheme).LegacyCodec(externalGV) if unv, ok := scheme.IsUnversioned(&InternalSimple{}); !unv || !ok { - t.Fatal("type not unversioned and in scheme: %t %t", unv, ok) + t.Fatalf("type not unversioned and in scheme: %t %t", unv, ok) } kind, err := scheme.ObjectKind(&InternalSimple{}) diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/codec_factory.go b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/codec_factory.go index 6f6310d9c..54b683682 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/codec_factory.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/codec_factory.go @@ -17,36 +17,44 @@ limitations under the License. package serializer import ( + "io/ioutil" + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime/serializer/json" "k8s.io/kubernetes/pkg/runtime/serializer/recognizer" + "k8s.io/kubernetes/pkg/runtime/serializer/streaming" "k8s.io/kubernetes/pkg/runtime/serializer/versioning" ) +// serializerExtensions are for serializers that are conditionally compiled in +var serializerExtensions = []func(*runtime.Scheme) (serializerType, bool){} + type serializerType struct { AcceptContentTypes []string ContentType string FileExtensions []string - Serializer runtime.Serializer - PrettySerializer runtime.Serializer + + Serializer runtime.Serializer + PrettySerializer runtime.Serializer + // RawSerializer serializes an object without adding a type wrapper. Some serializers, like JSON + // automatically include identifying type information with the JSON. Others, like Protobuf, need + // a wrapper object that includes type information. This serializer should be set if the serializer + // can serialize / deserialize objects without type info. Note that this serializer will always + // be expected to pass into or a gvk to Decode, since no type information will be available on + // the object itself. + RawSerializer runtime.Serializer + + // Specialize gives the type the opportunity to return a different serializer implementation if + // the content type contains alternate operations. Here it is used to implement "pretty" as an + // option to application/json, but could also be used to allow serializers to perform type + // defaulting or alter output. + Specialize func(map[string]string) (runtime.Serializer, bool) } -// NewCodecFactory provides methods for retrieving serializers for the supported wire formats -// and conversion wrappers to define preferred internal and external versions. In the future, -// as the internal version is used less, callers may instead use a defaulting serializer and -// only convert objects which are shared internally (Status, common API machinery). -// TODO: allow other codecs to be compiled in? -// TODO: accept a scheme interface -func NewCodecFactory(scheme *runtime.Scheme) CodecFactory { - return newCodecFactory(scheme, json.DefaultMetaFactory) -} - -// newCodecFactory is a helper for testing that allows a different metafactory to be specified. -func newCodecFactory(scheme *runtime.Scheme, mf json.MetaFactory) CodecFactory { +func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory) []serializerType { jsonSerializer := json.NewSerializer(mf, scheme, runtime.ObjectTyperToTyper(scheme), false) jsonPrettySerializer := json.NewSerializer(mf, scheme, runtime.ObjectTyperToTyper(scheme), true) - yamlSerializer := json.NewYAMLSerializer(mf, scheme, runtime.ObjectTyperToTyper(scheme)) serializers := []serializerType{ { AcceptContentTypes: []string{"application/json"}, @@ -55,34 +63,21 @@ func newCodecFactory(scheme *runtime.Scheme, mf json.MetaFactory) CodecFactory { Serializer: jsonSerializer, PrettySerializer: jsonPrettySerializer, }, - { - AcceptContentTypes: []string{"application/yaml"}, - ContentType: "application/yaml", - FileExtensions: []string{"yaml"}, - Serializer: yamlSerializer, - }, } - decoders := make([]runtime.Decoder, 0, len(serializers)) - accepts := []string{} - alreadyAccepted := make(map[string]struct{}) - for _, d := range serializers { - decoders = append(decoders, d.Serializer) - for _, mediaType := range d.AcceptContentTypes { - if _, ok := alreadyAccepted[mediaType]; ok { - continue - } - alreadyAccepted[mediaType] = struct{}{} - accepts = append(accepts, mediaType) + yamlSerializer := json.NewYAMLSerializer(mf, scheme, runtime.ObjectTyperToTyper(scheme)) + serializers = append(serializers, serializerType{ + AcceptContentTypes: []string{"application/yaml"}, + ContentType: "application/yaml", + FileExtensions: []string{"yaml"}, + Serializer: yamlSerializer, + }) + + for _, fn := range serializerExtensions { + if serializer, ok := fn(scheme); ok { + serializers = append(serializers, serializer) } } - return CodecFactory{ - scheme: scheme, - serializers: serializers, - universal: recognizer.NewDecoder(decoders...), - accepts: accepts, - - legacySerializer: jsonSerializer, - } + return serializers } // CodecFactory provides methods for retrieving codecs and serializers for specific @@ -96,6 +91,78 @@ type CodecFactory struct { legacySerializer runtime.Serializer } +// NewCodecFactory provides methods for retrieving serializers for the supported wire formats +// and conversion wrappers to define preferred internal and external versions. In the future, +// as the internal version is used less, callers may instead use a defaulting serializer and +// only convert objects which are shared internally (Status, common API machinery). +// TODO: allow other codecs to be compiled in? +// TODO: accept a scheme interface +func NewCodecFactory(scheme *runtime.Scheme) CodecFactory { + serializers := newSerializersForScheme(scheme, json.DefaultMetaFactory) + return newCodecFactory(scheme, serializers) +} + +// NewStreamingCodecFactory returns serializers that support the streaming.Serializer interface. +// TODO: determine whether this returns a streaming.Serializer AND runtime.Serializer, or whether +// streaming should be added to the CodecFactory interface. +func NewStreamingCodecFactory(scheme *runtime.Scheme) CodecFactory { + return newStreamingCodecFactory(scheme, json.DefaultMetaFactory) +} + +// newStreamingCodecFactory handles providing streaming codecs +func newStreamingCodecFactory(scheme *runtime.Scheme, mf json.MetaFactory) CodecFactory { + serializers := newSerializersForScheme(scheme, mf) + streamers := []serializerType{} + for i := range serializers { + if serializers[i].RawSerializer != nil { + serializers[i].Serializer = serializers[i].RawSerializer + } + if s, ok := serializers[i].Serializer.(streaming.Framer); ok { + // TODO: more elegant option? + // TODO: add tests and assertions for which serializers should + // have framers. We need to answer whether all Serializers + // are streaming serializers or not. + if s.NewFrameWriter(ioutil.Discard) == nil { + continue + } + streamers = append(streamers, serializers[i]) + } + } + return newCodecFactory(scheme, streamers) +} + +// newCodecFactory is a helper for testing that allows a different metafactory to be specified. +func newCodecFactory(scheme *runtime.Scheme, serializers []serializerType) CodecFactory { + decoders := make([]runtime.Decoder, 0, len(serializers)) + accepts := []string{} + alreadyAccepted := make(map[string]struct{}) + var legacySerializer runtime.Serializer + for _, d := range serializers { + decoders = append(decoders, d.Serializer) + for _, mediaType := range d.AcceptContentTypes { + if _, ok := alreadyAccepted[mediaType]; ok { + continue + } + alreadyAccepted[mediaType] = struct{}{} + accepts = append(accepts, mediaType) + if mediaType == "application/json" { + legacySerializer = d.Serializer + } + } + } + if legacySerializer == nil { + legacySerializer = serializers[0].Serializer + } + return CodecFactory{ + scheme: scheme, + serializers: serializers, + universal: recognizer.NewDecoder(decoders...), + accepts: accepts, + + legacySerializer: legacySerializer, + } +} + var _ runtime.NegotiatedSerializer = &CodecFactory{} // SupportedMediaTypes returns the RFC2046 media types that this factory has serializers for. @@ -109,7 +176,7 @@ func (f CodecFactory) SupportedMediaTypes() []string { // This method is deprecated - clients and servers should negotiate a serializer by mime-type and // invoke CodecForVersions. Callers that need only to read data should use UniversalDecoder(). func (f CodecFactory) LegacyCodec(version ...unversioned.GroupVersion) runtime.Codec { - return f.CodecForVersions(runtime.NewCodec(f.legacySerializer, f.universal), version, nil) + return versioning.NewCodecForScheme(f.scheme, f.legacySerializer, f.universal, version, nil) } // UniversalDeserializer can convert any stored data recognized by this factory into a Go object that satisfies @@ -127,14 +194,14 @@ func (f CodecFactory) UniversalDeserializer() runtime.Decoder { // // TODO: the decoder will eventually be removed in favor of dealing with objects in their versioned form func (f CodecFactory) UniversalDecoder(versions ...unversioned.GroupVersion) runtime.Decoder { - return f.CodecForVersions(runtime.NoopEncoder{f.universal}, nil, versions) + return f.CodecForVersions(runtime.NoopEncoder{Decoder: f.universal}, nil, versions) } // CodecFor creates a codec with the provided serializer. If an object is decoded and its group is not in the list, // it will default to runtime.APIVersionInternal. If encode is not specified for an object's group, the object is not // converted. If encode or decode are nil, no conversion is performed. func (f CodecFactory) CodecForVersions(serializer runtime.Serializer, encode []unversioned.GroupVersion, decode []unversioned.GroupVersion) runtime.Codec { - return versioning.NewCodecForScheme(f.scheme, serializer, encode, decode) + return versioning.NewCodecForScheme(f.scheme, serializer, serializer, encode, decode) } // DecoderToVersion returns a decoder that targets the provided group version. @@ -153,6 +220,10 @@ func (f CodecFactory) SerializerForMediaType(mediaType string, options map[strin for _, s := range f.serializers { for _, accepted := range s.AcceptContentTypes { if accepted == mediaType { + if s.Specialize != nil && len(options) > 0 { + serializer, ok := s.Specialize(options) + return serializer, ok + } if v, ok := options["pretty"]; ok && v == "1" && s.PrettySerializer != nil { return s.PrettySerializer, true } diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/codec_test.go b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/codec_test.go index 6c1b2ff95..259da6e30 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/codec_test.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/codec_test.go @@ -28,7 +28,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/conversion" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" "github.com/ghodss/yaml" "github.com/google/gofuzz" @@ -173,7 +173,7 @@ func GetTestScheme() (*runtime.Scheme, runtime.Codec) { s.AddUnversionedTypes(externalGV, &unversioned.Status{}) - cf := newCodecFactory(s, testMetaFactory{}) + cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{})) codec := cf.LegacyCodec(unversioned.GroupVersion{Version: "v1"}) return s, codec } @@ -187,12 +187,12 @@ func objDiff(a, b interface{}) string { if err != nil { panic("b") } - return util.StringDiff(string(ab), string(bb)) + return diff.StringDiff(string(ab), string(bb)) // An alternate diff attempt, in case json isn't showing you // the difference. (reflect.DeepEqual makes a distinction between // nil and empty slices, for example.) - //return util.StringDiff( + //return diff.StringDiff( // fmt.Sprintf("%#v", a), // fmt.Sprintf("%#v", b), //) @@ -222,7 +222,7 @@ func runTest(t *testing.T, source interface{}) { return } if !semantic.DeepEqual(source, obj2) { - t.Errorf("1: %v: diff: %v", name, util.ObjectGoPrintSideBySide(source, obj2)) + t.Errorf("1: %v: diff: %v", name, diff.ObjectGoPrintSideBySide(source, obj2)) return } obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface() @@ -263,7 +263,7 @@ func TestVersionedEncoding(t *testing.T) { t.Fatal(err) } - cf := newCodecFactory(s, testMetaFactory{}) + cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{})) encoder, _ := cf.SerializerForFileExtension("json") // codec that is unversioned uses the target version @@ -326,7 +326,7 @@ func TestConvertTypesWhenDefaultNamesMatch(t *testing.T) { } expect := &TestType1{A: "test"} - codec := newCodecFactory(s, testMetaFactory{}).LegacyCodec(unversioned.GroupVersion{Version: "v1"}) + codec := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{})).LegacyCodec(unversioned.GroupVersion{Version: "v1"}) obj, err := runtime.Decode(codec, data) if err != nil { diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/deep_copy_generated.go b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/deep_copy_generated.go new file mode 100644 index 000000000..f7392e03e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/deep_copy_generated.go @@ -0,0 +1,73 @@ +// +build !ignore_autogenerated + +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package serializer + +import ( + conversion "k8s.io/kubernetes/pkg/conversion" + runtime "k8s.io/kubernetes/pkg/runtime" +) + +func DeepCopy_serializer_CodecFactory(in CodecFactory, out *CodecFactory, c *conversion.Cloner) error { + if in.scheme != nil { + in, out := in.scheme, &out.scheme + *out = new(runtime.Scheme) + if err := runtime.DeepCopy_runtime_Scheme(*in, *out, c); err != nil { + return err + } + } else { + out.scheme = nil + } + if in.serializers != nil { + in, out := in.serializers, &out.serializers + *out = make([]serializerType, len(in)) + for i := range in { + if newVal, err := c.DeepCopy(in[i]); err != nil { + return err + } else { + (*out)[i] = newVal.(serializerType) + } + } + } else { + out.serializers = nil + } + if in.universal == nil { + out.universal = nil + } else if newVal, err := c.DeepCopy(in.universal); err != nil { + return err + } else { + out.universal = newVal.(runtime.Decoder) + } + if in.accepts != nil { + in, out := in.accepts, &out.accepts + *out = make([]string, len(in)) + copy(*out, in) + } else { + out.accepts = nil + } + if in.legacySerializer == nil { + out.legacySerializer = nil + } else if newVal, err := c.DeepCopy(in.legacySerializer); err != nil { + return err + } else { + out.legacySerializer = newVal.(runtime.Serializer) + } + return nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/json/json.go b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/json/json.go index f9fb4bbfb..bb19b1ee7 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/json/json.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/json/json.go @@ -25,6 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/framer" utilyaml "k8s.io/kubernetes/pkg/util/yaml" ) @@ -60,6 +61,9 @@ type Serializer struct { pretty bool } +// Serializer implements Serializer +var _ runtime.Serializer = &Serializer{} + // Decode attempts to convert the provided data into YAML or JSON, extract the stored schema kind, apply the provided default gvk, and then // load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, the raw data will be // extracted and no decoding will be performed. If into is not registered with the typer, then the object will be straight decoded using @@ -105,8 +109,8 @@ func (s *Serializer) Decode(originalData []byte, gvk *unversioned.GroupVersionKi } if unk, ok := into.(*runtime.Unknown); ok && unk != nil { - unk.RawJSON = originalData - // TODO: set content type here + unk.Raw = originalData + unk.ContentType = runtime.ContentTypeJSON unk.GetObjectKind().SetGroupVersionKind(actual) return unk, actual, nil } @@ -189,3 +193,31 @@ func (s *Serializer) RecognizesData(peek io.Reader) (bool, error) { } return ok, nil } + +// NewFrameWriter implements stream framing for this serializer +func (s *Serializer) NewFrameWriter(w io.Writer) io.Writer { + if s.yaml { + // TODO: needs document framing + return nil + } + // we can write JSON objects directly to the writer, because they are self-framing + return w +} + +// NewFrameReader implements stream framing for this serializer +func (s *Serializer) NewFrameReader(r io.Reader) io.Reader { + if s.yaml { + // TODO: needs document framing + return nil + } + // we need to extract the JSON chunks of data to pass to Decode() + return framer.NewJSONFramedReader(r) +} + +// EncodesAsText returns true because both JSON and YAML are considered textual representations +// of data. This is used to determine whether the serialized object should be transmitted over +// a WebSocket Text or Binary frame. This must remain true for legacy compatibility with v1.1 +// watch over websocket implementations. +func (s *Serializer) EncodesAsText() bool { + return true +} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/json/json_test.go b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/json/json_test.go index b9e1319fa..f9fbf47a0 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/json/json_test.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/json/json_test.go @@ -25,7 +25,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime/serializer/json" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" ) type testDecodable struct { @@ -113,7 +113,8 @@ func TestDecode(t *testing.T) { expectedGVK: &unversioned.GroupVersionKind{}, expectedObject: &runtime.Unknown{ - RawJSON: []byte(`{}`), + Raw: []byte(`{}`), + ContentType: runtime.ContentTypeJSON, }, }, { @@ -122,7 +123,8 @@ func TestDecode(t *testing.T) { expectedGVK: &unversioned.GroupVersionKind{}, expectedObject: &runtime.Unknown{ - RawJSON: []byte(`{"test":"object"}`), + Raw: []byte(`{"test":"object"}`), + ContentType: runtime.ContentTypeJSON, }, }, { @@ -131,8 +133,9 @@ func TestDecode(t *testing.T) { defaultGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, expectedObject: &runtime.Unknown{ - TypeMeta: runtime.TypeMeta{APIVersion: "other/blah", Kind: "Test"}, - RawJSON: []byte(`{"test":"object"}`), + TypeMeta: runtime.TypeMeta{APIVersion: "other/blah", Kind: "Test"}, + Raw: []byte(`{"test":"object"}`), + ContentType: runtime.ContentTypeJSON, }, }, @@ -241,7 +244,7 @@ func TestDecode(t *testing.T) { } if !reflect.DeepEqual(test.expectedObject, obj) { - t.Errorf("%d: unexpected object:\n%s", i, util.ObjectGoPrintSideBySide(test.expectedObject, obj)) + t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.expectedObject, obj)) } } } diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/protobuf/doc.go b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/protobuf/doc.go similarity index 88% rename from vendor/k8s.io/kubernetes/pkg/runtime/protobuf/doc.go rename to vendor/k8s.io/kubernetes/pkg/runtime/serializer/protobuf/doc.go index 33316d0c4..91b86af6c 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/protobuf/doc.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/protobuf/doc.go @@ -14,5 +14,5 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package protobuf implements ProtoBuf serialization and deserialization. +// Package protobuf provides a Kubernetes serializer for the protobuf format. package protobuf diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/protobuf/protobuf.go b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/protobuf/protobuf.go new file mode 100644 index 000000000..aa39c338d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/protobuf/protobuf.go @@ -0,0 +1,448 @@ +// +build proto + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 protobuf + +import ( + "bytes" + "fmt" + "io" + "reflect" + + "github.com/gogo/protobuf/proto" + + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/framer" +) + +var ( + // protoEncodingPrefix serves as a magic number for an encoded protobuf message on this serializer. All + // proto messages serialized by this schema will be preceeded by the bytes 0x6b 0x38 0x73, with the fourth + // byte being reserved for the encoding style. The only encoding style defined is 0x00, which means that + // the rest of the byte stream is a message of type k8s.io.kubernetes.pkg.runtime.Unknown (proto2). + // + // See k8s.io/kubernetes/pkg/runtime/generated.proto for details of the runtime.Unknown message. + // + // This encoding scheme is experimental, and is subject to change at any time. + protoEncodingPrefix = []byte{0x6b, 0x38, 0x73, 0x00} +) + +type errNotMarshalable struct { + t reflect.Type +} + +func (e errNotMarshalable) Error() string { + return fmt.Sprintf("object %v does not implement the protobuf marshalling interface and cannot be encoded to a protobuf message", e.t) +} + +func IsNotMarshalable(err error) bool { + _, ok := err.(errNotMarshalable) + return err != nil && ok +} + +// NewSerializer creates a Protobuf serializer that handles encoding versioned objects into the proper wire form. If a typer +// is passed, the encoded object will have group, version, and kind fields set. If typer is nil, the objects will be written +// as-is (any type info passed with the object will be used). +// +// This encoding scheme is experimental, and is subject to change at any time. +func NewSerializer(creater runtime.ObjectCreater, typer runtime.Typer, defaultContentType string) *Serializer { + return &Serializer{ + prefix: protoEncodingPrefix, + creater: creater, + typer: typer, + contentType: defaultContentType, + } +} + +type Serializer struct { + prefix []byte + creater runtime.ObjectCreater + typer runtime.Typer + contentType string +} + +var _ runtime.Serializer = &Serializer{} + +// Decode attempts to convert the provided data into a protobuf message, extract the stored schema kind, apply the provided default +// gvk, and then load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, +// the raw data will be extracted and no decoding will be performed. If into is not registered with the typer, then the object will +// be straight decoded using normal protobuf unmarshalling (the MarshalTo interface). If into is provided and the original data is +// not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk. On success or most +// errors, the method will return the calculated schema kind. +func (s *Serializer) Decode(originalData []byte, gvk *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) { + if versioned, ok := into.(*runtime.VersionedObjects); ok { + into = versioned.Last() + obj, actual, err := s.Decode(originalData, gvk, into) + if err != nil { + return nil, actual, err + } + // the last item in versioned becomes into, so if versioned was not originally empty we reset the object + // array so the first position is the decoded object and the second position is the outermost object. + // if there were no objects in the versioned list passed to us, only add ourselves. + if into != nil && into != obj { + versioned.Objects = []runtime.Object{obj, into} + } else { + versioned.Objects = []runtime.Object{obj} + } + return versioned, actual, err + } + + prefixLen := len(s.prefix) + switch { + case len(originalData) == 0: + // TODO: treat like decoding {} from JSON with defaulting + return nil, nil, fmt.Errorf("empty data") + case len(originalData) < prefixLen || !bytes.Equal(s.prefix, originalData[:prefixLen]): + return nil, nil, fmt.Errorf("provided data does not appear to be a protobuf message, expected prefix %v", s.prefix) + case len(originalData) == prefixLen: + // TODO: treat like decoding {} from JSON with defaulting + return nil, nil, fmt.Errorf("empty body") + } + + data := originalData[prefixLen:] + unk := runtime.Unknown{} + if err := unk.Unmarshal(data); err != nil { + return nil, nil, err + } + + actual := unk.GroupVersionKind() + copyKindDefaults(actual, gvk) + + if intoUnknown, ok := into.(*runtime.Unknown); ok && intoUnknown != nil { + *intoUnknown = unk + if len(intoUnknown.ContentType) == 0 { + intoUnknown.ContentType = s.contentType + } + return intoUnknown, actual, nil + } + + if into != nil { + typed, _, err := s.typer.ObjectKind(into) + switch { + case runtime.IsNotRegisteredError(err): + pb, ok := into.(proto.Message) + if !ok { + return nil, actual, errNotMarshalable{reflect.TypeOf(into)} + } + if err := proto.Unmarshal(unk.Raw, pb); err != nil { + return nil, actual, err + } + return into, actual, nil + case err != nil: + return nil, actual, err + default: + copyKindDefaults(actual, typed) + // if the result of defaulting did not set a version or group, ensure that at least group is set + // (copyKindDefaults will not assign Group if version is already set). This guarantees that the group + // of into is set if there is no better information from the caller or object. + if len(actual.Version) == 0 && len(actual.Group) == 0 { + actual.Group = typed.Group + } + } + } + + if len(actual.Kind) == 0 { + return nil, actual, runtime.NewMissingKindErr(fmt.Sprintf("%#v", unk.TypeMeta)) + } + if len(actual.Version) == 0 { + return nil, actual, runtime.NewMissingVersionErr(fmt.Sprintf("%#v", unk.TypeMeta)) + } + + return unmarshalToObject(s.typer, s.creater, actual, into, unk.Raw) +} + +// EncodeToStream serializes the provided object to the given writer. Overrides is ignored. +func (s *Serializer) EncodeToStream(obj runtime.Object, w io.Writer, overrides ...unversioned.GroupVersion) error { + var unk runtime.Unknown + if kind := obj.GetObjectKind().GroupVersionKind(); kind != nil { + unk = runtime.Unknown{ + TypeMeta: runtime.TypeMeta{ + Kind: kind.Kind, + APIVersion: kind.GroupVersion().String(), + }, + } + } + + prefixSize := uint64(len(s.prefix)) + + switch t := obj.(type) { + case bufferedMarshaller: + // this path performs a single allocation during write but requires the caller to implement + // the more efficient Size and MarshalTo methods + encodedSize := uint64(t.Size()) + estimatedSize := prefixSize + estimateUnknownSize(&unk, encodedSize) + data := make([]byte, estimatedSize) + + i, err := unk.NestedMarshalTo(data[prefixSize:], t, encodedSize) + if err != nil { + return err + } + + copy(data, s.prefix) + + _, err = w.Write(data[:prefixSize+uint64(i)]) + return err + + case proto.Marshaler: + // this path performs extra allocations + data, err := t.Marshal() + if err != nil { + return err + } + unk.Raw = data + + estimatedSize := prefixSize + uint64(unk.Size()) + data = make([]byte, estimatedSize) + + i, err := unk.MarshalTo(data[prefixSize:]) + if err != nil { + return err + } + + copy(data, s.prefix) + + _, err = w.Write(data[:prefixSize+uint64(i)]) + return err + + default: + // TODO: marshal with a different content type and serializer (JSON for third party objects) + return errNotMarshalable{reflect.TypeOf(obj)} + } +} + +// RecognizesData implements the RecognizingDecoder interface. +func (s *Serializer) RecognizesData(peek io.Reader) (bool, error) { + prefix := make([]byte, 4) + n, err := peek.Read(prefix) + if err != nil { + if err == io.EOF { + return false, nil + } + return false, err + } + if n != 4 { + return false, nil + } + return bytes.Equal(s.prefix, prefix), nil +} + +// NewFrameWriter implements stream framing for this serializer +func (s *Serializer) NewFrameWriter(w io.Writer) io.Writer { + return framer.NewLengthDelimitedFrameWriter(w) +} + +// NewFrameReader implements stream framing for this serializer +func (s *Serializer) NewFrameReader(r io.Reader) io.Reader { + return framer.NewLengthDelimitedFrameReader(r) +} + +// copyKindDefaults defaults dst to the value in src if dst does not have a value set. +func copyKindDefaults(dst, src *unversioned.GroupVersionKind) { + if src == nil { + return + } + // apply kind and version defaulting from provided default + if len(dst.Kind) == 0 { + dst.Kind = src.Kind + } + if len(dst.Version) == 0 && len(src.Version) > 0 { + dst.Group = src.Group + dst.Version = src.Version + } +} + +// bufferedMarshaller describes a more efficient marshalling interface that can avoid allocating multiple +// byte buffers by pre-calculating the size of the final buffer needed. +type bufferedMarshaller interface { + proto.Sizer + runtime.ProtobufMarshaller +} + +// estimateUnknownSize returns the expected bytes consumed by a given runtime.Unknown +// object with a nil RawJSON struct and the expected size of the provided buffer. The +// returned size will not be correct if RawJSOn is set on unk. +func estimateUnknownSize(unk *runtime.Unknown, byteSize uint64) uint64 { + size := uint64(unk.Size()) + // protobuf uses 1 byte for the tag, a varint for the length of the array (at most 8 bytes - uint64 - here), + // and the size of the array. + size += 1 + 8 + byteSize + return size +} + +// NewRawSerializer creates a Protobuf serializer that handles encoding versioned objects into the proper wire form. If typer +// is not nil, the object has the group, version, and kind fields set. This serializer does not provide type information for the +// encoded object, and thus is not self describing (callers must know what type is being described in order to decode). +// +// This encoding scheme is experimental, and is subject to change at any time. +func NewRawSerializer(creater runtime.ObjectCreater, typer runtime.Typer, defaultContentType string) *RawSerializer { + return &RawSerializer{ + creater: creater, + typer: typer, + contentType: defaultContentType, + } +} + +// RawSerializer encodes and decodes objects without adding a runtime.Unknown wrapper (objects are encoded without identifying +// type). +type RawSerializer struct { + creater runtime.ObjectCreater + typer runtime.Typer + contentType string +} + +var _ runtime.Serializer = &RawSerializer{} + +// Decode attempts to convert the provided data into a protobuf message, extract the stored schema kind, apply the provided default +// gvk, and then load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, +// the raw data will be extracted and no decoding will be performed. If into is not registered with the typer, then the object will +// be straight decoded using normal protobuf unmarshalling (the MarshalTo interface). If into is provided and the original data is +// not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk. On success or most +// errors, the method will return the calculated schema kind. +func (s *RawSerializer) Decode(originalData []byte, gvk *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) { + if into == nil { + return nil, nil, fmt.Errorf("this serializer requires an object to decode into: %#v", s) + } + + if versioned, ok := into.(*runtime.VersionedObjects); ok { + into = versioned.Last() + obj, actual, err := s.Decode(originalData, gvk, into) + if err != nil { + return nil, actual, err + } + if into != nil && into != obj { + versioned.Objects = []runtime.Object{obj, into} + } else { + versioned.Objects = []runtime.Object{obj} + } + return versioned, actual, err + } + + if len(originalData) == 0 { + // TODO: treat like decoding {} from JSON with defaulting + return nil, nil, fmt.Errorf("empty data") + } + data := originalData + + actual := &unversioned.GroupVersionKind{} + copyKindDefaults(actual, gvk) + + if intoUnknown, ok := into.(*runtime.Unknown); ok && intoUnknown != nil { + intoUnknown.Raw = data + intoUnknown.ContentEncoding = "" + intoUnknown.ContentType = s.contentType + intoUnknown.SetGroupVersionKind(actual) + return intoUnknown, actual, nil + } + + typed, _, err := s.typer.ObjectKind(into) + switch { + case runtime.IsNotRegisteredError(err): + pb, ok := into.(proto.Message) + if !ok { + return nil, actual, errNotMarshalable{reflect.TypeOf(into)} + } + if err := proto.Unmarshal(data, pb); err != nil { + return nil, actual, err + } + return into, actual, nil + case err != nil: + return nil, actual, err + default: + copyKindDefaults(actual, typed) + // if the result of defaulting did not set a version or group, ensure that at least group is set + // (copyKindDefaults will not assign Group if version is already set). This guarantees that the group + // of into is set if there is no better information from the caller or object. + if len(actual.Version) == 0 && len(actual.Group) == 0 { + actual.Group = typed.Group + } + } + + if len(actual.Kind) == 0 { + return nil, actual, runtime.NewMissingKindErr("") + } + if len(actual.Version) == 0 { + return nil, actual, runtime.NewMissingVersionErr("") + } + + return unmarshalToObject(s.typer, s.creater, actual, into, data) +} + +// unmarshalToObject is the common code between decode in the raw and normal serializer. +func unmarshalToObject(typer runtime.Typer, creater runtime.ObjectCreater, actual *unversioned.GroupVersionKind, into runtime.Object, data []byte) (runtime.Object, *unversioned.GroupVersionKind, error) { + // use the target if necessary + obj, err := runtime.UseOrCreateObject(typer, creater, *actual, into) + if err != nil { + return nil, actual, err + } + + pb, ok := obj.(proto.Message) + if !ok { + return nil, actual, errNotMarshalable{reflect.TypeOf(obj)} + } + if err := proto.Unmarshal(data, pb); err != nil { + return nil, actual, err + } + return obj, actual, nil +} + +// EncodeToStream serializes the provided object to the given writer. Overrides is ignored. +func (s *RawSerializer) EncodeToStream(obj runtime.Object, w io.Writer, overrides ...unversioned.GroupVersion) error { + switch t := obj.(type) { + case bufferedMarshaller: + // this path performs a single allocation during write but requires the caller to implement + // the more efficient Size and MarshalTo methods + encodedSize := uint64(t.Size()) + data := make([]byte, encodedSize) + + n, err := t.MarshalTo(data) + if err != nil { + return err + } + _, err = w.Write(data[:n]) + return err + + case proto.Marshaler: + // this path performs extra allocations + data, err := t.Marshal() + if err != nil { + return err + } + _, err = w.Write(data) + return err + + default: + return errNotMarshalable{reflect.TypeOf(obj)} + } +} + +// RecognizesData implements the RecognizingDecoder interface - objects encoded with this serializer +// have no innate identifying information and so cannot be recognized. +func (s *RawSerializer) RecognizesData(peek io.Reader) (bool, error) { + return false, nil +} + +// NewFrameWriter implements stream framing for this serializer +func (s *RawSerializer) NewFrameWriter(w io.Writer) io.Writer { + return framer.NewLengthDelimitedFrameWriter(w) +} + +// NewFrameReader implements stream framing for this serializer +func (s *RawSerializer) NewFrameReader(r io.Reader) io.Reader { + return framer.NewLengthDelimitedFrameReader(r) +} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/protobuf/protobuf_test.go b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/protobuf/protobuf_test.go new file mode 100644 index 000000000..a581bd4bd --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/protobuf/protobuf_test.go @@ -0,0 +1,341 @@ +// +build proto + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 protobuf_test + +import ( + "bytes" + "encoding/hex" + "fmt" + "reflect" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/api" + _ "k8s.io/kubernetes/pkg/api/install" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/runtime/serializer/protobuf" + "k8s.io/kubernetes/pkg/util/diff" +) + +type testObject struct { + gvk *unversioned.GroupVersionKind +} + +func (d *testObject) GetObjectKind() unversioned.ObjectKind { return d } +func (d *testObject) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) { d.gvk = gvk } +func (d *testObject) GroupVersionKind() *unversioned.GroupVersionKind { return d.gvk } + +type testMarshalable struct { + testObject + data []byte + err error +} + +func (d *testMarshalable) Marshal() ([]byte, error) { + return d.data, d.err +} + +type testBufferedMarshalable struct { + testObject + data []byte + err error +} + +func (d *testBufferedMarshalable) Marshal() ([]byte, error) { + return nil, fmt.Errorf("not invokable") +} + +func (d *testBufferedMarshalable) MarshalTo(data []byte) (int, error) { + copy(data, d.data) + return len(d.data), d.err +} + +func (d *testBufferedMarshalable) Size() int { + return len(d.data) +} + +func TestRecognize(t *testing.T) { + s := protobuf.NewSerializer(nil, nil, "application/protobuf") + ignores := [][]byte{ + nil, + {}, + []byte("k8s"), + {0x6b, 0x38, 0x73, 0x01}, + } + for i, data := range ignores { + if ok, err := s.RecognizesData(bytes.NewBuffer(data)); err != nil || ok { + t.Errorf("%d: should not recognize data: %v", i, err) + } + } + recognizes := [][]byte{ + {0x6b, 0x38, 0x73, 0x00}, + {0x6b, 0x38, 0x73, 0x00, 0x01}, + } + for i, data := range recognizes { + if ok, err := s.RecognizesData(bytes.NewBuffer(data)); err != nil || !ok { + t.Errorf("%d: should recognize data: %v", i, err) + } + } +} + +func TestEncode(t *testing.T) { + obj1 := &testMarshalable{testObject: testObject{}, data: []byte{}} + wire1 := []byte{ + 0x6b, 0x38, 0x73, 0x00, // prefix + 0x0a, 0x04, + 0x0a, 0x00, // apiversion + 0x12, 0x00, // kind + 0x12, 0x00, // data + 0x1a, 0x00, // content-type + 0x22, 0x00, // content-encoding + } + obj2 := &testMarshalable{ + testObject: testObject{gvk: &unversioned.GroupVersionKind{Kind: "test", Group: "other", Version: "version"}}, + data: []byte{0x01, 0x02, 0x03}, + } + wire2 := []byte{ + 0x6b, 0x38, 0x73, 0x00, // prefix + 0x0a, 0x15, + 0x0a, 0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x2f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // apiversion + 0x12, 0x04, 0x74, 0x65, 0x73, 0x74, // kind + 0x12, 0x03, 0x01, 0x02, 0x03, // data + 0x1a, 0x00, // content-type + 0x22, 0x00, // content-encoding + } + + err1 := fmt.Errorf("a test error") + + testCases := []struct { + obj runtime.Object + data []byte + errFn func(error) bool + }{ + { + obj: &testObject{}, + errFn: protobuf.IsNotMarshalable, + }, + { + obj: obj1, + data: wire1, + }, + { + obj: &testMarshalable{testObject: obj1.testObject, err: err1}, + errFn: func(err error) bool { return err == err1 }, + }, + { + // if this test fails, writing the "fast path" marshal is not the same as the "slow path" + obj: &testBufferedMarshalable{testObject: obj1.testObject, data: obj1.data}, + data: wire1, + }, + { + obj: obj2, + data: wire2, + }, + { + // if this test fails, writing the "fast path" marshal is not the same as the "slow path" + obj: &testBufferedMarshalable{testObject: obj2.testObject, data: obj2.data}, + data: wire2, + }, + { + obj: &testBufferedMarshalable{testObject: obj1.testObject, err: err1}, + errFn: func(err error) bool { return err == err1 }, + }, + } + + for i, test := range testCases { + s := protobuf.NewSerializer(nil, nil, "application/protobuf") + data, err := runtime.Encode(s, test.obj) + + switch { + case err == nil && test.errFn != nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil && test.errFn == nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil: + if !test.errFn(err) { + t.Errorf("%d: failed: %v", i, err) + } + if data != nil { + t.Errorf("%d: should not have returned nil data", i) + } + continue + } + + if test.data != nil && !bytes.Equal(test.data, data) { + t.Errorf("%d: unexpected data:\n%s", i, hex.Dump(data)) + continue + } + + if ok, err := s.RecognizesData(bytes.NewBuffer(data)); !ok || err != nil { + t.Errorf("%d: did not recognize data generated by call: %v", i, err) + } + } +} + +func TestDecode(t *testing.T) { + wire1 := []byte{ + 0x6b, 0x38, 0x73, 0x00, // prefix + 0x0a, 0x04, + 0x0a, 0x00, // apiversion + 0x12, 0x00, // kind + 0x12, 0x00, // data + 0x1a, 0x00, // content-type + 0x22, 0x00, // content-encoding + } + wire2 := []byte{ + 0x6b, 0x38, 0x73, 0x00, // prefix + 0x0a, 0x15, + 0x0a, 0x0d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x2f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // apiversion + 0x12, 0x04, 0x74, 0x65, 0x73, 0x74, // kind + 0x12, 0x03, 0x01, 0x02, 0x03, // data + 0x1a, 0x00, // content-type + 0x22, 0x00, // content-encoding + } + + //err1 := fmt.Errorf("a test error") + + testCases := []struct { + obj runtime.Object + data []byte + errFn func(error) bool + }{ + { + obj: &runtime.Unknown{}, + errFn: func(err error) bool { return err.Error() == "empty data" }, + }, + { + data: []byte{0x6b}, + errFn: func(err error) bool { return strings.Contains(err.Error(), "does not appear to be a protobuf message") }, + }, + { + obj: &runtime.Unknown{ + ContentType: "application/protobuf", + Raw: []byte{}, + }, + data: wire1, + }, + { + obj: &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "other/version", + Kind: "test", + }, + ContentType: "application/protobuf", + Raw: []byte{0x01, 0x02, 0x03}, + }, + data: wire2, + }, + } + + for i, test := range testCases { + s := protobuf.NewSerializer(nil, nil, "application/protobuf") + unk := &runtime.Unknown{} + err := runtime.DecodeInto(s, test.data, unk) + + switch { + case err == nil && test.errFn != nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil && test.errFn == nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil: + if !test.errFn(err) { + t.Errorf("%d: failed: %v", i, err) + } + continue + } + + if !reflect.DeepEqual(unk, test.obj) { + t.Errorf("%d: unexpected object:\n%#v", i, unk) + continue + } + } +} + +func TestDecodeObjects(t *testing.T) { + obj1 := &v1.Pod{ + ObjectMeta: v1.ObjectMeta{ + Name: "cool", + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "test", + }, + }, + }, + } + obj1wire, err := obj1.Marshal() + if err != nil { + t.Fatal(err) + } + + wire1, err := (&runtime.Unknown{ + TypeMeta: runtime.TypeMeta{Kind: "Pod", APIVersion: "v1"}, + Raw: obj1wire, + }).Marshal() + if err != nil { + t.Fatal(err) + } + + wire1 = append([]byte{0x6b, 0x38, 0x73, 0x00}, wire1...) + + testCases := []struct { + obj runtime.Object + data []byte + errFn func(error) bool + }{ + { + obj: obj1, + data: wire1, + }, + } + + for i, test := range testCases { + s := protobuf.NewSerializer(api.Scheme, runtime.ObjectTyperToTyper(api.Scheme), "application/protobuf") + obj, err := runtime.Decode(s, test.data) + + switch { + case err == nil && test.errFn != nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil && test.errFn == nil: + t.Errorf("%d: failed: %v", i, err) + continue + case err != nil: + if !test.errFn(err) { + t.Errorf("%d: failed: %v", i, err) + } + if obj != nil { + t.Errorf("%d: should not have returned an object", i) + } + continue + } + + if !api.Semantic.DeepEqual(obj, test.obj) { + t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintDiff(test.obj, obj)) + continue + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/protobuf_extension.go b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/protobuf_extension.go new file mode 100644 index 000000000..1f55df27e --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/protobuf_extension.go @@ -0,0 +1,46 @@ +// +build proto + +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 serializer + +import ( + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/runtime/serializer/protobuf" +) + +// contentTypeProtobuf is the protobuf type exposed for Kubernetes. It is private to prevent others from +// depending on it unintentionally. +// TODO: potentially move to pkg/api (since it's part of the Kube public API) and pass it in to the +// CodecFactory on initialization. +const contentTypeProtobuf = "application/vnd.kubernetes.protobuf" + +func protobufSerializer(scheme *runtime.Scheme) (serializerType, bool) { + serializer := protobuf.NewSerializer(scheme, runtime.ObjectTyperToTyper(scheme), contentTypeProtobuf) + raw := protobuf.NewRawSerializer(scheme, runtime.ObjectTyperToTyper(scheme), contentTypeProtobuf) + return serializerType{ + AcceptContentTypes: []string{contentTypeProtobuf}, + ContentType: contentTypeProtobuf, + FileExtensions: []string{"pb"}, + Serializer: serializer, + RawSerializer: raw, + }, true +} + +func init() { + serializerExtensions = append(serializerExtensions, protobufSerializer) +} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/streaming/streaming.go b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/streaming/streaming.go new file mode 100644 index 000000000..b7daf774d --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/streaming/streaming.go @@ -0,0 +1,137 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 streaming implements encoder and decoder for streams +// of runtime.Objects over io.Writer/Readers. +package streaming + +import ( + "bytes" + "fmt" + "io" + + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/runtime" +) + +// Framer is a factory for creating readers and writers that obey a particular framing pattern. +type Framer interface { + NewFrameReader(r io.Reader) io.Reader + NewFrameWriter(w io.Writer) io.Writer +} + +// Encoder is a runtime.Encoder on a stream. +type Encoder interface { + // Encode will write the provided object to the stream or return an error. It obeys the same + // contract as runtime.Encoder. + Encode(obj runtime.Object, overrides ...unversioned.GroupVersion) error +} + +// Decoder is a runtime.Decoder from a stream. +type Decoder interface { + // Decode will return io.EOF when no more objects are available. + Decode(defaults *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) +} + +// Serializer is a factory for creating encoders and decoders that work over streams. +type Serializer interface { + NewEncoder(w io.Writer) Encoder + NewDecoder(r io.Reader) Decoder +} + +type decoder struct { + reader io.Reader + decoder runtime.Decoder + buf []byte + maxBytes int + resetRead bool +} + +// NewDecoder creates a streaming decoder that reads object chunks from r and decodes them with d. +// The reader is expected to return ErrShortRead if the provided buffer is not large enough to read +// an entire object. +func NewDecoder(r io.Reader, d runtime.Decoder) Decoder { + return &decoder{ + reader: r, + decoder: d, + buf: make([]byte, 1024), + maxBytes: 1024 * 1024, + } +} + +var ErrObjectTooLarge = fmt.Errorf("object to decode was longer than maximum allowed size") + +// Decode reads the next object from the stream and decodes it. +func (d *decoder) Decode(defaults *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) { + base := 0 + for { + n, err := d.reader.Read(d.buf[base:]) + if err == io.ErrShortBuffer { + if n == 0 { + return nil, nil, fmt.Errorf("got short buffer with n=0, base=%d, cap=%d", base, cap(d.buf)) + } + if d.resetRead { + continue + } + // double the buffer size up to maxBytes + if cap(d.buf) < d.maxBytes { + base += n + d.buf = append(d.buf, make([]byte, cap(d.buf))...) + continue + } + // must read the rest of the frame (until we stop getting ErrShortBuffer) + d.resetRead = true + base = 0 + return nil, nil, ErrObjectTooLarge + } + if err != nil { + return nil, nil, err + } + if d.resetRead { + // now that we have drained the large read, continue + d.resetRead = false + continue + } + base += n + break + } + return d.decoder.Decode(d.buf[:base], defaults, into) +} + +type encoder struct { + writer io.Writer + encoder runtime.Encoder + buf *bytes.Buffer +} + +// NewEncoder returns a new streaming encoder. +func NewEncoder(w io.Writer, e runtime.Encoder) Encoder { + return &encoder{ + writer: w, + encoder: e, + buf: &bytes.Buffer{}, + } +} + +// Encode writes the provided object to the nested writer. +func (e *encoder) Encode(obj runtime.Object, overrides ...unversioned.GroupVersion) error { + if err := e.encoder.EncodeToStream(obj, e.buf, overrides...); err != nil { + return err + } + _, err := e.writer.Write(e.buf.Bytes()) + e.buf.Reset() + return err +} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/streaming/streaming_test.go b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/streaming/streaming_test.go new file mode 100644 index 000000000..a5a7f0d5c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/streaming/streaming_test.go @@ -0,0 +1,83 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 streaming + +import ( + "bytes" + "io" + "testing" + + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/framer" +) + +type fakeDecoder struct { + got []byte + obj runtime.Object + err error +} + +func (d *fakeDecoder) Decode(data []byte, gvk *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) { + d.got = data + return d.obj, nil, d.err +} + +func TestEmptyDecoder(t *testing.T) { + buf := bytes.NewBuffer([]byte{}) + d := &fakeDecoder{} + _, _, err := NewDecoder(buf, d).Decode(nil, nil) + if err != io.EOF { + t.Fatal(err) + } +} + +func TestDecoder(t *testing.T) { + frames := [][]byte{ + make([]byte, 1025), + make([]byte, 1024*5), + make([]byte, 1024*1024*5), + make([]byte, 1025), + } + pr, pw := io.Pipe() + fw := framer.NewLengthDelimitedFrameWriter(pw) + go func() { + for i := range frames { + fw.Write(frames[i]) + } + pw.Close() + }() + + r := framer.NewLengthDelimitedFrameReader(pr) + d := &fakeDecoder{} + dec := NewDecoder(r, d) + if _, _, err := dec.Decode(nil, nil); err != nil || !bytes.Equal(d.got, frames[0]) { + t.Fatalf("unexpected %v %v", err, len(d.got)) + } + if _, _, err := dec.Decode(nil, nil); err != nil || !bytes.Equal(d.got, frames[1]) { + t.Fatalf("unexpected %v %v", err, len(d.got)) + } + if _, _, err := dec.Decode(nil, nil); err != ErrObjectTooLarge || !bytes.Equal(d.got, frames[1]) { + t.Fatalf("unexpected %v %v", err, len(d.got)) + } + if _, _, err := dec.Decode(nil, nil); err != nil || !bytes.Equal(d.got, frames[3]) { + t.Fatalf("unexpected %v %v", err, len(d.got)) + } + if _, _, err := dec.Decode(nil, nil); err != io.EOF { + t.Fatalf("unexpected %v %v", err, len(d.got)) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/versioning/versioning.go b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/versioning/versioning.go index ea255a90c..eeafa2a32 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/versioning/versioning.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/versioning/versioning.go @@ -22,24 +22,63 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/runtime/serializer/streaming" ) +// EnableCrossGroupDecoding modifies the given decoder in place, if it is a codec +// from this package. It allows objects from one group to be auto-decoded into +// another group. 'destGroup' must already exist in the codec. +func EnableCrossGroupDecoding(d runtime.Decoder, sourceGroup, destGroup string) error { + internal, ok := d.(*codec) + if !ok { + return fmt.Errorf("unsupported decoder type") + } + + dest, ok := internal.decodeVersion[destGroup] + if !ok { + return fmt.Errorf("group %q is not a possible destination group in the given codec", destGroup) + } + internal.decodeVersion[sourceGroup] = dest + + return nil +} + +// EnableCrossGroupEncoding modifies the given encoder in place, if it is a codec +// from this package. It allows objects from one group to be auto-decoded into +// another group. 'destGroup' must already exist in the codec. +func EnableCrossGroupEncoding(e runtime.Encoder, sourceGroup, destGroup string) error { + internal, ok := e.(*codec) + if !ok { + return fmt.Errorf("unsupported encoder type") + } + + dest, ok := internal.encodeVersion[destGroup] + if !ok { + return fmt.Errorf("group %q is not a possible destination group in the given codec", destGroup) + } + internal.encodeVersion[sourceGroup] = dest + + return nil +} + // NewCodecForScheme is a convenience method for callers that are using a scheme. func NewCodecForScheme( // TODO: I should be a scheme interface? scheme *runtime.Scheme, - serializer runtime.Serializer, + encoder runtime.Encoder, + decoder runtime.Decoder, encodeVersion []unversioned.GroupVersion, decodeVersion []unversioned.GroupVersion, ) runtime.Codec { - return NewCodec(serializer, scheme, scheme, scheme, runtime.ObjectTyperToTyper(scheme), encodeVersion, decodeVersion) + return NewCodec(encoder, decoder, scheme, scheme, scheme, runtime.ObjectTyperToTyper(scheme), encodeVersion, decodeVersion) } // NewCodec takes objects in their internal versions and converts them to external versions before // serializing them. It assumes the serializer provided to it only deals with external versions. // This class is also a serializer, but is generally used with a specific version. func NewCodec( - serializer runtime.Serializer, + encoder runtime.Encoder, + decoder runtime.Decoder, convertor runtime.ObjectConvertor, creater runtime.ObjectCreater, copier runtime.ObjectCopier, @@ -48,11 +87,12 @@ func NewCodec( decodeVersion []unversioned.GroupVersion, ) runtime.Codec { internal := &codec{ - serializer: serializer, - convertor: convertor, - creater: creater, - copier: copier, - typer: typer, + encoder: encoder, + decoder: decoder, + convertor: convertor, + creater: creater, + copier: copier, + typer: typer, } if encodeVersion != nil { internal.encodeVersion = make(map[string]unversioned.GroupVersion) @@ -79,11 +119,12 @@ func NewCodec( } type codec struct { - serializer runtime.Serializer - convertor runtime.ObjectConvertor - creater runtime.ObjectCreater - copier runtime.ObjectCopier - typer runtime.Typer + encoder runtime.Encoder + decoder runtime.Decoder + convertor runtime.ObjectConvertor + creater runtime.ObjectCreater + copier runtime.ObjectCopier + typer runtime.Typer encodeVersion map[string]unversioned.GroupVersion decodeVersion map[string]unversioned.GroupVersion @@ -98,7 +139,7 @@ func (c *codec) Decode(data []byte, defaultGVK *unversioned.GroupVersionKind, in into = versioned.Last() } - obj, gvk, err := c.serializer.Decode(data, defaultGVK, into) + obj, gvk, err := c.decoder.Decode(data, defaultGVK, into) if err != nil { return nil, gvk, err } @@ -177,7 +218,7 @@ func (c *codec) Decode(data []byte, defaultGVK *unversioned.GroupVersionKind, in // encoding the object the first override that matches the object's group is used. Other overrides are ignored. func (c *codec) EncodeToStream(obj runtime.Object, w io.Writer, overrides ...unversioned.GroupVersion) error { if _, ok := obj.(*runtime.Unknown); ok { - return c.serializer.EncodeToStream(obj, w, overrides...) + return c.encoder.EncodeToStream(obj, w, overrides...) } gvk, isUnversioned, err := c.typer.ObjectKind(obj) if err != nil { @@ -188,7 +229,7 @@ func (c *codec) EncodeToStream(obj runtime.Object, w io.Writer, overrides ...unv old := obj.GetObjectKind().GroupVersionKind() obj.GetObjectKind().SetGroupVersionKind(gvk) defer obj.GetObjectKind().SetGroupVersionKind(old) - return c.serializer.EncodeToStream(obj, w, overrides...) + return c.encoder.EncodeToStream(obj, w, overrides...) } targetGV, ok := c.encodeVersion[gvk.Group] @@ -234,7 +275,25 @@ func (c *codec) EncodeToStream(obj runtime.Object, w io.Writer, overrides ...unv obj.GetObjectKind().SetGroupVersionKind(&unversioned.GroupVersionKind{Group: targetGV.Group, Version: targetGV.Version, Kind: gvk.Kind}) } - return c.serializer.EncodeToStream(obj, w, overrides...) + return c.encoder.EncodeToStream(obj, w, overrides...) +} + +// NewFrameWriter calls into the nested encoder to expose its framing +func (c *codec) NewFrameWriter(w io.Writer) io.Writer { + f, ok := c.encoder.(streaming.Framer) + if !ok { + return nil + } + return f.NewFrameWriter(w) +} + +// NewFrameReader calls into the nested decoder to expose its framing +func (c *codec) NewFrameReader(r io.Reader) io.Reader { + f, ok := c.decoder.(streaming.Framer) + if !ok { + return nil + } + return f.NewFrameReader(r) } // promoteOrPrependGroupVersion finds the group version in the provided group versions that has the same group as target. diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/versioning/versioning_test.go b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/versioning/versioning_test.go index 3d7eba27e..1b45abfd5 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/serializer/versioning/versioning_test.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/serializer/versioning/versioning_test.go @@ -24,7 +24,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/util" + "k8s.io/kubernetes/pkg/util/diff" ) type testDecodable struct { @@ -169,7 +169,7 @@ func TestDecode(t *testing.T) { for i, test := range testCases { t.Logf("%d", i) - s := NewCodec(test.serializer, test.convertor, test.creater, test.copier, test.typer, test.encodes, test.decodes) + s := NewCodec(test.serializer, test.serializer, test.convertor, test.creater, test.copier, test.typer, test.encodes, test.decodes) obj, gvk, err := s.Decode([]byte(`{}`), test.defaultGVK, test.into) if !reflect.DeepEqual(test.expectedGVK, gvk) { @@ -201,11 +201,11 @@ func TestDecode(t *testing.T) { switch { case test.expectedObject != nil: if !reflect.DeepEqual(test.expectedObject, obj) { - t.Errorf("%d: unexpected object:\n%s", i, util.ObjectGoPrintSideBySide(test.expectedObject, obj)) + t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.expectedObject, obj)) } case test.sameObject != nil: if test.sameObject != obj { - t.Errorf("%d: unexpected object:\n%s", i, util.ObjectGoPrintSideBySide(test.sameObject, obj)) + t.Errorf("%d: unexpected object:\n%s", i, diff.ObjectGoPrintSideBySide(test.sameObject, obj)) } case obj != nil: t.Errorf("%d: unexpected object: %#v", i, obj) diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/types.go b/vendor/k8s.io/kubernetes/pkg/runtime/types.go index 3b8cede44..9b1f8301d 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/types.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/types.go @@ -36,6 +36,10 @@ type TypeMeta struct { Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` } +const ( + ContentTypeJSON string = "application/json" +) + // RawExtension is used to hold extensions in external versions. // // To use this, make a field which has RawExtension as its type in your external, versioned @@ -80,8 +84,10 @@ type TypeMeta struct { // // +protobuf=true type RawExtension struct { - // RawJSON is the underlying serialization of this object. - RawJSON []byte + // Raw is the underlying serialization of this object. + // + // TODO: Determine how to detect ContentType and ContentEncoding of 'Raw' data. + Raw []byte // Object can hold a representation of this extension - useful for working with versioned // structs. Object Object `json:"-"` @@ -96,10 +102,16 @@ type RawExtension struct { // +protobuf=true type Unknown struct { TypeMeta `json:",inline"` - // RawJSON will hold the complete JSON of the object which couldn't be matched + // Raw will hold the complete serialized object which couldn't be matched // with a registered type. Most likely, nothing should be done with this // except for passing it through the system. - RawJSON []byte + Raw []byte + // ContentEncoding is encoding used to encode 'Raw' data. + // Unspecified means no encoding. + ContentEncoding string + // ContentType is serialization method used to serialize 'Raw'. + // Unspecified means ContentTypeJSON. + ContentType string } // Unstructured allows objects that do not have Golang structs registered to be manipulated @@ -109,16 +121,30 @@ type Unknown struct { // metadata and field mutatation. type Unstructured struct { TypeMeta `json:",inline"` + + // Name is populated from metadata (if present) upon deserialization + Name string + // Object is a JSON compatible map with string, float, int, []interface{}, or map[string]interface{} // children. Object map[string]interface{} } +// UnstructuredList allows lists that do not have Golang structs +// registered to be manipulated generically. This can be used to deal +// with the API lists from a plug-in. +type UnstructuredList struct { + TypeMeta `json:",inline"` + + // Items is a list of unstructured objects. + Items []*Unstructured `json:"items"` +} + // VersionedObjects is used by Decoders to give callers a way to access all versions // of an object during the decoding process. type VersionedObjects struct { // Objects is the set of objects retrieved during decoding, in order of conversion. - // The 0 index is the object as serialized on the wire. If conversion has occured, + // The 0 index is the object as serialized on the wire. If conversion has occurred, // other objects may be present. The right most object is the same as would be returned // by a normal Decode call. Objects []Object diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/types_proto.go b/vendor/k8s.io/kubernetes/pkg/runtime/types_proto.go new file mode 100644 index 000000000..dd9a288c4 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/runtime/types_proto.go @@ -0,0 +1,62 @@ +// +build proto + +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 runtime + +type ProtobufMarshaller interface { + MarshalTo(data []byte) (int, error) +} + +// NestedMarshalTo allows a caller to avoid extra allocations during serialization of an Unknown +// that will contain an object that implements ProtobufMarshaller. +func (m *Unknown) NestedMarshalTo(data []byte, b ProtobufMarshaller, size uint64) (int, error) { + var i int + _ = i + var l int + _ = l + data[i] = 0xa + i++ + i = encodeVarintGenerated(data, i, uint64(m.TypeMeta.Size())) + n1, err := m.TypeMeta.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + + if b != nil { + data[i] = 0x12 + i++ + i = encodeVarintGenerated(data, i, size) + n2, err := b.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + + data[i] = 0x1a + i++ + i = encodeVarintGenerated(data, i, uint64(len(m.ContentEncoding))) + i += copy(data[i:], m.ContentEncoding) + + data[i] = 0x22 + i++ + i = encodeVarintGenerated(data, i, uint64(len(m.ContentType))) + i += copy(data[i:], m.ContentType) + return i, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/unstructured.go b/vendor/k8s.io/kubernetes/pkg/runtime/unstructured.go index 59dfa2458..ba10aabf1 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/unstructured.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/unstructured.go @@ -17,10 +17,11 @@ limitations under the License. package runtime import ( - "encoding/json" + gojson "encoding/json" "io" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/util/json" ) // UnstructuredJSONScheme is capable of converting JSON data into the Unstructured @@ -30,13 +31,88 @@ var UnstructuredJSONScheme Codec = unstructuredJSONScheme{} type unstructuredJSONScheme struct{} -func (s unstructuredJSONScheme) Decode(data []byte, _ *unversioned.GroupVersionKind, _ Object) (Object, *unversioned.GroupVersionKind, error) { - unstruct := &Unstructured{} +func (s unstructuredJSONScheme) Decode(data []byte, _ *unversioned.GroupVersionKind, obj Object) (Object, *unversioned.GroupVersionKind, error) { + var err error + if obj != nil { + err = s.decodeInto(data, obj) + } else { + obj, err = s.decode(data) + } - m := make(map[string]interface{}) - if err := json.Unmarshal(data, &m); err != nil { + if err != nil { return nil, nil, err } + + gvk := obj.GetObjectKind().GroupVersionKind() + if len(gvk.Kind) == 0 { + return nil, gvk, NewMissingKindErr(string(data)) + } + + return obj, gvk, nil +} + +func (unstructuredJSONScheme) EncodeToStream(obj Object, w io.Writer, overrides ...unversioned.GroupVersion) error { + switch t := obj.(type) { + case *Unstructured: + return json.NewEncoder(w).Encode(t.Object) + case *UnstructuredList: + type encodeList struct { + TypeMeta `json:",inline"` + Items []map[string]interface{} `json:"items"` + } + eList := encodeList{ + TypeMeta: t.TypeMeta, + } + for _, i := range t.Items { + eList.Items = append(eList.Items, i.Object) + } + return json.NewEncoder(w).Encode(eList) + case *Unknown: + // TODO: Unstructured needs to deal with ContentType. + _, err := w.Write(t.Raw) + return err + default: + return json.NewEncoder(w).Encode(t) + } +} + +func (s unstructuredJSONScheme) decode(data []byte) (Object, error) { + type detector struct { + Items gojson.RawMessage + } + var det detector + if err := json.Unmarshal(data, &det); err != nil { + return nil, err + } + + if det.Items != nil { + list := &UnstructuredList{} + err := s.decodeToList(data, list) + return list, err + } + + // No Items field, so it wasn't a list. + unstruct := &Unstructured{} + err := s.decodeToUnstructured(data, unstruct) + return unstruct, err +} +func (s unstructuredJSONScheme) decodeInto(data []byte, obj Object) error { + switch x := obj.(type) { + case *Unstructured: + return s.decodeToUnstructured(data, x) + case *UnstructuredList: + return s.decodeToList(data, x) + default: + return json.Unmarshal(data, x) + } +} + +func (unstructuredJSONScheme) decodeToUnstructured(data []byte, unstruct *Unstructured) error { + m := make(map[string]interface{}) + if err := json.Unmarshal(data, &m); err != nil { + return err + } + if v, ok := m["kind"]; ok { if s, ok := v.(string); ok { unstruct.Kind = s @@ -47,30 +123,39 @@ func (s unstructuredJSONScheme) Decode(data []byte, _ *unversioned.GroupVersionK unstruct.APIVersion = s } } - - if len(unstruct.APIVersion) == 0 { - return nil, nil, NewMissingVersionErr(string(data)) - } - gv, err := unversioned.ParseGroupVersion(unstruct.APIVersion) - if err != nil { - return nil, nil, err - } - gvk := gv.WithKind(unstruct.Kind) - if len(unstruct.Kind) == 0 { - return nil, &gvk, NewMissingKindErr(string(data)) + if metadata, ok := m["metadata"]; ok { + if metadata, ok := metadata.(map[string]interface{}); ok { + if name, ok := metadata["name"]; ok { + if name, ok := name.(string); ok { + unstruct.Name = name + } + } + } } unstruct.Object = m - return unstruct, &gvk, nil + + return nil } -func (s unstructuredJSONScheme) EncodeToStream(obj Object, w io.Writer, overrides ...unversioned.GroupVersion) error { - switch t := obj.(type) { - case *Unstructured: - return json.NewEncoder(w).Encode(t.Object) - case *Unknown: - _, err := w.Write(t.RawJSON) - return err - default: - return json.NewEncoder(w).Encode(t) +func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList) error { + type decodeList struct { + TypeMeta `json:",inline"` + Items []gojson.RawMessage } + + var dList decodeList + if err := json.Unmarshal(data, &dList); err != nil { + return err + } + + list.TypeMeta = dList.TypeMeta + list.Items = nil + for _, i := range dList.Items { + unstruct := &Unstructured{} + if err := s.decodeToUnstructured([]byte(i), unstruct); err != nil { + return err + } + list.Items = append(list.Items, unstruct) + } + return nil } diff --git a/vendor/k8s.io/kubernetes/pkg/runtime/unstructured_test.go b/vendor/k8s.io/kubernetes/pkg/runtime/unstructured_test.go index cca0fe251..fee7fbe3a 100644 --- a/vendor/k8s.io/kubernetes/pkg/runtime/unstructured_test.go +++ b/vendor/k8s.io/kubernetes/pkg/runtime/unstructured_test.go @@ -18,10 +18,13 @@ package runtime_test import ( "fmt" + "reflect" + "strings" "testing" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/runtime" ) @@ -31,8 +34,16 @@ func TestDecodeUnstructured(t *testing.T) { pl := &api.List{ Items: []runtime.Object{ &api.Pod{ObjectMeta: api.ObjectMeta{Name: "1"}}, - &runtime.Unknown{TypeMeta: runtime.TypeMeta{Kind: "Pod", APIVersion: groupVersionString}, RawJSON: []byte(rawJson)}, - &runtime.Unknown{TypeMeta: runtime.TypeMeta{Kind: "", APIVersion: groupVersionString}, RawJSON: []byte(rawJson)}, + &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{Kind: "Pod", APIVersion: groupVersionString}, + Raw: []byte(rawJson), + ContentType: runtime.ContentTypeJSON, + }, + &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{Kind: "", APIVersion: groupVersionString}, + Raw: []byte(rawJson), + ContentType: runtime.ContentTypeJSON, + }, &runtime.Unstructured{TypeMeta: runtime.TypeMeta{Kind: "Foo", APIVersion: "Bar"}, Object: map[string]interface{}{"test": "value"}}, }, } @@ -46,3 +57,140 @@ func TestDecodeUnstructured(t *testing.T) { t.Errorf("object not converted: %#v", pl.Items[2]) } } + +func TestDecode(t *testing.T) { + tcs := []struct { + json []byte + want runtime.Object + }{ + { + json: []byte(`{"apiVersion": "test", "kind": "test_kind"}`), + want: &runtime.Unstructured{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "test", + Kind: "test_kind", + }, + Object: map[string]interface{}{"apiVersion": "test", "kind": "test_kind"}, + }, + }, + { + json: []byte(`{"apiVersion": "test", "kind": "test_list", "items": []}`), + want: &runtime.UnstructuredList{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "test", + Kind: "test_list", + }, + }, + }, + { + json: []byte(`{"items": [{"metadata": {"name": "object1"}, "apiVersion": "test", "kind": "test_kind"}, {"metadata": {"name": "object2"}, "apiVersion": "test", "kind": "test_kind"}], "apiVersion": "test", "kind": "test_list"}`), + want: &runtime.UnstructuredList{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "test", + Kind: "test_list", + }, + Items: []*runtime.Unstructured{ + { + TypeMeta: runtime.TypeMeta{ + APIVersion: "test", + Kind: "test_kind", + }, + Name: "object1", + Object: map[string]interface{}{ + "metadata": map[string]interface{}{"name": "object1"}, + "apiVersion": "test", + "kind": "test_kind", + }, + }, + { + TypeMeta: runtime.TypeMeta{ + APIVersion: "test", + Kind: "test_kind", + }, + Name: "object2", + Object: map[string]interface{}{ + "metadata": map[string]interface{}{"name": "object2"}, + "apiVersion": "test", + "kind": "test_kind", + }, + }, + }, + }, + }, + } + + for _, tc := range tcs { + got, _, err := runtime.UnstructuredJSONScheme.Decode(tc.json, nil, nil) + if err != nil { + t.Errorf("Unexpected error for %q: %v", string(tc.json), err) + continue + } + + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("Decode(%q) want: %v\ngot: %v", string(tc.json), tc.want, got) + } + } +} + +func TestDecodeNumbers(t *testing.T) { + + // Start with a valid pod + originalJSON := []byte(`{ + "kind":"Pod", + "apiVersion":"v1", + "metadata":{"name":"pod","namespace":"foo"}, + "spec":{ + "containers":[{"name":"container","image":"container"}], + "activeDeadlineSeconds":1000030003 + } + }`) + + pod := &api.Pod{} + + // Decode with structured codec + codec, err := testapi.GetCodecForObject(pod) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + err = runtime.DecodeInto(codec, originalJSON, pod) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + // ensure pod is valid + if errs := validation.ValidatePod(pod); len(errs) > 0 { + t.Fatalf("pod should be valid: %v", errs) + } + + // Round-trip with unstructured codec + unstructuredObj, err := runtime.Decode(runtime.UnstructuredJSONScheme, originalJSON) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + roundtripJSON, err := runtime.Encode(runtime.UnstructuredJSONScheme, unstructuredObj) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Make sure we serialize back out in int form + if !strings.Contains(string(roundtripJSON), `"activeDeadlineSeconds":1000030003`) { + t.Errorf("Expected %s, got %s", `"activeDeadlineSeconds":1000030003`, string(roundtripJSON)) + } + + // Decode with structured codec again + obj2, err := runtime.Decode(codec, roundtripJSON) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + // ensure pod is still valid + pod2, ok := obj2.(*api.Pod) + if !ok { + t.Fatalf("expected an *api.Pod, got %#v", obj2) + } + if errs := validation.ValidatePod(pod2); len(errs) > 0 { + t.Fatalf("pod should be valid: %v", errs) + } + // ensure round-trip preserved large integers + if !reflect.DeepEqual(pod, pod2) { + t.Fatalf("Expected\n\t%#v, got \n\t%#v", pod, pod2) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/securitycontext/doc.go b/vendor/k8s.io/kubernetes/pkg/securitycontext/doc.go new file mode 100644 index 000000000..9e9a84ba1 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/securitycontext/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 securitycontext contains security context api implementations +package securitycontext diff --git a/vendor/k8s.io/kubernetes/pkg/securitycontext/fake.go b/vendor/k8s.io/kubernetes/pkg/securitycontext/fake.go new file mode 100644 index 000000000..5c77f525c --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/securitycontext/fake.go @@ -0,0 +1,45 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 securitycontext + +import ( + "k8s.io/kubernetes/pkg/api" + + docker "github.com/fsouza/go-dockerclient" +) + +// ValidSecurityContextWithContainerDefaults creates a valid security context provider based on +// empty container defaults. Used for testing. +func ValidSecurityContextWithContainerDefaults() *api.SecurityContext { + priv := false + return &api.SecurityContext{ + Capabilities: &api.Capabilities{}, + Privileged: &priv, + } +} + +// NewFakeSecurityContextProvider creates a new, no-op security context provider. +func NewFakeSecurityContextProvider() SecurityContextProvider { + return FakeSecurityContextProvider{} +} + +type FakeSecurityContextProvider struct{} + +func (p FakeSecurityContextProvider) ModifyContainerConfig(pod *api.Pod, container *api.Container, config *docker.Config) { +} +func (p FakeSecurityContextProvider) ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *docker.HostConfig) { +} diff --git a/vendor/k8s.io/kubernetes/pkg/securitycontext/provider.go b/vendor/k8s.io/kubernetes/pkg/securitycontext/provider.go new file mode 100644 index 000000000..9bd5b16b6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/securitycontext/provider.go @@ -0,0 +1,187 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 securitycontext + +import ( + "fmt" + "strconv" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/kubelet/leaky" + + docker "github.com/fsouza/go-dockerclient" +) + +// NewSimpleSecurityContextProvider creates a new SimpleSecurityContextProvider. +func NewSimpleSecurityContextProvider() SecurityContextProvider { + return SimpleSecurityContextProvider{} +} + +// SimpleSecurityContextProvider is the default implementation of a SecurityContextProvider. +type SimpleSecurityContextProvider struct{} + +// ModifyContainerConfig is called before the Docker createContainer call. +// The security context provider can make changes to the Config with which +// the container is created. +func (p SimpleSecurityContextProvider) ModifyContainerConfig(pod *api.Pod, container *api.Container, config *docker.Config) { + effectiveSC := DetermineEffectiveSecurityContext(pod, container) + if effectiveSC == nil { + return + } + if effectiveSC.RunAsUser != nil { + config.User = strconv.Itoa(int(*effectiveSC.RunAsUser)) + } +} + +// ModifyHostConfig is called before the Docker runContainer call. +// The security context provider can make changes to the HostConfig, affecting +// security options, whether the container is privileged, volume binds, etc. +func (p SimpleSecurityContextProvider) ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *docker.HostConfig) { + // Apply pod security context + if container.Name != leaky.PodInfraContainerName && pod.Spec.SecurityContext != nil { + // TODO: We skip application of supplemental groups to the + // infra container to work around a runc issue which + // requires containers to have the '/etc/group'. For + // more information see: + // https://github.com/opencontainers/runc/pull/313 + // This can be removed once the fix makes it into the + // required version of docker. + if pod.Spec.SecurityContext.SupplementalGroups != nil { + hostConfig.GroupAdd = make([]string, len(pod.Spec.SecurityContext.SupplementalGroups)) + for i, group := range pod.Spec.SecurityContext.SupplementalGroups { + hostConfig.GroupAdd[i] = strconv.Itoa(int(group)) + } + } + + if pod.Spec.SecurityContext.FSGroup != nil { + hostConfig.GroupAdd = append(hostConfig.GroupAdd, strconv.Itoa(int(*pod.Spec.SecurityContext.FSGroup))) + } + } + + // Apply effective security context for container + effectiveSC := DetermineEffectiveSecurityContext(pod, container) + if effectiveSC == nil { + return + } + + if effectiveSC.Privileged != nil { + hostConfig.Privileged = *effectiveSC.Privileged + } + + if effectiveSC.Capabilities != nil { + add, drop := MakeCapabilities(effectiveSC.Capabilities.Add, effectiveSC.Capabilities.Drop) + hostConfig.CapAdd = add + hostConfig.CapDrop = drop + } + + if effectiveSC.SELinuxOptions != nil { + hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelUser, effectiveSC.SELinuxOptions.User) + hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelRole, effectiveSC.SELinuxOptions.Role) + hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelType, effectiveSC.SELinuxOptions.Type) + hostConfig.SecurityOpt = modifySecurityOption(hostConfig.SecurityOpt, dockerLabelLevel, effectiveSC.SELinuxOptions.Level) + } +} + +// modifySecurityOption adds the security option of name to the config array with value in the form +// of name:value +func modifySecurityOption(config []string, name, value string) []string { + if len(value) > 0 { + config = append(config, fmt.Sprintf("%s:%s", name, value)) + } + return config +} + +// MakeCapabilities creates string slices from Capability slices +func MakeCapabilities(capAdd []api.Capability, capDrop []api.Capability) ([]string, []string) { + var ( + addCaps []string + dropCaps []string + ) + for _, cap := range capAdd { + addCaps = append(addCaps, string(cap)) + } + for _, cap := range capDrop { + dropCaps = append(dropCaps, string(cap)) + } + return addCaps, dropCaps +} + +func DetermineEffectiveSecurityContext(pod *api.Pod, container *api.Container) *api.SecurityContext { + effectiveSc := securityContextFromPodSecurityContext(pod) + containerSc := container.SecurityContext + + if effectiveSc == nil && containerSc == nil { + return nil + } + if effectiveSc != nil && containerSc == nil { + return effectiveSc + } + if effectiveSc == nil && containerSc != nil { + return containerSc + } + + if containerSc.SELinuxOptions != nil { + effectiveSc.SELinuxOptions = new(api.SELinuxOptions) + *effectiveSc.SELinuxOptions = *containerSc.SELinuxOptions + } + + if containerSc.Capabilities != nil { + effectiveSc.Capabilities = new(api.Capabilities) + *effectiveSc.Capabilities = *containerSc.Capabilities + } + + if containerSc.Privileged != nil { + effectiveSc.Privileged = new(bool) + *effectiveSc.Privileged = *containerSc.Privileged + } + + if containerSc.RunAsUser != nil { + effectiveSc.RunAsUser = new(int64) + *effectiveSc.RunAsUser = *containerSc.RunAsUser + } + + if containerSc.RunAsNonRoot != nil { + effectiveSc.RunAsNonRoot = new(bool) + *effectiveSc.RunAsNonRoot = *containerSc.RunAsNonRoot + } + + return effectiveSc +} + +func securityContextFromPodSecurityContext(pod *api.Pod) *api.SecurityContext { + if pod.Spec.SecurityContext == nil { + return nil + } + + synthesized := &api.SecurityContext{} + + if pod.Spec.SecurityContext.SELinuxOptions != nil { + synthesized.SELinuxOptions = &api.SELinuxOptions{} + *synthesized.SELinuxOptions = *pod.Spec.SecurityContext.SELinuxOptions + } + if pod.Spec.SecurityContext.RunAsUser != nil { + synthesized.RunAsUser = new(int64) + *synthesized.RunAsUser = *pod.Spec.SecurityContext.RunAsUser + } + + if pod.Spec.SecurityContext.RunAsNonRoot != nil { + synthesized.RunAsNonRoot = new(bool) + *synthesized.RunAsNonRoot = *pod.Spec.SecurityContext.RunAsNonRoot + } + + return synthesized +} diff --git a/vendor/k8s.io/kubernetes/pkg/securitycontext/provider_test.go b/vendor/k8s.io/kubernetes/pkg/securitycontext/provider_test.go new file mode 100644 index 000000000..062f0e7f7 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/securitycontext/provider_test.go @@ -0,0 +1,316 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 securitycontext + +import ( + "fmt" + "reflect" + "strconv" + "testing" + + docker "github.com/fsouza/go-dockerclient" + "k8s.io/kubernetes/pkg/api" + apitesting "k8s.io/kubernetes/pkg/api/testing" +) + +func TestModifyContainerConfig(t *testing.T) { + var uid int64 = 123 + var overrideUid int64 = 321 + + cases := []struct { + name string + podSc *api.PodSecurityContext + sc *api.SecurityContext + expected *docker.Config + }{ + { + name: "container.SecurityContext.RunAsUser set", + sc: &api.SecurityContext{ + RunAsUser: &uid, + }, + expected: &docker.Config{ + User: strconv.FormatInt(uid, 10), + }, + }, + { + name: "no RunAsUser value set", + sc: &api.SecurityContext{}, + expected: &docker.Config{}, + }, + { + name: "pod.Spec.SecurityContext.RunAsUser set", + podSc: &api.PodSecurityContext{ + RunAsUser: &uid, + }, + expected: &docker.Config{ + User: strconv.FormatInt(uid, 10), + }, + }, + { + name: "container.SecurityContext.RunAsUser overrides pod.Spec.SecurityContext.RunAsUser", + podSc: &api.PodSecurityContext{ + RunAsUser: &uid, + }, + sc: &api.SecurityContext{ + RunAsUser: &overrideUid, + }, + expected: &docker.Config{ + User: strconv.FormatInt(overrideUid, 10), + }, + }, + } + + provider := NewSimpleSecurityContextProvider() + dummyContainer := &api.Container{} + for _, tc := range cases { + pod := &api.Pod{Spec: api.PodSpec{SecurityContext: tc.podSc}} + dummyContainer.SecurityContext = tc.sc + dockerCfg := &docker.Config{} + + provider.ModifyContainerConfig(pod, dummyContainer, dockerCfg) + + if e, a := tc.expected, dockerCfg; !reflect.DeepEqual(e, a) { + t.Errorf("%v: unexpected modification of docker config\nExpected:\n\n%#v\n\nGot:\n\n%#v", tc.name, e, a) + } + } +} + +func TestModifyHostConfig(t *testing.T) { + priv := true + setPrivSC := &api.SecurityContext{} + setPrivSC.Privileged = &priv + setPrivHC := &docker.HostConfig{ + Privileged: true, + } + + setCapsHC := &docker.HostConfig{ + CapAdd: []string{"addCapA", "addCapB"}, + CapDrop: []string{"dropCapA", "dropCapB"}, + } + + setSELinuxHC := &docker.HostConfig{} + setSELinuxHC.SecurityOpt = []string{ + fmt.Sprintf("%s:%s", dockerLabelUser, "user"), + fmt.Sprintf("%s:%s", dockerLabelRole, "role"), + fmt.Sprintf("%s:%s", dockerLabelType, "type"), + fmt.Sprintf("%s:%s", dockerLabelLevel, "level"), + } + + // seLinuxLabelsSC := fullValidSecurityContext() + // seLinuxLabelsHC := fullValidHostConfig() + + cases := []struct { + name string + podSc *api.PodSecurityContext + sc *api.SecurityContext + expected *docker.HostConfig + }{ + { + name: "fully set container.SecurityContext", + sc: fullValidSecurityContext(), + expected: fullValidHostConfig(), + }, + { + name: "container.SecurityContext.Privileged", + sc: setPrivSC, + expected: setPrivHC, + }, + { + name: "container.SecurityContext.Capabilities", + sc: &api.SecurityContext{ + Capabilities: inputCapabilities(), + }, + expected: setCapsHC, + }, + { + name: "container.SecurityContext.SELinuxOptions", + sc: &api.SecurityContext{ + SELinuxOptions: inputSELinuxOptions(), + }, + expected: setSELinuxHC, + }, + { + name: "pod.Spec.SecurityContext.SELinuxOptions", + podSc: &api.PodSecurityContext{ + SELinuxOptions: inputSELinuxOptions(), + }, + expected: setSELinuxHC, + }, + { + name: "container.SecurityContext overrides pod.Spec.SecurityContext", + podSc: overridePodSecurityContext(), + sc: fullValidSecurityContext(), + expected: fullValidHostConfig(), + }, + } + + provider := NewSimpleSecurityContextProvider() + dummyContainer := &api.Container{} + + for _, tc := range cases { + pod := &api.Pod{Spec: api.PodSpec{SecurityContext: tc.podSc}} + dummyContainer.SecurityContext = tc.sc + dockerCfg := &docker.HostConfig{} + + provider.ModifyHostConfig(pod, dummyContainer, dockerCfg) + + if e, a := tc.expected, dockerCfg; !reflect.DeepEqual(e, a) { + t.Errorf("%v: unexpected modification of host config\nExpected:\n\n%#v\n\nGot:\n\n%#v", tc.name, e, a) + } + } +} + +func TestModifyHostConfigPodSecurityContext(t *testing.T) { + supplementalGroupsSC := &api.PodSecurityContext{} + supplementalGroupsSC.SupplementalGroups = []int64{2222} + supplementalGroupHC := fullValidHostConfig() + supplementalGroupHC.GroupAdd = []string{"2222"} + fsGroupHC := fullValidHostConfig() + fsGroupHC.GroupAdd = []string{"1234"} + bothHC := fullValidHostConfig() + bothHC.GroupAdd = []string{"2222", "1234"} + fsGroup := int64(1234) + + testCases := map[string]struct { + securityContext *api.PodSecurityContext + expected *docker.HostConfig + }{ + "nil": { + securityContext: nil, + expected: fullValidHostConfig(), + }, + "SupplementalGroup": { + securityContext: supplementalGroupsSC, + expected: supplementalGroupHC, + }, + "FSGroup": { + securityContext: &api.PodSecurityContext{FSGroup: &fsGroup}, + expected: fsGroupHC, + }, + "FSGroup + SupplementalGroups": { + securityContext: &api.PodSecurityContext{ + SupplementalGroups: []int64{2222}, + FSGroup: &fsGroup, + }, + expected: bothHC, + }, + } + + provider := NewSimpleSecurityContextProvider() + dummyContainer := &api.Container{} + dummyContainer.SecurityContext = fullValidSecurityContext() + dummyPod := &api.Pod{ + Spec: apitesting.DeepEqualSafePodSpec(), + } + + for k, v := range testCases { + dummyPod.Spec.SecurityContext = v.securityContext + dockerCfg := &docker.HostConfig{} + provider.ModifyHostConfig(dummyPod, dummyContainer, dockerCfg) + if !reflect.DeepEqual(v.expected, dockerCfg) { + t.Errorf("unexpected modification of host config for %s. Expected: %#v Got: %#v", k, v.expected, dockerCfg) + } + } +} + +func TestModifySecurityOption(t *testing.T) { + testCases := []struct { + name string + config []string + optName string + optVal string + expected []string + }{ + { + name: "Empty val", + config: []string{"a:b", "c:d"}, + optName: "optA", + optVal: "", + expected: []string{"a:b", "c:d"}, + }, + { + name: "Valid", + config: []string{"a:b", "c:d"}, + optName: "e", + optVal: "f", + expected: []string{"a:b", "c:d", "e:f"}, + }, + } + + for _, tc := range testCases { + actual := modifySecurityOption(tc.config, tc.optName, tc.optVal) + if !reflect.DeepEqual(tc.expected, actual) { + t.Errorf("Failed to apply options correctly for tc: %s. Expected: %v but got %v", tc.name, tc.expected, actual) + } + } +} + +func overridePodSecurityContext() *api.PodSecurityContext { + return &api.PodSecurityContext{ + SELinuxOptions: &api.SELinuxOptions{ + User: "user2", + Role: "role2", + Type: "type2", + Level: "level2", + }, + } +} + +func fullValidPodSecurityContext() *api.PodSecurityContext { + return &api.PodSecurityContext{ + SELinuxOptions: inputSELinuxOptions(), + } +} + +func fullValidSecurityContext() *api.SecurityContext { + priv := true + return &api.SecurityContext{ + Privileged: &priv, + Capabilities: inputCapabilities(), + SELinuxOptions: inputSELinuxOptions(), + } +} + +func inputCapabilities() *api.Capabilities { + return &api.Capabilities{ + Add: []api.Capability{"addCapA", "addCapB"}, + Drop: []api.Capability{"dropCapA", "dropCapB"}, + } +} + +func inputSELinuxOptions() *api.SELinuxOptions { + return &api.SELinuxOptions{ + User: "user", + Role: "role", + Type: "type", + Level: "level", + } +} + +func fullValidHostConfig() *docker.HostConfig { + return &docker.HostConfig{ + Privileged: true, + CapAdd: []string{"addCapA", "addCapB"}, + CapDrop: []string{"dropCapA", "dropCapB"}, + SecurityOpt: []string{ + fmt.Sprintf("%s:%s", dockerLabelUser, "user"), + fmt.Sprintf("%s:%s", dockerLabelRole, "role"), + fmt.Sprintf("%s:%s", dockerLabelType, "type"), + fmt.Sprintf("%s:%s", dockerLabelLevel, "level"), + }, + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/securitycontext/types.go b/vendor/k8s.io/kubernetes/pkg/securitycontext/types.go new file mode 100644 index 000000000..61549cc00 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/securitycontext/types.go @@ -0,0 +1,45 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 securitycontext + +import ( + "k8s.io/kubernetes/pkg/api" + + docker "github.com/fsouza/go-dockerclient" +) + +type SecurityContextProvider interface { + // ModifyContainerConfig is called before the Docker createContainer call. + // The security context provider can make changes to the Config with which + // the container is created. + ModifyContainerConfig(pod *api.Pod, container *api.Container, config *docker.Config) + + // ModifyHostConfig is called before the Docker createContainer call. + // The security context provider can make changes to the HostConfig, affecting + // security options, whether the container is privileged, volume binds, etc. + // An error is returned if it's not possible to secure the container as requested + // with a security context. + ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *docker.HostConfig) +} + +const ( + dockerLabelUser string = "label:user" + dockerLabelRole string = "label:role" + dockerLabelType string = "label:type" + dockerLabelLevel string = "label:level" + dockerLabelDisable string = "label:disable" +) diff --git a/vendor/k8s.io/kubernetes/pkg/securitycontext/util.go b/vendor/k8s.io/kubernetes/pkg/securitycontext/util.go new file mode 100644 index 000000000..32b97af23 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/securitycontext/util.go @@ -0,0 +1,89 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 securitycontext + +import ( + "fmt" + "strings" + + "k8s.io/kubernetes/pkg/api" +) + +// HasPrivilegedRequest returns the value of SecurityContext.Privileged, taking into account +// the possibility of nils +func HasPrivilegedRequest(container *api.Container) bool { + if container.SecurityContext == nil { + return false + } + if container.SecurityContext.Privileged == nil { + return false + } + return *container.SecurityContext.Privileged +} + +// HasCapabilitiesRequest returns true if Adds or Drops are defined in the security context +// capabilities, taking into account nils +func HasCapabilitiesRequest(container *api.Container) bool { + if container.SecurityContext == nil { + return false + } + if container.SecurityContext.Capabilities == nil { + return false + } + return len(container.SecurityContext.Capabilities.Add) > 0 || len(container.SecurityContext.Capabilities.Drop) > 0 +} + +const expectedSELinuxFields = 4 + +// ParseSELinuxOptions parses a string containing a full SELinux context +// (user, role, type, and level) into an SELinuxOptions object. If the +// context is malformed, an error is returned. +func ParseSELinuxOptions(context string) (*api.SELinuxOptions, error) { + fields := strings.SplitN(context, ":", expectedSELinuxFields) + + if len(fields) != expectedSELinuxFields { + return nil, fmt.Errorf("expected %v fields in selinux; got %v (context: %v)", expectedSELinuxFields, len(fields), context) + } + + return &api.SELinuxOptions{ + User: fields[0], + Role: fields[1], + Type: fields[2], + Level: fields[3], + }, nil +} + +// HasNonRootUID returns true if the runAsUser is set and is greater than 0. +func HasRootUID(container *api.Container) bool { + if container.SecurityContext == nil { + return false + } + if container.SecurityContext.RunAsUser == nil { + return false + } + return *container.SecurityContext.RunAsUser == 0 +} + +// HasRunAsUser determines if the sc's runAsUser field is set. +func HasRunAsUser(container *api.Container) bool { + return container.SecurityContext != nil && container.SecurityContext.RunAsUser != nil +} + +// HasRootRunAsUser returns true if the run as user is set and it is set to 0. +func HasRootRunAsUser(container *api.Container) bool { + return HasRunAsUser(container) && HasRootUID(container) +} diff --git a/vendor/k8s.io/kubernetes/pkg/securitycontext/util_test.go b/vendor/k8s.io/kubernetes/pkg/securitycontext/util_test.go new file mode 100644 index 000000000..d2f1e48d0 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/securitycontext/util_test.go @@ -0,0 +1,206 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 securitycontext + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" +) + +func TestParseSELinuxOptions(t *testing.T) { + cases := []struct { + name string + input string + expected *api.SELinuxOptions + }{ + { + name: "simple", + input: "user_t:role_t:type_t:s0", + expected: &api.SELinuxOptions{ + User: "user_t", + Role: "role_t", + Type: "type_t", + Level: "s0", + }, + }, + { + name: "simple + categories", + input: "user_t:role_t:type_t:s0:c0", + expected: &api.SELinuxOptions{ + User: "user_t", + Role: "role_t", + Type: "type_t", + Level: "s0:c0", + }, + }, + { + name: "not enough fields", + input: "type_t:s0:c0", + }, + } + + for _, tc := range cases { + result, err := ParseSELinuxOptions(tc.input) + + if err != nil { + if tc.expected == nil { + continue + } else { + t.Errorf("%v: unexpected error: %v", tc.name, err) + } + } + + compareContexts(tc.name, tc.expected, result, t) + } +} + +func compareContexts(name string, ex, ac *api.SELinuxOptions, t *testing.T) { + if e, a := ex.User, ac.User; e != a { + t.Errorf("%v: expected user: %v, got: %v", name, e, a) + } + if e, a := ex.Role, ac.Role; e != a { + t.Errorf("%v: expected role: %v, got: %v", name, e, a) + } + if e, a := ex.Type, ac.Type; e != a { + t.Errorf("%v: expected type: %v, got: %v", name, e, a) + } + if e, a := ex.Level, ac.Level; e != a { + t.Errorf("%v: expected level: %v, got: %v", name, e, a) + } +} + +func TestHaRootUID(t *testing.T) { + var nonRoot int64 = 1 + var root int64 = 0 + + tests := map[string]struct { + container *api.Container + expect bool + }{ + "nil sc": { + container: &api.Container{SecurityContext: nil}, + }, + "nil runAsuser": { + container: &api.Container{ + SecurityContext: &api.SecurityContext{ + RunAsUser: nil, + }, + }, + }, + "runAsUser non-root": { + container: &api.Container{ + SecurityContext: &api.SecurityContext{ + RunAsUser: &nonRoot, + }, + }, + }, + "runAsUser root": { + container: &api.Container{ + SecurityContext: &api.SecurityContext{ + RunAsUser: &root, + }, + }, + expect: true, + }, + } + + for k, v := range tests { + actual := HasRootUID(v.container) + if actual != v.expect { + t.Errorf("%s failed, expected %t but received %t", k, v.expect, actual) + } + } +} + +func TestHasRunAsUser(t *testing.T) { + var runAsUser int64 = 0 + + tests := map[string]struct { + container *api.Container + expect bool + }{ + "nil sc": { + container: &api.Container{SecurityContext: nil}, + }, + "nil runAsUser": { + container: &api.Container{ + SecurityContext: &api.SecurityContext{ + RunAsUser: nil, + }, + }, + }, + "valid runAsUser": { + container: &api.Container{ + SecurityContext: &api.SecurityContext{ + RunAsUser: &runAsUser, + }, + }, + expect: true, + }, + } + + for k, v := range tests { + actual := HasRunAsUser(v.container) + if actual != v.expect { + t.Errorf("%s failed, expected %t but received %t", k, v.expect, actual) + } + } +} + +func TestHasRootRunAsUser(t *testing.T) { + var nonRoot int64 = 1 + var root int64 = 0 + + tests := map[string]struct { + container *api.Container + expect bool + }{ + "nil sc": { + container: &api.Container{SecurityContext: nil}, + }, + "nil runAsuser": { + container: &api.Container{ + SecurityContext: &api.SecurityContext{ + RunAsUser: nil, + }, + }, + }, + "runAsUser non-root": { + container: &api.Container{ + SecurityContext: &api.SecurityContext{ + RunAsUser: &nonRoot, + }, + }, + }, + "runAsUser root": { + container: &api.Container{ + SecurityContext: &api.SecurityContext{ + RunAsUser: &root, + }, + }, + expect: true, + }, + } + + for k, v := range tests { + actual := HasRootRunAsUser(v.container) + if actual != v.expect { + t.Errorf("%s failed, expected %t but received %t", k, v.expect, actual) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/serviceaccount/jwt.go b/vendor/k8s.io/kubernetes/pkg/serviceaccount/jwt.go new file mode 100644 index 000000000..d26349d86 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/serviceaccount/jwt.go @@ -0,0 +1,220 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 serviceaccount + +import ( + "bytes" + "crypto/rsa" + "errors" + "fmt" + "io/ioutil" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/auth/authenticator" + "k8s.io/kubernetes/pkg/auth/user" + + jwt "github.com/dgrijalva/jwt-go" + "github.com/golang/glog" +) + +const ( + Issuer = "kubernetes/serviceaccount" + + SubjectClaim = "sub" + IssuerClaim = "iss" + ServiceAccountNameClaim = "kubernetes.io/serviceaccount/service-account.name" + ServiceAccountUIDClaim = "kubernetes.io/serviceaccount/service-account.uid" + SecretNameClaim = "kubernetes.io/serviceaccount/secret.name" + NamespaceClaim = "kubernetes.io/serviceaccount/namespace" +) + +// ServiceAccountTokenGetter defines functions to retrieve a named service account and secret +type ServiceAccountTokenGetter interface { + GetServiceAccount(namespace, name string) (*api.ServiceAccount, error) + GetSecret(namespace, name string) (*api.Secret, error) +} + +type TokenGenerator interface { + // GenerateToken generates a token which will identify the given ServiceAccount. + // The returned token will be stored in the given (and yet-unpersisted) Secret. + GenerateToken(serviceAccount api.ServiceAccount, secret api.Secret) (string, error) +} + +// ReadPrivateKey is a helper function for reading an rsa.PrivateKey from a PEM-encoded file +func ReadPrivateKey(file string) (*rsa.PrivateKey, error) { + data, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + return jwt.ParseRSAPrivateKeyFromPEM(data) +} + +// ReadPublicKey is a helper function for reading an rsa.PublicKey from a PEM-encoded file +// Reads public keys from both public and private key files +func ReadPublicKey(file string) (*rsa.PublicKey, error) { + data, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + + if privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(data); err == nil { + return &privateKey.PublicKey, nil + } + + return jwt.ParseRSAPublicKeyFromPEM(data) +} + +// JWTTokenGenerator returns a TokenGenerator that generates signed JWT tokens, using the given privateKey. +// privateKey is a PEM-encoded byte array of a private RSA key. +// JWTTokenAuthenticator() +func JWTTokenGenerator(key *rsa.PrivateKey) TokenGenerator { + return &jwtTokenGenerator{key} +} + +type jwtTokenGenerator struct { + key *rsa.PrivateKey +} + +func (j *jwtTokenGenerator) GenerateToken(serviceAccount api.ServiceAccount, secret api.Secret) (string, error) { + token := jwt.New(jwt.SigningMethodRS256) + + // Identify the issuer + token.Claims[IssuerClaim] = Issuer + + // Username + token.Claims[SubjectClaim] = MakeUsername(serviceAccount.Namespace, serviceAccount.Name) + + // Persist enough structured info for the authenticator to be able to look up the service account and secret + token.Claims[NamespaceClaim] = serviceAccount.Namespace + token.Claims[ServiceAccountNameClaim] = serviceAccount.Name + token.Claims[ServiceAccountUIDClaim] = serviceAccount.UID + token.Claims[SecretNameClaim] = secret.Name + + // Sign and get the complete encoded token as a string + return token.SignedString(j.key) +} + +// JWTTokenAuthenticator authenticates tokens as JWT tokens produced by JWTTokenGenerator +// Token signatures are verified using each of the given public keys until one works (allowing key rotation) +// If lookup is true, the service account and secret referenced as claims inside the token are retrieved and verified with the provided ServiceAccountTokenGetter +func JWTTokenAuthenticator(keys []*rsa.PublicKey, lookup bool, getter ServiceAccountTokenGetter) authenticator.Token { + return &jwtTokenAuthenticator{keys, lookup, getter} +} + +type jwtTokenAuthenticator struct { + keys []*rsa.PublicKey + lookup bool + getter ServiceAccountTokenGetter +} + +func (j *jwtTokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, error) { + var validationError error + + for i, key := range j.keys { + // Attempt to verify with each key until we find one that works + parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return key, nil + }) + + if err != nil { + switch err := err.(type) { + case *jwt.ValidationError: + if (err.Errors & jwt.ValidationErrorMalformed) != 0 { + // Not a JWT, no point in continuing + return nil, false, nil + } + + if (err.Errors & jwt.ValidationErrorSignatureInvalid) != 0 { + // Signature error, perhaps one of the other keys will verify the signature + // If not, we want to return this error + glog.V(4).Infof("Signature error (key %d): %v", i, err) + validationError = err + continue + } + } + + // Other errors should just return as errors + return nil, false, err + } + + // If we get here, we have a token with a recognized signature + + // Make sure we issued the token + iss, _ := parsedToken.Claims[IssuerClaim].(string) + if iss != Issuer { + return nil, false, nil + } + + // Make sure the claims we need exist + sub, _ := parsedToken.Claims[SubjectClaim].(string) + if len(sub) == 0 { + return nil, false, errors.New("sub claim is missing") + } + namespace, _ := parsedToken.Claims[NamespaceClaim].(string) + if len(namespace) == 0 { + return nil, false, errors.New("namespace claim is missing") + } + secretName, _ := parsedToken.Claims[SecretNameClaim].(string) + if len(namespace) == 0 { + return nil, false, errors.New("secretName claim is missing") + } + serviceAccountName, _ := parsedToken.Claims[ServiceAccountNameClaim].(string) + if len(serviceAccountName) == 0 { + return nil, false, errors.New("serviceAccountName claim is missing") + } + serviceAccountUID, _ := parsedToken.Claims[ServiceAccountUIDClaim].(string) + if len(serviceAccountUID) == 0 { + return nil, false, errors.New("serviceAccountUID claim is missing") + } + + subjectNamespace, subjectName, err := SplitUsername(sub) + if err != nil || subjectNamespace != namespace || subjectName != serviceAccountName { + return nil, false, errors.New("sub claim is invalid") + } + + if j.lookup { + // Make sure token hasn't been invalidated by deletion of the secret + secret, err := j.getter.GetSecret(namespace, secretName) + if err != nil { + glog.V(4).Infof("Could not retrieve token %s/%s for service account %s/%s: %v", namespace, secretName, namespace, serviceAccountName, err) + return nil, false, errors.New("Token has been invalidated") + } + if bytes.Compare(secret.Data[api.ServiceAccountTokenKey], []byte(token)) != 0 { + glog.V(4).Infof("Token contents no longer matches %s/%s for service account %s/%s", namespace, secretName, namespace, serviceAccountName) + return nil, false, errors.New("Token does not match server's copy") + } + + // Make sure service account still exists (name and UID) + serviceAccount, err := j.getter.GetServiceAccount(namespace, serviceAccountName) + if err != nil { + glog.V(4).Infof("Could not retrieve service account %s/%s: %v", namespace, serviceAccountName, err) + return nil, false, err + } + if string(serviceAccount.UID) != serviceAccountUID { + glog.V(4).Infof("Service account UID no longer matches %s/%s: %q != %q", namespace, serviceAccountName, string(serviceAccount.UID), serviceAccountUID) + return nil, false, fmt.Errorf("ServiceAccount UID (%s) does not match claim (%s)", serviceAccount.UID, serviceAccountUID) + } + } + + return UserInfo(namespace, serviceAccountName, serviceAccountUID), true, nil + } + + return nil, false, validationError +} diff --git a/vendor/k8s.io/kubernetes/pkg/serviceaccount/jwt_test.go b/vendor/k8s.io/kubernetes/pkg/serviceaccount/jwt_test.go new file mode 100644 index 000000000..20a201f26 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/serviceaccount/jwt_test.go @@ -0,0 +1,275 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 serviceaccount_test + +import ( + "crypto/rsa" + "io/ioutil" + "os" + "reflect" + "testing" + + "github.com/dgrijalva/jwt-go" + + "k8s.io/kubernetes/pkg/api" + clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount" + "k8s.io/kubernetes/pkg/serviceaccount" +) + +const otherPublicKey = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArXz0QkIG1B5Bj2/W69GH +rsm5e+RC3kE+VTgocge0atqlLBek35tRqLgUi3AcIrBZ/0YctMSWDVcRt5fkhWwe +Lqjj6qvAyNyOkrkBi1NFDpJBjYJtuKHgRhNxXbOzTSNpdSKXTfOkzqv56MwHOP25 +yP/NNAODUtr92D5ySI5QX8RbXW+uDn+ixul286PBW/BCrE4tuS88dA0tYJPf8LCu +sqQOwlXYH/rNUg4Pyl9xxhR5DIJR0OzNNfChjw60zieRIt2LfM83fXhwk8IxRGkc +gPZm7ZsipmfbZK2Tkhnpsa4QxDg7zHJPMsB5kxRXW0cQipXcC3baDyN9KBApNXa0 +PwIDAQAB +-----END PUBLIC KEY-----` + +const publicKey = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA249XwEo9k4tM8fMxV7zx +OhcrP+WvXn917koM5Qr2ZXs4vo26e4ytdlrV0bQ9SlcLpQVSYjIxNfhTZdDt+ecI +zshKuv1gKIxbbLQMOuK1eA/4HALyEkFgmS/tleLJrhc65tKPMGD+pKQ/xhmzRuCG +51RoiMgbQxaCyYxGfNLpLAZK9L0Tctv9a0mJmGIYnIOQM4kC1A1I1n3EsXMWmeJU +j7OTh/AjjCnMnkgvKT2tpKxYQ59PgDgU8Ssc7RDSmSkLxnrv+OrN80j6xrw0OjEi +B4Ycr0PqfzZcvy8efTtFQ/Jnc4Bp1zUtFXt7+QeevePtQ2EcyELXE0i63T1CujRM +WwIDAQAB +-----END PUBLIC KEY----- +` + +const privateKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA249XwEo9k4tM8fMxV7zxOhcrP+WvXn917koM5Qr2ZXs4vo26 +e4ytdlrV0bQ9SlcLpQVSYjIxNfhTZdDt+ecIzshKuv1gKIxbbLQMOuK1eA/4HALy +EkFgmS/tleLJrhc65tKPMGD+pKQ/xhmzRuCG51RoiMgbQxaCyYxGfNLpLAZK9L0T +ctv9a0mJmGIYnIOQM4kC1A1I1n3EsXMWmeJUj7OTh/AjjCnMnkgvKT2tpKxYQ59P +gDgU8Ssc7RDSmSkLxnrv+OrN80j6xrw0OjEiB4Ycr0PqfzZcvy8efTtFQ/Jnc4Bp +1zUtFXt7+QeevePtQ2EcyELXE0i63T1CujRMWwIDAQABAoIBAHJx8GqyCBDNbqk7 +e7/hI9iE1S10Wwol5GH2RWxqX28cYMKq+8aE2LI1vPiXO89xOgelk4DN6urX6xjK +ZBF8RRIMQy/e/O2F4+3wl+Nl4vOXV1u6iVXMsD6JRg137mqJf1Fr9elg1bsaRofL +Q7CxPoB8dhS+Qb+hj0DhlqhgA9zG345CQCAds0ZYAZe8fP7bkwrLqZpMn7Dz9WVm +++YgYYKjuE95kPuup/LtWfA9rJyE/Fws8/jGvRSpVn1XglMLSMKhLd27sE8ZUSV0 +2KUzbfRGE0+AnRULRrjpYaPu0XQ2JjdNvtkjBnv27RB89W9Gklxq821eH1Y8got8 +FZodjxECgYEA93pz7AQZ2xDs67d1XLCzpX84GxKzttirmyj3OIlxgzVHjEMsvw8v +sjFiBU5xEEQDosrBdSknnlJqyiq1YwWG/WDckr13d8G2RQWoySN7JVmTQfXcLoTu +YGRiiTuoEi3ab3ZqrgGrFgX7T/cHuasbYvzCvhM2b4VIR3aSxU2DTUMCgYEA4x7J +T/ErP6GkU5nKstu/mIXwNzayEO1BJvPYsy7i7EsxTm3xe/b8/6cYOz5fvJLGH5mT +Q8YvuLqBcMwZardrYcwokD55UvNLOyfADDFZ6l3WntIqbA640Ok2g1X4U8J09xIq +ZLIWK1yWbbvi4QCeN5hvWq47e8sIj5QHjIIjRwkCgYEAyNqjltxFN9zmzPDa2d24 +EAvOt3pYTYBQ1t9KtqImdL0bUqV6fZ6PsWoPCgt+DBuHb+prVPGP7Bkr/uTmznU/ ++AlTO+12NsYLbr2HHagkXE31DEXE7CSLa8RNjN/UKtz4Ohq7vnowJvG35FCz/mb3 +FUHbtHTXa2+bGBUOTf/5Hw0CgYBxw0r9EwUhw1qnUYJ5op7OzFAtp+T7m4ul8kCa +SCL8TxGsgl+SQ34opE775dtYfoBk9a0RJqVit3D8yg71KFjOTNAIqHJm/Vyyjc+h +i9rJDSXiuczsAVfLtPVMRfS0J9QkqeG4PIfkQmVLI/CZ2ZBmsqEcX+eFs4ZfPLun +Qsxe2QKBgGuPilIbLeIBDIaPiUI0FwU8v2j8CEQBYvoQn34c95hVQsig/o5z7zlo +UsO0wlTngXKlWdOcCs1kqEhTLrstf48djDxAYAxkw40nzeJOt7q52ib/fvf4/UBy +X024wzbiw1q07jFCyfQmODzURAx1VNT7QVUMdz/N8vy47/H40AZJ +-----END RSA PRIVATE KEY----- +` + +func getPrivateKey(data string) *rsa.PrivateKey { + key, _ := jwt.ParseRSAPrivateKeyFromPEM([]byte(data)) + return key +} + +func getPublicKey(data string) *rsa.PublicKey { + key, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(data)) + return key +} + +func TestReadPrivateKey(t *testing.T) { + f, err := ioutil.TempFile("", "") + if err != nil { + t.Fatalf("error creating tmpfile: %v", err) + } + defer os.Remove(f.Name()) + + if err := ioutil.WriteFile(f.Name(), []byte(privateKey), os.FileMode(0600)); err != nil { + t.Fatalf("error creating tmpfile: %v", err) + } + + if _, err := serviceaccount.ReadPrivateKey(f.Name()); err != nil { + t.Fatalf("error reading key: %v", err) + } +} + +func TestReadPublicKey(t *testing.T) { + f, err := ioutil.TempFile("", "") + if err != nil { + t.Fatalf("error creating tmpfile: %v", err) + } + defer os.Remove(f.Name()) + + if err := ioutil.WriteFile(f.Name(), []byte(publicKey), os.FileMode(0600)); err != nil { + t.Fatalf("error creating tmpfile: %v", err) + } + + if _, err := serviceaccount.ReadPublicKey(f.Name()); err != nil { + t.Fatalf("error reading key: %v", err) + } +} + +func TestTokenGenerateAndValidate(t *testing.T) { + expectedUserName := "system:serviceaccount:test:my-service-account" + expectedUserUID := "12345" + + // Related API objects + serviceAccount := &api.ServiceAccount{ + ObjectMeta: api.ObjectMeta{ + Name: "my-service-account", + UID: "12345", + Namespace: "test", + }, + } + secret := &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: "my-secret", + Namespace: "test", + }, + } + + // Generate the token + generator := serviceaccount.JWTTokenGenerator(getPrivateKey(privateKey)) + token, err := generator.GenerateToken(*serviceAccount, *secret) + if err != nil { + t.Fatalf("error generating token: %v", err) + } + if len(token) == 0 { + t.Fatalf("no token generated") + } + + // "Save" the token + secret.Data = map[string][]byte{ + "token": []byte(token), + } + + testCases := map[string]struct { + Client clientset.Interface + Keys []*rsa.PublicKey + + ExpectedErr bool + ExpectedOK bool + ExpectedUserName string + ExpectedUserUID string + ExpectedGroups []string + }{ + "no keys": { + Client: nil, + Keys: []*rsa.PublicKey{}, + ExpectedErr: false, + ExpectedOK: false, + }, + "invalid keys": { + Client: nil, + Keys: []*rsa.PublicKey{getPublicKey(otherPublicKey)}, + ExpectedErr: true, + ExpectedOK: false, + }, + "valid key": { + Client: nil, + Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, + ExpectedErr: false, + ExpectedOK: true, + ExpectedUserName: expectedUserName, + ExpectedUserUID: expectedUserUID, + ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, + }, + "rotated keys": { + Client: nil, + Keys: []*rsa.PublicKey{getPublicKey(otherPublicKey), getPublicKey(publicKey)}, + ExpectedErr: false, + ExpectedOK: true, + ExpectedUserName: expectedUserName, + ExpectedUserUID: expectedUserUID, + ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, + }, + "valid lookup": { + Client: fake.NewSimpleClientset(serviceAccount, secret), + Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, + ExpectedErr: false, + ExpectedOK: true, + ExpectedUserName: expectedUserName, + ExpectedUserUID: expectedUserUID, + ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, + }, + "invalid secret lookup": { + Client: fake.NewSimpleClientset(serviceAccount), + Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, + ExpectedErr: true, + ExpectedOK: false, + }, + "invalid serviceaccount lookup": { + Client: fake.NewSimpleClientset(secret), + Keys: []*rsa.PublicKey{getPublicKey(publicKey)}, + ExpectedErr: true, + ExpectedOK: false, + }, + } + + for k, tc := range testCases { + getter := serviceaccountcontroller.NewGetterFromClient(tc.Client) + authenticator := serviceaccount.JWTTokenAuthenticator(tc.Keys, tc.Client != nil, getter) + + user, ok, err := authenticator.AuthenticateToken(token) + if (err != nil) != tc.ExpectedErr { + t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedErr, err) + continue + } + + if ok != tc.ExpectedOK { + t.Errorf("%s: Expected ok=%v, got %v", k, tc.ExpectedOK, ok) + continue + } + + if err != nil || !ok { + continue + } + + if user.GetName() != tc.ExpectedUserName { + t.Errorf("%s: Expected username=%v, got %v", k, tc.ExpectedUserName, user.GetName()) + continue + } + if user.GetUID() != tc.ExpectedUserUID { + t.Errorf("%s: Expected userUID=%v, got %v", k, tc.ExpectedUserUID, user.GetUID()) + continue + } + if !reflect.DeepEqual(user.GetGroups(), tc.ExpectedGroups) { + t.Errorf("%s: Expected groups=%v, got %v", k, tc.ExpectedGroups, user.GetGroups()) + continue + } + } +} + +func TestMakeSplitUsername(t *testing.T) { + username := serviceaccount.MakeUsername("ns", "name") + ns, name, err := serviceaccount.SplitUsername(username) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + if ns != "ns" || name != "name" { + t.Errorf("Expected ns/name, got %s/%s", ns, name) + } + + invalid := []string{"test", "system:serviceaccount", "system:serviceaccount:", "system:serviceaccount:ns", "system:serviceaccount:ns:name:extra"} + for _, n := range invalid { + _, _, err := serviceaccount.SplitUsername("test") + if err == nil { + t.Errorf("Expected error for %s", n) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/serviceaccount/util.go b/vendor/k8s.io/kubernetes/pkg/serviceaccount/util.go new file mode 100644 index 000000000..607912364 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/serviceaccount/util.go @@ -0,0 +1,104 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 serviceaccount + +import ( + "fmt" + "strings" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/auth/user" +) + +const ( + ServiceAccountUsernamePrefix = "system:serviceaccount:" + ServiceAccountUsernameSeparator = ":" + ServiceAccountGroupPrefix = "system:serviceaccounts:" + AllServiceAccountsGroup = "system:serviceaccounts" +) + +// MakeUsername generates a username from the given namespace and ServiceAccount name. +// The resulting username can be passed to SplitUsername to extract the original namespace and ServiceAccount name. +func MakeUsername(namespace, name string) string { + return ServiceAccountUsernamePrefix + namespace + ServiceAccountUsernameSeparator + name +} + +var invalidUsernameErr = fmt.Errorf("Username must be in the form %s", MakeUsername("namespace", "name")) + +// SplitUsername returns the namespace and ServiceAccount name embedded in the given username, +// or an error if the username is not a valid name produced by MakeUsername +func SplitUsername(username string) (string, string, error) { + if !strings.HasPrefix(username, ServiceAccountUsernamePrefix) { + return "", "", invalidUsernameErr + } + trimmed := strings.TrimPrefix(username, ServiceAccountUsernamePrefix) + parts := strings.Split(trimmed, ServiceAccountUsernameSeparator) + if len(parts) != 2 { + return "", "", invalidUsernameErr + } + namespace, name := parts[0], parts[1] + if ok, _ := validation.ValidateNamespaceName(namespace, false); !ok { + return "", "", invalidUsernameErr + } + if ok, _ := validation.ValidateServiceAccountName(name, false); !ok { + return "", "", invalidUsernameErr + } + return namespace, name, nil +} + +// MakeGroupNames generates service account group names for the given namespace and ServiceAccount name +func MakeGroupNames(namespace, name string) []string { + return []string{ + AllServiceAccountsGroup, + MakeNamespaceGroupName(namespace), + } +} + +// MakeNamespaceGroupName returns the name of the group all service accounts in the namespace are included in +func MakeNamespaceGroupName(namespace string) string { + return ServiceAccountGroupPrefix + namespace +} + +// UserInfo returns a user.Info interface for the given namespace, service account name and UID +func UserInfo(namespace, name, uid string) user.Info { + return &user.DefaultInfo{ + Name: MakeUsername(namespace, name), + UID: uid, + Groups: MakeGroupNames(namespace, name), + } +} + +// IsServiceAccountToken returns true if the secret is a valid api token for the service account +func IsServiceAccountToken(secret *api.Secret, sa *api.ServiceAccount) bool { + if secret.Type != api.SecretTypeServiceAccountToken { + return false + } + + name := secret.Annotations[api.ServiceAccountNameKey] + uid := secret.Annotations[api.ServiceAccountUIDKey] + if name != sa.Name { + // Name must match + return false + } + if len(uid) > 0 && uid != string(sa.UID) { + // If UID is specified, it must match + return false + } + + return true +} diff --git a/vendor/k8s.io/kubernetes/pkg/serviceaccount/util_test.go b/vendor/k8s.io/kubernetes/pkg/serviceaccount/util_test.go new file mode 100644 index 000000000..3a458d33f --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/serviceaccount/util_test.go @@ -0,0 +1,82 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 serviceaccount + +import "testing" + +func TestMakeUsername(t *testing.T) { + + testCases := map[string]struct { + Namespace string + Name string + ExpectedErr bool + }{ + "valid": { + Namespace: "foo", + Name: "bar", + ExpectedErr: false, + }, + "empty": { + ExpectedErr: true, + }, + "empty namespace": { + Namespace: "", + Name: "foo", + ExpectedErr: true, + }, + "empty name": { + Namespace: "foo", + Name: "", + ExpectedErr: true, + }, + "extra segments": { + Namespace: "foo", + Name: "bar:baz", + ExpectedErr: true, + }, + "invalid chars in namespace": { + Namespace: "foo ", + Name: "bar", + ExpectedErr: true, + }, + "invalid chars in name": { + Namespace: "foo", + Name: "bar ", + ExpectedErr: true, + }, + } + + for k, tc := range testCases { + username := MakeUsername(tc.Namespace, tc.Name) + + namespace, name, err := SplitUsername(username) + if (err != nil) != tc.ExpectedErr { + t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedErr, err) + continue + } + if err != nil { + continue + } + + if namespace != tc.Namespace { + t.Errorf("%s: Expected namespace %q, got %q", k, tc.Namespace, namespace) + } + if name != tc.Name { + t.Errorf("%s: Expected name %q, got %q", k, tc.Name, name) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/ssh/ssh.go b/vendor/k8s.io/kubernetes/pkg/ssh/ssh.go new file mode 100644 index 000000000..445415747 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/ssh/ssh.go @@ -0,0 +1,511 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 ssh + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io" + "io/ioutil" + mathrand "math/rand" + "net" + "net/http" + "net/url" + "os" + "sync" + "time" + + "github.com/golang/glog" + "github.com/prometheus/client_golang/prometheus" + "golang.org/x/crypto/ssh" + + utilnet "k8s.io/kubernetes/pkg/util/net" + "k8s.io/kubernetes/pkg/util/runtime" + "k8s.io/kubernetes/pkg/util/wait" +) + +var ( + tunnelOpenCounter = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "ssh_tunnel_open_count", + Help: "Counter of ssh tunnel total open attempts", + }, + ) + tunnelOpenFailCounter = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "ssh_tunnel_open_fail_count", + Help: "Counter of ssh tunnel failed open attempts", + }, + ) +) + +func init() { + prometheus.MustRegister(tunnelOpenCounter) + prometheus.MustRegister(tunnelOpenFailCounter) +} + +// TODO: Unit tests for this code, we can spin up a test SSH server with instructions here: +// https://godoc.org/golang.org/x/crypto/ssh#ServerConn +type SSHTunnel struct { + Config *ssh.ClientConfig + Host string + SSHPort string + running bool + sock net.Listener + client *ssh.Client +} + +func (s *SSHTunnel) copyBytes(out io.Writer, in io.Reader) { + if _, err := io.Copy(out, in); err != nil { + glog.Errorf("Error in SSH tunnel: %v", err) + } +} + +func NewSSHTunnel(user, keyfile, host string) (*SSHTunnel, error) { + signer, err := MakePrivateKeySignerFromFile(keyfile) + if err != nil { + return nil, err + } + return makeSSHTunnel(user, signer, host) +} + +func NewSSHTunnelFromBytes(user string, privateKey []byte, host string) (*SSHTunnel, error) { + signer, err := MakePrivateKeySignerFromBytes(privateKey) + if err != nil { + return nil, err + } + return makeSSHTunnel(user, signer, host) +} + +func makeSSHTunnel(user string, signer ssh.Signer, host string) (*SSHTunnel, error) { + config := ssh.ClientConfig{ + User: user, + Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, + } + return &SSHTunnel{ + Config: &config, + Host: host, + SSHPort: "22", + }, nil +} + +func (s *SSHTunnel) Open() error { + var err error + s.client, err = realTimeoutDialer.Dial("tcp", net.JoinHostPort(s.Host, s.SSHPort), s.Config) + tunnelOpenCounter.Inc() + if err != nil { + tunnelOpenFailCounter.Inc() + } + return err +} + +func (s *SSHTunnel) Dial(network, address string) (net.Conn, error) { + if s.client == nil { + return nil, errors.New("tunnel is not opened.") + } + return s.client.Dial(network, address) +} + +func (s *SSHTunnel) tunnel(conn net.Conn, remoteHost, remotePort string) error { + if s.client == nil { + return errors.New("tunnel is not opened.") + } + tunnel, err := s.client.Dial("tcp", net.JoinHostPort(remoteHost, remotePort)) + if err != nil { + return err + } + go s.copyBytes(tunnel, conn) + go s.copyBytes(conn, tunnel) + return nil +} + +func (s *SSHTunnel) Close() error { + if s.client == nil { + return errors.New("Cannot close tunnel. Tunnel was not opened.") + } + if err := s.client.Close(); err != nil { + return err + } + return nil +} + +// Interface to allow mocking of ssh.Dial, for testing SSH +type sshDialer interface { + Dial(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) +} + +// Real implementation of sshDialer +type realSSHDialer struct{} + +var _ sshDialer = &realSSHDialer{} + +func (d *realSSHDialer) Dial(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + return ssh.Dial(network, addr, config) +} + +// timeoutDialer wraps an sshDialer with a timeout around Dial(). The golang +// ssh library can hang indefinitely inside the Dial() call (see issue #23835). +// Wrapping all Dial() calls with a conservative timeout provides safety against +// getting stuck on that. +type timeoutDialer struct { + dialer sshDialer + timeout time.Duration +} + +// 150 seconds is longer than the underlying default TCP backoff delay (127 +// seconds). This timeout is only intended to catch otherwise uncaught hangs. +const sshDialTimeout = 150 * time.Second + +var realTimeoutDialer sshDialer = &timeoutDialer{&realSSHDialer{}, sshDialTimeout} + +func (d *timeoutDialer) Dial(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + var client *ssh.Client + errCh := make(chan error, 1) + go func() { + defer runtime.HandleCrash() + var err error + client, err = d.dialer.Dial(network, addr, config) + errCh <- err + }() + select { + case err := <-errCh: + return client, err + case <-time.After(d.timeout): + return nil, fmt.Errorf("timed out dialing %s:%s", network, addr) + } +} + +// RunSSHCommand returns the stdout, stderr, and exit code from running cmd on +// host as specific user, along with any SSH-level error. +// If user=="", it will default (like SSH) to os.Getenv("USER") +func RunSSHCommand(cmd, user, host string, signer ssh.Signer) (string, string, int, error) { + return runSSHCommand(realTimeoutDialer, cmd, user, host, signer, true) +} + +// Internal implementation of runSSHCommand, for testing +func runSSHCommand(dialer sshDialer, cmd, user, host string, signer ssh.Signer, retry bool) (string, string, int, error) { + if user == "" { + user = os.Getenv("USER") + } + // Setup the config, dial the server, and open a session. + config := &ssh.ClientConfig{ + User: user, + Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, + } + client, err := dialer.Dial("tcp", host, config) + if err != nil && retry { + err = wait.Poll(5*time.Second, 20*time.Second, func() (bool, error) { + fmt.Printf("error dialing %s@%s: '%v', retrying\n", user, host, err) + if client, err = dialer.Dial("tcp", host, config); err != nil { + return false, nil + } + return true, nil + }) + } + if err != nil { + return "", "", 0, fmt.Errorf("error getting SSH client to %s@%s: '%v'", user, host, err) + } + session, err := client.NewSession() + if err != nil { + return "", "", 0, fmt.Errorf("error creating session to %s@%s: '%v'", user, host, err) + } + defer session.Close() + + // Run the command. + code := 0 + var bout, berr bytes.Buffer + session.Stdout, session.Stderr = &bout, &berr + if err = session.Run(cmd); err != nil { + // Check whether the command failed to run or didn't complete. + if exiterr, ok := err.(*ssh.ExitError); ok { + // If we got an ExitError and the exit code is nonzero, we'll + // consider the SSH itself successful (just that the command run + // errored on the host). + if code = exiterr.ExitStatus(); code != 0 { + err = nil + } + } else { + // Some other kind of error happened (e.g. an IOError); consider the + // SSH unsuccessful. + err = fmt.Errorf("failed running `%s` on %s@%s: '%v'", cmd, user, host, err) + } + } + return bout.String(), berr.String(), code, err +} + +func MakePrivateKeySignerFromFile(key string) (ssh.Signer, error) { + // Create an actual signer. + buffer, err := ioutil.ReadFile(key) + if err != nil { + return nil, fmt.Errorf("error reading SSH key %s: '%v'", key, err) + } + return MakePrivateKeySignerFromBytes(buffer) +} + +func MakePrivateKeySignerFromBytes(buffer []byte) (ssh.Signer, error) { + signer, err := ssh.ParsePrivateKey(buffer) + if err != nil { + return nil, fmt.Errorf("error parsing SSH key %s: '%v'", buffer, err) + } + return signer, nil +} + +func ParsePublicKeyFromFile(keyFile string) (*rsa.PublicKey, error) { + buffer, err := ioutil.ReadFile(keyFile) + if err != nil { + return nil, fmt.Errorf("error reading SSH key %s: '%v'", keyFile, err) + } + keyBlock, _ := pem.Decode(buffer) + key, err := x509.ParsePKIXPublicKey(keyBlock.Bytes) + if err != nil { + return nil, fmt.Errorf("error parsing SSH key %s: '%v'", keyFile, err) + } + rsaKey, ok := key.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("SSH key could not be parsed as rsa public key") + } + return rsaKey, nil +} + +type tunnel interface { + Open() error + Close() error + Dial(network, address string) (net.Conn, error) +} + +type sshTunnelEntry struct { + Address string + Tunnel tunnel +} + +type sshTunnelCreator interface { + NewSSHTunnel(user, keyFile, healthCheckURL string) (tunnel, error) +} + +type realTunnelCreator struct{} + +func (*realTunnelCreator) NewSSHTunnel(user, keyFile, healthCheckURL string) (tunnel, error) { + return NewSSHTunnel(user, keyFile, healthCheckURL) +} + +type SSHTunnelList struct { + entries []sshTunnelEntry + adding map[string]bool + tunnelCreator sshTunnelCreator + tunnelsLock sync.Mutex + + user string + keyfile string + healthCheckURL *url.URL +} + +func NewSSHTunnelList(user, keyfile string, healthCheckURL *url.URL, stopChan chan struct{}) *SSHTunnelList { + l := &SSHTunnelList{ + adding: make(map[string]bool), + tunnelCreator: &realTunnelCreator{}, + user: user, + keyfile: keyfile, + healthCheckURL: healthCheckURL, + } + healthCheckPoll := 1 * time.Minute + go wait.Until(func() { + l.tunnelsLock.Lock() + defer l.tunnelsLock.Unlock() + // Healthcheck each tunnel every minute + numTunnels := len(l.entries) + for i, entry := range l.entries { + // Stagger healthchecks evenly across duration of healthCheckPoll. + delay := healthCheckPoll * time.Duration(i) / time.Duration(numTunnels) + l.delayedHealthCheck(entry, delay) + } + }, healthCheckPoll, stopChan) + return l +} + +func (l *SSHTunnelList) delayedHealthCheck(e sshTunnelEntry, delay time.Duration) { + go func() { + defer runtime.HandleCrash() + time.Sleep(delay) + if err := l.healthCheck(e); err != nil { + glog.Errorf("Healthcheck failed for tunnel to %q: %v", e.Address, err) + glog.Infof("Attempting once to re-establish tunnel to %q", e.Address) + l.removeAndReAdd(e) + } + }() +} + +func (l *SSHTunnelList) healthCheck(e sshTunnelEntry) error { + // GET the healthcheck path using the provided tunnel's dial function. + transport := utilnet.SetTransportDefaults(&http.Transport{ + Dial: e.Tunnel.Dial, + // TODO(cjcullen): Plumb real TLS options through. + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }) + client := &http.Client{Transport: transport} + _, err := client.Get(l.healthCheckURL.String()) + return err +} + +func (l *SSHTunnelList) removeAndReAdd(e sshTunnelEntry) { + // Find the entry to replace. + l.tunnelsLock.Lock() + defer l.tunnelsLock.Unlock() + for i, entry := range l.entries { + if entry.Tunnel == e.Tunnel { + l.entries = append(l.entries[:i], l.entries[i+1:]...) + l.adding[e.Address] = true + go l.createAndAddTunnel(e.Address) + return + } + } +} + +func (l *SSHTunnelList) Dial(net, addr string) (net.Conn, error) { + start := time.Now() + id := mathrand.Int63() // So you can match begins/ends in the log. + glog.Infof("[%x: %v] Dialing...", id, addr) + defer func() { + glog.Infof("[%x: %v] Dialed in %v.", id, addr, time.Now().Sub(start)) + }() + tunnel, err := l.pickRandomTunnel() + if err != nil { + return nil, err + } + return tunnel.Dial(net, addr) +} + +func (l *SSHTunnelList) pickRandomTunnel() (tunnel, error) { + l.tunnelsLock.Lock() + defer l.tunnelsLock.Unlock() + if len(l.entries) == 0 { + return nil, fmt.Errorf("No SSH tunnels currently open. Were the targets able to accept an ssh-key for user %q?", l.user) + } + n := mathrand.Intn(len(l.entries)) + return l.entries[n].Tunnel, nil +} + +// Update reconciles the list's entries with the specified addresses. Existing +// tunnels that are not in addresses are removed from entries and closed in a +// background goroutine. New tunnels specified in addresses are opened in a +// background goroutine and then added to entries. +func (l *SSHTunnelList) Update(addrs []string) { + haveAddrsMap := make(map[string]bool) + wantAddrsMap := make(map[string]bool) + func() { + l.tunnelsLock.Lock() + defer l.tunnelsLock.Unlock() + // Build a map of what we currently have. + for i := range l.entries { + haveAddrsMap[l.entries[i].Address] = true + } + // Determine any necessary additions. + for i := range addrs { + // Add tunnel if it is not in l.entries or l.adding + if _, ok := haveAddrsMap[addrs[i]]; !ok { + if _, ok := l.adding[addrs[i]]; !ok { + l.adding[addrs[i]] = true + addr := addrs[i] + go func() { + defer runtime.HandleCrash() + // Actually adding tunnel to list will block until lock + // is released after deletions. + l.createAndAddTunnel(addr) + }() + } + } + wantAddrsMap[addrs[i]] = true + } + // Determine any necessary deletions. + var newEntries []sshTunnelEntry + for i := range l.entries { + if _, ok := wantAddrsMap[l.entries[i].Address]; !ok { + tunnelEntry := l.entries[i] + glog.Infof("Removing tunnel to deleted node at %q", tunnelEntry.Address) + go func() { + defer runtime.HandleCrash() + if err := tunnelEntry.Tunnel.Close(); err != nil { + glog.Errorf("Failed to close tunnel to %q: %v", tunnelEntry.Address, err) + } + }() + } else { + newEntries = append(newEntries, l.entries[i]) + } + } + l.entries = newEntries + }() +} + +func (l *SSHTunnelList) createAndAddTunnel(addr string) { + glog.Infof("Trying to add tunnel to %q", addr) + tunnel, err := l.tunnelCreator.NewSSHTunnel(l.user, l.keyfile, addr) + if err != nil { + glog.Errorf("Failed to create tunnel for %q: %v", addr, err) + return + } + if err := tunnel.Open(); err != nil { + glog.Errorf("Failed to open tunnel to %q: %v", addr, err) + l.tunnelsLock.Lock() + delete(l.adding, addr) + l.tunnelsLock.Unlock() + return + } + l.tunnelsLock.Lock() + l.entries = append(l.entries, sshTunnelEntry{addr, tunnel}) + delete(l.adding, addr) + l.tunnelsLock.Unlock() + glog.Infof("Successfully added tunnel for %q", addr) +} + +func EncodePrivateKey(private *rsa.PrivateKey) []byte { + return pem.EncodeToMemory(&pem.Block{ + Bytes: x509.MarshalPKCS1PrivateKey(private), + Type: "RSA PRIVATE KEY", + }) +} + +func EncodePublicKey(public *rsa.PublicKey) ([]byte, error) { + publicBytes, err := x509.MarshalPKIXPublicKey(public) + if err != nil { + return nil, err + } + return pem.EncodeToMemory(&pem.Block{ + Bytes: publicBytes, + Type: "PUBLIC KEY", + }), nil +} + +func EncodeSSHKey(public *rsa.PublicKey) ([]byte, error) { + publicKey, err := ssh.NewPublicKey(public) + if err != nil { + return nil, err + } + return ssh.MarshalAuthorizedKey(publicKey), nil +} + +func GenerateKey(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error) { + private, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return nil, nil, err + } + return private, &private.PublicKey, nil +} diff --git a/vendor/k8s.io/kubernetes/pkg/ssh/ssh_test.go b/vendor/k8s.io/kubernetes/pkg/ssh/ssh_test.go new file mode 100644 index 000000000..d8ce2d9a6 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/ssh/ssh_test.go @@ -0,0 +1,366 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 ssh + +import ( + "fmt" + "io" + "net" + "os" + "reflect" + "strings" + "testing" + "time" + + "k8s.io/kubernetes/pkg/util/wait" + + "github.com/golang/glog" + "golang.org/x/crypto/ssh" +) + +type testSSHServer struct { + Host string + Port string + Type string + Data []byte + PrivateKey []byte + PublicKey []byte +} + +func runTestSSHServer(user, password string) (*testSSHServer, error) { + result := &testSSHServer{} + // Largely derived from https://godoc.org/golang.org/x/crypto/ssh#example-NewServerConn + config := &ssh.ServerConfig{ + PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { + if c.User() == user && string(pass) == password { + return nil, nil + } + return nil, fmt.Errorf("password rejected for %s", c.User()) + }, + PublicKeyCallback: func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { + result.Type = key.Type() + result.Data = ssh.MarshalAuthorizedKey(key) + return nil, nil + }, + } + + privateKey, publicKey, err := GenerateKey(2048) + if err != nil { + return nil, err + } + privateBytes := EncodePrivateKey(privateKey) + signer, err := ssh.ParsePrivateKey(privateBytes) + if err != nil { + return nil, err + } + config.AddHostKey(signer) + result.PrivateKey = privateBytes + + publicBytes, err := EncodePublicKey(publicKey) + if err != nil { + return nil, err + } + result.PublicKey = publicBytes + + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, err + } + + host, port, err := net.SplitHostPort(listener.Addr().String()) + if err != nil { + return nil, err + } + result.Host = host + result.Port = port + go func() { + // TODO: return this port. + defer listener.Close() + + conn, err := listener.Accept() + if err != nil { + glog.Errorf("Failed to accept: %v", err) + } + _, chans, reqs, err := ssh.NewServerConn(conn, config) + if err != nil { + glog.Errorf("Failed handshake: %v", err) + } + go ssh.DiscardRequests(reqs) + for newChannel := range chans { + if newChannel.ChannelType() != "direct-tcpip" { + newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", newChannel.ChannelType())) + continue + } + channel, requests, err := newChannel.Accept() + if err != nil { + glog.Errorf("Failed to accept channel: %v", err) + } + + for req := range requests { + glog.Infof("Got request: %v", req) + } + + channel.Close() + } + }() + return result, nil +} + +func TestSSHTunnel(t *testing.T) { + private, public, err := GenerateKey(2048) + if err != nil { + t.Errorf("unexpected error: %v", err) + t.FailNow() + } + server, err := runTestSSHServer("foo", "bar") + if err != nil { + t.Errorf("unexpected error: %v", err) + t.FailNow() + } + + privateData := EncodePrivateKey(private) + tunnel, err := NewSSHTunnelFromBytes("foo", privateData, server.Host) + if err != nil { + t.Errorf("unexpected error: %v", err) + t.FailNow() + } + tunnel.SSHPort = server.Port + + if err := tunnel.Open(); err != nil { + t.Errorf("unexpected error: %v", err) + t.FailNow() + } + + _, err = tunnel.Dial("tcp", "127.0.0.1:8080") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if server.Type != "ssh-rsa" { + t.Errorf("expected %s, got %s", "ssh-rsa", server.Type) + } + + publicData, err := EncodeSSHKey(public) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !reflect.DeepEqual(server.Data, publicData) { + t.Errorf("expected %s, got %s", string(server.Data), string(privateData)) + } + + if err := tunnel.Close(); err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +type fakeTunnel struct{} + +func (*fakeTunnel) Open() error { + return nil +} + +func (*fakeTunnel) Close() error { + return nil +} + +func (*fakeTunnel) Dial(network, address string) (net.Conn, error) { + return nil, nil +} + +type fakeTunnelCreator struct{} + +func (*fakeTunnelCreator) NewSSHTunnel(string, string, string) (tunnel, error) { + return &fakeTunnel{}, nil +} + +func TestSSHTunnelListUpdate(t *testing.T) { + // Start with an empty tunnel list. + l := &SSHTunnelList{ + adding: make(map[string]bool), + tunnelCreator: &fakeTunnelCreator{}, + } + + // Start with 2 tunnels. + addressStrings := []string{"1.2.3.4", "5.6.7.8"} + l.Update(addressStrings) + checkTunnelsCorrect(t, l, addressStrings) + + // Add another tunnel. + addressStrings = append(addressStrings, "9.10.11.12") + l.Update(addressStrings) + checkTunnelsCorrect(t, l, addressStrings) + + // Go down to a single tunnel. + addressStrings = []string{"1.2.3.4"} + l.Update(addressStrings) + checkTunnelsCorrect(t, l, addressStrings) + + // Replace w/ all new tunnels. + addressStrings = []string{"21.22.23.24", "25.26.27.28"} + l.Update(addressStrings) + checkTunnelsCorrect(t, l, addressStrings) + + // Call update with the same tunnels. + l.Update(addressStrings) + checkTunnelsCorrect(t, l, addressStrings) +} + +func checkTunnelsCorrect(t *testing.T, tunnelList *SSHTunnelList, addresses []string) { + if err := wait.Poll(100*time.Millisecond, 2*time.Second, func() (bool, error) { + return hasCorrectTunnels(tunnelList, addresses), nil + }); err != nil { + t.Errorf("Error waiting for tunnels to reach expected state: %v. Expected %v, had %v", err, addresses, tunnelList) + } +} + +func hasCorrectTunnels(tunnelList *SSHTunnelList, addresses []string) bool { + tunnelList.tunnelsLock.Lock() + defer tunnelList.tunnelsLock.Unlock() + wantMap := make(map[string]bool) + for _, addr := range addresses { + wantMap[addr] = true + } + haveMap := make(map[string]bool) + for _, entry := range tunnelList.entries { + if wantMap[entry.Address] == false { + return false + } + haveMap[entry.Address] = true + } + for _, addr := range addresses { + if haveMap[addr] == false { + return false + } + } + return true +} + +type mockSSHDialer struct { + network string + addr string + config *ssh.ClientConfig +} + +func (d *mockSSHDialer) Dial(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + d.network = network + d.addr = addr + d.config = config + return nil, fmt.Errorf("mock error from Dial") +} + +type mockSigner struct { +} + +func (s *mockSigner) PublicKey() ssh.PublicKey { + panic("mockSigner.PublicKey not implemented") +} + +func (s *mockSigner) Sign(rand io.Reader, data []byte) (*ssh.Signature, error) { + panic("mockSigner.Sign not implemented") +} + +func TestSSHUser(t *testing.T) { + signer := &mockSigner{} + + table := []struct { + title string + user string + host string + signer ssh.Signer + command string + expectUser string + }{ + { + title: "all values provided", + user: "testuser", + host: "testhost", + signer: signer, + command: "uptime", + expectUser: "testuser", + }, + { + title: "empty user defaults to GetEnv(USER)", + user: "", + host: "testhost", + signer: signer, + command: "uptime", + expectUser: os.Getenv("USER"), + }, + } + + for _, item := range table { + dialer := &mockSSHDialer{} + + _, _, _, err := runSSHCommand(dialer, item.command, item.user, item.host, item.signer, false) + if err == nil { + t.Errorf("expected error (as mock returns error); did not get one") + } + errString := err.Error() + if !strings.HasPrefix(errString, fmt.Sprintf("error getting SSH client to %s@%s:", item.expectUser, item.host)) { + t.Errorf("unexpected error: %v", errString) + } + + if dialer.network != "tcp" { + t.Errorf("unexpected network: %v", dialer.network) + } + + if dialer.config.User != item.expectUser { + t.Errorf("unexpected user: %v", dialer.config.User) + } + if len(dialer.config.Auth) != 1 { + t.Errorf("unexpected auth: %v", dialer.config.Auth) + } + // (No way to test Auth - nothing exported?) + + } + +} + +type slowDialer struct { + delay time.Duration + err error +} + +func (s *slowDialer) Dial(network, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + time.Sleep(s.delay) + if s.err != nil { + return nil, s.err + } + return &ssh.Client{}, nil +} + +func TestTimeoutDialer(t *testing.T) { + testCases := []struct { + delay time.Duration + timeout time.Duration + err error + expectedErrString string + }{ + // delay > timeout should cause ssh.Dial to timeout. + {1 * time.Second, 0, nil, "timed out dialing"}, + // delay < timeout should return the result of the call to the dialer. + {0, 1 * time.Second, nil, ""}, + {0, 1 * time.Second, fmt.Errorf("test dial error"), "test dial error"}, + } + for _, tc := range testCases { + dialer := &timeoutDialer{&slowDialer{tc.delay, tc.err}, tc.timeout} + _, err := dialer.Dial("tcp", "addr:port", &ssh.ClientConfig{}) + if len(tc.expectedErrString) == 0 && err != nil || + !strings.Contains(fmt.Sprint(err), tc.expectedErrString) { + t.Errorf("Expected error to contain %q; got %v", tc.expectedErrString, err) + } + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/storage/cacher.go b/vendor/k8s.io/kubernetes/pkg/storage/cacher.go index c5bcf2448..3b67f50e2 100644 --- a/vendor/k8s.io/kubernetes/pkg/storage/cacher.go +++ b/vendor/k8s.io/kubernetes/pkg/storage/cacher.go @@ -30,6 +30,7 @@ import ( "k8s.io/kubernetes/pkg/client/cache" "k8s.io/kubernetes/pkg/conversion" "k8s.io/kubernetes/pkg/runtime" + utilruntime "k8s.io/kubernetes/pkg/util/runtime" "k8s.io/kubernetes/pkg/util/wait" "k8s.io/kubernetes/pkg/watch" @@ -145,6 +146,14 @@ func NewCacherFromConfig(config CacherConfig) *Cacher { watchCache := newWatchCache(config.CacheCapacity) listerWatcher := newCacherListerWatcher(config.Storage, config.ResourcePrefix, config.NewListFunc) + // Give this error when it is constructed rather than when you get the + // first watch item, because it's much easier to track down that way. + if obj, ok := config.Type.(runtime.Object); ok { + if err := runtime.CheckCodec(config.Storage.Codec(), obj); err != nil { + panic("storage codec doesn't seem to match given type: " + err.Error()) + } + } + cacher := &Cacher{ usable: sync.RWMutex{}, storage: config.Storage, @@ -163,9 +172,8 @@ func NewCacherFromConfig(config CacherConfig) *Cacher { stopCh: make(chan struct{}), stopWg: sync.WaitGroup{}, } + // See startCaching method for explanation and where this is unlocked. cacher.usable.Lock() - // See startCaching method for why explanation on it. - watchCache.SetOnReplace(func() { cacher.usable.Unlock() }) watchCache.SetOnEvent(cacher.processEvent) stopCh := cacher.stopCh @@ -184,16 +192,22 @@ func NewCacherFromConfig(config CacherConfig) *Cacher { } func (c *Cacher) startCaching(stopChannel <-chan struct{}) { - // Whenever we enter startCaching method, usable mutex is held. - // We explicitly do NOT Unlock it in this method, because we do - // not want to allow any Watch/List methods not explicitly redirected - // to the underlying storage when the cache is being initialized. - // Once the underlying cache is propagated, onReplace handler will - // be called, which will do the usable.Unlock() as configured in - // NewCacher(). - // Note: the same behavior is also triggered every time we fall out of - // backend storage watch event window. - defer c.usable.Lock() + // The 'usable' lock is always 'RLock'able when it is safe to use the cache. + // It is safe to use the cache after a successful list until a disconnection. + // We start with usable (write) locked. The below OnReplace function will + // unlock it after a successful list. The below defer will then re-lock + // it when this function exits (always due to disconnection), only if + // we actually got a successful list. This cycle will repeat as needed. + successfulList := false + c.watchCache.SetOnReplace(func() { + successfulList = true + c.usable.Unlock() + }) + defer func() { + if successfulList { + c.usable.Lock() + } + }() c.terminateAllWatchers() // Note that since onReplace may be not called due to errors, we explicitly @@ -226,8 +240,8 @@ func (c *Cacher) Set(ctx context.Context, key string, obj, out runtime.Object, t } // Implements storage.Interface. -func (c *Cacher) Delete(ctx context.Context, key string, out runtime.Object) error { - return c.storage.Delete(ctx, key, out) +func (c *Cacher) Delete(ctx context.Context, key string, out runtime.Object, preconditions *Preconditions) error { + return c.storage.Delete(ctx, key, out, preconditions) } // Implements storage.Interface. @@ -334,8 +348,8 @@ func (c *Cacher) List(ctx context.Context, key string, resourceVersion string, f } // Implements storage.Interface. -func (c *Cacher) GuaranteedUpdate(ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, tryUpdate UpdateFunc) error { - return c.storage.GuaranteedUpdate(ctx, key, ptrToType, ignoreNotFound, tryUpdate) +func (c *Cacher) GuaranteedUpdate(ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, preconditions *Preconditions, tryUpdate UpdateFunc) error { + return c.storage.GuaranteedUpdate(ctx, key, ptrToType, ignoreNotFound, preconditions, tryUpdate) } // Implements storage.Interface. @@ -489,9 +503,11 @@ func (c *cacheWatcher) stop() { } func (c *cacheWatcher) add(event watchCacheEvent) { + t := time.NewTimer(5 * time.Second) + defer t.Stop() select { case c.input <- event: - case <-time.After(5 * time.Second): + case <-t.C: // This means that we couldn't send event to that watcher. // Since we don't want to blockin on it infinitely, // we simply terminate it. @@ -527,6 +543,8 @@ func (c *cacheWatcher) sendWatchCacheEvent(event watchCacheEvent) { } func (c *cacheWatcher) process(initEvents []watchCacheEvent) { + defer utilruntime.HandleCrash() + for _, event := range initEvents { c.sendWatchCacheEvent(event) } diff --git a/vendor/k8s.io/kubernetes/pkg/storage/cacher_test.go b/vendor/k8s.io/kubernetes/pkg/storage/cacher_test.go index dd829739c..2dd6382d1 100644 --- a/vendor/k8s.io/kubernetes/pkg/storage/cacher_test.go +++ b/vendor/k8s.io/kubernetes/pkg/storage/cacher_test.go @@ -17,7 +17,9 @@ limitations under the License. package storage_test import ( + "fmt" "reflect" + goruntime "runtime" "strconv" "testing" "time" @@ -106,17 +108,25 @@ func TestList(t *testing.T) { _ = updatePod(t, etcdStorage, podFooPrime, fooCreated) deleted := api.Pod{} - if err := etcdStorage.Delete(context.TODO(), etcdtest.AddPrefix("pods/ns/bar"), &deleted); err != nil { + if err := etcdStorage.Delete(context.TODO(), etcdtest.AddPrefix("pods/ns/bar"), &deleted, nil); err != nil { t.Errorf("Unexpected error: %v", err) } - result := &api.PodList{} - // TODO: We need to pass ResourceVersion of barPod deletion operation. - // However, there is no easy way to get it, so it is hardcoded to 8. - if err := cacher.List(context.TODO(), "pods/ns", "8", storage.Everything, result); err != nil { + // We first List directly from etcd by passing empty resourceVersion, + // to get the current etcd resourceVersion. + rvResult := &api.PodList{} + if err := cacher.List(context.TODO(), "pods/ns", "", storage.Everything, rvResult); err != nil { t.Errorf("Unexpected error: %v", err) } - if result.ListMeta.ResourceVersion != "8" { + deletedPodRV := rvResult.ListMeta.ResourceVersion + + result := &api.PodList{} + // We pass the current etcd ResourceVersion received from the above List() operation, + // since there is not easy way to get ResourceVersion of barPod deletion operation. + if err := cacher.List(context.TODO(), "pods/ns", deletedPodRV, storage.Everything, result); err != nil { + t.Errorf("Unexpected error: %v", err) + } + if result.ListMeta.ResourceVersion != deletedPodRV { t.Errorf("Incorrect resource version: %v", result.ListMeta.ResourceVersion) } if len(result.Items) != 2 { @@ -150,21 +160,40 @@ func TestList(t *testing.T) { } func verifyWatchEvent(t *testing.T, w watch.Interface, eventType watch.EventType, eventObject runtime.Object) { + _, _, line, _ := goruntime.Caller(1) select { case event := <-w.ResultChan(): if e, a := eventType, event.Type; e != a { + t.Logf("(called from line %d)", line) t.Errorf("Expected: %s, got: %s", eventType, event.Type) } if e, a := eventObject, event.Object; !api.Semantic.DeepDerivative(e, a) { + t.Logf("(called from line %d)", line) t.Errorf("Expected (%s): %#v, got: %#v", eventType, e, a) } case <-time.After(wait.ForeverTestTimeout): + t.Logf("(called from line %d)", line) t.Errorf("Timed out waiting for an event") } } +type injectListError struct { + errors int + storage.Interface +} + +func (self *injectListError) List(ctx context.Context, key string, resourceVersion string, filter storage.FilterFunc, listObj runtime.Object) error { + if self.errors > 0 { + self.errors-- + return fmt.Errorf("injected error") + } + return self.Interface.List(ctx, key, resourceVersion, filter, listObj) +} + func TestWatch(t *testing.T) { server, etcdStorage := newEtcdTestStorage(t, testapi.Default.Codec(), etcdtest.PathPrefix()) + // Inject one list error to make sure we test the relist case. + etcdStorage = &injectListError{errors: 1, Interface: etcdStorage} defer server.Terminate(t) cacher := newTestCacher(etcdStorage) defer cacher.Stop() @@ -212,7 +241,6 @@ func TestWatch(t *testing.T) { } defer initialWatcher.Stop() - verifyWatchEvent(t, initialWatcher, watch.Added, podFoo) verifyWatchEvent(t, initialWatcher, watch.Modified, podFooPrime) // Now test watch from "now". @@ -291,7 +319,7 @@ func TestFiltering(t *testing.T) { _ = updatePod(t, etcdStorage, podFooPrime, fooUnfiltered) deleted := api.Pod{} - if err := etcdStorage.Delete(context.TODO(), etcdtest.AddPrefix("pods/ns/foo"), &deleted); err != nil { + if err := etcdStorage.Delete(context.TODO(), etcdtest.AddPrefix("pods/ns/foo"), &deleted, nil); err != nil { t.Errorf("Unexpected error: %v", err) } @@ -311,7 +339,6 @@ func TestFiltering(t *testing.T) { } defer watcher.Stop() - verifyWatchEvent(t, watcher, watch.Added, podFoo) verifyWatchEvent(t, watcher, watch.Deleted, podFooFiltered) verifyWatchEvent(t, watcher, watch.Added, podFoo) verifyWatchEvent(t, watcher, watch.Modified, podFooPrime) diff --git a/vendor/k8s.io/kubernetes/pkg/storage/errors.go b/vendor/k8s.io/kubernetes/pkg/storage/errors.go index 8fe1df643..61b3cba52 100644 --- a/vendor/k8s.io/kubernetes/pkg/storage/errors.go +++ b/vendor/k8s.io/kubernetes/pkg/storage/errors.go @@ -17,29 +17,158 @@ limitations under the License. package storage import ( - etcdutil "k8s.io/kubernetes/pkg/storage/etcd/util" + "fmt" + + "k8s.io/kubernetes/pkg/util/validation/field" ) +const ( + ErrCodeKeyNotFound int = iota + 1 + ErrCodeKeyExists + ErrCodeResourceVersionConflicts + ErrCodeInvalidObj + ErrCodeUnreachable +) + +var errCodeToMessage = map[int]string{ + ErrCodeKeyNotFound: "key not found", + ErrCodeKeyExists: "key exists", + ErrCodeResourceVersionConflicts: "resource version conflicts", + ErrCodeInvalidObj: "invalid object", + ErrCodeUnreachable: "server unreachable", +} + +func NewKeyNotFoundError(key string, rv int64) *StorageError { + return &StorageError{ + Code: ErrCodeKeyNotFound, + Key: key, + ResourceVersion: rv, + } +} + +func NewKeyExistsError(key string, rv int64) *StorageError { + return &StorageError{ + Code: ErrCodeKeyExists, + Key: key, + ResourceVersion: rv, + } +} + +func NewResourceVersionConflictsError(key string, rv int64) *StorageError { + return &StorageError{ + Code: ErrCodeResourceVersionConflicts, + Key: key, + ResourceVersion: rv, + } +} + +func NewUnreachableError(key string, rv int64) *StorageError { + return &StorageError{ + Code: ErrCodeUnreachable, + Key: key, + ResourceVersion: rv, + } +} + +func NewInvalidObjError(key, msg string) *StorageError { + return &StorageError{ + Code: ErrCodeInvalidObj, + Key: key, + AdditionalErrorMsg: msg, + } +} + +type StorageError struct { + Code int + Key string + ResourceVersion int64 + AdditionalErrorMsg string +} + +func (e *StorageError) Error() string { + return fmt.Sprintf("StorageError: %s, Code: %d, Key: %s, ResourceVersion: %d, AdditionalErrorMsg: %s", + errCodeToMessage[e.Code], e.Code, e.Key, e.ResourceVersion, e.AdditionalErrorMsg) +} + // IsNotFound returns true if and only if err is "key" not found error. func IsNotFound(err error) bool { - // TODO: add alternate storage error here - return etcdutil.IsEtcdNotFound(err) + return isErrCode(err, ErrCodeKeyNotFound) } // IsNodeExist returns true if and only if err is an node already exist error. func IsNodeExist(err error) bool { - // TODO: add alternate storage error here - return etcdutil.IsEtcdNodeExist(err) + return isErrCode(err, ErrCodeKeyExists) } // IsUnreachable returns true if and only if err indicates the server could not be reached. func IsUnreachable(err error) bool { - // TODO: add alternate storage error here - return etcdutil.IsEtcdUnreachable(err) + return isErrCode(err, ErrCodeUnreachable) } // IsTestFailed returns true if and only if err is a write conflict. func IsTestFailed(err error) bool { - // TODO: add alternate storage error here - return etcdutil.IsEtcdTestFailed(err) + return isErrCode(err, ErrCodeResourceVersionConflicts, ErrCodeInvalidObj) +} + +// IsInvalidUID returns true if and only if err is invalid UID error +func IsInvalidObj(err error) bool { + return isErrCode(err, ErrCodeInvalidObj) +} + +func isErrCode(err error, codes ...int) bool { + if err == nil { + return false + } + if e, ok := err.(*StorageError); ok { + for _, code := range codes { + if e.Code == code { + return true + } + } + } + return false +} + +// InvalidError is generated when an error caused by invalid API object occurs +// in the storage package. +type InvalidError struct { + Errs field.ErrorList +} + +func (e InvalidError) Error() string { + return e.Errs.ToAggregate().Error() +} + +// IsInvalidError returns true if and only if err is an InvalidError. +func IsInvalidError(err error) bool { + _, ok := err.(InvalidError) + return ok +} + +func NewInvalidError(errors field.ErrorList) InvalidError { + return InvalidError{errors} +} + +// InternalError is generated when an error occurs in the storage package, i.e., +// not from the underlying storage backend (e.g., etcd). +type InternalError struct { + Reason string +} + +func (e InternalError) Error() string { + return e.Reason +} + +// IsInternalError returns true if and only if err is an InternalError. +func IsInternalError(err error) bool { + _, ok := err.(InternalError) + return ok +} + +func NewInternalError(reason string) InternalError { + return InternalError{reason} +} + +func NewInternalErrorf(format string, a ...interface{}) InternalError { + return InternalError{fmt.Sprintf(format, a)} } diff --git a/vendor/k8s.io/kubernetes/pkg/storage/etcd/api_object_versioner.go b/vendor/k8s.io/kubernetes/pkg/storage/etcd/api_object_versioner.go index 41875fc55..b7be1720a 100644 --- a/vendor/k8s.io/kubernetes/pkg/storage/etcd/api_object_versioner.go +++ b/vendor/k8s.io/kubernetes/pkg/storage/etcd/api_object_versioner.go @@ -21,6 +21,7 @@ import ( "time" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/storage" @@ -32,18 +33,18 @@ type APIObjectVersioner struct{} // UpdateObject implements Versioner func (a APIObjectVersioner) UpdateObject(obj runtime.Object, expiration *time.Time, resourceVersion uint64) error { - objectMeta, err := api.ObjectMetaFor(obj) + accessor, err := meta.Accessor(obj) if err != nil { return err } if expiration != nil { - objectMeta.DeletionTimestamp = &unversioned.Time{Time: *expiration} + accessor.SetDeletionTimestamp(&unversioned.Time{Time: *expiration}) } versionString := "" if resourceVersion != 0 { versionString = strconv.FormatUint(resourceVersion, 10) } - objectMeta.ResourceVersion = versionString + accessor.SetResourceVersion(versionString) return nil } @@ -63,11 +64,11 @@ func (a APIObjectVersioner) UpdateList(obj runtime.Object, resourceVersion uint6 // ObjectResourceVersion implements Versioner func (a APIObjectVersioner) ObjectResourceVersion(obj runtime.Object) (uint64, error) { - meta, err := api.ObjectMetaFor(obj) + accessor, err := meta.Accessor(obj) if err != nil { return 0, err } - version := meta.ResourceVersion + version := accessor.GetResourceVersion() if len(version) == 0 { return 0, nil } diff --git a/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_helper.go b/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_helper.go index 191c4bd61..f3ef8f8c7 100644 --- a/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_helper.go +++ b/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_helper.go @@ -34,69 +34,114 @@ import ( "k8s.io/kubernetes/pkg/storage/etcd/metrics" etcdutil "k8s.io/kubernetes/pkg/storage/etcd/util" "k8s.io/kubernetes/pkg/util" + utilcache "k8s.io/kubernetes/pkg/util/cache" + utilnet "k8s.io/kubernetes/pkg/util/net" "k8s.io/kubernetes/pkg/watch" etcd "github.com/coreos/etcd/client" + "github.com/coreos/etcd/pkg/transport" "github.com/golang/glog" "golang.org/x/net/context" ) // storage.Config object for etcd. -type EtcdConfig struct { - ServerList []string - Codec runtime.Codec - Prefix string - Quorum bool +type EtcdStorageConfig struct { + Config EtcdConfig + Codec runtime.Codec } // implements storage.Config -func (c *EtcdConfig) GetType() string { +func (c *EtcdStorageConfig) GetType() string { return "etcd" } // implements storage.Config -func (c *EtcdConfig) NewStorage() (storage.Interface, error) { - cfg := etcd.Config{ - Endpoints: c.ServerList, - // TODO: Determine if transport needs optimization - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - Dial: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }).Dial, - TLSHandshakeTimeout: 10 * time.Second, - MaxIdleConnsPerHost: 500, - }, - } - etcdClient, err := etcd.New(cfg) +func (c *EtcdStorageConfig) NewStorage() (storage.Interface, error) { + etcdClient, err := c.Config.newEtcdClient() if err != nil { return nil, err } - return NewEtcdStorage(etcdClient, c.Codec, c.Prefix, c.Quorum), nil + return NewEtcdStorage(etcdClient, c.Codec, c.Config.Prefix, c.Config.Quorum), nil +} + +// Configuration object for constructing etcd.Config +type EtcdConfig struct { + Prefix string + ServerList []string + KeyFile string + CertFile string + CAFile string + Quorum bool +} + +func (c *EtcdConfig) newEtcdClient() (etcd.Client, error) { + t, err := c.newHttpTransport() + if err != nil { + return nil, err + } + + cli, err := etcd.New(etcd.Config{ + Endpoints: c.ServerList, + Transport: t, + }) + if err != nil { + return nil, err + } + + return cli, nil +} + +func (c *EtcdConfig) newHttpTransport() (*http.Transport, error) { + info := transport.TLSInfo{ + CertFile: c.CertFile, + KeyFile: c.KeyFile, + CAFile: c.CAFile, + } + cfg, err := info.ClientConfig() + if err != nil { + return nil, err + } + + // Copied from etcd.DefaultTransport declaration. + // TODO: Determine if transport needs optimization + tr := utilnet.SetTransportDefaults(&http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, + MaxIdleConnsPerHost: 500, + TLSClientConfig: cfg, + }) + + return tr, nil } // Creates a new storage interface from the client // TODO: deprecate in favor of storage.Config abstraction over time func NewEtcdStorage(client etcd.Client, codec runtime.Codec, prefix string, quorum bool) storage.Interface { return &etcdHelper{ - etcdclient: client, - client: etcd.NewKeysAPI(client), - codec: codec, - versioner: APIObjectVersioner{}, - copier: api.Scheme, - pathPrefix: path.Join("/", prefix), - quorum: quorum, - cache: util.NewCache(maxEtcdCacheEntries), + etcdMembersAPI: etcd.NewMembersAPI(client), + etcdKeysAPI: etcd.NewKeysAPI(client), + codec: codec, + versioner: APIObjectVersioner{}, + copier: api.Scheme, + pathPrefix: path.Join("/", prefix), + quorum: quorum, + cache: utilcache.NewCache(maxEtcdCacheEntries), } } // etcdHelper is the reference implementation of storage.Interface. type etcdHelper struct { - etcdclient etcd.Client - client etcd.KeysAPI - codec runtime.Codec - copier runtime.ObjectCopier + etcdMembersAPI etcd.MembersAPI + etcdKeysAPI etcd.KeysAPI + codec runtime.Codec + copier runtime.ObjectCopier + // Note that versioner is required for etcdHelper to work correctly. + // The public constructors (NewStorage & NewEtcdStorage) are setting it + // correctly, so be careful when manipulating with it manually. // optional, has to be set to perform any atomic operations versioner storage.Versioner // prefix for all etcd keys @@ -111,7 +156,7 @@ type etcdHelper struct { // support multi-object transaction that will result in many objects with the same index. // Number of entries stored in the cache is controlled by maxEtcdCacheEntries constant. // TODO: Measure how much this cache helps after the conversion code is optimized. - cache util.Cache + cache utilcache.Cache } func init() { @@ -128,8 +173,7 @@ func (h *etcdHelper) Backends(ctx context.Context) []string { if ctx == nil { glog.Errorf("Context is nil") } - membersAPI := etcd.NewMembersAPI(h.etcdclient) - members, err := membersAPI.List(ctx) + members, err := h.etcdMembersAPI.List(ctx) if err != nil { glog.Errorf("Error obtaining etcd members list: %q", err) return nil @@ -159,10 +203,8 @@ func (h *etcdHelper) Create(ctx context.Context, key string, obj, out runtime.Ob if err != nil { return err } - if h.versioner != nil { - if version, err := h.versioner.ObjectResourceVersion(obj); err == nil && version != 0 { - return errors.New("resourceVersion may not be set on objects to be created") - } + if version, err := h.versioner.ObjectResourceVersion(obj); err == nil && version != 0 { + return errors.New("resourceVersion may not be set on objects to be created") } trace.Step("Version checked") @@ -171,11 +213,11 @@ func (h *etcdHelper) Create(ctx context.Context, key string, obj, out runtime.Ob TTL: time.Duration(ttl) * time.Second, PrevExist: etcd.PrevNoExist, } - response, err := h.client.Set(ctx, key, string(data), &opts) + response, err := h.etcdKeysAPI.Set(ctx, key, string(data), &opts) metrics.RecordEtcdRequestLatency("create", getTypeName(obj), startTime) trace.Step("Object created") if err != nil { - return err + return toStorageErr(err, key, 0) } if out != nil { if _, err := conversion.EnforcePtr(out); err != nil { @@ -193,21 +235,16 @@ func (h *etcdHelper) Set(ctx context.Context, key string, obj, out runtime.Objec } version := uint64(0) - if h.versioner != nil { - var err error - if version, err = h.versioner.ObjectResourceVersion(obj); err != nil { - return errors.New("couldn't get resourceVersion from object") - } - if version != 0 { - // We cannot store object with resourceVersion in etcd, we need to clear it here. - if err := h.versioner.UpdateObject(obj, nil, 0); err != nil { - return errors.New("resourceVersion cannot be set on objects store in etcd") - } + var err error + if version, err = h.versioner.ObjectResourceVersion(obj); err != nil { + return errors.New("couldn't get resourceVersion from object") + } + if version != 0 { + // We cannot store object with resourceVersion in etcd, we need to clear it here. + if err := h.versioner.UpdateObject(obj, nil, 0); err != nil { + return errors.New("resourceVersion cannot be set on objects store in etcd") } } - // TODO: If versioner is nil, then we may end up with having ResourceVersion set - // in the object and this will be incorrect ResourceVersion. We should fix it by - // requiring "versioner != nil" at the constructor level for 1.3 milestone. var response *etcd.Response data, err := runtime.Encode(h.codec, obj) @@ -217,19 +254,17 @@ func (h *etcdHelper) Set(ctx context.Context, key string, obj, out runtime.Objec key = h.prefixEtcdKey(key) create := true - if h.versioner != nil { - if version != 0 { - create = false - startTime := time.Now() - opts := etcd.SetOptions{ - TTL: time.Duration(ttl) * time.Second, - PrevIndex: version, - } - response, err = h.client.Set(ctx, key, string(data), &opts) - metrics.RecordEtcdRequestLatency("compareAndSwap", getTypeName(obj), startTime) - if err != nil { - return err - } + if version != 0 { + create = false + startTime := time.Now() + opts := etcd.SetOptions{ + TTL: time.Duration(ttl) * time.Second, + PrevIndex: version, + } + response, err = h.etcdKeysAPI.Set(ctx, key, string(data), &opts) + metrics.RecordEtcdRequestLatency("compareAndSwap", getTypeName(obj), startTime) + if err != nil { + return toStorageErr(err, key, int64(version)) } } if create { @@ -239,16 +274,13 @@ func (h *etcdHelper) Set(ctx context.Context, key string, obj, out runtime.Objec TTL: time.Duration(ttl) * time.Second, PrevExist: etcd.PrevNoExist, } - response, err = h.client.Set(ctx, key, string(data), &opts) + response, err = h.etcdKeysAPI.Set(ctx, key, string(data), &opts) if err != nil { - return err + return toStorageErr(err, key, 0) } metrics.RecordEtcdRequestLatency("create", getTypeName(obj), startTime) } - if err != nil { - return err - } if out != nil { if _, err := conversion.EnforcePtr(out); err != nil { panic("unable to convert output object to pointer") @@ -259,26 +291,76 @@ func (h *etcdHelper) Set(ctx context.Context, key string, obj, out runtime.Objec return err } +func checkPreconditions(preconditions *storage.Preconditions, out runtime.Object) error { + if preconditions == nil { + return nil + } + objMeta, err := api.ObjectMetaFor(out) + if err != nil { + return storage.NewInternalErrorf("can't enforce preconditions %v on un-introspectable object %v, got error: %v", *preconditions, out, err) + } + if preconditions.UID != nil && *preconditions.UID != objMeta.UID { + return etcd.Error{Code: etcd.ErrorCodeTestFailed, Message: fmt.Sprintf("the UID in the precondition (%s) does not match the UID in record (%s). The object might have been deleted and then recreated", *preconditions.UID, objMeta.UID)} + } + return nil +} + // Implements storage.Interface. -func (h *etcdHelper) Delete(ctx context.Context, key string, out runtime.Object) error { +func (h *etcdHelper) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions) error { if ctx == nil { glog.Errorf("Context is nil") } key = h.prefixEtcdKey(key) - if _, err := conversion.EnforcePtr(out); err != nil { + v, err := conversion.EnforcePtr(out) + if err != nil { panic("unable to convert output object to pointer") } - startTime := time.Now() - response, err := h.client.Delete(ctx, key, nil) - metrics.RecordEtcdRequestLatency("delete", getTypeName(out), startTime) - if !etcdutil.IsEtcdNotFound(err) { - // if the object that existed prior to the delete is returned by etcd, update out. - if err != nil || response.PrevNode != nil { - _, _, err = h.extractObj(response, err, out, false, true) + if preconditions == nil { + startTime := time.Now() + response, err := h.etcdKeysAPI.Delete(ctx, key, nil) + metrics.RecordEtcdRequestLatency("delete", getTypeName(out), startTime) + if !etcdutil.IsEtcdNotFound(err) { + // if the object that existed prior to the delete is returned by etcd, update the out object. + if err != nil || response.PrevNode != nil { + _, _, err = h.extractObj(response, err, out, false, true) + } + } + return toStorageErr(err, key, 0) + } + + // Check the preconditions match. + obj := reflect.New(v.Type()).Interface().(runtime.Object) + for { + _, node, res, err := h.bodyAndExtractObj(ctx, key, obj, false) + if err != nil { + return toStorageErr(err, key, 0) + } + if err := checkPreconditions(preconditions, obj); err != nil { + return toStorageErr(err, key, 0) + } + index := uint64(0) + if node != nil { + index = node.ModifiedIndex + } else if res != nil { + index = res.Index + } + opt := etcd.DeleteOptions{PrevIndex: index} + startTime := time.Now() + response, err := h.etcdKeysAPI.Delete(ctx, key, &opt) + metrics.RecordEtcdRequestLatency("delete", getTypeName(out), startTime) + if etcdutil.IsEtcdTestFailed(err) { + glog.Infof("deletion of %s failed because of a conflict, going to retry", key) + } else { + if !etcdutil.IsEtcdNotFound(err) { + // if the object that existed prior to the delete is returned by etcd, update the out object. + if err != nil || response.PrevNode != nil { + _, _, err = h.extractObj(response, err, out, false, true) + } + } + return toStorageErr(err, key, 0) } } - return err } // Implements storage.Interface. @@ -292,7 +374,7 @@ func (h *etcdHelper) Watch(ctx context.Context, key string, resourceVersion stri } key = h.prefixEtcdKey(key) w := newEtcdWatcher(false, h.quorum, nil, filter, h.codec, h.versioner, nil, h) - go w.etcdWatch(ctx, h.client, key, watchRV) + go w.etcdWatch(ctx, h.etcdKeysAPI, key, watchRV) return w, nil } @@ -307,7 +389,7 @@ func (h *etcdHelper) WatchList(ctx context.Context, key string, resourceVersion } key = h.prefixEtcdKey(key) w := newEtcdWatcher(true, h.quorum, exceptKey(key), filter, h.codec, h.versioner, nil, h) - go w.etcdWatch(ctx, h.client, key, watchRV) + go w.etcdWatch(ctx, h.etcdKeysAPI, key, watchRV) return w, nil } @@ -333,14 +415,13 @@ func (h *etcdHelper) bodyAndExtractObj(ctx context.Context, key string, objPtr r Quorum: h.quorum, } - response, err := h.client.Get(ctx, key, opts) + response, err := h.etcdKeysAPI.Get(ctx, key, opts) metrics.RecordEtcdRequestLatency("get", getTypeName(objPtr), startTime) - if err != nil && !etcdutil.IsEtcdNotFound(err) { - return "", nil, nil, err + return "", nil, nil, toStorageErr(err, key, 0) } body, node, err = h.extractObj(response, err, objPtr, ignoreNotFound, false) - return body, node, response, err + return body, node, response, toStorageErr(err, key, 0) } func (h *etcdHelper) extractObj(response *etcd.Response, inErr error, objPtr runtime.Object, ignoreNotFound, prevNode bool) (body string, node *etcd.Node, err error) { @@ -372,10 +453,8 @@ func (h *etcdHelper) extractObj(response *etcd.Response, inErr error, objPtr run if out != objPtr { return body, nil, fmt.Errorf("unable to decode object %s into %v", gvk.String(), reflect.TypeOf(objPtr)) } - if h.versioner != nil { - _ = h.versioner.UpdateObject(objPtr, node.Expiration, node.ModifiedIndex) - // being unable to set the version does not prevent the object from being extracted - } + // being unable to set the version does not prevent the object from being extracted + _ = h.versioner.UpdateObject(objPtr, node.Expiration, node.ModifiedIndex) return body, node, err } @@ -396,7 +475,7 @@ func (h *etcdHelper) GetToList(ctx context.Context, key string, filter storage.F opts := &etcd.GetOptions{ Quorum: h.quorum, } - response, err := h.client.Get(ctx, key, opts) + response, err := h.etcdKeysAPI.Get(ctx, key, opts) metrics.RecordEtcdRequestLatency("get", getTypeName(listPtr), startTime) trace.Step("Etcd node read") @@ -404,7 +483,7 @@ func (h *etcdHelper) GetToList(ctx context.Context, key string, filter storage.F if etcdutil.IsEtcdNotFound(err) { return nil } - return err + return toStorageErr(err, key, 0) } nodes := make([]*etcd.Node, 0) @@ -414,10 +493,8 @@ func (h *etcdHelper) GetToList(ctx context.Context, key string, filter storage.F return err } trace.Step("Object decoded") - if h.versioner != nil { - if err := h.versioner.UpdateList(listObj, response.Index); err != nil { - return err - } + if err := h.versioner.UpdateList(listObj, response.Index); err != nil { + return err } return nil } @@ -450,10 +527,8 @@ func (h *etcdHelper) decodeNodeList(nodes []*etcd.Node, filter storage.FilterFun if err != nil { return err } - if h.versioner != nil { - // being unable to set the version does not prevent the object from being extracted - _ = h.versioner.UpdateObject(obj, node.Expiration, node.ModifiedIndex) - } + // being unable to set the version does not prevent the object from being extracted + _ = h.versioner.UpdateObject(obj, node.Expiration, node.ModifiedIndex) if filter(obj) { v.Set(reflect.Append(v, reflect.ValueOf(obj).Elem())) } @@ -490,10 +565,8 @@ func (h *etcdHelper) List(ctx context.Context, key string, resourceVersion strin return err } trace.Step("Node list decoded") - if h.versioner != nil { - if err := h.versioner.UpdateList(listObj, index); err != nil { - return err - } + if err := h.versioner.UpdateList(listObj, index); err != nil { + return err } return nil } @@ -507,7 +580,7 @@ func (h *etcdHelper) listEtcdNode(ctx context.Context, key string) ([]*etcd.Node Sort: true, Quorum: h.quorum, } - result, err := h.client.Get(ctx, key, &opts) + result, err := h.etcdKeysAPI.Get(ctx, key, &opts) if err != nil { var index uint64 if etcdError, ok := err.(etcd.Error); ok { @@ -517,14 +590,14 @@ func (h *etcdHelper) listEtcdNode(ctx context.Context, key string) ([]*etcd.Node if etcdutil.IsEtcdNotFound(err) { return nodes, index, nil } else { - return nodes, index, err + return nodes, index, toStorageErr(err, key, 0) } } return result.Node.Nodes, result.Index, nil } // Implements storage.Interface. -func (h *etcdHelper) GuaranteedUpdate(ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, tryUpdate storage.UpdateFunc) error { +func (h *etcdHelper) GuaranteedUpdate(ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, preconditions *storage.Preconditions, tryUpdate storage.UpdateFunc) error { if ctx == nil { glog.Errorf("Context is nil") } @@ -538,7 +611,10 @@ func (h *etcdHelper) GuaranteedUpdate(ctx context.Context, key string, ptrToType obj := reflect.New(v.Type()).Interface().(runtime.Object) origBody, node, res, err := h.bodyAndExtractObj(ctx, key, obj, ignoreNotFound) if err != nil { - return err + return toStorageErr(err, key, 0) + } + if err := checkPreconditions(preconditions, obj); err != nil { + return toStorageErr(err, key, 0) } meta := storage.ResponseMeta{} if node != nil { @@ -551,7 +627,7 @@ func (h *etcdHelper) GuaranteedUpdate(ctx context.Context, key string, ptrToType // Get the object to be written by calling tryUpdate. ret, newTTL, err := tryUpdate(obj, meta) if err != nil { - return err + return toStorageErr(err, key, 0) } index := uint64(0) @@ -577,14 +653,9 @@ func (h *etcdHelper) GuaranteedUpdate(ctx context.Context, key string, ptrToType } // Since update object may have a resourceVersion set, we need to clear it here. - if h.versioner != nil { - if err := h.versioner.UpdateObject(ret, meta.Expiration, 0); err != nil { - return errors.New("resourceVersion cannot be set on objects store in etcd") - } + if err := h.versioner.UpdateObject(ret, meta.Expiration, 0); err != nil { + return errors.New("resourceVersion cannot be set on objects store in etcd") } - // TODO: If versioner is nil, then we may end up with having ResourceVersion set - // in the object and this will be incorrect ResourceVersion. We should fix it by - // requiring "versioner != nil" at the constructor level for 1.3 milestone. data, err := runtime.Encode(h.codec, ret) if err != nil { @@ -598,13 +669,13 @@ func (h *etcdHelper) GuaranteedUpdate(ctx context.Context, key string, ptrToType TTL: time.Duration(ttl) * time.Second, PrevExist: etcd.PrevNoExist, } - response, err := h.client.Set(ctx, key, string(data), &opts) + response, err := h.etcdKeysAPI.Set(ctx, key, string(data), &opts) metrics.RecordEtcdRequestLatency("create", getTypeName(ptrToType), startTime) if etcdutil.IsEtcdNodeExist(err) { continue } _, _, err = h.extractObj(response, err, ptrToType, false, false) - return err + return toStorageErr(err, key, 0) } if string(data) == origBody { @@ -621,14 +692,14 @@ func (h *etcdHelper) GuaranteedUpdate(ctx context.Context, key string, ptrToType PrevIndex: index, TTL: time.Duration(ttl) * time.Second, } - response, err := h.client.Set(ctx, key, string(data), &opts) + response, err := h.etcdKeysAPI.Set(ctx, key, string(data), &opts) metrics.RecordEtcdRequestLatency("compareAndSwap", getTypeName(ptrToType), startTime) if etcdutil.IsEtcdTestFailed(err) { // Try again. continue } _, _, err = h.extractObj(response, err, ptrToType, false, false) - return err + return toStorageErr(err, key, int64(index)) } } @@ -693,3 +764,21 @@ func (h *etcdHelper) addToCache(index uint64, obj runtime.Object) { metrics.ObserveNewEntry() } } + +func toStorageErr(err error, key string, rv int64) error { + if err == nil { + return nil + } + switch { + case etcdutil.IsEtcdNotFound(err): + return storage.NewKeyNotFoundError(key, rv) + case etcdutil.IsEtcdNodeExist(err): + return storage.NewKeyExistsError(key, rv) + case etcdutil.IsEtcdTestFailed(err): + return storage.NewResourceVersionConflictsError(key, rv) + case etcdutil.IsEtcdUnreachable(err): + return storage.NewUnreachableError(key, rv) + default: + return err + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_helper_test.go b/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_helper_test.go index 7a892b340..109cf9fc8 100644 --- a/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_helper_test.go +++ b/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_helper_test.go @@ -35,7 +35,6 @@ import ( "k8s.io/kubernetes/pkg/storage" "k8s.io/kubernetes/pkg/storage/etcd/etcdtest" etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" - etcdutil "k8s.io/kubernetes/pkg/storage/etcd/util" storagetesting "k8s.io/kubernetes/pkg/storage/testing" ) @@ -125,6 +124,7 @@ func TestList(t *testing.T) { if err != nil { t.Errorf("Unexpected error %v", err) } + if e, a := list.Items, got.Items; !reflect.DeepEqual(e, a) { t.Errorf("Expected %#v, got %#v", e, a) } @@ -250,7 +250,7 @@ func TestGetNotFoundErr(t *testing.T) { var got api.Pod err := helper.Get(context.TODO(), boguskey, &got, false) - if !etcdutil.IsEtcdNotFound(err) { + if !storage.IsNotFound(err) { t.Errorf("Unexpected reponse on key=%v, err=%v", key, err) } } @@ -352,22 +352,6 @@ func TestSetWithVersion(t *testing.T) { } } -func TestSetWithoutResourceVersioner(t *testing.T) { - obj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}} - server := etcdtesting.NewEtcdTestClientServer(t) - defer server.Terminate(t) - helper := newEtcdHelper(server.Client, testapi.Default.Codec(), etcdtest.PathPrefix()) - helper.versioner = nil - returnedObj := &api.Pod{} - err := helper.Set(context.TODO(), "/some/key", obj, returnedObj, 3) - if err != nil { - t.Errorf("Unexpected error %#v", err) - } - if returnedObj.ResourceVersion != "" { - t.Errorf("Resource revision should not be set on returned objects") - } -} - func TestSetNilOutParam(t *testing.T) { obj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}} server := etcdtesting.NewEtcdTestClientServer(t) @@ -387,7 +371,7 @@ func TestGuaranteedUpdate(t *testing.T) { helper := newEtcdHelper(server.Client, codec, key) obj := &storagetesting.TestResource{ObjectMeta: api.ObjectMeta{Name: "foo"}, Value: 1} - err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { + err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { return obj, nil })) if err != nil { @@ -397,7 +381,7 @@ func TestGuaranteedUpdate(t *testing.T) { // Update an existing node. callbackCalled := false objUpdate := &storagetesting.TestResource{ObjectMeta: api.ObjectMeta{Name: "foo"}, Value: 2} - err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { + err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { callbackCalled = true if in.(*storagetesting.TestResource).Value != 1 { @@ -432,7 +416,7 @@ func TestGuaranteedUpdateNoChange(t *testing.T) { helper := newEtcdHelper(server.Client, codec, key) obj := &storagetesting.TestResource{ObjectMeta: api.ObjectMeta{Name: "foo"}, Value: 1} - err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { + err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { return obj, nil })) if err != nil { @@ -442,7 +426,7 @@ func TestGuaranteedUpdateNoChange(t *testing.T) { // Update an existing node with the same data callbackCalled := false objUpdate := &storagetesting.TestResource{ObjectMeta: api.ObjectMeta{Name: "foo"}, Value: 1} - err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { + err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { callbackCalled = true return objUpdate, nil })) @@ -469,13 +453,13 @@ func TestGuaranteedUpdateKeyNotFound(t *testing.T) { }) ignoreNotFound := false - err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, ignoreNotFound, f) + err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, ignoreNotFound, nil, f) if err == nil { t.Errorf("Expected error for key not found.") } ignoreNotFound = true - err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, ignoreNotFound, f) + err = helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, ignoreNotFound, nil, f) if err != nil { t.Errorf("Unexpected error %v.", err) } @@ -500,7 +484,7 @@ func TestGuaranteedUpdate_CreateCollision(t *testing.T) { defer wgDone.Done() firstCall := true - err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { + err := helper.GuaranteedUpdate(context.TODO(), key, &storagetesting.TestResource{}, true, nil, storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { defer func() { firstCall = false }() if firstCall { @@ -530,6 +514,26 @@ func TestGuaranteedUpdate_CreateCollision(t *testing.T) { } } +func TestGuaranteedUpdateUIDMismatch(t *testing.T) { + server := etcdtesting.NewEtcdTestClientServer(t) + defer server.Terminate(t) + prefix := path.Join("/", etcdtest.PathPrefix()) + helper := newEtcdHelper(server.Client, testapi.Default.Codec(), prefix) + + obj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", UID: "A"}} + podPtr := &api.Pod{} + err := helper.Create(context.TODO(), "/some/key", obj, podPtr, 0) + if err != nil { + t.Fatalf("Unexpected error %#v", err) + } + err = helper.GuaranteedUpdate(context.TODO(), "/some/key", podPtr, true, storage.NewUIDPreconditions("B"), storage.SimpleUpdate(func(in runtime.Object) (runtime.Object, error) { + return obj, nil + })) + if !storage.IsTestFailed(err) { + t.Fatalf("Expect a Test Failed (write conflict) error, got: %v", err) + } +} + func TestPrefixEtcdKey(t *testing.T) { server := etcdtesting.NewEtcdTestClientServer(t) defer server.Terminate(t) @@ -550,3 +554,79 @@ func TestPrefixEtcdKey(t *testing.T) { assert.Equal(t, keyBefore, keyAfter, "Prefix incorrectly added by EtcdHelper") } + +func TestDeleteUIDMismatch(t *testing.T) { + server := etcdtesting.NewEtcdTestClientServer(t) + defer server.Terminate(t) + prefix := path.Join("/", etcdtest.PathPrefix()) + helper := newEtcdHelper(server.Client, testapi.Default.Codec(), prefix) + + obj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", UID: "A"}} + podPtr := &api.Pod{} + err := helper.Create(context.TODO(), "/some/key", obj, podPtr, 0) + if err != nil { + t.Fatalf("Unexpected error %#v", err) + } + err = helper.Delete(context.TODO(), "/some/key", obj, storage.NewUIDPreconditions("B")) + if !storage.IsTestFailed(err) { + t.Fatalf("Expect a Test Failed (write conflict) error, got: %v", err) + } +} + +type getFunc func(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) + +type fakeDeleteKeysAPI struct { + etcd.KeysAPI + fakeGetFunc getFunc + getCount int + // The fakeGetFunc will be called fakeGetCap times before the KeysAPI's Get will be called. + fakeGetCap int +} + +func (f *fakeDeleteKeysAPI) Get(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) { + f.getCount++ + if f.getCount < f.fakeGetCap { + return f.fakeGetFunc(ctx, key, opts) + } + return f.KeysAPI.Get(ctx, key, opts) +} + +// This is to emulate the case where another party updates the object when +// etcdHelper.Delete has verified the preconditions, but hasn't carried out the +// deletion yet. Etcd will fail the deletion and report the conflict. etcdHelper +// should retry until there is no conflict. +func TestDeleteWithRetry(t *testing.T) { + server := etcdtesting.NewEtcdTestClientServer(t) + defer server.Terminate(t) + prefix := path.Join("/", etcdtest.PathPrefix()) + + obj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", UID: "A"}} + // fakeGet returns a large ModifiedIndex to emulate the case that another + // party has updated the object. + fakeGet := func(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) { + data, _ := runtime.Encode(testapi.Default.Codec(), obj) + return &etcd.Response{Node: &etcd.Node{Value: string(data), ModifiedIndex: 99}}, nil + } + expectedRetries := 3 + helper := newEtcdHelper(server.Client, testapi.Default.Codec(), prefix) + fake := &fakeDeleteKeysAPI{KeysAPI: helper.etcdKeysAPI, fakeGetCap: expectedRetries, fakeGetFunc: fakeGet} + helper.etcdKeysAPI = fake + + returnedObj := &api.Pod{} + err := helper.Create(context.TODO(), "/some/key", obj, returnedObj, 0) + if err != nil { + t.Errorf("Unexpected error %#v", err) + } + + err = helper.Delete(context.TODO(), "/some/key", obj, storage.NewUIDPreconditions("A")) + if err != nil { + t.Errorf("Unexpected error %#v", err) + } + if fake.getCount != expectedRetries { + t.Errorf("Expect %d retries, got %d", expectedRetries, fake.getCount) + } + err = helper.Get(context.TODO(), "/some/key", obj, false) + if !storage.IsNotFound(err) { + t.Errorf("Expect an NotFound error, got %v", err) + } +} diff --git a/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_watcher.go b/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_watcher.go index c54e4b3ac..6d574a0cd 100644 --- a/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_watcher.go +++ b/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_watcher.go @@ -42,6 +42,7 @@ const ( EtcdSet = "set" EtcdCAS = "compareAndSwap" EtcdDelete = "delete" + EtcdCAD = "compareAndDelete" EtcdExpire = "expire" ) @@ -77,7 +78,10 @@ func exceptKey(except string) includeFunc { // etcdWatcher converts a native etcd watch to a watch.Interface. type etcdWatcher struct { - encoding runtime.Codec + encoding runtime.Codec + // Note that versioner is required for etcdWatcher to work correctly. + // There is no public constructor of it, so be careful when manipulating + // with it manually. versioner storage.Versioner transform TransformFunc @@ -108,9 +112,12 @@ type etcdWatcher struct { // watchWaitDuration is the amount of time to wait for an error from watch. const watchWaitDuration = 100 * time.Millisecond -// newEtcdWatcher returns a new etcdWatcher; if list is true, watch sub-nodes. If you provide a transform -// and a versioner, the versioner must be able to handle the objects that transform creates. -func newEtcdWatcher(list bool, quorum bool, include includeFunc, filter storage.FilterFunc, encoding runtime.Codec, versioner storage.Versioner, transform TransformFunc, cache etcdCache) *etcdWatcher { +// newEtcdWatcher returns a new etcdWatcher; if list is true, watch sub-nodes. +// The versioner must be able to handle the objects that transform creates. +func newEtcdWatcher( + list bool, quorum bool, include includeFunc, filter storage.FilterFunc, + encoding runtime.Codec, versioner storage.Versioner, transform TransformFunc, + cache etcdCache) *etcdWatcher { w := &etcdWatcher{ encoding: encoding, versioner: versioner, @@ -215,7 +222,7 @@ func etcdGetInitialWatchState(ctx context.Context, client etcd.KeysAPI, key stri if err != nil { if !etcdutil.IsEtcdNotFound(err) { utilruntime.HandleError(fmt.Errorf("watch was unable to retrieve the current index for the provided key (%q): %v", key, err)) - return resourceVersion, err + return resourceVersion, toStorageErr(err, key, 0) } if etcdError, ok := err.(etcd.Error); ok { resourceVersion = etcdError.Index @@ -310,10 +317,8 @@ func (w *etcdWatcher) decodeObject(node *etcd.Node) (runtime.Object, error) { } // ensure resource version is set on the object we load from etcd - if w.versioner != nil { - if err := w.versioner.UpdateObject(obj, node.Expiration, node.ModifiedIndex); err != nil { - utilruntime.HandleError(fmt.Errorf("failure to version api object (%d) %#v: %v", node.ModifiedIndex, obj, err)) - } + if err := w.versioner.UpdateObject(obj, node.Expiration, node.ModifiedIndex); err != nil { + utilruntime.HandleError(fmt.Errorf("failure to version api object (%d) %#v: %v", node.ModifiedIndex, obj, err)) } // perform any necessary transformation @@ -446,7 +451,7 @@ func (w *etcdWatcher) sendResult(res *etcd.Response) { w.sendAdd(res) case EtcdSet, EtcdCAS: w.sendModify(res) - case EtcdDelete, EtcdExpire: + case EtcdDelete, EtcdExpire, EtcdCAD: w.sendDelete(res) default: utilruntime.HandleError(fmt.Errorf("unknown action: %v", res.Action)) diff --git a/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_watcher_test.go b/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_watcher_test.go index 6a304ea86..423d34eeb 100644 --- a/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_watcher_test.go +++ b/vendor/k8s.io/kubernetes/pkg/storage/etcd/etcd_watcher_test.go @@ -317,7 +317,7 @@ func TestWatchEtcdState(t *testing.T) { } if e, a := endpoint, event.Object; !api.Semantic.DeepDerivative(e, a) { - t.Errorf("%s: expected %v, got %v", e, a) + t.Errorf("Unexpected error: expected %v, got %v", e, a) } } @@ -367,7 +367,7 @@ func TestWatchFromZeroIndex(t *testing.T) { } if e, a := pod, event.Object; !api.Semantic.DeepDerivative(e, a) { - t.Errorf("%s: expected %v, got %v", e, a) + t.Errorf("Unexpected error: expected %v, got %v", e, a) } } @@ -397,7 +397,7 @@ func TestWatchListFromZeroIndex(t *testing.T) { } if e, a := pod, event.Object; !api.Semantic.DeepDerivative(e, a) { - t.Errorf("%s: expected %v, got %v", e, a) + t.Errorf("Unexpected error: expected %v, got %v", e, a) } } diff --git a/vendor/k8s.io/kubernetes/pkg/storage/etcd/testing/certificates.go b/vendor/k8s.io/kubernetes/pkg/storage/etcd/testing/certificates.go new file mode 100644 index 000000000..c3fea5ffc --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/storage/etcd/testing/certificates.go @@ -0,0 +1,113 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 testing + +// You can use cfssl tool to generate certificates, please refer +// https://github.com/coreos/etcd/tree/master/hack/tls-setup for more details. +// +// ca-config.json: +// expiry was changed from 1 year to 100 years (876000h) +// ca-csr.json: +// ca expiry was set to 100 years (876000h) ("ca":{"expiry":"876000h"}) +// key was changed from ecdsa,384 to rsa,2048 +// req-csr.json: +// key was changed from ecdsa,384 to rsa,2048 +// hosts were changed to "localhost","127.0.0.1" +const CAFileContent = ` +-----BEGIN CERTIFICATE----- +MIIEUDCCAzigAwIBAgIUKfV5+qwlw3JneAPdJS7JCO8xIlYwDQYJKoZIhvcNAQEL +BQAwgawxCzAJBgNVBAYTAlVTMSowKAYDVQQKEyFIb25lc3QgQWNobWVkJ3MgVXNl +ZCBDZXJ0aWZpY2F0ZXMxKTAnBgNVBAsTIEhhc3RpbHktR2VuZXJhdGVkIFZhbHVl +cyBEaXZpc29uMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIEwpDYWxp +Zm9ybmlhMRkwFwYDVQQDExBBdXRvZ2VuZXJhdGVkIENBMCAXDTE2MDMxMjIzMTQw +MFoYDzIxMTYwMjE3MjMxNDAwWjCBrDELMAkGA1UEBhMCVVMxKjAoBgNVBAoTIUhv +bmVzdCBBY2htZWQncyBVc2VkIENlcnRpZmljYXRlczEpMCcGA1UECxMgSGFzdGls +eS1HZW5lcmF0ZWQgVmFsdWVzIERpdmlzb24xFjAUBgNVBAcTDVNhbiBGcmFuY2lz +Y28xEzARBgNVBAgTCkNhbGlmb3JuaWExGTAXBgNVBAMTEEF1dG9nZW5lcmF0ZWQg +Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDP+acpr1USrObZFu+6 +v+Bk6rYw+sWynP373cNUUiHfnZ3D7f9yJsDscV0Mo4R8DddqkxawrA5fK2Fm2Z9G +vvY5par4/JbwRIEkXmeM4e52Mqv0Yuoz62O+0jQvRawnCCJMcKuo+ijHMjmm0AF1 +JdhTpTgvUwEP9WtY9JVTkfMCnDqZiqOU5D+d4YWUtkKqgQNvbZRs6wGubhMCZe8X +m+3bK8YAsWWtoFgr7plxXk4D8MLh+PqJ3oJjfxfW5A9dHbnSEmdZ3vrYwrKgyfNf +bvHE5qQmiSZUbUaCw3mKfaEMCNesPT46nBHxhAWc5aiL1tOXzvV5Uze7A7huPoI9 +a3etAgMBAAGjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEC +MB0GA1UdDgQWBBQYc0xXQ6VNjFvIOqWfXorxx9rKRzAfBgNVHSMEGDAWgBQYc0xX +Q6VNjFvIOqWfXorxx9rKRzANBgkqhkiG9w0BAQsFAAOCAQEAaKyHDWYVjEyEKTXJ +qS9r46ehL5FZlWD2ZytBP8aHE307l9AfQ+DFWldCNaqMXLZozsresVaSzSOI6UUD +lCIQLDpPyxbpR320u8mC08+lhhwR/YRkrEqKHk56Wl4OaqoyWmguqYU9p0DiQeTU +sZsxOwG7cyEEvvs+XmZ/vBLBOr59xyjwn4seQqzwZj3VYeiKLw40iQt1yT442rcP +CfdlE9wTEONvWT+kBGMt0JlalXH3jFvlfcGQdDfRmDeTJtA+uIbvJhwJuGCNHHAc +xqC+4mAGBPN/dMPXpjayHD5dOXIKLfrNpqse6jImYlY9zduvwIHRDK/zvqTyPlNZ +uR84Nw== +-----END CERTIFICATE----- +` +const CertFileContent = ` +-----BEGIN CERTIFICATE----- +MIIELzCCAxegAwIBAgIUcjkJA3cmHeoBQggaKZmfKebFL9cwDQYJKoZIhvcNAQEL +BQAwgawxCzAJBgNVBAYTAlVTMSowKAYDVQQKEyFIb25lc3QgQWNobWVkJ3MgVXNl +ZCBDZXJ0aWZpY2F0ZXMxKTAnBgNVBAsTIEhhc3RpbHktR2VuZXJhdGVkIFZhbHVl +cyBEaXZpc29uMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIEwpDYWxp +Zm9ybmlhMRkwFwYDVQQDExBBdXRvZ2VuZXJhdGVkIENBMCAXDTE2MDMxMjIzMTQw +MFoYDzIxMTYwMjE3MjMxNDAwWjBVMRYwFAYDVQQKEw1hdXRvZ2VuZXJhdGVkMRUw +EwYDVQQLEwxldGNkIGNsdXN0ZXIxFTATBgNVBAcTDHRoZSBpbnRlcm5ldDENMAsG +A1UEAxMEZXRjZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOiW5A65 +hWGbnwceoZHM0+OexU4cPF/FpP+7BOK5i7ymSWAqfKfNuio2TB1lAErC1oX7bgTX +ieP10uz3FYWQNrlDn0I4KSA888rFPtx8GwoxH/52fGlE80BUV9PNeOVP+mYza0ih +oFj2+PhXVL/JZbx9P/2RLSNbEnq+OPk8AN82SkNtpFzanwtpb3f+kt73878KNoQu +xYZaCF1sK45Kn7mjKSDu/b3xUbTrNwnyVAGOdLzI7CCWOu+ECoZYAH4ZNHHakbyY +eWQ7U9leocEOPlqxsQAKodaCYjuAaOFIcz8/W81q+3qNw/6GbZ4znjRKQ3OtIPZ4 +JH1iNofCudWDp+0CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw +FAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFMJE +43qLCWhyZAE/wxNneSJw7aUVMB8GA1UdIwQYMBaAFBhzTFdDpU2MW8g6pZ9eivHH +2spHMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsFAAOC +AQEAuELC8tbmpyKlA4HLSDHOUquypNyiE6ftBIifJtp8bvBd+jiv4Pr8oVGxHoqq +48X7lamvDirLV5gmK0CxO+EXkIUHhULzPyYPynqsR7KZlk1PWghqsF65nwqcjS3b +tykLttD1AUDIozYvujVYBKXGxb6jcGM1rBF1XtslciFZ5qQnj6dTUujo9/xBA2ql +kOKiVXBNU8KFzq4c20RzHFLfWkbc30Q4XG4dTDVBeGupnFQRkZ0y2dSSU82QcLA/ +HgAyQSO7+csN13r84zbmDuRpUgo6eTXzJ+77G19KDkEL7XEtlw2jB2L6/o+3RGtw +JLOpEsgi7hsvOYCuTA3Krw52Mw== +-----END CERTIFICATE----- +` +const KeyFileContent = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA6JbkDrmFYZufBx6hkczT457FThw8X8Wk/7sE4rmLvKZJYCp8 +p826KjZMHWUASsLWhftuBNeJ4/XS7PcVhZA2uUOfQjgpIDzzysU+3HwbCjEf/nZ8 +aUTzQFRX08145U/6ZjNrSKGgWPb4+FdUv8llvH0//ZEtI1sSer44+TwA3zZKQ22k +XNqfC2lvd/6S3vfzvwo2hC7FhloIXWwrjkqfuaMpIO79vfFRtOs3CfJUAY50vMjs +IJY674QKhlgAfhk0cdqRvJh5ZDtT2V6hwQ4+WrGxAAqh1oJiO4Bo4UhzPz9bzWr7 +eo3D/oZtnjOeNEpDc60g9ngkfWI2h8K51YOn7QIDAQABAoIBAQCj88Fc08++x0kp +ZqEzunPebsvcTLEOPa8aiUVfYLWszHbKsAhg7Pb+zHmI+upiyMcZeOvLw/eyVlVR +rrZgCRFaNN2texMaY3zigXnXSDBzVb+cyv7V4cGqpgmnBp7i3ia/Jh3I/A2gyK8l +t8HI03nAjXWvE0gDNS5okXBt16sxq6ZWyzHHVbN3UYtCDxnyh2Ibck4b+K8I8Bn1 +mwMsSqPXJS1UQ3U5UqcaMs7WOEGx+xmaPJTWm5Lb//BkakGuBTQj+7wotyXQYG5U +uZdPPcFRk6cqgjzUeKVUtGkdmfgHSTdIwZowkKibB4rdrudsRnSwfeB+83Jp9JwG +JPrGvsbNAoGBAPULIO+vVBZXVpUEAhvNSXtmOi/hAbQhOuix8iwHbJ5EbrWaDn4B +Reb2cw/fZGgGG4jtAOXdiY8R1XGGP8+RPZ5El10ZWnNrKQfpZ27gK/5yeq5dfGBG +4JLUpcrT180FJo00rgiQYJnHCk1fWrnzXNV6K08ZZHGr6yv4S/jbq/7vAoGBAPL9 +NTN/UWXWFlSHVcb2dFHcvIiPwRj9KwhuMu90b/CilBbSJ1av13xtf2ar5zkrEtWH +CB3q3wBaklQP9MfOqEWGZeOUcd9AbYWtxHjHmP5fJA9RjErjlTtqGkusNtZJbchU +UWfT/Tl9pREpCvJ/8iawc1hx7sHHKzYwnDnMaQbjAoGAfJdd9cBltr5NjZLuJ4in +dhCyQSncnePPegUQJwbXWVleGQPtnm+zRQ3Fzyo8eQ+x7Frk+/s6N/5PUlt6EmW8 +uL4TYAjGDq1LvXQVXTCp7cPzULjDxogDI2Tvr0MrFFksEtvYKQ6Pr2CeglybWrS8 +XOazIpK8mXdaKY8jwbKfrw0CgYAFnfrb3OaZzxAnFhXSiqH3vn2RPpl9JWUYRcvh +ozRvQKLhwCvuohP+KV3XlsO6m5dM3lk+r85F6NIXJWNINyvGp6u1ThovygJ+I502 +GY8c2kAwJndyx74MaJCBDVMbMwlZpzFWkBz7dj8ZnXRGVNTZNh0Ef2XAjwUdtJP3 +9hS7dwKBgQDCzq0RIxFyy3F5baGHWLVICxmhNExQ2+Vebh+DvsPKtnz6OrWdRbGX +wgGVLrn53s6eCblnXLtKr/Li+t7fS8IkQkvu5guOvI9VeVUmZhFET3GVmUxu+JTb +iQY4uBgaf8Fgay4dkOfjvlOpFDR4E7UbJpg8/cFKTrpwgOiUVyFVdQ== +-----END RSA PRIVATE KEY----- +` diff --git a/vendor/k8s.io/kubernetes/pkg/storage/etcd/testing/utils.go b/vendor/k8s.io/kubernetes/pkg/storage/etcd/testing/utils.go index 7dc65b6de..1cfec9fd2 100644 --- a/vendor/k8s.io/kubernetes/pkg/storage/etcd/testing/utils.go +++ b/vendor/k8s.io/kubernetes/pkg/storage/etcd/testing/utils.go @@ -23,15 +23,16 @@ import ( "net/http" "net/http/httptest" "os" + "path" "testing" "time" etcd "github.com/coreos/etcd/client" "github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver/etcdhttp" + "github.com/coreos/etcd/pkg/testutil" "github.com/coreos/etcd/pkg/transport" "github.com/coreos/etcd/pkg/types" - "github.com/coreos/etcd/rafthttp" "github.com/golang/glog" "golang.org/x/net/context" ) @@ -42,6 +43,11 @@ type EtcdTestServer struct { PeerListeners, ClientListeners []net.Listener Client etcd.Client + CertificatesDir string + CertFile string + KeyFile string + CAFile string + raftHandler http.Handler s *etcdserver.EtcdServer hss []*httptest.Server @@ -56,6 +62,39 @@ func newLocalListener(t *testing.T) net.Listener { return l } +// newSecuredLocalListener opens a port localhost using any port +// with SSL enable +func newSecuredLocalListener(t *testing.T, certFile, keyFile, caFile string) net.Listener { + var l net.Listener + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + tlsInfo := transport.TLSInfo{ + CertFile: certFile, + KeyFile: keyFile, + CAFile: caFile, + } + l, err = transport.NewKeepAliveListener(l, "https", tlsInfo) + if err != nil { + t.Fatal(err) + } + return l +} + +func newHttpTransport(t *testing.T, certFile, keyFile, caFile string) etcd.CancelableTransport { + tlsInfo := transport.TLSInfo{ + CertFile: certFile, + KeyFile: keyFile, + CAFile: caFile, + } + tr, err := transport.NewTransport(tlsInfo, time.Second) + if err != nil { + t.Fatal(err) + } + return tr +} + // configureTestCluster will set the params to start an etcd server func configureTestCluster(t *testing.T, name string) *EtcdTestServer { var err error @@ -68,9 +107,26 @@ func configureTestCluster(t *testing.T, name string) *EtcdTestServer { t.Fatal(err) } - cln := newLocalListener(t) + m.CertificatesDir, err = ioutil.TempDir(os.TempDir(), "etcd_certificates") + if err != nil { + t.Fatal(err) + } + m.CertFile = path.Join(m.CertificatesDir, "etcdcert.pem") + if err = ioutil.WriteFile(m.CertFile, []byte(CertFileContent), 0644); err != nil { + t.Fatal(err) + } + m.KeyFile = path.Join(m.CertificatesDir, "etcdkey.pem") + if err = ioutil.WriteFile(m.KeyFile, []byte(KeyFileContent), 0644); err != nil { + t.Fatal(err) + } + m.CAFile = path.Join(m.CertificatesDir, "ca.pem") + if err = ioutil.WriteFile(m.CAFile, []byte(CAFileContent), 0644); err != nil { + t.Fatal(err) + } + + cln := newSecuredLocalListener(t, m.CertFile, m.KeyFile, m.CAFile) m.ClientListeners = []net.Listener{cln} - m.ClientURLs, err = types.NewURLs([]string{"http://" + cln.Addr().String()}) + m.ClientURLs, err = types.NewURLs([]string{"https://" + cln.Addr().String()}) if err != nil { t.Fatal(err) } @@ -86,10 +142,7 @@ func configureTestCluster(t *testing.T, name string) *EtcdTestServer { if err != nil { t.Fatal(err) } - m.Transport, err = transport.NewTimeoutTransport(transport.TLSInfo{}, time.Second, rafthttp.ConnReadTimeout, rafthttp.ConnWriteTimeout) - if err != nil { - t.Fatal(err) - } + m.InitialClusterToken = "TestEtcd" m.NewCluster = true m.ForceNewCluster = false m.ElectionTicks = 10 @@ -106,7 +159,7 @@ func (m *EtcdTestServer) launch(t *testing.T) error { } m.s.SyncTicker = time.Tick(500 * time.Millisecond) m.s.Start() - m.raftHandler = etcdhttp.NewPeerHandler(m.s.Cluster(), m.s.RaftHandler()) + m.raftHandler = &testutil.PauseableHandler{Next: etcdhttp.NewPeerHandler(m.s)} for _, ln := range m.PeerListeners { hs := &httptest.Server{ Listener: ln, @@ -161,6 +214,9 @@ func (m *EtcdTestServer) Terminate(t *testing.T) { if err := os.RemoveAll(m.ServerConfig.DataDir); err != nil { t.Fatal(err) } + if err := os.RemoveAll(m.CertificatesDir); err != nil { + t.Fatal(err) + } } // NewEtcdTestClientServer creates a new client and server for testing @@ -168,11 +224,12 @@ func NewEtcdTestClientServer(t *testing.T) *EtcdTestServer { server := configureTestCluster(t, "foo") err := server.launch(t) if err != nil { - t.Fatal("Failed to start etcd server error=%v", err) + t.Fatalf("Failed to start etcd server error=%v", err) return nil } cfg := etcd.Config{ Endpoints: server.ClientURLs.StringSlice(), + Transport: newHttpTransport(t, server.CertFile, server.KeyFile, server.CAFile), } server.Client, err = etcd.New(cfg) if err != nil { diff --git a/vendor/k8s.io/kubernetes/pkg/storage/etcd3/store.go b/vendor/k8s.io/kubernetes/pkg/storage/etcd3/store.go new file mode 100644 index 000000000..5ccf5e578 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/storage/etcd3/store.go @@ -0,0 +1,424 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 etcd3 + +import ( + "bytes" + "errors" + "fmt" + "path" + "reflect" + "strings" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/conversion" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" + "k8s.io/kubernetes/pkg/storage/etcd" + "k8s.io/kubernetes/pkg/watch" + + "github.com/coreos/etcd/clientv3" + "github.com/golang/glog" + "golang.org/x/net/context" +) + +type store struct { + client *clientv3.Client + codec runtime.Codec + versioner storage.Versioner + pathPrefix string +} + +type elemForDecode struct { + data []byte + rev uint64 +} + +type objState struct { + obj runtime.Object + meta *storage.ResponseMeta + rev int64 + data []byte +} + +func newStore(c *clientv3.Client, codec runtime.Codec, prefix string) *store { + return &store{ + client: c, + versioner: etcd.APIObjectVersioner{}, + codec: codec, + pathPrefix: prefix, + } +} + +// Backends implements storage.Interface.Backends. +func (s *store) Backends(ctx context.Context) []string { + resp, err := s.client.MemberList(ctx) + if err != nil { + glog.Errorf("Error obtaining etcd members list: %q", err) + return nil + } + var mlist []string + for _, member := range resp.Members { + mlist = append(mlist, member.ClientURLs...) + } + return mlist +} + +// Codec implements storage.Interface.Codec. +func (s *store) Codec() runtime.Codec { + return s.codec +} + +// Versioner implements storage.Interface.Versioner. +func (s *store) Versioner() storage.Versioner { + return s.versioner +} + +// Get implements storage.Interface.Get. +func (s *store) Get(ctx context.Context, key string, out runtime.Object, ignoreNotFound bool) error { + key = keyWithPrefix(s.pathPrefix, key) + getResp, err := s.client.KV.Get(ctx, key) + if err != nil { + return err + } + + if len(getResp.Kvs) == 0 { + if ignoreNotFound { + return runtime.SetZeroValue(out) + } + return storage.NewKeyNotFoundError(key, 0) + } + kv := getResp.Kvs[0] + return decode(s.codec, s.versioner, kv.Value, out, kv.ModRevision) +} + +// Create implements storage.Interface.Create. +func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error { + if version, err := s.versioner.ObjectResourceVersion(obj); err == nil && version != 0 { + return errors.New("resourceVersion should not be set on objects to be created") + } + data, err := runtime.Encode(s.codec, obj) + if err != nil { + return err + } + key = keyWithPrefix(s.pathPrefix, key) + + txnResp, err := s.client.KV.Txn(ctx).If( + notFound(key), + ).Then( + clientv3.OpPut(key, string(data)), + ).Commit() + if err != nil { + return err + } + if !txnResp.Succeeded { + return storage.NewKeyExistsError(key, 0) + } + + if out != nil { + putResp := txnResp.Responses[0].GetResponsePut() + return decode(s.codec, s.versioner, data, out, putResp.Header.Revision) + } + return nil +} + +// Delete implements storage.Interface.Delete. +func (s *store) Delete(ctx context.Context, key string, out runtime.Object, precondtions *storage.Preconditions) error { + v, err := conversion.EnforcePtr(out) + if err != nil { + panic("unable to convert output object to pointer") + } + key = keyWithPrefix(s.pathPrefix, key) + if precondtions == nil { + return s.unconditionalDelete(ctx, key, out) + } + return s.conditionalDelete(ctx, key, out, v, precondtions) +} + +func (s *store) unconditionalDelete(ctx context.Context, key string, out runtime.Object) error { + // We need to do get and delete in single transaction in order to + // know the value and revision before deleting it. + txnResp, err := s.client.KV.Txn(ctx).If().Then( + clientv3.OpGet(key), + clientv3.OpDelete(key), + ).Commit() + if err != nil { + return err + } + getResp := txnResp.Responses[0].GetResponseRange() + if len(getResp.Kvs) == 0 { + return storage.NewKeyNotFoundError(key, 0) + } + + kv := getResp.Kvs[0] + return decode(s.codec, s.versioner, kv.Value, out, kv.ModRevision) +} + +func (s *store) conditionalDelete(ctx context.Context, key string, out runtime.Object, v reflect.Value, precondtions *storage.Preconditions) error { + getResp, err := s.client.KV.Get(ctx, key) + if err != nil { + return err + } + for { + origState, err := s.getState(getResp, key, v, false) + if err != nil { + return err + } + if err := checkPreconditions(key, precondtions, origState.obj); err != nil { + return err + } + txnResp, err := s.client.KV.Txn(ctx).If( + clientv3.Compare(clientv3.ModifiedRevision(key), "=", origState.rev), + ).Then( + clientv3.OpDelete(key), + ).Else( + clientv3.OpGet(key), + ).Commit() + if err != nil { + return err + } + if !txnResp.Succeeded { + getResp = (*clientv3.GetResponse)(txnResp.Responses[0].GetResponseRange()) + glog.V(4).Infof("deletion of %s failed because of a conflict, going to retry", key) + continue + } + return decode(s.codec, s.versioner, origState.data, out, origState.rev) + } +} + +// GuaranteedUpdate implements storage.Interface.GuaranteedUpdate. +func (s *store) GuaranteedUpdate(ctx context.Context, key string, out runtime.Object, ignoreNotFound bool, precondtions *storage.Preconditions, tryUpdate storage.UpdateFunc) error { + v, err := conversion.EnforcePtr(out) + if err != nil { + panic("unable to convert output object to pointer") + } + key = keyWithPrefix(s.pathPrefix, key) + getResp, err := s.client.KV.Get(ctx, key) + if err != nil { + return err + } + for { + origState, err := s.getState(getResp, key, v, ignoreNotFound) + if err != nil { + return err + } + + if err := checkPreconditions(key, precondtions, origState.obj); err != nil { + return err + } + + ret, err := s.updateState(origState, tryUpdate) + if err != nil { + return err + } + + data, err := runtime.Encode(s.codec, ret) + if err != nil { + return err + } + if bytes.Equal(data, origState.data) { + return decode(s.codec, s.versioner, origState.data, out, origState.rev) + } + + txnResp, err := s.client.KV.Txn(ctx).If( + clientv3.Compare(clientv3.ModifiedRevision(key), "=", origState.rev), + ).Then( + clientv3.OpPut(key, string(data)), + ).Else( + clientv3.OpGet(key), + ).Commit() + if err != nil { + return err + } + if !txnResp.Succeeded { + getResp = (*clientv3.GetResponse)(txnResp.Responses[0].GetResponseRange()) + glog.V(4).Infof("GuaranteedUpdate of %s failed because of a conflict, going to retry", key) + continue + } + putResp := txnResp.Responses[0].GetResponsePut() + return decode(s.codec, s.versioner, data, out, putResp.Header.Revision) + } +} + +// GetToList implements storage.Interface.GetToList. +func (s *store) GetToList(ctx context.Context, key string, filter storage.FilterFunc, listObj runtime.Object) error { + listPtr, err := meta.GetItemsPtr(listObj) + if err != nil { + return err + } + key = keyWithPrefix(s.pathPrefix, key) + + getResp, err := s.client.KV.Get(ctx, key) + if err != nil { + return err + } + if len(getResp.Kvs) == 0 { + return nil + } + elems := []*elemForDecode{{ + data: getResp.Kvs[0].Value, + rev: uint64(getResp.Kvs[0].ModRevision), + }} + if err := decodeList(elems, filter, listPtr, s.codec, s.versioner); err != nil { + return err + } + // update version with cluster level revision + return s.versioner.UpdateList(listObj, uint64(getResp.Header.Revision)) +} + +// List implements storage.Interface.List. +func (s *store) List(ctx context.Context, key, resourceVersion string, filter storage.FilterFunc, listObj runtime.Object) error { + listPtr, err := meta.GetItemsPtr(listObj) + if err != nil { + return err + } + key = keyWithPrefix(s.pathPrefix, key) + // We need to make sure the key ended with "/" so that we only get children "directories". + // e.g. if we have key "/a", "/a/b", "/ab", getting keys with prefix "/a" will return all three, + // while with prefix "/a/" will return only "/a/b" which is the correct answer. + if !strings.HasSuffix(key, "/") { + key += "/" + } + getResp, err := s.client.KV.Get(ctx, key, clientv3.WithPrefix()) + if err != nil { + return err + } + + elems := make([]*elemForDecode, len(getResp.Kvs)) + for i, kv := range getResp.Kvs { + elems[i] = &elemForDecode{ + data: kv.Value, + rev: uint64(kv.ModRevision), + } + } + if err := decodeList(elems, filter, listPtr, s.codec, s.versioner); err != nil { + return err + } + // update version with cluster level revision + return s.versioner.UpdateList(listObj, uint64(getResp.Header.Revision)) +} + +// Watch implements storage.Interface.Watch. +func (s *store) Watch(ctx context.Context, key string, resourceVersion string, filter storage.FilterFunc) (watch.Interface, error) { + panic("TODO: unimplemented") +} + +// WatchList implements storage.Interface.WatchList. +func (s *store) WatchList(ctx context.Context, key string, resourceVersion string, filter storage.FilterFunc) (watch.Interface, error) { + panic("TODO: unimplemented") +} + +func (s *store) getState(getResp *clientv3.GetResponse, key string, v reflect.Value, ignoreNotFound bool) (*objState, error) { + state := &objState{ + obj: reflect.New(v.Type()).Interface().(runtime.Object), + meta: &storage.ResponseMeta{}, + } + if len(getResp.Kvs) == 0 { + if !ignoreNotFound { + return nil, storage.NewKeyNotFoundError(key, 0) + } + if err := runtime.SetZeroValue(state.obj); err != nil { + return nil, err + } + } else { + state.rev = getResp.Kvs[0].ModRevision + state.meta.ResourceVersion = uint64(state.rev) + state.data = getResp.Kvs[0].Value + if err := decode(s.codec, s.versioner, state.data, state.obj, state.rev); err != nil { + return nil, err + } + } + return state, nil +} + +func (s *store) updateState(st *objState, userUpdate storage.UpdateFunc) (runtime.Object, error) { + ret, _, err := userUpdate(st.obj, *st.meta) + version, err := s.versioner.ObjectResourceVersion(ret) + if err != nil { + return nil, err + } + if version != 0 { + // We cannot store object with resourceVersion in etcd. We need to reset it. + if err := s.versioner.UpdateObject(ret, nil, 0); err != nil { + return nil, fmt.Errorf("UpdateObject failed: %v", err) + } + } + return ret, nil +} + +func keyWithPrefix(prefix, key string) string { + if strings.HasPrefix(key, prefix) { + return key + } + return path.Join(prefix, key) +} + +// decode decodes value of bytes into object. It will also set the object resource version to rev. +// On success, objPtr would be set to the object. +func decode(codec runtime.Codec, versioner storage.Versioner, value []byte, objPtr runtime.Object, rev int64) error { + if _, err := conversion.EnforcePtr(objPtr); err != nil { + panic("unable to convert output object to pointer") + } + _, _, err := codec.Decode(value, nil, objPtr) + if err != nil { + return err + } + // being unable to set the version does not prevent the object from being extracted + versioner.UpdateObject(objPtr, nil, uint64(rev)) + return nil +} + +// decodeList decodes a list of values into a list of objects, with resource version set to corresponding rev. +// On success, ListPtr would be set to the list of objects. +func decodeList(elems []*elemForDecode, filter storage.FilterFunc, ListPtr interface{}, codec runtime.Codec, versioner storage.Versioner) error { + v, err := conversion.EnforcePtr(ListPtr) + if err != nil || v.Kind() != reflect.Slice { + panic("need ptr to slice") + } + for _, elem := range elems { + obj, _, err := codec.Decode(elem.data, nil, reflect.New(v.Type().Elem()).Interface().(runtime.Object)) + if err != nil { + return err + } + // being unable to set the version does not prevent the object from being extracted + versioner.UpdateObject(obj, nil, elem.rev) + if filter(obj) { + v.Set(reflect.Append(v, reflect.ValueOf(obj).Elem())) + } + } + return nil +} + +func checkPreconditions(key string, preconditions *storage.Preconditions, out runtime.Object) error { + if preconditions == nil { + return nil + } + objMeta, err := api.ObjectMetaFor(out) + if err != nil { + return storage.NewInternalErrorf("can't enforce preconditions %v on un-introspectable object %v, got error: %v", *preconditions, out, err) + } + if preconditions.UID != nil && *preconditions.UID != objMeta.UID { + errMsg := fmt.Sprintf("Precondition failed: UID in precondition: %v, UID in object meta: %v", preconditions.UID, objMeta.UID) + return storage.NewInvalidObjError(key, errMsg) + } + return nil +} + +func notFound(key string) clientv3.Cmp { + return clientv3.Compare(clientv3.ModifiedRevision(key), "=", 0) +} diff --git a/vendor/k8s.io/kubernetes/pkg/storage/etcd3/store_test.go b/vendor/k8s.io/kubernetes/pkg/storage/etcd3/store_test.go new file mode 100644 index 000000000..917a64af8 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/storage/etcd3/store_test.go @@ -0,0 +1,510 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 etcd3 + +import ( + "fmt" + "reflect" + "sync" + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" + + "github.com/coreos/etcd/integration" + "golang.org/x/net/context" +) + +func TestCreate(t *testing.T) { + ctx, store, cluster := testSetup(t) + defer cluster.Terminate(t) + etcdClient := cluster.RandClient() + + key := "/testkey" + out := &api.Pod{} + obj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}} + + // verify that kv pair is empty before set + getResp, err := etcdClient.KV.Get(ctx, key) + if err != nil { + t.Fatalf("etcdClient.KV.Get failed: %v", err) + } + if len(getResp.Kvs) != 0 { + t.Fatalf("expecting empty result on key: %s", key) + } + + err = store.Create(ctx, key, obj, out, 0) + if err != nil { + t.Fatalf("Set failed: %v", err) + } + // basic tests of the output + if obj.ObjectMeta.Name != out.ObjectMeta.Name { + t.Errorf("pod name want=%s, get=%s", obj.ObjectMeta.Name, out.ObjectMeta.Name) + } + if out.ResourceVersion == "" { + t.Errorf("output should have non-empty resource version") + } + + // verify that kv pair is not empty after set + getResp, err = etcdClient.KV.Get(ctx, key) + if err != nil { + t.Fatalf("etcdClient.KV.Get failed: %v", err) + } + if len(getResp.Kvs) == 0 { + t.Fatalf("expecting non empty result on key: %s", key) + } +} + +func TestCreateWithKeyExist(t *testing.T) { + ctx, store, cluster := testSetup(t) + defer cluster.Terminate(t) + obj := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}} + key, _ := testPropogateStore(t, store, ctx, obj) + out := &api.Pod{} + err := store.Create(ctx, key, obj, out, 0) + if err == nil || !storage.IsNodeExist(err) { + t.Errorf("expecting key exists error, but get: %s", err) + } +} + +func TestGet(t *testing.T) { + ctx, store, cluster := testSetup(t) + defer cluster.Terminate(t) + key, storedObj := testPropogateStore(t, store, ctx, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}) + + tests := []struct { + key string + ignoreNotFound bool + expectNotFoundErr bool + expectedOut *api.Pod + }{{ // test get on existing item + key: key, + ignoreNotFound: false, + expectNotFoundErr: false, + expectedOut: storedObj, + }, { // test get on non-existing item with ignoreNotFound=false + key: "/non-existing", + ignoreNotFound: false, + expectNotFoundErr: true, + }, { // test get on non-existing item with ignoreNotFound=true + key: "/non-existing", + ignoreNotFound: true, + expectNotFoundErr: false, + expectedOut: &api.Pod{}, + }} + + for i, tt := range tests { + out := &api.Pod{} + err := store.Get(ctx, tt.key, out, tt.ignoreNotFound) + if tt.expectNotFoundErr { + if err == nil || !storage.IsNotFound(err) { + t.Errorf("#%d: expecting not found error, but get: %s", i, err) + } + continue + } + if err != nil { + t.Fatalf("Get failed: %v", err) + } + if !reflect.DeepEqual(tt.expectedOut, out) { + t.Errorf("#%d: pod want=%#v, get=%#v", i, tt.expectedOut, out) + } + } +} + +func TestUnconditionalDelete(t *testing.T) { + ctx, store, cluster := testSetup(t) + defer cluster.Terminate(t) + key, storedObj := testPropogateStore(t, store, ctx, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}) + + tests := []struct { + key string + expectedObj *api.Pod + expectNotFoundErr bool + }{{ // test unconditional delete on existing key + key: key, + expectedObj: storedObj, + expectNotFoundErr: false, + }, { // test unconditional delete on non-existing key + key: "/non-existing", + expectedObj: nil, + expectNotFoundErr: true, + }} + + for i, tt := range tests { + out := &api.Pod{} // reset + err := store.Delete(ctx, tt.key, out, nil) + if tt.expectNotFoundErr { + if err == nil || !storage.IsNotFound(err) { + t.Errorf("#%d: expecting not found error, but get: %s", i, err) + } + continue + } + if err != nil { + t.Fatalf("Delete failed: %v", err) + } + if !reflect.DeepEqual(tt.expectedObj, out) { + t.Errorf("#%d: pod want=%#v, get=%#v", i, tt.expectedObj, out) + } + } +} + +func TestConditionalDelete(t *testing.T) { + ctx, store, cluster := testSetup(t) + defer cluster.Terminate(t) + key, storedObj := testPropogateStore(t, store, ctx, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", UID: "A"}}) + + tests := []struct { + precondition *storage.Preconditions + expectInvalidObjErr bool + }{{ // test conditional delete with UID match + precondition: storage.NewUIDPreconditions("A"), + expectInvalidObjErr: false, + }, { // test conditional delete with UID mismatch + precondition: storage.NewUIDPreconditions("B"), + expectInvalidObjErr: true, + }} + + for i, tt := range tests { + out := &api.Pod{} + err := store.Delete(ctx, key, out, tt.precondition) + if tt.expectInvalidObjErr { + if err == nil || !storage.IsInvalidObj(err) { + t.Errorf("#%d: expecting invalid UID error, but get: %s", i, err) + } + continue + } + if err != nil { + t.Fatalf("Delete failed: %v", err) + } + if !reflect.DeepEqual(storedObj, out) { + t.Errorf("#%d: pod want=%#v, get=%#v", i, storedObj, out) + } + key, storedObj = testPropogateStore(t, store, ctx, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", UID: "A"}}) + } +} + +func TestGetToList(t *testing.T) { + ctx, store, cluster := testSetup(t) + defer cluster.Terminate(t) + key, storedObj := testPropogateStore(t, store, ctx, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}) + + tests := []struct { + key string + filter storage.FilterFunc + expectedOut []*api.Pod + }{{ // test GetToList on existing key + key: key, + filter: storage.Everything, + expectedOut: []*api.Pod{storedObj}, + }, { // test GetToList on non-existing key + key: "/non-existing", + filter: storage.Everything, + expectedOut: nil, + }, { // test GetToList with filter to reject the pod + key: "/non-existing", + filter: func(obj runtime.Object) bool { + pod, ok := obj.(*api.Pod) + if !ok { + t.Fatal("It should be able to convert obj to *api.Pod") + } + return pod.Name != storedObj.Name + }, + expectedOut: nil, + }} + + for i, tt := range tests { + out := &api.PodList{} + err := store.GetToList(ctx, tt.key, tt.filter, out) + if err != nil { + t.Fatalf("GetToList failed: %v", err) + } + if len(out.Items) != len(tt.expectedOut) { + t.Errorf("#%d: length of list want=%d, get=%d", i, len(tt.expectedOut), len(out.Items)) + continue + } + for j, wantPod := range tt.expectedOut { + getPod := &out.Items[j] + if !reflect.DeepEqual(wantPod, getPod) { + t.Errorf("#%d: pod want=%#v, get=%#v", i, wantPod, getPod) + } + } + } +} + +func TestGuaranteedUpdate(t *testing.T) { + ctx, store, cluster := testSetup(t) + defer cluster.Terminate(t) + key, storeObj := testPropogateStore(t, store, ctx, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", UID: "A"}}) + + tests := []struct { + key string + name string + ignoreNotFound bool + precondition *storage.Preconditions + expectNotFoundErr bool + expectInvalidObjErr bool + expectNoUpdate bool + }{{ // GuaranteedUpdate on non-existing key with ignoreNotFound=false + key: "/non-existing", + ignoreNotFound: false, + precondition: nil, + expectNotFoundErr: true, + expectInvalidObjErr: false, + expectNoUpdate: false, + }, { // GuaranteedUpdate on non-existing key with ignoreNotFound=true + key: "/non-existing", + ignoreNotFound: true, + precondition: nil, + expectNotFoundErr: false, + expectInvalidObjErr: false, + expectNoUpdate: false, + }, { // GuaranteedUpdate on existing key + key: key, + ignoreNotFound: false, + precondition: nil, + expectNotFoundErr: false, + expectInvalidObjErr: false, + expectNoUpdate: false, + }, { // GuaranteedUpdate with same data + key: key, + ignoreNotFound: false, + precondition: nil, + expectNotFoundErr: false, + expectInvalidObjErr: false, + expectNoUpdate: true, + }, { // GuaranteedUpdate with UID match + key: key, + ignoreNotFound: false, + precondition: storage.NewUIDPreconditions("A"), + expectNotFoundErr: false, + expectInvalidObjErr: false, + expectNoUpdate: true, + }, { // GuaranteedUpdate with UID mismatch + key: key, + ignoreNotFound: false, + precondition: storage.NewUIDPreconditions("B"), + expectNotFoundErr: false, + expectInvalidObjErr: true, + expectNoUpdate: true, + }} + + for i, tt := range tests { + out := &api.Pod{} + name := fmt.Sprintf("foo-%d", i) + if tt.expectNoUpdate { + name = storeObj.Name + } + version := storeObj.ResourceVersion + err := store.GuaranteedUpdate(ctx, tt.key, out, tt.ignoreNotFound, tt.precondition, + storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { + if tt.expectNotFoundErr && tt.ignoreNotFound { + if pod := obj.(*api.Pod); pod.Name != "" { + t.Errorf("#%d: expecting zero value, but get=%#v", i, pod) + } + } + pod := *storeObj + pod.Name = name + return &pod, nil + })) + + if tt.expectNotFoundErr { + if err == nil || !storage.IsNotFound(err) { + t.Errorf("#%d: expecting not found error, but get: %v", i, err) + } + continue + } + if tt.expectInvalidObjErr { + if err == nil || !storage.IsInvalidObj(err) { + t.Errorf("#%d: expecting invalid UID error, but get: %s", i, err) + } + continue + } + if err != nil { + t.Fatalf("GuaranteedUpdate failed: %v", err) + } + if out.ObjectMeta.Name != name { + t.Errorf("#%d: pod name want=%s, get=%s", i, name, out.ObjectMeta.Name) + } + switch tt.expectNoUpdate { + case true: + if version != out.ResourceVersion { + t.Errorf("#%d: expect no version change, before=%s, after=%s", i, version, out.ResourceVersion) + } + case false: + if version == out.ResourceVersion { + t.Errorf("#%d: expect version change, but get the same version=%s", i, version) + } + } + storeObj = out + } +} + +func TestGuaranteedUpdateWithConflict(t *testing.T) { + ctx, store, cluster := testSetup(t) + defer cluster.Terminate(t) + key, _ := testPropogateStore(t, store, ctx, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}) + + errChan := make(chan error, 1) + var firstToFinish sync.WaitGroup + var secondToEnter sync.WaitGroup + firstToFinish.Add(1) + secondToEnter.Add(1) + + go func() { + err := store.GuaranteedUpdate(ctx, key, &api.Pod{}, false, nil, + storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { + pod := obj.(*api.Pod) + pod.Name = "foo-1" + secondToEnter.Wait() + return pod, nil + })) + firstToFinish.Done() + errChan <- err + }() + + updateCount := 0 + err := store.GuaranteedUpdate(ctx, key, &api.Pod{}, false, nil, + storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) { + if updateCount == 0 { + secondToEnter.Done() + firstToFinish.Wait() + } + updateCount++ + pod := obj.(*api.Pod) + pod.Name = "foo-2" + return pod, nil + })) + if err != nil { + t.Fatalf("Second GuaranteedUpdate error %#v", err) + } + if err := <-errChan; err != nil { + t.Fatalf("First GuaranteedUpdate error %#v", err) + } + + if updateCount != 2 { + t.Errorf("Should have conflict and called update func twice") + } +} + +func TestList(t *testing.T) { + cluster := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1}) + defer cluster.Terminate(t) + store := newStore(cluster.RandClient(), testapi.Default.Codec(), "") + ctx := context.Background() + + // Setup storage with the following structure: + // / + // - one-level/ + // | - test + // | + // - two-level/ + // - 1/ + // | - test + // | + // - 2/ + // - test + preset := []struct { + key string + obj *api.Pod + storedObj *api.Pod + }{{ + key: "/one-level/test", + obj: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, + }, { + key: "/two-level/1/test", + obj: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, + }, { + key: "/two-level/2/test", + obj: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "bar"}}, + }} + + for i, ps := range preset { + preset[i].storedObj = &api.Pod{} + err := store.Create(ctx, ps.key, ps.obj, preset[i].storedObj, 0) + if err != nil { + t.Fatalf("Set failed: %v", err) + } + } + + tests := []struct { + prefix string + filter storage.FilterFunc + expectedOut []*api.Pod + }{{ // test List on existing key + prefix: "/one-level/", + filter: storage.Everything, + expectedOut: []*api.Pod{preset[0].storedObj}, + }, { // test List on non-existing key + prefix: "/non-existing/", + filter: storage.Everything, + expectedOut: nil, + }, { // test List with filter + prefix: "/one-level/", + filter: func(obj runtime.Object) bool { + pod, ok := obj.(*api.Pod) + if !ok { + t.Fatal("It should be able to convert obj to *api.Pod") + } + return pod.Name != preset[0].storedObj.Name + }, + expectedOut: nil, + }, { // test List with multiple levels of directories and expect flattened result + prefix: "/two-level/", + filter: storage.Everything, + expectedOut: []*api.Pod{preset[1].storedObj, preset[2].storedObj}, + }} + + for i, tt := range tests { + out := &api.PodList{} + err := store.List(ctx, tt.prefix, "0", tt.filter, out) + if err != nil { + t.Fatalf("List failed: %v", err) + } + if len(tt.expectedOut) != len(out.Items) { + t.Errorf("#%d: length of list want=%d, get=%d", i, len(tt.expectedOut), len(out.Items)) + continue + } + for j, wantPod := range tt.expectedOut { + getPod := &out.Items[j] + if !reflect.DeepEqual(wantPod, getPod) { + t.Errorf("#%d: pod want=%#v, get=%#v", i, wantPod, getPod) + } + } + } +} + +func testSetup(t *testing.T) (context.Context, *store, *integration.ClusterV3) { + cluster := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1}) + store := newStore(cluster.RandClient(), testapi.Default.Codec(), "") + ctx := context.Background() + return ctx, store, cluster +} + +// testPropogateStore helps propogates store with objects, automates key generation, and returns +// keys and stored objects. +func testPropogateStore(t *testing.T, store *store, ctx context.Context, obj *api.Pod) (string, *api.Pod) { + // Setup store with a key and grab the output for returning. + key := "/testkey" + setOutput := &api.Pod{} + err := store.Create(ctx, key, obj, setOutput, 0) + if err != nil { + t.Fatalf("Set failed: %v", err) + } + return key, setOutput +} diff --git a/vendor/k8s.io/kubernetes/pkg/storage/interfaces.go b/vendor/k8s.io/kubernetes/pkg/storage/interfaces.go index 7c5b15395..4a76e4052 100644 --- a/vendor/k8s.io/kubernetes/pkg/storage/interfaces.go +++ b/vendor/k8s.io/kubernetes/pkg/storage/interfaces.go @@ -21,6 +21,7 @@ import ( "golang.org/x/net/context" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/watch" ) @@ -69,6 +70,18 @@ func Everything(runtime.Object) bool { // See the comment for GuaranteedUpdate for more details. type UpdateFunc func(input runtime.Object, res ResponseMeta) (output runtime.Object, ttl *uint64, err error) +// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. +type Preconditions struct { + // Specifies the target UID. + UID *types.UID `json:"uid,omitempty"` +} + +// NewUIDPreconditions returns a Preconditions with UID set. +func NewUIDPreconditions(uid string) *Preconditions { + u := types.UID(uid) + return &Preconditions{UID: &u} +} + // Interface offers a common interface for object marshaling/unmarshling operations and // hides all the storage-related operations behind it. type Interface interface { @@ -91,7 +104,8 @@ type Interface interface { Set(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error // Delete removes the specified key and returns the value that existed at that spot. - Delete(ctx context.Context, key string, out runtime.Object) error + // If key didn't exist, it will return NotFound storage error. + Delete(ctx context.Context, key string, out runtime.Object, preconditions *Preconditions) error // Watch begins watching the specified key. Events are decoded into API objects, // and any items passing 'filter' are sent down to returned watch.Interface. @@ -124,9 +138,13 @@ type Interface interface { // GuaranteedUpdate keeps calling 'tryUpdate()' to update key 'key' (of type 'ptrToType') // retrying the update until success if there is index conflict. - // Note that object passed to tryUpdate may change acress incovations of tryUpdate() if - // other writers are simultaneously updateing it, to tryUpdate() needs to take into account + // Note that object passed to tryUpdate may change across invocations of tryUpdate() if + // other writers are simultaneously updating it, to tryUpdate() needs to take into account // the current contents of the object when deciding how the update object should look. + // If the key doesn't exist, it will return NotFound storage error if ignoreNotFound=false + // or zero value in 'ptrToType' parameter otherwise. + // If the object to update has the same value as previous, it won't do any update + // but will return the object in 'ptrToType' parameter. // // Example: // @@ -146,7 +164,7 @@ type Interface interface { // return cur, nil, nil // } // }) - GuaranteedUpdate(ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, tryUpdate UpdateFunc) error + GuaranteedUpdate(ctx context.Context, key string, ptrToType runtime.Object, ignoreNotFound bool, precondtions *Preconditions, tryUpdate UpdateFunc) error // Codec provides access to the underlying codec being used by the implementation. Codec() runtime.Codec diff --git a/vendor/k8s.io/kubernetes/pkg/storage/util.go b/vendor/k8s.io/kubernetes/pkg/storage/util.go index 43056c3da..c8f3b02f1 100644 --- a/vendor/k8s.io/kubernetes/pkg/storage/util.go +++ b/vendor/k8s.io/kubernetes/pkg/storage/util.go @@ -20,9 +20,7 @@ import ( "fmt" "strconv" - "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" - "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/validation/field" @@ -48,7 +46,7 @@ func ParseWatchResourceVersion(resourceVersion string) (uint64, error) { } version, err := strconv.ParseUint(resourceVersion, 10, 64) if err != nil { - return 0, errors.NewInvalid(unversioned.GroupKind{}, "", field.ErrorList{ + return 0, NewInvalidError(field.ErrorList{ // Validation errors are supposed to return version-specific field // paths, but this is probably close enough. field.Invalid(field.NewPath("resourceVersion"), resourceVersion, err.Error()), diff --git a/vendor/k8s.io/kubernetes/pkg/storage/util_test.go b/vendor/k8s.io/kubernetes/pkg/storage/util_test.go index d24ae5d5f..7d0675cce 100644 --- a/vendor/k8s.io/kubernetes/pkg/storage/util_test.go +++ b/vendor/k8s.io/kubernetes/pkg/storage/util_test.go @@ -16,11 +16,7 @@ limitations under the License. package storage -import ( - "testing" - - "k8s.io/kubernetes/pkg/api/errors" -) +import "testing" func TestEtcdParseWatchResourceVersion(t *testing.T) { testCases := []struct { @@ -42,7 +38,7 @@ func TestEtcdParseWatchResourceVersion(t *testing.T) { t.Errorf("%s: unexpected non-error", testCase.Version) continue } - if !errors.IsInvalid(err) { + if !IsInvalidError(err) { t.Errorf("%s: unexpected error: %v", testCase.Version, err) continue } diff --git a/vendor/k8s.io/kubernetes/pkg/storage/watch_cache.go b/vendor/k8s.io/kubernetes/pkg/storage/watch_cache.go index 2e842a28e..87bce0c0e 100644 --- a/vendor/k8s.io/kubernetes/pkg/storage/watch_cache.go +++ b/vendor/k8s.io/kubernetes/pkg/storage/watch_cache.go @@ -302,14 +302,13 @@ func (w *watchCache) GetAllEventsSinceThreadUnsafe(resourceVersion uint64) ([]wa } return result, nil } - if resourceVersion < oldest { - return nil, errors.NewGone(fmt.Sprintf("too old resource version: %d (%d)", resourceVersion, oldest)) + if resourceVersion < oldest-1 { + return nil, errors.NewGone(fmt.Sprintf("too old resource version: %d (%d)", resourceVersion, oldest-1)) } - // Binary search the smallest index at which resourceVersion is not smaller than - // the given one. + // Binary search the smallest index at which resourceVersion is greater than the given one. f := func(i int) bool { - return w.cache[(w.startIndex+i)%w.capacity].resourceVersion >= resourceVersion + return w.cache[(w.startIndex+i)%w.capacity].resourceVersion > resourceVersion } first := sort.Search(size, f) result := make([]watchCacheEvent, size-first) diff --git a/vendor/k8s.io/kubernetes/pkg/storage/watch_cache_test.go b/vendor/k8s.io/kubernetes/pkg/storage/watch_cache_test.go index e2268fe95..8d9327e3c 100644 --- a/vendor/k8s.io/kubernetes/pkg/storage/watch_cache_test.go +++ b/vendor/k8s.io/kubernetes/pkg/storage/watch_cache_test.go @@ -122,7 +122,7 @@ func TestWatchCacheBasic(t *testing.T) { func TestEvents(t *testing.T) { store := newTestWatchCache(5) - store.Add(makeTestPod("pod", 2)) + store.Add(makeTestPod("pod", 3)) // Test for Added event. { @@ -145,7 +145,7 @@ func TestEvents(t *testing.T) { if result[0].Type != watch.Added { t.Errorf("unexpected event type: %v", result[0].Type) } - pod := makeTestPod("pod", uint64(2)) + pod := makeTestPod("pod", uint64(3)) if !api.Semantic.DeepEqual(pod, result[0].Object) { t.Errorf("unexpected item: %v, expected: %v", result[0].Object, pod) } @@ -154,8 +154,8 @@ func TestEvents(t *testing.T) { } } - store.Update(makeTestPod("pod", 3)) store.Update(makeTestPod("pod", 4)) + store.Update(makeTestPod("pod", 5)) // Test with not full cache. { @@ -176,22 +176,22 @@ func TestEvents(t *testing.T) { if result[i].Type != watch.Modified { t.Errorf("unexpected event type: %v", result[i].Type) } - pod := makeTestPod("pod", uint64(i+3)) + pod := makeTestPod("pod", uint64(i+4)) if !api.Semantic.DeepEqual(pod, result[i].Object) { t.Errorf("unexpected item: %v, expected: %v", result[i].Object, pod) } - prevPod := makeTestPod("pod", uint64(i+2)) + prevPod := makeTestPod("pod", uint64(i+3)) if !api.Semantic.DeepEqual(prevPod, result[i].PrevObject) { t.Errorf("unexpected item: %v, expected: %v", result[i].PrevObject, prevPod) } } } - for i := 5; i < 9; i++ { + for i := 6; i < 10; i++ { store.Update(makeTestPod("pod", uint64(i))) } - // Test with full cache - there should be elements from 4 to 8. + // Test with full cache - there should be elements from 5 to 9. { _, err := store.GetAllEventsSince(3) if err == nil { @@ -207,7 +207,7 @@ func TestEvents(t *testing.T) { t.Fatalf("unexpected events: %v", result) } for i := 0; i < 5; i++ { - pod := makeTestPod("pod", uint64(i+4)) + pod := makeTestPod("pod", uint64(i+5)) if !api.Semantic.DeepEqual(pod, result[i].Object) { t.Errorf("unexpected item: %v, expected: %v", result[i].Object, pod) } @@ -215,7 +215,7 @@ func TestEvents(t *testing.T) { } // Test for delete event. - store.Delete(makeTestPod("pod", uint64(9))) + store.Delete(makeTestPod("pod", uint64(10))) { result, err := store.GetAllEventsSince(9) @@ -228,11 +228,11 @@ func TestEvents(t *testing.T) { if result[0].Type != watch.Deleted { t.Errorf("unexpected event type: %v", result[0].Type) } - pod := makeTestPod("pod", uint64(9)) + pod := makeTestPod("pod", uint64(10)) if !api.Semantic.DeepEqual(pod, result[0].Object) { t.Errorf("unexpected item: %v, expected: %v", result[0].Object, pod) } - prevPod := makeTestPod("pod", uint64(8)) + prevPod := makeTestPod("pod", uint64(9)) if !api.Semantic.DeepEqual(prevPod, result[0].PrevObject) { t.Errorf("unexpected item: %v, expected: %v", result[0].PrevObject, prevPod) } diff --git a/vendor/k8s.io/kubernetes/pkg/types/unix_user_id.go b/vendor/k8s.io/kubernetes/pkg/types/unix_user_id.go new file mode 100644 index 000000000..b59792abf --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/types/unix_user_id.go @@ -0,0 +1,23 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 types + +// int64 is used as a safe bet against wrap-around (uid's are general +// int32) and to support uid_t -1, and -2. + +type UnixUserID int64 +type UnixGroupID int64 diff --git a/vendor/k8s.io/kubernetes/pkg/ui/data/README.md b/vendor/k8s.io/kubernetes/pkg/ui/data/README.md new file mode 100644 index 000000000..0cbe73d72 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/ui/data/README.md @@ -0,0 +1,9 @@ +The datafiles contained in these directories were generated by the script +```sh +hack/build-ui.sh +``` + +Do not edit by hand. + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/pkg/ui/data/README.md?pixel)]() diff --git a/vendor/k8s.io/kubernetes/pkg/ui/data/swagger/datafile.go b/vendor/k8s.io/kubernetes/pkg/ui/data/swagger/datafile.go new file mode 100644 index 000000000..1a49fe677 --- /dev/null +++ b/vendor/k8s.io/kubernetes/pkg/ui/data/swagger/datafile.go @@ -0,0 +1,17031 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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. +*/ + +// generated by hack/build-ui.sh; DO NOT EDIT + +package swagger + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + "time" +) + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindata_file_info struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindata_file_info) Name() string { + return fi.name +} +func (fi bindata_file_info) Size() int64 { + return fi.size +} +func (fi bindata_file_info) Mode() os.FileMode { + return fi.mode +} +func (fi bindata_file_info) ModTime() time.Time { + return fi.modTime +} +func (fi bindata_file_info) IsDir() bool { + return false +} +func (fi bindata_file_info) Sys() interface{} { + return nil +} + +var _third_party_swagger_ui_license = []byte(`Copyright 2014 Reverb Technologies, Inc. + +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 [apache.org/licenses/LICENSE-2.0](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. +`) + +func third_party_swagger_ui_license_bytes() ([]byte, error) { + return _third_party_swagger_ui_license, nil +} + +func third_party_swagger_ui_license() (*asset, error) { + bytes, err := third_party_swagger_ui_license_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/LICENSE", size: 596, mode: os.FileMode(416), modTime: time.Unix(1423116215, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_readme_md = []byte(`# Readme + +URL: https://github.com/swagger-api/swagger-ui/tree/master/dist +License: Apache License, Version 2.0 +License File: LICENSE + +## Description +Files from dist folder of https://github.com/swagger-api/swagger-ui. +These are dependency-free collection of HTML, Javascript, and CSS assets that +dynamically generate beautiful documentation and sandbox from a +Swagger-compliant API. +Instructions on how to use these: +https://github.com/swagger-api/swagger-ui#how-to-use-it + +## Local Modifications +- Updated the url in index.html to "../../swaggerapi" as per instructions at: +https://github.com/swagger-api/swagger-ui#how-to-use-it +- Modified swagger-ui.js to list resources and operations in sorted order: https://github.com/kubernetes/kubernetes/pull/3421 +- Set supportedSubmitMethods: [] in index.html to remove "Try it out" buttons. + +LICENSE file has been created for compliance purposes. +Not included in original distribution. +`) + +func third_party_swagger_ui_readme_md_bytes() ([]byte, error) { + return _third_party_swagger_ui_readme_md, nil +} + +func third_party_swagger_ui_readme_md() (*asset, error) { + bytes, err := third_party_swagger_ui_readme_md_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/README.md", size: 931, mode: os.FileMode(416), modTime: time.Unix(1449869777, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_css_reset_css = []byte(`/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */ +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +`) + +func third_party_swagger_ui_css_reset_css_bytes() ([]byte, error) { + return _third_party_swagger_ui_css_reset_css, nil +} + +func third_party_swagger_ui_css_reset_css() (*asset, error) { + bytes, err := third_party_swagger_ui_css_reset_css_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/css/reset.css", size: 1066, mode: os.FileMode(416), modTime: time.Unix(1436317036, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_css_screen_css = []byte(`/* Original style from softwaremaniacs.org (c) Ivan Sagalaev */ +.swagger-section pre code { + display: block; + padding: 0.5em; + background: #F0F0F0; +} +.swagger-section pre code, +.swagger-section pre .subst, +.swagger-section pre .tag .title, +.swagger-section pre .lisp .title, +.swagger-section pre .clojure .built_in, +.swagger-section pre .nginx .title { + color: black; +} +.swagger-section pre .string, +.swagger-section pre .title, +.swagger-section pre .constant, +.swagger-section pre .parent, +.swagger-section pre .tag .value, +.swagger-section pre .rules .value, +.swagger-section pre .rules .value .number, +.swagger-section pre .preprocessor, +.swagger-section pre .ruby .symbol, +.swagger-section pre .ruby .symbol .string, +.swagger-section pre .aggregate, +.swagger-section pre .template_tag, +.swagger-section pre .django .variable, +.swagger-section pre .smalltalk .class, +.swagger-section pre .addition, +.swagger-section pre .flow, +.swagger-section pre .stream, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .apache .cbracket, +.swagger-section pre .tex .command, +.swagger-section pre .tex .special, +.swagger-section pre .erlang_repl .function_or_atom, +.swagger-section pre .markdown .header { + color: #800; +} +.swagger-section pre .comment, +.swagger-section pre .annotation, +.swagger-section pre .template_comment, +.swagger-section pre .diff .header, +.swagger-section pre .chunk, +.swagger-section pre .markdown .blockquote { + color: #888; +} +.swagger-section pre .number, +.swagger-section pre .date, +.swagger-section pre .regexp, +.swagger-section pre .literal, +.swagger-section pre .smalltalk .symbol, +.swagger-section pre .smalltalk .char, +.swagger-section pre .go .constant, +.swagger-section pre .change, +.swagger-section pre .markdown .bullet, +.swagger-section pre .markdown .link_url { + color: #080; +} +.swagger-section pre .label, +.swagger-section pre .javadoc, +.swagger-section pre .ruby .string, +.swagger-section pre .decorator, +.swagger-section pre .filter .argument, +.swagger-section pre .localvars, +.swagger-section pre .array, +.swagger-section pre .attr_selector, +.swagger-section pre .important, +.swagger-section pre .pseudo, +.swagger-section pre .pi, +.swagger-section pre .doctype, +.swagger-section pre .deletion, +.swagger-section pre .envvar, +.swagger-section pre .shebang, +.swagger-section pre .apache .sqbracket, +.swagger-section pre .nginx .built_in, +.swagger-section pre .tex .formula, +.swagger-section pre .erlang_repl .reserved, +.swagger-section pre .prompt, +.swagger-section pre .markdown .link_label, +.swagger-section pre .vhdl .attribute, +.swagger-section pre .clojure .attribute, +.swagger-section pre .coffeescript .property { + color: #8888ff; +} +.swagger-section pre .keyword, +.swagger-section pre .id, +.swagger-section pre .phpdoc, +.swagger-section pre .title, +.swagger-section pre .built_in, +.swagger-section pre .aggregate, +.swagger-section pre .css .tag, +.swagger-section pre .javadoctag, +.swagger-section pre .phpdoc, +.swagger-section pre .yardoctag, +.swagger-section pre .smalltalk .class, +.swagger-section pre .winutils, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .go .typename, +.swagger-section pre .tex .command, +.swagger-section pre .markdown .strong, +.swagger-section pre .request, +.swagger-section pre .status { + font-weight: bold; +} +.swagger-section pre .markdown .emphasis { + font-style: italic; +} +.swagger-section pre .nginx .built_in { + font-weight: normal; +} +.swagger-section pre .coffeescript .javascript, +.swagger-section pre .javascript .xml, +.swagger-section pre .tex .formula, +.swagger-section pre .xml .javascript, +.swagger-section pre .xml .vbscript, +.swagger-section pre .xml .css, +.swagger-section pre .xml .cdata { + opacity: 0.5; +} +.swagger-section .swagger-ui-wrap { + line-height: 1; + font-family: "Droid Sans", sans-serif; + max-width: 960px; + margin-left: auto; + margin-right: auto; +} +.swagger-section .swagger-ui-wrap b, +.swagger-section .swagger-ui-wrap strong { + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap q, +.swagger-section .swagger-ui-wrap blockquote { + quotes: none; +} +.swagger-section .swagger-ui-wrap p { + line-height: 1.4em; + padding: 0 0 10px; + color: #333333; +} +.swagger-section .swagger-ui-wrap q:before, +.swagger-section .swagger-ui-wrap q:after, +.swagger-section .swagger-ui-wrap blockquote:before, +.swagger-section .swagger-ui-wrap blockquote:after { + content: none; +} +.swagger-section .swagger-ui-wrap .heading_with_menu h1, +.swagger-section .swagger-ui-wrap .heading_with_menu h2, +.swagger-section .swagger-ui-wrap .heading_with_menu h3, +.swagger-section .swagger-ui-wrap .heading_with_menu h4, +.swagger-section .swagger-ui-wrap .heading_with_menu h5, +.swagger-section .swagger-ui-wrap .heading_with_menu h6 { + display: block; + clear: none; + float: left; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + width: 60%; +} +.swagger-section .swagger-ui-wrap table { + border-collapse: collapse; + border-spacing: 0; +} +.swagger-section .swagger-ui-wrap table thead tr th { + padding: 5px; + font-size: 0.9em; + color: #666666; + border-bottom: 1px solid #999999; +} +.swagger-section .swagger-ui-wrap table tbody tr:last-child td { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap table tbody tr.offset { + background-color: #f0f0f0; +} +.swagger-section .swagger-ui-wrap table tbody tr td { + padding: 6px; + font-size: 0.9em; + border-bottom: 1px solid #cccccc; + vertical-align: top; + line-height: 1.3em; +} +.swagger-section .swagger-ui-wrap ol { + margin: 0px 0 10px; + padding: 0 0 0 18px; + list-style-type: decimal; +} +.swagger-section .swagger-ui-wrap ol li { + padding: 5px 0px; + font-size: 0.9em; + color: #333333; +} +.swagger-section .swagger-ui-wrap ol, +.swagger-section .swagger-ui-wrap ul { + list-style: none; +} +.swagger-section .swagger-ui-wrap h1 a, +.swagger-section .swagger-ui-wrap h2 a, +.swagger-section .swagger-ui-wrap h3 a, +.swagger-section .swagger-ui-wrap h4 a, +.swagger-section .swagger-ui-wrap h5 a, +.swagger-section .swagger-ui-wrap h6 a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap h1 a:hover, +.swagger-section .swagger-ui-wrap h2 a:hover, +.swagger-section .swagger-ui-wrap h3 a:hover, +.swagger-section .swagger-ui-wrap h4 a:hover, +.swagger-section .swagger-ui-wrap h5 a:hover, +.swagger-section .swagger-ui-wrap h6 a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap h1 span.divider, +.swagger-section .swagger-ui-wrap h2 span.divider, +.swagger-section .swagger-ui-wrap h3 span.divider, +.swagger-section .swagger-ui-wrap h4 span.divider, +.swagger-section .swagger-ui-wrap h5 span.divider, +.swagger-section .swagger-ui-wrap h6 span.divider { + color: #aaaaaa; +} +.swagger-section .swagger-ui-wrap a { + color: #547f00; +} +.swagger-section .swagger-ui-wrap a img { + border: none; +} +.swagger-section .swagger-ui-wrap article, +.swagger-section .swagger-ui-wrap aside, +.swagger-section .swagger-ui-wrap details, +.swagger-section .swagger-ui-wrap figcaption, +.swagger-section .swagger-ui-wrap figure, +.swagger-section .swagger-ui-wrap footer, +.swagger-section .swagger-ui-wrap header, +.swagger-section .swagger-ui-wrap hgroup, +.swagger-section .swagger-ui-wrap menu, +.swagger-section .swagger-ui-wrap nav, +.swagger-section .swagger-ui-wrap section, +.swagger-section .swagger-ui-wrap summary { + display: block; +} +.swagger-section .swagger-ui-wrap pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; +} +.swagger-section .swagger-ui-wrap pre code { + line-height: 1.6em; + background: none; +} +.swagger-section .swagger-ui-wrap .content > .content-type > div > label { + clear: both; + display: block; + color: #0F6AB4; + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap .content pre { + font-size: 12px; + margin-top: 5px; + padding: 5px; +} +.swagger-section .swagger-ui-wrap .icon-btn { + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .info_title { + padding-bottom: 10px; + font-weight: bold; + font-size: 25px; +} +.swagger-section .swagger-ui-wrap p.big, +.swagger-section .swagger-ui-wrap div.big p { + font-size: 1em; + margin-bottom: 10px; +} +.swagger-section .swagger-ui-wrap form.fullwidth ol li.string input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.url input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.text textarea, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.numeric input { + width: 500px !important; +} +.swagger-section .swagger-ui-wrap .info_license { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_tos { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .message-fail { + color: #cc0000; +} +.swagger-section .swagger-ui-wrap .info_url { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_email { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_name { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_description { + padding-bottom: 10px; + font-size: 15px; +} +.swagger-section .swagger-ui-wrap .markdown ol li, +.swagger-section .swagger-ui-wrap .markdown ul li { + padding: 3px 0px; + line-height: 1.4em; + color: #333333; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input { + display: block; + padding: 4px; + width: auto; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input.title { + font-size: 1.3em; +} +.swagger-section .swagger-ui-wrap table.fullwidth { + width: 100%; +} +.swagger-section .swagger-ui-wrap .model-signature { + font-family: "Droid Sans", sans-serif; + font-size: 1em; + line-height: 1.5em; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a { + text-decoration: none; + color: #AAA; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav .selected { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap .model-signature .propType { + color: #5555aa; +} +.swagger-section .swagger-ui-wrap .model-signature pre:hover { + background-color: #ffffdd; +} +.swagger-section .swagger-ui-wrap .model-signature pre { + font-size: .85em; + line-height: 1.2em; + overflow: auto; + max-height: 200px; + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav { + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li:last-child { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li { + float: left; + margin: 0 5px 5px 0; + padding: 2px 5px 2px 0; + border-right: 1px solid #ddd; +} +.swagger-section .swagger-ui-wrap .model-signature .propOpt { + color: #555; +} +.swagger-section .swagger-ui-wrap .model-signature .snippet small { + font-size: 0.75em; +} +.swagger-section .swagger-ui-wrap .model-signature .propOptKey { + font-style: italic; +} +.swagger-section .swagger-ui-wrap .model-signature .description .strong { + font-weight: bold; + color: #000; + font-size: .9em; +} +.swagger-section .swagger-ui-wrap .model-signature .description div { + font-size: 0.9em; + line-height: 1.5em; + margin-left: 1em; +} +.swagger-section .swagger-ui-wrap .model-signature .description .stronger { + font-weight: bold; + color: #000; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper { + border-spacing: 0; + position: absolute; + background-color: #ffffff; + border: 1px solid #bbbbbb; + display: none; + font-size: 11px; + max-width: 400px; + line-height: 30px; + color: black; + padding: 5px; + margin-left: 10px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper th { + text-align: center; + background-color: #eeeeee; + border: 1px solid #bbbbbb; + font-size: 11px; + color: #666666; + font-weight: bold; + padding: 5px; + line-height: 15px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper .optionName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .propName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-container { + clear: both; +} +.swagger-section .swagger-ui-wrap .body-textarea { + width: 300px; + height: 100px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap .markdown p code, +.swagger-section .swagger-ui-wrap .markdown li code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #f0f0f0; + color: black; + padding: 1px 3px; +} +.swagger-section .swagger-ui-wrap .required { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap input.parameter { + width: 300px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap h1 { + color: black; + font-size: 1.5em; + line-height: 1.3em; + padding: 10px 0 10px 0; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .heading_with_menu { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap .heading_with_menu ul { + display: block; + clear: none; + float: right; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + margin-top: 10px; +} +.swagger-section .swagger-ui-wrap h2 { + color: black; + font-size: 1.3em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap h2 span.sub { + font-size: 0.7em; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap h2 span.sub a { + color: #777777; +} +.swagger-section .swagger-ui-wrap span.weak { + color: #666666; +} +.swagger-section .swagger-ui-wrap .message-success { + color: #89BF04; +} +.swagger-section .swagger-ui-wrap caption, +.swagger-section .swagger-ui-wrap th, +.swagger-section .swagger-ui-wrap td { + text-align: left; + font-weight: normal; + vertical-align: middle; +} +.swagger-section .swagger-ui-wrap .code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.text textarea { + font-family: "Droid Sans", sans-serif; + height: 250px; + padding: 4px; + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.select select { + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean label { + display: block; + float: left; + clear: none; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean input { + display: block; + float: left; + clear: none; + margin: 0 5px 0 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.required label { + color: black; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label { + display: block; + clear: both; + width: auto; + padding: 0 0 3px; + color: #666666; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label abbr { + padding-left: 3px; + color: #888888; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li p.inline-hints { + margin-left: 0; + font-style: italic; + font-size: 0.9em; + margin: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.buttons { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap span.blank, +.swagger-section .swagger-ui-wrap span.empty { + color: #888888; + font-style: italic; +} +.swagger-section .swagger-ui-wrap .markdown h3 { + color: #547f00; +} +.swagger-section .swagger-ui-wrap .markdown h4 { + color: #666666; +} +.swagger-section .swagger-ui-wrap .markdown pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; + margin: 0 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown pre code { + line-height: 1.6em; +} +.swagger-section .swagger-ui-wrap div.gist { + margin: 20px 0 25px 0 !important; +} +.swagger-section .swagger-ui-wrap ul#resources { + font-family: "Droid Sans", sans-serif; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource { + border-bottom: 1px solid #dddddd; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading h2 a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading ul.options li a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading ul.options li a { + color: #555555; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:last-child { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading { + border: 1px solid transparent; + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 14px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + border-right: 1px solid #dddddd; + color: #666666; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a { + color: #aaaaaa; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { + color: #999999; + padding-left: 0; + display: block; + clear: none; + float: left; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { + color: #999999; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0 0 10px; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 { + display: block; + clear: none; + float: left; + width: auto; + margin: 0; + padding: 0; + line-height: 1.1em; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path { + padding-left: 10px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.http_method a { + text-transform: uppercase; + text-decoration: none; + color: white; + display: inline-block; + width: 50px; + font-size: 0.7em; + text-align: center; + padding: 7px 0 4px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -o-border-radius: 2px; + -ms-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 6px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content { + border-top: none; + padding: 10px; + -moz-border-radius-bottomleft: 6px; + -webkit-border-bottom-left-radius: 6px; + -o-border-bottom-left-radius: 6px; + -ms-border-bottom-left-radius: 6px; + -khtml-border-bottom-left-radius: 6px; + border-bottom-left-radius: 6px; + -moz-border-radius-bottomright: 6px; + -webkit-border-bottom-right-radius: 6px; + -o-border-bottom-right-radius: 6px; + -ms-border-bottom-right-radius: 6px; + -khtml-border-bottom-right-radius: 6px; + border-bottom-right-radius: 6px; + margin: 0 0 20px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content h4 { + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header a { + padding: 4px 0 0 10px; + display: inline-block; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header input.submit { + display: block; + clear: none; + float: left; + padding: 6px 8px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber { + background-image: url('../images/throbber.gif'); + width: 128px; + height: 16px; + display: block; + clear: none; + float: right; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form input[type='text'].error { + outline: 2px solid black; + outline-color: #cc0000; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + padding: 10px; + font-size: 0.9em; + max-height: 400px; + overflow-y: auto; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading { + background-color: #f9f2e9; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading h3 span.http_method a { + background-color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0e0ca; + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li a { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content { + background-color: #faf5ee; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content h4 { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #ffd20f; + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li a { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content h4 { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading { + background-color: #f5e8e8; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #e8c6c7; + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li a { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + background-color: #f7eded; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content h4 { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content div.sandbox_header a { + color: #c8787a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading { + background-color: #e7f6ec; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading h3 span.http_method a { + background-color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3e8d1; + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li a { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content { + background-color: #ebf7f0; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content h4 { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading { + background-color: #FCE9E3; + border: 1px solid #F5D5C3; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading h3 span.http_method a { + background-color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0cecb; + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li a { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content { + background-color: #faf0ef; + border: 1px solid #f0cecb; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content h4 { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + border-top: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap p#colophon { + margin: 0 15px 40px 15px; + padding: 10px 0; + font-size: 0.8em; + border-top: 1px solid #dddddd; + font-family: "Droid Sans", sans-serif; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap p#colophon a { + text-decoration: none; + color: #547f00; +} +.swagger-section .swagger-ui-wrap h3 { + color: black; + font-size: 1.1em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown ol, +.swagger-section .swagger-ui-wrap .markdown ul { + font-family: "Droid Sans", sans-serif; + margin: 5px 0 10px; + padding: 0 0 0 18px; + list-style-type: disc; +} +.swagger-section .swagger-ui-wrap form.form_box { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box label { + color: #0f6ab4 !important; +} +.swagger-section .swagger-ui-wrap form.form_box input[type=submit] { + display: block; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box p.weak { + font-size: 0.8em; +} +.swagger-section .swagger-ui-wrap form.form_box p { + font-size: 0.9em; + padding: 0 0 15px; + color: #7e7b6d; +} +.swagger-section .swagger-ui-wrap form.form_box p a { + color: #646257; +} +.swagger-section .swagger-ui-wrap form.form_box p strong { + color: black; +} +.swagger-section .title { + font-style: bold; +} +.swagger-section .secondary_form { + display: none; +} +.swagger-section .main_image { + display: block; + margin-left: auto; + margin-right: auto; +} +.swagger-section .oauth_body { + margin-left: 100px; + margin-right: 100px; +} +.swagger-section .oauth_submit { + text-align: center; +} +.swagger-section .api-popup-dialog { + z-index: 10000; + position: absolute; + width: 500px; + background: #FFF; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + color: #777; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog p.error-msg { + padding-left: 5px; + padding-bottom: 5px; +} +.swagger-section .api-popup-dialog button.api-popup-authbtn { + height: 30px; +} +.swagger-section .api-popup-dialog button.api-popup-cancel { + height: 30px; +} +.swagger-section .api-popup-scopes { + padding: 10px 20px; +} +.swagger-section .api-popup-scopes li { + padding: 5px 0; + line-height: 20px; +} +.swagger-section .api-popup-scopes .api-scope-desc { + padding-left: 20px; + font-style: italic; +} +.swagger-section .api-popup-scopes li input { + position: relative; + top: 2px; +} +.swagger-section .api-popup-actions { + padding-top: 10px; +} +.swagger-section .access { + float: right; +} +.swagger-section .auth { + float: right; +} +.swagger-section #api_information_panel { + position: absolute; + background: #FFF; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + max-width: 300px; + line-height: 30px; + color: black; + padding: 5px; +} +.swagger-section #api_information_panel p .api-msg-enabled { + color: green; +} +.swagger-section #api_information_panel p .api-msg-disabled { + color: red; +} +.swagger-section .api-ic { + height: 18px; + vertical-align: middle; + display: inline-block; + background: url(../images/explorer_icons.png) no-repeat; +} +.swagger-section .ic-info { + background-position: 0 0; + width: 18px; + margin-top: -7px; + margin-left: 4px; +} +.swagger-section .ic-warning { + background-position: -60px 0; + width: 18px; + margin-top: -7px; + margin-left: 4px; +} +.swagger-section .ic-error { + background-position: -30px 0; + width: 18px; + margin-top: -7px; + margin-left: 4px; +} +.swagger-section .ic-off { + background-position: -90px 0; + width: 58px; + margin-top: -4px; + cursor: pointer; +} +.swagger-section .ic-on { + background-position: -160px 0; + width: 58px; + margin-top: -4px; + cursor: pointer; +} +.swagger-section #header { + background-color: #89bf04; + padding: 14px; +} +.swagger-section #header a#logo { + font-size: 1.5em; + font-weight: bold; + text-decoration: none; + background: transparent url(../images/logo_small.png) no-repeat left center; + padding: 20px 0 20px 40px; + color: white; +} +.swagger-section #header form#api_selector { + display: block; + clear: none; + float: right; +} +.swagger-section #header form#api_selector .input { + display: block; + clear: none; + float: left; + margin: 0 10px 0 0; +} +.swagger-section #header form#api_selector .input input#input_apiKey { + width: 200px; +} +.swagger-section #header form#api_selector .input input#input_baseUrl { + width: 400px; +} +.swagger-section #header form#api_selector .input a#explore { + display: block; + text-decoration: none; + font-weight: bold; + padding: 6px 8px; + font-size: 0.9em; + color: white; + background-color: #547f00; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -o-border-radius: 4px; + -ms-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +} +.swagger-section #header form#api_selector .input a#explore:hover { + background-color: #547f00; +} +.swagger-section #header form#api_selector .input input { + font-size: 0.9em; + padding: 3px; + margin: 0; +} +.swagger-section #content_message { + margin: 10px 15px; + font-style: italic; + color: #999999; +} +.swagger-section #message-bar { + min-height: 30px; + text-align: center; + padding-top: 10px; +} +`) + +func third_party_swagger_ui_css_screen_css_bytes() ([]byte, error) { + return _third_party_swagger_ui_css_screen_css, nil +} + +func third_party_swagger_ui_css_screen_css() (*asset, error) { + bytes, err := third_party_swagger_ui_css_screen_css_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/css/screen.css", size: 43042, mode: os.FileMode(416), modTime: time.Unix(1449047502, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_css_typography_css = []byte(`/* droid-sans-regular - latin */ +@font-face { + font-family: 'Droid Sans'; + font-style: normal; + font-weight: 400; + src: url('../fonts/droid-sans-v6-latin-regular.eot'); /* IE9 Compat Modes */ + src: local('Droid Sans'), local('DroidSans'), + url('../fonts/droid-sans-v6-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('../fonts/droid-sans-v6-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ + url('../fonts/droid-sans-v6-latin-regular.woff') format('woff'), /* Modern Browsers */ + url('../fonts/droid-sans-v6-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */ + url('../fonts/droid-sans-v6-latin-regular.svg#DroidSans') format('svg'); /* Legacy iOS */ +} +/* droid-sans-700 - latin */ +@font-face { + font-family: 'Droid Sans'; + font-style: normal; + font-weight: 700; + src: url('../fonts/droid-sans-v6-latin-700.eot'); /* IE9 Compat Modes */ + src: local('Droid Sans Bold'), local('DroidSans-Bold'), + url('../fonts/droid-sans-v6-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('../fonts/droid-sans-v6-latin-700.woff2') format('woff2'), /* Super Modern Browsers */ + url('../fonts/droid-sans-v6-latin-700.woff') format('woff'), /* Modern Browsers */ + url('../fonts/droid-sans-v6-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */ + url('../fonts/droid-sans-v6-latin-700.svg#DroidSans') format('svg'); /* Legacy iOS */ +} +`) + +func third_party_swagger_ui_css_typography_css_bytes() ([]byte, error) { + return _third_party_swagger_ui_css_typography_css, nil +} + +func third_party_swagger_ui_css_typography_css() (*asset, error) { + bytes, err := third_party_swagger_ui_css_typography_css_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/css/typography.css", size: 1474, mode: os.FileMode(416), modTime: time.Unix(1436317036, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_fonts_droid_sans_v6_latin_700_eot = []byte("\x8cY\x00\x00\xaeX\x00\x00\x02\x00\x02\x00\x04\x00\x00\x00\x02\v\b\x06\x03\b\x04\x02\x02\x04\x01\x00\xbc\x02\x00\x00\b\x00LP\xef\x02\x00\xe0[ \x00@(\x00\x00\x00\x00\x00\x00\x00\x9f\x01\x00 \x00\x00\x00\x00ׂ6W\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00D\x00r\x00o\x00i\x00d\x00 \x00S\x00a\x00n\x00s\x00\x00\x00\b\x00B\x00o\x00l\x00d\x00\x00\x00,\x00V\x00e\x00r\x00s\x00i\x00o\x00n\x00 \x001\x00.\x000\x000\x00 \x00b\x00u\x00i\x00l\x00d\x00 \x001\x001\x002\x00\x00\x00\x1e\x00D\x00r\x00o\x00i\x00d\x00 \x00S\x00a\x00n\x00s\x00 \x00B\x00o\x00l\x00d\x00\x00\x00\x00\x00BSGP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00w$\x009J\x00Q\xb2\x00-\x92\x12\xcd\xe9\x8a\xc8`\xd8W\xc9hKropq\"U:b,/\x962\xd9\xe3\xdb\xd3\xf0\xe0\xc6g@\x9e\xba\xd6$@6\xa4\x92\x9f\x83\xec\x03\x8a;\"\xf1\xa5\xb1\x18\xe0[E\xc7LFM\xad#^S[\xaf\xd0ϋ^>\x17\x1eZ\xc9\"\xc5$\xc0\xf1tYx\xa2X\xad\xa9\x86y\xe0\t.~iVAV\x91\xa5\xb5\fqIH3Y\xa2K]I\x165\xe6K\xefcB\xec\x80>E\xadZ\x93+͡,\xb0\xa3J\x85\xb7fEk2\f-\x15\xf5\xe2\x02\x82zF\x15\x99瞏\xd5B\xecj\x1eKҮS\xeb\n\xac\a\xc7XJ\x8b\x17UX\x0f)\xddJmq\xf51\xbal\x02\xd4l\x10\xc5\xd4]\xd9\x18%\x9b\x974S\xe5\x98\t\xdf%ՇCY9\x9fΌ\xab60IZ\x10ϡRI\a\xbbx\xcd\xd4o\xc0Ã\xefL\xbd\x12U\xe2\xf8.\xe6nc\xb4\xb4\xc67'\"\xdcI\x8f\x00Mg\u007f\f\xa8ŵ\x95\x9c\xb4\xdc\x04Z\x15\xee\nC#\xe4f\xbf/5\x9f\xea\xbf'\xad\x9f\xa6C\x84\xaa\x953\x91\x16\x19\xb8'\xe1z\xfet8n\x89\xff`\xdbs/J\xcf\b\xad\x93v\u007f\xa2\x918\x11EH\xc0\x84\"\xc1\x85\xb2p\x15\x83\x00s\xa0o\x85\xaa\xa48M\x17\xa4\x1e`\x1f\xe0\x8eۊ4Ռ>P6D\xc9\f#\"4i\x82S\x14\x11%\x0e5\xdce\xd8\xc3^\xb0\x16\xe1Sn\n\x9fBYL`\n\x9aB\x18\x12.\x03\xcc\x061\xfcJ\x18Vp\x06\x02\x87\xe8\xa9\xe6\xe2\xa2W\x13\\\xa0\xac\xee\xb1\xb2~\xcaq\xe7v\x83\u007fNt\xf3m\x92v5\xa3q\xa7\x1d\xa6\xda\xcbb݃\xb6\xbd\u05ee\xbd\xa5\xef/\xa9\x9cq[\xcb\xd6\xf9\xdbSǞ\xbca\xc6\xef>\xe5?\v\x8ez\x8d\xe8.=]D\uf5d7\x1e\xc5sHʎ\xacd\xcf7\xf8K\x8c\xd9\x10@\x12\x1em\xbbe9\x82\x8dr.\xe6\x05 \xc8<)\x02\x04\xfd\x12)\x18\xa1\x01\f\x8e\x9a\xe9hբ;\xf5p\xe6 \x95\x1f\xba\x03\x8f\xb93\x93\xb9=+\xf9̈]\x92^\xd3\xf6߸\xfd\xd7\xf1:\xe8,\x84\xcb\b\xe7}d!x\xe0R\xeb\xfa\x1c\xbf\xb5\xa7V2\x03\xe4$\x9fC\x94\x99\x8a\x14\x82\\6\x04Fƛ\xf6S\x90\xb9\x13\xee>\xea]i\xfe\x1fzhѥ\xb6\x8dZ>B\xa5\xd7\x16\x90\x83}\x99\x93j\x05X\f\x0f\xcd\xe7'9\xb9\xd1N\xaaxR\xa2\x95T\xac\xa5uz^\xf5\xfcg,\xa4ZRڗ\x17\xc1[T\xf2\xe2\xfe\xa9\xe9@K\x8c\xc2S\x05^X\x8d\xc0\x87Y\x90\xf1\xc7\a\"\xb0\x98\x9d\x8a\xc1\b-\xca\f0i\x83\x8d\xb0\xe4\xa18\f a\x13\t\x1c\xa1\xb6\x87\x8a\xfd\x99C\x1b2y\b\xcaܳk\xb8\x9d\xf1\xcd\x05\x98q\xb7\x1b\xcd\x00\x85N\x90\x1e\x8e\xe3\xb1Қ\x8a\x16+\xe9#\xc2+`\x00\x1c4{\xe0LO\x00\x00\xa2\xbb\x9b\x01\x8b\x18\xa9\xa1\x18\x86\x06O\xeb\xa0R\x81\x8c\x0e`\x83\x04\x98(\xe7\x0et\xe7\x8e|\xd0\r\xbc\xe4\xec\t\xa3F\xa8:\x93\xaa7\x83A0\x9a\x8bJaC\xa6?%\x9dy\x86z\xabBۇ\xe2\f\x9a\x94\n\xf8WgG\x80x)\xa52͙W\xbb\x83'\xcaٝ\xf0\xd1Շd\t\x8cL\x12\x00\xa6\x99,E6\x19\xbd\xf2x\xe9\x05\xa3\fa\xe3\x17\xee\xe0\x19#`\xed\x11\r\x00\xdbXڸ\x87/\x9c\x10k(J\x04Wn\xc4\x10Ղy\xaf\x1e\xa8\x9dȜ@\x90\x82\xe6\x03S\xe4\x1a\x85r+\"\xff\xa1\xb0\x06%\xc42g\xa9\xe2\x82巇\x18$\xb57A]\r\x90ז;̓n\xbc\x8e(`*m\xf8\n\x1b\"Zd\xc1\xb0&\xfb\xe7ɘdA\xf5?y\xf8=Ÿ`\x93\x86\r\n\xa1\xe2\xe9\x03\v\xbc\xbd\x10\v\xe0\t\x18?\xa2\xa5P4$\xa3\x81j\xc2\x06\xe4m\xb8o\x8e\b\xb7<\xaaIx\xf4~k\xa8\x1bƛ5\xfe)[w\xf5\xbdlQ\xc0\xb5\xebw\xd6\xe8y\xdbA\x81\xf4\xe6\x1b\x8e\x9cW\xf0+#}\n\xb6E \xe3¾\xc4n3\xc7\xd1!\x06%\x85/=,K\x02\x97-\x11aBa(\xc6Qu\xd0\x12\xa8\x83\x11\x9a7\xe8V4\xeb~\x15H\x84\xf5\xb19\x88\\%,&_Q\xcd[C\xf6fj\x98\xda$܉\xb5\x93)\xa2\x1d%\xe7}\bq\x95dv\x10{,d%\xb1N\x04\x1c\xc0\x97\x03.\x12\x9a\b\x94\xa2\x8a\u007f\x98^p/\x99a\xa4\xe1.\x86-L\x955\xa6\xfd\n\x83\x00f\xc1ö\xe3N1\xfd\"0\xe1(\n\x18\xd0\xfbwMʹ\xe1zzqb\x14\xe6+:\xd8\xf5aU\xb2\xcewb!\x87\x0f\x8c`DH\v\x9bF;!\x8aIl\x8cfJ4ԋS\xa1\x84\x1by\xd9\xd1Rq\x97DI\x9ac\x90ٱP\x1d\x9d\x93\x03\xceR\xe3\x82\bN\"\xa4\x90\xc8\x11\x12e\x91\x12H\xc5\xe3\xe9\xfa\xfc\xcdg\x14}<`\v\xd2\x14#\x9c!<\";\xf3\xaa**T\vu%%\xac\x9a\x00\xcf\u052a\xa9\xc7X\x11Q\x82MA\xfd3LJp\xd6\xdc.\xe8Ws\x17\r\xae\x1f\x8eJ\xe23\xea\xd8꼮u\xfa6\x16j_>$\x18\x14\xf6J\x8e\x9eJ\x95\x1b\x9e\x8cM\x96\f\x18\x8e,\x8cfn\xff[\xbcM\xdb~\x90\xc5\x00_Bo)\xae\x05\xde\aѨKQ\xfds\xca]\xe3ˤ\xa9\x95\xcaEF\xe6\x1d\xd8~к\xf2\x98\x17\x8d\x13\r2+\xcbz\xbc\x9d3\xbe\xd7+Cס\x96\xbd\xdf\xe4\"\xfeՑ<\xf2\xe0\x8c\xfb\x13\xe1:\xac\x01\x19\x94[=\x02d\xff^\x12\xcd\x12\n\f\xb1j\xd6N\xc8A\xad\xdf\rƐ\x10X\a\xcc,f(T-XM\xf8\xde\xd5\xd2\"\x11\xbap\xa8\xf0\xd7\xc1\xf0\xe5Qa\xcb\x1f\xc1\x96\x8c\xc1\x8d\xaf\n\xc4F\xec-=\xe0Hʥw\xc2O&\x81\xba\xd0 i$~\xe0\xab~F\xdb>2\x9bz\xc0\xd6'\r\n\x03\x12wL=\x98\x9c\x1c\xc8 έ\xa3\t\xadh,3l\x9d\xd3\xfce\x14\xd1\xda:\x02f\"H\xbb\xe76D\x18%3\xb0\x0e\xacz\xc1\x18\x95\x10\x00\xd65T \r\x98\xa4\x95\xbbνiU\x06)\xbe\xb1 (V\r\v\xce[75\"\x06\x13\x93\x824t\xccyЂ\xdc\xc8\x00\xca\x0f<\xba\x10\xbd\x8c\x9f\x8d\xb8'\x0e\xac\x02;\x85\xe2>@\xcf\xda\xccy\x14t\xce\xe3:\x01\xbf\xf4\xd4=\x9c\xc6\xce/\x02\xdd\xef\xa1ί\xc6(+I\x16 \x00\x1e5<\x11?\x80\x84B8|,9\x92\f]͎V\xbb\v\xd4\xc0\x06\x0eSI\b>\x84x\xd4\x01p\x17>v4\xa3g\\JS\xe6\x93#\xf2\xb1\xe7v\x83jgDo\x02\x91\xc0Q\xc8\x03\x90\x18\x03hs\xe4qh(FуhK\x12br\xdb\x0e\xd3\xdc3}\x15\xc3\x1c#\xdb\xf5sf\x9d(\xaa\xd2T\x02\xfc\xb4\xb4H+:!1\x86\x94\xc9S\xd8P8\xdb\xee:1\xa5s\t˪d\x85\x03GJ\xb8N\x95\xcf\xe9w\x1eL?\xaa\xd5ʍ\xcc\xc4\x11\x00oV\xcb%\xbd\x13\x96G,\x87'\xaamy\xc0\x8e\x1c\xd0C\x0e\x18\x00\x8f\x18\x12\xc4\xd8\x00\x98\xa5\xb5\xcd\xde\x00\xd95\x15\x0fr\xcf\rK%2D\xd8b\x82\x06tT'\x88t\xdc&@\x94\xd7B\xd1O\xac\x9d\xdb]\x11\x9df\xf0\x10\xe9\"\x89 \xb4M\x80\x0e\xc0Ѝ|\x06\xef\x89c\xdcl\xef>.ȶA\x04L\x0f\xd6\x1b2bϽ\x10\xcc\x0f\x14\x8bV\x12J*\x9fc!\x19\x05\a\x0ef\x04R\x82%-\xf3\x8f\xe7\tF\x02\xdf\xd5+\x01\x88\xe1;o\f\x1b\x88. \x85/E\xc1\x82¤\xc1`\xa9\x95}\x04\x95\xa3`de\xa0\xfb\xa2b\xd2w\xb7\xa3DI!\x10\xe4;\"\fiz\xfaOE\xdb({\xeb\"\x1d\x9cK\xd6\x03x\x89\xdaL}\xe4?\xd8\xdae\"^w\x96gG2\xc1\x03\x95\xf6[\xaf\x93X\x17\"\xa91\xbf\x99\x98\xa5\x01\xf4\xee\x11<\xc4M\f[P\xc4ņ\x94R\xf6\x11\xfb\x83jK\xf2r\x13\x05j\x06\xd7+\x03\x925R\xf0\xbb\x84h,\xf9\xe6\xf8u[o\x90ذ\x92̙\xcb\xc8sK7b\xfcB\xc4&\x18\xe6=h\xa3\b/Y&\\\x91\x19plR\x90l\xfex\x8c>\xcbi\xbb\xc6P\x01\x0f\xd2P9\xa0$~\xff\a;Y\x1a&R̈q\x88\xa1\xf9\v0V\x8a\xe0/\x87\x9d\xc9f4\xfb\xd70?\x82N@\n+\x95\x02/N\x12M\x0e\x16n\x13\x8e\xfc\x88J\xcfU@o\x933\x9fW\x11\xfa\xdc\xdfY\xa4\xc9+\xe2$\x00\xca +\x9c\xc2S\xef\x955~F\xb8(\x96\xf4\xa3\xa8?T\x1bn\xfcי\xdf\xcbv\x01ܘ\xe5~윽\x1e\x03\u007fMpk\xd3@\x87\x85u\xe1\x15\xfb\x93 \xeaV\x9fEӇ\xf7\xed\x9ck\x90F\xcd\xf2\xad!P#*J\xc13\xf0Fj\xbaL\x81u\x04\x9f\xa0\xbe.S\xff\xf0\x90t\x18m8D^\xe9.M\x82\xe2\x0e\x03\x97\x8aU\xd4s^\x9dGW\x8a.\xb0\xa5r\x81\xe5?\x05\xcd \xfe\xc5\x17\xc5,H\xab\x9c\x11\xf8ۑ'\xa8C#{RK\x85D\x15\x03\rW\xd2\xdd\x00\x1f\x95\x11!n\xab\x15Ջjj\xb8m\xe9\x00\x96\xed쐙\xe4wv\xd0\x12_T\x11\x9c&\xa1_\xca\x17Lj\xb74\xa8\v\xab4\\\x01\x18\x1e\xc8\f\xd1sc\xb9\xa8\x86\x88\xe8J\xd7\xd8/\xeb\x16\x15\x98\xe8+dQ\x94T\x91\x12k\x9c\x834\f\x0e38\xc1\xed\x0fd*\x10U,\xcd\xc7DX(\x9e`\\\xa7֏)\r?2&\xdf\xfc+\x14\xff\xbd\x82\xb1\v\xee\x84\v\r\xdf\xc2\x17\x83 D\xa5\x84\xc8/\x85\xf5\xf8\xae`\a\xa3\xbb\xa0\x0e\xe4a\xc0}dx\xff\\zKTh\xabc\xe9\xa5\xe1~\xb2\xbe\xdc\x00\x86\xa0\xfaS\xe2\x1fq,ɨs\x16-\xb9}B\xe83ͪ\n\x91\xdf\xf2fJ$\x02\x8e\xd0 1-\xddq\xd7\xfd\xa7\x15\xd9\xe7\x80{\fm\xaeN\xc9\xcb\xe3\b\xc0\xd1\ns\xa3\x87\x9a\t\xb3\x83:\xd1፳|\xdbYz`7\xec\xc4t\xec\x10!1\xf15\x15\x17\x1b\xfd\xbdbCy\x93\x8f\x1a\xbd\a\x80\xb3\x8c\xbb+\xa6\xf9v\x82\x83/\x0f\xeb=\r\x84\u007fu᪦\x18\xf0\xa6\x88=\xdc\x14\f\xc9\x11\x03ft\xbfmx\xf2}\xc2\x19\x84\x01\x8a\xa1\x1b\xf0ɐ(3\x98\x99j\xe2\xa1A\xd9?\xcf̓ \xa0%{\t\x96\xb0\x06\x94\xfdY\xb7OkoR\x89?\aE\xc7-\x852\x1ep;\xbav\xc2^.#w\xa9\b[\u007f\x8d\x18[5\x91=I\xae\xcemU\xf6\xdfT\x8e\xea\x89*\xc8c\xf3M\xf61jz\x8a\xddo^\xf9\xa6̗\x1aK\x19O\xac\xed\u0093\x15\x13IIj\x8f\x95\x10\xb0\xb0\x95Q\x13I\xb2\x0f\x80?\xb9\x8d\xce\xf0\xb9<\x86T\xea\x00|TV\x1an\xe9\xf3\x1b\xa0\x86\xed\xf6\x9c\u007f\x8a?\xfe\x05\fU\x1d(xhH\x848\xaf\x9e$7\x81QF鵕:;\x82f9\xfc5-N\xb9V\xb2\b\x8aօqeh\xb4+\xb8\xab3hE8\f\x05q\xec3\x82C\xe1\xa5;\xd1Y\xcd|\xeb*b\x88BO\xd3\x0f\u007f\xa7\x95\x80xϨv:\xb1\xef\t\x1dz+oV\xbb\xeb\x87E\xb6\x904b\xaf\xc8\xee,\xec}\x9b\u0092\x03\xe2\xc6S\xc4h\xdb\x00B\x02\xcdd5\xd4G\xaa\x13\x17\xfadr+\xbd*\x1c\xf7*@\x12\v&q\x1b\x81\xacp(\f\a\x80\x1d\xcb\x03\xeab\xa1Ʈ\x8a\xfbA\xa6\x86J\xba\xc3p9yl\xe9\xea^\xc2\xf0=i\n\x03\v\\\xf4p\x1b\xdc(\xa2[ݢDu\xac\x80\xe4\x19\x1b)\x9b\xcat\xc2\xec\x9c\xdc\xe1\x00\xae{)U\x95\xb5\x1f\x90}r\xe3\xb3e\b\xa7\x15\x14\x1d\xb2+~[6HO\xa9\xc2\x136\x86ٝ7#\xa6\n^\x11\xe2\f\xf8\xf4\x80M\xfd\xc0\x13\xd0\x13\x83\x1b\xb2B6\x9b\xd1\xc2L%oɉ\xd0N&g\x19R|{\xb1\x84\x9a\xc5-Ri\x94N&n\x0e46\x14(L\x03\xee\xf1\xc0-%\xc3)\x1d\xf9H\x8e\xf1\x86[\xbeh\xebeQE\xa9\x85\xc8\xd1\xc0\x98\xa0_\x19\x95\xe0l\xa1\x12bp\xa00\x9a٧@8\xec\x1bŔ\x04\xae\xadUE\xf1\x9a?\x9bg\x12DE\x02\xfd#\xb9\xbaLm~\xcb\xd3HZI\xb0\xe1\xc5'Rŀ\x12\xf8\xb6D\x9f\xff%\xc3p\x80\a\tM\xaa\xb8o\xc6\xe7\x9d\\J\x1aL\xa39\xbcp1\xae\xe4u<.\xe8\x1c\x83\xb3\xec3\xac}\b\xde)\x1eZa\x92g\xe7\x1f\xc0\x8c\x9a\x96\f\x14\x94@\xa1\xc5\v$\tRi\xf6\xe8\xf1\xa4N%CQ\xc1h\xa8\x90\xacޞ(\u007fY\xef{{ۑ\x03\xbeI\xaa\U000bc0f8\f\xacND\xefDZ$\x9b\t\xc5\aTV\xa9\xfd\xc2\x16\x02\xf9\xf66\x8b\x90\f\xb2{\x00\x00\x06\x90\n\x84\x87Q\xaf\a\xe50W\xf95\x95\x91<\x9a\xd1\xe4ظ?\x82xD\x871Vr\xcf\xd1\xf7E\xb0_\x9a\xf9\xea\x16\x13\x8a\xc0@ĔF\xcfA\xb9Ly\x11{I\a\xbc^\xea\x0eE\t&\xdcet2%\xa5\x19\x01%D\xf5$\xf3Kymݴ\x9a\\\xda\xe3S\xb3\xb8f\xac8\x0f\x06w-\xdc_%\x93}*\xf2\x13\x12\xd8c\xad1\xb4\xe7y\x81\xbb\xf2\xcbi\u007f\xf8\x12\xa6\xe8F]\xfd^\x0e\x99TL\xf3w\xb6\xc1DP\x96r,S\x03\x06\x05\xd6\xf2\xc9d\x0eW\xc7g\x9f\x92\x02Z\xf4\x1d#Dl\x88yr+^\xbfb\xf7\xe4\xa1U\xac\x9b\xf1\xb3\xd8S\xab\x81\xd4Rt\xb0\xd6\x1e\xa3R\x1d5\xf0\\\xb7\xef-_\xe7X`\n\xc5\t\xef\xb8\xe8\xdd'\x8e\x11\x87\xf3\x05_\x95f\xdfA\a\xa70\x8b^\xa8u\xefV}\xbc\x96\xe4\xc0h\x87\xd6%\x04Q}O\xe4\\\xf5\x82\x8d0o>\xdeF~-|\u007f\x13kXP\xbb\r}m*e`!\xeb!\xb0\xda\xc4KC\x90\x1e8\x9cȩ\"\x8c?\xa0\t˟\xd2ҝ\"\x82\x00\xad0cj쬊%\x80TCE\xad\xe6\x19\x91\x8c|F\xa6j\xd499\xe1\x95\x13\xbep\xd1\\\xe1\x85{\xf5\xe3Y\xd9\x11\xac*\xdbʜ\x03\x01\x14!\x19܇.\xcbQ\x14XP\x178*\x19\x17\x16\xfcɝ`θ\x82x\xc11\xfcׅ\x00*nGR\x83\xc0sS\x9am\x8f\xe0\xbf\xd5~\xeaX%\r+z\f+\xbf5\xb4\xb8\x91\x93fe\xe8W)\xbc\x80\bW\xc5I\xd5rk6\xcd*\x01\\Dp\xaf/\x92(x\xee\xf3\x19^\xedk^hז\xfe\xf0X\xfb\x17 \xbe\u007fO\x93m\xac\x1b(\xdaq\x00=ϵؑ\xf7\xaf\xeb\xa0\b\x81\xbc\vJޗf\x82\x00?D\xffy\x12\xf1K\x8a\xa55;\t\xb2\xb4\x03S\x90\r1\x8f\x06\xac\x95D5\x00T\x91\xc1z\v>\x9f\x04\x85\x82\x04\x14Y\x9a\x94\x81vC\xa6C$\x00Q\xa2b\xd1\xd1\x15\xac0\xd5\x1dԁO\xa3=d^\x9ej\u070e7\xa3j\xe4O/\xbb\x84l\x97z\xf9&;\u052d\xd46O\x11\x1cي\xc22.\xf3\xfe\xf6\x8dχ\xb9%\x1d\xa8\xaci\x18\xc3L\x1cvDW\x83FE\x90(\t\xb2xg\x00\xda_\xdd\xcbeTj\x976NU(T\x8b\xce\xd3\xe2\x9e-\x9f99\x89˽\xb2(O%?\xcc$\xfe\xc8u\xbcd\xf0\x83\akg\xe6\xef\xa9\xf8!K\xf9T\xf0≮B\x8b\x1dg\x84`{\x99\xd8\xc0>\x84\xefie\xa1\x00m\x1bi\xe1\x0fZ\x8c\x8e\xd9;\x8a\x85\xf2\xd8!\x04P{{6\x83d\xd9e\xf7tN\xd1\x17\xe20\xbe\xe1\xfc\xce\xcf'\\\xafd\xc1x\xc9{\xe5\xa2s\xa5\x18ɳK*\xd8\xc9+@\x10\x8d\xb9z\xd8w!\xcdu0)\x05\x1e\x9c\xfa\x1f\xf5ڛFp\nV\x16\xe1K\n\xac*\xaag\xa8o\xaa/\xe0\xe4\x06\xeaM\xca!q\x92ʫ\xb5\xa4\x15\x9d\u05c8*b\xb4S\x9bz\xa3\xaft3\x96 \x8f\xf1\x03F\xf2\x1b\xc7\u007fрe\x00\x0e\xbb\xc9N\x9b\x9f_\"t빎{\xe2=\xa4h\xd8K\xa2&Pd0(<\xd9eQ\xbd\x12]0A0\v'\x02\x03\xb3\xf9gh\xd7ɩ\r\xce\x0fy\xb1\xa2\xf8le@!e\xbdPq\xa2\xd2:J\xaap\t\xe5\r\x1f\xef%/\xbc\x9b\x81x4&\xabj\xa9\x8dr5]ʏ\x14F\x9a\xbd\xc1m\xe3\x1a\x18\xee\xdf\xf0\u007f\x8f\xafq\x1fW\xb1\xed\xa4ӓ(\x05\xec\xa0\x04\b\xdf\xdd\x14*\x84E\u009e\xec\x1f\xcc\x14H&\f\x11\x06\x10xA\xea(\xe9\xc5\xd60\x1d\xfc\x82\xae\xe4\x87\xfa\x1a\xfd\x94\xa2\a\x02\xb3A8\r\xf2:\xc9\x18.\xa1\xfa\xa6{\x1f\xad\xd9/\xb1\xf3\x8c\x8d\f\x8e\x88\xa2 \xfc~\x89[dP[\xa5Iw\xbe>\xc5\x13β\x97ա1\x0f\xa1@\x15\x940~\xe4\x03G\xa4:ݰ2\xb9%\xe3\xc2\\\xb7\x02rJ:\x00\xe7\xc6\xc1\x88n\x93\xcc\x1e!\x9b\x97\xe3\x94\x19\xe8\\/MbTkV\xac5\xd9\xfan\xc1\x87\xb9\xe9\x10:\t\x83N\xb2H\xe0\xf1\x9ei\xe2\xacm\x83\xf9\x94\xfb\u007f;\x99\xa2\xb2*t\x06\xefX-\xe6\xb2tM\x9e\xe9\xb0\xd1Ȩ\xd9\x1f\x1bC\x01O@(7=\x13=\xccp\v\xaa\xc2\x13\xeb\xde\xfb\xc2 \xf3\xf3\xfb\xae\xf2\x83`d\x8a\x1e\x9a+v4\x9c+\xe36)=13S\xfa&H\x83\x83$\x9e\xcbb\xb8\x81\xe1\xeakY&*șѨ\x9a2\xa8\aiY\a!V-<\x97\xd8Ik\xebCju\xbc\xc0\xc7hz\xe7\xd5E\x8d\xe9\xcd]vN\x9b+\xe2\xec\x9b\xfb\f\x1e\xf2\xeb\xca\x10RI\x98+N3M\xda\x1d\x92Pf\x1c0\x9cޘl\x84\n\xf2\x95ڞ?t\xf96!p9\x10s*M\x8e\xac^\x86\x8b\xa8P\xf5V\x01\r.\xa3 \xaf\x16\u007f\xacax1\x041`\x85\x19\x146z\x13a&\xd3q\x1a\x8cg\t\t\x8f\xf2#\xf3\xa5\x0f\x0f\x0flf\xfa5\xeeÃ\xe4[\xa8\xfd\x92\x05pt2o\xb6\xaasp_\x87\xecL#\x90do\x9c\xdaH\x1e罎\x10\xac\x04u3\xf0\f\x13\xf4|8\xb1\xe1'\x14\x88\xc2T\x9d\xa2\xf9,\xcf\aʆh\xef'\xc6\t{&5\xd62\to-_\x04\x04\x8d\x8a\xbb\x1dv\x88\xccF\xa0\xb6\x8b\x03\x82\xb5 og\xf1\x8c\\b\x9f\xe7&\xcb\xc1ч\x90\x04\xddC\xba,\x82\xa0e#\x01)\xa1$n\xcb\xea\x8e\xd2\t\xa4\xfa\x18hM\xa5\xb3a<\x8ez?\vk\x15Fo\r\x9b\xe3\x16\xc2IP\x06x\x91\xfePO\xf9\xce\xd9\nV=\x04|<\xa6\xd9\xcb\xe1\xc2f\xb6\xae8r\xda\x1d\xfb$\\\x16\x0e4\xd7\xd1{<ʌ=i^Q\xf2\xc0kJ\xc42MT5>\xf9o*\x10!\xe3=\xe6N ٸ\xfa+eYqP\x8e,\a\xeb\xb4\xca\xc4\xcd #hT\xeby\x82\v\t\f\r\xc3y\x8b2ڶ\xa6\xc1\xd7\xc53\xc05\r\xec\x1f\x1f\xf8\xdfK\xf6\x90\xa8\x1d\xa2C\x90\x11\x96\x18\xb7L0\x8c\x99O;,\xed\xed1\x14\x82IG\xb6מ\x98\xef\xaf\xfe\x11\b\x9dǵr;\xbb\xb4%D\xc0αF)U]hn\x8b\xd8\n\xb9\xaf\xf2\xf4R\xc0_Au\x04\xab\x92\xbem\x05\v\x01\x8f\x92\x01\x98\xfc~\xf0A,7 \u007f\xbc\x18\xf4\xd0\x11\b^\xc2\v\xad\xbb\u0560/\x99H'\xed\\\xeb\xadҼ\x14\x02\x04\v\xbd\x01B\x00^\xacL.\xa4T \xeb\xd2(\x06\vu\xfa\xa4\x9b\x05\xfeW\x11u\a\x82\x10\xc0\v^d\xac\nQ*I\xc2\xf8\x81Izc\xa2\x872\x92 \xb8\x8eڽΪ\xd79\x91\xbc\v(\x98\x01!J\xf2\xc0\xb0`\x92\xfdL\xc3k\xfb\x82\xf4Y\x90\x88\xb8fGWR\xa3Є\n\xc0H[\x9bC\x14\x01]Ii\x85\xcbٯ\xa2T\v\xb7\xa0\f\x87ƀ\xde\xc2f\xd1P\xd5RF\n\xf5\xb6\f\f\xa8\x96>\u009e~\x1b\x04^\xe6!z\xac\x14a\x90%\xa4\x06\xf1xl\xb9% \x8a\xbb\r?\x10\xf0\xf7u\xcdX\tΰ\x05\xbe\xbe\xa3\xcd+,ʺT\xf2\xbe3\xa2`\x92\x9b\xacl\n\xa26ˀ|\x9e\xacSb\\\xb7\x1b\xf7\x84V\x00K\xb4\x94Z#\x12\xbe\x8a\xa8`7J\x03@\x04Bb\xdc\xcd@ٕ\xe8\xa1/\xc9\xda)2\xc2\x01\x05\xd4\xcc=\xdaL{e\x9f\ap3\xf3\xd91\xa8:\x1f\x91\xfay|Z\xe6~\x14\xc6M\x1c\xacV\xbaN\xaf\xe3t'`\xe2\xa01\xa4F4P\xa9e\v\x87?\x10\xfc\xed㔎O\x01\xc4\xe0\xfe[h\xc1-\xd0;\x17!\xe2\x9eG\xc0\x00\xd4\f\xb7\x01l\xd2\x05\xe1̚<\x93\xbb}<\x92\xf2\"Q\x1d\xdb\xda:\xf7s\x9a\x98\xd4'\xbdU@\xa3}M\x88\xb7\xdf\x11D\xb4N\xd18\xb8\xb3\xa4~$\xa8\xed.\x1c\xfd\xe6~\x0f1t6g\t\xb5/l\x14\x9a\xbd\x1a\x850?l\x1d5\xcb\xef\x0f\xe8ĝ\x94\xd39;\x97\x87\x1eב\x8f\xad\\\xb5\xec,\x9e\xeb\xbfB\x16\xf0J\x0e\xb4KD6\xecl\xf1\xb9\xa4\xee\xf1%\x83\xd4\x14\xb9~\x95\aә\x9e\aptVbg\xab\x10v\x81\"B-\xc4U\xe0\xd7\xc1\tʘ\xcf|4?<\x16\xaf\xa97QL\x93\x85\a9|\xae\x14©c\xa2\xeb7d(\xe0\"\x92\x1db4+\xf8i[\xdc\xea\xa36\x1dZ)\x8c5W\xfd\xae\x9c\x98 \x9f\x15\x1e\x89\xe2\x14o\xc0\xb6.\x1dVh\xa6\x92\xb4t2,\x9aC\xcb\xe0\xf9\x81\x00Ǵ\x92\xcbA\xf2\x98\xa3\x02\xf1\xfeR3\xad\xea\\\x12;\x85\x10\x87\xa1Ŝ\xda{\xeb\x01!\x98\xf9\x95\x1b\x94\xce\x19\xae\xbaћ\x0e\xb8$a\xc5FL\xf5\x8cu\xf2\x12\x1b\xd8\xe6ӫ)>,k9\f;b\t\xb8$\xa57\x8e\xd0\xc2Y$\xd7\x10\x85\xb6\xa2\x8dѭR\x01P\a\x95\xd2\xc21\x03_\u007fA\xb4\x1ff\x81\xc2\xd9\xd1\xccKT\xff\\\b@\x00\x82\xd9\xe6\x9bX\xea?\xa4N\x17*\xe51b\xba\xda{\x8b\x06z4m]\xe5\x83\x1d\xc3\x1b\xf6\xe3\xa2\xc4a\a\x8b\xd9\xe6\x88WQ\nz\xfd@5\x16\x97\xaaNH\xfeJZ\x13\x955Û\xfe0\xbbL \xffP\x13\xe7(\x9dc\x12\x02;p\x0fG\x98\xfd)N\xbe\xe17nn\xc8\xd88?\xe2\xe1Ы\xa2\x99\x04\xa0\x88D\x0f\x18\xf4\xec:eN\xeb\xc3\xee#\xee\x87\x1e\xfa\v\xe0R(/\xf0H\xa3X\x0eG&\xbb+\t4X\xd2\xdf\x01.3|\xfdWZD\xb9\x99\x04;\x9e\xe6\x0eq\xed72\xe0jR\xf0\xc0\xd8\x10\x0e\xb0\ued60\xf8\x04\x94,ٱSC\xa9\x92My`~ku\x9a6\t&IJa\xdbɿa&\x9d2̴\x10\xb0\xc4JQ\xfa\x87\xd5xW\x97#\xe4\x15\xaf!\xf4s\x87\xecc~.\xbe\xec\x0e\x11\x9a\x14\xe1ԖԤ\xcc\x0eA\xef\xb86\xd2k\x83\xac\xbc\xc6\rΨ=\xfc\xa6>\xc5\v\xee#\x90\x94\xf8r\n)\x9ey\"\x99\x14\xc5d\xcf\xd5\xc1\x029b&\x97:\x80@\n\t\x93\xc9\xc4 \x16\x9d\xee\xe8\xee\x15\x10Oy\xd5\xe3\x89f\U0006b484\"\xbeK\xd8\x17^\xb8>L\xa3\x9a\x16\x02\x06\xf1\x88\xbb^$_\xad\x1e\b~\xd3tBzhOB\xcf(\x89\x94\vja\xe0\xdav\x93\x11\x93\x99݊\xb4\xd1\x1e,\\XP9^\t\n\xd0y_\xa6^\xb8H \xfa\xd8\x00\x94f\xab\x17\x05\xd6\xd2c%R\xd5Y\xe6g\x8f\x9b\xf3\xc4\xebEp\xe5Qm\x8b\x05\x17\v\x9f#{,D\xfd\xe9\x8f]\xa9\x8f\x13\x97\xa9\xaaR\xa5|'\x1c\xc7,I\x06\x01\x12\x0e\x98\xa6\x19\x1b\xa8\xc8\\Q5m\x9d\xa3\xf9%\xdc0S\x0f\x95W/\xe52\xe6\x8a`\xe9N\xa7\x03zpY\x1cR\xbc\x12Gad\xd3<(\x02\xb5#$\x82\xb8\xe3,\xc1\"\x12\xa0\x94m\xccQ\x1e\xc8.C\x12}PX\x19(\xeb\xaao\x8b\xe4\xdck,\x99\x1aމ\xd4\x0e\xd1,\xd3\n\xcflߟ]_S\x90\xaf$\x81\xc3\x15\xaa\r\xc8w\\I\xc32,\xe2\x8cVR\xbbML\xf1\xc3s9\x17R\x02Ad\xb1ްY-O\xc7\xce+V\xb7\f\x068D\xcfK\xe2\xe1li\xed\x9a\x15\xe5\xeel\x8a\"\xb2\x0f\xa4u\x18\x8a,0\xffl\xbd3\xdb[\x9d\x9e\t\x83v\xe2\x80l\xb7\xa4\xf7\x84\x06\xbd\x05+\x1b\xf0\xa2\xa9.\xd1m\xd5\xc6\x15l\x14j\x9c\x8e\xc2:l,\x11\x01S\xb8\x9d\x90\xaawz\x86\xf1&^\x16$\x06g<´\xa0\xf2M\x1eV\x1c\xc0.v\xc5*Ą\x8805:\x88%?ҩ\xb0\x1c\xd8\x19\xfedw$Ю&\x17E\bk\x10\xae\"!\x82\xf8\x98H\x1fe_WA\xc1\a\xa0C\x95\xd9\x02\xc3b*\\(\xea\x1e>\xb2U\xb0l]\x01\x80\x91\x18\x14Up\x1c\x85\xa5\xd8n\xb4\xfb\x99\x82\xa51\x98\xc1\x8e\xb4\x85\xc1A\x0e\x1b\x16\xc4z\x93yKnV>JOҳ\xb3U\v\x93X\x0e\xdeּ=\xf7\xd0a\x82\xbc\xb1\xe8져;I\xa3[\x14\x13i|\xeb\x9b8#\xb7vg\x1cҢ\xfc\xc6a\x9a\x80\xa7\x16\xacFj\x01\x8b\xfb\x97\x85\xbf\\\x95\xa2]\x88\x9dJa\x96x\x87\x19\x01410\x13Ci\xfdŻ\xdbи\xbc\x9b9\x9e\xfd\xe6\rVp\xa4\x12\x1c\xa5ʭ\xdcfC\xa7\xa5]OV$\xae\xa7ӈ!\xf8v\xbao\xd2\x06\xf4\x87Qx\xd2+6T\x02B\x8d\x81F\x8b\xeb\x13\"a6$\x83Q9\xd015؛j\x1bPB@\x99\x8e\xa4\x8b\xbeM\x99\xb2L\td?31\x99HX\xdb-%\x06p\xbd\xfa\a\x9e\x89'\a\xa7\xfeEOh\x9fMBiJ\xc5\xfb\x8e\xa1\x82\xfeCR\xa6\xe7\xf8\x134\xba\x84\xd0\x1e\x89\xd3\xe0\n#\xaf8nBNu#\x9c\x1bD\xc3\xc7o\xc1\xb9\x89\x98\xb7{\x83i\xd4=\xf0\xb0\u007f\xdcy|\fP\x9d\xe5\x95\xf1MR\xc6\xe6\a\xb9\xfa\x80\x99\xd0\x0f\xea\xa9\x10\xf2\xe8\r\x01Y\xd2|9ZY\x8a\xb2\x11W\xe4\b<\xf7Q\xf1\xd1\xcf\xef\xef\xa1~\x16v\xe3<\x80\x19\xff7l\x03\x8d\nD\x11\t\b\x1e-\x8c:\xdd\b\x83\xa4a\x8f1\xbf\r\x8f\x04lj\x00wI\xf3\xc50*\xc4\x13}\x11O\xa5.\xa9#\xf7\x03-9\x92\x1b\xdaqp\x16\v\xf3=\xbf\xf8͊t\xcaj\xe1G\x03\xb7\xe9\xf2\xce\xf8\x97+\xb6\xae\x83\xf4\xf8\xafa\xca|s\x1bB\xab\x1b`q\x9c\x9f)h\t,\xec8A\xecC\x14\xe9i\xfd\x94!\\\xb1)_\xa2/\xd3\x19`\x97\x12\x86\x1d)\x98\x82\x88\x9f\\:\x02\xbc\xd93+o\xc1\xe0\x19\xbeУr\xd4\xdb\x10\xf9\xafb{\xeb2ת\xb7\xdb\xc9|r\x85\x1fא\xb4\x02\xc5\xe1\xb8c\x89i\xc8Y\xf3h\xee\xa6\xd1W\xaa\x8a\xe7d\x0f\x97E8\xc2@\x96H\xd7l\xf5\x02\\\xd2\xfe\xae\x82\xfa\x87\xc5\xed.\v\xb4\xa1\xde\xed#\x9a\xf0\x1amn\xad4\xc1\xe1\\T\x11%\x0f\xd5\x1d\xa5*8\xc1\xe4\xd1\f\xfd\x8a\xc2!\xbc\x86\xe9\x99\x15\xfe\x1b\x04\xb1\x1dI\xbc\x99t\xc5I\x03\x9e\xa0\f4\x94\xea\b\xa4p\x8dP\xef)~$\x80j\xe6`\xe5\x03#\xf9G\f\x87(y\xe7i\x8e\xe0\x88\x1a(\x8a5\xa1\xb4\xf7\xcd\xff_\x10z\xe8f\xe7\x9f\xce)\xcc\xe1\xeddR\x9eƲ\x98\x0f\x10\x90*\x82\xa4\x8d\xa6 M\x03\x936a\xea\xe5\x1e@\x1d\xfa\x17z\xa4$#M\xd9-\x0e\x8e\xb9H\t\xde\xeaw\x92\x16Bi\xf3\x85\x1ej\x01\xe2\xab?!#\\\xac2\x16c\xa6\xd8\xdeYZE\xcc<\xdc\xf05qRc\x98\x83/\x19\x9f#G]\xc4\xf49B7t3\x01\xa9\xb9\x8eɎ\r~\xb5\xd9\x11h\xb2aL\xf8ؤ\u08d8e\xfd\a.\xed\x8dg^z\xd6X\xfbK6\xa8Y\xbd\x1fb\x00\xe9\x00\x03x\x1c\xb5\x92\x066\xe4\xd1m\x8ct\xd5\xf4t\xe9\x10Z:t\x88\xa7\xab\x145\x98\xf7q3\x8e&\xc6my\xdd\u007f\xb8\x16\f%+a\x87\xaad\xf9\xa0\x90\x8a\x15\x91;\x8aP\x93\xd5\x18\x86\xd8\x05\xf8\xe4g\xb3\x85\xdc\xc7\x03\x81\xf1\x1a\xa4DQXi\xb7>\x13\x9fUn\xf9B\x88\x8ai\xa3\x85\x10)/PCO\x19~s\x92[M\xdcm\xf0Xψf\x8f6\x9f\xfc,\xf5\x15\xc1\xa1\x9fp\x95\xc5?/\xa2\xe8\"\x886{6s\x8eq\xf5\x81\x15@\x17\xd2\xdeVW\x9d\xff9Z9;\xe8 x\xb3=?H\xe1\x9e\xf0}a<\xa0\x06\x02\xf9\t\x9c\x873\xeb\x17M\xed5\xd3\x12\xb3\xe2j\xae\x16:\x1d\x96_I\x11\x1fn\xd17\x17\x81\xb3\x01\xe5\x89#\x06\xf9\x03\x90Ӽeb\x87\xa6\xb5\x9c\xe9# \x8c\xc6\r\x1f\xef\x91\"\x03\xf2Կ3\x83\xe6o\x9a\xd6\xd4\v\x880}kY\xfdT\xf2\x86\xbbE\xb8]Y\x82\xd0\t\x1bD\x9e.\xa8\xcf\xe7\xf4><ړj\xe0\x96\xd2ڈ\xfc\xb5ԟ*bXl\xca\xfe\fJ\x95\xed7{b\x99\x0eƊB\x18\x99q\"\x9f\x18K\xa3֖\x15b\x8c\xaa\ve\xaa\x82VG\xf4\x99Hs\x88\xe5q@\xc0\f\xe3\x94\f\xb1\x86Π*\x83\xe8\x9d&\xf4\vA\x95\x01\xc7Q.\xc5T\xfd\xdc\xe5lI\x85:\xae\x8e\xc1\xb3\xcd\xf0\x1bX\xb5\tk1\xd7\xfdq,-\xf2S\x0f\x98|\xe8l\xb2\x1e\x13;\x91\xdf\x1f\xd1¾jI\x06F\x91\xcf\xc1T\x82\xfc\xf2\xf1!I\xc1\x1dt!\xf0\xcd\x1aJ\t\x1c\xa3\xecb\xce%2\xcf\x0f\xaa\x85\u0379L)\xfb\xf54\xa7\xeb\x93v4@\xa4\xb9\x06\xf1\x8d\x86\x0f\x19\x06\x19\xb6\xc4\xf9\xac\xda7\x14\xd9t\x9a^\x83\x91u\x8byӻ\xabE\xc5\u007f\\\\yd`Y\x87\x82\x88R,\x1c\x106T݈\x88\xb6T\xb6G\xee\xa9\x16\x9d\x02H~\x83\x10PZ\x97\xf0\xfc\x03\x1d\xf1\x94\xb8pк\xa4\xb6:\xc9F\xb3Ӷn\xa0\xf6(\x021\xff\x15\n\xd7^\x91]\x05È\xb9\xf1\x8dm \xd7\xcfQZ\xea?\xc8\x02\f.\x86\xa1\xc7S\x01\x14Rex\xf9V\x86\xf3t\x1dX\x80\xb2\xc8\x1e\xc9dJ(\xe6\xbfN\x84y\xddB\x11\xed&x\xcf\x04\xa4\x84>\xd9ѦB\f*\xd5=\x0esG#\x1b\x8f\xe0\xea\xa5,\x9cV\xb8pk\x9d\x11\x87L\x06\t\xd8\xedC0L\x13]Ƽ\al\xd04,\x9a֥l\xe2\xec\xb8za軚\x99\xb5\x1f\xe9\x14h\xdb\xd2|T\xc2;S7\x14A\x91\xf1t\xd8\x03\xb8\xe8\x9b\xfc\xd0A\x95\xf2\x0f\xc3\xc6e\x85\xf2Ԉ\xa4\x8eN\xe6\x16\x85\xa1U3\x87\\Q\xd1Z\xb3\x86\xa1\x88\xe0\x9f\x1bBd\xe0\x19\b0\xbfJ\xc0\x17\xd4\xf1\x18\x19E\x84C\xa9DHXE\xbc&\x12\x12\xf5\xfb\xd9\x1b'a\xb0ݓv\xb0\x02J\xa5S{\x91\xfb\xd8\x1b\xe3G\x14\xee\xdf!HfqB~\xefG\x81\xf1\xf3\xd8\x1d\x82a\xa6S\xb8\xe6\x12\x81K\x9f\x14\x1a\a\xcb\xc8\xfd\x8c(3\x8du\x8f\x96\xf5\xdd]\x9a\xbc[\xe0\vc%\x1b.\xed\xed\x95O\n\x98\x8dv\xdc'\x17\xa2\xe7\x83\xc0\xd1ũ\xf9\x9e\xea\xb5;hӲ\xb4i]4\xb4\x97\xc0\x01\xc7\xc6â)ӫb\x10O\xa9t\v\u007f0\x983\b\x83*◙l\xcd\n\xed\xd0v)u\x10\xf0\"^\x92\x88\xaaM鍮\xc9e[ o\v\xaeg\xf7\xab\xca\x02ћ\x1a\x1a\x00\xec:-\xe5yB\xff\x9c\xceW\xd17\x03v|6\xd3u\x1f:\xb0\xb5XY^\xf2\xeb\xa7V~J\xcfO8\xf7\xb4\x87cX\xa4\x04]\xf3 \xba\x89\xc0D\xbb\x0fO\xf5\xc942\xcc\x10M\xea\x81Jp.\xa9\xe6K\x97\xf6\x80\xd7\x14Z\xb4I\xfc\x8e\x91\x91\xca\x1c\xac䊪s\xb2\xc6~\x89\x13\x02\xab\x18Q\x17\xc9I\x82\xa2&&\xc7B\x04\xf8\v\x11\xba\nc\xff\x18\xfb\xbc\xdc\x10\xf4\xc81\x909A\x99\x92\x145\xdfV\x86\xd0\x12\xe4\x11<1Q mX\\\\A\x83$\x9b-\u007f\xf5\xc0ȓLɱ;`\xed\xc0\xea\xdc\"[\b\x03J\x19\xa4#\x8bI7}\x97\x1d\x98\xd7/gQ:>\\\xe3)7\xb0\xd6\x19\xbao_\xe0N\x9av\xe4\xeb\x13\nz\xdcU\xe5\x17\xae\x1a-\xeb\xf9\x12\xe4\xa3A\x1b\xacK\xf2\xc1\x17Q!\x937\x10\x89\xc032&\x95\xc3\xcaL\xa7\xdfi \x8a3@\xdfђ\x1aOD#\t\xbc\xf6B\\t\x1b\xe2$z\xcdΠ)\xa6\x939\xd4C\xc5\xc9\xdbSI \x17q\x95\x80\xb0ӲTpv\xe5\xb5\xfe\xf6\n\xea\xa7J̔\x18\t\x17 (\x17Z!\xebD6.\r\u007f\x19Q\x17\x1f\xf7ݦ\xb2\x8f\x98\x89pE\xe4\xf6\xcb3U\xdcF\xcf ,|\x13m.\"z*\xaaAȁ\xfa\x15\x05\t\xa0\xf0\x15=\x11l\xe4\x01\xc32Ӻ\x01T$\x02b\x1d\xd1\xc2\xf40\xbc\x13\x8bqa \xb5\x06+d8\x14\xc1\xb5W\x92je\x857\xaa\xd0i#\x94Cg\r~\xa6\x93\xb8\xf2\xd8\xda\x1bC8\x95\xec\x04$\xab\x19\xbe@\x88-\x01\xbcF颀\x82\xce\xec\x82HB\u0529\xfb$v\x1c\x0f\x91\n;Y\b\x1c\xed%\x8cX\xe9\x924\xa2$^\n\x9f\xccݤGU\xa7\xc0\x1b\xc7`*G\xe6\xe4\xf5\xf1\xceS\xfeq\xf4\u007fe\n?\x9c\x11\x9f\xa9K\x18h\x1fq\xbe\xb2\xc3\x16\xe06\xd6z\xa5\b\x1a\x99$\x89|K!,Fj\x80v瓸/2\x91\n\xef\x12Ջ\xdc-\xf2\xd1\ts37\xb8=A\xb3\x12\xc2Dz\xa2\x94\x12\xee7\xe4s[\xa2N\x90\xb5}^aD\x04Dd]:ح<\x03\xa2\xe4 \x1e\xe2\xa4fI\xbf\x10gIa\x1c\xe6\xd5M\xe9\xbe\x00\x9d\xeb\xe7\x0fRR o)7(\xa2<\xa9\u007f <\xd8\xdf/\x0f\u007f8!\xbb\x98Q7\xe0D2c\x16D\xc2\xdb0\x80\xfb\b\x1a\xe9~\xd9\xf7^\x122\xa9\xa6\xdf\xee\xa3!L\xd0l\xadS\u0558\x9d\x8d7\x91\xd6\xc45\x87\xbbV\x8a\x9d\x9ażj\r\xee\xcd\\\xb5\x04z\x8c\x147\x1e;p?\xd1aR\xc0\xc7\t\xa6\x9f\xe3ZS\x16\xabviT\xef\xaf*\xf6\xbfka\x9fN7\xb1\x91V\xc2.\xc0\xef\x17)qq\x00Y:\xf9yH\xae\xf7w\xd8\xc8Db\xb2\x84\xd1\xe3\\\x91\xf99Cs\xdb\xf7k\x9c\x8f\xed\x13y\xad\xac\xc0 ڗ\x04\xae|3\x90\xb5\x9f֣q\xe2\x1b\x9a\x89\xadߖ\u009e۶R\x9e\xdb\xc0\r\x9b\x9f\xec\xfa\u052f\x16F\x19jZYRB\xa3\xdb\xceQ$V'\x82IXiY\xfb\x831\xaf\x11&T\x1a\xa1\xe5\xc1\n\x1f\t\xedLGd\"\xa7c'\xb1\xc1\xfc\xb9\x800a\x12\x8c\x87\x06\xe5\x10-\x8a%\xe3\x9a?C\xaa\x9d\xbd\xc01\xc9ʦ\xe01\xb0\x00 \x97\x80`U\x80\xc5\x18\xf0\f\xc2\x01\x95~\xe6\xd4\xf4\x06f\xe7:\u007f,a\x96j\xadX\x80\xea,\xf1BnQΥn8\x88\xba\x89O^X\xbc\xf8\xc0U\x8c(\xa2Y\xfc\xae}\xc6\xcb2Dx\xf90\x11\xea\f\x01\x83T\xefV2F\xa6\x0f-v\xd1x]\\\xa4\xc1\x80\x0fG)\x98\xaa\x9d]\tw{\xf7\u0381p\x1e\x8dS\x15Q\xd80Nz\a\x1fp\b\xd4\x04gq\xa2cW\xefо-\xca]\xab\xb7\xdfX\xc0\xd1`W=\xb90(\r\x17\xa8Z\a1\xd6ȇ\xbb\u0603\x917J\x14u\xcf4\xf9\xads\x923\x8d)\x8b\x85\xd8>fL\xad\xf9\xca\x00\x12⧻%\xa8\xae\b\x9b\xec\x02\xf4\x1d\xea4\a\x11\x06\n\xe1κ\x19\xa6\xc8\x13\x91)\xee\x9a,\xfa\x85'\x1a\f\x0fƌ\xd4\xe2!\x03\x039\x971\xbf\xd3\x1d6Ng:\xc8L-\x1f\xf2\xda\xcea\x0f\x05\xeanj\xa1\xf7\xf3¬\xfc\xbf\xa8\xd8f\xca\xd1\xdf\xe6\xbb\xd6Eѫ_\xe4u\x93x#\xd3i\x93I_h\x02\f\xfdD\xb9)\xfb_!v\xf3\xf1\xb2㧧\U000cb357\\\xb1_\x00*cJL\xa5\xc4\xe1 \xb5\x836\xf2\xb7\x83\xed,\xb7\xca{\x90\x10H\xfe\xa1n\x02T~\xab\x89\xa0J=40mK3\x83\xb4\xdf`\x86\xb2K\xd4\xc7\xd5u\x9c\xb5\x026%#Z\xef\x81\fP\x1c-g\xa5\u07ff+\x12\nƾ6H\xff\x1b:\x81\xc7ᒨ7\x8d3\"F\x05\x87\xfe\xe0\xcb{^,k\xbe\x19\xeeg3\xd5I\xd1?\xed\xaaג\x03\xb6\x1c֪P\x89\a\xdbt:\xd9\n\xab,\xfe^\xc1[\x91\x02$\x88B\xaa\xa5*.\xa5/-\xd7@\xcd\x05&\xac\x9c\xc0hq\xea\xf9\x95Z1F\xbe{\xebs\xedG\xb0\xd3\xd1y\x94\xed\x04\xc1\xdfސ\x1b&\x82\xe5\x9b \xf5\x15)\x92\\噅\xa9\xe0\x9b\x90\xba|@\x13NI\xb1\xa9z\xfa\xdd\xf85\x92\xb0\x9c\b\xa6DH\xff\x14A\xaf\x06\xb5K\xe8y\b\xaf\x90 \xb8\xa5tDeZ\xc1MD\u0528\xda\xe70v\x91Dcr\xdcȼA\x89\x9bMk=\xac\xd5\xc8\x14\x06\x97Y\xaa\xd7/\x9c1G\x93\xfdD-G\xf3\x12nO\x9b\xcd榁h\xa0\"\xf0\\\xc8!\xa3X\xc0\xc8<\xea\x1f\x81\av\x1d\xe2\x82\x01\x88X\xd6JX\xfe\xb77\xc1\xe5\xd9\x11\x8a5.\xea\xc1K\xc0\x01\f\xfb\x95\x92\x00F9\"\x13\\\xc2\x10\xde+\rn\x8a\xe7\x95A\x94|\xbfŵ\x92_\xc0\x98J\"\b-\x95\x96\x89\xc0\xb1\x11\x1c\x01\xf0\xe8#\x15\x06\x04\x19/G\xc2Ƣ\xda0\xbd\xea\x10Bmh\xe0\x88&>6\x89\xa1\xab-\x1c\x05\xb6\xc1D!\xa9tF#ػ\x02ܖ'|\b`\xbf\xc3[HJ\xec\bm\xa0E-\xf2\x1d[CH\x000M\xec\f>\xd2CC\u007f˓\r\x06F\xc9\xcf\xda\xf4Q\xc1\xf6ؓ\x05n\x15\x17\xe5\xeb\x84h\xf6)\xb4\x13Մ\xe8K\xbf\xcb\x03\xecga\x18\x82\x87&\x9f\x18\x94\xdcaA\x85\xe03*\x1d\b\xf5m\x90\t\xe8\xe2d\xa3\x92\xe7\xd8g\v\xa7\xa4\xef\x81r[\xf35 \x15#E.N\xe4Ljh\x9fm\xc0[\x1d\xd2\"쓺l\xb3\f-sn6;\v\xb0\x8dƇ\xc2Wӏ\x86\x82\xfb\r\xe1\x1f\x02]\f\xbcg\x95\x92M\xa7\x1d\x18\xe5n\x906\x10}(S\bd:\xd2\\\x96\xd0Y\x01\x91\x8eW\xfdw\x92h\xc6*\x96\xba\xf6p\xeex\xe5Q\x12\xa0\xbdM\xebe5\xae\xff\x8c\xa9\xa1ʔmUs1\x93\x03LC1\fv\xad뎻\xe2\x10\xa9\xdea\x9f\x19q\x98CG\t\x13\xca\x01\xf8s抵\x80:\xa3\xb6\x8bxc]\xb4=\x98\xf8\x0e\x99\x06\x98\xb9\xfb\xc8\u058e\xf9\xe4\xb0\xdc1I䊱\xce\xe3\x151\x1a\x03;[\xf6p\xeeI\x1f\x88\xc5\xfa}\xee\xcef\xe8\xe8\xaa8ؔ\xa8S\xdaC\t\xc2044\x1b\xf8*\xb3vTƎ\x98\x1aO\x833\xaa\x90\xfa0>\xa5\xf9k\xd0Q0\xaf|\xc0\x97\x06\xc5N)&\x06V,\af\xf5\xd8<[\x12\xe0\x11\xf1+\x85Ш\xba\x1e(\xfd[&Ճ\x8e\x01\x90=H1+ET\xa1}yyd\x8ax\xeeC\xb4\xe8\\O%?>H\x94\x95\xcb`\xcd\xeb\x04\x97\x0e\xdc-\x82\x06\x9fΘ\\_\x86\xfc-&\xf1{c\xc8\xc3\xca\xeb\x9f(\xd0i\xe9\xf7>\xada\x0e\x84\x01B\xc4\xeb\xdb\x1d\x92\x8es\xf4\xae\x1c\x83M\x8b\xf3 \xa1\x1c\xdbo\xaf\xad^\xbb`{\xe8\xf7\x01\xe7\b\xb9\x90e%\xdb\xfe]\x15'\x8e\x13\x81\x13\xbb\xca\x11i\xdeI\vM\xd2\x03*ehL\xe3\x80(}\x1e\xf25,\xc8\xe8\xe6i\xb0\x12r\x1a\x84\x03\xe0]\x02\"\xa0N\xbc2^\xb6\x01\xc6\xc4b\xcd\x0fΰ\xb7A\x80d\xe8k\xe1\xd2B\x91?.,\xa3]\r\x97\xe4K\v\xbf\x14D\x16Ə/\x1b.j\uef35\xec\xf1\x1c \x96\xa3P\xd2\xd9\x11\xa9\u0080\xa3\xfe\x84\xc0*\x1f\u058d,\x0f\x89.\xa7\xaf\x963\xa6\x1fZʟ4i\x88\xc5r\x9dƵ,\xfb\x91\x1d\xb93\xba\xf0\xf9\x1a~͚/\xc3?\xc2,I)\vk\xb0\xd3\xe5\x8a\x14\x11h54ؾ\x0f\x18\x92-0w<\x06\x81\xba\xec\x0fp\xa6m\x8f\xd7\x00n\xf1\x97@\xcf\xd7\b\xb4T3\xc3F\xc4_\xb1\xc0\x98\x9bH*\x8b\b\xf3&\xa0\xbfkdd\xd6\x0e\xed\x90\xf6\x83;\x18ߋ\xdfh\xe8\x9eҁ\x91=KS\xc4OW}5렐\xabo\xb7l\xc1\x92\xaa\x0eJ!\x9awQ\xb7Y@\xa6.+\xd3\xf0\xd32\x8fHz\xe4\xed\xf3\xd9\x00L\x00\u0094 t\x1f\x03ɴpo%I\x19\xf9\xe2\xe2\x13ۨ\xdc>\x16\xe5\x9fʁ\x04\x06\xdc\x14/\xb1\xd2X\xcf\xf7\x0eX'\xe9\xb7]\x8b\xfb9\xfa\x80\xce\x0e\x8b\x93\xf7ͦ\xd4D\xab\x93\x84\"B>\x1f{F70\xa2{\rn\xcf?\xe9\xa6<\xfe\xec\x87\xfd'\xf7\xf3\\\xf0\xff\xa4R9\xa3kr\x18e\xf1\xb4\xeb\x05\xbd\xfbt\x8f䗾\xd0f-6\xf7\x12\x8ar\xb0\xe3\xb0\xe8\xe1\xd3\xd6\x01\x17\xc0\">\x8f\x9e\u07b6\xb6-\x0e\x1fX\x87\xa8\x02Vq\xfc\xf8\x1c\xfcr\x10t\xa1\x06Fk \x14\x97\xfd\x15'\xab\xe4IX\xd5u\xcb;\xdcu0\xc22\x87\xc7I\xb1P\xc4I8\xbax\xe4\xbeVsDŽ\xac\xd0\xd2DWڿ\x96\x99D\x92\xb8\xfa\x06'\xa7\x12@\xbdD\a\xc4K\xf5iG\x9d\xb7>\xe5\x8e\x18\b|\x89߱\xa9\xf8\x01cЖ!\xdd\f\x8c\xb6R#T\xa0r\xa1\xa0*\x051\x11\x1e\xee\xc4Ǻ`\x9a\x8d\xb8\xf4Mj\x16!b\xd7B\t\x10\x15Ǿ_\x90\xdd\x1f\v\xf1\x01!\xe6y`j\xaa\xb5\x83\xf7z\xe1'\x0fc\xdbS\xe8\x1a\xb7,\x03\x00빦m\x86\x13\x00t\xa9\xc1\xba\x80r?\xb4\xb4Gk\x87(0\xe0j\xb6SE\xfd\x86\x8c\xbc\u007fL\xe5\xbf\xe8i\xfbo\xdc\xed\xe7\x03\xd86G\x94M\xaf]\xa8\xa2e\xae#sd\x9cVg{\xff@\x8d\xa4\x9a(\x9e\x15@:\xb9\xc6!_۔\xfbq\xccp\xb2\xd8\x19y\xc67\xcbP\xfcX\x18\xefN\xab\x1e\xf5?9\xa0=\xa4\xad-\x15(u\x06)ӯ\r'\xfa\x98\x9d|\xd5#\x83\x88\x00V1t\x1f\xc2j\x12M:1\a\f:fys\x04\x107n)-D\xf2돇Z\xba\xf7\xdd\xd6yc{W\n\x0e\xe4sSl\xf8\x0e\xdaS\xddp\xe2\x91\xe8\xac\xc3H\xa7\x11r\x87S@\xb9\xa0\xa5\xbdL\x17\xc0hô\x94\x9anj\x9bϜQq\xbbM\xf9Jӆ\xbeҸ\x994\xa5~b\x1aZ\xaeX\xecо\xe4%\xc7\xeb\x05V\x02\xac\x1d\u061c\xb0W` \xdf\x13\x8e\b\u007fۮ\rEC\x11\xab\xae\x10q\xbf\xa0\xafbŚ\x11\x04*\x90^@k\xf7\x13ɰ[\xc1;\\\x13W\xee\x13\x1e\xc0\x9f4c\xda{\xae\xd5\xe2\x85\x1e\xb9\a\xae\x9a\xaf[\x1dظ\x89.@\x9dN\xbe)x\xfe\a\x1aMN\x94\x9f!\xc7\b&\x91c3C\x83\x82\xb1\xf4\xb0N\xed\xcc\xc1C\xbb&\x9e;Z<\v\xa3\xb9V q\xc5I\x85\x80\v\xc7.\b\xcd96\xc6\xe2\xe1\xe2\xcbpV\x9d6\xael\x1dI\xa9A&~ῑ\tgś\xdd<\xf0\x04l:u:\xd0\xe8.[kA\x1e\"\xbcKM\xf0\bᯄ\x1aЋ\x13,\x8b\xa5\f\f\x12\x13S\x02\xb0r\x13t\xabt\x00\x1e\xe5dcxվ\r\a\xbc\xfb\x8d\xe6\xbc\b\xc4\xd3\x10\xc2\xd8\x19\x8cE*\xd6\x11@\x05\x02m\xc0\xdf\xc5[\xc9L\xec[\x15\xe7M\f\fSK\xa9\x8e\x17\x9cEj\x02B\xb4\xb3}\x1d\a%\xaf\xf2y\x9b\xac\x00tQWE\x8d\xf4\xe1S;d\x91/o&0\\hY\x10\xd1_8\xc7\xda\x17\xeefV\x16\xc3\xd2\xf3\x88T&\xb3\xe5\x17\xea%\b\x11\xcaŰP\xcbS\x86`\xa8\xa6XmQ\x03\x97\x15ARu\xa9\xa0}\x0fʱ\xe5\t\xebS:\x0e\x98\xd2~uL\x0eU\x02\xeb7/SbT\b\xa2h\x9c\xd2D\xdf\xec<\xb5{\xb0\xe5\xc8\xcb\xc4\x0f컧T\xb2\x90Tp5)\x01Z\n\xd2)a{䟯\x97T\x15\t\xbc/\xa4ɅB.XF\xce\xc4\xd3\xd2`\xe2\xa2(\x81˹\x9a\x92\xce\xc451\x95S\xb6\vLHaQQ\xc4\xf0\xdf\xe7\x16\x14\x02'\x10\x84\x93\xc4)N\xc2\x12\x85m\xe1\x05\xe22\x9du\x92\x99 \xe2\f\xa5\x00\xb7;\x0f\xc2\x130\xbb\xbd0ʹ\x00\xca\x14\xa4[\xe3\x9e$\xa7\xfeeĻ\xf1%\xfd\xa0nlm\xbc\xb5(0;\xca\xd2z1\x19\x02\t\xd2Io\xf6%\x12dL\xc6\xf2\\i+\x03\xcc\x1d\xbe\xb8v(\x13\xc6Mo\x8d\xc2\xe8\x1f\xab\x8bzg\xe2\x99\xd3'Y\x9d\xdd\x18\xa9!\x8e~\xcb\xe25\xc5\f\xea\xaeO5\xc4&\v\x16Ig{g\xeb\x90\x02*\x80P\x815w~\xaal\xe7\x17\x80zE\x80\x00\x10`m\x99\x01b`\xd9P\xc4UZ\x13\xb5Ue\x1f\x17\x00G\x82\xcf\x12\xb3D\xb0-\xad\x91:)\xa4\xaaj\xc1\x01\xa7\xa6\xf4dQ\xb2%\x18V}\xdbdV\xb7\x16\xd8fҦ0\xb1㗘bf\xf6X\xa72\n\xdc\xe6\xb9R\xd07\x10,\x8bY\xdf\x11\v.\fQM,\xc8\xe3|\xd9\xed#i$\xe2I\xb4L\x89\xb8\x1f\x81F{\xaa\xddb\xb6\xb1\xef\xa3\xd4u|e@E\xe5\x02\xacl\xf1=l$\xb6&\xbc\xb5f䊪L\xed\xfeL\xf69\xcaN-\xcd#\xf2\x99\xc5c\xd4\xf40\x04\x86\x8e\x94\xa4\xbb\x92;Q_\x82p\x04EB\">\nv\xa5\xc1\x93\x9b}\x176\xee\x92]\x84\v\xa2\t\x0f\xdac\xc0\x8c\xbf}\xfc¡\x90\xc8D\xb5q\x89\xf9|5\x00\x97\vTVo\x02\x88\x86\xec\x03'\xea\x05\xa7B \xa6)\x90\xb0\xb6;p\xd4\b\xab\xb2_c\xb0\x9ca\xc4\xc7A7\xc7\x18s_\x84\vXC\\\xa4\x1dM\xd2\x1c)I\xe9\x1a\xacI\x0f\xb3\x9a\x1d\x11矗\x94\xdaP\x01P\x90\xbd\x93\xf0Du(6\xa2\xd0)$\xb1r\x98QB\xfdTyuT\x18!P\xe0D\xfe\xd2L \x95ǒ^p\xba\x9e\f\x8b\xef\xb1H\x8a\xa2v\xd8\"\xbe\x8c\x98\xb0\x16\x9d\xe0\x903\xb3H\x1b\xca\\\xa7\x8cy\x18j\xb5\x03ҍ V\xd6\xe4\x87̀\x86S\xe4\x1a\r\xf8\x10'\xf0p\xdc\xf3\x1e\x10G8\xb0\x84\xfa\x05q\xd1\xd1\xf3\xccJɨ\xcbH\r\xf3\x1f\xc6+b\x80\x98\xdb~\xf9\x9c3\xd5\xf8\xea\xf47\x9a\xadh\x92\xb5\xeb\x1da:\x82\x1a\xdc\x1c\x0e\xac\xfbB\x9e\xac4H(\xac֒\xa4\xd8~\xb6\xe4RS#߂$\x14\x10\at\xa1\xb9\xa8\xc2\xf9\xaeP\x06\x16\t\xdd3\vqe\xa7\x879\nkV\"\xd6\x1c:jS\xebJ4\x96\xf9\xef\xaf\xca\xe8\xa2O\xb8]\x87\xcb\rn̈\xd0\u052b\xef\xed9\xfa\xbc\x10p\xe3\xf3\xe64\x19\x9cc\xf2\x84\xe3\x1f\xeaj$\xe0\xa4\x16k\xec\xfd\x81\x95\x81u0\u007f\xf6\n\xcd\xfd}֛^*N\xf45\xb0\xf3\x97Y\x01\xa0\xb6hpV3\x1f\x83\x84(I\xc7\xc8)\u007f/\x19\xa8\x81r\x16A6\xe9\xa3H\xbb\xa7\x97&%\x8d\xd2\xc3\xd37.\x9a\xfc\x17|\x9ad\x97\xe2\xda|\x9d\x88\x96\xf8,3l\xe4i\xb5\bDQL\xc1G\x86\x1c!\xa1\xd3\x0e\x9e\x8d&\xacFPG\xe8C\xe8\xf4\xa8Ajϱ\x813\xff\xd4g\xf0Njx`\x91<=LN\xbaAw\xed\xf7\x9c݆\xf2\xa9\x98\xf0\xbe1@\x9d2\x8b\xe2\x85*\xbc\xf2/$\xaa\x8bM\xff\\L\xacK|\x18X\x85\xfc3<\x90@\x95\xbd\xdft+\xbd\xccD\xe1r\xaf\xa8E\xeb\xd8F\\\xc1Q\xc1\xb6\xd7C۲\xf8\x06u\xb6\xe6\xe9\xb5\b\xc1ަ8e)\x89X\x14&\xfe\x02\x05\xa2ky\xce\xd5Ӿ>g\xfd\aD\xc0XmV@@\xc7\x1a6\xb8\xe1raH\xd1\xd1}\x8a\xe0\xe6\x02>\x11\x8d\x1b\x1d\x13y\x88\x14\xb7GE\x9eV\xd5\xe5\xe1\xfd\x1fT\x86Y\x11\xcfqi#1G_\xd4\xe8XaD _;R\xa3~\xf4}|aV\x81\x91\x9el\xe7,\x0f~\xc0\xfaAR\xbd\xb1\xc5sZe\xa0\x06Wi\x13\xb1uH\x98iycӏz\x0fDi\x01v\xbd\x80VX\xe5i\x9c\xf0`UL\x0e\xda\xd2h3\x8d\xf27\xc0M:5\xb5\xd9\xc2\xe3\x06\x19\xf0bG\x870\xcd0S\xf8I\x97r=\x992\xba6D\xb1\x9d\x0e(e\x1fJ\x10\x11\x81d\xbcj\x0eT\xcd^EY5s\x96\xd8J\x88\x03\x9e>\x1a\xc8v\xf2\x93[\xd8\xca\xf8\x80#Jƿ&t\x00\xa4&NV\x9bC[\xa1\xa1\xed1\x02\x12\xf3Ӣ\xe7H)\xfd\xe5\u007fi\xaa\xe8\xc5\xcfO\xac\xf7\x04\xa2\x14I\x05J\vW\xc3NI\r\xa0\xa4\xc0T$\xc0;\xef\nS\xa4\x85\\\xea\x88w\xe1\xec{Tm\x13\fMe\x92\xbb>n\x1cO\x13̦\x1bJ(l\xf9\x15:\x93\x16$\xc6TROܗ\xf9;\x85\xd84\x8b\xe8\xebS\x84\aw[A\xc2\x19\x03\xfdx\x90<\xf0\x16\xdaiI\xcf\xccnP\xfd\xa1\xf2\xa3\xa1\xfcO\xa5\xe8\xf0\xe0W\xee6\xf0\xda֮c\xfd\xb7\x1c$Y\xf0\xf2Ci\xdbi\x03)ٻ\x02\x8e,$\xce#\t\xd5\xffp\x18&\uf0a0\xc6J#\x13\xe1\x11P&\xbbu\xa3K(\xe1#r_\b\xb8\xbc!\xdb\x0e\xc0\x92u\x11\x1d\xe8\xdb́\xfd\xfb4\x9b\xbaW\xb0w\x16\x05\x83L\xd05q\xda\x10\x8a\x80\x8b(\x03`\xc0+#~\b\x8d\x0f\xd5K\xcf_C\t\xdc\xf8u\bh\x16\xf5\xbe\xb2\x05\xc84a\xb2\xce\x17XY\x8d \xf8\x13\x85\x02\x9f\xe5\xbeG\xab\xf9\x80T\x13\xa1\x1b\x10\x83`\b\x98\x82D\xc0\b_/ؑ\x89a=ښ\x92]\f\xbd\x13\x96P\x10D\xbd\xdf&LY\x8bm\xb0m\x8cvw\xf1I\xb6\x00l\xeb\xa2\xc5\v\x04u=\xf0(\xc0Bn\xfd\xd00\x1d\xeak\xf8\xf8\xa0\xe0\x8e\x9aU!\x91\u00a0\xee\xc3\x04\xc1x_\x00p\x90\xa2\"H\x0eF\x91F\x12o\x91A\x90\x1b\x92\xfc\x84\x93\x91>\xe6\x1a\xb2\xba\xd3\xc3\x05\xa9\xca\x1f\xe2\x04\xe0\x02\xf0\xe8\x94+P\xd0\xf1v\x13\xd8D/\x03\xc4Ԙ\xbfx\nKj\x013n^a\xbd\xee\xe4b(\xb0\xceR\xa3\x18\t{9`f\xa7\x8c-su\x8c\xf6\xb7\xaf4T\x85e\x18\x9f\x97\xf2\xb9\x03\xf1#Wr\x85\x14\xb1\x00\xa1`?\xcd)\x1a\xbc\x99x\xd0\xe2\x18\xd8ǩm\x00l\x0f\x9a\x0eM\x9d\xb7e\xaa:\x1d\xb1\t_y\x8bFA\xc2\xdeQf!-\x92 \x83\xfcQ\x89\xbc-\xc9ݶ/\x05\x1f\xf9\xb0\xd9}X\x97{Z\xc8d\xf7\x96U3\"(\aq/)Z\xd3!(\x03\xef\x900\xf4߃\x1f\\L\t\x88r\xb1\x8e\x16H\xa4\x17\xe3k$$\xa2\x91~\x03T\xce:\x9a\x81i\xb3%\x00\xe2L\n G*3\x123\xdc\xc1Y\x0f\x1ax\x1f\xfeF\x9d\xc5\xda\x01\x18\xbe^\x82E\xdaH~\\F\x96\x04\xd7\xe4\x12\x96h\xc6]\xb8\xa4}\xa4V)i㤰\x0ej\xe5\xc2ٕl\xcbs\x95y\x82+0\x1e$\xa7\bU\x90\x0f\xad\xb84\xa6\xcaw\\\x91\xca\x033\x06B\x8a\x93\"&\xae\xdfSM\xb6\xa79\x13\x01e\xbc\x8e\xaex\xf5\xee\x84\"\x0f\x10Џ\xbe\x05\x8c\u007fF\xf9S\x18\x81\\ÔK\x16\xe3~\xab\xa4\x86\x17n\x9b\xf4j\xac\x88\a\x03\r\x88R\xe8\xc5\xd8G\xf17\xc9\x19\xff\xe5\xd0\x1eD\x8a\xc0\x0e\xd8=\n\x94\xc5$\xf3\xdf\xdc\\\\\xdc\x11/\xe1\x18\xa5\xa5\xbc\xec\xd4MDǙ\xb4\xae\x06~\xd3R\xdb\x10\r\xe0\xef\x93V\xd5h\x11.\xc0;\xb6\x15\"raR \x9e\xfc\x013\u007f\xee\xbdnv%C\xdf8a\xed>\xc0\x013\xc1\xa0\x84\xe1a9\xfc\x01\f\xd8\xe4\x90\a\xa5n\x16\xda\x02f!\xc8\x12p\xe7/-\x01\xf5c\x00\xad\v\xf61es\xaa\xbd/\xf8\x8f\xb8\xd8x\bWɅU\x172\xb4\xf6\x01y\xf8\x82\xe7\xeb`\f\xf5\xb8\xc5\x13\x83[lQl\xa3\xd07\x9a\xb7\xdc\xdc\x11W\xbd?ɱ\x9e?\xb2}\x91\t!E`\x120\xa1\x1e$\xaa \x00\xa0'ͩY\xd7*#\xe9\xb8\x01=\xc3l\xa4\f9K\x01\x01[`\t\x19P3\nA\x81\x9b ?\xcc\xc5\x19\x80@qM\xf9\xe8$\b\xc5\x14\xd4\x16\x88\x06H\x11\x124\x18\xab\xdaj`i\xf4\x88\xbaS\x81]\rc\x9d\x9e\x13\xbbCvb\xf8,\x83\"\x8f\x1f\x14\xc8DRA\x81-\x11\xe96\xa4i6\xaeU)\xa3o\xa8\xae\v7\xe4\x93{U\":\xa9=\xc31\nj^\x91i\x89\xc1\xf3\xcbH\xeeȐ\xb2۰\x03\x1c$\xe6\xed`h\xea\x86\xdc\xd3\xc9d\x93\xa2\r\xacY\xfe\x03%\x99V\xd9i\u007fW\x9esp-0\x04Rb\xde\xe3^i\xa9\xe0\xf6\x8e/i@T\xfa\x8d\xc8\x11Z\xc0\x92\xa6\x99\xba\xb9qKc\xce$\xad\x0e\xdb\xdd5\x88[\rh\xde\x80̹\x87Ǧ\xb2\xc78\xe7\xf5\x86\xa2\bY\xc3}\xe2\x02\x99\xed\xa1+\\\x92\xc7\x01\xd1\xef\x00\x8c\xd9\x04\x83\xf6\x94z\xb6\xb7\xf6QX\xd6\b\xb9\xe8\x9dd\xa2\x13%\x91\xd58^\xc5l\x9cJ\xd2f\x1e\xe8\x10\xbc\xef\a\xa1|c\x10<\xf0\xea[\xa9\x1cΎ[\x00\x06\xa6z}\x82\xcc\xeb\x1a\x1e\xe5\xc1\x1d\xf0\x01q\x95\xabr\x85o\xbe\bY\xcbN\x8e\x87Tz\xe87Ko-A!\xae\x15)\xc8\b\xc1\x10c\x06Ҥ\xab\x81\xbc>\x06\xda\xdfo\x0f\xe1l\x8dK_\x96\xfbd\x14\xd8M\xd4M\x03)&އ\x1fd\xe8\u007fz~\xad\u007f\x034}\xdb\x15\x883\x1c\xe1J\a/\xafR*`y\xd2/\x95*\x18\x8c\x82\xf2\x8c\xec~\xa27Y\x1d\xa1¶\x80\xa0\x89\x9f4\x1c \x91\x8f\xc7\xdc\x18\x85\x12I\x85\xec\x04\x01\t$\x1d=\xa2\xb0\x1c\xb1&/\xc1\x93,\x0fȃ\xbd\x10\r\xe0ԁ\x8a8=\xab\xa5^\x0e\xc4Zk(\x9dN\x80\xd7\xd1\xe2\xd8\xe4\xe1\t\xa9\xfa\xd37)\xa7ۦJ8F\xe4\x87崙\x06Rht\xe50\xee\x95ڵ2\b\x06\xd2tXߌC\vTN\xa2\x9a\xc0\xbb\xaaKUn}KTNQ\xce\xc2z\x89\xb5g\xb9\x02\"|q\xdfL\xa8\x15\xba\xb8v\x81%\x87x\x11>E\x10\x97\n\xc8\x0e]ڽ\x19N9c\xbf\xc0\xe8\x89d\x82\xd9\xeb)\xafǢ\n\x843\xef\xea\xa1\x06\xdbL\xf4\xd6-E\xe7\xcd6\x91\xb7\x1bЗfh\xfa\xba\x15\x17\x84\x9a\xb6\xd5s\xfb\x18+\xcb\xc5`0@\x02z\vɬꅃ\r\xb5\u058e\xc9uU#\xa5\xf7B\xd4\x0e\x03\xe8^\x10B^\xc1O\x80k\xc67\xe02\xc2\xf5\xaf/Ej\x9d\xb4\xbcp\xb2\xc1\xac\xa5\x83\n\x1d\x82\x11\x81&\xbe\x1c\x16[Rh1\xd6\bQ\xe8[\xe73\xbb\xdb\v\xe81\xcf\xea\x87\xe9/6Ҡ8sF,\xf02\xd9|;=\x885A@\x0e\x13\xad\x9b\xac\xb8A\x8ayX\t\x0e`\x88ҋ\xcc\x13\x16\xb0\xec\xe4\U00064384\r!{\x12\a\x88%\x03s\xc0\xe8\x80q\xd3jU(\x97\xf0\x18\x85\xedV\xf3\xadw\x98s횠A\x9ddh;\xf9\x15\xb8\x8c\xb2f\xfe\x92B\x8c\x9aW4Y\xdb'L\xce\t\xee*3+\xc7\x14U\x113I\xb7\x0f3\xcc\x1f\xfce\xb2\xc1e\xa7\xc7\x02.K\xa0\xaa\xea\xeb\x93\xde\xe8P\xc2V\xd4\x18\xa3\x81E\xec7\x8f\xdc\xed\x04P\xb40p_\x13Ӫ\x0eH\x8dz\x84\x90\x90\x9f\x87\xa0\xf0`\xdap\xf4\u007fa\b\xe9\xbc\a\xb7=\xd9˭\x8eN\x00\xea\xf4\x1c\xb3\x89{-ɻ\x03E`5یWV\x8d\xbe*El\xfamSv/$\x84\x02f:\xd4\x0ey\x059\xb0\xec\x15\x83\x83\xcc\xe0\xfc\xdf \xec\\\x1c+\xb9\xab\xb8P{pE\x8d\xc5u*k\xf7\x16\x8f\xb9y\xd6\xc4d\\\xe8\xc3\x14+\x99(?}Q\xb5\x11\bO\xdd\xd7\xd1\x14\xbaP\x13\xa5\xdcR\xd0\x1dA\a\x8e\\\xbf\xa7\xf3\xb8hx\x85\x15@\\S\x80z\xeb\x873\x91Ҩ\xe1\xf5D7\xe64\xf9v\x9e\xb0g\xed\xda\xcc70\x03[&b\x90\xc2軫\x82j{\xf4\x9a\xcf\\\xea\xaaXm-\xb0\v>@\xb3\x10\xfa[ʉ\x85\x9ep\x9f$U_9\x97b\xabKVC\xe6^XQ\xd1\x12\x82ެ\x9cI]&\x1b\x12\xe1\xf3\xbe\xfacM\t\xfex\x9d,\xe4\xc6:d\xc0\x8b\xf8\x0f\f\x16\xd26)\xc4(r\x9c\xc5ܲ\xf8(\x96^\xb1\xd0Fg\x1d!\xfe\xa8\xbe\x83\xf1S?i\x84\x91Bc\xcabBʘ_\x82\"\xed\x0fSj\xacd\xc1\xe5ʌ\v\xdb\x0ekj\x94\x94\x19F\x02\x9a\x11\xec\xeax\x12\x81\f\xf6y\xc2h\x14\v7N\x90=d\xe7\x00\x8b\x05\x1c\xc0z\xec\xd0\x03\xd0B\xe8\x11\xdd\x11a\x81\xe0\xc4\\\a-\xd2}9\x98\x105ya\xe3(B\xc6ұ9\f\x0e\x80\x8b\x8a\xe1\xa9\x11\xa4\x8e\xc1L\xc1\x95\xa6}\x9f\x17\xa6a\x10\x92\x8a\xacΰ4\r\x90ڍ\x18R\xca\xe8-\xad\x15+q\x1cBa\rW\xf8\x82\x06\x83t\x17\x0eI\x94\xed\x01R\xe2t&|\xaf\x16\x87\xa0]~\xc2\xd0`\xcc\x1d\xf92Z\x9d\x8eWd\xa0,\xa3\xf1\xb9\xac \x95\t\xcb\x1a\xb4\x06\xe1\t\xd3R\x92M\xcd\xfd\xe7\xb9\x14+\xf9\x9c\xb2\x83\xe2QBNq^@-J~\x85\x83\xa09_2\xb7\x00\xe0\x85b\x03\xc3\t\x10p1g{\x80\xe33\xb68'\x98Aha\a@`\xb8\xe7\xcc\x03jy\x82.\xa4f\t\x12I\r(\xb4\xeb\xc3ss\x00\xf8]\xae\xa8\x19\xa5n\xc7&ۏ\xf3g\xa5\xe6$p\xb9\xf1BQ\xad*\x98&\x84ʉ\xaa\xf2\xd4ra\xc4\x03\xc1o\x10J\xaa\x1e\x86$A\xed\xbde,njo\x8b\n\xb9\xbef\xd8\x18\xb5M\xf6\xbcW\v˳\x884\b\x140M\x0e\xe9\xafΤݓ\x06\xf4\x19\x90I\xa4\x01\x89\u052aF\xdft\x8d(A\x88\xfb\xe6\xfc\xb1\x9ee\x17\xca~\x04\xf3\x8c\xab5\x94\x96\x85\xb6T\x0e\x1c\xb9\x8a!\xb7\xb0\x96#\x84\xadz&\xa4\xe2\xb0'\xbc\xb5\xa3\x12n\x99\x1eE\xb1\xa6\xdc\t-,\xb1\xe7S>\xb3\xd3.\xdc\fTU8tIO\x8a\x18\x88\x03\x90\xa0\xbap\x92A^r\x8awA\xd69`\xcbZ\x90\xd0\x16\xae![\x9f{\x9d\xacx\xb4\xd39\x1e\x16\" \xd9P\xb2\xc8q\xf9\xf3ھ\xe3\xfa\xb1\xfe\xe7s\blIA\xd7I\x9f\x12]`\x9e\x00p͗\x16\x11\n\xe0\xe2-\x8ex\x8cc[\x8dQ\xc9\\\xf6R\xa9\xe3\x9f\xfdѿ\x94\x96J\x84i\xd6<غXho\x0fJ\xa8\xd9\xd5a ޞ\xfb\xb81\x110\x0e\x14\t\x10\x15\x84\xa0\xe1\f\x8b̨\xea\xb6\"|\x90@\xffv\xaf\xa9`=u\x0fąT\xe9f#|*\xd5\bT\xb3\x91-\xa3\xc1g\x8a\x1a\x14\x99\x10>_z\xaf\xf9\x95\x00\xbe*U3\x8b\x86\xf1CV\xe3\xa1A\xa9\xa23\xa4\xe2a\"g\x11q\x82\x93\v\xd0\xf7\xa2bIr\x91B\x91\x02)\x1d\x02\xb1\x94\xaeO\x0e\xe8\xd5\xc2d\x98\x06\x93\x88\x1a\xed\x10\xac\xfe\x1dyQ\xe4\xd1e!sl \xa6^P\xcf\f\xcd\xd54 \xc6\x15\xfdrP\xe9\x86\xe0\xa1m:\x051\x87\xed@\xf0\xf2\xea\x04\xa0:,\xf8\xe1\xfbO\x15\x19\xf2\xaeZ\xa7*W8I\x04\\繢\x93\fO\xc1\xdcC\x0fA>\x031\xb4P\xae)!KQ\xb9\x1d=\b\\\x94\xfe\xa1Idg\x1f\xd1F\xf5\xf3]\x19\xf9-\xb9(\xc1S\x86ۦ'7B\x16!\xc2Bڪ\xa1kncJ\n\xb0gi\x13n\xda;\xd32\x98@\xc5KEo\xc5\x1d\x14\x04\x10\xb9\xf2}ǎN\xe8\xcb'\x85H\x91\x00S\xca0\x1e\x89:,\x06\xeb\x82Ÿ\xd1&\x00g\xb6D\x1d=iQ$sj\xb9\xf6w\xee\xc0\x19\x80\x13\xf9iP\xfa\x84\xfc\x1f/b\xb8\x11\nQ\x85g\x0e\x94F\xe9\x01\xca\xccO\x1a\xb0C鲓[y\x02\xebf^\xd8\"zK\xc1\x15\xaf\xd4\xc0\xf7b^\t\xc6\n\x9cYw\x9e5ޠ\xba\x17\x84!\xf5\x13\xadW\xbbϕ^\xaa\xc1\x94\x92\xb7'~U\xbc\x89\xbb\x9b\x88\uea95M[\xa3ȵ bR\xa0L*\x9d\xa54\x1c\xa83\xb7\xcctP\xc5\x1f\xaf\x98v\x90\x88\x14W\"\x86N\xfc4\x19}\x92\xa1\x02\x04\xa4\xb1\x9c\x91(\xb0\xb8o\x84\b\xac\xd2Y\xcbAe\x85o\b\x11\xe5v,\x93L\xec\xe5\xf6\x10\x1b\xe6\xf6ƓSd\n0\x95\xc1m\xe3a\x1a\x92\xdan\xe8\x9f7\xef\ty\xe9\x8eH~o\x99\xeb|\xf5=\x84\b~øT\x93_mX0ҋ]$\n\xec_n\xe0\xa5\xe1p)3\xe3=!/\xbf2\x06\f\x8b+Io3\x00s\xbc\xb5RZ\xea\u007f̑\xed\xbfRT?\xd3/Y\xda\x04\xceJb:\xa6\xef\v\xd6H\xfc&\xa8\xa0\xcf\x00[a\xc1\x03\xe9E\x00]3\x00|\r\xc2\xc0\x14\xa8\x95A\x83\xe03\xba\xcc \xc7K\xe0A\t\x9dt\xbc\x97\x84\xaa\x85\x10#\x00w\xe5q\xc4W\xcfM\xf6\x1bV\v\x01n\f/\xec%\x9a\xd2\x1a\xd7 \xc1\u0095\xa5\x89WVT*Eƚ\x05FYT\x95\xdd\x11,\xbfI\xc1ġ\xbe\xee!\xe4W\xfcW\xfc\"\xec\xba\x1f7\x8dV\xfc\xc8.\x96\x0fIT\xc8\"\x05\xed cr\xe0\xaa\xf8 \x9c\v(x\xb0:\x1d\xf8\xa7\x96\u0081d\xcc\nJ\x15\xc0\x02X\u0082\xdf[\x06\x86 _\xe3\x12*\x9a\xb1\x96\x04\x9c\x87ׁK\x16H\xee\xba\x04\xd5\xef\xaa\f5\x86\xc0L\xafӮ\x1e\x00C\xb2\xf4҆\x9c\x03\x01\xb1\x9c8\xa4\xb6\f\xc7\xf9\x00\x12\x98\xc0\xe3M\x1a*RA\nu\x01\x87\x847t5\xe0\x85\xc0\xdb~\xc1\xb9\x00>\xaaԏ\xea\t\xc4$\xe0T\xcdA#Gx\xba\a\xc1tx\xf5]P\xddy\xf0\x97\x03\xf4\xbe\x1f\x88`\x91b\xe6\xa2\xf2\xf8\x8f\xd6\x03\\\xa6\x04z\xbfk\x02\xf3\xb0Eo\x04\xda^\xba\xb7\xf4\xad\x11!*@/\xad0\t\xc9\x18\xe5\xc98\x93\xaa,S릝N\xe9 B\x93\xf6\xa9[\xa1\xb7\xfe\x98`F\x84\x92\xfb\x04\xaa\x9a\xb5L\xed\x8b~\xc1=\xab\x04\x03\x8ev\x84ε\xf1Z\x88X?v\b\x10\xb3G\x026\xbb\xb9\r\x9dG\xb0\xba薱n\xb1a\xb8F\xab\xa8\x14\xbf0&de\xf4I\xf4\b&V\xc0\x843\xe0\x16\x8e\xe9\xf07\x16-\x9b\xdf\xe8\xc1\xd2e\x8f\x1cG\xb5\xacHPNR\xb0\x8aZQ\x84y\x96\x1e\x17ZB1%\r\xd4\x05\x020\x95\xe3ϡ\x9e\f\xb3\xdb\x13\a.\x98\x00\x03\xe6\xa3\xc6X̬v\xb4\xd8\xd09!]Z\x8e\xf4\x832T\x10\xb4\x1fbڨ\x80\xcb+\xbc,1C\xf3\x923\xf6\x03S\xc3*h\xe5\xe1\x9d \x19\xcd&\xc9\xd5*\xa1qE\xb7\x8f\xfac\xd1{!E\xbfn\r\x1b\x15\x18\x98\x99\x1bk\xb7\x16\x1c\xb7\xa6a\xe1\xb7\xf7\x1f\xde\x00\xac\x03\xbc=\xb3\xb5\xa44\xdcغJ\xa4`\xd0W]\xb2\xc4\x13z\xb3\x0eݧ\xe8Q\x85\x99\xb9\xab\x9c\x0e_\xa4\x95\r\"՚9\xca\xf7\xaf\xa0\x04\xa3\x06eB\xcf$4\x1fb\x05\xc7S ,\xad\u007f\xa0P\x13v\xf3J\xa3n\x85\\\x01O \xfbNٖ.\an\x9f\xdb&'n-\x96$\xe4\xd7\x12AC\\\xc0\x92\x9a~(d\x87\x80#\xc0\x00A\x9e{\xfe\x9b\xc7`*Zt&\x89\x9d\xb3s$6\xdbff\xe4 6|ne5\x86H!\xab\x87U\xb6f(;\x02C\xf7\xe6Y\b\x12F\x80\xbd8\xdb>\xc0\x97\xb8\xefQ\xbf\xf2\x00s\xa4\xf6K\x85\xe6Ou*\x13\x9a\xba\xa7Ka\xb8\x8e\x8c\xca\x13nn\x11#B\x898A!\x87UQ\xe5\x17k\a\x890ňd\xaaJ\xb3\xa1̕\xad.\xee\x8d \x16U\xa5\xf1\x0e\x1aCR\xed$I\xc3?A\xcd\x04Rjhyq4\x1d\xb8M\xa2݆\x9a\x9b\xd4\xe0\x85\v\x06ڱ&')\xa7\xf7\xee\xc2kF\xfb\r=\x16\xc9~kX\x80\xf9\x06̖\x022\bDm\x80\x8c\xf20\xa2\xb8\x8a\x99\x9d\xf8\x0fA\\\x86\x90\x19R\xc2\x12L\xff\xa8ը\xab\xb6\xffQ\xbf\x03\\\x1d\xedF\xff\xf4J\xd5~O7\xa1\xber\x80Yx\x80\xbd\x0f\xdf\t\x15\x9a\x10Q\xd5tE\xe42T\"\xfa\b(q\xf0\xe2p?\x03\xe6\xc1\x8b;1\xa6\xbfD\x1bC\x02\xb4o@\x0f>Ё3ͬE\xd4V\x14\xbdD\xb6\x9d\xa5\x89\xc1č\xc0\x85\xdf3\x14,\xbdH\xc47\x1fO,\xe4\x1e\xe0\r\xccG\xe9}\x9e\x98\xc1Ķ@\x00\b\xb0\nږ\x85\x1f\"\xc3\xe2'\x17\x88\x85\x10\x18]\x16\x82¸\xb63z\x1a\xb4\xc4OX&%\x04\x18\xa3\x19\xf0\xe5`h\x8b\x04\xa9\x9c\xe1\f\x85P\xe0\xc0+ҏ6-\n\x15Q\xa0\f\x10\xa0)\xc0}\x02\xbd\x01`.\x82Rj\xea/\x14`\x13\xc1\xa8\x0e.\x80f\x03\xdc\x01\xb8\xd4\x00nGQp+1\xc4\x11\xf7\xa7\x83GXt\x0e\x98\xfa6\x8e\t\\[Z+\xb9\x84\xa7\xf2\xb5\xabP=\x94\xdb\xe5\xd6D\xf0\xc4\xe2UR\x0e\xea\xffI\u058dc\x1f%\xf5\x867\xca\x1aӣ\xc8e\x99f\xb1\xa4~\xd7Q\vf\xf6~)3\xb6\xaf\x83\a\x8c\xdc\xf3\x05(\x80\x82:\fa0;\x9f\x05\xe3M\xe1\xcfX\x9c\x9c\n\xb6w)\x04L\x17\x05\x1a3_\xf9l\u007f\xddMGxbg\xb4\x12\x83Ā\xe4\xc0\xab\"\xfe9e\u0089\x1f\xf6`R\xff\xd9\x17\x1cA[\x00$\xc0\x8a\xb9O\x1d\xea\xe30\xbf\"\r\x80\x14\x05\x92F\u007f\xb9\xab\x1c\xea\xc6A\"\xb1\r\txF\x9e+\x0f=\xc5\x06\xe8\x0e\xb5n\xe1t\x86]\"\x1dg\x02\x8d\xe7c\xfcEW&\x97\x11\xb6\x82R\xd1\x0e\x94$\x90\x16\x0e\a\xb1q\xcd\x12{\tK\xdb\x1ez\x15\x01!\x1c\x96\x84:\x167\xab%iMa\xea\\\xc1$1\rDb\xefԼU\x11\xff\x01\xd5\xc8\x1b\xc620\x02\xe3U\xe7\u007f\xed,\x12\x85A\x96\x88\x83\x0e\xa7\xd1;\v\x8cdD\xfc\xd2\xc2\v}\x11r\fD\xd81\x10\xa3\xddQ́y\xbf\xe9հ\x80|ň\xa29\x9dJ\xd8c\nK\x01\x9c8dq\xae\xf0\xa1\x10l\xc8\tgNP\xd3k\xab\xdaNm\xb2\xef\xca\x17\xa5_\xcc^\xbbCX:\xdc?\x8b\x81\xabQ\x14\xb5\x90\x18\v*\xbdƅj\x17\x18\xc9Q\x94\xd0/-_R\xa1\xc8\v\f\xf5\x9fU\f\xe0\x84^f\xfb\u007f\xe4\n\xe3\x9de\x00\xb0\x02\x9c\xdb&2`\xdd\b\x82F\xa9\x89[\xdd\xf0&b\xcbS\xf2T\xb31\xe1V`\xb1~u\xd4|rU,\x01\xdbH!\b + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`) + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_700_svg_bytes() ([]byte, error) { + return _third_party_swagger_ui_fonts_droid_sans_v6_latin_700_svg, nil +} + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_700_svg() (*asset, error) { + bytes, err := third_party_swagger_ui_fonts_droid_sans_v6_latin_700_svg_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/fonts/droid-sans-v6-latin-700.svg", size: 73575, mode: os.FileMode(416), modTime: time.Unix(1436317036, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_fonts_droid_sans_v6_latin_700_ttf = []byte("\x00\x01\x00\x00\x00\x11\x01\x00\x00\x04\x00\x10GDEF\x00\x10\x00\xd2\x00\x00\x8b`\x00\x00\x00\x16GPOS\xf2ZM^\x00\x00\x8bx\x00\x00\x12\xc0GSUB\x00\x15\x00\n\x00\x00\x9e8\x00\x00\x00\fOS/2\xa2\t\xb7\x96\x00\x00{\xa8\x00\x00\x00`cmapmag\xda\x00\x00|\b\x00\x00\x00\x8ccvt K\xe2RQ\x00\x00\x86\x00\x00\x00\x02\x06fpgms\xd3#\xb0\x00\x00|\x94\x00\x00\a\x05gasp\x00\a\x00\a\x00\x00\x8bT\x00\x00\x00\fglyf}]p\b\x00\x00\x01\x1c\x00\x00u\x1chead\xf5\xcd \xd7\x00\x00x\x00\x00\x00\x006hhea\r\x9b\x05a\x00\x00{\x84\x00\x00\x00$hmtx\x9f\xc7I\xb4\x00\x00x8\x00\x00\x03Llocada\x83\"\x00\x00vX\x00\x00\x01\xa8maxp\x03\x17\x02\x14\x00\x00v8\x00\x00\x00 name\x19w4\x0f\x00\x00\x88\b\x00\x00\x01dpost\xa2\xc2\x0f;\x00\x00\x89l\x00\x00\x01\xe7prepeq֊\x00\x00\x83\x9c\x00\x00\x02b\x00\x02\x00u\xff\xe5\x01\xd3\x05\xb6\x00\x03\x00\x17\x00]@B\xb0\x19\xe0\x19\xf0\x19\x03\x1f\x19/\x19?\x19\u007f\x19\x9f\x19\x05\xd7\x03\x01\xc6\x03\x01w\x03\x01\x16\x03f\x03\x02\x03\x03\x01\x03\x0e\x96\xd8\x02\x01\xc9\x02\x01x\x02\x01i\x02\x01\x02 \x04\x01\x00\x04`\x04\x02\x04\x01\t\x9b\x13\x02\x03\x00?/\xfd\xce\x01/]q3]]]]\xed2]]]]]]]10\x01#\x03!\x014>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x01\xa0\xf43\x01Z\xfe\xa2\x1b0@%#?0\x1c\x1c0?#%@0\x1b\x01\xe5\x03\xd1\xfa\xd9/A(\x12\x12(A/-@*\x13\x13*@\x00\x00\x00\x02\x00\x85\x03\xa6\x03B\x05\xb6\x00\x03\x00\a\x00/@\x1a\x04\x98\a\a\t_\t\x01\x00\x98\x00\x03\x10\x03\x02\b\x03\x06\x00\x01\x01\x01\x01\a\x03\x03\x00?33/]3\x01/^]\xe1]\x129/\xe110\x01\x03#\x03!\x03#\x03\x01\x9c)\xc5)\x02\xbd)\xc5)\x05\xb6\xfd\xf0\x02\x10\xfd\xf0\x02\x10\x00\x00\x02\x00-\x00\x00\x04\xfe\x05\xb4\x00\x1b\x00\x1f\x00\xcd@\x80\x03\x03\x1a\x1a\x18\x16\x1e\x1d\a\x04\x06\x17\x17\x06\x19\x00\x01\x04\x04\x05\xb1\xf4\x18\x01P\x18\x01\x18\x18!\x15\x1f\x1c\b\x04\t\x14\x14\x12\x0f\x0e\v\x04\x13\xb1\n`\x10\x01\x10\x10\f\f\t\xfb\n\x01`\np\n\x02\n\x1c\x01H\r\x01\r\xae\f\b\x04\f\x1f\x00\xe7\x10\x01\x10\xae\x11\x19\x15\x11\xf4\f\x01\xe5\f\x01\x92\f\x01T\f\x010\f@\f\x02\xeb\x11\xfb\x11\x02\x9d\x11\x01[\x11\x01?\x11O\x11\x02\f\x11\f\x11\x05\x17\x13\x06\n\x05\x00/3?3\x1299//]]]]]]]]]\x1133\x10\xe9]22\x1133\x10\xe9]22\x01/]]33/3/]\x10\xec\x1792\x11\x12\x179\x113/]]\xec\x17923\x11\x12\x179\x113/3/10\x01\a!\x15!\x03#\x13#\x03#\x13#5!7#5!\x133\x033\x133\x033\x15\x0537#\x03\xe7/\x01\x02\xfe\xd7M\xdcN\xc2L\xd7J\xee\x01\x15/\xfc\x01!M\xdbM\xc6N\xd7N\xf0\xfd\x1d\xc4/\xc4\x03L\xe8\xce\xfej\x01\x96\xfej\x01\x96\xce\xe8\xd1\x01\x97\xfei\x01\x97\xfei\xd1\xe8\xe8\x00\x00\x00\x00\x03\x00b\xff\x89\x04%\x06\x14\x003\x00<\x00C\x00\xce@F&.@#@\xfb@\x01?@\x01@:\x06\x13\xf4\a\x01\xe6\a\x01\x00\a\x10\a0\a\x03\a\a\x1e\v4\x1b4+4{4\x8b4\x054)\x0f\x00O\x00\x02\x00E\x04=\x14=$=t=\x84=\x05=\x0e\x90\x1e\xa0\x1e\x02\x1e\xb8\xff\xc0@?\v\x0fH\x1e-*AA&9\x14\x14.@\xb6@\x01\x89@\x01@\x13&%@\x0e\x14H%%# &@&P&\x80&\xb0&\x05\x0f&\x01&:\x0e\x13\x13\bP\a`\a\x02\a\a\x05@\b\x01\b\x00/]33/]\x113\x1133/]]33/+\x11\x129]]\x1133\x113\x113\x1133\x01/+]3\xc9]\x10\xde]2\xc9]\x119/]]]3\xc923]]\x113\x10\xc9210\x01\x14\x0e\x02\a\x15#5.\x03'\x11\x1e\x03\x17\x11&'.\x01'.\x0354>\x02753\x15\x1e\x01\x17\a.\x01'\x11\x17\x1e\x03\x054.\x02'\x15>\x01\x01\x14\x16\x175\x0e\x01\x04%5i\x9cf\x89BpbY+*cjm4\b\t\b\x10\x06[\x88[-9j\x98_\x89W\xb6deA\x8d>'_\x8e^.\xfe\xd3\r\x1c+\x1f;8\xfe\x974967\x01\xc9K\x80`>\n\xcd\xc9\x02\f\x16\x1f\x13\x01\b\x15&\x1f\x16\x03\x01>\x03\x04\x03\x06\x02#L^rHK{[9\t\x9d\x97\x05(+\xea\x1a)\x05\xfe\xdb\x0e#J\\rR\x18$\x1e\x19\x0e\xfc\t<\x02\x8e/?\x15\xe9\x060\x00\x00\x00\x00\x05\x00?\xff\xee\x06\xcd\x05\xcb\x00\n\x00\x1e\x00\"\x00-\x00A\x00\x9d\xb51\x18\t\rH?\xb8\xff\xe8@\x0e\t\rH:\x18\t\rH5\x18\t\rH\x1c\xb8\xff\xe8@$\t\rH\x18\x18\t\rH\x13(\t\rH\x0e\x18\t\rH#\xb48\xb5.Y\"\x01\"\x1fV \x01 !\x1f\x10!\xb8\xff\xf0@#\x1f!\x1f!\x15)\xb4.C\x06\xb4\v\xb5\x00\xb4\x15+\xb6=\xb7&\xb63\x19\"\x06!\x03\xb6\x10\xb7\b\xb6\x1a\a\x00?\xe9\xfc\xe9/??\xe9\xfc\xe9\x01/\xe9\xfc\xe9\x10\xde\xe9\x1299//88\x113]\x113]\x10\xfc\xe910\x00+\x01++\x00++\x01+\x00++\x01\x14\x1632654#\"\x06\x05\x14\x0e\x02#\".\x0254>\x0232\x1e\x02\t\x01#\x01\x13\x14\x1632654#\"\x06\x05\x14\x0e\x02#\".\x0254>\x0232\x1e\x02\x01;-21/`2-\x01\xbb)V\x84[U\x81W,(U\x82ZV\x83X-\x02\x9b\xfc\xd5\xef\x03+p-21/`2-\x01\xbb)V\x84[U\x80W,(T\x82ZV\x83X-\x04\x00\u007f}|\x80\xfa{}l\xacv??v\xacll\xaau>>u\xaa\x01H\xfaJ\x05\xb6\xfc\x02\u007f}|\x80\xfa{}l\xabv??v\xabll\xaau>>u\xaa\x00\x00\x03\x00R\xff\xec\x05\xc3\x05\xcb\x00-\x009\x00I\x00\x9f@A'G():\x89:\x02:H\x1eG#$-\x04\x00&D\x86D\x02DH\x14\x0267\x0f\x04\x14\x01\xf4\x1e\x01\xd0\x1e\xe0\x1e\x02;\x1eK\x1e\x02\x04\x14\x14\x14\x02(\x1e\x14\x14\x1e(\x03\n\v\x01\x1b\x01\x02\x01\x00\xb8\xff\xc0@\"\t\fH\x00\x00K.G\n\x0267\x0f\x04?\x01?\x19''3-$#G\x04\x01\x19\x043\x05\x16\x01\x15\x00??3?\x12\x179\x129/\x113\x11\x12\x179\x01/\xe9\x113/+3]\x12\x179///]]]]\x11\x12\x179\x10\xe9]\x11\x179\x10\xe9]\x10\xe910)\x01'\x0e\x01#\".\x0254>\x027.\x0354>\x0232\x1e\x02\x15\x14\x0e\x02\a\x01>\x017!\x0e\x03\a%\x14\x1e\x023267\x01\x0e\x01\x014.\x02#\"\x0e\x02\x15\x14\x16\x17>\x01\x05\xc3\xfe\x87aP\xc1txËL%Da<&5 \x0e=m\x97ZV\x91i<-Lg;\x01\x04#5\x13\x01=\x0f)6F+\xfc\xeb 9M-\x0273\x06\x02\x15\x14\x1e\x02\x17#.\x03R$JqN\xfa\x8d\x90$HjE\xf8NqJ$\x021}\xf3\xe5\xd3]\xc1\xfe2\xf4w\xec\xe2\xd4^Z\xce\xe1\xf0\x00\x00\x01\x00=\xfe\xbc\x02d\x05\xb6\x00\x13\x00\x18@\v\x06\x0e\xf1\v\xf0\x00\x15\x0e\xf8\x05\xf9\x00??\x01\x10\xde\xe9\xec210\x01\x14\x0e\x02\a#>\x0354\x02'3\x1e\x03\x02d$JqN\xf8EjH$\x90\x8d\xfaNqJ$\x021|\xf0\xe1\xceZ^\xd4\xe2\xecw\xf4\x01\xce\xc1]\xd3\xe5\xf3\x00\x00\x01\x00?\x02V\x04\x1d\x06\x14\x00\x0e\x00#@\x14@\x00\x011\x00\x01\x00\x84\x0e\x94\x0e\x02\x0e\x1f\x06\x01\x06\x06\x00\x00\x00?2/]\x01/]\xcd]]10\x01\x03%\x17\x05\x13\a\v\x01'\x13%7\x05\x03\x02\xb0)\x01u!\xfe\xac\xdf㜉\xec\xdd\xfe\xae'\x01m)\x06\x14\xfe\x90h\xfc\x18\xfe\xd7y\x019\xfe\xc9w\x01)\x1a\xfah\x01p\x00\x00\x00\x01\x00X\x00\xf8\x04\x10\x04\xb0\x00\v\x00 @\x0e\a\x06\t\xaa\x03\x02\x00\v\t\x00\xad\x06\x04\x03\x00/33\xe922\x01/22\xe92210\x01!5!\x113\x11!\x15!\x11#\x01\xc7\xfe\x91\x01o\xdb\x01n\xfe\x92\xdb\x02d\xdb\x01q\xfe\x8f\xdb\xfe\x94\x00\x01\x00?\xfe\xf8\x01\xcb\x00\xee\x00\f\x00V@A\xa0\x0e\xb0\x0e\xe0\x0e\xf0\x0e\x04/\x0e?\x0e\x02\xab\v\xbb\v\x02<\vL\v\x02y\v\x89\v\x99\v\x03\n\v\x1a\v*\v\x03\v\x00\x97&\x056\x05F\x05\x03\x05/\x06?\x06O\x06\x03\x06\x05\x9c\v@\t\fH\v\x00/+\xed\x01/]3]\xed2]]]]]]10%\x0e\x03\a#>\x037!\x01\xcb\x0e'/3\x19\xdc\x0f\x1d\x1b\x16\b\x01\x18\xd76z|{8=\x84\x83}5\x00\x00\x01\x00=\x01\xa8\x02V\x02\xa2\x00\x03\x00\x1a@\r\x02\x05\x1f\x05\x01\x80\x00\x01\x00\x00\xbb\x01\xbd\x00?\xe9\x01/]]\x10\xce10\x135!\x15=\x02\x19\x01\xa8\xfa\xfa\x00\x00\x01\x00u\xff\xe5\x01\xd3\x019\x00\x13\x00&@\x19\xb0\x15\xe0\x15\xf0\x15\x03/\x15?\x15\x02\n\x96\x00\x00\x10\x00`\x00\x03\x00\x05\x9b\x0f\x00/\xed\x01/]\xed]]1074>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02u\x1b0@%#?0\x1c\x1c0?#%@0\x1b\x8f/A(\x12\x12(A/-@*\x13\x13*@\x00\x00\x00\x00\x01\x00\x0e\x00\x00\x03D\x05\xb6\x00\x03\x00(@\x0e\x06\x01\x01\t\x03\x01\x03\x00\x10\x00\x00\x05\x01\x02\xb8\xff\xf0\xb3\x02\x01\x00\x03\x00?/\x01/83\x113/8210]]\t\x01!\x01\x03D\xfd\xdf\xfe\xeb\x02!\x05\xb6\xfaJ\x05\xb6\x00\x02\x00?\xff\xec\x04)\x05\xcd\x00\x13\x00!\x00\x1e@\x0f\x1an\x00#\x14n\n\x1dt\x0f\a\x17t\x05\x19\x00?\xe9?\xe9\x01/\xe9\x10\xde\xe910\x01\x14\x02\x0e\x01#\".\x01\x0254\x12>\x0132\x1e\x01\x12\x05\x14\x1632654&#\"\x0e\x02\x04)7y\xbf\x87\u007f\xbc|=7x\xbe\x87~\xbc~>\xfdJVjh[[h5I.\x14\x02۱\xfe\xea\xc2ff\xc2\x01\x16\xb1\xb1\x01\x18\xc2gf\xc2\xfe\xe8\xb2\xfa\xfc\xfa\xfc\xfb\xfd@~\xbd\x00\x01\x00\\\x00\x00\x031\x05\xb6\x00\x10\x00%@\x14\x0f\x01\x0e\x0e\a\x00n\x9a\x01\x01 \x01`\x01\x02\x01\x0f\x06\x00\x18\x00??\x01/]]\xe933/\x11310)\x01\x114>\x027\x0e\x03\x0f\x01'\x013\x031\xfe\xcb\x01\x03\x03\x01\x05\x18\x1e \x0f\xa8\x96\x01\xd7\xfe\x03N\x1aIOP!\x06\x18\x1d\x1e\f\x87\xba\x01w\x00\x00\x01\x00N\x00\x00\x04'\x05\xcb\x00!\x00/@\x19!\bn\x19#\x1f\x0f@\x02P\x02`\x02\x03\x02\b \x0e\v\x14\a\x02 \x01\x18\x00?\xc92?\xc93\x129\x01/]33\x10\xde\xe9310)\x015\x01>\x0354&#\"\x06\a'>\x0332\x1e\x02\x15\x14\x0e\x02\x0f\x01\x15!\x04'\xfc+\x01XAjL*YKO\x92P\xa8-bv\x8eXi\xa7v?\x0254.\x02+\x01532>\x0254&#\"\x0e\x02\a'>\x0332\x1e\x02\x03\xee1UsC\xb1\xb6E\x8fٓv\xd0Z-dda+VrD\x1d%S\x86bhf\\zI\x1eai0SG;\x18\x9c*ct\x86Ll\xb1~E\x04oLy[=\x10\x06\x16\xab\x91`\xa3xC'(\x01\a\x18$\x19\f :Q0-I3\x1c\xd9!9L+NX\x13\x1d#\x11\xce\x1f4'\x16/Y\x81\x00\x00\x02\x00\x04\x00\x00\x04=\x05\xb6\x00\n\x00\x18\x008@\x1d\t\x00\x02n\v\a\x9a\x03\x01\x00\x03\x01\x03\x03\x1a\x18\x05\x01\x05u\t\x18\x18\x02\x11\a\x06\x02\x18\x00??3\x129/3\xe12\x01/2\x129/]]33\xe92210\x01#\x11!\x11!5\x01!\x113!54>\x047#\x0e\x01\a\x03\x04=\xb0\xfe\xd3\xfd\xa4\x02m\x01\x1c\xb0\xfe#\x01\x02\x03\x03\x03\x01\t\x12-\x1d\xf4\x01/\xfe\xd1\x01/\xd7\x03\xb0\xfci\xf8\r1>B<-\n*^/\xfe\x8e\x00\x00\x01\x00V\xff\xec\x04\x12\x05\xb6\x00(\x00b@#%&\x05&\x15&\x02&\"$\x05! \x12\x18H\x89!\x01{!\x01]!m!\x02!!\x0f\x18n\x05*\x0f\xb8\xff\xc0@\x17\f\x0fH\x0f\x1bt\x04\x00\x14\x00\x02\x00\x00\n%s\"\x06\x15t\x10\n\x19\x00?3\xe9?\xe9\x129/]\xe9\x01/+\x10\xde\xe9\x129/]]]+\x12933]\x11310\x012\x1e\x02\x15\x14\x0e\x02#\".\x02'\x11\x1e\x0332654&#\"\x0e\x02\a'\x13!\x11!\x03>\x01\x02V^\xa2xDJ\x90Պ7lcY$#\\cd-\x86\x8c\x89\x8f\x1a;94\x13{7\x03\x04\xfe\b\x18 U\x03\xa6:p\xa7lw\xbd\x83F\n\x13\x1e\x14\x01\v\x14#\x19\x0foylq\x06\n\v\x06B\x02\xe9\xfe\xfa\xfe\xe1\a\x0e\x00\x00\x00\x02\x00L\xff\xec\x04+\x05\xc7\x00)\x00;\x00M@/\x05\"\x01\n(\x01\n\x03\x01!\r\x01\r\r7/n =\x157nP\x00`\x00\x02\x002u\x15\x02\x1b\x12\x1b\x02\x1b\x1b\a*t%\x19\x10u\a\a\x00?\xe9?\xe9\x129/]3\xe9\x01/]\xe92\x10\xde\xe9\x129/]10]]]\x134>\x0432\x1e\x02\x17\x15.\x01#\"\x0e\x02\a3>\x0332\x1e\x02\x15\x14\x0e\x02#\".\x02\x052>\x0254&#\"\x0e\x02\x15\x14\x1e\x02L\x17;e\x9bّ\x15230\x13&U+\x87\xaef+\x05\f\x149L_;_\x98i8C|\xb0nl\xbc\x8bO\x01\xfc)C1\x1bY[.L6\x1d\x193K\x02miп\xa4yE\x02\x03\x06\x04\xf7\t\vCx\xa8f$?-\x1a>v\xacow\xbc\x83EM\x9e\xf1\xe5\x1f?`Bk{$:H%3eQ2\x00\x00\x00\x01\x007\x00\x00\x04'\x05\xb4\x00\x06\x00-@\x1a\v\x01\x01\x01\x04\b\u007f\x02\x8f\x02\x9f\x02\x03\x02\x04\x06\x01\x06\x00\x05\x02s\x03\x06\x00\x18\x00??\xe99\x01/3]/]\x10\xce2]103\x01!\x11!\x15\x01\xcf\x02\b\xfd`\x03\xf0\xfd\xeb\x04\xb0\x01\x04\xc2\xfb\x0e\x00\x00\x00\x03\x00H\xff\xec\x04!\x05\xc9\x00'\x00:\x00N\x00\x9c@j\x05\x11\x01\n\x17\x01\x05\x03\x01\n%\x01\b\x1e\x01\a\n\x01\n\x1e#\bJ\x01Jn\xfc\x05\x01\xe9\x05\x01\xc6\x05\xd6\x05\x02\x05\x05\b0\x010n\x0fP\a@\x01@n\xe2#\xf2#\x02\xc9#\xd9#\x02##\a(\x01(nP\x19`\x19\x02\x19\x1e\n\n6\x016\xebE\xfbE\x024EDEdE\x03\aE\x01EE\x00-v\x14\x19;v\x00\a\x00?\xe9?\xe9\x119/]]]\xc9]99\x01/]\xe9]3/]]\xe9]\x10\xde\xe9]3/]]]\xe9]\x1299]]10]]]]\x012\x1e\x02\x15\x14\x0e\x02\a\x1e\x03\x15\x14\x0e\x02#\".\x0254>\x027.\x0354>\x02\x03\x14\x1e\x0232654.\x02/\x01\x0e\x03\x13\"\x0e\x02\x15\x14\x1e\x02\x17>\x0354.\x02\x025[\xa2zH(F`8:oV4H\x82\xb5mv\xb8~A,Lf:1V?%I|\xa2v\x1a3L2ih#7F#\x16,H3\x1c\xcd!9)\x18\x19+9 \x1f8+\x1a\x18*:\x05\xc9,X\x84YBkWD\x1c\x1fL_vI[\x94h86d\x92[Kx`J\x1c\x1fIYlAW\x83Y,\xfb\xbc(C0\x1bcQ*C90\x16\x0e\x163=H\x038\x14&8#*=/%\x12\x10&1>(#8&\x14\x00\x00\x02\x00?\xff\xec\x04\x1f\x05\xc7\x00)\x00;\x00G@*\n\"\x01\x05(\x01\x05\x03\x01'\r\x01\r\r \x157n\x00=/n 2u\x1d\x1b\x01\x06\x1b\x01\x1b\x1b\a*t%\a\x10u\a\x19\x00?\xe9?\xe9\x119/]]\xe9\x01/\xe9\x10\xde\xe92\x119/]10]]]\x01\x14\x0e\x04#\".\x02'5\x1e\x0132>\x027#\x0e\x03#\".\x0254>\x0232\x1e\x02%\"\x0e\x02\x15\x14\x1632>\x0254.\x02\x04\x1f\x17;e\x9bْ\x15230\x12%U,\x87\xaef+\x05\r\x148L`;_\x98i8C|\xb1nl\xbc\x8aP\xfe\x04)D1\x1bZ[.L6\x1d\x193K\x03FiѾ\xa5xE\x02\x03\x05\x04\xf8\n\vCy\xa8e$>.\x1a>v\xacow\xbc\x83FM\x9e\xf2\xe5\x1e?aBj|$:H%3eQ2\x00\x00\x00\x00\x02\x00u\xff\xe5\x01\xd3\x04s\x00\x13\x00'\x000@\x1f\xb0)\xe0)\xf0)\x03/)?)\x02\x1e\n\x96\x14\x00\x00\x10\x00`\x00\x03\x00#\x9b\x19\x10\x05\x9b\x0f\x00/\xed?\xed\x01/]3\xed2]]1074>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x114>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02u\x1b0@%#?0\x1c\x1c0?#%@0\x1b\x1b0@%#?0\x1c\x1c0?#%@0\x1b\x8f/A(\x12\x12(A/-@*\x13\x13*@\x03g/A(\x12\x12(A/-A)\x13\x13)A\x00\x00\x00\x02\x00?\xfe\xf8\x01\xd3\x04s\x00\f\x00 \x00k@P\xab\f\xbb\f\x02<\fL\f\x02y\f\x89\f\x99\f\x03\n\f\x1a\f*\f\x03\f\x01\xa0\"\xb0\"\xe0\"\xf0\"\x04/\"?\"\x02\x17\x96\x00\r\x10\r`\r\x03\r\x01\x97&\x066\x06F\x06\x03\x06/\a?\aO\a\x03\a\x1c\x9b\x12\x10\x06\x9c\f@\t\fH\f\x00/+\xed?\xed\x01/]3]\xed/]\xed]]\x113]]]]10%\x17\x0e\x03\a#>\x037\x034>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x01\xbc\x0f\x0e'/3\x19\xdc\x0f\x1d\x1b\x16\b/\x1b0@%#?0\x1c\x1c0?#%@0\x1b\xee\x176z|{8=\x84\x83}5\x02\xdb/A(\x12\x12(A/-A)\x13\x13)A\x00\x01\x00X\x00\xcb\x04\x10\x05\x00\x00\x06\x00`@A4\x05D\x05d\x05t\x05\x04\x06\x05\x16\x05\x02\x03\x00\b\x01\x02\x01\x05\x05\x035\x06E\x06\x02\x16\x06&\x06\x02\x06/\x00O\x00\x02\x00:\x04J\x04\x02\x19\x04)\x04\x02\x00\x04\x00\x03\x01 \x030\x03@\x03\xb0\x03\x04\x03\x00/]q33]]/]2]]\x129=/33\x01\x18/\x10\xce210]]%\x015\x01\x15\t\x01\x04\x10\xfcH\x03\xb8\xfd}\x02\x83\xcb\x01\xb6\x8f\x01\xf0\xf0\xfe\xc3\xfe\xe7\x00\x00\x00\x02\x00X\x01\xa2\x04\x10\x04\x00\x00\x03\x00\a\x002@\x1e\a\x02\t\x04\x00I\x04\x01\x04\xad\x90\x05\x01\x05\x05I\x00\x01\x00\xad \x01@\x01\x80\x01\xe0\x01\x04\x01\x00/]\xe9]3/]\xe9]\x01/3\x10\xce210\x135!\x15\x015!\x15X\x03\xb8\xfcH\x03\xb8\x03'\xd9\xd9\xfe{\xdb\xdb\x00\x00\x00\x00\x01\x00X\x00\xcb\x04\x10\x05\x00\x00\x06\x00`@A;\x01K\x01k\x01{\x01\x04\t\x01\x19\x01\x02\x05\b\x03\x06\x05\x04\x01\x01\x035\x00E\x00\x02\x16\x00&\x00\x02\x00/\x06O\x06\x02\x06:\x02J\x02\x02\x19\x02)\x02\x02\x06\x02\x00\x03\x01 \x030\x03@\x03\xb0\x03\x04\x03\x00/]q33]]/]3]]\x129=/33\x01\x18/3\x10\xce10]]\x13\t\x015\x01\x15\x01X\x02\x83\xfd}\x03\xb8\xfcH\x01\xba\x01\x19\x01=\xf0\xfe\x10\x8f\xfeJ\x00\x00\x00\x02\x00\x19\xff\xe5\x03u\x05\xcb\x00'\x00;\x008@\x1e\x03\x1a\x13\x1a\x022\x96(('\x00\x00\x12\vH\x1c=\x12\v\x17\x00-\x9b7\x16\x11\x0eM\x17\x04\x00?\xe93?\xfd\xce\x119\x01/\x10\xde\xe9\x119/\xc93/\xed10]\x0154>\x027>\x0354&#\"\x06\a'>\x0332\x1e\x02\x15\x14\x0e\x02\a\x0e\x03\x1d\x01\x014>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x01\b\x15+D0*:$\x10MOE\x9fUf+emp6f\xa4r=\x1c7S7*5\x1e\v\xfe\xd7\x1b0A%#?0\x1c\x1c0?#%A0\x1b\x01\xe5J3SKG&!438%9J:*\xdd\x19-#\x141^\x86V?cUO,!1,/ <\xfe\xaa/A(\x12\x12(A/-@*\x13\x13*@\x00\x02\x00f\xfff\x06\x89\x05\xc9\x00U\x00f\x00U@,V_\x15\x01\x15\x15J-BB-\x00_\n@#\x01##7\x00h7J(Y\x05?\x10O\x10\x02\x10\x10E2Qb\x1a\x1aQ\x04\x0232\x1e\x02\x17\x03\x06\x14\x15\x14\x1e\x0232>\x0254.\x02#\"\x0e\x02\x15\x14\x1e\x0232>\x027\x15\x0e\x01#\"$&\x0254>\x0432\x04\x16\x12\x01\x14\x1632>\x02?\x01.\x01#\"\x0e\x02\x06\x89-\\\x8b^&C8)\v\x0f\x132>K,S\x81X.>t\xa6g-`YN\x1c\x15\x02\r\x15\x1b\x0f#4\"\x12L\x87\xbam\x99\xe9\x9dQF\x8aʄ8vuq3^債\xfe\xe3\xc4h6f\x93\xbc⁞\x01\n\xc1l\xfc>L?*='\x15\x03\f\x146\x1c;R3\x17\x02\xf0_\xba\x93[\x13#/\x1c\x19/$\x159i\x93Yg\xac}E\n\x10\x14\n\xfe]\x16*\x06*6\x1f\f6\\{E\x80ȊHf\xb7\xfa\x95\x8aՑK\x0e\x18\"\x14\xc0*1g\xc2\x01\x19\xb2|\xe3ßq=e\xbd\xfe\xf2\xfe\xdapc'Gc=\xdd\x05\x062Ri\x00\x00\x00\x02\x00\x00\x00\x00\x053\x05\xbc\x00\a\x00\x16\x00\x8d@\f\x06\x06&\x066\x06F\x06v\x06\x05\x06\xb8\xff\xf0@?\x15\x18H\t\x05)\x059\x05I\x05y\x05\x05\x05\x10\x15\x18H\x02\x16\x03\b\x01\x00\x06\x05\x0e\x0e\x04\t\x00\x19\x00\x02\x00\x00\a`\ap\a\x90\a\x04\a\x10\a\a\x18\u007f\x18\x01\x10\x18\x01\x06\x03\x16\x03\x02\x03\x04\xb8\xff\xf0@\x0e\x04\x02_\x16\x16\x1b\x0e\x01\x0e\x05\x03\x04\x00\x12\x00?2?3]9/\xe9\x01/83]]]\x113/8]3]\x129\x1133\x1299\x129910+]+]!\x03!\x03!\x01!\t\x01\x03.\x03'\x0e\x05\a\x03\x03\xfad\xfe\be\xfe\xc7\x01\xdb\x01{\x01\xdd\xfe\x1b^\x06\x18\x1b\x18\x05\x04\r\x11\x12\x10\r\x03]\x01\\\xfe\xa4\x05\xbc\xfaD\x02`\x01@\x12Rcd#\x16\x0254&+\x01\x19\x0132>\x0254.\x02#\xb8\x01\xac\x8aЌG\x1e:S67_G)F\x83\xbbv\xfd\xfc\x016\xa1:N0\x15ir\x93\xb6=S3\x16\x165V@\x05\xb6'W\x8dg>lR7\t\n\f-OxVd\x9dm:\x03s\x15*?*TI\xfd\xc5\xfe\x83\x1c4J-)C0\x1a\x00\x00\x00\x00\x01\x00w\xff\xec\x04\xd1\x05\xcb\x00#\x00B@+e\r\x01\n!\x1a!Z!j!\xba!\x05 \x0eg%\xb0%\x01\x9f%\x01`%\x01\x1f%\x01\x05[\x18f$\x00_\x1d\x04\n_\x13\x13\x00?\xe9?\xe9\x01\x10\xf6\xe9]]]]\x10\xe6210\x00]]\x01\"\x0e\x02\x15\x14\x1e\x023267\x11\x0e\x03#\".\x01\x0254\x126$32\x16\x17\a.\x01\x03%Y\x89]0+Y\x8b`Y\xb3i0^bg;\xa9\xf8\xa2NZ\xae\x01\x00\xa6m\xdbddR\xa6\x04\xc9E\x81\xb9su\xb6}A(%\xfe\xfc\x14\x1c\x12\tl\xc4\x01\x14\xa9\xa6\x01\x15\xc8o70\xfc':\x00\x00\x00\x02\x00\xb8\x00\x00\x05#\x05\xb6\x00\f\x00\x17\x00&@\x15\rZ\x00g\x19?\x19\x01\x14Z\x06d\x18\x13_\a\x03\x14_\x05\x12\x00?\xe9?\xe9\x01\x10\xf6\xe9]\x10\xf6\xe910\x01\x14\x02\x06\x04#!\x11!2\x04\x16\x12\x054.\x02+\x01\x11326\x05#e\xbf\xfe\xeb\xb1\xfe\u007f\x01\xac\xa1\x01\x03\xb8c\xfe\xc61]\x87W\x8fr\xc4\xc5\x02\xe9\xb9\xfe\xe9\xbb^\x05\xb6\\\xb5\xfe\xf4\xb8z\xb1t8\xfcH\xf0\x00\x01\x00\xb8\x00\x00\x04\x02\x05\xb6\x00\v\x00F@*\b\x04\x00g\r\x06\nZ\x01d\f\t_\xaf\x06\x01\x88\x06\x01L\x06\x01;\x06\x01\x19\x06\x01\b\x06\x01\x06\x06\n\x05_\x02\x03\n_\x00\x12\x00?\xe9?\xe9\x129/]]]]]]\xe9\x01\x10\xf6\xe92\x10\xe62210)\x01\x11!\x15!\x11!\x15!\x11!\x04\x02\xfc\xb6\x03J\xfd\xec\x01\xef\xfe\x11\x02\x14\x05\xb6\xfe\xfe\xbf\xfe\xfe\x87\x00\x00\x00\x01\x00\xb8\x00\x00\x03\xfe\x05\xb6\x00\t\x00W@:\b\x9f\x03\x01\x03g\v\x1f\v\x8f\v\xaf\v\x03\x06\x00\\\x01d\n\t_\x9f\x06\xaf\x06\xbf\x06\xdf\x06\x04\x88\x06\x01o\x06\x01L\x06\x01;\x06\x01\x19\x06\x01\b\x06\x01\x06\x06\x00\x05_\x02\x03\x00\x12\x00??\xe9\x129/]]]]]]]\xe9\x01\x10\xf6\xe92]\x10\xe6]210)\x01\x11!\x15!\x11!\x15!\x01\xe9\xfe\xcf\x03F\xfd\xeb\x01\xf0\xfe\x10\x05\xb6\xfe\xfe\x87\xfd\x00\x00\x00\x00\x01\x00w\xff\xec\x05'\x05\xcb\x00'\x00D@(''\f%\\\x14\x90\x01\x01\x01)`)p)\x02\x1f)\x01\x1d[\ff('_+\x00\x01\x00\x00\"\x18_\x11\x04\"_\a\x13\x00?\xe9?\xe9\x129/]\xe9\x01\x10\xf6\xe9]]\x10\xde]2\xe9\x129/10\x01!\x11\x0e\x03#\".\x01\x0254\x126$32\x16\x17\a.\x01#\"\x0e\x02\x15\x14\x1e\x023267\x11!\x02\xe3\x02D:v\u007f\x8bN\xa4\xfd\xadZd\xc3\x01\x1d\xb8u\xe0]gD\xab^f\xa3t>+\\\x91eB[(\xfe\xeb\x035\xfd\n\x13\x1f\x15\fa\xbf\x01\x19\xb8\xac\x01\x16\xc3i2(\xf8\".G\x82\xb8ql\xb3\x82H\f\b\x011\x00\x00\x01\x00\xb8\x00\x00\x05\x14\x05\xb6\x00\v\x00G@+\t\x01Z\x00e\r\x8f\r\x01\x00\r\x01\b\x04Z\x05d\f\x03_\x88\b\x01L\b\x01;\b\x01\x19\b\x01\b\b\x01\b\b\n\x06\x03\x05\x00\x12\x00?2?39/]]]]]\xe9\x01\x10\xf6\xe92]]\x10\xf6\xe9210)\x01\x11!\x11!\x11!\x11!\x11!\x05\x14\xfe\xcb\xfe\x0f\xfe\xca\x016\x01\xf1\x015\x02w\xfd\x89\x05\xb6\xfd\xc3\x02=\x00\x00\x00\x00\x01\x00B\x00\x00\x02\xdb\x05\xb6\x00\v\x00;@#\x9f\r\xff\r\x02\r@\r\x10H0\r\x01\b\v\v\nZ\x05\x02\x02\xc0\x03\x01\x03\t\x04_\x06\x03\n\x03_\x00\x12\x00?\xe92?\xe92\x01/]3\x113\xe92\x113]+]10)\x0157\x11'5!\x15\a\x11\x17\x02\xdb\xfdg\xb2\xb2\x02\x99\xb2\xb2\xb0R\x03\xb2R\xb0\xb0R\xfcNR\x00\x00\x00\x01\xff9\xfeR\x01\xee\x05\xb6\x00\x13\x004@#\x06\x11\x16\x11\x02\x04\x04\fZ\x0fe\x15\x1f\x15o\x15\u007f\x15\x8f\x15\xaf\x15\x05\r\x03\a\a\x17\a'\a\x03\a_\x00\x00/\xe9]?\x01]\x10\xf6\xe12/10]\x03\"&'\x11\x1e\x0132>\x025\x11!\x11\x14\x0e\x02\x02Ab\"%Q0.O;!\x016I\x83\xb6\xfeR\r\t\x01\x02\b\f\x141R>\x05\x8b\xfa\u007f~\xb6w8\x00\x01\x00\xb8\x00\x00\x05\x12\x05\xb6\x00\f\x00r@N9\x02y\x02\x02\x1d\x03\x01\t\n\x19\nI\nY\n\x04\t\x01\x19\x019\x01I\x01Y\x01y\x01\x06\n\v\v\x01d\x00\x01\x00\x00\x01\x00\x10\x00\x00\x0ei\x02\x01\x05\f\x15\fE\fU\f\x04\x02\b\f\x03\x04Z\x05d\r$\b\x01\x02\b\x01\n\x06\x03\x05\x01\x12\x00?3?3\x1299]\x01\x10\xf6\xe9\x172]]\x113/8]]33\x11310]]\x00]])\x01\x01\a\x11!\x11!\x117\x01!\x01\x05\x12\xfe\xa0\xfe\xb0t\xfe\xca\x016z\x01N\x01X\xfe-\x02`V\xfd\xf6\x05\xb6\xfd@\xcf\x01\xf1\xfdm\x00\x00\x00\x00\x01\x00\xb8\x00\x00\x04\x02\x05\xb6\x00\x05\x00)@\x19/\x04?\x04\x8f\x04\xaf\x04\x04\x04\a\x1f\a\x01\x03Z\x00d\x06\x01\x03\x03_\x00\x12\x00?\xe9?\x01\x10\xf6\xe9]\x10\xce]103\x11!\x11!\x11\xb8\x016\x02\x14\x05\xb6\xfbJ\xff\x00\x00\x01\x00\xb8\x00\x00\x06\x96\x05\xb6\x00\x1d\x00\x8d\xb5\x10\x18\t\rH\r\xb8\xff\xe0@Y\t\rHG\x1d\x016\x1d\x01\a\x1d\x17\x1d'\x1d\x03I\x00\x01;\x00\x01\t\x00\x19\x00)\x00\x03\x1b\x10\x88\x13\xa8\x13\xf8\x13\x03\x13\x10\n\rH\x13]\x1d\x00\x0e\x0e\v\x12e\x1fX\x02\xb8\x02\x02\x02\r6\nF\n\x02\a\n\x17\n'\n\x03\n^\vd\x1e\x1c\x02\x02\x10\f\x03\v\x0e\x13\x03\x00\x12\x00?\x172?33/3\x01\x10\xf6\xe9]]22]\x10\xf6\x119\x1133\xe9+]2210]]]]]]++!\x01#\x16\x17\x1e\x03\x15\x11!\x11!\x013\x01!\x11!\x114>\x02767#\x01\x03\x04\xfe\xbf\t\x06\x04\x02\x03\x03\x01\xfe\xeb\x01\xa6\x01<\x06\x01P\x01\xa6\xfe\xdf\x01\x02\x03\x01\x04\x03\b\xfe\xa6\x04{\\V%NLF\x1c\xfdX\x05\xb6\xfb\xa2\x04^\xfaJ\x02\xb4\x1aBJL$T[\xfb\x87\x00\x00\x00\x00\x01\x00\xb8\x00\x00\x05\x8b\x05\xb6\x00\x17\x00\x85@,\x01 \x0e\x18H9\x01I\x01\x02\v\x01\x1b\x01+\x01\x03\x0e\x01I\x16\x01\x16\b\t\fH\x16^\x17e\x19\x00\x19\x10\x19 \x19\xb0\x19\xf0\x19\x05\x19\xb8\xff\xc0\xb3\r\x10H\f\xb8\xff\xe0@\x15\x0e\x18H6\fF\f\x02\x04\f\x14\f$\f\x03\x03\fF\t\x01\t\xb8\xff\xf8@\x10\t\fH\t^\nd\x18\x16\x02\v\x03\x0e\n\x00\x12\x00?22?33\x01\x10\xf6\xe9+]22]]++]\x10\xf6\xe9+]22]]+10)\x01\x01#\x16\x17\x1e\x01\x15\x11!\x11!\x013&'.\x035\x11!\x05\x8b\xfew\xfd\xc1\t\x06\x04\x04\x05\xfe\xeb\x01\x87\x02>\x06\x03\x04\x01\x03\x02\x01\x01\x16\x04RMLA\x8f9\xfdP\x05\xb6\xfb\xb9LJ CC>\x19\x02\xb4\x00\x00\x00\x00\x02\x00w\xff\xec\x05\x96\x05\xcd\x00\x13\x00'\x00(@\x17\x1e[\x00g)/)?)\x02\x14[\nf(#_\x0f\x04\x19_\x05\x13\x00?\xe9?\xe9\x01\x10\xf6\xe9]\x10\xf6\xe910\x01\x14\x02\x0e\x01#\".\x01\x0254\x12>\x0132\x1e\x01\x12\x05\x14\x1e\x0232>\x0254.\x02#\"\x0e\x02\x05\x96O\xa2\xf7\xa8\xa8\xf7\xa1OO\xa2\xf7\xa9\xa8\xf6\xa1O\xfc (S~WY\u007fQ''Q~XW\x80S(\x02ݩ\xfe\xea\xc6ll\xc6\x01\x17\xaa\xaa\x01\x15\xc4kk\xc5\xfe\xeb\xabs\xb7\u007fDD\u007f\xb7ss\xb7\x80DD\x80\xb7\x00\x00\x00\x00\x02\x00\xb8\x00\x00\x04m\x05\xb6\x00\b\x00\x17\x00=@&\x06\x16\x01\x04Z\t\x19\x1f\x19?\x19_\x19\x8f\x19\xaf\x19\x05\x00\x10Z\x11d\x18\x00_\x00\x0f\x01\x0f\x0f\x10\b_\x12\x03\x10\x12\x00??\xe9\x119/]\xe9\x01\x10\xf6\xe92]\x10\xde\xe910]\x0132654&+\x01\x05\x14\x0e\x02+\x01\x11!\x11!2\x1e\x02\x01\xee=\x83\x85w\u007fO\x02\u007f:\x85٠G\xfe\xca\x01\x96\x8dͅ@\x03\x06humh\xca`\xb0\x86P\xfd\xf8\x05\xb6?u\xa9\x00\x02\x00w\xfe\xa4\x05\xc1\x05\xcd\x00\x1b\x00/\x00W@8\x16\x05&\x056\x05\x03\x04\x05\x01\x05\b\x12&[\x00g1/1?1\x02\x1c[\x12f0;\aK\a[\a\x03)\a\x01\v\a\x01\a\x06\x10\x06\x05\r+_\x17\x04!_\r\x13\a\x00/?\xe9?\xe9\x129\x01/83]]]\x10\xf6\xe9]\x10\xf6\xe9\x1299]]10\x01\x14\x0e\x02\a\x01!\x01\"\a\x06\"#\".\x01\x0254\x12>\x0132\x1e\x01\x12\x05\x14\x1e\x0232>\x0254.\x02#\"\x0e\x02\x05\x96'OwQ\x01i\xfer\xfe\xf4\a\x06\x05\v\x04\xa8\xf7\xa1OO\xa2\xf7\xa9\xa8\xf6\xa1O\xfc (S~WY\u007fQ''Q~XW\x80S(\x02\xddwѬ\x86,\xfem\x01J\x01\x01l\xc6\x01\x17\xaa\xaa\x01\x15\xc4kk\xc5\xfe\xeb\xabs\xb7\u007fDD\u007f\xb7ss\xb7\x80DD\x80\xb7\x00\x02\x00\xb8\x00\x00\x05\n\x05\xb6\x00\b\x00\x1e\x00~@P\t\x1e\x01\t\x1d\x01\x05\x0f\x01\x1e\x15\x1a\x04\x01\x04Z\x04\x15D\x15\x02\x15\x0f\x10\x01\x10\x10\x1d\x00\x1c\x01\x1c\x10\x1c\x1c \x1f \x01\x00\nZ\vd\x1f\x15\f\xbb\x00\x01\xa9\x00\x01\x88\x00\x01g\x00\x01L\x00\x01;\x00\x01\b\x00\x01\x00`\t\t\n\b_\f\x03\x1d\n\x12\x00?3?\xe9\x119/\xe9]]]]]]]\x129\x01\x10\xf6\xe92]\x113/8]39/]9]\xe9]\x11310]]]\x0132654&+\x01\x19\x01!\x11! \x04\x15\x14\x0e\x02\a\x16\x17\x1e\x02\x1f\x01!\x01\x01\xeeT\x81px~O\xfe\xca\x01\x90\x01\x19\x01\f(CW0oX&G8\x12\x11\xfe\xa8\xfe\xc3\x03-gdhX\xfdy\xfd\xcf\x05\xb6\xd9\xddKz_G\x18\xb2\x8c\x0254.\x02'.\x0354>\x0232\x1e\x02\x17\a.\x03#\"\x06\x15\x14\x1e\x02\x17\x1e\x03\x03\xd9C\x81\xbbyj\xc5T0bee23I-\x15#?Y7.reDAx\xabj5ecd5d-NJG$NS\x197W>K~[2\x01\x96b\x9do<,,\x01 \x17+\"\x14\x17)9\")?74\x1d\x18De\x8fdb\x9bk8\x0e\x1a&\x19\xf1\x15 \x16\vSE%924!(Se\x80\x00\x00\x00\x01\x00)\x00\x00\x04;\x05\xb6\x00\a\x00>@'\xc0\t\xd0\t\x02o\t\x010\t@\t\x02\x0f\t\x01\x84\x06\x01\x06\x06\x00Z\x01\x03\x03@\x01\x90\x01\x02\x01\a\x03_\x04\x03\x00\x12\x00??\xe12\x01/]3/\x10\xe92/]]]]]10)\x01\x11!\x11!\x11!\x02\xcd\xfe\xcb\xfe\x91\x04\x12\xfe\x92\x04\xb4\x01\x02\xfe\xfe\x00\x01\x00\xae\xff\xec\x05\f\x05\xb6\x00\x17\x000@\x1c\t\b\x01\t\x04\x01\x16Z\x01e\x19\u007f\x19\x8f\x19\x02\x0eZ\vd\x18\x13_\x06\x13\f\x00\x03\x00?2?\xe1\x01\x10\xf6\xe9]\x10\xf6\xe910\x00]]\x01\x11\x14\x0e\x02#\".\x025\x11!\x11\x14\x1e\x023265\x11\x05\fE\x8dԏ\x87ϋH\x015 ?^?\x83u\x05\xb6\xfcNrĐRM\x8e\xc7z\x03\xae\xfciQsI\"\x98\x99\x03\x95\x00\x00\x00\x00\x01\x00\x00\x00\x00\x04\xe1\x05\xb6\x00\x10\x00X@+\t\x03\x01\x06\x02\x01\x03\x02\v\v\x04\t\x00\x19\x00\x02\x00\x00\x01`\x01p\x01\x90\x01\x04\x01\x10\x01\x01\x12\x0f\x12o\x12\x02\x06\x05\x16\x05\x02\x05\x04\xb8\xff\xf0@\n\x04\x14\v\x01\v\x02\x12\x04\x00\x03\x00?2?3]\x01/83]]\x113/8]3]\x129\x113310]]\x01!\x01!\x01!\x13\x1e\x03\x17>\x037\x03\xa8\x019\xfe8\xfe\xae\xfe9\x019\xf8\x05\x11\x13\x12\x05\x05\x12\x14\x13\x05\x05\xb6\xfaJ\x05\xb6\xfc\x90\x11P``!!`_P\x12\x00\x01\x00\x00\x00\x00\aj\x05\xb6\x006\x01&@m\t5)595I5\x04f%v%\x02'%\x01\x06%\x01i$y$\x02($\x01\t$\x01\x06\x14&\x146\x14F\x14\x04k\x12{\x12\x8b\x12\x03\x12\x10\n\x0eH\v\x12\x01d\x11t\x11\x84\x11\x036\x11F\x11V\x11\x03%\x11\x01\x16\x11\x01\x05\x11\x01k\x01{\x01\x8b\x01\x039\x01I\x01Y\x01\x03*\x01\x01\x19\x01\x01\n\x01\x01d\x00t\x00\x84\x00\x03\x00\xb8\xff\xf0@>\n\x0eH\x04\x00\x01\x01\x00-V%\x86%\x02\x17%\x01%Y$\x89$\x02\x18$\x01$\t\x12\x11\x1c\x1c\t-\x03\x13\x195\x015\x006`6p6\x906\x046\x10668\x0f8\x01\x16\x14\x01\x14\x13\xb8\xff\xf0@&\x135$\x8f\t\x01{\t\x01,\t<\tL\t\x03\x1b\t\x01\n\t\x01\t\x13\x03-\x1c\x14\x1c\x01\x06\x1c\x01\x1c\x12\x12\x00\x12\x00?2\x113]]\x113?3]]]]]33\x01/83]]\x113/8]3]\x12\x179\x1133\x113]]3]]\x113310]+]]]]]]]]]]]]+]]]]]]]]])\x01\x03.\x05'\x0e\x05\a\x03!\x01!\x13\x1e\x05\x17>\x057\x13!\x13\x1e\x05\x17>\x057\x13!\x06\n\xfe\xa0\xb4\x04\v\x0e\x0e\f\t\x02\x02\t\f\r\r\f\x04\xb2\xfe\x9f\xfe\xa0\x011\xa6\x03\v\x0e\x0f\x0e\f\x03\x03\v\f\x0f\f\v\x03\xcb\x01\x10\xcb\x03\v\r\x0e\r\v\x03\x03\v\x0e\x0f\x0e\v\x03\xa6\x011\x02\xd1\x0f6DJF;\x12\x12;EJD8\x10\xfd1\x05\xb6\xfc\xe2\x10=LTPE\x16\x16DMRG7\f\x033\xfc\xcd\f7GRMD\x16\x16EPTL=\x10\x03\x1e\x00\x00\x00\x00\x01\x00\x00\x00\x00\x05\x04\x05\xb6\x00\v\x00|@8\t\t\x19\t\x02\t\n\n\x00\v\b\x05\x02\x02\x04\t\x01\x19\x01\x02\x01\x00\x00`\x00p\x00\x90\x00\x04\x00\x10\x00\x00\r\x80\r\x01o\r\x01\x06\a\x16\a\x02\a\x06\x06\x06\x03\x16\x03\x02\x03\x04\xb8\xff\xf0@\x14\x04\x04\b\x14\b\x02\v\x02\x1b\x02\x02\b\x02\x01\t\x06\x03\x03\x01\x12\x00?3?3\x1299]]\x01/83]3\x113]]]\x113/8]3]\x129\x11333\x113\x113]10)\x01\t\x01!\t\x01!\t\x01!\x01\x05\x04\xfe\x9e\xfe\xd5\xfe\xd5\xfe\xb4\x01\xbc\xfec\x01V\x01\x12\x01\f\x01N\xfe^\x02)\xfd\xd7\x02\xf2\x02\xc4\xfd\xf2\x02\x0e\xfd+\x00\x01\x00\x00\x00\x00\x04\xac\x05\xb6\x00\b\x00q@\x16\x05\b\x01\n\x01\x01\xcf\n\x010\n\xb0\n\x02\x0f\n\x01\b?\a\x01\a\xb8\xff\xf0@\x1f\a\a\x05\x01\xeb\x02\x01\x8d\x02\x01d\x02t\x02\x02\x19\x02\x01\x02\x10\x02\x02\x00\x04Z0\x05\x01\x05\x03\x00\xb8\xff\xe8@\f\t\x11H\x00\x06\x06\x01\x04\x12\b\x01\x03\x00?3?\x129/3+3\x01/]\xe992/8]]]]3\x113/8]3]]]10]]\t\x01!\x01\x11!\x11\x01!\x02V\x01\b\x01N\xfeD\xfe\xcc\xfeD\x01P\x03\\\x02Z\xfc\x83\xfd\xc7\x02/\x03\x87\x00\x00\x00\x01\x001\x00\x00\x04\x1f\x05\xb6\x00\t\x00t@1i\x03\x01\x03\x10\x13\x18H\v\x03\x01\x03\a\a\x00\t@\t`\tp\t\x90\t\xa0\t\xb0\t\a\t\t\v\xaf\v\xcf\v\xef\v\x03P\v\x01O\v\x01f\b\x01\b\xb8\xff\xf0@\x1c\x13\x18H\x04\b\x01\b\x04O\x02\xaf\x02\xbf\x02\x03\x02\x02\n\a\x04_\x05\x03\x02\b_\x01\x12\x00?\xe92?\xe92\x11\x013/]33]+]]]]\x113/]3\x113]+]10)\x015\x01!\x11!\x15\x01!\x04\x1f\xfc\x12\x02k\xfd\xa8\x03\xc8\xfd\x96\x02}\xc9\x03\xed\x01\x00\xc8\xfc\x12\x00\x00\x00\x01\x00\x8f\xfe\xbc\x02s\x05\xb6\x00\a\x00\"@\x13\x04\x00\xf2\x06\xf0\x00\x01\x10\x01\x02\x01\x05\xf7\x02\xf8\x06\xf7\x01\xf9\x00?\xe1?\xe1\x01/]\xe9\xed210\x01!\x11!\x15#\x113\x02s\xfe\x1c\x01\xe4\xe0\xe0\xfe\xbc\x06\xfa\xd3\xfa\xac\x00\x00\x01\x00\f\x00\x00\x03B\x05\xb6\x00\x03\x00(@\x0e\x06\x00\x01\t\x02\x01\x02\x01\x10\x01\x01\x05\x00\x03\xb8\xff\xf0\xb3\x03\x01\x00\x03\x00?/\x01/83\x113/8310]]\t\x01!\x01\x01!\x02!\xfe\xeb\xfd\xdf\x05\xb6\xfaJ\x05\xb6\x00\x01\x003\xfe\xbc\x02\x17\x05\xb6\x00\a\x00 @\x10\x03\x00\xf2\x01\xf0\x06\x06\t\x00\xf7\a\xf9\x03\xf7\x04\xf8\x00?\xe9?\xe9\x11\x013/\xe9\xed210\x173\x11#5!\x11!3\xdf\xdf\x01\xe4\xfe\x1cq\x05T\xd3\xf9\x06\x00\x00\x01\x00\b\x02\b\x04=\x05\xbe\x00\b\x007@\x1e+\x05\x01\x05\b\x15\b\x02\t\x04\x19\x04\x02\t\x01\x01\x02\x01\x05\x05\x04\x03\x03\n\b\x00\x00\x00\x01\x03\x00?3/\x01/2\x113/39=/3310]]]\x00]\x13\x013\x01#\x01\x06\x02\a\b\x01\xb6\x90\x01\xef\xef\xfe\xbeE\x8fD\x02\b\x03\xb6\xfcJ\x02\x83\xa1\xfe\xba\x9c\x00\x00\x00\x01\xff\xfc\xfe\xbc\x03N\xffH\x00\x03\x00\x12\xb6\x00\x00\x05\x01\x01\xb9\x02\x00/\xe9\x01/\x113/10\x01!5!\x03N\xfc\xae\x03R\xfe\xbc\x8c\x00\x00\x00\x00\x01\x01L\x04\xd9\x03P\x06!\x00\r\x00*@\x19E\a\x01\x03\a\x01\f\f\a\x06\x0f\a_\ao\a\x03\a\a\x0f\x00_\x00\x02\x00\x00/]2/]\x01/33/]]10\x01.\x03'5!\x1e\x03\x17\x15\x02\x85\"_\\L\x10\x01V\x10+.0\x15\x04\xd9\x1cSXQ\x1b\x15\"QQL\x1d\x1b\x00\x00\x00\x02\x00V\xff\xec\x03\xfe\x04u\x00\x1f\x00.\x00a@>\x05\x1d\x15\x1d\x02\n\r\x1a\r\x02\n\n\x1a\n\x02@\x18\x01\x18\x18\f\x10\x02.F\x1eU0O0\xbf0\x0200\x01&G0\f\x01\f Q\x10\x10\a:\x17\x01\x17\x14N\x1b\x10\x02)N\a\x16\x00\x15\x00??\xe92?\xe13]\x129/\xe9\x01/]\xe9]]\x10\xf6\xe922\x119/]10]]]!'#\x0e\x03#\".\x02546?\x0154&#\"\x06\a'>\x0132\x16\x15\x11\x01\a\x0e\x03\x15\x14\x1632>\x025\x03);\t!BNa@DtU0\xe4\xe3\xb2PHH\x89EcT\xccp\xd1\xdf\xfe\xd1e=T3\x17D7*H5\x1e\x98-A*\x14+W\x85[\xb2\xa9\t\x06TEB*#\xca/6\xc4\xc8\xfd\x17\x02\x06\x04\x02\x1c/A(F;\x1d9S6\x00\x00\x00\x00\x02\x00\xa0\xff\xec\x04w\x06\x14\x00\x1f\x000\x008@ \x05\a\x01\x05\x03\x01.G\x05W2\x15\x0f&F\x12T1\x13\x00\x12\x15+M\x10\n\x16\x1b M\x00\x10\x00?\xe92?3\xe9??\x01\x10\xf6\xe922\x10\xf6\xe910]]\x012\x1e\x02\x15\x14\x0e\x02#\".\x02'#\a#\x11!\x11\x14\x06\a\x06\a3>\x03\a\"\x0e\x02\a\x15\x14\x1e\x0232654&\x02\xf4V\x8ef99h\x92X8WD3\x15\x153\xe9\x011\x04\x02\x03\x03\f\x156GZ03G,\x14\x02\x13,I6[UU\x04sJ\x92؎\x90ْJ\x18(3\x1c{\x06\x14\xfe\x96!M!''#<-\x1a\xf4%JqK!Q~U,\xad\xa5\xa5\xa5\x00\x00\x00\x01\x00f\xff\xec\x03\xbc\x04s\x00\x1f\x00<@&\n\a\x01\n\x03\x01\r\x00\x1b\x10\x1b\x02\x1b\x1b!\xb0!\x01/!O!o!\x03\x14G\x05V \x11M\n\x10\x17M\x00\x16\x00?\xe9?\xe9\x01\x10\xf6\xe9]]\x113/]310]]\x05\".\x0254>\x0232\x16\x17\a.\x01#\"\x06\x15\x14\x163267\x15\x0e\x03\x02qx\xc1\x89IK\x89\xc1vV\xaaKXBz7oddkW\x8eJ%FGM\x14B\x8bٗ\xa7\xe1\x88:*&\xe8\x1d%\xa9\xa9\xa8\xa0-#\xfe\x12\x1c\x12\t\x00\x00\x02\x00f\xff\xec\x04=\x06\x14\x00\x1f\x000\x008@ \n\a\x01\n\x03\x01\x1a\x15%H\x17U2.G\x05V1\x19\x15\x16\x00\x0f+M\n\x10\x1b M\x00\x16\x00?\xe92?\xe92??\x01\x10\xf6\xe9\x10\xf6\xe92210]]\x05\".\x0254>\x0232\x1e\x02\x173&'.\x015\x11!\x11#'#\x0e\x0372>\x02754.\x02#\"\x06\x15\x14\x16\x01\xe9V\x8ef99i\x92X6ZH9\x16\n\x06\x05\x05\a\x011\xe9;\r\x156GY76L/\x17\x01\x14.N;`Z[\x14J\x91؎\x90ٓJ\x19-;#'(\"M!\x01f\xf9\xec\x91\"=,\x1a\xf3%KpK!Q~U,\xad\xa5\xa5\xa5\x00\x00\x00\x00\x02\x00f\xff\xec\x04D\x04s\x00\b\x00)\x00e@A\n\x10\x01\n\f\x01\x8f%\x01%%\x1a\t\x04\x01\x04H\x18W+\xcf+\x010+\x01\x03\a\x1a\x01\x1aF\x0eV*9\x1aI\x1a\x02(\x1a\x01\x1aP\xd9\x03\x01\xc8\x03\x01|\x03\x01\x03\x03\x1f\x00O\x13\x10\x1fN\t\x16\x00?\xe9?\xe9\x129/]]]\xe9]]\x01\x10\xf6\xe9]2]]\x10\xf6\xe9]\x129/]10]]\x01\"\x06\a!.\x03\x03\".\x0254>\x0232\x1e\x02\x1d\x01!\x1e\x0332>\x027\x15\x0e\x03\x02dQk\b\x01\x85\x01\x180H\txʒQJ\x85\xbbro\xb3}C\xfdV\x02%C_=3[VT,(QZh\x03\x9arz3V?$\xfcRF\x8dב\x93ܓJC\x82\xbdz\x94@gG&\v\x16!\x16\xec\x15\x1d\x14\t\x00\x00\x00\x00\x01\x00)\x00\x00\x03H\x06\x1f\x00\x19\x00J@,\n\x18\t\x0eH\x00\x00\x10\x10\x1b/\x1b\xaf\x1b\x02\x10\x1b\x01\x18\x02F\x03\a\x03\x05\x05\x00\x03\x10\x03\x02\x03\x01\x05N\a\x18\x0f\x14M\r\x01\x02\x15\x00??\xe9?3\xe12\x01/]3/\x113\x10\xe92]]\x113/9/10+\x01#\x11!\x11#5754>\x0232\x16\x17\a.\x01#\"\x06\x1d\x013\x02\xe5\xe3\xfeϨ\xa84`\x88U\\~,H\x1fD.<1\xe3\x03y\xfc\x87\x03y\x93RRk\x8dT#\x1d\x12\xe0\v\x12M\x027.\x0354>\x027.\x0154>\x0232\x1e\x02\x17\x01\x14\x1e\x0232654&+\x01\"\x0e\x02\x13\x14\x1632654&#\"\x04=\xa3\x14\x106j\xa0j\x176\r\x14\x17\x1a*8\x1e\xaeQ\x80Y0L\x97\xe0\x93r\xaar9*FY/\x15)!\x15\x13$6\"Xg8l\xa0h\x1421*\f\xfe\\\x150N9\x9c\x9ebg\x8d\x1b>4#nDEH@?I\x89\x04\\\xa63\"I*U\x8dd7\x05\x03\x11%\x1a\x15\x19\x0f\x05%LuQ_\x99k9+QrH=Z@(\v\t *3\x1c 4-(\x15&\xa8rZ\x8ec4\x05\a\b\x03\xfb\x06\x19/$\x15XJ?*\r 5\x03f[dd[Zj\x00\x00\x01\x00\xa0\x00\x00\x04j\x06\x14\x00\x1d\x006@!\x05\x1b\x15\x1b\x02\x01F\x00U\x1f\x00\x1f0\x1f\x80\x1f\x03\x0f\vF\fT\x1e\x15\x05M\x18\x10\r\x00\f\x00\x15\x00?2??\xe92\x01\x10\xf6\xe92]\x10\xf6\xe910])\x01\x114&#\"\x0e\x02\x15\x11!\x11!\x11\x14\x06\a\x06\a3>\x0132\x1e\x02\x15\x04j\xfe\xcfKN;P0\x14\xfe\xcf\x011\x04\x03\x04\x03\x101\x98`S\x87`4\x02\x8dyy0^\x8aY\xfd\xf2\x06\x14\xfe\xc3*]'.,WM/d\x9bl\x00\x00\x02\x00\x93\x00\x00\x01\xdf\x06\x14\x00\x13\x00\x17\x00/@\x1c\xbf\x19\x010\x19`\x19\x90\x19\x03\n\x14F\x00\x15T\x18\x05S\xdf\x0f\x01\x0f\x0f\x16\x0f\x14\x15\x00??3/]\xed\x01\x10\xf62\xe92]]10\x134>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x01!\x11!\x93\x1a-=\"\"<-\x1b\x1b-<\"\"=-\x1a\x01>\xfe\xcf\x011\x05\u007f+9#\x0e\x0e#9+*:#\x0f\x0f#:\xfa\xab\x04^\x00\x00\x02\xff\xae\xfe\x14\x01\xdf\x06\x14\x00\x13\x00'\x00E@,\n\x12\x1a\x12\x02\x14\x04\x04\fF\x1e\x0fU)\xbf)\x010)`)\x90)\x03\x19S\xdf#\x01##\r\x0f\a\a\x17\a'\a\x03\aM\x00\x1b\x00?\xe9]?3/]\xed\x01]]\x10\xf62\xe12/210\x00]\x13\"&'5\x1e\x0132>\x025\x11!\x11\x14\x0e\x02\x034>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02f0f\"\x1f6\"\x19-!\x14\x011'W\x8a6\x1a-=\"\"<-\x1b\x1b-<\"\"=-\x1a\xfe\x14\x0e\v\xf0\n\t\x0f'A3\x04\xaa\xfb)M\x87e:\ak+9#\x0e\x0e#9+*:#\x0f\x0f#:\x00\x01\x00\xa0\x00\x00\x04\xb8\x06\x14\x00\x0e\x00n\xb6\n\x06\x01\f\x02\x01\x04\xb8\xff\xf0\xb3\x12\x17H\x04\xb8\xff\xf0@6\v\x0eH\x05\x04\x15\x04\x02\x04\a\x02\x03\x03\x060\x05\x01$\x05\x01\x00\x05\x10\x05\x02\x05\x10\x05\x05\x10\r\tF\nT\x0f\v\x00\a\x10\x11\x14H\b\a\x01\a\x00\x02\x06\n\x15\x02\x0f\x00??3\x1299]+?\x01\x10\xf6\xe92\x113/8]]]33\x11399]++10]]\x017\x01!\t\x01!\x01\a\x11!\x11!\x11\a\x01\xc5p\x01\x11\x01X\xfel\x01\xae\xfe\xa0\xfe\xf0w\xfe\xcf\x011\x10\x02`\xaa\x01T\xfe\x1b\xfd\x87\x01\xaeR\xfe\xa4\x06\x14\xfdJ\xfe\x00\x00\x01\x00\xa0\x00\x00\x01\xd1\x06\x14\x00\x03\x00 @\x13\xbf\x05\x010\x05`\x05\x90\x05\x03\x00F\x01T\x04\x02\x00\x00\x15\x00??\x01\x10\xf6\xe9]]10)\x01\x11!\x01\xd1\xfe\xcf\x011\x06\x14\x00\x00\x00\x01\x00\xa0\x00\x00\x06\xf0\x04s\x00*\x00q@4\x05 \x15 \x02\x03\x17\x01\x18\x00F\xf6\x01\x01\x99\x01\x01\x86\x01\x01y\x01\x018\x01\x01&\x01\x01\x19\x01\x01\x01\x01\f#F\"U,_,\x01\x0f\vF\fT+\x18\x10\x10\xb8\xff\xe8@\x10\t\rH\x10'\x05M\x1e\x15\x10\r\x0f#\f\x00\x15\x00?22??3\xe922+\x113\x01\x10\xf6\xe92]\x10\xf6\xe9\x119/]]]]]]]\xe9210]])\x01\x114&#\"\x0e\x02\x15\x11!\x113\x173>\x0332\x16\x173>\x0332\x16\x15\x11!\x114&#\"\x06\x15\x04`\xfe\xcfHM:M.\x14\xfe\xcf\xe9)\x11\x18CPZ.s\xa1+\x19\x18DR[.\xb4\xb7\xfe\xceHMm\\\x02\x8dyy0^\x8aY\xfd\xf2\x04^\x8f+>(\x13OU+>(\x13\xc3\xd7\xfd'\x02\x8dyy\xad\xa1\x00\x00\x01\x00\xa0\x00\x00\x04j\x04s\x00\x1a\x00;@$E\x12\x01\x05\x18\x15\x18\x02\x01F\x00U\x1c\x00\x1c0\x1c\x80\x1c\x03\x0f\vF\fT\x1b\x10\x05M\x15\x10\r\x0f\f\x00\x15\x00?2??\xe92\x01\x10\xf6\xe92]\x10\xf6\xe910]\x00])\x01\x114&#\"\x0e\x02\x15\x11!\x113\x173>\x0332\x1e\x02\x15\x04j\xfe\xcfIP

(\x13/d\x9bl\x00\x00\x00\x00\x02\x00f\xff\xec\x04d\x04s\x00\v\x00\x1f\x006@!\x05\x0e\x01\x05\x1e\x01\n\x14\x01\n\x18\x01\x06G\fW!_!\x01\x00G\x16V \tM\x1b\x10\x03M\x11\x16\x00?\xe9?\xe9\x01\x10\xf6\xe9]\x10\xf6\xe910]]]]\x01\x14\x1632654&#\"\x06\x05\x14\x0e\x02#\".\x0254>\x0232\x1e\x02\x01\x9e^ji^^ki]\x02\xc6G\x85\xbfwo\xba\x87LG\x85\xbexo\xba\x87L\x021\xa7\xa9\xaa\xa6\xa7\xa5\xa5\xa7\x8cؔMM\x94،\x8bؓLL\x93\xd8\x00\x00\x02\x00\xa0\xfe\x14\x04w\x04s\x00\x1f\x000\x008@ \x05\x1d\x01\x05\x19\x01.G\x1bW2&\x10\fF\rT1\x11 M\x16\x10\x0e\x0f\f\x1b\x05+M\x00\x16\x00?\xe92???\xe92\x01\x10\xf6\xe922\x10\xf6\xe910]]\x05\".\x02'#\x16\x17\x1e\x01\x15\x11!\x113\x173>\x0332\x1e\x02\x15\x14\x0e\x02\x03\"\x0e\x02\a\x15\x14\x1e\x0232654&\x02\xec7WD4\x15\x10\x04\x04\x03\x05\xfe\xcf\xf8+\x0e\x156GZ7W\x8ef8:i\x91\xb63G,\x14\x02\x13,I6[UU\x14\x18(3\x1c#\x1f\x1a7\x0f\xfe;\x06J\x91\"<-\x1bJ\x92؎\x8fٓJ\x03\x93%JqK!Q~U,\xad\xa5\xa5\xa5\x00\x00\x00\x02\x00f\xfe\x14\x04=\x04s\x00\x10\x000\x008@ \n\x18\x01\n\x14\x01!\x05&F#U2\x0eG\x16V1$\x1b\"\x0f \vM\x1b\x10,\x00M\x11\x16\x00?\xe92?\xe92??\x01\x10\xf6\xe9\x10\xf6\xe92210]]%2>\x02754.\x02#\"\x06\x15\x14\x16\a\".\x0254>\x0232\x1e\x02\x1737!\x11!\x1146767#\x0e\x03\x02Z7K.\x16\x01\x13/M:`Z[\x10W\x8ef89i\x92X8ZI8\x16\b\x18\x01\x02\xfe\xcf\x05\x02\x03\x03\r\x146GZ\xdb%JqK%Q~U,\xad\xa5\xa8\xa6\xefJ\x91؎\x8fٓK\x19-;#\x8f\xf9\xb6\x01\xd5\x139\x1b !\"=,\x1a\x00\x00\x00\x01\x00\xa0\x00\x00\x03H\x04s\x00\x1a\x00@\xb3\xc0\x06\x01\x06\xb8\xff\xc0@!\t\rH\x06\x06\x1c\xff\x1c\x01F\x15\x01\x15\x11F\x12T\x1b\x13\x0f\x11\x15\x05\x16E\x16U\x16\x03\x16\v\x00\x10\x00?\xc93]??\x01\x10\xf6\xe92]]\x113/+]10\x012\x1e\x02\x17\x11.\x03#\"\x0e\x02\x15\x11!\x113\x173>\x03\x02\xe7\f\x1d\x1b\x17\x06\b\x1c\x1f\x1e\n;cG'\xfe\xcf\xe7-\x0f\x188EW\x04s\x01\x03\x03\x02\xfe\xe2\x02\x04\x03\x01\x1eCmO\xfd\xc7\x04^\xa8+F1\x1b\x00\x00\x00\x00\x01\x00b\xff\xec\x03\x89\x04s\x007\x00c@\x1c\x05\x02\x15\x02\x02\n!\x1a!\x02'\x15\b\t\fH\x15F\x00W9\xcf9\x0109\x01.\xb8\xff\xf8@\x19\t\fH.F\n\x1fV8\x15.\x05+N(\x10\n\rH($\x10\x10N\v\xb8\xff\xf0\xb5\n\rH\v\x05\x16\x00?3+\xe1?3+\xe1\x1299\x01\x10\xf62\xe9+]]\x10\xf6\xe9+310]]\x01\x14\x0e\x02#\".\x02'5\x1e\x0332>\x0254.\x02'.\x0354>\x0232\x16\x17\a.\x01#\"\x06\x15\x14\x1e\x02\x17\x1e\x03\x03\x89@v\xa8h7^TN(*]\\W%):%\x11\r.YKIkE\"7\x0e*L=GrR,\x01LX\x84X,\a\x10\x18\x12\xfc\x15\"\x19\x0e\x0f\x1b%\x15\x15!%/\"!APgGNuN'..\xd8$.,&\x14\x1f!'\x1c\x1f=Nh\x00\x00\x01\x00/\xff\xec\x03\x0e\x05L\x00\x19\x00\\\xb6\n\x18\t\fH\x15\x03\xb8\xff\xc0@4\n\x0fH\x02\x03\x01\x03\x03\x1b\x1f\x1bO\x1b_\x1bo\x1b\x9f\x1b\xdf\x1b\x06\x13\x17F\f\x0e\x0e\x10_\fo\f\xdf\f\xef\f\xff\f\x05\f\x16\x0eN\x12\x10\x13\x0f\x00M\a\x16\x00?\xe9?33\xe92\x01/]33/\x10\xe92]\x113/]+310+%267\x15\x0e\x01#\".\x025\x11#5?\x013\x15!\x15!\x11\x14\x16\x02f-Q*+\u007fKI~\\5\x92\xa8X\xc3\x01\x10\xfe\xf0@\xdf\x14\x0f\xe3\x16\x1d\"U\x8fl\x02\x1b\x81f\xec\xee\xe5\xfd\xe5A>\x00\x00\x00\x00\x01\x00\x9a\xff\xec\x04d\x04^\x00\x1a\x00;@#J\x04\x01\n\n\x01\x01\x18F\x19U\x1c\x00\x1c0\x1c\x80\x1c\x03\x0eF\rT\x1b\x00\n\x01\x01\x01\x12\a\x16\x18\r\x0f\x00?3?\xc92]/\x01\x10\xf6\xe9]\x10\xf6\xe9210]\x00]!'#\x0e\x03#\".\x025\x11!\x11\x14\x1632>\x025\x11!\x11\x03{)\x10\x19ER\\0R\x86`4\x011IP

\x017\x13!\x01\x01\x8b\xfeu\x01?\xb9\x11\x19\x03\x06\x03\x19\x11\xb8\x01@\xfeu\x04^\xfd\x839{15w9\x02}\xfb\xa2\x00\x00\x01\x00\x00\x00\x00\x06s\x04^\x003\x01\x17\xb7\xd63\xe63\xf63\x033\xb8\xff\xf0@\x10\t\x0fH1\b\t\fH\xd5$\xe5$\xf5$\x03$\xb8\xff\xe8@\x10\t\x0fH\xda#\xea#\xfa#\x03#\x18\t\x0fH\x14\xb8\xff\xf8@\x17\t\fH\xd9\x12\xe9\x12\xf9\x12\x03\x12\x10\t\x0fH\xd5\x11\xe5\x11\xf5\x11\x03\x11\xb8\xff\xe8@Q\t\x0fH\xda\x00\xea\x00\xfa\x00\x03\x00\x18\t\x0fH\x98\x12\xa8\x12\x02\x12\x97\x11\xa7\x11\x02\x11\x1a\x97$\xa7$\x02$\x98#\xa8#\x02#\t\x973\xa73\x023\x98\x00\xa8\x00\x02\x00*\x06\x1a\x16\x1a&\x1a\x03\t*\x19*)*\x03*\t\x1a\x03\x1312\xa02\xc02\x022\xb8\xff\xc0@\x11\t\fH2\x1025\x805\x905\x02\x0f5\x01\x14\x13\xb8\xff\xf0@\x1a\x131#\t\t\x19\tI\t\x03\t\x13\x0f+\x06\x1b\x16\x1bF\x1b\x03\x1b\x1b\x00\x12\x15\x00?33/]3?3]33\x01/83]]\x1138+]\x113\x12\x179]]\x113]3]\x113]3]\x113]3]10+]+]+]++]+]++]!\x03.\x03'&'#\x06\a\x0e\x03\a\x03!\x01!\x13\x1e\x03\x173>\x057\x13!\x13\x1e\x03\x173>\x037\x13!\x01\x03\xf6V\x04\r\x10\x11\t\x14\x18\x06\x17\x13\b\x11\x10\r\x04Z\xfe\xb8\xfe\xd3\x01/q\t\x13\x11\x0e\x04\x06\x01\a\n\v\v\t\x03z\x01Pu\x05\x10\x11\f\x01\x06\x03\x0f\x12\x15\tu\x01+\xfe\xcf\x01\x87\x11?OY,gwwh,ZO@\x12\xfe}\x04^\xfe\x11'moc\x1d\x13=FI@1\n\x02\x18\xfd\xe8\x16\\ic\x1c\x19bqp'\x01\xef\xfb\xa2\x00\x00\x00\x01\x00\n\x00\x00\x04X\x04^\x00\v\x00\xfd@[\x05\n\x01\n\b\x01\n\x04\x01\x05\x02\x01\x18\x04\x01\x04\x05\x05\x9a\x00\xaa\x00\xba\x00\xda\x00\xea\x00\x05i\x00\x01Z\x00\x019\x00I\x00\x02\x18\x00(\x00\x02\n\x00\x01F\x06V\x06f\x06\x96\x06\xa6\x06\xb6\x06\xd6\x06\xe6\x06\b\x17\x06'\x067\x06\x03\x06\x06\x01\x06\t\x03\x00\x04\v\x18\b\x01\b\xa0\a\xc0\a\x02\a\xb8\xff\xc0@\x19\t\fH\a\x10\a\a\r\x1f\r/\r\xaf\r\x03\x17\x02\x01\x02\x01\x17\n\x01\n\v\xb8\xff\xf0@\x1b\v\t\x18\x10\x13Hi\t\x01Z\t\x019\tI\t\x02*\t\x01\x19\t\x01\n\t\x01\x03\xb8\xff\xe0@ \x10\x13Hf\x03\x01T\x03\x016\x03F\x03\x02$\x03\x01\x16\x03\x01\x04\x03\x01\t\x03\x01\b\n\x15\x04\x01\x0f\x00?3?3\x1299]]]]]]+]]]]]]+\x01/83]32]]\x113/8+]3]\x12\x179]]]]]]]]]3\x113]10]]]]\t\x01!\x1b\x01!\t\x01!\v\x01!\x01\x85\xfe\x98\x01Z\xba\xbd\x01Z\xfe\x93\x01}\xfe\xa6\xcd\xcd\xfe\xa6\x02;\x02#\xfe\xb0\x01P\xfd\xdd\xfd\xc5\x01j\xfe\x96\x00\x00\x01\x00\x00\xfe\x14\x04P\x04^\x00\x1e\x00\xaa@.\x18\x1e8\x1e\x02\n\x1e\x01\x1e\xf6\x0e\x017\x0e\x01\x16\x0e\x01\x0e\x05\x05\x00\xe9\f\x01\xda\f\x01)\f9\f\x02\x1a\f\x01\t\f\x01\f\xa0\r\xc0\r\x02\r\xb8\xff\xc0@,\t\fH\r\x10\r\r p \x90 \xb0 \xd0 \x04\x1f \x01\x15\x15\xe6\x01\x01\xd5\x01\x01\xa8\x01\x01&\x016\x01\x02\x15\x01\x01\x06\x01\x01\x01\x00\xb8\xff\xf0@\x12\x00\x0e\xe0\x05\x01\x04\x05\x01\x05\x1e\x1e\x1f\x18\x11\x1b\f\x00\x0f\x00?2?\xc9\x113\x113]]3\x01/83]]]]]]3/]]\x113/8+]3]]]]]\x129\x113]]]3]]10\x11!\x13\x1e\x01\x173>\x037\x13!\x01\x0e\x01#\"&'5\x1e\x0132>\x02?\x01\x01N\xb4\x10\x0f\x02\x06\x02\a\n\r\a\xb0\x01P\xfeF>֡4L\x1b\x15@#0D1#\r\x13\x04^\xfd\x8b4v/\x178:9\x17\x02u\xfb\x13\xb1\xac\v\x06\xf2\x05\b\x1a/B)8\x00\x01\x007\x00\x00\x03m\x04^\x00\t\x00|@\x17I\x03Y\x03i\x03\x03\x03\x10\x11\x18H\n\x03\x01\x03\a\a\t\x04\x04\x02\t\xb8\xff\xc0@\x1b\t\x11H\t\t\v\xa0\v\xc0\v\x02\u007f\v\x01\v@\v\x0eHF\bV\bf\b\x03\b\xb8\xff\xf0@\x1c\x11\x18H\x05\b\x01\b\xa0\x02\xc0\x02\x02\x02@\r\x12H\x02\a\x04N\x05\x0f\x02\bN\x01\x15\x00?\xe12?\xe12\x01/+]3]+]+]]\x113/+\x129/\x113\x113]+]10)\x015\x01!5!\x15\x01!\x03m\xfc\xca\x01\xc9\xfeV\x03\x04\xfeF\x01ʹ\x02\xc1\xe9\xc6\xfdQ\x00\x00\x00\x00\x01\x00\x1f\xfe\xbc\x02\xd5\x05\xb6\x00(\x00N@2\v(\t\x11H%\x18\t\x11H\x0f!\xf3\x15\x1c\xf0\b\x02\xf40'\x01'\x17\x02\xf6\xcd\x03\x01o\x03\u007f\x03\x8f\x03\x03I\x03\x01\x03\x03\x0e!\xf5\"\xf9\x0f\xf5\x0e\xf8\x00?\xe9?\xe9\x119/]]]\xe99\x01/]\xee3\xe92\xec210++\x004>\x02'\x114>\x023\x15\x0e\x03\x15\x11\x06\a\x15\x1e\x01\a\x11\x14\x1e\x02\x17\x15\".\x025\x11\x01\x1f\x83}>aB!\x02&c\xaa\x83(A-\x18\x06\xe4sz\x03\x18-A(\x83\xaac&\x01oR\xef\x13+D0\x01>JiC \xe1\x01\f\x1f6+\xfeջ#\f\x11n^\xfe\xd5+6\x1f\f\x01\xe2 CjJ\x01;\x00\x00\x01\x01\xc7\xfe/\x02\xa2\x06\x0e\x00\x03\x00\x18@\r\x02\xaa0\x03@\x03p\x03\x03\x03\x02\x00\x00\x00?/\x01/]\xe110\x013\x11#\x01\xc7\xdb\xdb\x06\x0e\xf8!\x00\x00\x00\x01\x00\x1f\xfe\xbc\x02\xd5\x05\xb6\x00(\x00R\xb9\x00\x1c\xffس\t\x11H\x02\xb8\xff\xe8@)\t\x11H\x12\v\xf0%\xf4\x18\x1f\x0f\x05\xf3(\x10%\xf6\xcd$\x01o$\u007f$\x8f$\x03I$\x01$$\x06\x18\xf5\x19\xf8\x06\xf5\x05\xf9\x00?\xe9?\xe9\x129/]]]\xe99\x01/\xed332\xee\xe9210++\x05\x14\x0e\x02#5>\x035\x11&675&'\x114.\x02'52\x1e\x02\x15\x11\x06\x1e\x023\x15\"\x06\x15\x01\xd5&c\xaa\x83(@-\x19\x03zr\xe3\x06\x19-@(\x83\xaac&\x01 Ba>}\x83-JjC \xe2\x01\f\x1f6+\x01+^n\x11\f#\xbb\x01++6\x1f\f\x01\xe1 CiJ\xfe\xc20D+\x13\xefRa\x00\x00\x00\x00\x01\x00X\x02'\x04\x10\x03}\x00$\x00\\@\x10\x1c\x1f,\x1f\x02\x1f(\x0e\x13H\x15\f%\f\x02\f\xb8\xff\xe8@\r\x0e\x13H\x1e&\n\x18\xad\n\x00 \x01 \xb8\xff\xc0\xb3\x14\x18H \xb8\xff\xc0@\x13\f\x0fH \x1d\x05\xad?\x0e_\x0e\x02\x0e@\x15\x18H\x0e\x00/+]\xe933/++]3\xe9\x01/\x10\xce10\x00+]+]\x01.\x03#\"\x0e\x02\a5>\x0132\x1e\x02\x17\x1e\x0332>\x027\x15\x06#\".\x02\x02\x10%9/*\x17\x1d><9\x1a3\u007fN\x1e39F0&90*\x16\x1d><9\x19e\x9b\x1e39F\x02h\x10\x16\r\x05\x13!,\x19\xe767\x05\x0e\x19\x14\x10\x15\r\x05\x13 -\x19\xe7m\x05\r\x19\x00\x00\x00\x00\x02\x00u\xfe\x8f\x01\xd3\x04^\x00\x03\x00\x17\x00i@J[\x00\x01\x06\x00\x0e\x01\xb0\x19\xe0\x19\xf0\x19\x03\x1f\x19/\x19?\x19\u007f\x19\x9f\x19\x05\xd7\x02\x01\xc6\x02\x01w\x02\x01\x16\x02f\x02\x02\x03\x02\x01\x02\x00\x04\x01\x04\x96\xd8\x03\x01\xc9\x03\x01x\x03\x01i\x03\x01\x00\x03\x01\x03`\x0e\x01\x0e\x00\t\x9b\x13\x0f\x03\x00/?\xfd\xc6\x01/]3]]]]]\xed]2]]]]]]]]10_]\x133\x13!\x01\x14\x0e\x02#\".\x0254>\x0232\x1e\x02\xa8\xf43\xfe\xa6\x01^\x1b0@%#?0\x1c\x1c0?#%@0\x1b\x02^\xfc1\x05%/A(\x12\x12(A/-A)\x13\x13)A\x00\x00\x00\x00\x01\x00\x8f\xff\xec\x03\xe1\x05\xcb\x00)\x00c\xb1\x0e)\xb8\x01\x02@\x18\v\x00\x00\x06\x13\xaf%\xbf%\xcf%\x03%+\xa0+\xb0+\xc0+\x03\x1en\x06\xb8\xff\xc0@ \t\fH\x06*\x1f*?*\x02!u\x01\x00(\x19v\u007f\f\x8f\f\x9f\f\x03\f\v\x80\x0e\x90\x0e\x02\x0e\x00/]3\xcd]\xe1/\xcd3\xe1\x01]\x10\xd6+\xe1]\x10\xc6]2\x119/3\xe1210\x055.\x0354>\x02753\x15\x1e\x03\x17\a.\x03#\"\x0e\x02\x15\x14\x163267\x15\x0e\x01\a\x15\x02\x1f\\\x94h88i\x94[\xb2&LG?\x18V\x15587\x18B\\;\x1ar\x81L\x8d23|E\x14\xce\rK\x85lj\x8dˈK\r\xac\xa4\x01\v\x10\x14\v\xe2\n\x13\x0f\t(S\u007fW\xab\x9f%\x18\xef\x1d\x1f\x02\xc8\x00\x00\x00\x01\x00R\x00\x00\x04B\x05\xcb\x00&\x00\x9a@j\n$\x1a$*$\x03\v\xe7\x0f\x01\xd6\x0f\x01\xc5\x0f\x01'\x0fW\x0f\x97\x0f\x03\x15\x0f\x01\x0fn!\xb9\x1d\xf9\x1d\x02k\x1d{\x1d\x02:\x1d\x01\x04\r\x01\x1d\r\x1d\r\x15\x1f@\x17`\x17\x02\x17\x03c\x15\x83\x15\x02 \x15@\x15\x02\x04\x15\x01\x15\x0e\x1fw\v\x92 \x01 \x00\x18G\x14\xc7\x14\xd7\x14\x03\x14s\x16\x18\xe9\a\x01\xc8\a\x01\at\x04\x00\a\x00?2\xe1]]?\xe1]2\x119/]3\xe12\x01/]]]3/]3\x1299//]]]]3\xe1]]]]]210]\x012\x16\x17\a.\x01#\"\x06\x1d\x01!\x15!\x15\x14\x0e\x02\a!\x11!5>\x03=\x01#5354>\x02\x02\xa8n\xb3P]Gu?CK\x01N\xfe\xb2\x1b,5\x1b\x02\xa6\xfc\x10*C/\x18\xb2\xb2?o\x99\x05\xcb0\"\xe6\x1d#M_\xc1ۏ7Q:(\x0e\xfe\xfc\xf8\x12*:R:\x91\xdb\xc3q\x9fd.\x00\x00\x02\x00\\\x00\xfe\x04\f\x04\xaa\x00\"\x006\x00\x96@_\x0132\x16\x177\x17\a\x1e\x01\x15\x14\x06\a\x17\a'\x0e\x01#\"&'\a'7&7\x14\x1e\x0232>\x0254.\x02#\"\x0e\x02\xa8\x1c\x19\x81\x94\u007f+f36`/\u007f\x95\x81\x19\x1d\x1c\x1a}\x91\u007f,c68c+}\x92\u007f5\xcf\x1e3D'(F5\x1e\x1e5F('D3\x1e\x02\xd36c,\u007f\x93\u007f\x19\x1c\x1b\x1c\x81\x8f\x81*g68b-}\x91}\x17\x1c\x19\x1a{\x91}[j'E3\x1d\x1d3E'(E3\x1e\x1e3E\x00\x00\x00\x01\x00\b\x00\x00\x04b\x05\xb6\x00\x16\x00\xaf@\x12\x0f\x13\x13\f\x06\x16\x01\x16\x0f\x15O\x15\x9f\x15\xaf\x15\x04\x15\xb8\xff\xf0@<\x15\x15\f\b\x04\x04\t\x01\x01\x01@\x02\xa0\x02\x02\x02\x10\x02\x02\x00\a\x05\x03\x15\x03\x02\x03\vn\n\x14\x01\x14\x10\x00\f@\f\x80\f\x90\f\xa0\f\xd0\f\x06\f\n\x0e\xfa\x0f\a\x0f\x06\x12\xfa\x13\x03\x00\xb8\xff\xd8@\x1c\f\x0fH\a\x00\x01\x00\x13\xdf\x0f\x01\x0f\x13\x1f\x13\x9f\x13\x03\x0f\x13\x0f\x13\x01\v\x12\x16\x01\x03\x00?3?\x1299//]]\x113]+3\x10\xe12\x113\x10\xe12\x01/]33]\xe12]292/8]3]9\x113\x113/8]3]\x129\x11310\x01\x13!\x013\x15#\x153\x15#\x15!5#535#53\x01!\x025\xf4\x019\xfe\x96\xc2\xf5\xf5\xf5\xfe\xe1\xf8\xf8\xf8\xbf\xfe\x9b\x01<\x03\\\x02Z\xfd\x15\xb2\x8a\xb2\xddݲ\x8a\xb2\x02\xeb\x00\x00\x00\x00\x02\x01\xc7\xfe/\x02\xa2\x06\x0e\x00\x03\x00\a\x00$@\x13\x02\x06\xaa\x030\a@\ap\a\x03\a\x04\x03\x04\x03\x06\x00\x00\x00?/99//\x01/]3\xe1210\x013\x11#\x113\x11#\x01\xc7\xdb\xdb\xdb\xdb\x06\x0e\xfc\xd1\xfe\u007f\xfc\xd1\x00\x00\x00\x02\x00j\xff\xec\x03\u007f\x06)\x00C\x00V\x00\xc5@\x1f51E1\x02\x161&1\x0220B0\x02\x140$0\x02:\x11J\x11\x02\x19\x11)\x11\x02)\xb8\xff\xe8@\t\t\rH\n\x18\t\x0eH$\xb8\xff\xe0@V\t\rH$H'D\x05 \t\rHR\x05\b\bM\x18M(M\x03M\x9a!!:\x99\x10'XoX\u007fX\x8fX\x03X@\n\rH\x17\x99\b\b\aD\x17D'D\x03D\x9a\x0000\x00\x00\x90\x00\x02\x00?$H\x1c\x05RHRHR\r05\x9d,\x16\x11\x14\x9d\r\x01\x00?\xe12?\xe12\x1199//\x1133\x1133\x01/]3/\x10\xe1]3/\xe1+]\x10\xde2\xe13/\xe1]\x1199+\x11\x1299+10++\x00]]]]]]\x134>\x027.\x0154>\x0232\x16\x17\a.\x01#\"\x06\x15\x14\x1e\x02\x17\x1e\x03\x15\x14\x06\a\x1e\x01\x15\x14\x0e\x02#\"&'5\x1e\x0332>\x0254.\x02'.\x037\x14\x16\x1f\x01>\x0354.\x02'\x0e\x03y\x15%0\x1b?F:k\x95[f\xb0URD\x8dNQJ\x184Q8GuS.E8>?>r\xa3en\xa9F'Z\\Y'8J-\x13\x0f-QBLxR+\xdfqv\x0f\x0f\x1c\x16\r\x154[F\x12 \x19\x0f\x03%,K?2\x12(vKAkL)/%\xbe 3.0\x19*''\x17\x1cDSe>d{%(iJJwT.)&\xcf\x14#\x1b\x10\x12 *\x19\x19'&*\x1c @Qf[?a3\x06\v\x1d$,\x1a 631\x19\a\x1b$,\x00\x00\x02\x00\xf8\x04\xf8\x03\xa6\x06\x04\x00\x13\x00%\x00%@\x14\x1e\x82\x14\x14\n\x82\x00\x19\x05\x8c#\xef\x0f\x01\x80\x0f\xd0\x0f\x02\x0f\x00/]]3\xed2\x01/\xed2/\xed10\x134>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02%4>\x0232\x1e\x02\x15\x14\x0e\x02#\"&\xf8\x16%3\x1d\x1d3&\x17\x17&3\x1d\x1d3%\x16\x01\x93\x16&4\x1e\x1c3'\x17\x17'3\x1c\x01>\xc82\x13\x18H\x01H\xc8$\x04\x00?\xe9]?\xe9]\x1199//]]\x10\xe9\x10\xe9\x01/\xe9]\x10\xde\xe9]\x1199//]]\x113\xe910]]\x01\"\x06\x15\x14\x163267\x15\x0e\x01#\".\x0254>\x0232\x16\x17\a&\x014>\x0432\x1e\x04\x15\x14\x0e\x04#\".\x047\x14\x1e\x0232>\x0254.\x02#\"\x0e\x02\x03\u007faj`k9\x8499vMk\xa0k64i\x9eiS\x9aDJq\xfc}6a\x8a\xa7\xc0hh\xc0\xa7\x8aa66a\x8a\xa7\xc0hh\xc0\xa7\x8aa6\x8e`\xa5\xde\u007f\u007fޥ``\xa5\xde\u007f\u007fޥ`\x03\U000940c7\x91\x1e\x1d\xbf\x1b\x1eD|\xadjg\xab{D,\"\xa8:\xfe\xe9h\xc0\xa7\x8aa66a\x8a\xa7\xc0hh\xc0\xa7\x89b55b\x89\xa7\xc0h\u007fޥ``\xa5\xde\u007f\u007fޥ``\xa5\xde\x00\x00\x00\x02\x00/\x02\xf0\x02\x8f\x05\xc7\x00\x1f\x00.\x00\x81\xb9\x00\x1c\xff\xe8@\x0e\t\rH\v\x18\t\rH\a\x18\t\rH\x02\xb8\xff\xe8@D\t\rH\n\x06\x1a\x06\x02\x05\x0f\x15\x0f\x02.\x01\x0f\xe2\x1e0\x0f0\x8f0\xaf0\xbf0\xdf0\xef0\x060@\v\x0fH%\xe2\x16O\t_\t\x02\t@\x11\x15H\t.\xe8\x0f\x0f(\x15\x12\xe6\x19\xde\x01(\xe7\x04\xfc\x00\xfb\x00??\xe92?\xe93\x129/\xe9\x01/+]3\xe9+]\x10\xde\xe922\x00]10]\x01++++\x01'\x0e\x01#\".\x0254>\x02?\x014&#\"\x06\a'>\x0132\x1e\x02\x15\x11\x01\x0e\x03\x15\x14\x1632>\x02=\x01\x02\b\x1f(qD2Q: +QwKZ;6(b4B>\x94[DeB!\xfe\xe6&/\x1b\t&\x1b 3$\x13\x02\xfcn:@\x1b7T9\xfeF\x01=\x03\x14\x1d\"\x12&$\x16'7 $\x00\x02\x00R\x00^\x04\\\x04\x04\x00\x06\x00\r\x00[@2\x03\xeb\x06\xec\x01\x02\x02\x05\x12\x04\x01\x04\x04\n\xeb\r\xec\f\v\v\b/\t_\t\x02\x19\t\x01\t\x0f\r\n\n\x03\v\f\f\x04\x05\xed\t\b\b\x02\x01\xed\x06\x03\xef\x00?3\xed22\x113\xed22\x113\x113\x113\x01\x10\xde]]22\x113\xfd\xe93/]33\x113\xfd\xe910\x13\x01\x17\x03\x13\a\x01%\x01\x17\x03\x13\a\x01R\x015\xdb\xd9\xd9\xdb\xfe\xcb\x01\xfa\x015\xdb\xd9\xd9\xdb\xfe\xcb\x02=\x01\xc7w\xfe\xa4\xfe\xa4w\x01\xc5\x1a\x01\xc7w\xfe\xa4\xfe\xa4w\x01\xc5\x00\x00\x01\x00X\x00\xf8\x04\x10\x03?\x00\x05\x00\x16@\t\x01\xaa\x00\a\x03\x00\x03\xad\x04\x00/\xe93\x01/\x10\xde\xe910%#\x11!5!\x04\x10\xdb\xfd#\x03\xb8\xf8\x01l\xdb\x00\x00\x00\xff\xff\x00=\x01\xa8\x02V\x02\xa2\x12\x06\x00\x10\x00\x00\x00\x04\x00d\xff\xec\x06D\x05\xcb\x00\r\x00\x18\x004\x00H\x00\x8a@W\x06\x03\x00\x0e\b\xc4\t\x12\xc4\x00\x04\x00?\x00\x8f\x00\x02@\t`\t\x80\t\x03\t\x00\t\x00\x19\x18?\x01?\xc3'J\x175\x015\xc3\x19\x05\t\x03\a\xc9\x0e\x18\xc9\n\u007f\t\x8f\t\x02\x00\n\x10\np\n\x80\n\x04\t\x0e\n\n\x0e\t\x03 \x17:\x01:\xc8.\x13\x18D\x01D\xc8 \x04\x00?\xe9]?\xe9]\x11\x179///]]\x10\xe9\x10\xe19\x113\x01/\xe9]\x10\xde\xe9]\x1199//]]\x113\x10\xe9\x10\xe92\x119910\x01\x14\x06\a\x13#\x03#\x11#\x11!2\x16\x0132654.\x02+\x01\x014>\x0432\x1e\x04\x15\x14\x0e\x04#\".\x047\x14\x1e\x0232>\x0254.\x02#\"\x0e\x02\x04\x85MB\xed\xfe\xb2/\xe5\x01\b\xb6\xa8\xfe\u007f\x1fB9\x0f\x1f/ \x1d\xfd`6a\x8a\xa7\xc0hh\xc0\xa7\x8aa66a\x8a\xa7\xc0hh\xc0\xa7\x8aa6\x8e`\xa5\xde\u007f\u007fޥ``\xa5\xde\u007f\u007fޥ`\x03\x89^n\x1d\xfep\x01R\xfe\xae\x03\x94\x8c\xfe\xf29B#.\x1b\v\xfe\xdfh\xc0\xa7\x8aa66a\x8a\xa7\xc0hh\xc0\xa7\x89b55b\x89\xa7\xc0h\u007fޥ``\xa5\xde\u007f\u007fޥ``\xa5\xde\x00\x00\x01\xff\xfa\x06\x14\x04\x06\x06\xdd\x00\x03\x00\x11\xb6\x00\x05\x01\x01\xbc\x02\xbd\x00?\xe9\x01/\x10\xc610\x01!5!\x04\x06\xfb\xf4\x04\f\x06\x14\xc9\x00\x02\x00\\\x03\x19\x03\x10\x05\xcb\x00\x13\x00'\x00\x1d@\x0e\x14\xaa\x00\x1e\xaa\n)\x19\xae\x0f#\xae\x05\a\x00?\xe9\xd4\xe9\x01\x10\xde\xe9\xd4\xe910\x134>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x027\x14\x1e\x0232>\x0254.\x02#\"\x0e\x02\\6^~HH\u007f]66]\u007fHH~^6\xbf\x18*9 9*\x19\x19*9 9*\x18\x04qG~^77^~GH~]55]~H\x1f8*\x19\x19*8\x1f 9+\x19\x19+9\x00\x02\x00X\x00\x00\x04\x10\x04\xee\x00\v\x00\x0f\x00D@(\a\x04\x01\x0e\a\r\x02\a\x06\t\xaa\x02\x03\x00\r\xad\f\v\t\x00\xad\x04\x06\u007f\x03\x8f\x03\xaf\x03\xcf\x03\xff\x03\x05\x03@\t\x0eH\x03\x00/+]33\xe922/\xe9\x01/33\xe922\x113\x11310\x00]\x01!5!\x113\x11!\x15!\x11#\x015!\x15\x01\xc7\xfe\x91\x01o\xdb\x01n\xfe\x92\xdb\xfe\x91\x03\xb8\x02\xa2\xdb\x01q\xfe\x8f\xdb\xfe\x93\xfe\xcb\xdb\xdb\x00\x00\x00\x01\x00/\x02J\x02\xbe\x05\xcb\x00\x1e\x00R\xb9\x00\x15\xff\xe8@3\t\rH\x0e\x10\v\x0eH\b\xe0\x00\x17 \x0f \x1f / _ \xdf \xef \xff \a\x05\x1dE\x1de\x1d\x03\x1d\x0f\xa0\x01\x01\x01\x0e\v\xe4\x12\xde\x02\x1d\xe4\x01\xdd\x00?\xe92?\xe93\x01/]33]]\x10\xde2\xe9\x00+10\x01+\x01!57>\x0354&#\"\x06\a'>\x0132\x1e\x02\x15\x14\x0e\x02\x0f\x01!\x02\xbe\xfdy\xe0.=%\x0f0((W5{A\xa2mBmM+\x196T\x0254&#\"\x06\a'>\x0132\x1e\x02\x02\x9aQY3J1\x18\xb0\xbaL\x84AB\x84IJE\x10&@0p\\4@$\f23/T9e>\x97g>jM,\x04\xe1Ed\x1d\r\n*7B$y\x8b##\xbe(265\x15&\x1d\x12\xa0\x12\x1f'\x15&2&(\x8d/>!\x037!\x15\x0e\x05\a\x01L\x150/*\x10\x01V\v*6>?:\x17\x04\xd9\x1b\x1dLQQ\"\x15\x1218;82\x13\x00\x01\x00\xa0\xfe\x14\x04j\x04^\x00\x1d\x009@\"B\x0f\x01\r\tF\nU\x1f\x00\x1f0\x1f\x80\x1f\x03\x19\x1dF\x1cT\x1e\t\x1c\x0f\x1b\x1b\x0e\x03M\x11\x16\f\x15\x00??\xe12??3\x01\x10\xf6\xe12]\x10\xf6\xe1210]\x01\x14\x1632>\x025\x11!\x11#'#\x0e\x01#\"&'\x16\x17\x1e\x01\x15\x11!\x11!\x01\xd1KQ:N0\x14\x011\xe9+\f#iK6Z\x1c\x02\x03\x02\x03\xfe\xcf\x011\x01\xd1yy0^\x8aY\x02\x0e\xfb\xa2\x96UU.,*+%T$\xfe\xc0\x06J\x00\x00\x00\x00\x01\x00q\xfe\xfc\x04\x8f\x06\x14\x00\x13\x005\xb2\x04\xfd\x05\xb8\xff\xc0@\x17\t\x13H\x05\x05\r\x01\xfd\x00\x15\x00\r\x01\r\b\b\x00\x03\x9e\x12\x00\x05\x00\x00/2?\xe9\x129/\x01/]\x10\xde\xe9\x119/+\xe910\x01#\x11#\x11#\x11\x06#\".\x0254>\x023!\x04\x8f\xa1\xa6\xa2=U_\x9bm\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02u\x1b0@%#?0\x1c\x1c0?#%@0\x1b\x02\xd3/A(\x12\x12(A/-A)\x13\x13)A\x00\x00\x00\x01\xff\xdb\xfe\x14\x01\xa2\x00\x00\x00\x1b\x00K@0\x03(\t\x11H\x17\x15\x14\x14\b\b\x11\x83?\x00\u007f\x00\x8f\x00\xcf\x00\xdf\x00\x05\x00?\x1d\u007f\x1d\x8f\x1d\xcf\x1d\x04\x17\x15\x0f\x14\x1f\x14\x02\x14\x14\x05\x15\x0e\x8d\x05\x00/\xe1/\x129/]\x129\x01]/]\xe13/3\x113310\x00+\x05\x14\x0e\x02#\"&'5\x1e\x0332654&'73\a\x1e\x03\x01\xa2\x1fHwW-H\x1d\x0f%'%\x0f\x1d+J\\N\xc1\x1b\x1f:-\x1c\xfa9Z>!\f\t\xa8\x04\a\x06\x04\x1b#%9\x0e\x9a=\n\"/=\x00\x01\x00\\\x02J\x02H\x05\xb6\x00\x10\x007@$\x0f\x12\x1f\x12/\x12_\x12\xdf\x12\xef\x12\xff\x12\a\x0f\x01\x0e\x0e\a\x00\xe0p\x01\x80\x01\x90\x01\x03\x01\r\a\x0f\xdc\x00\xdd\x00??3\xcd\x01/]\xe933/\x113]10\x01#\x114>\x027\x0e\x03\x0f\x01'%3\x02H\xee\x01\x03\x03\x01\x06\x13\x15\x15\bNm\x01-\xbf\x02J\x01\xbe\x14=>4\f\b\x15\x16\x14\a=\u007f\xeb\x00\x00\x00\x00\x02\x009\x02\xf0\x02\xb8\x05\xc7\x00\x13\x00\x1f\x00Y\xb9\x00\x11\xff\xe8\xb3\t\rH\r\xb8\xff\xe8@5\t\rH\a\x18\t\rH\x03\x18\t\rH\x1a\xe2\x00!\x0f!\x8f!\xaf!\xbf!\xdf!\xef!\x06!@\v\x0eH\x14\xe2O\n_\n\x8f\n\x03\n\x1d\xe6\x0f\xde\x17\xe6\x05\xfc\x00?\xe9?\xe9\x01/]\xe9+]\x10\xde\xe910\x00++++\x01\x14\x0e\x02#\".\x0254>\x0232\x1e\x02\x05\x14\x1632654&#\"\x06\x02\xb8-TvJEuU/-SwKDsV0\xfeL7><77<>7\x04\\W\x87]11]\x87WW\x87]00]\x87Wdeeddcc\x00\x00\x00\x02\x00T\x00^\x04^\x04\x04\x00\x06\x00\r\x00]@4\f\v\v\b\t\xec\n\xeb\x05\x04\x13\r\x01\x02\r\x01\x04\r\x03\x02\xec\x03\xeb/\x00_\x00\x02\x16\x00\x01\x00\x0f\r\n\n\x03\v\f\f\x04\x05\xed\t\b\b\x02\x01\xed\x00\x03\xef\x00?3\xed22\x113\xed22\x113\x113\x113\x01\x10\xde]]\xe9\xed\x172/_]\x113\xe9\xed22\x11310\t\x01'\x13\x037\x01\x05\x01'\x13\x037\x01\x04^\xfe\xcb\xdb\xd9\xd9\xdb\x015\xfe\x06\xfe\xcb\xdb\xd9\xd9\xdb\x015\x02#\xfe;w\x01\\\x01\\w\xfe9\x1a\xfe;w\x01\\\x01\\w\xfe9\x00\x00\xff\xff\x00.\x00\x00\x06\x92\x05\xb6\x10'\x00\xd1\x02\xc9\x00\x00\x10&\x00{\xd2\x00\x11\a\x00\xd2\x03\x9c\xfd\xb7\x00*@\x1a\x03\x02\x18\x18\x03\x02\x10\x18`\x18\x02\x18\x00p\x00\x01\x00\x01\x00\x04\x10\x04`\x04\x03\x04\x11]5\x11]5\x11]55\x00?55\xff\xff\x00.\x00\x00\x06\xb4\x05\xb6\x10'\x00\xd1\x02\xc9\x00\x00\x10&\x00{\xd2\x00\x11\a\x00t\x03\xf6\xfd\xb7\x000@\x1f\x02\x16\x18\x02\xaf\x16\x01\x10\x16\x01\x16\x00\x80\x00\x01p\x00\x01D\x00\x01\x00\x01\x00\x04\x10\x04`\x04\x03\x04\x11]5\x11]]]5\x11]]5\x00?5\x00\x00\xff\xff\x00Z\x00\x00\x06\xb0\x05\xc9\x10'\x00\xd1\x03\x10\x00\x00\x10'\x00\xd2\x03\xba\xfd\xb7\x11\x06\x00u\x1f\x00\x00 @\x12\x02\x01\a\x18\x02\x01P\a\x01\a\x00\x80\x00\x010\x00\x01\x00\x11]]5\x11]55\x00?55\x00\x00\x00\x02\x00B\xfey\x03\x9e\x04^\x00'\x00;\x00C@)\f\x1a\x1c\x1a\x022\x96(('\x00\x00\x12\vH\x1c\x00\x12\x10\x12 \x12@\x12P\x12`\x12\x06\x12\v\x17\x00-\x9b7\x0f\x11\x0eM\x17\x00/\xe93?\xfd\xce\x119\x01/]/\xe9\x119/\xc93/\xed10]\x01\x15\x14\x0e\x02\a\x0e\x03\x15\x14\x163267\x17\x0e\x03#\".\x0254>\x027>\x03=\x01\x01\x14\x0e\x02#\".\x0254>\x0232\x1e\x02\x02\xae\x15+D0*:$\x10NNE\xa0Tg+fmp6f\xa3r=\x1b8S7)5\x1e\v\x01)\x1b0@%#?0\x1c\x1c0?#%@0\x1b\x02^J3SKF&!438%9J;)\xdd\x1a-\"\x141]\x87U?cUP,!1,0\x1f;\x01V/A(\x12\x12(A/-A)\x13\x13)A\x00\xff\xff\x00\x00\x00\x00\x053\as\x12&\x00$\x00\x00\x11\a\x00C\xff\xf3\x01R\x00\x15\xb4\x02\x17\x05&\x02\xb8\xff\xa8\xb4\x1c#\x04\a%\x01+5\x00+5\x00\xff\xff\x00\x00\x00\x00\x053\as\x12&\x00$\x00\x00\x11\a\x00v\x00\xa2\x01R\x00\x13@\v\x02\x17\x05&\x02V\x17\x1e\x04\a%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\x00\x00\x00\x053\as\x12&\x00$\x00\x00\x11\a\x00\xc3\x00N\x01R\x00\x13@\v\x02\x17\x05&\x02\x02\x1e*\x04\a%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\x00\x00\x00\x053\a`\x12&\x00$\x00\x00\x11\a\x00\xc5\x00N\x01R\x00\x13@\v\x02\x1a\x05&\x02\x03\x1b)\x04\a%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\x00\x00\x00\x053\aV\x12&\x00$\x00\x00\x11\a\x00j\x00L\x01R\x00\x17@\r\x03\x02&\x05&\x03\x02\x01\x175\x04\a%\x01+55\x00+55\x00\x00\x00\xff\xff\x00\x00\x00\x00\x053\a\n\x12&\x00$\x00\x00\x11\x06\x00\xc4LX\x00 @\x114.3\x03\x02\x1c\x1c4\x03\x03\x02\x01!\x17\x04\a%\x01+55\x00?3/55\x11\x129\x00\x02\x00\x00\x00\x00\x06\xe7\x05\xb6\x00\x0f\x00\x13\x00\x8c@%\x05\x04\x01F\x10\x01\x05\x10\x01F\x03\x01\x05\x03\x01\x10\x03\x13\x04\x13\n\x0eZ\x11\x06p\x01\x01\x01\x01\x14\f\b\x0fg\x15\x15\xb8\xff\xc0@.\x0f\x14H\x0f\x15\x01\x04\x14\x05\x02_\x11\x11\r_\xaf\n\x01\x88\n\x01L\n\x01;\n\x01\x19\n\x01\b\n\x01\n\n\x0f\x12\b_\a\x03\x0f_\x04\x00\x12\x00?2\xe1?\xe12\x129/]]]]]]\xe12/\xe1\x01/\x113]+\x10\xe622\x119/]33\xe123\x11\x1299]]]]10])\x01\x11!\x03!\x01!\x15!\x11!\x15!\x11!\x01!\x11#\x06\xe7\xfc\xb7\xfe3\x96\xfe\xc5\x02\x8f\x04X\xfd\xec\x01\xf0\xfe\x10\x02\x14\xfb[\x01\\a\x01\\\xfe\xa4\x05\xb6\xfe\xfe\xbf\xfe\xfe\x87\x01`\x02N\x00\x00\xff\xff\x00w\xfe\x14\x04\xd1\x05\xcb\x12&\x00&\x00\x00\x11\a\x00z\x01\xfc\x00\x00\x00\v\xb6\x01\x16,$\x18 %\x01+5\x00\x00\x00\xff\xff\x00\xb8\x00\x00\x04\x02\as\x12&\x00(\x00\x00\x11\a\x00C\xff\xb7\x01R\x00\x15\xb4\x01\f\x05&\x01\xb8\xff\xa8\xb4\x11\x18\x01\x00%\x01+5\x00+5\x00\xff\xff\x00\xb8\x00\x00\x04\x02\as\x12&\x00(\x00\x00\x11\a\x00v\x00\\\x01R\x00\x13@\v\x01\f\x05&\x01M\f\x13\x01\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\xb8\x00\x00\x04\v\as\x12&\x00(\x00\x00\x11\a\x00\xc3\x00\x1f\x01R\x00\x13@\v\x01\f\x05&\x01\x10\x13\x1f\x01\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\xb8\x00\x00\x04\x02\aV\x12&\x00(\x00\x00\x11\a\x00j\x00\x19\x01R\x00\x17@\r\x02\x01\x1b\x05&\x02\x01\v\f*\x01\x00%\x01+55\x00+55\x00\x00\x00\xff\xff\x00*\x00\x00\x02\xdb\as\x12&\x00,\x00\x00\x11\a\x00C\xfe\xde\x01R\x00\x15\xb4\x01\f\x05&\x01\xb8\xff\x9e\xb4\x11\x18\x01\x00%\x01+5\x00+5\x00\xff\xff\x00B\x00\x00\x02\xf1\as\x12&\x00,\x00\x00\x11\a\x00v\xff\xa1\x01R\x00\x13@\v\x01\f\x05&\x01`\f\x13\x01\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\xff\xf0\x00\x00\x03,\as\x12&\x00,\x00\x00\x11\a\x00\xc3\xff@\x01R\x00\x13@\v\x01\f\x05&\x01\x00\x13\x1f\x01\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x006\x00\x00\x02\xe4\aV\x12&\x00,\x00\x00\x11\a\x00j\xff>\x01R\x00\x19\xb6\x02\x01\x1b\x05&\x02\x01\xb8\xff\xff\xb4\f*\x01\x00%\x01+55\x00+55\x00\x00\x02\x00/\x00\x00\x05#\x05\xb6\x00\x10\x00\x1f\x00g@B\x11Z\bg!?!\x01X\x1a\x01;\x1a\x01\x1a\x18\x1cZ'\x10\x01\x10\x01\x0ed \x1b\x0f_\x18\xaf\x01\xbf\x01\xdf\x01\x03\x88\x01\x01o\x01\x01L\x01\x01;\x01\x01\x19\x01\x01\b\x01\x01\x01\x01\x02\x1c_\x0e\x12\x17_\x02\x03\x00?\xe1?\xe1\x119/]]]]]]]3\xe12\x01\x10\xf62\xc2]\xf12\xc2]]]\x10\xf6\xe110\x133\x11!2\x04\x16\x12\x15\x14\x02\x06\x04#!\x11#%4.\x02+\x01\x113\x15#\x11326/\x89\x01\xac\xa1\x01\x03\xb8ce\xbf\xfe\xeb\xb1\xfe\u007f\x89\x03\xba1]\x87W\x8f\xed\xedr\xc4\xc5\x03R\x02d\\\xb5\xfe\xf4\xb0\xb9\xfe\xe9\xbb^\x02T\x8dz\xb1t8\xfe\x9a\xfe\xfe\xac\xf0\x00\x00\xff\xff\x00\xb8\x00\x00\x05\x8b\a`\x12&\x001\x00\x00\x11\a\x00\xc5\x00\xc5\x01R\x00\x15\xb4\x01\x1b\x05&\x01\xb8\xff\xf3\xb4\x1c*\n\x00%\x01+5\x00+5\x00\xff\xff\x00w\xff\xec\x05\x96\as\x12&\x002\x00\x00\x11\a\x00C\x00T\x01R\x00\x15\xb4\x02(\x05&\x02\xb8\xff\x9c\xb4-4\n\x00%\x01+5\x00+5\x00\xff\xff\x00w\xff\xec\x05\x96\as\x12&\x002\x00\x00\x11\a\x00v\x01\x02\x01R\x00\x13@\v\x02(\x05&\x02I(/\n\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00w\xff\xec\x05\x96\as\x12&\x002\x00\x00\x11\a\x00\xc3\x00\xae\x01R\x00\x15\xb4\x02(\x05&\x02\xb8\xff\xf6\xb4/;\n\x00%\x01+5\x00+5\x00\xff\xff\x00w\xff\xec\x05\x96\a`\x12&\x002\x00\x00\x11\a\x00\xc5\x00\xba\x01R\x00\x13@\v\x02+\x05&\x02\x02,:\n\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00w\xff\xec\x05\x96\aV\x12&\x002\x00\x00\x11\a\x00j\x00\xb4\x01R\x00\x19\xb6\x03\x027\x05&\x03\x02\xb8\xff\xfd\xb4(F\n\x00%\x01+55\x00+55\x00\x00\x01\x00m\x01\f\x03\xfc\x04\x9a\x00\v\x00n@D\xa7\n\x01V\x03\x96\x03\x02\x15\x03%\x035\x03\x03\x06\x03\x01Y\t\x99\t\x02\x1a\t*\t:\t\x03\t\t\x01V\x06\x96\x06\x02\x15\x06%\x065\x06\x03\x06\x06\x01Y\x00\x99\x00\x02\x1a\x00*\x00:\x00\x03\t\x00\x01\x03\xe0\x00\x01\x00\xb8\xff\xe0@\v\x0e\x14H_\x00\u007f\x00\xaf\x00\x03\x00\x00\x19/]+]\x01/10]]]]]]\x00]]]]]]]\t\x017\t\x01\x17\t\x01\a\t\x01'\x01\x98\xfe\u0557\x01-\x011\x9a\xfe\xcf\x01-\x96\xfe\xcf\xfeӕ\x02\xd3\x01-\x9a\xfe\xd5\x01+\x96\xfe\xcf\xfeј\x01-\xfe\u0558\x00\x03\x00w\xff\xb4\x05\x96\x05\xfc\x00\x1a\x00$\x00/\x002@\x1b\x1e(\x1b%[\x00g1/1?1\x02\x1b[\rf0\x1d'+ \x12\x04+\x05\x12\x00?\xc1?\xc1\x1199\x01\x10\xf6\xe1]\x10\xf6\xe1\x119910\x01\x14\x02\x0e\x01#\"'\a'7&\x0254\x12>\x0132\x16\x177\x17\a\x16\x12\x05\x14\x17\x01&#\"\x0e\x02\x054'\x01\x1e\x0132>\x02\x05\x96O\xa2\xf7\xa8\xb3\x80H\xa8Ra]O\xa2\xf7\xa9[\x9bAF\xa6P^]\xfc /\x01\xc9EaW\x80S(\x02\xa0+\xfe9\"P0Y\u007fQ'\x02ݩ\xfe\xea\xc6l=u^\x86d\x01(\xbb\xaa\x01\x15\xc4k!\x1fo`\x81c\xfeݸ\xb4s\x02\xea+D\x80\xb7s\xabt\xfd\x1b\x13\x14D\u007f\xb7\x00\x00\x00\xff\xff\x00\xae\xff\xec\x05\f\as\x12&\x008\x00\x00\x11\a\x00C\x00=\x01R\x00\x15\xb4\x01\x18\x05&\x01\xb8\xff\xae\xb4\x1d$\v\x00%\x01+5\x00+5\x00\xff\xff\x00\xae\xff\xec\x05\f\as\x12&\x008\x00\x00\x11\a\x00v\x00\xee\x01R\x00\x13@\v\x01\x18\x05&\x01_\x18\x1f\v\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\xae\xff\xec\x05\f\as\x12&\x008\x00\x00\x11\a\x00\xc3\x00\x8d\x01R\x00\x15\xb4\x01\x18\x05&\x01\xb8\xff\xfe\xb4\x1f+\v\x00%\x01+5\x00+5\x00\xff\xff\x00\xae\xff\xec\x05\f\aV\x12&\x008\x00\x00\x11\a\x00j\x00\x91\x01R\x00\x17@\r\x02\x01'\x05&\x02\x01\x03\x186\v\x00%\x01+55\x00+55\x00\x00\x00\xff\xff\x00\x00\x00\x00\x04\xac\as\x12&\x00<\x00\x00\x11\a\x00v\x00Z\x01R\x00\x13@\v\x01\t\x05&\x01R\t\x10\a\x02%\x01+5\x00+5\x00\x00\x00\x00\x02\x00\xb8\x00\x00\x04m\x05\xb6\x00\x10\x00\x19\x00H@,\x06\x0f\x01\x15Z\x9f\x00\x01\x00g\x1b\x1f\x1b?\x1b_\x1b\x8f\x1b\xaf\x1b\x05\x11\v\aZ\bd\x1a\x19`\v\x11`\x06\v\x06\v\x06\a\t\x03\a\x12\x00??\x1299//\x10\xe1\x10\xe1\x01\x10\xf6\xe122]\x10\xf6]\xe110]\x01\x14\x0e\x02+\x01\x11!\x11!\x1532\x1e\x02\x0132654&+\x01\x04m3u\xbf\x8d\x8b\xfe\xca\x016\xa1|\xb4u9\xfd\x81Tyxjsh\x03\x02^\xac\x84O\xfe\xdb\x05\xb6\xe5By\xab\xfe\xb4izlg\x00\x01\x00\xa0\xff\xec\x05+\x06\x1f\x00C\x00o\xb5<\x18\t\rH\x12\xb8\xff\xe8\xb3\t\rH\x0e\xb8\xff\xe8@7\t\rHF1\x011G\x00\x99\a\x01H\a\x01\aF*\x00*9E \x01 G\x11WE\x80E\x01\x188F9TD\a \x144M?\x019\x15\xe8\x1d\x01\x1dO\x18\x14\x16\x00?3\xe1]??\xe1\x1299\x01\x10\xf6\xf1\xc2]\x10\xf6\xe1]\x1299\x10\xe1]]\x10\xe1]10+++\x01\x14\x0e\x04\x15\x14\x1e\x02\x17\x1e\x03\x15\x14\x06#\"&'5\x1e\x0332654.\x02'.\x0354>\x0454&#\"\x06\x15\x11!\x114>\x0232\x1e\x02\x04\xa4+?K?+\x195R91L5\x1b\xea\xe3b\x91<\x18ELP\"PX\x0e)JI>)dalk\xfe\xcfJ\x89\xc0us\xbc\x85H\x04\xd9@aL:0*\x16\x14\"(3&\x1fAM\\<\xac\xae\x1d\"\xf2\x10\x1f\x18\x0f=>\x1b**1\"$@?E(5N>45>(?Nah\xfb\x98\x04sm\xa1j4+Sz\x00\x00\x00\xff\xff\x00V\xff\xec\x03\xfe\x06!\x12&\x00D\x00\x00\x11\x06\x00C\xa3\x00\x00\x15\xb4\x02/\x11&\x02\xb8\xffǴ4;\f\x1e%\x01+5\x00+5\x00\x00\x00\xff\xff\x00V\xff\xec\x03\xfe\x06!\x12&\x00D\x00\x00\x11\x06\x00vm\x00\x00\x13@\v\x02/\x11&\x02\x91/6\f\x1e%\x01+5\x00+5\x00\xff\xff\x00V\xff\xec\x03\xfe\x06 \x12&\x00D\x00\x00\x11\x06\x00\xc3\x00\xff\x00\x13@\v\x02/\x11&\x02$6B\f\x1e%\x01+5\x00+5\x00\xff\xff\x00V\xff\xec\x03\xfe\x06\x0e\x12&\x00D\x00\x00\x11\x06\x00\xc5\xff\x00\x00\x13@\v\x022\x11&\x02$3A\f\x1e%\x01+5\x00+5\x00\xff\xff\x00V\xff\xec\x03\xfe\x06\x04\x12&\x00D\x00\x00\x11\x06\x00j\x06\x00\x00\x17@\r\x03\x02>\x11&\x03\x02+/M\f\x1e%\x01+55\x00+55\x00\xff\xff\x00V\xff\xec\x03\xfe\x06\xb2\x12&\x00D\x00\x00\x11\x06\x00\xc4\x00\x00\x00\x17@\r\x03\x024\x11&\x03\x02%9/\f\x1e%\x01+55\x00+55\x00\x00\x03\x00V\xff\xec\x06\xac\x04u\x004\x00C\x00L\x00\x94@^\n\x0e\x01\n\v\x01\x04\x1e\x01\n\x02\x01\x03\x1eG'F\x1155\r*H\x01\tH\x01HH/%WNONoN\x02;G\x19\rVM59'I'\x02('\x01'P\x11\xd9G\xe9G\x02\xc8G\x01|G\x01GG,(D\x01DO \x10\x18(\x15\x01\x15N\x1c\x10>N\b\x16,N/\x00\x16\x00?2\xe1?\xe1?\xe1]3?\xe1]\x129/]]]3\xe9]]2\x01\x10\xf62\xe9]\x10\xf62\xe9]]\x119/3\xe929910\x00]]\x01]]\x05\"&'\x0e\x03#\".\x02546?\x0154&#\"\x06\a'>\x0132\x17632\x1e\x02\x1d\x01!\x1e\x033267\x15\x0e\x03\x01\a\x0e\x03\x15\x14\x1632>\x025\x01\"\x06\a!.\x03\x04\xf4\x83\xd6E+Tb{RD{]6\xe4\xe3\xb2PHH\x89EcT\xccp\xd6m~\xbco\xb3|C\xfdV\x02%C_=^\xafX(QZh\xfd\x9de=T3\x17D7*H5\x1e\x01\xfeRk\b\x01\x85\x01\x18/H\x14ei6M3\x18+W\x85[\xb2\xa9\t\x06TEB*#\xca/6\x83\x81C\x82\xbdz\x94@gG&+-\xec\x15\x1d\x14\t\x02\x1a\x04\x02\x1c/A(F;\x1d9S6\x01\xf0rz3V?$\x00\x00\x00\xff\xff\x00f\xfe\x14\x03\xbc\x04s\x12&\x00F\x00\x00\x11\a\x00z\x01h\x00\x00\x00\x10@\n\x01P(\x01\x15( \x05\r%\x01+]5\x00\x00\xff\xff\x00f\xff\xec\x04D\x06!\x12&\x00H\x00\x00\x11\x06\x00C\xbd\x00\x00\x15\xb4\x02*\x11&\x02\xb8\xff\xb6\xb4/6\x0e\x18%\x01+5\x00+5\x00\x00\x00\xff\xff\x00f\xff\xec\x04D\x06!\x12&\x00H\x00\x00\x11\x06\x00vs\x00\x00\x13@\v\x02*\x11&\x02l*1\x0e\x18%\x01+5\x00+5\x00\xff\xff\x00f\xff\xec\x04D\x06!\x12&\x00H\x00\x00\x11\x06\x00\xc3\x12\x00\x00\x13@\v\x02*\x11&\x02\v1=\x0e\x18%\x01+5\x00+5\x00\xff\xff\x00f\xff\xec\x04D\x06\x04\x12&\x00H\x00\x00\x11\x06\x00j\x12\x00\x00\x17@\r\x03\x029\x11&\x03\x02\f*H\x0e\x18%\x01+55\x00+55\x00\xff\xff\xff\xd4\x00\x00\x01\xd8\x06!\x12&\x00\xc2\x00\x00\x11\a\x00C\xfe\x88\x00\x00\x00\x15\xb4\x01\x04\x11&\x01\xb8\xff\x9e\xb4\t\x10\x01\x00%\x01+5\x00+5\x00\xff\xff\x00\x91\x00\x00\x02\x95\x06!\x12&\x00\xc2\x00\x00\x11\a\x00v\xffE\x00\x00\x00\x13@\v\x01\x04\x11&\x01Z\x04\v\x01\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\xff\x98\x00\x00\x02\xd4\x06!\x12&\x00\xc2\x00\x00\x11\a\x00\xc3\xfe\xe8\x00\x00\x00\x15\xb4\x01\x04\x11&\x01\xb8\xff\xfe\xb4\v\x17\x01\x00%\x01+5\x00+5\x00\xff\xff\xff\xe0\x00\x00\x02\x8e\x06\x04\x12&\x00\xc2\x00\x00\x11\a\x00j\xfe\xe8\x00\x00\x00\x19\xb6\x02\x01\x13\x11&\x02\x01\xb8\xff\xff\xb4\x04\"\x01\x00%\x01+55\x00+55\x00\x00\x02\x00J\xff\xec\x04H\x06#\x00'\x007\x00v@Q\v$\x01\x05\x11\x01\n\x17\x01\n\x1b\x01(G\xcf\x0f\x01\x0fW9\xcb9\x01/9_9\u007f9\x9f9\x040G\xc4\x19\x01\x19V8\xcf8\x01!\b-\x01-N\u007f\x1e\x8f\x1e\x9f\x1e\x03\x1e\x1e\x04\a5\x015N\x14\x16\b_\x04\x01K\x04\x01/\x04\x01\x1b\x04\x01\x04\x01\x00?]]]]3?\xe9]\x119/]\xe9]2\x01]\x10\xf6]\xe9]]\x10\xf6]\xe910]]]]\x01.\x01'7\x1e\x01\x177\x17\a\x1e\x03\x15\x14\x0e\x02#\".\x0254>\x0232\x16\x177.\x01'\a'\x014.\x02#\"\x06\x15\x14\x1e\x02326\x01\xc9\"N*`I\x809\xe2d\xaeHlG$H\x86\xbevo\xba\x87LAu\xa3a`\x88 \x15\x1ceB\xe7e\x01\xfe\x171K3l]\x171L5j\\\x05\x1d\x150\x17\xaa\"E&\x8b\x9ajE\x9c\xb6\xd0y\x8eܘOD\x82\xbdzz\xbc\x81C?1\x02X\x9b8\x90\x9c\xfdh/VB'\x8d\x8e?hJ)\xa3\xff\xff\x00\xa0\x00\x00\x04j\x06\x0e\x12&\x00Q\x00\x00\x11\x06\x00\xc53\x00\x00\x15\xb4\x01\x1e\x11&\x01\xb8\xff\xfd\xb4\x1f-\f\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00f\xff\xec\x04d\x06!\x12&\x00R\x00\x00\x11\x06\x00C\xc4\x00\x00\x15\xb4\x02 \x11&\x02\xb8\xff\xad\xb4%,\x16\f%\x01+5\x00+5\x00\x00\x00\xff\xff\x00f\xff\xec\x04d\x06!\x12&\x00R\x00\x00\x11\x06\x00vf\x00\x00\x13@\v\x02 \x11&\x02O '\x16\f%\x01+5\x00+5\x00\xff\xff\x00f\xff\xec\x04d\x06!\x12&\x00R\x00\x00\x11\x06\x00\xc3\x14\x00\x00\x15\xb4\x02 \x11&\x02\xb8\xff\xfd\xb4'3\x16\f%\x01+5\x00+5\x00\x00\x00\xff\xff\x00f\xff\xec\x04d\x06\x0e\x12&\x00R\x00\x00\x11\x06\x00\xc5\x12\x00\x00\x15\xb4\x02#\x11&\x02\xb8\xff\xfc\xb4$2\x16\f%\x01+5\x00+5\x00\x00\x00\xff\xff\x00f\xff\xec\x04d\x06\x04\x12&\x00R\x00\x00\x11\x06\x00j\x12\x00\x00\x19\xb6\x03\x02/\x11&\x03\x02\xb8\xff\xfc\xb4 >\x16\f%\x01+55\x00+55\x00\x00\x00\x00\x03\x00X\x00\xdd\x04\x10\x04\xc7\x00\x03\x00\x17\x00+\x00`@7\x0e\xa0\"\x01\"\x04\xc4\x18\xd4\x18\x02\x18\x18\x01=\x02M\x02}\x02\x8d\x02\x04\v\x02+\x02\x02\x02-\v\x01\x01\x01'\xad\xe0\x1d\x01\x0f\x1d?\x1d_\x1d\xaf\x1d\x04\x1d\t\xad\x90\x13\x01\x13\xb8\xff\xc0\xb6\v\x0fH\x13\x00\xad\x01\x00/\xe9/+]\xe9/]]\xe9\x01/]\x10\xce]]\x119/]3\xcd]210\x135!\x15\x054>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x114>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02X\x03\xb8\xfd\x98\x16&3\x1c\x1c2&\x17\x17&2\x1c\x1c3&\x16\x16&3\x1c\x1c2&\x17\x17&2\x1c\x1c3&\x16\x02d\xdb\xdb\xef*:#\x10\x10#:*(:%\x11\x11%:\x02\xe2*:$\x10\x10$:*(9%\x11\x11%9\x00\x03\x00f\xff\xb4\x04d\x04\x91\x00\x1b\x00#\x00,\x00\xaa@S\xe7'\x01\xd6'\x01w'\x97'\x02#'3'\x02\x14'\x01\xf8&\x01\xe9\x1f\x01\xb8\x1f\x01,\x1f<\x1f\x02\x1a\x1f\x01\n\x10\x01\x05\x02\x01%\x16\x01\xdf\t\x01'\x1f\x1c\b$\x18$8$H$\x04$G\x00W._.\x01\a\x1c\x17\x1c7\x1cG\x1c\x04\x1cG\x0eV-\x1e\xb8\xff\xe0@%\t\x0eH& \t\x0eH\x1e&*\b!\x18!8!H!\x04!M\x13\x10\a*\x17*7*G*\x04*M\x05\x16\x00?\xe9]?\xe9]\x1199++\x01\x10\xf6\xe9]]\x10\xf6\xe9]\x119910\x00]]\x01]]]]]]]]]]]]\x01\x14\x0e\x02#\"&'\a'7.\x0154>\x0232\x16\x177\x17\a\x1e\x01\x05\x14\x17\x01&#\"\x06\x054'\x01\x1e\x01326\x04dG\x85\xbfw9h09\xa2DEOG\x85\xbex>s31\xa0>?F\xfd:\f\x01\x1b(9i]\x01\x8f\x06\xfe\xf4\x11%\x15i^\x021\x8cؔM\x14\x12^ZoJۏ\x8bؓL\x1a\x17O`bHхVA\x01\xca\x19\xa5\xa7@1\xfeN\b\a\xaa\xff\xff\x00\x9a\xff\xec\x04d\x06!\x12&\x00X\x00\x00\x11\x06\x00C\xc2\x00\x00\x15\xb4\x01\x1b\x11&\x01\xb8\xff\x91\xb4 '\f\x19%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\x9a\xff\xec\x04d\x06!\x12&\x00X\x00\x00\x11\a\x00v\x00\x81\x00\x00\x00\x13@\v\x01\x1b\x11&\x01P\x1b\"\f\x19%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\x9a\xff\xec\x04d\x06!\x12&\x00X\x00\x00\x11\x06\x00\xc3)\x00\x00\x15\xb4\x01\x1b\x11&\x01\xb8\xff\xf8\xb4\".\f\x19%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\x9a\xff\xec\x04d\x06\x04\x12&\x00X\x00\x00\x11\x06\x00j/\x00\x00\x19\xb6\x02\x01*\x11&\x02\x01\xb8\xff\xff\xb4\x1b9\f\x19%\x01+55\x00+55\x00\x00\x00\xff\xff\x00\x00\xfe\x14\x04P\x06!\x12&\x00\\\x00\x00\x11\x06\x00v=\x00\x00\x13@\v\x01\x1f\x11&\x01c\x1f&\x00\r%\x01+5\x00+5\x00\x00\x02\x00\xa0\xfe\x14\x04w\x06\x14\x00$\x005\x008@ \x05\f\x01\x05\b\x013G\nW7+\x1f\x1bF\x1cT6\x1d\x00\x1b\x1b\x140M\x0f\x16\x00%M\x05\x10\x00?\xe12?\xe12??\x01\x10\xf6\xe122\x10\xf6\xe110]]\x01>\x0332\x1e\x02\x15\x14\x0e\x02#\".\x02'#\x16\x17\x1e\x01\x15\x11!\x11!\x11\x14\x06\a\x06\a\x17\"\x0e\x02\a\x15\x14\x1e\x0232654&\x01\xd1\x147HY7V\x8ef98f\x8eW7ZG6\x15\x0e\x03\x04\x03\x04\xfe\xcf\x011\x04\x03\x04\x03\xca3G,\x14\x02\x13,I6[UU\x03\xcd#<-\x1aJ\x92؎\x8fٓJ\x15&2\x1c \x1e\x1a4\x10\xfe;\b\x00\xfey\x18B\x1e#%N%JqK!Q~U,\xad\xa5\xa5\xa5\x00\x00\x00\xff\xff\x00\x00\xfe\x14\x04P\x06\x04\x12&\x00\\\x00\x00\x11\x06\x00j\xda\x00\x00\x17@\r\x02\x01.\x11&\x02\x01\x01\x1f=\x00\r%\x01+55\x00+55\x00\x00\x01\x00\xa0\x00\x00\x01\xd1\x04^\x00\x03\x00 @\x13\xbf\x05\x010\x05`\x05\x90\x05\x03\x00F\x01T\x04\x02\x0f\x01\x15\x00??\x01\x10\xf6\xe9]]10)\x01\x11!\x01\xd1\xfe\xcf\x011\x04^\x00\x00\x00\x01\x00\xb0\x04\xd9\x03\xec\x06!\x00\x14\x006@!\x00\x00\x14\xf0\x14\x02\x14\x14\x06\a\xfb\x03\x01)\x03\x01\x03\x0f\r_\ro\r\x03\r\r\a\x0f\x00_\x00\x02\x00\x00/]22/]3]]\x01/33/]310\x01.\x01'\x0e\x01\a#5>\x037!\x1e\x03\x17\x15\x03!3n46h3\xcb\x1a?A<\x16\x01d\x15\x0232\x1e\x02\a4&#\"\x06\x15\x14\x16326\x03J'E\\67\\A$$A\\75\\E(\x9e6**600*6\x05\xc78Y>!!=Y77X=!!=W8-33--44\x00\x00\x01\x00\xc5\x04\xd7\x03\xd9\x06\x0e\x00\x1b\x00H@0\x11\x90\x12\x01o\x12\x01\x12\x03\x00\x04p\x04\x02\x04\x12H\x00\x01\x00\x8e\x0f\t_\to\t\u007f\t\xdf\t\xef\t\x06\t\tG\x0e\x01\x0e\x8e\x04\x0f\x17_\x17\x02\x17\x00/]3\xe1]3/]\xe1]3\x01/]3/]]310\x01\"\x06\a#>\x0332\x1e\x0232673\x0e\x03#\".\x02\x01\xaa\x1f%\f\x95\x06(AX5)NMK$\x1f$\r\x95\x06)BW4(PLK\x05B56PtL% '!46OtM%!'!\x00\x00\x00\x00\x01\x00R\x01\xb4\x03\xae\x02\x9a\x00\x03\x00\x11\xb6\x02\x05\x00\x00\xba\x01\xbd\x00?\xe9\x01/\x10\xce10\x135!\x15R\x03\\\x01\xb4\xe6\xe6\x00\x00\x00\x01\x00R\x01\xb4\a\xae\x02\x9a\x00\x03\x00\x11\xb6\x02\x05\x00\x00\xba\x01\xbd\x00?\xe9\x01/\x10\xce10\x135!\x15R\a\\\x01\xb4\xe6\xe6\x00\x00\x00\x01\x00\x17\x03\xc1\x01\xa2\x05\xb6\x00\f\x00E@2\x0f\x0e\x1f\x0e/\x0e\x03)\x069\x06I\x06\xb9\x06\x04\x06\a\x97\xa4\f\xb4\f\x02v\f\x86\f\x96\f\x033\fC\f\x02%\f\x01\x06\f\x16\f\x02\f\x01\f\x9c\x06\x03\x00?\xed\x01/3]]]]]\xed2]]10\x13'>\x0373\x0e\x03\a%\x0e\x0e'.4\x19\xdb\x0f\x1d\x1b\x16\b\x03\xc1\x166z|{8=\x84\x83|5\x00\x00\x00\x01\x00\x17\x03\xc1\x01\xa2\x05\xb6\x00\f\x00C@1\x0f\x0e\x1f\x0e/\x0e\x03\xab\f\xbb\f\x02y\f\x89\f\x99\f\x03<\fL\f\x02\n\f\x1a\f*\f\x03\f\x01\x97&\x066\x06F\x06\xb6\x06\x04\x06\a\x06\x9c\f\x03\x00?\xed\x01/3]\xed2]]]]]10\x01\x17\x0e\x03\a#>\x037\x01\x93\x0f\x0e'/3\x19\xdb\x0e\x1d\x1b\x16\b\x05\xb6\x167y}z8<\x84\x84|5\x00\x00\x00\x00\x01\x00?\xfe\xf8\x01\xcb\x00\xee\x00\f\x00V@A\xa0\x0e\xb0\x0e\xe0\x0e\xf0\x0e\x04/\x0e?\x0e\x02\xab\v\xbb\v\x02y\v\x89\v\x99\v\x03<\vL\v\x02\n\v\x1a\v*\v\x03\v\x00\x97&\x056\x05F\x05\x03\x05/\x06?\x06O\x06\x03\x06\x05\x9c\v@\t\fH\v\x00/+\xed\x01/]3]\xed2]]]]]]10%\x0e\x03\a#>\x037!\x01\xcb\x0e'/3\x19\xdc\x0f\x1d\x1b\x16\b\x01\x18\xd76z|{8=\x84\x83}5\x00\x00\x02\x00\x17\x03\xc1\x03u\x05\xb6\x00\f\x00\x19\x00x@Z\x0f\x1b\x1f\x1b\x02)\x129\x12I\x12\xb9\x12\x04\x12\x13\x97\xa4\x18\xb4\x18\x023\x18C\x18\x02v\x18\x86\x18\x96\x18\x03\x05\x18\x15\x18%\x18\x03\x18\r)\x059\x05I\x05\xb9\x05\x04\x05\x06\x97\xa4\v\xb4\v\x02v\v\x86\v\x96\v\x033\vC\v\x02\x05\v\x15\v%\v\x03\v\xa0\x00\x01\x00\x18\v\x9c\x12\x05\x03\x00?3\xed2\x01/]3]]]]\xed2]/3]]]]\xed2]]10\x01>\x0373\x0e\x03\a!%>\x0373\x0e\x03\a!\x01\xe9\x0e(.4\x19\xdb\x0f\x1d\x1b\x16\b\xfe\xe8\xfe\x1f\x0e'.4\x19\xdb\x0f\x1d\x1b\x16\b\xfe\xe8\x03\xd76z|{8=\x84\x83|5\x166z|{8=\x84\x83|5\x00\x00\x02\x00\x17\x03\xc1\x03u\x05\xb6\x00\f\x00\x19\x00x@Z\x0f\x1b\x1f\x1b\x02\xab\x18\xbb\x18\x02<\x18L\x18\x02y\x18\x89\x18\x99\x18\x03\n\x18\x1a\x18*\x18\x03\x18\r\x97&\x126\x12F\x12\xb6\x12\x04\x12\xa0\x13\x01\x13\xab\v\xbb\v\x02y\v\x89\v\x99\v\x03<\vL\v\x02\n\v\x1a\v*\v\x03\v\x00\x97&\x056\x05F\x05\xb6\x05\x04\x05\x06\x12\x05\x9c\x18\v\x03\x00?3\xed2\x01/3]\xed2]]]]/]3]\xed2]]]]]10\x01\x0e\x03\a#>\x037!\x05\x0e\x03\a#>\x037!\x01\xa2\x0e'/3\x19\xdb\x0e\x1d\x1b\x16\b\x01\x18\x01\xe2\x0e'/3\x19\xdc\x0f\x1d\x1b\x16\b\x01\x18\x05\xa07y}z8<\x84\x84|5\x167y}z8<\x84\x84|5\x00\x00\x02\x00?\xfe\xf8\x03\x9e\x00\xee\x00\f\x00\x19\x00\xa7@\x83\xa0\x1b\xb0\x1b\xc0\x1b\xe0\x1b\xf0\x1b\x05_\x1bo\x1b\u007f\x1b\x03 \x1b0\x1b\x02\xab\x18\xbb\x18\x02y\x18\x89\x18\x99\x18\x03<\x18L\x18\x02\n\x18\x1a\x18*\x18\x03\x18\r\x97&\x126\x12F\x12\xb6\x12\x04\x12O\x13o\x13\u007f\x13\x8f\x13\x04\x10\x13\x01\x13\xab\v\xbb\v\x02y\v\x89\v\x99\v\x03<\vL\v\x02\n\v\x1a\v*\v\x03\v\x00\x97&\x056\x05F\x05\xb6\x05\x04\x05\xc0\x06\x01O\x06_\x06o\x06\x03\x06\x12\x05\x9c\x18\v@\t\fH\v\x00/+3\xed2\x01/]]3]\xed2]]]]/]]3]\xed2]]]]]]]10%\x0e\x03\a#>\x037!\x05\x0e\x03\a#>\x037!\x01\xcb\x0e'/3\x19\xdc\x0f\x1d\x1b\x16\b\x01\x18\x01\xe2\x0e'/3\x19\xdc\x0f\x1d\x1b\x16\b\x01\x18\xd76z|{8=\x84\x83}5\x176z|{8=\x84\x83}5\x00\x00\x00\x00\x01\x00b\x01\xae\x02\xa0\x04)\x00\x13\x00G@-`\x15p\x15\x90\x15\xe0\x15\x04\x0f\x15\x1f\x15/\x15O\x15_\x15\x05\x80\n\x01\n`\x00p\x00\x90\x00\x03\x1f\x00\x01\x000\x05\xc0\x05\xd0\x05\xe0\x05\x04\x05\xb8\xff\xc0\xb4\x0f\x13H\x05\x0f\x00/\xcd+]\x01/]]\xcd]]]10\x134>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02b,Mi=;iN--Ni;=iM,\x02\xecVxL##LxVUxM$$Mx\x00\x00\x01\x00R\x00^\x02b\x04\x04\x00\x06\x00+@\x16\x03\xeb\x06\xec\x05\x04\x04\x01\x19\x02\x01\x02\b\x04\x05\xed\x02\x01\xed\x00\x03\xef\x00?3\xed2\xed2\x01\x10\xde]22\x113\xfd\xe910\x13\x01\x17\x03\x13\a\x01R\x015\xdb\xd9\xd9\xdb\xfe\xcb\x02=\x01\xc7w\xfe\xa4\xfe\xa4w\x01\xc5\x00\x00\x00\x00\x01\x00R\x00^\x02b\x04\x04\x00\x06\x00+@\x16\x05\x04\x04\x01\x02\xec\x03\xeb\x16\x06\x01\x06\b\x04\x05\xed\x02\x01\xed\x06\x03\xef\x00?3\xed2\xed2\x01\x10\xde]\xe9\xed22\x11310\t\x01'\x13\x037\x01\x02b\xfe\xcb\xdb\xd9\xd9\xdb\x015\x02#\xfe;w\x01\\\x01\\w\xfe9\x00\x00\x00\x01\xfew\x00\x00\x02\x91\x05\xb6\x00\x03\x00\x1f\xb7\x03\x00\x10\x00\x00\x05\x01\x02\xb8\xff\xf0\xb3\x02\x01\x03\x03\x00?/\x01/83\x113/8210\t\x01#\x01\x02\x91\xfc\xd5\xef\x03+\x05\xb6\xfaJ\x05\xb6\x00\x00\x00\x02\x00\f\x02J\x02\xf6\x05\xbc\x00\n\x00\x15\x00\\@<5\x15E\x15\x02\x0f\x17\x1f\x17/\x17_\x17\xdf\x17\xef\x17\xff\x17\a\t\x006\x02F\x02\x02\x02\xe0\v\a2\x03B\x03\x02\x03\x03\x16\x17\x15\x05@\x14\x18H\x05\x01\x05\xe5\t\x06\x15\x15\x03\x0f\a\xdc\x03\xdd\x00??3\x129/33\xe92\x01/+2\x11\x129/]33\xe9]22]10]\x01#\x15#5!5\x013\x113!5467\x0e\x03\x0f\x01\x02\xf6}\xee\xfe\x81\x01\x81\xec}\xfe\x95\x03\x03\x05\x13\x16\x16\t\u007f\x02ᗗ\x9a\x02A\xfdͤ*]1\r+-*\x0e\xbf\x00\x00\x01\x00\x00\x00\xd3\x00g\x00\x05\x00U\x00\x04\x00\x02\x00\x10\x00/\x00Z\x00\x00\x01\xcd\x01&\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00X\x00\x86\x01$\x01\xf2\x02\xa2\x03^\x03\x86\x03\xb6\x03\xe4\x04\x1a\x04B\x04\x86\x04\xa0\x04\xd4\x04\xf8\x05>\x05p\x05\xbc\x06:\x06\x82\x06\xf2\al\a\x96\bR\b\xca\t\x1c\t\x84\t\xca\t\xf8\n>\n\xae\vf\v\xda\f\\\f\xb6\f\xf4\r0\rr\r\xd2\x0e\x10\x0eF\x0e\x82\x0e\xda\x0e\xfe\x0fz\x0f\xe8\x10<\x10\x82\x10\xf8\x11l\x11\xe2\x12\x14\x12T\x12\xa2\x13\x88\x13\xe6\x148\x14\x8a\x14\xae\x14\xd2\x14\xf4\x15(\x15@\x15p\x15\xe6\x16J\x16\x98\x16\xfc\x17n\x17\xbc\x18\x9a\x18\xe4\x19$\x19\x82\x19\xdc\x19\xfa\x1ap\x1a\xb8\x1b\x04\x1bh\x1b\xcc\x1c\x18\x1c\x98\x1c\xf0\x1d8\x1d\xa2\x1e\x80\x1f\x1e\x1f\xa6\x1f\xfc ` z \xe0!F!F!\xa4\"\x12\"\x98#6#\xb4#\xda$\xb4$\xfe%\xa4&*&|&\x98&\xa0'L'b'\xaa'\xec(F(\xd6)\x06)T)\x90)\xc4*\x14*P*\xae+\x02+(+R+t+\xea,\x02,\x1a,2,J,d,\x80,\xee-\x02-\x1a-2-J-d-|-\x94-\xac-\xc6...F.^.v.\x8e.\xa6.\xc0/\x18/\x82/\x9a/\xb2/\xca/\xe4/\xfc0J0\xde0\xf61\f1\"181P1h2 262N2d2z2\x922\xaa2\xc22\xda2\xf43\x823\x9a3\xb23\xc83\xe03\xf84\x124\x825 585P5h5\x825\x986\x046\x1c6:6z6\xf67F7\\7r7\xae7\xea8.8\x968\xfe9~9\xc29\xee:\x1a:::\x8e\x00\x01\x00\x00\x00\x01\x00\x00W6\x82\xd7_\x0f<\xf5\x00\x1f\b\x00\x00\x00\x00\x00\xc8\x17O\xfa\x00\x00\x00\x00\xc8\\\x86Y\xfew\xfe\x14\a\xae\as\x00\x01\x00\b\x00\x02\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x14\x00\x00\x02J\x00u\x03\xc7\x00\x85\x05+\x00-\x04h\x00b\a\f\x00?\x05\xc7\x00R\x02!\x00\x85\x02\xb6\x00R\x02\xb6\x00=\x04\\\x00?\x04h\x00X\x02R\x00?\x02\x93\x00=\x02H\x00u\x03N\x00\x0e\x04h\x00?\x04h\x00\\\x04h\x00N\x04h\x009\x04h\x00\x04\x04h\x00V\x04h\x00L\x04h\x007\x04h\x00H\x04h\x00?\x02H\x00u\x02R\x00?\x04h\x00X\x04h\x00X\x04h\x00X\x03\xac\x00\x19\x06\xee\x00f\x053\x00\x00\x05#\x00\xb8\x05\x19\x00w\x05\x9a\x00\xb8\x04{\x00\xb8\x04d\x00\xb8\x05\xcb\x00w\x05\xcd\x00\xb8\x03\x1d\x00B\x02\xa6\xff9\x05\x12\x00\xb8\x04H\x00\xb8\aN\x00\xb8\x06D\x00\xb8\x06\f\x00w\x04\xc9\x00\xb8\x06\f\x00w\x05\n\x00\xb8\x041\x00^\x04d\x00)\x05\xba\x00\xae\x04\xe1\x00\x00\aj\x00\x00\x05\x04\x00\x00\x04\xac\x00\x00\x04P\x001\x02\xa6\x00\x8f\x03N\x00\f\x02\xa6\x003\x04B\x00\b\x03J\xff\xfc\x04\x9e\x01L\x04\x98\x00V\x04\xdd\x00\xa0\x03\xfe\x00f\x04\xdd\x00f\x04\xa6\x00f\x03\x19\x00)\x04j\x00\x14\x05\x04\x00\xa0\x02q\x00\x93\x02q\xff\xae\x04\xb8\x00\xa0\x02q\x00\xa0\a\x89\x00\xa0\x05\x04\x00\xa0\x04\xcb\x00f\x04\xdd\x00\xa0\x04\xdd\x00f\x03y\x00\xa0\x03\xd9\x00b\x03P\x00/\x05\x04\x00\x9a\x04P\x00\x00\x06s\x00\x00\x04b\x00\n\x04P\x00\x00\x03\xa8\x007\x02\xe9\x00\x1f\x04h\x01\xc7\x02\xe9\x00\x1f\x04h\x00X\x02\x14\x00\x00\x02J\x00u\x04h\x00\x8f\x04h\x00R\x04h\x00\\\x04h\x00\b\x04h\x01\xc7\x03\xe3\x00j\x04\x9e\x00\xf8\x06\xa8\x00d\x02\xe7\x00/\x04\xae\x00R\x04h\x00X\x02\x93\x00=\x06\xa8\x00d\x04\x00\xff\xfa\x03m\x00\\\x04h\x00X\x03\b\x00/\x03\b\x00;\x04\x9e\x01L\x05\n\x00\xa0\x05=\x00q\x02H\x00u\x01\xa4\xff\xdb\x03\b\x00\\\x02\xf2\x009\x04\xae\x00T\a\f\x00.\a\f\x00.\a\f\x00Z\x03\xac\x00B\x053\x00\x00\x053\x00\x00\x053\x00\x00\x053\x00\x00\x053\x00\x00\x053\x00\x00\a`\x00\x00\x05\x19\x00w\x04{\x00\xb8\x04{\x00\xb8\x04{\x00\xb8\x04{\x00\xb8\x03\x1d\x00*\x03\x1d\x00B\x03\x1d\xff\xf0\x03\x1d\x006\x05\x9a\x00/\x06D\x00\xb8\x06\f\x00w\x06\f\x00w\x06\f\x00w\x06\f\x00w\x06\f\x00w\x04h\x00m\x06\f\x00w\x05\xba\x00\xae\x05\xba\x00\xae\x05\xba\x00\xae\x05\xba\x00\xae\x04\xac\x00\x00\x04\xc9\x00\xb8\x05s\x00\xa0\x04\x98\x00V\x04\x98\x00V\x04\x98\x00V\x04\x98\x00V\x04\x98\x00V\x04\x98\x00V\a\x0e\x00V\x03\xfe\x00f\x04\xa6\x00f\x04\xa6\x00f\x04\xa6\x00f\x04\xa6\x00f\x02q\xff\xd4\x02q\x00\x91\x02q\xff\x98\x02q\xff\xe0\x04\x9e\x00J\x05\x04\x00\xa0\x04\xcb\x00f\x04\xcb\x00f\x04\xcb\x00f\x04\xcb\x00f\x04\xcb\x00f\x04h\x00X\x04\xcb\x00f\x05\x04\x00\x9a\x05\x04\x00\x9a\x05\x04\x00\x9a\x05\x04\x00\x9a\x04P\x00\x00\x04\xdd\x00\xa0\x04P\x00\x00\x02q\x00\xa0\x04\x9e\x00\xb0\x04\x9e\x01T\x04\x9e\x00\xc5\x04\x00\x00R\b\x00\x00R\x01\xb8\x00\x17\x01\xb8\x00\x17\x02R\x00?\x03\x8b\x00\x17\x03\x8b\x00\x17\x04%\x00?\x03\x02\x00b\x02\xb4\x00R\x02\xb4\x00R\x01\n\xfew\x03\b\x00\f\x00\x01\x00\x00\as\xfe\x14\x00\x00\b\x00\xfew\xfey\a\xae\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\x00\x03\x04c\x02\xbc\x00\x05\x00\b\x05\x9a\x053\x00\x00\x01\x1e\x05\x9a\x053\x00\x00\x03\xd0\x00f\x01\xfc\x00\x00\x02\v\b\x06\x03\b\x04\x02\x02\x04\xe0\x00\x02\xef@\x00 [\x00\x00\x00(\x00\x00\x00\x001ASC\x00 \x00 D\x06\x1f\xfe\x14\x00\x84\as\x01\xec \x00\x01\x9f\x00\x00\x00\x00\x04^\x05\xb6\x00\x00\x00 \x00\x02\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x14\x00\x03\x00\x01\x00\x00\x00\x14\x00\x04\x00x\x00\x00\x00\x1a\x00\x10\x00\x03\x00\n\x00~\x00\xff\x011\x02\xc6\x02\xda\x02\xdc \x14 \x1a \x1e \" : D\xff\xff\x00\x00\x00 \x00\xa0\x011\x02\xc6\x02\xda\x02\xdc \x13 \x18 \x1c \" 9 D\xff\xff\xff\xe3\xff\xc2\xff\x91\xfd\xfd\xfd\xea\xfd\xe9\xe0\xb3\xe0\xb0\xe0\xaf\xe0\xac\xe0\x96\xe0\x8d\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@EYXUTSRQPONMLKJIHGFEDCBA@?>=<;:9876510/.-,('&%$#\"!\x1f\x18\x14\x11\x10\x0f\x0e\r\v\n\t\b\a\x06\x05\x04\x03\x02\x01\x00,E#F` \xb0&`\xb0\x04&#HH-,E#F#a \xb0&a\xb0\x04&#HH-,E#F`\xb0 a \xb0F`\xb0\x04&#HH-,E#F#a\xb0 ` \xb0&a\xb0 a\xb0\x04&#HH-,E#F`\xb0@a \xb0f`\xb0\x04&#HH-,E#F#a\xb0@` \xb0&a\xb0@a\xb0\x04&#HH-,\x01\x10 <\x00<-, E# \xb0\xcdD# \xb8\x01ZQX# \xb0\x8dD#Y \xb0\xedQX# \xb0MD#Y \xb0\x04&QX# \xb0\rD#Y!!-, E\x18hD \xb0\x01` E\xb0Fvh\x8aE`D-,\x01\xb1\v\nC#Ce\n-,\x00\xb1\n\vC#C\v-,\x00\xb0(#p\xb1\x01(>\x01\xb0(#p\xb1\x02(E:\xb1\x02\x00\b\r-, E\xb0\x03%Ead\xb0PQXED\x1b!!Y-,I\xb0\x0e#D-, E\xb0\x00C`D-,\x01\xb0\x06C\xb0\aCe\n-, i\xb0@a\xb0\x00\x8b \xb1,\xc0\x8a\x8c\xb8\x10\x00b`+\fd#da\\X\xb0\x03aY-,\x8a\x03E\x8a\x8a\x87\xb0\x11+\xb0)#D\xb0)z\xe4\x18-,Ee\xb0,#DE\xb0+#D-,KRXED\x1b!!Y-,KQXED\x1b!!Y-,\x01\xb0\x05%\x10# \x8a\xf5\x00\xb0\x01`#\xed\xec-,\x01\xb0\x05%\x10# \x8a\xf5\x00\xb0\x01a#\xed\xec-,\x01\xb0\x06%\x10\xf5\x00\xed\xec-,F#F`\x8a\x8aF# F\x8a`\x8aa\xb8\xff\x80b# \x10#\x8a\xb1\f\f\x8apE` \xb0\x00PX\xb0\x01a\xb8\xff\xba\x8b\x1b\xb0F\x8cY\xb0\x10`h\x01:-, E\xb0\x03%FRK\xb0\x13Q[X\xb0\x02%F ha\xb0\x03%\xb0\x03%?#!8\x1b!\x11Y-, E\xb0\x03%FPX\xb0\x02%F ha\xb0\x03%\xb0\x03%?#!8\x1b!\x11Y-,\x00\xb0\aC\xb0\x06C\v-,!!\fd#d\x8b\xb8@\x00b-,!\xb0\x80QX\fd#d\x8b\xb8 \x00b\x1b\xb2\x00@/+Y\xb0\x02`-,!\xb0\xc0QX\fd#d\x8b\xb8\x15Ub\x1b\xb2\x00\x80/+Y\xb0\x02`-,\fd#d\x8b\xb8@\x00b`#!-,KSX\x8a\xb0\x04%Id#Ei\xb0@\x8ba\xb0\x80b\xb0 aj\xb0\x0e#D#\x10\xb0\x0e\xf6\x1b!#\x8a\x12\x11 9/Y-,KSX \xb0\x03%Idi \xb0\x05&\xb0\x06%Id#a\xb0\x80b\xb0 aj\xb0\x0e#D\xb0\x04&\x10\xb0\x0e\xf6\x8a\x10\xb0\x0e#D\xb0\x0e\xf6\xb0\x0e#D\xb0\x0e\xed\x1b\x8a\xb0\x04&\x11\x12 9# 9//Y-,E#E`#E`#E`#vh\x18\xb0\x80b -,\xb0H+-, E\xb0\x00TX\xb0@D E\xb0@aD\x1b!!Y-,E\xb10/E#Ea`\xb0\x01`iD-,KQX\xb0/#p\xb0\x14#B\x1b!!Y-,KQX \xb0\x03%EiSXD\x1b!!Y\x1b!!Y-,E\xb0\x14C\xb0\x00`c\xb0\x01`iD-,\xb0/ED-,E# E\x8a`D-,E#E`D-,K#QX\xb9\x003\xff\xe0\xb14 \x1b\xb33\x004\x00YDD-,\xb0\x16CX\xb0\x03&E\x8aXdf\xb0\x1f`\x1bd\xb0 `f X\x1b!\xb0@Y\xb0\x01aY#XeY\xb0)#D#\x10\xb0)\xe0\x1b!!!!!Y-,\xb0\x02CTXKS#KQZX8\x1b!!Y\x1b!!!!Y-,\xb0\x16CX\xb0\x04%Ed\xb0 `f X\x1b!\xb0@Y\xb0\x01a#X\x1beY\xb0)#D\xb0\x05%\xb0\b%\b X\x02\x1b\x03Y\xb0\x04%\x10\xb0\x05% F\xb0\x04%#B<\xb0\x04%\xb0\a%\b\xb0\a%\x10\xb0\x06% F\xb0\x04%\xb0\x01`#B< X\x01\x1b\x00Y\xb0\x04%\x10\xb0\x05%\xb0)\xe0\xb0) EeD\xb0\a%\x10\xb0\x06%\xb0)\xe0\xb0\x05%\xb0\b%\b X\x02\x1b\x03Y\xb0\x05%\xb0\x03%CH\xb0\x04%\xb0\a%\b\xb0\x06%\xb0\x03%\xb0\x01`CH\x1b!Y!!!!!!!-,\x02\xb0\x04% F\xb0\x04%#B\xb0\x05%\b\xb0\x03%EH!!!!-,\x02\xb0\x03% \xb0\x04%\b\xb0\x02%CH!!!-,E# E\x18 \xb0\x00P X#e#Y#h \xb0@PX!\xb0@Y#XeY\x8a`D-,KS#KQZX E\x8a`D\x1b!!Y-,KTX E\x8a`D\x1b!!Y-,KS#KQZX8\x1b!!Y-,\xb0\x00!KTX8\x1b!!Y-,\xb0\x02CTX\xb0F+\x1b!!!!Y-,\xb0\x02CTX\xb0G+\x1b!!!Y-,\xb0\x02CTX\xb0H+\x1b!!!!Y-,\xb0\x02CTX\xb0I+\x1b!!!Y-, \x8a\b#KS\x8aKQZX#8\x1b!!Y-,\x00\xb0\x02%I\xb0\x00SX \xb0@8\x11\x1b!Y-,\x01F#F`#Fa# \x10 F\x8aa\xb8\xff\x80b\x8a\xb1@@\x8apE`h:-, \x8a#Id\x8a#SX<\x1b!Y-,KRX}\x1bzY-,\xb0\x12\x00K\x01KTB-,\xb1\x02\x00B\xb1#\x01\x88Q\xb1@\x01\x88SZX\xb9\x10\x00\x00 \x88TX\xb2\x02\x01\x02C`BY\xb1$\x01\x88QX\xb9 \x00\x00@\x88TX\xb2\x02\x02\x02C`B\xb1$\x01\x88TX\xb2\x02 \x02C`B\x00K\x01KRX\xb2\x02\b\x02C`BY\x1b\xb9@\x00\x00\x80\x88TX\xb2\x02\x04\x02C`BY\xb9@\x00\x00\x80c\xb8\x01\x00\x88TX\xb2\x02\b\x02C`BY\xb9@\x00\x01\x00c\xb8\x02\x00\x88TX\xb2\x02\x10\x02C`BY\xb9@\x00\x02\x00c\xb8\x04\x00\x88TX\xb2\x02@\x02C`BYYYYY-,E\x18h#KQX# E d\xb0@PX|Yh\x8a`YD-,\xb0\x00\x16\xb0\x02%\xb0\x02%\x01\xb0\x01#>\x00\xb0\x02#>\xb1\x01\x02\x06\f\xb0\n#eB\xb0\v#B\x01\xb0\x01#?\x00\xb0\x02#?\xb1\x01\x02\x06\f\xb0\x06#eB\xb0\a#B\xb0\x01\x16\x01-,z\x8a\x10E#\xf5\x18-\x00\x00\x00@3\t\xf8\x03\xff\x1fP\xf4\x01\x9f\xf3\x010\xf1\x017\xf0G\xf0W\xf0\x03/\xef\x9f\xef\x02@\xedP\xed\xd0\xed\x030\xec\x01%\xeb5\xebU\xebe\xeb\x04W\xe4\x01f\xe1\x01\xb8\xff\xf0\xb3\xe0\x13\x16F\xb8\xff\xf0@?\xe0\v\x0eF\xfc\xfb3\x1f\xdf3\xddU\xde3\xdcU0\xfb@\xfb`\xfb\x03_\xdd\xcf\xdd\x02 \xdd0\xdd\x02\xdc\x03\x19\x1f\xc9\xc8\x19\x1f\xc6\xc53\x1f\xfe\xc2\x19\x1f0\xc0\x01\xbb\xba\x19\x1f@\xb7`\xb7\x80\xb7\x03\xb8\xff\xc0\xb3\xb7\x11\x14F\xb8\xff趴\t\rF\xc0\xb1\x01\xb8\xff\xe8@\xa1\xaf\n\x0eF/\x9c?\x9c\xff\x9c\x03\x8f\x9b\xef\x9b\x02\x9a\x99\x19\x1f\xc0\x97З\x02\x90\x8d\x19\x1f\x1f\x8c/\x8c?\x8c\x03O\x8c\xbf\x8cό\x03\xbb\x82\x01g\xfa\xa7\xfa\x02Ft\x01&n6n\x02\x1a\x01\x18U\x19\x13\xff\x1f\a\x04\xff\x1f\x06\x03\xff\x1f\xbfg\x01 f\x80f\x90f\x03\x8fe\x9fe\x02 d\xb0d\x02'^7^\x02&]\x01\\Z\x14\x1f[Z\x14\x1f&Z6Z\x02\x133\x12U\x05\x01\x03U\x043\x03U\x0f\x03\x01/\x03\x9f\x03\x02\vW\x1bW+W\xebW\x04\a\x12V\"V2V\xa2V\x04\xafU\x01\xc4T\x01\xb8\xff\xe0@!T\a\fF S\xf0S\x02FO\x017O\x01FN\x017N\x01[\xffk\xff{\xff\x03 \xff\x13\x18F\xb8\xff\xf0\xb7H\t\fFGF\x19\x1f\xb8\xff\xf0@1F\t\fF\x163\x15U\x11\x01\x0fU\x103\x0fU\x02\x01\x00U\x01G\x00U\xaf\x0f\xcf\x0f\x020\x0f\x01o\x00\u007f\x00\xaf\x00\xef\x00\x04\x10\x00\x01\x80\x16\x01\x05\x01\xb8\x01\x90\xb1TS++K\xb8\a\xffRK\xb0\tP[\xb0\x01\x88\xb0%S\xb0\x01\x88\xb0@QZ\xb0\x06\x88\xb0\x00UZ[X\xb1\x01\x01\x8eY\x85\x8d\x8d\x00B\x1dK\xb02SX\xb0`\x1dYK\xb0dSX\xb0@\x1dYK\xb0\x80SX\xb0\x10\x1d\xb1\x16\x00BYssss+++++\x01++++s\x00sssss\x01+sss^s\x00st+++\x01s++ssssss\x00++++\x01s\x00ss\x01s\x00st+\x01s+\x00ss+\x01s+\x00+s+\x01s\x00+\x01+\x00++sss+++\x01++s\x00s\x01ss\x00ss\x01ssss\x00+\x18^\x00\x00\x06\x14\x00\v\x00N\x05\xb6\x00\x17\x00u\x05\xb6\x05\xcd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04^\x00\x15\x00{\x00\x00\xff\xec\x00\x00\x00\x00\xff\xec\x00\x00\x00\x00\xff\xec\x00\x00\xfe\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x00\x00\x011\x018\x01\x1f\x01\a\x01L\x00\x00\x00\x00\x00\xf5\x00\xe2\x00\xd9\x00\xcb\x00\xb2\x00\xbf\x01+\x00\xa0\x00\xa0\x00f\x00f\x00\x00\x00\x00\x016\x01?\x01/\x01!\x01\x14\x01\x02\x00\xf6\x00\x8e\x00\x00\x00\x00\x00\xb8\x00\xb8\x00w\x00w\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x016\x01?\x00\x89\x00\x00\x00\x00\x01\x02\x00\xfa\x00\xf0\x00\xe3\x00\xd9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x1a\x00\xf0\x00\x9b\x00\xd3\x01\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\f\x00\xa8\x00\xd3\x00\x8d\x00\xb7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01^\x01}\x01\x17\x00\xf5\x00\xe1\x01T\x01\xf6\x00\xbe\x00\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdb\x01\b\x00\x00\x00\xdb\x00\xce\x01\x00\x00\x00\x01\x14\x00\x00\x00\x00\x00\xfc\x02\xb6\x00\xcf\x03\x96\x00\x00\x00\x8c\x00\xe6\x00\xfa\x00\xc8\x02\x9e\x00\xa8\x00\xb5\x01L\x00\x00\x01y\x00\x8e\x00\xe6\x00\xa8\x00\xa3\x00\x00\x00\x8e\x00\xa8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\xb6\x02J\x00\x14\xff\xef\x00\xee\x00\xda\x00\xca\x00\x00\x00\xc4\x00\xaa\x00\xa0\x00\x96\x00x\x00\x00\x00\x00\x017\x02\x10\x01\xd3\x00\x00\x021\x01\b\x02%\x01\xe4\x01\xb6\x01\x00\x00\xe2\x00\xef\x00\xd3\x05\xb6\xfe\xbc\x00\xb2\x02\xfc\xff\xf4\x00\xa2\x01\xa1\x00\xe8\x00U\x00\x9a\x00\xb2\x00\x00\x00\x00\x00\a\x00Z\x00\x03\x00\x01\x04\t\x00\x01\x00\x14\x00\x00\x00\x03\x00\x01\x04\t\x00\x02\x00\b\x00\x14\x00\x03\x00\x01\x04\t\x00\x03\x004\x00\x1c\x00\x03\x00\x01\x04\t\x00\x04\x00\x1e\x00P\x00\x03\x00\x01\x04\t\x00\x05\x00,\x00n\x00\x03\x00\x01\x04\t\x00\x06\x00\x1c\x00\x9a\x00\x03\x00\x01\x04\t\x00\x0e\x00T\x00\xb6\x00D\x00r\x00o\x00i\x00d\x00 \x00S\x00a\x00n\x00s\x00B\x00o\x00l\x00d\x00A\x00s\x00c\x00e\x00n\x00d\x00e\x00r\x00 \x00-\x00 \x00D\x00r\x00o\x00i\x00d\x00 \x00S\x00a\x00n\x00s\x00 \x00B\x00o\x00l\x00d\x00D\x00r\x00o\x00i\x00d\x00 \x00S\x00a\x00n\x00s\x00 \x00B\x00o\x00l\x00d\x00V\x00e\x00r\x00s\x00i\x00o\x00n\x00 \x001\x00.\x000\x000\x00 \x00b\x00u\x00i\x00l\x00d\x00 \x001\x001\x002\x00D\x00r\x00o\x00i\x00d\x00S\x00a\x00n\x00s\x00-\x00B\x00o\x00l\x00d\x00h\x00t\x00t\x00p\x00:\x00/\x00/\x00w\x00w\x00w\x00.\x00a\x00p\x00a\x00c\x00h\x00e\x00.\x00o\x00r\x00g\x00/\x00l\x00i\x00c\x00e\x00n\x00s\x00e\x00s\x00/\x00L\x00I\x00C\x00E\x00N\x00S\x00E\x00-\x002\x00.\x000\x00\x02\x00\x00\x00\x00\x00\x00\xfff\x00f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\a\x00\b\x00\t\x00\n\x00\v\x00\f\x00\r\x00\x0e\x00\x0f\x00\x10\x00\x11\x00\x12\x00\x13\x00\x14\x00\x15\x00\x16\x00\x17\x00\x18\x00\x19\x00\x1a\x00\x1b\x00\x1c\x00\x1d\x00\x1e\x00\x1f\x00 \x00!\x00\"\x00#\x00$\x00%\x00&\x00'\x00(\x00)\x00*\x00+\x00,\x00-\x00.\x00/\x000\x001\x002\x003\x004\x005\x006\x007\x008\x009\x00:\x00;\x00<\x00=\x00>\x00?\x00@\x00A\x00B\x00C\x00D\x00E\x00F\x00G\x00H\x00I\x00J\x00K\x00L\x00M\x00N\x00O\x00P\x00Q\x00R\x00S\x00T\x00U\x00V\x00W\x00X\x00Y\x00Z\x00[\x00\\\x00]\x00^\x00_\x00`\x00a\x00\xac\x00\xa3\x00\x84\x00\x85\x00\xbd\x00\x96\x00\xe8\x00\x86\x00\x8e\x00\x8b\x00\x9d\x00\xa9\x00\xa4\x01\x02\x00\x8a\x01\x03\x00\x83\x00\x93\x00\xf2\x00\xf3\x00\x8d\x00\x97\x00\x88\x00\xc3\x00\xde\x00\xf1\x00\x9e\x00\xaa\x00\xf5\x00\xf4\x00\xf6\x00\xa2\x00\xad\x00\xc9\x00\xc7\x00\xae\x00b\x00c\x00\x90\x00d\x00\xcb\x00e\x00\xc8\x00\xca\x00\xcf\x00\xcc\x00\xcd\x00\xce\x00\xe9\x00f\x00\xd3\x00\xd0\x00\xd1\x00\xaf\x00g\x00\xf0\x00\x91\x00\xd6\x00\xd4\x00\xd5\x00h\x00\xeb\x00\xed\x00\x89\x00j\x00i\x00k\x00m\x00l\x00n\x00\xa0\x00o\x00q\x00p\x00r\x00s\x00u\x00t\x00v\x00w\x00\xea\x00x\x00z\x00y\x00{\x00}\x00|\x00\xb8\x00\xa1\x00\u007f\x00~\x00\x80\x00\x81\x00\xec\x00\xee\x00\xba\x00\xd7\x00\xd8\x00\xdd\x00\xd9\x00\xb2\x00\xb3\x00\xb6\x00\xb7\x00\xc4\x00\xb4\x00\xb5\x00\xc5\x00\x87\x00\xbe\x00\xbf\x00\xbc\x01\x04\auni00AD\toverscore\ffoursuperior\x00\x00\x00\x00\x02\x00\b\x00\x02\xff\xff\x00\x03\x00\x01\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x00\x00\x00\xd1\x00\x01\x00\x00\x00\x01\x00\x00\x00\n\x00\x1e\x00,\x00\x01latn\x00\b\x00\x04\x00\x00\x00\x00\xff\xff\x00\x01\x00\x00\x00\x01kern\x00\b\x00\x00\x00\x01\x00\x00\x00\x01\x00\x04\x00\x02\x00\b\x00\x01\x00\b\x00\x01\x00\xd6\x00\x04\x00\x00\x00f\x01p\x01p\n\xea\x02\"\x11\xdc\x02\"\x02x\x02\xde\rX\x02\xf8\x03R\x0e.\x03\xac\x03\xea\x0e\xf8\x04P\x04\x92\x04\xec\x04\xf2\x06\x1c\x06F\a@\b:\b\xbc\x0e.\n\xea\x10\xea\x10\xea\x10\xf0\x10\xea\t\xce\t\xf4\n\n\n\x10\x10\xea\x10\xea\x110\n\"\x10\xf0\nT\nj\nj\n\x80\n\xb2\n\xc8\n\xea\v\x86\n\xf0\n\xf0\v\x86\f$\f\xba\rX\r\xe4\r\xa2\r\xe4\r\xe4\x0e.\x0e.\x0e.\x0e.\x0el\x0e\x8a\x0e\x8a\x0e\x8a\x0e\x8a\x0e\x8a\x0e\xf8\x0fR\x0fR\x0fR\x0fR\x0f\xa0\x10\xea\x10\xea\x10\xea\x10\xea\x10\xea\x10\xea\x110\x10\xf0\x11\x02\x11\x02\x11\x02\x11\x02\x11\f\x11\x1a\x11\x1a\x11\x1a\x11\x1a\x11\x1a\x110\x11:\x11:\x11:\x11:\x11D\x11\xbe\x11\xdc\x11\xdc\x11\xe2\x11\xe2\x00\x02\x00\x19\x00\x05\x00\x05\x00\x00\x00\n\x00\v\x00\x01\x00\x0f\x00\x11\x00\x03\x00$\x00'\x00\x06\x00)\x00)\x00\n\x00,\x00,\x00\v\x00.\x00/\x00\f\x002\x005\x00\x0e\x007\x00>\x00\x12\x00D\x00F\x00\x1a\x00H\x00K\x00\x1d\x00N\x00N\x00!\x00P\x00R\x00\"\x00U\x00W\x00%\x00Y\x00^\x00(\x00\x82\x00\x87\x00.\x00\x89\x00\x92\x004\x00\x94\x00\x98\x00>\x00\x9a\x00\x9f\x00C\x00\xa2\x00\xad\x00I\x00\xb3\x00\xb8\x00U\x00\xba\x00\xbf\x00[\x00\xc1\x00\xc1\x00a\x00\xc6\x00\xc8\x00b\x00\xcb\x00\xcb\x00e\x00,\x00$\xff\xae\x00,\x00)\x007\x00R\x009\x00R\x00:\x00f\x00;\x00)\x00<\x00R\x00=\x00)\x00F\xff\xc3\x00G\xff\xc3\x00H\xff\xc3\x00J\xff\xd7\x00R\xff\xc3\x00T\xff\xc3\x00W\x00)\x00Y\x00)\x00Z\x00\x14\x00\\\x00)\x00\x82\xff\xae\x00\x83\xff\xae\x00\x84\xff\xae\x00\x85\xff\xae\x00\x86\xff\xae\x00\x87\xff\xae\x00\x88\xff\\\x00\x8e\x00)\x00\x8f\x00)\x00\x90\x00)\x00\x91\x00)\x00\x9f\x00R\x00\xa8\xff\xc3\x00\xa9\xff\xc3\x00\xaa\xff\xc3\x00\xab\xff\xc3\x00\xac\xff\xc3\x00\xad\xff\xc3\x00\xb4\xff\xc3\x00\xb5\xff\xc3\x00\xb6\xff\xc3\x00\xb7\xff\xc3\x00\xb8\xff\xc3\x00\xba\xff\xc3\x00\xbf\x00)\x00\xc1\x00)\x00\x15\x00&\xff\xc3\x00*\xff\xc3\x002\xff\xc3\x004\xff\xc3\x007\xff\x9a\x008\xff\xd7\x009\xff\x9a\x00:\xff\xae\x00<\xff\x9a\x00\x89\xff\xc3\x00\x94\xff\xc3\x00\x95\xff\xc3\x00\x96\xff\xc3\x00\x97\xff\xc3\x00\x98\xff\xc3\x00\x9a\xff\xc3\x00\x9b\xff\xd7\x00\x9c\xff\xd7\x00\x9d\xff\xd7\x00\x9e\xff\xd7\x00\x9f\xff\x9a\x00\x19\x00\x05\xff\xae\x00\n\xff\xae\x00&\xff\xec\x00*\xff\xec\x002\xff\xec\x004\xff\xec\x007\xff\x85\x008\xff\xec\x009\xff\xc3\x00:\xff\xd7\x00<\xff\x9a\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\x9b\xff\xec\x00\x9c\xff\xec\x00\x9d\xff\xec\x00\x9e\xff\xec\x00\x9f\xff\x9a\x00\xc9\xff\xae\x00\xcc\xff\xae\x00\x06\x00,\xff\xec\x007\xff\xec\x009\xff\xec\x00;\xff\xec\x00<\xff\xec\x00\x9f\xff\xec\x00\x16\x00\x0f\xff\xc3\x00\x11\xff\xc3\x00$\xff\xec\x00,\xff\xec\x007\xff\xc3\x009\xff\xec\x00:\xff\xec\x00;\xff\xec\x00<\xff\xd7\x00=\xff\xec\x00\x82\xff\xec\x00\x83\xff\xec\x00\x84\xff\xec\x00\x85\xff\xec\x00\x86\xff\xec\x00\x87\xff\xec\x00\x88\xff\xc3\x00\x8e\xff\xec\x00\x8f\xff\xec\x00\x90\xff\xec\x00\x91\xff\xec\x00\x9f\xff\xd7\x00\x16\x00\x05\x00=\x00\n\x00=\x00\f\x00)\x00\x0f\xff\x9a\x00\x11\xff\x9a\x00\"\x00)\x00$\xff\xd7\x009\x00\x14\x00:\x00\x14\x00<\x00\x14\x00@\x00)\x00`\x00)\x00\x82\xff\xd7\x00\x83\xff\xd7\x00\x84\xff\xd7\x00\x85\xff\xd7\x00\x86\xff\xd7\x00\x87\xff\xd7\x00\x88\xff\xc3\x00\x9f\x00\x14\x00\xc9\x00=\x00\xcc\x00=\x00\x0f\x00\x05\x00)\x00\n\x00)\x00&\xff\xd7\x00*\xff\xd7\x002\xff\xd7\x004\xff\xd7\x00\x89\xff\xd7\x00\x94\xff\xd7\x00\x95\xff\xd7\x00\x96\xff\xd7\x00\x97\xff\xd7\x00\x98\xff\xd7\x00\x9a\xff\xd7\x00\xc9\x00)\x00\xcc\x00)\x00\x19\x00\x05\xff\x9a\x00\n\xff\x9a\x00&\xff\xec\x00*\xff\xec\x002\xff\xec\x004\xff\xec\x007\xff\x85\x008\xff\xec\x009\xff\xae\x00:\xff\xc3\x00<\xff\x9a\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\x9b\xff\xec\x00\x9c\xff\xec\x00\x9d\xff\xec\x00\x9e\xff\xec\x00\x9f\xff\x9a\x00\xc9\xff\x9a\x00\xcc\xff\x9a\x00\x10\x00\x0f\xff3\x00\x11\xff3\x00$\xff\xae\x00&\xff\xec\x00;\xff\xec\x00<\xff\xec\x00=\xff\xd7\x00\x82\xff\xae\x00\x83\xff\xae\x00\x84\xff\xae\x00\x85\xff\xae\x00\x86\xff\xae\x00\x87\xff\xae\x00\x88\xffq\x00\x89\xff\xec\x00\x9f\xff\xec\x00\x16\x00\x0f\xff\xc3\x00\x11\xff\xc3\x00$\xff\xec\x00,\xff\xec\x007\xff\xc3\x009\xff\xd7\x00:\xff\xec\x00;\xff\xd7\x00<\xff\xd7\x00=\xff\xec\x00\x82\xff\xec\x00\x83\xff\xec\x00\x84\xff\xec\x00\x85\xff\xec\x00\x86\xff\xec\x00\x87\xff\xec\x00\x88\xff\xc3\x00\x8e\xff\xec\x00\x8f\xff\xec\x00\x90\xff\xec\x00\x91\xff\xec\x00\x9f\xff\xd7\x00\x01\x007\xff\xec\x00J\x00\x05\x00R\x00\n\x00R\x00\x0f\xff\x9a\x00\x10\xff\x9a\x00\x11\xff\x9a\x00\"\x00)\x00$\xff\x85\x00&\xff\xc3\x00*\xff\xc3\x002\xff\xc3\x004\xff\xc3\x006\xff\xec\x007\x00\x14\x00D\xff\x85\x00F\xff\x85\x00G\xff\x85\x00H\xff\x85\x00J\xff\x9a\x00P\xff\xae\x00Q\xff\xae\x00R\xff\x85\x00S\xff\xae\x00T\xff\x85\x00U\xff\xae\x00V\xff\x85\x00X\xff\xae\x00Y\xff\xc3\x00Z\xff\xc3\x00[\xff\xc3\x00\\\xff\xc3\x00]\xff\xc3\x00\x82\xff\x85\x00\x83\xff\x85\x00\x84\xff\x85\x00\x85\xff\x85\x00\x86\xff\x85\x00\x87\xff\x85\x00\x88\xffq\x00\x89\xff\xc3\x00\x94\xff\xc3\x00\x95\xff\xc3\x00\x96\xff\xc3\x00\x97\xff\xc3\x00\x98\xff\xc3\x00\x9a\xff\xc3\x00\xa2\xff\x85\x00\xa3\xff\x85\x00\xa4\xff\x85\x00\xa5\xff\x85\x00\xa6\xff\x85\x00\xa7\xff\x85\x00\xa8\xff\x85\x00\xa9\xff\x85\x00\xaa\xff\x85\x00\xab\xff\x85\x00\xac\xff\x85\x00\xad\xff\x85\x00\xb3\xff\xae\x00\xb4\xff\x85\x00\xb5\xff\x85\x00\xb6\xff\x85\x00\xb7\xff\x85\x00\xb8\xff\x85\x00\xba\xff\x85\x00\xbb\xff\xae\x00\xbc\xff\xae\x00\xbd\xff\xae\x00\xbe\xff\xae\x00\xbf\xff\xc3\x00\xc1\xff\xc3\x00\xc6\xff\xae\x00\xc7\xff\x9a\x00\xc9\x00R\x00\xcc\x00R\x00\n\x00\x0f\xff\xd7\x00\x11\xff\xd7\x00$\xff\xec\x00\x82\xff\xec\x00\x83\xff\xec\x00\x84\xff\xec\x00\x85\xff\xec\x00\x86\xff\xec\x00\x87\xff\xec\x00\x88\xff\xd7\x00>\x00\x05\x00R\x00\n\x00R\x00\x0f\xff\x9a\x00\x11\xff\x9a\x00\"\x00)\x00$\xff\xc3\x00&\xff\xd7\x00*\xff\xd7\x002\xff\xd7\x004\xff\xd7\x00D\xff\xc3\x00F\xff\xc3\x00G\xff\xc3\x00H\xff\xc3\x00J\xff\xc3\x00P\xff\xd7\x00Q\xff\xd7\x00R\xff\xc3\x00S\xff\xd7\x00T\xff\xc3\x00U\xff\xd7\x00V\xff\xd7\x00X\xff\xd7\x00\x82\xff\xc3\x00\x83\xff\xc3\x00\x84\xff\xc3\x00\x85\xff\xc3\x00\x86\xff\xc3\x00\x87\xff\xc3\x00\x88\xff\x85\x00\x89\xff\xd7\x00\x94\xff\xd7\x00\x95\xff\xd7\x00\x96\xff\xd7\x00\x97\xff\xd7\x00\x98\xff\xd7\x00\x9a\xff\xd7\x00\xa2\xff\xc3\x00\xa3\xff\xc3\x00\xa4\xff\xc3\x00\xa5\xff\xc3\x00\xa6\xff\xc3\x00\xa7\xff\xc3\x00\xa8\xff\xc3\x00\xa9\xff\xc3\x00\xaa\xff\xc3\x00\xab\xff\xc3\x00\xac\xff\xc3\x00\xad\xff\xc3\x00\xb3\xff\xd7\x00\xb4\xff\xc3\x00\xb5\xff\xc3\x00\xb6\xff\xc3\x00\xb7\xff\xc3\x00\xb8\xff\xc3\x00\xba\xff\xc3\x00\xbb\xff\xd7\x00\xbc\xff\xd7\x00\xbd\xff\xd7\x00\xbe\xff\xd7\x00\xc9\x00R\x00\xcc\x00R\x00>\x00\x05\x00f\x00\n\x00f\x00\x0f\xff\xae\x00\x11\xff\xae\x00$\xff\xd7\x00&\xff\xec\x00*\xff\xec\x002\xff\xec\x004\xff\xec\x00D\xff\xd7\x00F\xff\xd7\x00G\xff\xd7\x00H\xff\xd7\x00J\xff\xec\x00P\xff\xec\x00Q\xff\xec\x00R\xff\xd7\x00S\xff\xec\x00T\xff\xd7\x00U\xff\xec\x00V\xff\xd7\x00X\xff\xec\x00]\xff\xec\x00\x82\xff\xd7\x00\x83\xff\xd7\x00\x84\xff\xd7\x00\x85\xff\xd7\x00\x86\xff\xd7\x00\x87\xff\xd7\x00\x88\xff\xae\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\xa2\xff\xd7\x00\xa3\xff\xd7\x00\xa4\xff\xd7\x00\xa5\xff\xd7\x00\xa6\xff\xd7\x00\xa7\xff\xd7\x00\xa8\xff\xd7\x00\xa9\xff\xd7\x00\xaa\xff\xd7\x00\xab\xff\xd7\x00\xac\xff\xd7\x00\xad\xff\xd7\x00\xb3\xff\xec\x00\xb4\xff\xd7\x00\xb5\xff\xd7\x00\xb6\xff\xd7\x00\xb7\xff\xd7\x00\xb8\xff\xd7\x00\xba\xff\xd7\x00\xbb\xff\xec\x00\xbc\xff\xec\x00\xbd\xff\xec\x00\xbe\xff\xec\x00\xc9\x00f\x00\xcc\x00f\x00 \x00\x05\x00)\x00\n\x00)\x00&\xff\xd7\x00*\xff\xd7\x002\xff\xd7\x004\xff\xd7\x00F\xff\xec\x00G\xff\xec\x00H\xff\xec\x00R\xff\xec\x00T\xff\xec\x00\x89\xff\xd7\x00\x94\xff\xd7\x00\x95\xff\xd7\x00\x96\xff\xd7\x00\x97\xff\xd7\x00\x98\xff\xd7\x00\x9a\xff\xd7\x00\xa8\xff\xec\x00\xa9\xff\xec\x00\xaa\xff\xec\x00\xab\xff\xec\x00\xac\xff\xec\x00\xad\xff\xec\x00\xb4\xff\xec\x00\xb5\xff\xec\x00\xb6\xff\xec\x00\xb7\xff\xec\x00\xb8\xff\xec\x00\xba\xff\xec\x00\xc9\x00)\x00\xcc\x00)\x00D\x00\x05\x00R\x00\n\x00R\x00\x0f\xff\x9a\x00\x11\xff\x9a\x00\"\x00)\x00$\xff\x9a\x00&\xff\xd7\x00*\xff\xd7\x002\xff\xd7\x004\xff\xd7\x006\xff\xec\x00D\xff\x9a\x00F\xff\x9a\x00G\xff\x9a\x00H\xff\x9a\x00J\xff\x9a\x00P\xff\xc3\x00Q\xff\xc3\x00R\xff\x9a\x00S\xff\xc3\x00T\xff\x9a\x00U\xff\xc3\x00V\xff\xae\x00X\xff\xc3\x00[\xff\xd7\x00\\\xff\xec\x00]\xff\xc3\x00\x82\xff\x9a\x00\x83\xff\x9a\x00\x84\xff\x9a\x00\x85\xff\x9a\x00\x86\xff\x9a\x00\x87\xff\x9a\x00\x88\xffq\x00\x89\xff\xd7\x00\x94\xff\xd7\x00\x95\xff\xd7\x00\x96\xff\xd7\x00\x97\xff\xd7\x00\x98\xff\xd7\x00\x9a\xff\xd7\x00\xa2\xff\x9a\x00\xa3\xff\x9a\x00\xa4\xff\x9a\x00\xa5\xff\x9a\x00\xa6\xff\x9a\x00\xa7\xff\x9a\x00\xa8\xff\x9a\x00\xa9\xff\x9a\x00\xaa\xff\x9a\x00\xab\xff\x9a\x00\xac\xff\x9a\x00\xad\xff\x9a\x00\xb3\xff\xc3\x00\xb4\xff\x9a\x00\xb5\xff\x9a\x00\xb6\xff\x9a\x00\xb7\xff\x9a\x00\xb8\xff\x9a\x00\xba\xff\x9a\x00\xbb\xff\xc3\x00\xbc\xff\xc3\x00\xbd\xff\xc3\x00\xbe\xff\xc3\x00\xbf\xff\xec\x00\xc1\xff\xec\x00\xc9\x00R\x00\xcc\x00R\x00\t\x00\x05\x00f\x00\n\x00f\x00Y\x00\x14\x00Z\x00\x14\x00\\\x00\x14\x00\xbf\x00\x14\x00\xc1\x00\x14\x00\xc9\x00f\x00\xcc\x00f\x00\x05\x00\x05\x00)\x00\n\x00)\x00J\x00\x14\x00\xc9\x00)\x00\xcc\x00)\x00\x01\x00\n\xff\xc3\x00\x04\x00\x05\x00)\x00\n\x00)\x00\xc9\x00)\x00\xcc\x00)\x00\f\x00\x05\x00f\x00\n\x00f\x00D\xff\xec\x00J\xff\xec\x00\xa2\xff\xec\x00\xa3\xff\xec\x00\xa4\xff\xec\x00\xa5\xff\xec\x00\xa6\xff\xec\x00\xa7\xff\xec\x00\xc9\x00f\x00\xcc\x00f\x00\x05\x00\x05\x00R\x00\n\x00R\x00W\x00\x14\x00\xc9\x00R\x00\xcc\x00R\x00\x05\x00\x05\x00R\x00\n\x00R\x00I\x00\x14\x00\xc9\x00R\x00\xcc\x00R\x00\f\x00\x05\x00)\x00\n\x00)\x00R\xff\xd7\x00\xa8\xff\xd7\x00\xb4\xff\xd7\x00\xb5\xff\xd7\x00\xb6\xff\xd7\x00\xb7\xff\xd7\x00\xb8\xff\xd7\x00\xba\xff\xd7\x00\xc9\x00)\x00\xcc\x00)\x00\x05\x00\x05\x00=\x00\n\x00=\x00I\x00\x14\x00\xc9\x00=\x00\xcc\x00=\x00\b\x00R\xff\xec\x00\xa8\xff\xec\x00\xb4\xff\xec\x00\xb5\xff\xec\x00\xb6\xff\xec\x00\xb7\xff\xec\x00\xb8\xff\xec\x00\xba\xff\xec\x00\x01\x00-\x00{\x00%\x00\x05\xff\xae\x00\n\xff\xae\x00\r\xff\x85\x00\x0f\x00D\x00\x1e\x00D\x00\"\xff\xc3\x00&\xff\xec\x00*\xff\xec\x002\xff\xec\x004\xff\xec\x007\xff\x85\x008\xff\xec\x009\xff\xc3\x00:\xff\xd7\x00<\xff\x9a\x00=\x00;\x00I\xff\xec\x00W\xff\xec\x00Y\xff\xd7\x00Z\xff\xec\x00\\\xff\xd7\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\x9b\xff\xec\x00\x9c\xff\xec\x00\x9d\xff\xec\x00\x9e\xff\xec\x00\x9f\xff\x9a\x00\xbf\xff\xd7\x00\xc1\xff\xd7\x00\xc9\xff\xae\x00\xcc\xff\xae\x00'\x00\x05\xff\xae\x00\n\xff\xae\x00\r\xff\x85\x00\x0f\x00D\x00\x1e\x00D\x00\"\xff\xc3\x00&\xff\xec\x00*\xff\xec\x00-\x00^\x002\xff\xec\x004\xff\xec\x007\xff\x85\x008\xff\xec\x009\xff\xc3\x00:\xff\xd7\x00<\xff\x9a\x00=\x00;\x00I\xff\xec\x00W\xff\xec\x00Y\xff\xd7\x00Z\xff\xec\x00\\\xff\xd7\x00\x82\xff\xd7\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\x9b\xff\xec\x00\x9c\xff\xec\x00\x9d\xff\xec\x00\x9e\xff\xec\x00\x9f\xff\x9a\x00\xbf\xff\xd7\x00\xc1\xff\xd7\x00\xc9\xff\xae\x00\xcc\xff\xae\x00%\x00\x05\xff\xae\x00\n\xff\xae\x00\r\xff\u007f\x00\x0f\x00D\x00\x1e\x00D\x00\"\xff\xd7\x00&\xff\xec\x00*\xff\xec\x00-\x00^\x002\xff\xec\x004\xff\xec\x007\xff\x85\x008\xff\xec\x009\xff\xc3\x00:\xff\xd7\x00<\xff\x9a\x00=\x00;\x00W\xff\xe5\x00Y\xff\xd5\x00Z\xff\xe5\x00\\\xff\xdb\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\x9b\xff\xec\x00\x9c\xff\xec\x00\x9d\xff\xec\x00\x9e\xff\xec\x00\x9f\xff\x9a\x00\xbf\xff\xdb\x00\xc1\xff\xdb\x00\xc9\xff\xae\x00\xcc\xff\xae\x00'\x00\x05\xfff\x00\n\xfff\x00\r\xff\u007f\x00\x0f\x00D\x00\x1e\x00D\x00\"\xff\xd7\x00&\xff\xec\x00*\xff\xec\x00-\x00^\x002\xff\xec\x004\xff\xec\x007\xff\x85\x008\xff\xec\x009\xff\xc3\x00:\xff\xd7\x00<\xff\x9a\x00=\x00;\x00W\xff\xe5\x00Y\xff\xd5\x00Z\xff\xe5\x00\\\xff\xdb\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\x9b\xff\xec\x00\x9c\xff\xec\x00\x9d\xff\xec\x00\x9e\xff\xec\x00\x9f\xff\x9a\x00\xbf\xff\xdb\x00\xc1\xff\xdb\x00\xc8\xfff\x00\xc9\xff\xae\x00\xcb\xfff\x00\xcc\xff\xae\x00\x12\x00\x05\x00)\x00\n\x00)\x00\f\x00)\x00&\xff\xd7\x00*\xff\xd7\x002\xff\xd7\x004\xff\xd7\x00@\x00)\x00`\x00)\x00\x89\xff\xd7\x00\x94\xff\xd7\x00\x95\xff\xd7\x00\x96\xff\xd7\x00\x97\xff\xd7\x00\x98\xff\xd7\x00\x9a\xff\xd7\x00\xc9\x00)\x00\xcc\x00)\x00\x10\x00\x05\x00)\x00\n\x00)\x00\x10\xff\xd7\x00&\xff\xec\x002\xff\xec\x004\xff\xec\x00\x89\xff\xec\x00\x8b\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\xc9\x00)\x00\xcc\x00)\x00\x12\x00\x05\x00)\x00\n\x00)\x00\x10\xff\xd7\x00&\xff\xec\x002\xff\xec\x004\xff\xec\x00\x84\xff\xec\x00\x89\xff\xec\x00\x8a\xff\xec\x00\x8f\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\xc9\x00)\x00\xcc\x00)\x00\x0f\x00\x05\x00)\x00\n\x00)\x00&\xff\xec\x00*\xff\xec\x002\xff\xec\x004\xff\xec\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\xc9\x00)\x00\xcc\x00)\x00\a\x00$\xff\xec\x00\x82\xff\xec\x00\x83\xff\xec\x00\x84\xff\xec\x00\x85\xff\xec\x00\x86\xff\xec\x00\x87\xff\xec\x00\x1b\x00\f\xff\xd7\x00\x0f\xff\xc3\x00\x11\xff\xc3\x00$\xff\xec\x00,\xff\xec\x00-\xff\xf6\x006\xff\xec\x007\xff\xc3\x009\xff\xd7\x00:\xff\xec\x00;\xff\xd7\x00<\xff\xd7\x00=\xff\xec\x00@\xff\xd7\x00`\xff\xd7\x00\x82\xff\xec\x00\x83\xff\xec\x00\x84\xff\xec\x00\x85\xff\xec\x00\x86\xff\xec\x00\x87\xff\xec\x00\x88\xff\xd7\x00\x8e\xff\xec\x00\x8f\xff\xec\x00\x90\xff\xec\x00\x91\xff\xec\x00\x9f\xff\xd7\x00\x16\x00\x0f\xff\xc3\x00\x11\xff\xc3\x00$\xff\xec\x00,\xff\xec\x007\xff\xc3\x009\xff\xd7\x00:\xff\xec\x00;\xff\xd7\x00<\xff\xd7\x00=\xff\xec\x00\x82\xff\xec\x00\x83\xff\xec\x00\x84\xff\xec\x00\x85\xff\xec\x00\x86\xff\xec\x00\x87\xff\xec\x00\x88\xff\xd7\x00\x8e\xff\xec\x00\x8f\xff\xec\x00\x90\xff\xec\x00\x91\xff\xec\x00\x9f\xff\xd7\x00\x13\x00\x0f\xff\xd7\x00\x11\xff\xd7\x00$\xff\xec\x000\xff\xec\x00=\xff\xec\x00D\xff\xec\x00\x82\xff\xec\x00\x83\xff\xec\x00\x84\xff\xec\x00\x85\xff\xec\x00\x86\xff\xec\x00\x87\xff\xec\x00\x88\xff\xd7\x00\xa2\xff\xec\x00\xa3\xff\xec\x00\xa4\xff\xec\x00\xa5\xff\xec\x00\xa6\xff\xec\x00\xa7\xff\xec\x00R\x00\x05\x00R\x00\t\xff\xc3\x00\n\x00R\x00\f\x00=\x00\r\x00)\x00\x0f\xff\x9a\x00\x10\xff\x9a\x00\x11\xff\x9a\x00\"\x00)\x00$\xff\x9a\x00&\xff\xd7\x00*\xff\xd7\x00-\xff\xbe\x000\xff\xc3\x002\xff\xd7\x004\xff\xd7\x006\xff\xec\x007\x00'\x009\x00)\x00:\x00\x14\x00@\x00=\x00D\xff\x9a\x00F\xff\x9a\x00G\xff\x9a\x00H\xff\x9a\x00I\xff\xe5\x00J\xff\x9a\x00P\xff\xc3\x00Q\xff\xc3\x00R\xff\x9a\x00S\xff\xc3\x00T\xff\x9a\x00U\xff\xc3\x00V\xff\xae\x00X\xff\xc3\x00Y\xff\xd7\x00Z\xff\xec\x00[\xff\xd7\x00\\\xff\xec\x00]\xff\xc3\x00`\x00=\x00\x82\xff\x9a\x00\x83\xff\x9a\x00\x84\xff\x9a\x00\x85\xff\x9a\x00\x86\xff\x9a\x00\x87\xff\x9a\x00\x88\xffq\x00\x89\xff\xd7\x00\x94\xff\xd7\x00\x95\xff\xd7\x00\x96\xff\xd7\x00\x97\xff\xd7\x00\x98\xff\xd7\x00\x9a\xff\xd7\x00\xa2\xff\x9a\x00\xa3\xff\x9a\x00\xa4\xff\x9a\x00\xa5\xff\x9a\x00\xa6\xff\x9a\x00\xa7\xff\x9a\x00\xa8\xff\x9a\x00\xa9\xff\x9a\x00\xaa\xff\x9a\x00\xab\xff\x9a\x00\xac\xff\x9a\x00\xad\xff\x9a\x00\xb3\xff\xc3\x00\xb4\xff\x9a\x00\xb5\xff\x9a\x00\xb6\xff\x9a\x00\xb7\xff\x9a\x00\xb8\xff\x9a\x00\xba\xff\x9a\x00\xbb\xff\xc3\x00\xbc\xff\xc3\x00\xbd\xff\xc3\x00\xbe\xff\xc3\x00\xbf\xff\xec\x00\xc1\xff\xec\x00\xc9\x00R\x00\xcc\x00R\x00\x01\x00\n\xff\xd7\x00\x04\x00\x05\x00=\x00\n\x00=\x00\xc9\x00=\x00\xcc\x00=\x00\x02\x00\x05\xff\x98\x00\n\xff\xd7\x00\x03\x00\x05\xff\x98\x00\n\xff\xd7\x00\xcc\xff\xd7\x00\x05\x00\x05\xffo\x00\n\xffo\x00I\xff\xdb\x00[\xff\xd7\x00]\xff\xec\x00\x02\x00[\xff\xd7\x00]\xff\xec\x00\x02\x00\x05\xff\xbe\x00\n\xff\xbe\x00\x1e\x00\x05\x00=\x00\n\x00=\x00\x0f\xff\xbe\x00\x11\xff\xbe\x00\"\xff\xb4\x00F\xff\xf6\x00G\xff\xf6\x00H\xff\xf6\x00I\x00\x14\x00J\xff\xf6\x00R\xff\xf6\x00T\xff\xf6\x00W\x00\x06\x00\xa8\xff\xf6\x00\xa9\xff\xf6\x00\xaa\xff\xf6\x00\xab\xff\xf6\x00\xac\xff\xf6\x00\xad\xff\xf6\x00\xb4\xff\xf6\x00\xb5\xff\xf6\x00\xb6\xff\xf6\x00\xb7\xff\xf6\x00\xb8\xff\xf6\x00\xba\xff\xf6\x00\xc9\x00=\x00\xca\xff\x8d\x00\xcc\x00=\x00\xcd\xff\x8d\x00\xd0\x00\f\x00\a\x00\x05\x00=\x00\n\x00=\x00\x0f\xff\xbe\x00\x11\xff\xbe\x00I\x00\x14\x00\xc9\x00=\x00\xcc\x00=\x00\x01\x007\xff\x9a\x00)\x00$\xff\xae\x00,\x00)\x007\x00R\x009\x00R\x00:\x00f\x00;\x00)\x00<\x00R\x00=\x00)\x00F\xff\xc3\x00G\xff\xc3\x00H\xff\xc3\x00J\xff\xd7\x00R\xff\xc3\x00T\xff\xc3\x00W\x00)\x00Y\x00)\x00Z\x00\x14\x00\x82\xff\xae\x00\x83\xff\xae\x00\x84\xff\xae\x00\x85\xff\xae\x00\x86\xff\xae\x00\x87\xff\xae\x00\x88\xff\\\x00\x8e\x00)\x00\x8f\x00)\x00\x90\x00)\x00\x91\x00)\x00\x9f\x00R\x00\xa8\xff\xc3\x00\xa9\xff\xc3\x00\xaa\xff\xc3\x00\xab\xff\xc3\x00\xac\xff\xc3\x00\xad\xff\xc3\x00\xb4\xff\xc3\x00\xb5\xff\xc3\x00\xb6\xff\xc3\x00\xb7\xff\xc3\x00\xb8\xff\xc3\x00\xba\xff\xc3\x00\x01\x00\x00\x00\n\x00\n\x00\n\x00\x00") + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_700_ttf_bytes() ([]byte, error) { + return _third_party_swagger_ui_fonts_droid_sans_v6_latin_700_ttf, nil +} + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_700_ttf() (*asset, error) { + bytes, err := third_party_swagger_ui_fonts_droid_sans_v6_latin_700_ttf_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/fonts/droid-sans-v6-latin-700.ttf", size: 40516, mode: os.FileMode(416), modTime: time.Unix(1449047502, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_fonts_droid_sans_v6_latin_700_woff = []byte("wOFF\x00\x01\x00\x00\x00\x00e\x88\x00\x11\x00\x00\x00\x00\x9eD\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00GDEF\x00\x00\x01\x80\x00\x00\x00\x16\x00\x00\x00\x16\x00\x10\x00\xd2GPOS\x00\x00\x01\x98\x00\x00\x06$\x00\x00\x12\xc0\xf2ZM^GSUB\x00\x00\a\xbc\x00\x00\x00\f\x00\x00\x00\f\x00\x15\x00\nOS/2\x00\x00\a\xc8\x00\x00\x00`\x00\x00\x00`\xa2\t\xb7\x96cmap\x00\x00\b(\x00\x00\x00j\x00\x00\x00\x8cmag\xdacvt \x00\x00\b\x94\x00\x00\x01\x01\x00\x00\x02\x06K\xe2RQfpgm\x00\x00\t\x98\x00\x00\x04'\x00\x00\a\x05s\xd3#\xb0gasp\x00\x00\r\xc0\x00\x00\x00\f\x00\x00\x00\f\x00\a\x00\aglyf\x00\x00\r\xcc\x00\x00O~\x00\x00u\x1c}]p\bhead\x00\x00]L\x00\x00\x003\x00\x00\x006\xf5\xcd \xd7hhea\x00\x00]\x80\x00\x00\x00\x1f\x00\x00\x00$\r\x9b\x05ahmtx\x00\x00]\xa0\x00\x00\x01\xed\x00\x00\x03L\x9f\xc7I\xb4loca\x00\x00_\x90\x00\x00\x01\xa8\x00\x00\x01\xa8da\x83\"maxp\x00\x00a8\x00\x00\x00 \x00\x00\x00 \x03\x17\x02\x14name\x00\x00aX\x00\x00\x00\xb8\x00\x00\x01d\x19w4\x0fpost\x00\x00b\x10\x00\x00\x01Y\x00\x00\x01\xe7\xa2\xc2\x0f;prep\x00\x00cl\x00\x00\x02\x1c\x00\x00\x02beq֊\x00\x01\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x00\x00\x00\xd1\x00\x01\x00\x00x\x01<\xcc\x03\xac\x1cQ\x14\x80\xe1\u007f\xb0\x1a\xed\xdcF\xb5m۶m\xdb\x0ek\x86\xb5\x19\xa7\xb6m\xc6\rj\x04ճ\xc2}g\x99\xef\xf2\b\r\xb0\xa8LC\xb4\x85\xd3V.&\x8c\t\x10\x8b\xa1\x01ڂY\xcb%\x06\xc9\x1f&\xba\xfc\xb4\xc4\xfe ?\x98\xad-ՖZ\u007f\xf4\xea\xea\x93^]_\xab\u007fq\xc7\xea%\xc6p\xaf\xb1q\xd6\xf8㕘C\xcd\xdd\xe6?3/X1\xd8;\xd45\xdc>|\xd7kl\xfd\xf1\xe3r\xfc?\x91\xf7\x91\x02˲\xfc\xf8_5\xb5\xaa\xfb9\xd6Hk\xbeXo]\xb6^Z\u007f\xec\xadV\x8e\x95couj:\xb7ݱ\xee/\xf7\xb4\xec_^㔅\xdeΌ\x92\xe8\xf0\x94\x932-C5\xf5s\x94\x9e\xe2\xa8\xf2\x19MU\xfb\x94\x9e\xea\xbe\xfa$~\xa8\x1f\xe8\x94# \xc0\xc2F#\x8a\u00a0&u\bROX4\x146\x8di\x82CsZ\xe1цΔ\xa1'\xbd)O_\x06P\x89\xc1\xa2\x1aC\x19NuF1\x86Z\x8cc2u\xd9\xc86\x1a\xb3\x83ݴd/\a\xe9\xccaNЃӜ\xa3\x1fW\xb8)շy\xc0\x04\x1e\x89i<\xe7%\xd3y#fѐ\x9a\xb1\xf3r֣\r\xc3i'\xbb=\xb3\xe9 \xff\x8e\f\xa7\xb4\x1a\xbb\x86\x8e\xe3Z\x03\x00\x85\xd13A\x89XF\xa5\xbajj\x82\x1f\x06\xb3\xc5Z\xe3\xd4QO\x03\x8d4\xb1 :;X\xa6~9+X\xc9vc7\x1b\xa3\x85V\xdah\xa7\x83N\xba覇^\xfa\x19\xd1\xef ?\n\xfe\xec\xe8\xef\xfc\x9fGy\"\xba)xRfSĩ\xce8]\\\xa8|\rkY\xc7z6\xb0\x89\xcd\xdana+\xdbخ\xbd\xfb\xa3g\x84?G\xbf\t\xfe\xce\xffy\x94'\xa2\r\xc1\x93\xe2\x14=\xa7j\x1d\x1b\xdd\xf1\x1aֲ\x8e\xf5l`\x13\x9b\xd9\xc2V\xb6a\xf4\xe0\xa4q?d|\xf0\xcf؈Fc\x1a\xd3\xd9Ώ\x83t\xe3g\xf2GG\xb1V~O\x11\xa7\x86-\xcf\x043\xc4Zꨧ\x81F\x9aX\xa0\xfd2q9+X\x19\x1b\xf9\x8c\x91ǹ\x8f\x11\xd2\xcc^\xbal2\xf9\xbd\xdf\u007fT;\xc5ݛ\xcat\x9eV6\x9fZ\xe5u\xd4\xd3@#M8C\xb0]\xbb\x93\xc6\xfa\x90t#[\xbb\xfcY\xed\xdf\xf9?\x8f\xb2\x905\xace\x1d\xeb\xd9\xc0&Nj\xff!f\\\x1e\x11\x92\xcf\xf8ۮ\xfe\xf0\xad\xcd8\x1f\x92\xe1\x8a\x1f\t2\xb1Ν)\x9cusy&Ś}\xc9\x19Sߙ3\x97\xee\x8cx\xabwfTl%\x14\x9aŒ B\xfa\xf7\xf9^u\u007f\x1a\x12\xd6\xfa\xe3z<\xe1\x1ed\xab\xcb%\x8f|\n\xf5)\x92y1%\x8eK\xc52\xb1\\\xac\x10\xab\xc4j\xfdk\x98\xc5l\xe6P\xab\xae\x8ez\x1ah\xa4\t3\x90\xf2\x19ڥ\xcdn\xf6\xb0\x97}짙\x16Zi\xa3\x9d\x0e>p\xfeN\xb1\x8bnz襟\x01u\x83\f1̈\xf1\x0fr\xcc\xef\xe3\xae뤹\xf9\x90H\x90n\xd62\xf9c\xaa\xd9V?3\x9cӫ\xd7\xfbᄕ\x9a\xad\xec\xaa7\x1cEʋ)\xf1\xbbT,\x13\xcb\xc5\n\xaa\xa8u\\G=\r4\xd2\xc4\x02גj\xe5\xef\xd2f7{\xd8\xcb>\xf6\x93\xecM\xf8\x81>\xc9ކ\x03\xea\x06\x19b\x98\xf8\x1c\xb9r\xef\xef\b\xe9f/\x13Oy\xc2\x13\x96\xad,\x97<\xf2)TVD1%\x8eK\xc52\xb1\\\x8c]\xb58\x87\xe4o\x86\xb7S>\xa1\xbb\xb4\xd9\xcd\x1e\xf6\xb2\x8f\xfd4\xd3B+m\xb4\xd3\xc1\a\xfat\x8a]t\xd3C/\xfd\f\xa8\x1bd\x88aN\xba\xd6\x0f\xf9mҷQ\xaeVy\xe4SB\x19\xa9\xeeS\xb3\xfa\x16Zi\xa3\x9d\x0e:颛\x1ez\xe9'\xfeF\xcbN\xb2\xea6%d\xf4\xb8^\xd9\xcas\xc9#\x9fB\x8a\xdc\xd9bJ\xfc.\x15\xcb\xc4r\xb1\xc2\xfcV\x89\xb3\xf4\x9d\xado\xec\xc9UWG=\r4҄'7\xf5*\xd4f7{\xd8\xcb>\xf6\xd3L\v\xad\xb4\xd1N\a\x1f8W\xa7\xd8E7=\xf4\xd2π\xbaA\x86\x18fDn\a\x89\xaf\xc6I\xe1j\xac\xf6Ϊa6#\x1c$~\xe7ƅw\xaePi|6G\x05\x11#\x8e\x8dׅ\xe5i\xe1\x98\xd9\xceT\xc8.v\xb3\x87\xbd\xecc?\xe1\xf8\xe1}\xa94~<\xb7˥\x05W\x94\xa6\x85\xe7+\xb9\xb4>\x93\xad\xc30\x9fq\xe1\xf7\xb5\xe0\x8ao\xe3\xc4\xd8JkN\xbej\\\u07ff\x82W\x82?\x85\xfb\x8e\xc9\xde\x1e\xe9\xd6Я\xf9\xbd+\xbf\x91}\xc8\f;\xb5\x02e\x95T+\xab\x11g\x8b7\xfb\xb5\x1c\xd1\xe7 \xf1}\xca_\xae\x97\x95\xdc\xe7\xdeRf\xb5w \xbb+\xe7\xec\x8d+\xb2;s3\xd9\xc9\xec\xa2̾\x92\xd9E\x99]\xb8\xf9\xac\xf49ȕsf]\xf2 duB\x1e2\vN\x8b\x1f\x8aY\xe1\xbaNK|C\x86;\xbf\x1bٷe\x84#e\\\xba\xb6\xf0\x9a\x16\xb28y\xbe\xe1\bYIG\xa8g!\x8bX~\x03#\x85;Є\xa7e\xe1\r\xf4\x9e\x90r\x1f\xf1\x8b -z&a\xef\xf7\xaf\xe8\xb7\xc1\xe3)\xf6\x80O\x8b\xf3I\xb1;I\xb2K\xbf\xf5]f\xea\x91\u007fpծ\xe9\xbf\xcc ;\xf5HIߩޛL\x92[DL\xb3f'\x9b\xc9p\xe7\x9a\xf8\xcd3c\xc3\xcez\xf8\xaao\xdf\x13\x9e\x96)\xdaM\x8d\xfd\xd51#\xe1[X`\xf5\xdf\xc0\xf70|\xb3\\\xf9]\x9co\xbc\x87\xe1\xdb\xe8\xfb\xe6\x8cc\xe3_\x8f\xf0\xcb1\xda{dC\xacnL\xf8\xebC\xc69z!\x88P\xe0\xf9vŁ\x1d\x99\xd6\xe1/\xf5\xc3A\x84_\x87c\xa6;\xca\xe4\xf7\xd1\xce \xd7\xca\xcd#\x1f_\xaa\xa0P,\xa1\x8cJ\u007f\x996\x8b-\xb4\xd2F;\x1dt\xd2E7=\xf4ҏ|\x83Sѥ\xb1\x9c?\x12?\xb5\x1a&$\x9c9\xfc&\xe2\xef\x1d\xf3\xf2\xb7;\xf1\x1f\x87\xbb\xfd߆\xef\x00\xe8\x11\x93\xb0\x00\x01\x00\x00\x00\n\x00\n\x00\n\x00\x00\x00\x03\x04c\x02\xbc\x00\x05\x00\b\x05\x9a\x053\x00\x00\x01\x1e\x05\x9a\x053\x00\x00\x03\xd0\x00f\x01\xfc\x00\x00\x02\v\b\x06\x03\b\x04\x02\x02\x04\xe0\x00\x02\xef@\x00 [\x00\x00\x00(\x00\x00\x00\x001ASC\x00 \x00 D\x06\x1f\xfe\x14\x00\x84\as\x01\xec \x00\x01\x9f\x00\x00\x00\x00\x04^\x05\xb6\x00\x00\x00 \x00\x02x\x01c```\x02bf \x16\x01\x92\x8c`\x9a\x85\xa1\x02HK1\b\x00E\xb8\x18\xea\x18\xfe3\x1a2\x1dc\xba\xc5tGADAJANAI\xc1J\xc1\xe5\xff\u007f\xa0\x1a\x05\x86\x05p9a\x05\t\x05\x19\xa0\x9c%H\xee\xff\xe3\xff\x87\xfeO\xfc\xfb\xf7\ufaff/\x1fl~\xb0\xe1\xc1\xfa\ak\x1eL{\xd0\v\xb4\x01'\x00\x00;\x03#\xf4\x00\x00x\x01\xad\x8a#`.P\x18@\xcfw\x9fm[\xe5iF[\x1e\xd3V\xff\xb8\x1ef\xf4\xb9\xcd6\xdal\xdb*C\x99m[m\xf69\xf7~\xbe\xfd\x92\aX\xdd\xca\xe2\r\xf6\xb7\xb2n5\xb2\x1fnjx\x85+lO\xc0\xfe\xb8\xf5\x92k\xe5.\x00\xa2#\xc6\xf2E\xee\x88\x05\x00\x8b\xf4\xd1I=i\x14\xcb\x1f\xa2v\xb5\xdd\x15\x10C1\x11-\xf9&/E\xb1\x84\x1f\x009\xbb:\uee8f\xbd;<\x01D\xb1\xc6\f\xfdtr\f\xf2\x9e\x19Bi\x97\x17\xfbf\x0fI\xa2\x1d_\xb2\xf7\xcd4\xe2.oX\xa4W\xace\x89\"j8\x9en\xb9\xbb\x17i\x12@^\x02\xb0\xa1\xb2h\xbe\x11\x00\xf80\xc4\x1a5*\x82$2\xc5\x02\xc4\x19?\x86H\"\x16\xf0#\x89sp+K\x99\xf1r{\x9a)\xba\xa8\x03*H!\x8a\x00\x9c\x00\xc4H=\x95vP:rW\xfd\x92\x01\xc9\x12\xe8c\x9a\xf6[Y[\x05\xa4\xa9\x8d\xed\x05b$\x9a\x11l\b!m\a\x1aUO\x85\x00\x00\x00x\x01uSG\x93\xdbF\x13\x1d\x80Q\x19Td\x15\xbe\xcf\x1e\xb8\xc5U\"\x95s\x84I\fDZ\x91AU\x039\x01\x9b\x8a\xeb\xd3^\x9cӞ\x1cf\xe5\xffҐ/\\\x9f\xf4\a\xfc\x1b\x1c\x8f\xde\xe3\xfa*\xf7\f\x01e1\xf6{\xdd\xfdf\xe6\xf5\xc0\x17\x91\xbc7\x1a\x0e\xfaw\xefܾu\xf3\xc6;\xbd\xee\xf5P\x04\x9d\xf6\xdb\xfe\xb5\xabW._\xbax\xe1\xfc\xb9\xb3gN\x1c?v\xb4\xd5U.\xbeP\xe9\x9bJJ\xe5\xb4\xe5\xf0\xcb\xecr\xab\xc9\x05p\xfc5\x00>\xb1\xee\xf7%\xc5?\x05\x10q\\7\xf1-\x1dS\x87\x01\xdb\bx\x1eupQ\x1f\a\x1c\xad\x98\v\f?\x19+\x11\a\xa4\x97n\xd9܁\xce\xc2\xe6V\x93\xa5\x9b\xb7P\xb8\x85\"<\b˩u\xf0\xaae\x02\xfb\xa0\xb8\x98ڬ\xbaM/\x8b\x85\x86H\xe6\xf1n_\x8a\xc0\xf5\xbc\xa8\xd5\xec\xe1v\bL\x8au\x8c$\x96;X1\x92|Io\x9d\xad\xf2\xb4\xf9H=\x988l6>\xb2u\x1e\xe6\x93\xf7$\x16\x12\xeaU\x05\xa1\xd4\xf7X;\x82\x87 \xc0C_\xfc]\xa7\x93/`\x13\x02\x81G\xb4\xea\x8d\xc1\x93un<]\xd2\xc2R\xc3\x01\xae6\x18\x1d\a\xd6\xffy\x9eI2\xa6\xdcp6\x98\x0eC\xb2W\xa9\x10x\xa8b\x95L\x1e\xaf\xcc\x02w@\xa5[\xb7\xaaeA\x0e\xb3\xbb\x12-\xe2\u007fYu1|\x10\xa1\x13\x8f\xad\x8b\xd9a\xc3\xc1\r\xdc\xd5\u007fW\xa2\xdd\b\xf98!\x86>\xd7\xc0;\xefz\xb5(\xaf\xb9\xfb\xba4##\xb0\xac=\xf5<}\xf0Չ\xcff\t\xe0J_N1g\xb3\xeeC\xe6\x1f;\x12\xa1\x1d\xeḅ<\xb3\xe7\x9eά\xe4\x99'\xed1xd\xc6P*,6z\xf3 \xc8\xe3\xd5\x04Wf\x91'\x1f\xe9Q\x80\x83\xdb\xffu=P;k\xfc±\xc8\xd4r,P\xed\x12\xc7\xd2\f\x96u׳\rtSt\x8br4\xa0`\xfa\xb7\xee*J\xd4v\xf2\v@2ZG\x80\x88\xb3\xcf'\xe3:\t\xf0V\x13\xbbG\xa6\xa3\x1fI\xf4\x03\n\xfc$\x9b\x91H\x8f\x1f\xa3\x8e$\xa6\x11-\x05f|x\f\x96q7\xb4\xf3y\x9am\x89\xa5\xa14-Y\x1b\xee\xee \x8b\xe7\xb2.<&\x02\"\x81\v\x15\x9b@\xc4Z\v\xfar\x8d\x9dz\xfc{z\x9a\xbb?\x9fb\xa7Y\x14\xe8\xe2\xbd\x1d\x89\x85\x19\xa1\xe4\xfc\"\xbe\x19\xbb\xf3\xc8\xe3E.]\x0f\xfd\x88\x06\x1c\x81\\\x88\xf0\x90q\xe8\xd0ﴜgVD\xbb3\x927\x86p\xa3\u007f_\x9e7\x1b\xc9\x13$G\x1e\x8b\x17d@\xbaS\x19\xbarXmT\xb9\xb4\xddBD\x85\x0e\x11<\xa4\x00ڗ\xe9\x17+\x8d*}\x1d,OY:\x11%\xb8\xb4\\\x96W\xd36\xf0\x10\x17\vAVG\xf8yQ\x02\x85F\xa7\x9b\xab\x955$\x9dN\xd7\xf5\"o\xfaj5mJ\xf3la\xea\xa8jS\xbbY\x8ab\x8eE\xe2l\x92є\xf1\xb2\xae\xef<\x97\xb0\x00\x11\x8c9\xfaw%\x9d\xcd\xd8c\\\xce\xcc0\x9eg\xb3\x1a=\x87r\xb3\fB\xe6QZ\x83\xdcL\f\x8f\x10z\x06_'\xfc\f쾐\xee\xe5i\xae\xaapc\xa8\xb48d\x82\x8cv\xdeC\xa6\xaf\xb0\u007f\xbe\xe6j\xca\xd2\xcf3\x84\tp\x87\x87\xd3\xe7Y\xa5\xbe\xaf\x9f\xe5\xf1E-\x02\xbdy\x05Cy\xd9T\xdf\x18ȯ\xdd/(\xc0\x9d\xec\x86uc\xd4n5S\x9b\xb5S\xb0~觾\xf5\xc3\xf0\xbe\\s\x18\xe3?\x8c\xe4C۲;q;J\xf7SN\xaeq\xc6|\xc3ښ%\xd2\x00\xae\x81V\x1a\x10\xa8\x9azw\xcdgl\xc5d\x8b\x860xnb1\xc3Us\xcebs\x13{\xca99g\x13W\x9cr\xbe\xe1\xf4\x8b\xa6T\x1f\x93\xc7\x12\xb8\xe0\xf3z>_Ec\x15G\x81v{/9B\x1f\v-\xb8J\xee\xc0\xd5Բ\xcb[q3,\xb4q\v\xb45\u007fM\xf3צ|Y\xf3\x15h\xa3\xb5\xd7j5\xbfP\x8e\x80\x8dz\xeb?v\xf0\xf3\xb7\x00\x00\x00\x00\x02\x00\b\x00\x02\xff\xff\x00\x03x\x01\x8c|\t|\x13\u05f5\xf7=\xf7\u03a2}_-k\xb3,ɶlK\x96l˲\xb1%\xc0\x1b\xc66`\xc0\x80A\xb6\x03f\xdf\xf7\x10\x02&%)%ib\xb2\xbc\xecih^K\b\xd9H\x9aRڤMҬ4_B\xf3\xe5\xa5)\x8f\x97\x12\xd2f\xe1e\xa3yiK\x88=|\xf7\x8elL\xc8믟d͌F\xd6\xe8\xec\xe7\u007fϹw\x10F\x9b/|\x00o\xf1G\x10Av\x94\xcdL:\xec|\xcfy\xd6I\xbcΨ3\xed\x1ct\xde\xef\xe4\xdf!\xf02\x81\xad\x04ld\t\xc1\x84\x00\xd1\xdd\xfeG\f\xc70\\\x89a9\x06\xec\xe3\x00q\xfd\x1c\xe6@y\x8f\x19\x13\x94\x8e\x8e\xbc\x01\xd1\xec\xfaD\x96>>\x8bgs\x8f\x8a\x18\x04\x88\x1f*\x1bp\"\xee\xc1V\x8b\x0e\a\n\xca1\xec\xff*\x01=\xd2\x03\xaeX&\x18H\xc7\xf2\xf3c\xe9@0\x13s\xc1\a\xe4\xcd\xf3'\xa2\x13\x8bL\xa6\xa2\x89ѲL\x89\xd9\\\x92A\bat\x1d9@&\xc9\xf4\x8a(\x9a\xc9\xe3\xee\x14Ee\x9f\x12Н\x88\x18\bV\x10\x01\x01}\x88\x84ґHD\xb3\t\x88\xf6fOgM\xa9\xe8iJ\x01\xa1$\xd0\x17\xdc[\xfcR1\xfe5\xdd\xf0GF\xceb\x03{\xb1k\x97!\xc4I\xfcSȅ\xbc\xe8\xf5\xcc.B\xf2\xf2\x1c6\x8f[\xe4\x04\xbb]p\"\xe08\xfe\x89\xaf\x1c0\xdd\x01\x0e\x87\xdf\xea\xcdWpJ\x8bŤש9\xf3\x13\xaa~\x03\x18\f\x1a\x8d\xf2\x1b\x15\xf4\xab֩\xb0*\x1fZ\xb5\xa0}L\xa3\xe04^\xf4\x11\xfd\xf41\xa3\xd3j\xfcJ\x03\x1fh\xe0\x16\r\xcc\xd2@L\x93\xd1\xe0O\x8c\xdf\x18\xf1}F\x98o\x84\xb4q\x9a\x11k\x8c\x1a#o7\v*\x1eE\x13\xe9\x84)\x95\x8aF\xb3c\x0fc\"a8\x93\x8d\xc7\xc7\xf6@?\xa2lRF\r\x9f\xdaSq\xa3ɞ2\xd27Y\xf6&1\xfa.\x11\xa5\x9c\x8b~+\xe5\xdc\x1c`\xaf*\u007f\x92\xbe\xcc\t\x92`/+\x9fH\x06\xc8GQ\xc0\xd2;\x1d\xff\xd5\xf9\xdb\xf6w\xda>\akt\x18\xfc\x1d';^\xee|\xa7\xf3\xec\x88\xfb\xc5苤\xfd\xe37\xa4\x15p;{\xbd\xf1\xf1\x9bp\x87\xb4\x9c\xbd\xde\xfc\xf8cD\x1f\x04-\xbc\xb0\x97\v\n\x16\x94@\x13\xd0d\xf4F\xa69T\x9e\td\xbe\xc9@:\x03\x99Z\xc1\xfc\x95\b\x1f\x8a\x80D\x83\x18\x13\x89(zԕ\xae\xcaH\xe5\xf6\xca\x1b*\xf9\xcab=\x9a\x860j\xe2\xea-\xf5\x85\xf5\x9b꯭\xe7\xebu\xfb<\xfb=\xd8s\xf4\xc2s\x99\xb4Z\xdf\xea)+\x9981\x94\xb2X\xca3G2\xb0\x97^\xd3\x1c\nft\x96\xd6`0\xe0\veB\xd3C\xbbB\x87C\xbc>\x04\xa1Z\x9d٬\x98.\xf6\x8bX\x14\xf9\x8c\x02\x14(\xcadDegd;v\x1c1\x9aR\xb20\x8d\xf2\x93\x1aI$\x9b8\x965\xfc)\x1b?\x965\xa6\xe8?ѷ\xf1\x04\xfb\x17ñ8\x15\x1f5W\xd1\x1a\xa8*'a\xa3\x87؍\xa1p9\x84\xcbI\x15\xb5\xe6dU\xc2\xea\x01\xbbHO\x18\xed\x1e\xc2W\x96㰵\x01\xc0b\xb3W\xe9\x80\vV-\xbfw\xc9\xdeI\xeb\x16\u038b\x94,Z\xb1\xbaR\xa1T\x18\x84\xf9?\x98_\x96Zqg\xdf\xde9G\x06\x16O\xbc\xb1!\xdcwSo\xb9\xf4\x966?\u2b6b\x91\xee\xa8LU'\xe1\xd8\xd4]\xfd\r\xaa\u05cfa\x8d\xcdk\x06\x855\xe4\xb5\x11h \x1c\x11p\xa0\xbdwC\xeb\xd4\xed\xf3S\xca\xfb\xee\xe0\x8b\"\xff\x9dW\xccK'u\x81\xb6\x05\x1b\xba\x1c\x85\x1e\xa7nX9\x01\xdf\x14M[\xcf\b1\xa6\x1b\x1e\xa5/|.\xbcο\x86Tȃ\n\xa8\x95OD\xf7\xfd\xbc¡Զ\xa6\x8f^\xf88\xa3\xa3\a\xb5\xec]\x15\xdb\xe4\xb3S\x85\xf4\xc0\xc1ޙ\x8b\xe8Fǎ\x02O\xd5\xfc\xbc|^\x01\x14x\xbb}\xe0\xf3{\r\xfe\xa3\x17\xcef\x02^\xbf\xd7o-~\xaa|\xb2\xf0\x94\xfa\xe7\xe8)k\xe4H\xfd/BG\x12\xce\x02\xc1O\x8e\x18~\xa18\x92'\xa2\xf4\x99\xe13\xd14\xdbB\x94n\f\u007f:#\x1buM\x8d1\x91\xa5\u007f\x86\xe13\x151\x14\x81H\x04E\xe8\x96m,\xb6D\xbc\xba\xaa2P 𣁢j4p(!\x00\xe6\u007f\xfe1ԕ\xc5+\xa2\xfd\xf12x\xba\xb8\xfb\xda\xf9\xb3\xaf\x99SZ4\xfb{=ݻ\xe7\x96\xe1{\x86\xdf\xfe\x82D\xd6}\xfb\x1fv\xd1\u007f\x98\x95\xfb\a\x0e\r\xee\xb8z\xd7\xf9\xed;V=\xb2%\x9d\xde\xf2ȪU\x87674l>\x04\xad\xe7\xdb\xf8#\xc3x\xecӇ٧\x0f\x8f}\xcal\xbf\xeb§\xfc\vT\xbee(\x85\xa6\xa0\xfb3\x13\xc3-Eŵ{kqm\xab\xa7%PH\xaf\x1cj\xfc~#nl\xb5\xe0ꤞ\xb3\xc0W\x1e\xf8\xbd\xe7=\x0f\xae\xf3L\xf5`\xceb\xb1\xe0\"\x8f\xc5\xe2)\"*5\xb8\x00\x03bf_\xa0Դ\"4\xb5\xbcE%\u007f+\rig8\x9c(+\f\xb4p\xe0\xe4\x12\xbc\r\xac(\x9dN\xa4\xa9\x97\xd3\xf0f\x94ݝ\n\xd8H\xcd;\x91\xa5\xc7\xd1\\\xdc`\xa7i\xac0\xca[\x03\x15u1\x84u0&\xb4$\xb3\xe2\xf1\x98,B\x03$\xfd:\"\x06-\x1ez\x92ڡ\x0e\x80\x9at\xa0@\x87\xadԤ\x1b\x80\u007fA\xdas\xc5\xf4\xe77]\xf9\xc2\r\xed\xc1\xc6+&\x84\xaa|\xba\xfa\xd5w\xf4t\u07fc|BY\xfb\xd2:\xe0\x02Uf\xa8\xd7\x17W7G\x86?\xf1\xa5:\xca&,\x89H\xaf%j\xa0\xd5\xe2/\xb6\xdaʂ\xf6\xf2\xa2\x8ei\x03ɉ\x13Vߕ\xedضpza\xf9\xec\x993\xcb:\xb7\xcf)/\x99\xb1\xb5\xbdi\xe3®\x80\xf4դ[\xdaR;\aw\xa6\xae+m\x8e\xe5y\U000e1b64\x1f\xef\xf0F\xf2\xb5\x1aW\x99\xbfrAy\xe9\\\x04,\x17\xc0\xbdr.\x88e\xc6\x00\xbc\"\xdd\fkO\xc2\x1a間x\xe0$\xac\x97\x86NJ\xb71ڥs\xf0\x1a\xfa\x1ciPwf\xe2~\xdda\xdd{\xba\xb3:.\xaaK\xeb\xf0\xc3\xea\xa7\xd5x\x82\xba]\x8d\xb7\xa9\xf7\xaa\xefRS\x0f\xc9S\x97\xa8\x89\x1a\xdd\x11\xe2\xab\xf9f\x9e\xf0Q!-L\x13\x88\xc0߫\xceP\x97Q\xa3h\xe43\xcagb\x14{0v\x83:\u0084\x9a\xf4\xc3k\xbap4\xe1\xfc/\xbd\xdbeS\x80\xe3\x9dꫮ\xde^S\u007f\xed\xee\x1dU\xb2~\xe0 \xee\xc6\x0f \x82\xf22Z\xcc{y\u0605\x00\xa1\xa7\xe1\xd74\x86\xb1Tkx\x83j\xbf\xcao\xad\xc7N8x\xfe\xc1~\xfe\b\vb\b\xd3\\\xf0)W̿N\xe9\xf5#OF\x9f\xb7\x06\x05,kT\xeeMzѾ\x89wR>\x19\xa7,F3[\xc4r\xa0\x00\x1a)L\r@\x89\x06\x13?\x1a{C,\"p\xc5\xc9m\xcf\xee\x19|\xe6\xea\xfa䕿ٳ\xf3\x99\x9d\r#m\xdd+\x96͟\xbf\xacjJ\xb9\x05\x9f|B\xfa\xef\xdf.Y\xf2[\xb0=\xf1\x048~\xbbt\xc9o\xa5\x8f\x9fpec۾\xb7\xafO\xaeZ4\xaf0\xb0`\xd1@\xd9\xf7\u007f\xb8w(\xaf.Uiޞ$\x9c\xa4p\xf8f\x93\x03\xb5\xeb\x1e\\\xb5\xf5\u05fb\x9bUf\x8f\x05Ԗ\x80S\xbfv۪\xf5\x82J-L\xc2g\xa4\xf3\xd2i\x91\x19\x1bF픿\b\xff\n*\xa6\xd6֑\x89\xf2\x05\xa0*\x02\x15\x01\xbf\x16\xb4\xdadt\x8d\xafޚ\\3\x9d\xc6*\x8c⛭\xd8era\x97K,\xd9\x14t\x1a6\x8b\f\xbf\xe5\x18J\xb0\x18x&>\xca\x133\xb1\xac\x99\n\x9e\x99\x8e\xddZ\x0e2\v\x89q?a\f\xf2\xe3VƲe\xbb\xbdn\xf1='n\xb6\xc6\x131shvd\xcfcK\"\xbcƒj\xef\xab\xeb\xbbsy\xcd\xe4\xab\x0f\xafY\xf5\xcc\r\xd3`\xb8xr\x85k\xde\xfc\xf2\xf6j\xb731\x15\xaf^\xfe\xfbg\u007f\xb2\xad\t\x13\x81\xfb\x87R=\xf9ʃK\n\xd3ey\r[\x1eY\xbb\xf5\x99\xddM\x1d?\xfa\xeb\a\xdet\xff\xa4\x95\xdb\vk[\x83\x89\xc53\xe2\xb2\u007f%e\xbf\u007f\n\t\xa8,\x93\xa7\x06\x00N1\x88\x87\xf0\xfd\x98`N\x00\x01\xf1x#\xc9E\x18\xe6=\x89l\x94\x06\xf78e*\x01T\aV8\x8e\x15#\xfd\xe4\xec\xc8'\xdca\xe0~\xfb\r\x93#A\xadT\x8e~\xfe\x18\n\xa3Z\x1aW\xeeͬ\xe0\x8d\xa0\xb2\x03O@\x15\x04\x85\aD\x15\xa8<\x01E\x1b\xb4\xad\x19\xe6\xe1\f\x0f/\xf3\u007f\xe01\xcf+b\x10[\xa3\x9f.f \xb3\xe6\xfd\xc0\x97\x01|,p\x82\xb9\xb3X\x04Ek\xa6;\xfb\x9d\xd8\xe9Q\xa9\xaa\xa1\xfa\x93\xa6o\x9apeSc\xd3@\x13\x11\x9b\xa0\xa9\t\x95m\xb18붠\x9c\x12r#\x93c\xd9T\x8a)\"\x9b`\xaeO\a-\xf2\x11;\xa4\xb0:\x9b\xd3K\x16\xc6\x00\x9d\x87\xb0\xddep\x8f\xe4\xd0\x1d\v\x05Q\xd0\x11\xf3\xa8z\xec\r\x84\x9d\xc2U\xf3\x1f\xb8\xaa\xb5\xa8\xb9\xbf\xa6vmwe\xeb\xf7~\xbez\xcbѝ\x13Kۗ\xd4Vt\xa7\x83S\xae~`K^\xa2=\xbe|Y \xd9\x1c\xb0\x95\xb6&\xf2_\xf7\xa7\x8a\x1d\xceH\xca等\xe49Jj\xf9c\xa5s\xaf\x9d7i\xe5\x9c\xc6|o{ߖ)\xf3o[VS=p\xcb\xfc\xa9W\xf6\xb7\xe5{\xa7\xcc[5q\xce\xeey\xa5\xdff\xba\xcd\x1d?\xfa\xf2\x03O\xfa\x8aI+\xae\xbe\xc4t\xc7j2\xdcFdFa\x8al\xbd\x87\x8b\xdf+>[L\xa2\xc5\xe9b\xecQ\xddn\x19\xc3\x15\x81{\x9c\x86\x1c\xb6Hˀ\x87\xe1\x9d\xef\xe2\v\xe3\xbf\xc2\x1b\xff\n\u007f\x90\xa5\x17OL,6\x9b\x8b'2\x1ae\xcc\xc6h\xd4 \x1fZ\x99\x99\xfe\xb0\xe6i\r\x9e\xa0i\xd7\xe0m\x9a\xbd\x9a\xbb4D\xa5\xc9Ӕh\x88\x06\xf6\x17\x1c.x\xaf\xe0l\x01\x17-H\x17`\xfb\xedHk\xd0\xf6k\x89\x16\xee\b\t\xd5B\xb3@\x84\xa8\x98\x16\xa7\x89D̿\xc7d\x10\xee\xd50\x10\xa7a .=\n\xe3d\xe0dL\x8cB9\xfb(\x96#\x97\xf1\x05\xcf\xe8/\xc1v\xd1˹\xfa\xdc>\x8e\xf5\xf0\xc9\xcb9b\x98\xf65\xce\xc0#$\xa0\xfe\xcc\xc4J\xbe\x91\x1f\xe07\xf1\x9c\xc0\xdbxL\x90\x020\xf0<\xa9\x12\x9a\x04l\x13B\x02\x16\xa2rŢ\x96k㰓+\xe6XN#\xe0#1\x92!\x87\tG\x10\xab\u007fQ\x8a\xa3Y\xaa\x13S\xaa\x9e\xa2bpDi\x84a\xb6\x17\x84*\xb0*\x813\f\xb7\x92\xa3#;\xf0\xee\xd7\xe0\xc8\x10\x9c=+\xbd }$\xcbv.<@\xf15\x92k\\\xf1\x8cG\xc4J\x0eM\xe1\x80{t\x1fOɘ\x82\x00=\xea\x83\f\xec\x82\xf7\x80\x03$\x87\x02\xb6\x81h\x82\xfd\x82\x8cP\x81\xbe撣\xec\x17H\xf8\xc4\ti\xfbɓ\b\xa1\xcb\xf9\xac\x83\xa9\xb0\x12\xb6\x03\xa7\x04'`^A\x04\x9e\x03 U\xa8\ta\x1b\n!\x8c\xa2\x14Zc\xa1\x16\xb7a\xec\xc4\xc5\x18\v\xf8\x9f\xf0\x99\x18瓑\xc1\xf84+\x19\xa30\x17\xef\x1e\xd9\xc1H\x81_\x81\x13\xea\xcfJ\x86!\xa9M\xe6\xd3y\xe1\x03\xb2\x99b\xa00\xaa\xa38\xc0C\xf2\xccy8~{QQ\x18!\x93\xba5\xbfޤ\xb6\xa3\xb2{\x926\xa3\xae\xc3\xce!\x8av\xd2#o\x18i죎L\xfd\xf8X\"\xfa\x19\xfd\x19\x90\xe3\xda\xff\x06\xd4D\x1dq\xc3w*\x8e\nk\xa41VR[h\xe8\x98\xd6t\xff\xec%\x91ū\xd7U/\xf9Ɇ\xfa\xfc\xe4\xccdI\x95G-\xbd\xe3\x8aM\x1c7\x9d\x89\xac\x18ٖ\x989\xb5%\xe4\xafL\xd4\x04Sm\xb5%\xef:\xcb\x02\x96\x8a\xde\xefw\xa7\x17͞V\xea\xaf(\x8d\xfa&H\x87.\x87\xed\x18-\xb9\xb0D\xd8K\xf3\xc5l\xb4\x04\xcdΔv\xf7Y\xc1jm+\x9b4\xa9\f\xf5\xa92\x01\b\x04\x92hY\xb2\xadh\x1e\x9f6L3`\x83\xa1)>ca^\xde\fnB\x13\x8a\x1eKS\x16\r\xc7(\xa3\xac\x16\x05\xd1\xe7\ro\xe4\x8e\r'\x9e\x8b\x1aY\x06~\x9eA\xf4Q qY\xf4\xb2\x13\xc1\xc2\"\xfaX\xf8\x1aK\xc0r\xa4\xb32T_\x18\xc2Ur\xf2\xe6l&\xc8Ź4䒸\xb0\xb7l\xc1\r\xbd\xa1\xc95\xc5j\xbd9\xde0\xb5t\xe65s\xcb\x1b6\x1dXZ\xd6?\xaf3ߊ\xb5V\x97>PY`j\xdf\xf3\xab\xd5w\x9d\xb9oF\xf3\xf5\xbf\xbb\xb6f\xcb\xe6\xf5\x89\xde\x0f\xbe\xf7s\xe9\xcf/.\xab^r\xeb3\xef_\xf3#P=\xbfj\xb8\xa1=]R\x1f\xb6\x12\x8d\xa5:\xbf\xae+a\xc7g\xfb~u\xeb|s \x9a\xef\x8c\x16ZS\xcbo\x9d\xb7\xf4\x91\x1dM*\x83E%em%BI\xb5WS\xbd`{ӮW\xafo]\xf2\x8b\xf3\xffv\xfd\xdb7O\xd59\n,ϕT,\xfd-8\x9f\xbc\xfa\xcf/ܿ\xbe~\xf1\xaf\xa5/\xa5\xff\\\xb7(ܲ\xa8\xfe]^\x88w-\x97\xed\x89>\xf8\x04\xff\f\x12\x91\rݘ\xd1\bB.\xc2l\x11x\x81\x15\xab\xd2VG\xab\x92/\xe6S\xfc\x14~\x1b\xcf\xf3\x06\xfa\x1eۈ\x02\x90\xc0\xebt\x9c\x129\xd95h\xe1p\x9d\xb8O\xe4D\x83(:\x06\x1d`p\x80@l\x04\x13\x8e]C\xc7\xe1>\x9bͥ\x03\x1dO8dB\xe9x:\x91MEiz\xa9a\x01\x8a\x8d\xbd\xa8+\x98hݕUn\xe9_E,\x92\x8dd\xfd\x84>\xc1\xaf\x04BK\x88:^$\xe4\xfc\x80\xa4X,\xbd\x02'\xa9\xfb\xbd+\xb9z\x05\x87\xcb\xc1sZ\xa3ɠ%YX \xfd\x84\u007f\xe6|#\ue1cc\xa9k\xd1@\xc06ar\xe3\x84r\xb5\xf4\x9c\x8cC\x8e\"\xc4\x1d\xa1xՎ\nP\x14mɴ\vf\bq\xc0s\x10R\x83\xba\xd0\xe9\x01O\x8f 8K\xa0\xa4Ǹ\xb4\xc2[\x01\x8e\xc2\x1e\xfb@L\x1d\xe8\u007f\xda\x01\x0f9\xe0\a\x0eX\xea\x80v\a\xd49\xc0\xe9\x00\x05\xabg\xa3\xc2>\x9b\xa9\xa0\x0f\x91\x8bpo\xf4A\xe1\x91\xe1\xefg\xe2Y\xba\xa1a\x86\x81\x8cQ\xe8\xe7\x1fs3\xeb(\xc6\xf0\xc3\xe8\xa0\"\x02N\xb8hyG\xe1\x91\xeb\u007f\xff\xc3\x16O\xed\xcc\xead_Kq\xf3\ue9f7\x8c\fC\xf5\x8fk;c\xd6\xe5\x1bn=R?3a\xb3Uug\xf8#\xe197.mXՕT\xaa4eӮ\xec\x1e\xb8ou-\xd9h-I\x97̚2\xf2\x92\xb4;\xbf\xb2\xad\xacxr,/\x17ǶRL\xf0&\x8d\x1d\x014)\x13Y\xac\x05\x95?\xcf\xdf\xe3_\xe1\xff\x95\x9f\xf7\xe9\x96\x06\x0f\a\xe1\xfe \xf4\a\xc1\x1b\x04~\xbecI!\xeass\xaa>\xb3Yf\x8fq\xc4x3|H\xc3%\xa2`\xe0\xa2{T'\x8d:26\xe6\xad.L\xc4m\xac\xb4K\x82\xf3\xf6fc\x91y7\xf4\xcf\xfb\xd9\xf2X\xef¥u\x0f\x9d{\xa0\xb3\xe71@\aV\x9f\x1c\x18\xe8:\xc0\x1dk\xba\xe6\x97\x1b7\x1f\xd91\xb1((\r[\xf2M\xcaU/\x82\xe5\xa1\x03`}um26\x1c\xaee\xf6\xc9\xf4\xc6\a\xa8\xde4Ts\xa1\x8cUۃ\x96\xd2.\vXz\x84\x01\x87\xb9O$\x96>\xde4N\x1f}\xc9cp\x81\vPH\xca\xfc\x94\x97\x87nFJ%\x1fX\xfc\xac\xf4\xc9\x13\xd2 <\xf2c G\x17I/Wd\xf7\xcc\x19\xda\xf0\xe2K\xf8\xcc/\xa53O\xf7\xf2G\x16\xfc\\\xfa\xea\xe8UOl\xaa\x19n=\x8b@\xb6\x19L\u007f[\x8d\x9a3%\n\x0e-\xd5\n\xaa\x1e\x18\xd0(\xfb\x1e\x17\xe0\a\x02\xb4\vP'\x80S\x00\x05+\x17\xa9\xf8>LT}\xc8\xf4mk\x90\t\x8bS\xa9\xc5\xd9\xf0\x98Uur/\x0e\x0f\x1f!m#\x9f\xc2\x17\x92\x11[\xf8#\x92\xf4\xac$\xedA(\xf7\xbbD\xa2\xbf\xab\xa4\xe3\xdcZ\xc5\xfd\x04\xc8R\xb5W=\xa4~\\M\x04\xb4\x00\x06Tʾ\xfb\x85Džg\x85S\x02G\xc9X{9%\x88Q\xc2\xe8\x18#c\x9c\x0e\xaa\xbe\xec\xb7\b\x813\xd2q\xd2<\xf2\t\xd0\x1cÈ\xd83r\xd1V\xf8\xb0\x9cg\x1a3E\xe1\xb0&\xb8\xc0\xb2\x0f\x00\x8a\xfb\x8b\xd7\x15co1\xb8\xe7k\x96\x14\x85\xfb\"\b\x10*p\xf4\x19\xb9\x82>\xd1|\x91\xf3\x9c6\xe4\xe6\x00{\xcfjZ\xff\x9b\x8d\x04.\xb5!?\xfe3n\xac\xdd2xC\xe7OF\x1e\xed\x19x\x01\xdcG7\xbf\x97]\xda\xf8p\xef\x92\u007f\xdf\xd4\x10Yp\xf3\xe2I\xf3\x8b\xa4OHՈ\xca\xec\xb5j\xaex\x16\x9cG\x1f\x01\xdb\v\xcb\xe3E\xe7\n\xca[\xbewt\xfd\xaa\x9f}\xafU\xa3\x80\x8aQ\x19\xf2\x16Yw-\x99\x88\x12z\xd0b\xed\x90\x16\x90\x16\x14\\\x0f?\xa0!}?P@\xbb\x02\xea\x14\xe0T\x80\x82\xfd\xa9\x04\u008fF\xa7T\xf4R\x911\xab\x1a\x95\xd9ؓ\xb7H\xafIz\xe9wP\r\u007f\x85*\xbcud/\xad\x89\xbe\x80\xebs\xb2\x9b\x84\x10>)\xffv]&p\xbf\xf6\x82\x16k3ZCk\x8c\xfe\xb8Z\xad\xea\xe11~\x8e\x00Qr}\x02Q\x91\x9c\xc1\xc4\xe9\x1fCn\xc6\x04\xc5\xdb\t\x1a\xfd\xe4ZJ\xd2\x18\xa6\x98D4\xda\xf1ɑ\xa5O>\x89\xefz\xf2\xc9\xc3]\xe4ɮÇ\xbb\x86;\xbb\xd8o]HI]\xf0\xb9\\\x1f\xad\xcc\x04\x04\xa3͈9Nӣ_l\xf5Z\xd7Z\a\xadC\xd6ǭ\xbc\x96\x88\xa2]\f\x8bD\xecC\x88\x86\xa34P\x96NǩZ\xb2D.r\xe4\"\x0fe\x8c\x86$\"ui\x95\x80\x15\x1aKEW\x03\u007f\xc3\xf9\xc1\x9dG\xb6\u058c\xca\xd7$\xfb\xe5\x86Lg\no\xc3\xd8M@\xa9r\xaa\xa6\xa8\xe6\xa9dl\x94\x82)0\x0f\xb6\x81\xa0R\xaba\x80\xd9\t\xb0\xe2\x9bn9\x06^c\xd54ifk8z]\xc2\xf4\xa1-T\xd0\xdf\x00\xa6\x02\xa0\xe2\xc8\xf5\xf0\xb2L\xfc\xf6\xf8h\x86Ȳ\xce\x13\x8b\xa24\xf6\x14\x03\x88\xb2\"\x92\xe0\a\xde$\xed\x97\x0eobʸ\n:a\xaeT\x86\xfb\xbbG\xfeN\x15\x929\x0e\u007f\x1dY\x8d\x10\xbaėyT\x9cqF\xb947\xc4=\xceq\x9c\xe8\x15\x81\xf4\xa0\x01\x01\b\x19u\xdd\\ y\x83\x8dk\xe5\xdf8\n\xd5\xcc?\xbfi\xbb0z\x1d\xe1vz\x1d7\xba\xf1\xe7\x06\xd6\xe4\xd1\x1e\xbd\xf0^f\x1e=hqC\xb5\x1bD\xb7\xdd\x1dv\x93)\b\xea\x10\xb0\xf4X\x8c\x88\xcb\xf0\x03\xf3A\xf3931\x1bT\xb4?\x94u#\x9dNmZ읋\x8fb\x8c\xb5ժf\x15\x16UvUXET\xbd\xea\x01O>\xc6\x06\rQ\xeb̲\x13\xdb\xe3\xac+\x9b\xc8yT\x9c%\x15#K\x95g\"\xf4\r\x93\a{D\"~\b\xd8\xec4\xa90\x92A\x1e\x90\xcbu\xbf\xead\x00\b'=\xab\x148L\bH\x9f\xc0\x01\x98 \xc0t8 \x9d\x02L\x80#\n\xe9\x00\xb7}Aw\xb0\xb3\xbd9\u007fd.\xe5\xf3\x01\xae\xf7|\x1b~*oR[{\xe1\xac\xf9\xdf\xec\xb9(?\xfe\x069\u007f^\x97)\x05\x9f\xce\xd1\xca\xf4\x8bY\xef%\x02D\aSl`S\xd0Q\x88\xad\u05fe؉\x9c\x06\xa7\xcfy\x98v\xc5y'-\xfc\xfc\x8c\x1a\xbf\x86\x89\xc9J\xbfV\xadi\xd6`Nc\xd1\x14j\b\xd14+Ay\xf4¹\x8c\x81~U٫\x1apذ\x9a\xe8T\xb2\x1f2\xbe\x19\u05ccQ\xc6bv\xfc\x98y\x87\xcc0\x8c2\x1cb\xadE\xea\x977H[G\x9e\xa7\xccr\xde\xfc\xdd,\xf3\xddJ\xefe\xf0\x91\xbf}\xda\x03\xff8x\xf0\x1f?\x9eF\xf7\x0f\x1d\xfc\xfb\x8f\xa7\r\xfb\x8af\xee\x9c3opF8\x87AT\xb2\xdc=hgf\xba\xd2\x03J7\xf0z\xf0X\xf38\xaa\x02\xce\xdah\xc5V=\x9b\xcb\xe0F\xf9\x90o\xc8\xcf\xf7y}\x80T=\xea\x01\x9a\xa8\x9eF\xf0\x10\x82\x1f X\x8a\xa0]\x0e\v\n\x04\xa8_\xa9T)\xfa4ĭ\x92\xe3\x1dSљl\xeeaʡ\xc7Q`LU\x97\xa2\x03\xd5\\\xec\x1bW\x9c\x93\xb9\xb8\x8f\x93\xc1$\xf5\x03\xec\x05?\xc0糮Yw\xe5\xceiTq\xfb\xc0\t\x9a\xa2\xc9sbk\xe7\x86ZjLF\xe9\xa0\xf4\x02)[:\xb0l\xeeȶ\x91\xe3\xfc\x91\x13\xefN\xbd\xaa\xaf\xc5\xf1\xe4\x0f'l\xe8q\xe5\xe3\n\xd9\xc7{/|JN\U0002fc4el\xa6LE\xf2\b\xe6}V\x1f\xb6)!l^\x80\x96\xd6y\xeb\xd2u\xf7ב\xd8\x02\xa5{Im\xcc̗\xf5\x15\x15p\xba>%o\x96+\xb8\t\xa67Jz\x9c\xeaP:\x93@2\xe6\x85\xefV\xd2\xc3\xe3\xfdS\n\x02(*\x10\xe4B\x9a\x87\x90\x13\x93\xafyzۊ\x97f\xc5\x16.^\x1cOL)\xb3\x06\xd2\xf3\x92\xe5\x1b\x167N\xbc\xf2\xe1\x15U\x8b\x17\rT\r\x94u\xb6\xb5\x14v\xcet&\xe74L\xdd9?\x0e\xb7/\xbco\xed\x84R\x1a\x91\xec\x91\x02\x8b\xbd8UP\x9cNV\xba\x1d\x8d\x8b\x87\x06\x16\u07b3\xb2F\x97\x17r\xfe\xd5곩g6\x05S\xf1J\u007f\xd1\xccŻd^\x8b\x11\xe2\xea\xa8NEԐ\t?\xa7\xfc\xbd\x12\xafUBL\x99Qb\xbd\x12\xae\x95QT\x0f\x10\x92\xa1\x92\xc4 \x92>.\a\xa8N\xcbY:j8\x13\xcfA\x84qX\x80_\xa7\x90\xe0f\xce$\xdd\xc2=\x05X\x92\x10\xa0\xc7h\\\xd1\xc813\x96\xc9W*@Ɂ\xad\a\x16;\a\xa9\xb7b]\x8f\x9a!X\xc1\xacA\x84\x81\x8e\xd3㡅\xa1k\xe3X\x05\x92^{\xb4\xf2h\xe45M7\xfe\xc7О\xe37\xb4B\x95/ݛ\u07bd\x99\xf6\xd8;7\xbc\xb8\xaf\xab\xe3\xa6W\xae\"\x8f\r/\x9f\xb1qJ\xc1\x9dw\x91\u007f\x93c6\xdbp\xa7\xe5^\xd1\\\x8a\x81\b\b\x18\bV\xab\xc7Fm\xd0\x0f\xeb({\x1c\x18\x00Lz\xd3Z\x13\x96K7\xbc\x9f\x82g\xa4E\xd0\r&\xd0@\xa7ԋ\x8bG\xde\xc1_\xe2\x17G\xbeĺ\x91Ȩ\x8f<\"\xc7\xf6\xf5\x19\x1b\xaf`\x86r\\\x051\xd5a\x15֫@\x91\x16Ad\x94yE\x91\x87O0܈a\x00ob\x85=\xc0\x06\x8c\x11\xd7\x13\xe3\x81'\x88\xcd\x00\xd2(\x8d\xadH\x10\x803)\x800\xaaY\x0f.\x92`}\x87T\x9c\x92˴\x9f\xa3\x9b\x1e\x8d5\x9b\xa9{\x83\x1fw\x83\x82R\xd7(\xfd\x1f\xa9\x11\xa6\x93\x05\xb8gx\xf7\xc8+8Jr\xe3\xc1\nJ\xa3W\x1e\x0fn\xcaT,'@\ffG\xab\x9a\xeeE\x11шկ\\\xa7ܧܯ<\xac\x14\x95J:H<\xae\xfeBM\xa6\xaba\x9a\x1a\x96(@\xc1\xc8ϧ_\xe0\xe817\r?\x8e\x9f\xc5\x04c\x95\xc8\xf5\xf1\x04+\xfa`l\xf8a\x84\x04k\xe4\xe6L\xceȎ\xc7\a\"\xb9\x06\x8f\x9f\xf3\x0e\x9b\xf0ʑ\x83\xe4Ց\xdb\xf1\x8ec\xe43@\xaf\x0e\x9bd\x1a\x87\xa4g\xf0F9\x9e\x16d\xcc\x1c\xfaR8\x8b\xc0\x00\x18\xf8\u007f\xe0s\xc2?\x80\xce\xd78\x9d>\xcdD\xf1\x19\x05\xad\xf2\xe5\x02\xc6\x04\xde(\xe5\xc3_\xde{OzF8\xff\xd6\xf9G\xd8u4\b\x91I\x17{\xf6\b\x94\x180\vP<\"\xacgO\xbeݳO\x8c\x89\x91\xca\xd0/}2rj\xb4g\x0f(A\xe9\xb1\xcb\xf4\xf82\x06\x82\xbe\x84\xb3\x82\xa0D\xff\x10\xbf&\xff\xe0\xce\xc9P\x8e\xf1+ScO\x18\x03U\x94\xa2ĩS\xf0\x17)\u007f=?뭯\x05F\x8b\x02+\xb8z\xfe7H\x81\x92\x19O\x84A\x1b\xab\x02+9'\x87\x95 W\xb79BT\n\xc6>S7%*Φ\x0e\xb2\x8an.Y\xa3\xac\x19\x12\x10\x00\x01\x8b\n8\xb2\x0f\xbe\xf8B\xfaM\xd3P#V\x90#\xc3mx\xf7\x8f\xa5_\xdd+\x8fᆥgH\xe7\x85VD\x90\xe9\bB<\xc0/1b\xe51z\xb5\xdcl\x10\xd29\xfc\x18\xa13m~(\xfb\x1b\xb4s'\xc8t\xc1\x8f\xb4\xa8$\xe3l\x12\x81\x88\xa0ш\x82^\xec\x13ײ\x89\x82z\xd4\xc7\xe2{4+\xcf^\x91'\xf0\xd1\xeb\x94\x13:\x98\xa4\x81ڊ\xaf+\xe8[\xd0n\x80nC\xa4V\xa0\x11d\xe9W\xd3\x028\xc1\xeb\xaf|~\uf529{\x9f\xdf\xd2}h\xea\xdcIW%\xd7\x0e\f\xac\x9csS[\xb0\xb9\xa5\xc32\xe9\x86\x13w\x1a\xa9\x8c\x15\xa8\x18-\xceLT\x19@\xa5\x81!\x8a\x1d\x82yJ\x0e\xb8Vǜ\xc8\xf1\b\xc4\"@\xc4<\xc8k\xd6u\x97\xa4\xf2\xa6\xe4\xe1\"\xfaf\xfa\t\x02\xaf\x12\xb8\x9a\x00!^4\xcdl\xf0v*m\xe3\x85B\xe6\x15\xb2&rզ\xec\xd8d\x01\xa0\xae\xe0/'\xe4\x12\xde\xdd\xe0\xcfAiY%\x033V*\xe0:p\xc4Z\x95W\xfe\xee\x96\x19m\xd7=\xbda\xed\xcfvL\x1e\xe9\xc6\xc1\xc9}\xf5\x89\xf9ݳJ\x8bf\xf4,#wo\xb8*ѝ.\x1c\xeej\xbe\xf1\x9d\x9bo\xfd\xaf[\xdb&\u007f\xef\xd7WݖY\xda\x12R\xdb\xfc\xb6O\xadn\x8b\x12\x8d\xe1b\xd2*x\x91\x13\xb5eJU\x0e\xa5\xae\x15!\x83\xc1\x15u=\xee\xc2\x06\x178p3\x11\t\xcf\xcb3\x05\t\xf0\x9d\xa2Co\xe9\xd0\x02\x96\xfd<\x9d\x18\xc5\xc7\xc6\x04EȲY\xb1\xe2_\x04\x02LUU\xc9o[\x96\x1b\x12\xf8\x83?K\xc7\x0f\x1e\xac\xec\xff\xc1\xec\x05;K[\xbd\x8d\xe5\x13*\xfeL\xb6\r\xef!\xdbn\xed\xeaZy㬀\xdb\xf4\x9e\xda\xd41\xa1\x99\xd5\xf0-\x92\x85\xab\xa7\U0009f23aP\x16=\x93\xb9\x86\x17\xadbP$,@\x05݄wZ\x9dA'Q\x05\xf2\x02%\x01\xa2\xaa\x02U*/U\x92\"\x95>Z\x9d\xa9\xd4&\x15\xf3`^+\xcf\xcfl3\xb0\x86w\xdf\xe3}\x10\xeb\x03q&\xcclM\x96\xf8\x94\xa6\xd6\x12C4\x9a\x9c4-\xd8\x17\\\x1b$\xc1\xa0!\xe9Kƒ$Y\xc9\xe3\x89܄\xee\xa9*P\xa9\xac\v&\x18ZZ\xa0e\xba\xcf5\xb5\x13:\xad\xb4\xe5\x94M\x9fΦ\xe5\x96\xd31\n\xe6\x18\xf7\xf4\x882~,\x151\x9c\x19ͤǨ>\xe5\xa9\x06\x91\xb1\x82\rX\xc5\xf1YF:`\x01\xa2\x8eE\x91\xefL<`;\xb8h\xf7p1\x90DX\xfd\xdd<>\xb3\x8c\xab\xffw\x8b\xa1z\xc5\xfe\x15\xf6j\xadŞWR\xe3ylƮy\xb1\xf6;\u07bbuá\r\xa9\x92\xe6yQk\xb1\xdfj.\xac.\x98\xbb\xb4f\xd5\xfee\x96xE\x89FZ`\x8du\xa6\xee\xfd\xd1¥7\xba\x1a*\x03k\x1a\x9bZ3\xe9){\xb9\x05\a\x12\x05SJf\xdf8\x90\xe4\x891\x98gu\xea\xf9`\xfb\xe6\x19}w\xadLEflh\xad\xef\xc9\x14\xa9\x95\xbe\x92D\xbe\xaf\xb2\xac\xc8\x1a:\xb8\xa1\xe7\xa6E\x95\xbc\xa8 \xdf\b\xacE5\xb7-]\xa2\xf5U\x91%\xf3\a\x06\xe6\xf7\xac`v\xb5\x1f!n\x05\x8d\x01nT\x9d\xf1\xf3.\xab\vC3\x9a\xedEޘw\x97\x97\xe8\xd5͚Y\x1e+\xdf\xe10h\x91\x86%\x8d8K\x1b\x17\xbb(Tl\xc5`\x1c\x9d\x14c\xf4\x8f\xc7W\x90\xfb\x94\xdc\n\xe9\xf8\xd4κ\xe91\x8bt\x9c\xc6S\xc2\x11Cŝ\xfd3\xf7\xf4W\xe2\x1b\xb7m\x8b\xf5^?o\xe4K\x1a@_(Ɇ\xcbK\xe7tD\a\xeeY\xc5\xfc\xf9V\x84\xe0\x14\xa5Ɍ\xec(\x9a\xc9\u007f\xd6\t1:\xb1d\x1f5!K3\xb2\xcer\xf03O\xb1B\x8aMo\x91g\rӞ\xba<\xba\x95\xa7\f\x98/\xef\x91R\xa2n\xcd+\xab/(\x98P\xe6r\x95M((\xa8/˃\x06F\x0f?\x18I\x05t\xba@*RR\x1b\xd0\xeb\x03\xb5\xe7\x1f\xe6z\x11\xc2\x17\x1e\x93,\xa3\xbf\x1fFM\xd4\xdbLy&L!\xb0\xa6٣\x9f]\xfcl1\xc4h\xb5}_1q\xce<\xc5\xfa\x9eZ\xfdX\x15\xb7\x03\xb9h\xe8\xc8\xe6(b\x01#N\x8b\xb9rW\xc6L\xad\xa9\xea[\xe5\xdc\xcb[\xfeKbK\n\xbc\xd5\x05\xce2\xbf\x05*\xc2s\xae\xaf\xbe\x9cfɢS\x9fU)\xf5\xe1\x89\t\xee\xd07\xc5\x1d{\x16\u05ca+/c`T\x9fG平k\x8e\xa8\x04\xd0`\xe0\x18>3\xd9[\xd9>S\xadֵ\xf2\x9c\x95Ü\x88\t\x11b<\x14\xf2\x80x\x03\x8f\xe9\x8b7h\x95ͪYz5\x12\rFK\xabB\x04\x11aAe\xc5z\x94\xceA\xfdH.\xbf\xcah9W\xf9e'e\uf064\x8c\xf0G\v\xc0\"\xbc\xb4\x0e\x8c0WZ\x05\x8f\xd12\xf0٭L\xe0\x06\xdc\u007f\bfI\xae\x91=\xf0X\x97\xf4\x13\xc12\xd2&\x8d\xda \xbcIi&\x14\x13\x9a\x9f\xe5!\xc6\xf7\xf3\xfbx\x82\x9aa\x16\x87\x11\xb3:\xf9g\xc7F\xf5\xf0&\xbb\x18\xfd\xff\xd1\xef\ngi\xec)\xa1\xe3\x84J\xb9,B\xec\xe0@\xcd\u007f\a\xb8\v\xe0\xfb\x00\xdb\x00j\x00B\xf2h\x13@\x13h.\x98]\xdaW\n\xb2eG\x1c\x06\x03\x1b!\x18h 2\x84\xf9\x0e\x8f\u0560\xd5\arvN\r\x9dM\u038b\x18\x13\x17\xad\xdd8\u07b4a\xa9\xefۦ\x9f\xb0\xcb\xf3\xc5l\xa3;vJ\x86LV\xae_:\xde\xdaQ\xdbQN\x9d\xe0L\xb1\xd11yzO\xf9\xc6\x1fG\x9c\x8eƮ\xf9\xe5O\xfdBz\xa3\xb5c\xf5\x82q\u007f\xe0z\x87\"\rE\xe6i\xb3\xd9\xf6\x85wF\xc2\xec\xa3G\u007f|\xd1W)\xafy\xa8.S\xd8d\x02\xdeau\xc8\ue68f\xf2c\xf9\xbb\xf2s\xee\xea2\xf0\x1d\x8c\x8f\xff\xdd]\xd1\xffJ\xf5\xa8\xafN\x99>azt\x94̦\xae\x05\xb1\xae\xef3O\xbd\x8c2\xe6\xa5\xe3yw\x80ңF^9z\xe8\x80\xf7\x80\xca\x02*\a\b-\x9a9\xfe>?\xa0\x16[\xb7O\xd9\xe12\x90\x0e\xa3\xed\xb2J\xad\x1co\xc7#\xe5w\x17@\xfc\xa8w\xc5\xf2\xdeޕ˳\xf8\xe5\x96\xeb\x9eݺ\xf6W{\xda[\xae\xfb͕l\x8f+\x1e|\xe8Ё\a\u007f\xfa\xd3\a\u007f\xf8\xc7\xdb::n\xfb\xe3\x0fo\xf8\xe3\xad\xed\xed\xb7\xfeQƎ4\x1fm\xa5t\x8daG7\xf0N\x8a\x1d]s\xe2!\x83\xa6Y;\xab\xc2\xe8\xeb\xb0\x19tz\x8d\x8b\x8f\x8c\xe1\x1a&\xa7oaG\x9eaF\xb9\x94\xfdm91\u07fd\f;~\x9a\x9c\xd3Xi5p\x1c\xe1\xa5\xe3\xe7\":\x86\x19\x93snZRS\xbb\xfc\xe6#\x97bG\v\x83\x8c\x01o^R/\xd5\tm73\xf7f`r\x88\"\x1dr\xebeؑ\xc9w4\xaf\x1aF\xf1\x99\x83\t\xd7χ\x9a\x03\xb3\xe3:*؊BW\x81ާ\xa6\xd2-E\x1d\xc6\u007f\x82ς\xdf\x06a\xe2\xb7\xe0ZR\xeeGT\xb3v\x84\x8e\xe0\x9e\xe4\xd4r\x1b\x98\xa3\x1d\xb5\x14\x83\x19\x18\x03\f\xac\xd5\xf4L\xa9\xb1)\x1c\x80\xa5\xe3<&Dk\xa1ܝd\xc4\x06s\xc4\x1e<\xf0\x05\x83k\x94\x89\xa9\f\xae\r}}\x04\xde6\xa7\\>?\x83kc>JZe\xbb\xcd\xfc\xec9\x01\x046\xe7\xd4O]N\x10\xf2/\xe4C\xb3\x95J\x98\"z\x97Yo\xb4\xf2\xb6&\xdbl\x1b\xb1\xa9\x19\xa4?\x96\xc8\xcǎB\x166\xe6\x05F\xb6\xb1\x9c|\xcb~\xf1G\x1a\xb7\xcb.(\xf2\xbd\x1eUݢ\x96\xb0t\xfc\xa32\xbd\xa3\xa6i\x0e\xb7\x11\b\xc1\xd2\xfb\x98#\xe0\x99\xbcz\xda\xc8+\\\xef\xc1Hs\x85+\x87\xa9\x16Rl\xbe\x97ҕD\x8b2\xf9<\xb6b\xcc:\xef8le\r\x13k3\x9a\x93:\x9e\x82X\n\xcaY'\xc4Iϕ7\xab\xbc\xdd5\xd6r>\xd2Y\xc4ZFE\x85\x06C\xa7\x9a\xc6՟\xd37j\x9ej \x119\xcd^c\x05\u05c8\f\x1b#\x89\xf1\x8a+\x9b\xa5wY\xc9u\x1c{\x8dW\\\xf7f\xb6\x1c\\\x96\xec\x9d\xd5YT\x92]0'X\\\x1b4j\xcb\xe7M\x9d\xb2\xb2\xa9`\u009a{\xfa\x06\x9e\xa2#\xc5\xeb\x9b\x1a\x92\xba\x92\xf6\xfa\x96\r]\xa5\xd0>\xf7ڹ\xa5\xa2\xc1a\x1a\xb6\x168uzW\xd0j\xf5\a\xa3\x05\xfe\x89ӗ\xb6tn\xee\f\x97\x97\xff\xb1\xb0\xbc4d\xf1\xfa\xc3\xf9\xde\xfa\xcee\x8c\xff(\xe5_Ƿ#'ZpD\xe5`<\x13\xa6\x9aJ\x95\xbe\x153@\xec\U000ba9b9\xfa\\k]\xf7\xbbN\xb9\x04\xb3\xbdY\xa3\xd3\x19\xfa4k5\xa74_h.hx\x8dM\xd7i2\x98\xf5\xa8C\x94};\x91\xc85K\xe5:\xac\f\xb0\x18瑠\xfd\xfc\x83\x91\x0f&6\xe4\xf4u\xb7\x1coz\xe5\xf8\x17h\xe3@\xa5\x02p4;/\x86?\x1d\xf5m\x17\xa2'\xc1$\xda\x1cZ=\xab\xf5\x1c\x8bg\xa3c!\x87\xf9\x03\xbad\\kdD\x8c\xe5d\xb2\xbd\xd8\xe0\x1c\vzP\x91\v\x85P1\x14\xa9g!\xef\xeeU\xf8\xc4\xc8\xc6\\$ĺo\x1e\xb8XǝN\xe9Ѣ\xdb3^Ak\xd3Vk\x97h\xb7h\x8fh_\xd6~\xa8\xfd\xbbVyB\rj\xd6@\xfb\x03\x96\x176ȫ\xa0X\x8d7\x85\x96\xa3m\xe8\x97\xe8\x18:\x83\xbeFJ-+N\xa9\xf7k\x9e\xd3`\r\x93\xbf\x8dM\x1a4h4\xfau\xfa}\xfa\xc3\xfa\xdf\xeb9\xaf\x1e0\xb0+hA\rz\x9b\x00\xf2\xb4_\x16\u07b3\xd9\xf4%\x85\xcbH61V\x01\x8e\xb0z\x91\\\x91\x04\xea\"@+\x91\x007H\x9b!\xfdK\xa3\x93\b\xc4i<\n\x19i3\xd7;\xb2;\xb5\xbd\xa2jk\n\xef`L\xc9<\t\x1b)O\t\xb0\xff\xe2\x0f\x89\x0f\x13\u007fO\x90\x04\xfbe\x83R\xdfZ\xc1\xfc\xe2\xed\xc2\x0f\n\xffVH\nY\xded'\xff3\xf0߁\xf3\x01\x12p\xd0c\vs\x14;\xfd\xa7\x13\xa63\xa6\xafYA\x98\x9e|\xdb\xf8\x81\xf1oFbd_\x98\xc1\xbe\x80\xfe\x1b\x9dG\x04\xb1/\xdci:h¦;\x8c\x0f\x1a\xb11\xef\x8e\xc2\a\vqᝁ\x83\x01\x1cPޑx0\x81\x13w\xa2\x83\b\xa3\x12!ϖ\x17\xca#\xca\x12gIq\t)Q\xe6\x11sE|\u007f\xfc\xb98\x8e3\x81\x19\xe9/\xc6\r\xf1\xaa]U\xfb\xaa\xb0\xbej\xb4\x10\x9cg\xae\b\xb0\xa2\xe7\x14%Q\x9a\xf5\x11\xc1es5\xbb\x88˅L\xd6\xd1\xf5\xb4\xe9\xc4%eߚ\b\xddБ\x02;\xce&.ٰYO\xf2st\xe3g\xf3\x9eB\xe1\x80 \xea\x88HF\xab\xec\x89Ѫ/;\"L\xd8\xe4\xefݜ\xd6`TZ\x1c\x82ݬ0\x1a\xb4\\\x8ftTz\v\xa2\xeb\x95f\xa3\x8e\x13@\xa4\xedw%\xb9\n\xa6o\xe6\rF\r\bDo\xb2*7C\x84b\x9b=\xc6\xf4\xb4y\xa5K\xb7n]V\xda3-c\x92vp\xbd\x921\xbcz\xed\"\xb7\xb9\xbeyJ\xa6B\x85\x1d#\x1f\xdb\x16,_\x94\xef\\\xb8~]\x18\xbe\x18\xb5G\x15B\xdc\\\xaa;5\x1a\xc9\xcc\xe7U\xa0R\x80\x8a\x03\x1e\x83\x83\x03\x8e\xe7\xefF\x87Я\x10\x13?\xbf\x1cA\x0f\x02\xb6\xc2\x0f;P\x11\xc2*\x04\xcdB\xb7\xb0D\xb8]8 \x1c\x11\xfe |((\xecBXH\nD\x10@P\x12ĩ\x1d\nP\xec\x17\x9f\x13\xb1\xc8d\xce\xc2!\x9bV\xa6\xf5j\xa3\xdaǵĎ\x01\x83\x9d\xfe\xa6\x9a\xc9ޥV:\f\xe6\xd6\xe5J\xe8QB\x8a\xaa\x00\x97(\xc1)\x17\xe2\tk8\xfb\xe8\x87K\b\xcc\"PM\x9a\t.$`c=oP\x12P\xa8\xac\x1c\xe8Nj˗T\xdb#\xb9Bs\xfc\xa2\xb1g\x99\xbe\xc6\x1e\xb9b3;R\x82\xdf%\x83Q5\xf8\xe1:\xe9N\xe8\xf9կ\xa1G\xba\x15vH\a^\u007f]:\x80\xebp@:\f\xd3G\xde\x1dy\tVH\xb7\xcbv/Yd_\xf6\xa0C\x99r\x87\xa7ƃU\x1e\xf0\xfc]\aI\x1d\xd8\xd8\x149\x1e\x9d\xd1\xc0\u007fj\xa0X\x93\xd2\xe0<\r(5\xa0ٯ}N\x8b\xb5L\x1c\xa5T\x1cZ\x83V\xeb[\xe7\xdb\xe7;\xec\xfb\xbd\x8f\xf3\xfa\xc0j\xfd\x10\xe0m\x80\x83\f\u007fV\x03\xb6\x02\b\x00\x80\x98\x84LH\xf7\x1e\x0f\xac/\xeb\xf1x\x1dF\x97\x06\xe9\x19Z;f\xcc5\x1d\x18\xa7\xa3lE\xc7\xd9e\x0f\xe6\xe0\xec4C\xc3F\xe6\xdec\x16G#\xec\xf8(#\r\xd0\xf9\x94A\x8f\x05,\xaa\xb4\"\xe5Ujn\xf8Ï+\xdb]\xd6L \xd6X\x11К\xa9\xeb\xdfP\xb9%j\xaf\xa9M\xd9\xf1\xe6o\xccO<\xa2\x16\xbe\xe4\x15y\xd1I\xc55\xb95\ad5\x95\x87\x92\xf6\x0e\xecS\xc8<\xb2\x9c\x10b0:ZUr}[\xc9qX\xc9\x18w\xd1\xe2:\xadn\xefW?\xa7ƃ4\xdee\xe8\x00\xa3YѭX\xa2 \xb9\n7\xfd\n\xcf,\a?\x871\xcehM\xadX\xe4:y=Vt\x82\x15Ѿ\x9dܺc\xcc1\xf7\x92\xd3<[\x01\xfa\xad\x1aw\x15\xabq\x93\xd5ÿ\x83cR7\xe1\xa4fx\xfd)\xfc\xfc\x99\x97Gf\xe4\xf2\x82\x97֔ߖ\u05fctf\xe2\xea\"JQ\xd0A7z\xff\xffX\xf3\xcf*\xf0W\xb10\x84\xed\xf8\xef\xaf\x13XK\x06\xc9\x10!SXv\xd3\xf9\xffV\xf0\xb5\xfeo\xbas\xe33\xebs\xab\x8f>\xa7i\x8c\xad_\x8cDP(PE\x85\x19\x96\xe7\x9d[u\xc4j\x14\xd8*$\xd1Ȳ\xb4UN!\xe0ݽ\xa3\xe1\x8aI~\x1cZthw\xd1\xc42\x87\xf0\x97\x8dW\x11G\xd9Ģ݇\x16\x85`m\xd7\x17f:G\x17\x1aږO\xf6\x9d\x06\x8d\xb7:\"\xbd\xfdt@c\\\xd3+\xbd\x1d\xa9\xf6j\xe0}\xdf\xe4\x15mP\x87\x10\xc0+R\x14? \xe8Xl\xcch\xf1!61y\x1d!\x04#$\x97ֳl\xd9\u007f\x82\xad=6\x83\xc5\xe2he\xfb\x8cY\xa3o\xf5\xf9\xdc\xfc\xa3i]\x9f\x0e\xeb2VG\xab\x0eE#\xf2r\xbbHD^\xee\xc3&r#fd\x90\x83\x8fbU\xaeJa\x1f+\xdb\t\f\x14`C0\x15-\xb1\xbb\x1b&\xa4\xf2\x12\x83\x9d\x9eD\xaa9\x16J\xc5Jl\xec\x8cs\xf1=\xec\x04^f\xb0iy\xb3\xbf\xd4\xf9Qu\x92\xd79-\x06+}\xeb+s~\xb4\x9a\xd7:G\xd7=HC\xf0\x16\xd7+ߋby\xa6m>\x02\x01\xe9\xe0\u007f\xb9%\x05\x86\x971l\xc5@\xd1\x10\xc6\x04\x03F\x1cp\xb7\xff\x91\xc01\x02W\x12X\x9e[J\xd6OC\x10\xa2\xf7\xa4\xd0\x13\x14M\x8f\xbc\f\xd1\xd1h\xf0\x19\xabL\x8e-\r\xed˚\x134\x10\\6\x8c:\xf8UB:\x00\xbd\x97/\"\xc0\xbd\xc3\x15|\xf0\xbb\xcb!Xψ\xe2\xc2\xd3\xfck\xa8\x18-zBW|\x14pơFH0?\x1e|6x\x9c\x96\xe0\"\xfb#\x87#\xcfE\x88g\x8d\f\xe5}4\xf6\t%^:\xb9\x15\xfb7\x03*rn\x19\xd4\fi\xee\xd7\x10\x8dz\x97n\x1f\xd5\x06\xa2Ծ\x9e=\x1d}=q\x1a\xb2\x86?DNg\r/g\xe3Fy%\\E\x8c\xaf\xba\xe4\xee\x03\xc4.\xe6\xb4s\xb1\xbc\r\xa2\x15{\x17\xd0U75\xcbo\x9b\xffd\xa8\xbd%\xed\xe8\xb6V\xd5$\x1d\x93\x16\xd4\xe5m\xb8\xa6\xfd\xc6x\xe2\xea&\xcb\x1bک\u05fd\xb2\xf7\xc6\xd7~0U\xfb\xc8O@m\xb0\xa8\xdfW\x99\xf5ʢ\x99\x83s\x1e\xbe?\xe8\xf8\xc2\xedů\xe6\xd6E#\xc4M\xa2\xbc\x85\xd0ݙ\x15\xaa¼\u0092B\xa2\xfeH\x0f\u007f\xd0\xc3Kz\b\xeb\xe7\xe8\xef\xd0\x13\xab\x1e\xf4k\xfc\xbft\u007f\xed\xc6+\xdd\xdbݸ\xd6\r\x9c\x16\xdcZ\xb7\xd6\xea\xcd\xd8\xfb\xed\xd8N\x16Yw[\xb1Ϛ\xb1b\xce\nV\x9dw\xab\xfa\x16j\x92>\xe4h\xb1\xbcby\xc7B,\x1bm\x8e3\"\xbc*\x82\xb8\x89C\"\x8d\xed\xa7\xb3\xac$)\xf7Wr\xe5X9\xb6_\xbc)\x06=)\xab\x91\x81T\xb8\xa4\x16K\xa1\xb2<\x99\x84-\x10l \xf5\x10\xa8J0i\xe1\x83k~6=۲9=y*tJO\xbaJ\xab\\\xf8\xc0\xb0\xa1dr\xd4\xf1\xe4\x93\xe9\xb5w\xf1\xaf\xc5\n>t\a:\xfa\x9e?9\x94\x9cQ[\xa4\x93\x86ϙJj\xbbjo>\xf9\xc2\xfa\xfb\aʙ\x8d.@\x12\xa7\xe1\x0e\xa1\x02TM\x11l\xdf\x04\u007f\xbb\x9f:\xf5d+N\xe8'\xeb\xf1\x04\xd2N\xf0\x04O\xbb\aOp\xb4;pB3Y\x83\x13\xc2d\x01\xdbt~\xe2\x11\x1c\x1a\xab^\xaf\x11\b\x87\xca\x0e\x99|\\\xe0\xd0~t\x18\xfd\x1e\x11\xe4\xb5k\x04*B\xbf\xc3\xe3\xf1\x13\x81S\x16=\xe6\xd2\xf2\xf1ǔ(z\xfa\xe5\xf8\u007f\x9d\x96;\xeb\xb9\xe7\xcbL\n\xdf=\xc9\nf\xf4\x01lc\xa6\x83\xd3p\xd2.w\x9d\xecI\xbb\\\x9c\x15X\xb9-\x97\xed\xc4p2\x94\xbcl\xca\xd1\xc1|\xe75\xb7\rF\x96$\xaa\xfb\xa3\x83\xffv\x8dӝ\x9f\xb7\xe3\xe6\xc1\xd2E\xd55\x8b\";n\x19\xac:\xeeI4\x86\x8b\x9a\xab<\x9e\xaa\xe6\xa2pc\u0083ߪ^T:x\xeb\xa03ߕ\u007f\xcd\xd05%K\xabk\x16\x96\xed\xb8y\x87=ߙ\xb7\xfd\xe6\x1d\xf3W\x84\x9b\x12nw\xa2)\\Ԕ\xf0x\x12M\xb2\x1d)\x10\xe2\x16ҸkC\x8fgLz\xb3Y#\xd8\xc0\xa6\xb7N\xb3\xdeOgvrV\x96\xfa&X\xad\x1a\x05\xc7)\x01 \x83\xf7c,7\xa6E\x9eX\t&\xea5*\vX\fH\x93\xd1\xec\xd2\xec\xd3\xec\xd7\xfc^#hT\xba\xf3zQ/\x98ΛY\xe3\xfa\x8f\x99|\x1a\xd4D\x04\xc8\xccʝf\xaf\xf9~3ћ\xf5fP\x9blc\x8dl\xd9vX\xaeL\x18NǍl\x03r\xb3\xf8t<\x1b\x97\x9bۉ,\x93\xecxW\x9e\xa6\x10\x1a\"\x12ր\x95\xbd\xfcUԜ\xd8\v\xfc\xb8\xea+HI\xb7\xff\xf6o\u007f\xfb\x9bt\xfaܹs\xcfJ\xf7\xc0\x04\xd6\xf2\x1e\xb1>y\xfd\x93\xef\xbeK7\xf8\x139\xb6]\x92\xa7DT\x981c\xe1\x10\x89\x89\x19q\x9dHDV\xe3\x15\xe4|\xc5H\x03\xd9\xd4sY\xcb8\x9a\xb9X\xee\x1a~S\x1a\x1c~S\x8e\x93+h\xac\x19\x14\x8a\xd1dԍ^\xcax\xab*\x9a*\xb0\xad\"T\x81\xe3\xb1I1l\x89\x15\xc6p\xad\xb1͈\x9d\xc6b#.fI@I+\ar\x03\xa4\x90!\xc9n\xfa\xae\xb05\xdcȳfB\x17\xafPt8:\x8a:H\xc7\xdd~\u007f\xed]\x86\xf0ܵs\a\xe7\x0e\xcd%s3t\x84n\xbfK\xa1\x10\x1b\xed\x8d\xe1F\xd2x7\x8a\xc5\x10\xda\xc7V\u007f\x15\xb6\xe6\xf3]\xad\xf4\xa9\x8dU\xddWj3Z\xee\xd3\xc2(212.\xe4\x89 \x89\xdc\xfc\xa2\xd3ts:Bg\xeb\xc5OӃ,\xfd\x15\x1ai\\\xcfG5\xa8\x1dm\xcbL˳@\x9e\x01ȋy\xaa\xb4\n\xf4&0\xa9L*\xb7c2L~!\xd2iOA\xea\x05\xb7pL\x8b\x8e\xd9\a\xe9\xfc\xf3\xfb\xb5\x04\xd9\rv\x9f}\x9d}\x97}\x9f]\xd0ڵ\xf6B\x1at\x1a^\x8d\x9b\x1d\xad\xd0\xfaj!\xc7\n\xfb\xf4Ϙ\xf38\xc3\x19\x03E\x14\xb9\x05\xab\xc61'̵\x9c/\xe9\xccB\xe0Үm\br\v\x8c9kn\xb9&wy\xe0\"\x83W\xac\xe8_\x99\xba6\x95\xdaұr\xff\xca\xea\xca\xe5?Z>\xf3\xeeƶ\xf5\xc3;\xaa\xaf\xb8\xfe\xc1\xe7\x96-{\xee\xc1믨\xbe\xf4\xf8\xa6\xfe\x9f\xfeip\xf0O?\xed\x1fۓ/o۽\xe7f\x8f\xfbY\x97\xa7\xf1\xeaGW,}x{ci\xc1\xc1Z\xe9\xcce\xdfݻ\xb0\xaaj\xe1^z<\xfeey/\xfbd\x14\x9f\xc5C\xfc+\xf2<\x83k\x18\xaa\xcc\xddXG\xed\xa0\x1b\x91md\x80\xd9\xc8\xfcP\xc8\x130\xaf\xb7\xeaq9\xe8\xdf\xf7\xc4\xf4\xb1\xa1\xd8\xe3t\xea\xc1\xa9\xd8\x171!\x96Q\xeb[\x83\xefۦ)\xfb\x94X\x991Z[\x95\xe5\x1f\xeb\xf5EVӇ\xce?A\xd1G\xdc0\xfa&7\v\xe1L\"7\t\x81\x015\xe6\\\xb4t)\xcfR\x84\b}\\z\x8b\x974\x8cOC\x90a(\\\x9c\x86P\x0fX\xe1-Z\xdf\x18\x9fQ\xeb\x8b\xcc\xd8:\xb5\xa7\xae\xbaha夆\xdb\xe67.\x9e\xe4\x97>\fE]ʐ˗(4\xe3\xe15\xb5\x19WrVj\xc2\xcc*\x17ǥ\xab\v\\{|\xf1p\xcb\xe2\x06\xa9\x19\xea\x89\xc5]`\n\x15\xda\xc2I_!\xc2\x14/\xf4r\v8\x0e\tH\x8b\xe6g\xe2\xe4\x13\xe1S\xc0\x987q\xc0q\xaaO\xb4\x9fj\xd4jE\x941\xe8T\x82R\xafU\xa9\x88Z\xa3\xe1\xf8ϔ\n\x05\x86\xcf\x04\xf2\x05JS?\x88\x1bs\x1b\xf6\x04\xba\xfa\"\xcbތ\x9c\x91'\xf6\xb0\x03\xea!`'f\x11\x82\xb9]\x17T\x9d3G H\xa7xQiz\x11Q\x0f@C\b\xb3iM\xbb\x94D\x89\x94\xc8\xe9HC\xfa\x85p\x9b\xbd\n\xaa^p\xf2J\"\x1e\xd39\x8e\xa9\x06\x95CJ\x8cT\x06\xd5:\xd5.\x15\xa7\u0529T:%\xf1\xd9k\xa1\xf6\xd5r\xb3\xa3\x11\x1a_\xf5]\xf4&{*:\xeaN\xa7in\xfa\x8eG17c1\x98M9\x16Dv\xc7/#\xe5$n\x83\xd1\x05\xdf\x11\xf8W>\xc5]\xd71\xe93\xe9\xc9\xe8\a\xa08rP\x1a\xf4NJ\xe9\xbdQ\x9f{\xa4\xff\xffߣ\xf6\xf6\xaeqK\xeb\xa0Kz\x8c\xdc\xf6C\xe9\xcbԤ@\xb9K-\x9d\xfa\xffv(\xb8p^\xb0p\x82\xf0.\"\xc8x\x04\xf1\x00\xcf\xe0\xdcMX\f/\xe7\xa6\x11q\xc27_q\x1a\xc1r\x8ca2\xe2$\x06\xaa\x03\xd6{tgt\x96C\xc8sHU\xec|L\x1fx\x8cg+\xe7\xff\xe3\f\xb5\x1f\xba\xfdnL\xbd\x9c\xf5\x05ս;[[\a\xb3\xd5\xd5\xd9\xc1\xd6֝\xbd\xd5\xcf:JR>_\x8a\xc6\xf7\xdc\xde\xc1\xado\xd9ٛL\xf6\xeeliݙ\xad\xaa\xca\xeel\xf5ְOk\xbc\xbeT\xc4錤\xd8\xfa]\x848\x03\xf79R#=[q$r\xa0\x13\xb5\x98\xdd(\a\x13\xa4}T\xc3\xee\x95\xc3\tlT\xfd89N.\x10\x9edhj&\x88\xf92\xeb\x9f1\xb7f\a\xb2\xf13d\xf7\xad\xbb谑\xfd%wґn&G\xf1\x03\xa3wӹUzM^\xe8\v(\x8a\xdb\xf0o\xa8L\xa8\x8f\x15\x15ͩ\xda>\xf1\x81ՓVwD\x9cճ&,\x87~\xdcv\xf0d٤\xea\xa8+TT\x1c\xbd3\xd5\xeaOg\xeb\xcbgtͫ\xee\x93i\xae\xc3)|\x84?\x86*\xd0m\xbfD\xaa\v\x1f\xff\x8c\x92\x1c;\xca\xf6T:\f\x1d2\xe0B\x90ۭ\x0f\x9cF\xc8vZ\x99\xd0'\xbc\x89h\xa2/q*\xf1E\xe2BB,٧\xdfO\x13i\x95\xa1\xc90\xdb@B\x06(f\f\x93\xa5\xf9[\xf3\xf7\xe4\x93\xfc\x0f\x8f\xbba\x99\xfbJ\xf7\x0f(\xa7nsq胲?\x99?0hN}\xeb^*\xd9\x14-\xa9\xc8c\xc5\xdc\rU\xe8YS\x0e\xab0\u007f\xb2\xcax\xe4bѨ\xfa\xb2\xbb\xa9\x8c\x8b\x03\xdf=c^\xa2\xad\xc2q\xf8W\xed\xd7N\x9ct픶&C(\x13[\xb7\xa02S\xa8\x89'\xa2\xb3R\x8b\x1b\xeeXڰ\xa2\xa3\x94;\xdd4\xe0֪J\x92\x93\n\xb7\xdd\x10\b\xfc\xa6\x88^\xd4\x1ar\x9b\xf6\x9b\xbcak(\x1e*\xba1\xda\xe0\x9f\xd0}\xe9\\9=*\xca8\x049\xb3\x88\x02\xfa'S\xe5\xa8\x191]U\xb1\xdb\x1eYu\xbc\b\xed\xd6X\xb4\xc4\x00\xdd\xea\x92\xea\x86t\xad\x9d;\xe1r\xb7ϘQ`5U\xd4\xd4\xd5\xc4\xcd\b\xe4\x1e\xe0\n\xae\x17\xb9Q*S0I\x0f\xac\xb3}qZ\x83\xd3ݜ?ˣ\xcc\u05fb\\:֘\xd4X\xd9\x14r\xd6\xf1\x05\xb6\x00+\xcb6,\xfd\x8cw\a\xd8|&`\xa2\x1a_\xdb\xf2\xe6\xd4\x19tݧ\x05*\xceD4\x81\xe5S\xab{\xf21\xc1\x84\xb5\xa6\xe1\xcd\xf1F\xc1\xed\xb3g\x97\x97\x96D\x82\xb3\n\xa5然\xdc\xf8z\xbd4\xcc\r\xc9\xf3\v\xaa\x9e\xe4Fx\xf9\x16,Js+\xcfka\x04Y\x91\x16\xb4\n\x05\"?2!\x9eJ .\xd7f\xa8\x12s+\xc5#g\xe4\xdbŰ\xa7p\x11^\xf8\xb9\xa1\x1f\x1fx\xa0~v\xdf=\xab'L\xdcz`\x00/\x90\x86\x85\xe9_\x1f&\tSb\xcbs?\xbc\xf5\xa5+\xe3\xf2\xfd\x9dp1\xbcEv\xe4\xee\xef4~O\xa7K\xef\xf44z\u007f'\xb6\n\x9e\xc1\xb6\u007fu\xbf\x05\xfc\xd6w\v\bp\xe1\xa4d\x01V\x91v\xa1\xa9\x99\x18a\xf58\xbb\xd5bQ(\x8c\xbb\xd3h\x10\r\xa1\xe3\xe8\x14e-\xed\x1et\x0f\xb9\x8f\xbb9\xbbUo\xf1Z\xb0\xc5\xc2[u7\xf2t\x98\x18e\xd6kJ\x01%\x82\xa2l\x06\xbeY冿\x145\xb3\xbek8\x99\x10=\x04\x1e\xf0\xb6n\x9dS\xd6\xea\xd6\a\xc3A\xbd;Ҷ\xa0\xf3y\x97\xb7\xb6,\xff|\xaa\xa7\xc1\xafQ\x1e\xe4D\x81s\x05\x82)\xdd\xdd\xf5\xaa\x82h=\x02\xb4\x80ƓVy\xfe}2S\xa87yMQS\x9f\xe9\x94\xe9\v\xd3\x05\x93\x98\xbbu\xd3{\xeb`\x17\xec\x03\x02ZQ\xff_\x88Ǝt\xe2u\x88\x8e\u07bc)\xcb40~\xf7\xa6`\x02\xb7~\x0e\x84\x80`\xb6Z\x15\x9d\xab\xa1\xecY\xdc\x06\xbf\xb1\xd47Tj\x14V\x9bE\xac\x1f̍\x9bP\x8a⪣\x14W\x99\x91\x17\xcd\xfb%2梂\x96\x81\xa9\xaa1hE\xd8&\xef}\xe4\xd7\xfb\x87\xfc\x8f\xfb\x9f\xf5\x9f\xf2\u007f\xe1\x17\xfc\xac\xacjy\u007f\x9a\xaaO5\xa4\"*\xf7\x87\xfa?\xd9?\xe4\x87Goq\x95́&&$\xfa\xb8\xbc\xfe\xc3_\xd2b\xc7G\xcbfmik\xda<;Z6s\xeb\xd4ƍ\xdd1\xa9=\xd90!\x99\x9cА\xe4\x16\xccٓ\xad`ko\xe9>\x16\xa3\xfb\x81ŋ\a\x06\x16-\x92i\x9fEqP\xef(\x0e\xcaf*\x19\xecQ~\xaa\xfa\x84\xe7\xccZ\xc0Z\xe0\xb4\x04\u007fJ>\x892\xb7\xb5!@\xdfAB\xe8\x9f!\xa13\x9f\xd9\xe3\xd1>\x06\x9f\xe5\xf3\x151\xb6\xaa\x85$\x81\xcf\xedh\xeb\xe35\x06\x80\xa0J\x12Ǝp@\xaa\xdb\n\v`\xc1V)\x957~(c\x9ar\x84\x84[\xf8#\x860z\x13\x1fC\xc8\x10B\xdb\xff/2\x8a\xe8\xff\x92{G\xfe_mW\x01\xd7V\xb2\xee\xe7\x9b#!B\\\x0e\x04H\x80\x84\x84\x04\x1a $!hh\vI\xbb\x10\xb8\x15ʲ@ho\x95\x15\xf6\xf7\xbae\xdd]\xbb\xbdR[wo\xf7wo\u07baw{\xdd\xdd\xddݵ\x8473\xe7pHi\x9e\xbf\x87D\xe0?sf\xe6\xfb\xce\xe4K\xbe\xef\xff?/\xa2P\xaa\x92Ò\xc4a\xb34#a\t]\x88\x00\x01}\x81\x9b\xe19\xde2\x15\x93\u007fb\xa8/\x16S\xfaʗ\xe8\xeb\"\xee/\xa4/\xa2f\x82\x9d\x12>\xee\x04\xb3\x13\x9c\xe8\x1a\x04\xa4\xb7\xb5gt8\xc5nh\x97l|\x13\xa4\xcf煏\xd3>93\xe93@F\xf6\xca\u008b\x16\x11\xed\xf3 \xe4MY1h$\f\xa3\x1a\xd0\xd0.[\x10 \xd2\\\x1d\x15\xb3\xc5\xea¥\xdc\xfd\xfc4ӚX\x93j4TV\x9d\xa65\x81\xacf\xabך\xb2\x8eZg\xac\xa2\xa2;a\xa2\xba\x13(\xa2\xcaNLE\x8ae'\x14\x85\t卋kY\u007f!A?qZ\xe9P\xf8ؒ\xecD6;\xf0\xe0\xa6]\xe1\x9dTv\xe2\x91\u007f\xe9qwlL4\x12\xd9\th<\xf3\xc3F*;1\xb8$;\xd1\xd5\xf8\xed\xca\xe6:;\xf1\xb5\xcdDvb\x94\xcaN\xb4x\xba`l\xe5f\xb2\xb8(\xeb h\xf6Z\x1b\x90\x0fѵ_\xb3\xf8G\u0600\x1cy\xec\x12\x1a\xc8ې'\xf3U\xf5\xbc\xc6\x0f\xe1\x18\"?%\xf0\xf3\xe8a\x82\xb7\xa5\xf4\xac\xc1\x98\xabf\x19\x8dJ\xe1O\xa0l\x11\x1eׄJ\xe2gT\xfcI\x15_I\U0001cef1$~L\xc5Ϣ!\x82w\xa5\xca9LĹ8\f\xae\x98܂69\xad\x8dNi#\xa2w\x87Ɖg\x90\x0fߣ\x1c\xae\xaaj\xe7H\xabZ\x97Ҋ\x16\xb8\xc7b\x16kR\u058c\x10\u007fJ\xf65\x13\xb2\xa1;R~\x81\x87A3\bf\x18\xe4@\xe0\xc0\xcc\xd9x\x9b\xce8a\x11/\x04\x00\xb0\x1b\xcaL\xbb\x1c\x0e\xfa\xe2\xb3\xcadϘ\x1c\xc0\xdb\x05\x9c\xb3X\xcas\xc7up\x8b\x0e\x86tХ\x83\n\x1d\x94\xd1\nK\x93\xb5,\xa7\xe1L9Y(\x82p\x06\xdaT\n;)r\xfb>\xd0}1L\xd9\xf4\xec\xf3M\xf2\xa9OԢ\xa4\xef\xe4\x1aA\xaa\x19\xa1r쁼\xa0\x8a?=\xf5b!z\xb0p\x12\xef\xe7\xc7\x17~Ey\xee\xd8\xfe\xcfs`r+S\x8cPx\xf70\x83\xb3lM..ةF\x02Y\x93\x06\xb6\x8e\x97\xc1)\x84\x90\xfe\x05p6\xf9$/[r\x86c|bfϠ\xe2//2\u007f\x01\x83\xd0\x00\xd4_,\x12 \xc5B\xa5\xf0\xf3hR\xb6'k0l\xb0)\xe8\xe2\xfe\xf5E\xf8\x13\xc8S\x847\xdb<%\xf0X3\xa6\xe2gQ\x85l\u007f\fn\xe20\xa07\x84\x00\xad\xb0\u007f\x88\xf2\xd4\xd91\x9a\xe49\x14\xbeS<\x87\xfbW\u0381\xf2\xda\u007f_\x84\x9f_|\xa8hL3+\xe7\xb0\xf8;\x84\xb8\xa6\"\xfc\x89\xc5T\x11\x1e\x9d1\x878\xe9\xffG\x9a1\x15?\xbb\xd8K\xf0\x15/\xc8S #Z̯\x98\x05F\x11E\x1f\u008c\x14Am)wM\xd0\xedW9\xd2\xees\xcaw\xb6T\a\xc2^+\x1f\xa6Z&o\xf7\xbdmae]ߧ\xdeF\x1e\xaa\\s\x96\x86\xa0$h5M\xe1$Dh\x17\xb0*K\xa1=\x00,'\xaf\xb0\xce?|M\xe6\xc9\r[\xa7(\a\xfa\x9c{\xfa\a\x1f\x1f\x9d\x9e:\xe5\x8d\xc0\xc7\a\xb62\x02\xf4\x83\xe1B\xb2n\xb4\x85\x92\xa2\x15\x06zϾ零C\xf0UƄ\xae\xf5\xcc\xcd\\\xfb\xde·_\xca\xefſ\b\x13\x1a\xf4\xdeg.Zp\xdb\xec\x84\x18-ۍq[\x99_t(~\xd7#\xfb\xa9\xc4\xfc\xf4X\xbeڧW-]\x02?\x8f~#\xfb\x05k\x90\x93<*\xbat\xff'Н\xc5\xfd\x17\xf2\x9ep\x89\xfe\xc7T\xfc,:\xb0\xb4\xff\x05\xe8\xe6\xc1Iq\xfd\xca\xfd\x8fq\xff\xd81\xba\x951M(cҒ\x83lК5X\x1dS1\xffތ*P&\xd5$\x9a\xc01\xf1\x00\x02\xb4\xcb\xedq\xf7\xb9s\xee\xfd\x84$!X\xf4\x9a\x89\xb2\xed\x95\x153zˌ\xa8'\xdf\x1a-\xa7!f\x95\x93(\xe6\uf6ffOM\xcbt\x1ch\xa2}\nT\x06\xbe\x83\x06=*\xc1\x9b??\xba\xef\xcd;o\xa7\xb2\x16\x0f]\x91ߗ\\\xb8vӥ\x97\xcc\xee\xdd\xcd\xe1\xe9go\x18)\x10}\x91\x1f\xaf\xbe\xf4\x99B~\xcfe\xe7\xedB@yLBX\xf4\x10;\xcc\xfdk7\x8d\xed\xad/\xc9a\xbf\x91\x86\xfd\t\xf2`\xb0\x15Z\xd3\xe8\x88\x062\x1a\xd0\f\x86P(9\xe0\x05oڲe\xe0\x9a\x01\x90:\x06\x93\x9b\xd6j\xbc\xf6\xf6\xe1>H:~V\r\xd5#\x92\x9d\x16\x1a\x12vC\x9fRg\xf8\xfbw\xa8GR\x1e\x83\x99\xe4\x10\xc9ok\x8b\xfc>\x80_N;\x14\xbdsR\xeb\x0ey\xf9M\x01+U\x96C;\xfe\xd1p\xdfY}\xe1\x8a؆d\xebP\xcc\xfd\x8b\x1fn;\xd0-\r\f\x8d\u058d\x8e\x1b\x1b\xd7w\xf7m\x8e9\x1b{\xd7\xf56n\xdfz\u07b9\x85Ϯ\xbf\xf5\xad}{_\xbb1\xc3\u007f=\xb5u\xa8\x93\xe4\xc8\xedu\xc1h\x83\xa7\u007fx\xb2\xfb\xd9c\xd5u\u007f0{$SO\xaf;\x14j\xad\xf3\xa5\xfa\x06\x82\xb1lo{\xac7ؗݺ\xfb\x9f\x87\xf9\xbd\xe7?4\xdb\x1e\xdex\x99ls\xc6m\x13k\x89\xcdײ\x98g\xcd#\x88\xeef\x11\v\xdd\xcd>\x9ao\xef2\xd4(6/\x8d\x9f?\x1fQ\x0fa\r\x0eD\xe2*z\x19\xebU\xb1'Т\x8a\xf5\xc5W\x97\xc0\x1aU\xec\xc9E\xb9\xdf6\x8a\x8d\xf6\x97\xc0\xf2*vVDrd\xd7ki\xe0p82,\xa3)\xbc\b\xff!\x15\xff.R\xf0\xed\x14O\xea\x0e\x8a\xf1\x88\xa3x\xf1Y~\x1fj'\xde3\x84>\x90\x9a\xd6\x19A\xa7\a\xbe\x06t\x18\xb8\x9at`\xd0\x12\x8b\x95\x872\xa0\xcd@&\x13\xf1oɎd粸+]Q>6\x1cK\x06\xd6\x05p0\x00\x81Q\xcb\xd7\xd3?O㏥\xe1\x8a4\xa4\xd3M\xc1\xb5\xb0v\xc4k\x96\x82\x0epd\xab̽\xd92gS6\x82\x9c,\xae#\xdfSQ\xf2+GwQ*m\xa2H\aЛ)\x9a\xb4\xffy\x1b\xdd\xfb\x10\xcb\xd0\n\x94&S\x9a%芫\xb4(\x85\xa8VL\x1aT\x18T\xfc\x9f\xae\xff\xca@xӶ\xcb7\xac\xbd|*^L\x1e\xfc\xca\xf9W\xbd6\xf7\xe1+\x96(S\xd3\xc7\xc7)aj\xe1\xbe\"B!\x146\xc8,\xabHƾcO|8*\xadd\x17^\u007f\xadʧ\n736\x15\xae,\xe6\x1a\xc2\xefd\xfe\x15\xf3)ZoM\xb9z\xc4>\x83J\x8c\xb9\x1b!dN\xe9`4\b\x8e\xa0W('晊1,\xe3\x9c1\xff\xcb\xc8\xfe\xfa:\xf3\xd7\x10\xf3\xd7\x17\xf2\x91\xb8QR\xfd\xb5\x14~~/\xf3+\xd6\xe0\xbcP\xab\x8a.\x85=a]\xc6\xea[{J`y\x15;kU|*I}\xca\x10\xca0\xb4ꃋ_B\b\xbe\xc6\xfa~G\x89-oAt\xe4\xc0[\xe4\xd8Rk>-\xb6<\x80\x10\xfe`\x11~~q\x00\xb1Ѱ\x06\x13\xbc~Ely\x98\xe0\xbfT\x84?Q\xf8Yq\xff\x85\xbc\xdeU\xdc\xff\xe2\xf7\b\xfe.\x91W\xf1\xb3\fOcK\x9bE\x89-\xf9\xba\x15\xb1\xe5z2\xe7\x8cX\x8f\x02(\x81\xe6S\xef\xd1\xfb@\x16\xafչ!\x98\xfe\xac\tL[\x92\x9fLB$\x99K^\x9d| ɷ\xa4߭\x80\x8a\xb1\x8e\xcfv@mY34g\xaf\xae\xd9_\xf3@\rWS\xc3kb\x10\xcbڝe9\x1e\xce\xe2!\u0083\x9b\a\x1eP\x1f\x8b0Y\xba\x8f\x12cۨ.\x13\xb9g\xb7\noa\x15\x04\x125\xe0J\xb8V\xa8Ҳ\x00\x80\xfcS\x13\x00\xb9\xe6^\xe1\t\xc0\xc7벡\x99u\xd7$\u007f\xb0\xfdX漴/s\xd3\x1b\xf3\x94\xc8п\uf46d3\xb7x\x1dU;V\xfft\a\x14\\\xadgEϛr\x91\xcdwv\x92\xd0\xe7Z\\O\xd7\r4\xdc~tv\xe0\xde\x17>w\xe9]\xdf:<\xb2\x96\xf8\xf3e\xaf]\xbb\x86\x04(\xe3\xf7t\xdc}\xef\xc2\xee\xc8\xd8\xea\xc0\x9dw\xf5\xed^\xdf\xf8\b\xb1\x17㈰\xfd\xeb=\xf2\xfe\x15e\xab_\xc3V\u007f!\xefi6\xa03\xfcr;\xb3\xd7\x06ُ\xdfe~\xece~\xfc\\\xde\xdf\xe44\xfc\x87\xf8\xf9\x9d\xcc\x1bX\x83\x11o@E\x97\u009e\xb0\xab}ӱ\x04\xa2%\xfb6\xaa\xf8\x93V\x86\xafg\xf8Sy_[I<\xaf\xe2\xa9\xef\xd3\b5B]\x9f\xb6\xf0\xf6\xb2\x16jl\x8184\x8e\xbe͛\xf9\x8f\xb2\xba\xac0\x9aI%\x8c\x0f\xd6A\x1d\xff\xae\xf4%\tK\x12\xf4\xe0a|%\xbe\x13\xf3z\x1c\xc6\x187\xeb\x01 \xf0\xdc\xf7\xaa\xc1T\xddW\x9d\xab>^\xcdWk\x9f\xbb\xdb\x066\xf2\xbe\xf9\x05\x92\u07b4\xa1\xe7\x00E~N+\xcdde^\xaaPE݆V<)\xfa\xa5\xc2\u007f\xa2bK\xb4M\x17\x0e;\x1b\xa2UUm4\x03\xdeVU\x15mp\xae|\x8e\xb7\u007f\xf3\x9b\xbf%4/\xb3\xb9\xbe3\x14$t\x00\x8b\xbf\x13\xff\x80~\bc\xf6\x91?$\xe9\x1f\x92\x88#k\x92\xe7\xb7\xf3\a\x90\x1bգ&R˺\xf1\xa7\x01\xf8J\x00.\x0e\x1c\n\xe0\xfa@4\x80\xed\x01\xf8[\x03\xfc\xdc\x03/y\xa0\xc9\xd3\xed\xc1\x95\x1eЙA\xc0\xe0w\xc2w\xb5\x10\xf0T\x95\xf9$_\x87/\xe3\xe3}i\xb4eUn\x15h\xaa\\U\x89\xaat\x15_\x956\x8e5\xd7а\xdcO\xf2\x18\r^rS\xd3\x10*\xab\x95j;j3\xb5|\xed\xb0ͬ\t\xb9B\x89P:ć\x86\x05\xe7r\xd6<\x1c\x96)\\\xf4Ƣ\xbe~\xa8_˂4\x9a\x80Z<\xa1T\xfc(\xa1\xb4\xb8\x14I\xc7\xf9\xed\x94\x0e\x94\xdcݒ|x\xed\xc0\b\xa5\x04\xf5\ue376>\xd8\xdb7\xb8\xd0i\x00w0\xb9g\n\xf6\x8b\x85?Y\xfc\x8e=Ӹ\x952\x84\xec\xd6鉹\xf5\xdf\xdcOYB\x95\xae\x91\x99m\x99\xcf\xdf8\xd6\x0f\x9f\xa8x\xec\x89Tk![\xa6y\x9a\xf8\xd3Q\xd5_\xc7\xe5s\xe1\x1dv\xee\xb8ٹs \xef\r\x18*\x8a\xfd\xaf\x18\xafĩ\xd7*{#m2\xea\xae\xfb\x8f\xf0\xf4|h,\xea\xffo\xf9\xbaU%\U0007c29f\x8d\xc8;chigt'Y\x8b\xe2ؙ\xd51\xb3cL\xca\xe7g\x8f<\"\x0f9\xc8{=\r\xa8\\9\x82ʓ\x12\xedȇb\x8c'e\x00\xa1\f\xa2iݖD\xd8\xe3&\x19\x92x5r\xbb\xed-\xc3&'\xf2\x0f\vf\xa5&\xa6\xafO\x0e\x94\xc9\r+x(VI/\xa6L)\\P\xd7\xe9d)\xf8\xbc=\x919;A\x99\xe0\x1d;\xefڒ\x98H\xc7Id\xc0s\xfc\x12C\xf4\x13ń)\xeeӔV\xbfđr\x90s\xc1[S\xd9n.t\x95\xa1¥\xd2\xea\x9az\u007fv%mJ]\x03^]\x83\xd9o \xf9\x1d\xc7*\xbap\xe0\xe9A\xe5ꪩ\xfcC~\xba4\xff\xd0\x04\xa5\xf9\x87\x04O\xdb>O\xf2X\xbf\x12k\x91\x9d2\xe0\x10\xb2\xff\x8e&2D\xcd?9h\xe4\x803\x95\xe7\xca\xe7\x88@tyQB\xab\x8d\xe6\xe3\x14\xfd\x87(\xd5\u007f\xa0\x05\x0e\x1aZf\x9b`2\x10\\m\xf4\x82\xf6\xf8\xee\xe8'+\xfb\xfa\xbb\x9d\xb0\xdd\xd1\xdd\xdf_\xc1\u007f\xbdn\xac\xa3c\xacN\xces-kB\x90\x11~\x95[/~\x88\xe5\x14\x0e\xbd\x8c\xac\xacVÚ1H䦌ްZ\x8d\n\xf2\xc0^F\xaag\xed7\x98\b0\x87\xdedI\x18\x11UR\xc6F+\xf9G\xe5\r#\xba9\x1dF:\xd0=\xe9\x82#.p\x95\x19I\xdef\xbf\xc9\xe41\xe5Ls&ޔ\xb2\xb92\xa6'\xaa\xe1`5T\xd3Vz\xf2\xff\xea\xfd&!'`\x81̋TEM})\xcc\xee 2E\xee\xbfD~[[Je$4\xed\r\xaa\x80\x01\xb7>00\x19OL\xf6\xfb|\xfd\x93\x89\xd8\xe4@\xf0\xfex(\x14oi\tŅ\x8fv\x9c\xdd[[\xdbsv\"1\xdeC\xee\xb7t4G\xa3\xcd\xcd\xed\xedt\xddO\x92y\u007f]4\"7ʤZ,w[a\xce\nV\x0e\xf1\x17\xf2\x98\xb7f\b\xe0.\x13\xa9ʘ\xd3^\xad\xfd\xae\xf6\xb7ZQ\xabM\x1b\xc1x\x17or\xe5\\\u0605h]\x1a\xad\xbe$723\x9cZ\x82\x86\xa4\xf5\xb27Ӏ5*ǵ\xf0\xb4\xc7o\xf8\xa0\x18\xec\x1f\x8f5f\x87\xcf\xf2y|\xe5\x1f\x14\x1bWoi\x0f\x8e\x0e\x9d%\xac\x8e\xc5G/\x1a\xf2{\x03\xb5\xed\U000512c6\xfd\xb5\x81Z\xe5\xfa;\x90\xe7\x8e\xe1\xa3,\xe7\x8f\x05\x84^\x01%\xe7\xaf\\ve\x037\t\xf9\x9f\xfcd\t\xab\xf9\x0f\xb1\x9ae\xac\x8b{\x1b\x1ef\xfa\x8e\x03\xa96\x93\xd1c\x8c\x18\xb9F1)\xae\x13_\x16yQs\xe8QCހ\xe7\r7\x19\x0e\x1a8V\xb2\xe97\x80hp\x1a\xb0\x01\f\xf7\x8a\x1c\xa2B\xe4J\xe9\xb0̗\x0e\xf4rt\xaa\x1a\xbf\xd1\x18X\xd5^\xf1M&9ν\xed\\\x92\x18\xbf\"\xb6\xe2\xb8kR\xad\xcaq\x99P\xba\xa2\x91\xce\xf4\xd2U\xa1tE\x0f\xfd\x05:$\"\x83\xae\x1c\x96\x1eR\xb9~ޒ\xf29\xbc_V:\xff\xa6\x91\x1eVx\xc1\x99\xb8\xf4\xca\xcb:\xbao\xb8\x81\x1d\xf6?\xbd\x9e\x8ez)\x1dvY\x9d\xff\xbf\xeb\xe9`:\u007fn\x1f\x9b\u007f\x05\xba$5ar{ܸњ\xb4\xae\xb3\xbel孶C\x8fJy\tG\xa55\x12\x9e\x97n\x92\x0eJ\x1c%\xe7\xfa%N*\x97E\x9a_\x16xA<\xf4\xa8>\xaf\xc7\xf3\xfa\x9b\xf4\a\xf5\\T\xbfF\x8f\x05\xbdC\xef'C~\x10\x01\x92\xf4\xf7Z\x05\x8ee\xc9\xe8\x18\x15\x1bE\xa2\xcb\xc6\x02\xc5V\xb5\xfe\xa5\a\xf0scP\xb5Z\xe1g\x05\x8f1P\xf4\x94\xfb\xea\xb2\x15O3h\xe9\xf9<#\xbd*\xc9ž\x97J\xb7JG$N'UJ!:\x87C\rָu\xd0\xfa\x02\x99\xea\x836\xb0\xfd\xa7+\xff\x02\x9d\xadU\xb8W\xd2+\xf3Q\x8d\x1f\x99*v\x83\xa5\xb5\x17T#<\\\xe4\x0e \xc1\x0fN\xb3\x89\xf0\xe0\xb2{\x14y\x8a\xa2\xe1\xcf\xdd\xcf\xfc\xa4\x02=\x91\xba\xfeA\xf7\xf3\xee\xb7\xdc\xdfs\xff\xce-P\x02\xe3\xd5n\xce\xebna3T'\xc7&Zj\x86#\xb69\xdbն\xfd6\xde\xfc_\x9d*!\xb5\x8e\x889qN\xe4ؤU_\x93-\xa9\xceX}\xb8\xd2\xf5\x84\xd2>Ȧ_\xda%]\xcb\x0f\x15.+\x1c\xc3\x0f\xf2\x8dȆҩ\xe6\x19Dž\x8e\xbbIV\x9f79<\x8e\b)J\xce9\x84kt\xa0\x93ž8\x0f\xc1\xb7\bo\t\xf4\x02c<-?țH\xf5\x81\tE>\x1df{\xf7\xa7\xa7J))lk\x1a\xde\xd3ӵ'\xdbܜ\xdd\xd3ճg\xb8\t\xffj쒡\xfa\xfa\xa1K\xc66_2\xec\xf3\r_\"\xd7\xd7O\xe3m,O\x1cN9i\xb5\x9c\xc0\xf3@U\xb9\xcaH*\xb8(\x0fLV\x86\xaa\x10\xb7\x9dV\xfd\xb6\xa1T\xb1\x1bBg\xf4K\xfb\xa4\xe9f\xa7\b\xa2үxZ\xbf+r\xc9x[\xa9\xd41\xed\xb7p1B\xf8\x00ӵ\xf2\xbcx\xfaE\xa8\xb8\x15\x17\xa1\xa2\x97\xeb\xc3\a\u0605\xf7\x145+\xeaw\x06\xbc\x1e\xffEx\r鐃\xb0V\xbac\x8e\x01\a6\xb9<\xae\x88+\xe7\xfa\xae뷮E\x97F\x8b\xe2x\x10c\xfc=\xbd\xa6\x8d[\xcda\x8es\xba\x1cB\x8aPW\x04\x10~\xac\x15\x1d\x0eΤ\xf9\x16\xf7m\xe5\xf2@2\x8b6\xdc\xc6t\xf1\xc9\x13\xb2H\xac\x16\xb2\xdeA\u0530b\x10e\xd7\b\x8a\xb3\xda\x03\xfc\x97+\u007fS\xb8\x16\xae\xfdՕ\x85\x0fr\x9c`s:\xb5W\xe3\xef\x1f:t\x14\xf7/|\xfa\xd1\xd0Tky\xb89d|\x13\xfd\x1b=\xe5s@\x00\x00x\x01c`d`\x00\xe1p\xb3\xa6\xeb\xf1\xfc6_\x19\xe49\x18@\xe0\x84\xb8\xff/0\x1d\xd3\x16\xf9\xaf\xfc\x9f\b\xfb:\xf6b\xa0:\x0e\x06&\x90(\x00:\xfe\v\x9b\x00x\x01c`d``/\xfe'\xc2\xc0\xc0\xc1\xf0\xaf\xfc_%\xfb:\xa0\b*\xb8\f\x00\x80\x88\x06\v\x00x\x01m\x92C\xb4\x1eA\x14\x84\xeb\xef\xbe\xfd\xcfĶm۶m\xdb\xf66\xb6mۘ\x93u\x8cUl\xdb\xd9ęԍ\x93\xf7\xe6\x9c\xefT㶪\xc6ǏO\xd5$'u1\xc2\x1e\xc7\x04\x97\x0f\x05\xa47\xbaz\xf1Q\xc9\x1dGS\x93\x05\x13\xccA4%\x15\xa4=*q\xae\xb5i\x8aJf\x1e*\x98\xda\\\xd3\x10\t9V\x89\xb4'\rI\x19\"\xa4%\xa9OJ\x91ڤ\x92\xd6\xebZ\xdd\xe3\x17v;\xd2D_\xa2\xa7+\x06\xb8l\b\\\x1a\x8crK\x11\xc88ҝ\xfd\xd3\xec\x9fE`3\xa0\xaa\xd9\x18\x96qI9^\x1b\x81\xd7\x10A\xb4:\x89\x8fQr\U00087eb8\x9c+\x82\x8e\\\x97\xc7\x1d\xc6N\xb9\x03x}\x01'\x80l'\x8dQ\xc4l\xc4,\xde9>\xb5\x98T\x85o놟ee\xa4\xbe,\xe6}o`\xb5\xfd\x8a\x9er\x83lDO\x9b\x06y\xa4/\x92s\xfdj3\x04\xf3̐p\xa7\x04\xda\xc6jo\nV븜\xd6z*\xd7\xd81\\\u007f\x05]mc\x14\xe2\xdcR\x9e\x87\xe80@\xba\"\xae\xb6\xedf\x942O\x90IzG\x8e\xffP\xb4\xfe\xe5=۳HSҞ\xf8Zc\uf86f\xac\xc4\xfb\xe8ft7\x8fPHv\xa2\xa9\xaeQ\xefuL\x10~\xb4\x03\xb4\x9e>\xfa(D\xca\xe9[\xe8\xc3jW\x01C\xd4\xef\xc8\xfa\xf0\x1a\xc7ۛ\xb7(\xc3\xf5͙kA\x85\xb4\xa5\xf7U\xd5\xf7\xd8\xf0:\x03\x9a\x85\xe6\xf07\xcc!/\xa9j3\x84\xaf\xa9%\x99U\xa1_9\xfc\x0f\xef5@U\xb3\xf8\x1b\xcdB3sðZ}\x8f\r/!Zj\x16\xb2\xf1_\x98\xc1y\xfa?\x87\xba\x98ܦ?u\u007f\xe5\x10\x03\xfa\xa2\xaaY\xfc\x8df\xa1\x99\xa9j\x96\xdcc7}kN=*@S\x9fD\x02\xa4R\xf4\u007f\xb5ӐJ\x91\x1cl\x1bt5\xfb\xd1T\x89\xc4\xfd:\x8a\xde\xc6\xff\x06\x00\x9b\xb5\xb3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00X\x00\x86\x01$\x01\xf2\x02\xa2\x03^\x03\x86\x03\xb6\x03\xe4\x04\x1a\x04B\x04\x86\x04\xa0\x04\xd4\x04\xf8\x05>\x05p\x05\xbc\x06:\x06\x82\x06\xf2\al\a\x96\bR\b\xca\t\x1c\t\x84\t\xca\t\xf8\n>\n\xae\vf\v\xda\f\\\f\xb6\f\xf4\r0\rr\r\xd2\x0e\x10\x0eF\x0e\x82\x0e\xda\x0e\xfe\x0fz\x0f\xe8\x10<\x10\x82\x10\xf8\x11l\x11\xe2\x12\x14\x12T\x12\xa2\x13\x88\x13\xe6\x148\x14\x8a\x14\xae\x14\xd2\x14\xf4\x15(\x15@\x15p\x15\xe6\x16J\x16\x98\x16\xfc\x17n\x17\xbc\x18\x9a\x18\xe4\x19$\x19\x82\x19\xdc\x19\xfa\x1ap\x1a\xb8\x1b\x04\x1bh\x1b\xcc\x1c\x18\x1c\x98\x1c\xf0\x1d8\x1d\xa2\x1e\x80\x1f\x1e\x1f\xa6\x1f\xfc ` z \xe0!F!F!\xa4\"\x12\"\x98#6#\xb4#\xda$\xb4$\xfe%\xa4&*&|&\x98&\xa0'L'b'\xaa'\xec(F(\xd6)\x06)T)\x90)\xc4*\x14*P*\xae+\x02+(+R+t+\xea,\x02,\x1a,2,J,d,\x80,\xee-\x02-\x1a-2-J-d-|-\x94-\xac-\xc6...F.^.v.\x8e.\xa6.\xc0/\x18/\x82/\x9a/\xb2/\xca/\xe4/\xfc0J0\xde0\xf61\f1\"181P1h2 262N2d2z2\x922\xaa2\xc22\xda2\xf43\x823\x9a3\xb23\xc83\xe03\xf84\x124\x825 585P5h5\x825\x986\x046\x1c6:6z6\xf67F7\\7r7\xae7\xea8.8\x968\xfe9~9\xc29\xee:\x1a:::\x8e\x00\x01\x00\x00\x00\xd3\x00g\x00\x05\x00U\x00\x04\x00\x02\x00\x10\x00/\x00Z\x00\x00\x01\xcd\x01&\x00\x03\x00\x01x\x01}\xcf\x03nd\x01\x18\x00\xe0o\xed\x8d&\\\xbd\x18cD\x1b-k\xbb\x8d\xc66\xef҃\xf4z\xe5\xcb\v\x8a\xdf\x06\xde8\xf3³\x97\xef<\x13#\xb4\x9f{+\x16\xda/\x14|\r헾\xdb\t\xedW\x12\xfa\xa1\xfd\xdaW\xe7\xa1\xfdف\v\xff\x8c\r\xb4T\x05\xf6\x95\xf4M\xfc1\xd0U\xf5\xdbDEM_U\xcdX )x\xb0#\x88z\x9e\xce\x1e\xa9\x19\x9bh\x19\xe8\vd\xa5dd\x04\xcafZ\xba\xaa\xb7Ѭ\x9chR4'\x19\xcdi\x9a\x9a\x1a\xfa)-mq\x8b)%C%\x15M5)\x03c\ri]\xad\xf0\x8b\x89\x9a\x89\xb4\r\xab\xfe\xfao˾\xff\x92rR2W\xe4\xf41\xa3x\x01l\xc1\x83\x01\x03A\x00\x00\xb0\xf4k۶\x8d\x91n\xe1\x0e\xd4.\xf0\x89\b\xf8\x05A\x9c/\x12\"I)i\x19Y9y\x05E%e\x15U5u\rM-m\x1d]=}\x03C#c\x13S3s\vK+k\x1b[;{\aG'g\x17W7w\x0fO/o\x9f\u007fA\xf0\xb0\x05\x05\x00\x00\x00p2?foٶ\xed\xb5\xbd\x9b\xf5\xb2]\x97l۶\xf92\xcf\xf9\xe5s3\x9ah\xaa\x99\xe6Zh\xa9\x95\xd6\xdah\xab\x9d\xf6:訓κ誛\xeez詗\xde\xfa諟\xfe\x06\x18h\x90\xc1\x86\x18j\x98\xe1F\x18i\x94\xd1\xc6\x18k\x9c\xfd\xb6\x99m\x8e\v\xd6\xfah\xae\xa5\x16\xd9`\x8f\xed5jZX\xa3\x96YV\xf9\xe5\xb7%֙\xef\x9a\x0f~\xdah\xaf\xbf\xfe\xf8g\xab\x03\xee\xb8堠\x90\xe5\xc2\ue278\xed\xaeG\xee{\xe0\xa1O\xa2\x9ez\xec\x89Cb~X\xe1\x85g\x9e\x8b\xfb\xe2\x9b\x05\x92\x12R2Ҳ6\xcb)\xc8+*\xa9(\xab\x1a\xef\xb3\t&\x99h\xb2\xa9\xa68m\x8b香a\xa6\xaf\xbe;\xeb\xa5W\xde{\xed\xa8cN:\xe5\xba\xe3N\xb8a\x9e\x8b.9_\xa3v\xbdJ6\x11\b\xb4\xeb\xd8 W\x8d\x14K\xa1\\1\xd28\x9a\xab\x14K\x95|\xa4\x98\xc8\x15\xff\x03@\xec\xfe\x80k\xde\rx\xd7\xf4\x8eHo\xa1\xcd\x1b\xff\x1b\xfef\xc2\x1b\x9d\xf2\x14&>\x82S\xe2\xe3ч6'\xd3xM\xe8aa\t\xfb\xfb\xc7K\x1fx\xef\xf1\xfb\u07bb\\\xfaS\xf3g\xf4\x1f2\xea^\xf6\x1e\xf6ܓ\xeeỤ\x94\u07beUJo\\\xf7\xd2\xffWJ\xa9t\tΞ)\xa55'G\x9f\\v\x92\x9c\x12\x97\x8e\x9e\xb4صZ/O\x1c\xcb/f\x972\xa0\x895{\xd3\x05%\xacێ\xd0\x0e\xb1\x83$\xb7\u007f؎\xb7m-\xa5\x9767mƩx)\xa5\xb1n\xb1P\x8c\xf4\x8f]\x8cec\xe4\xec\n\x98\xf4\xfb\xf0o\xcc\xe6Cۙ\x81\x99\xb8\fܼ\xd4&\xa8Q'\xa8A[\xee\xe2$\xf0L\\615\x91$'잀=\xe3\xd5\xf1\xb8\xdd\xc8\xe0H\xdcv\x04\f\x1fj\xa7\xc34l;40\x14ۼV\xae\a\xc2u^\xc2M\x04\xba\x91\xdd\x04\x17*\xe5J'卢3Z\xa3-\xa3\xd5\xd1}Q]\x9aõ\x886\xe5Ú\x16\x11c\x11\xf3\xc8\x1fe\xcc\xfaC\xb0?\xb0~\x10\xec\a\xc3\xc44\xb1P\x10\x8f\xb0\xb9s':\xd9#\xbf\x88\x85Y)͝\xab;\xd3\x14\xa7\xd7\xc1-`\xe2f\xaf\x89c@\x1c\u0088\xa7MY\x13\x96L0\v-Ei\xf4\x01\xe9\xcc\b\x969A\x0f\xa7 \x95\x89ȝ:\xf5>e\x14\x83z\xab\xf9\x03\x86\xa9\xb0Nm#\xe7h\xcd\xc0\xa1\xaaa\x9d\x8a\xf8\xd0a\x833\x00\x89!\xab\xe3qTW\xd9[\xad\x96\a\xab\xa3+\x87\xf4V\xc7kBMNX\xa6\t\xe6ʌ\x13\xd55W(&$\x00\x041\fl]\x9c\x87\x18\xba\xdfD\xd7?;\x13 \x0f\x99kPTkS\xa3:\xd8\xcfX\x1e\x13\xa6\xaa8e\xb4\xe5C\x8f\x11\xa3\xd24a9J\x01b:\x99\xe3ߟF֗") + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_700_woff_bytes() ([]byte, error) { + return _third_party_swagger_ui_fonts_droid_sans_v6_latin_700_woff, nil +} + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_700_woff() (*asset, error) { + bytes, err := third_party_swagger_ui_fonts_droid_sans_v6_latin_700_woff_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/fonts/droid-sans-v6-latin-700.woff", size: 25992, mode: os.FileMode(416), modTime: time.Unix(1436317036, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_fonts_droid_sans_v6_latin_700_woff2 = []byte("wOF2\x00\x01\x00\x00\x00\x00,\xd8\x00\x0e\x00\x00\x00\x00[D\x00\x00,\x81\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x16\x1b\xa5@\x1c\f\x06`\x00\x81\f\x11\f\n\xfb@\xe6n\x016\x02$\x03\x86L\v\x83(\x00\x04 \x05\x82d\a\x83g\x1bXN%㘕\xb8\x1d @Um\xc2(J\")\xc9\xfe\xff[\x02\x1d2\x84\xa2)\xa0\xf3O$\u0084\x15фi\x86&:\x10\xb3\x16\xf3\xcc3i^\xe7&\x96\xf4\xb2j\b\x93\x8f<\xb4\xb2G\xd9Կ\xc2$n\xfdK\a}℣\xd75\u007fC\x93\xba)m\xcd\xc2\xc1e\x8d6ŕ$\x824\xe9#;\x9d\v]>\x05\xa2\xd5?\xfaQ\xad\x169\xda\xd7\r\xa5\x84'|W\xfa4\x1aY\x06L\x9cp\x19(\xe0\xb6_i\xf7\xca\xc0{[\xf8$\xc2\xd5\x10l\xb3\x9b\x99`$\xa2\"!\x12VQ%\x02\x92\x82\x05\xa8\xa8\x18\xd13z\x9b\x9bK}{.\x8a\xb9ʟ\xeb\xcfm\xef\"\xe2c\xdf\xea\x9c\ry\x19+\xe7\x17FΈ\xabp\x97X0\xb7\xb9\xdc\x1c9\xbc\xd2\xccC&\xf4d\x1f\xe2B\x81\xe7\u007f\xbf\xe1\xf1\xfb\x1e\x8a\xa0\xc5ni\xc6`0\xb48XX˻\x01phS\xa5U\x1f\x06\x90_!\x9a\x10o\x9a\xb2\x19\xa6`\x19\xbee\x9bu*Ѱ\xf6\xef\xb7yT*S\x8f\xed\xd3\xf2RP\xb1'\x93d\x05\xf0\x0f\xfd\xfb\xf7\xa4\xc2&5*?\xdd\x14\xa8t\xb1\xde\x1c\x81K\xe5\x95\x03\xdc\f|\xfe6\xbc[:\xff\x93l\x87\xb4\xab\xc0\a\xec\xe1:\xae:\xdb\x1bx\xc0\xa1\x03\xb6\x1d5\xeb6\x99\xeb>\"ů(\xaf\x01\x02\x0e\xeaοNs\x95\f\a\x18\xe0\xe9\x8a8L\xbd\x9b \xf9\v\x80\xb3\x87\xb3\xadf\x8d\xc6lN\x97\x15\xaa\xaa\x13JV\xd8BN\x139\xf2\xed\xc3\xe0\xb0jj:\xedoL}\x1f^M\xc7\xe6R\xbb\x8cAC\x11\rA\x04DD\xe2\x1f\xb39\x06\xc8yd\xa5\x80^\xde\xc2<\xbe\x94\xd9\tf\xcd\x01OE\x11\xb0@\x01B Z\xcd\xfeoY\x99\xa3\x90\x89\"\xac\xc1\xec\x1bT\xc5\xd3LI5\xda^\v\xbcy\xe7C'5\xe3~!\x1f_\xad\xe8\b\xb1K\xf6u\xf9U\x055\xaao\xf5s\xe4\x151\x16\xd2\x1a\x13\x1f/C\xd2\xd75\x18?2+\xb9\x9e\xfc\x9a\x8a\xa9\x8b\x01\x1a\xbc\a\xaf\xfbT\xbc\xe5\xa7\xfeZ\xd2\xfd\xe9\xa5\xf4u֕~\fbv\x1a\x92\a\xe4G\v\xf9\xfe\xb4\x98\u0093O\xcb\x10\xb4C\u007f\xf4n\xfbZ\x89N\xdaQ\u007f\xb3W\x83O\xfaFv䎓\xb7\xad\xd3}x6O5\xef\x8d<\xde\ta\x12\xa6_\a)\x19\x86\x8a\x06c\xe6%\b3\x16J6Y\xb1\xe8\x8f[\xb388lX\x9ap\xecX\x81\x04\x9c\xa0\xc1\xc0\x03\x16\\\xbc\xe1'\xab\xed\x87\xc0\n\x10\x81\x93\x90\t\xa4\x90\x0e\xa9M\xb7\x10=F\x85Yg\xa3\x04\x13\xb6\xa3\x9a\xb2\v\xdb\x01\xcb$\x8e:E\xe9L\x92\xcdr\xd1%jW.\xce\x16,\u088bQ\"\x89D\xa3\xe11r\xc4B\x89#\x12\x9f'\xc62\x01+q\"\x82(\x86 \x83\xa2H\xa9 T\x90\xdan;\xe8ЩKwv\xed\xcfA(C\x86\x8dd\xb7\x13\x99\xdf\x05\x8bvZ\xb2\xcbA\x87\x18\x1c\xb6\xech8\x05\xe5\xcc\xceP\x88E#\n\xbb9r\xa2\x12\r!\xe6ƥ\x9e]\a\xd6\xdb`\xdcF\x13as\xb6\xd8j\xdb\xec\xf6\xd1\xcdl\xd6\n\x02\xa2hDa\x1f\x8b\xf6Z|uVY+\xeb91\x14k\xac\xe0v\x0eS[\xb3\xb2\xf5\xddb\a\xdd,=v><\xa2\x91 h\x87\x811\xb1\xb2\x9c\x11\\ᏉN\xca\x15\x1fɕ\x1e\xf9UljBI%-\xb4\x9dv\x1d:u\xe9\xeeH_\xd1}\xd2\xd4\xf9\x06\x996c֜y\v\x16\xed\xb4dW\xf6\xc0=x\x0e18l\xd9\xd1p\xec\x1ew\xc2\xc9pjϬ|\xf1~;+D\xae\xaa'U\xc1\x11\xea\a(\xfaY\x12\xd4\xf9\x80*\x97\xae\x95F\x1az\x84w\xbb\xba\x82\b\xbd\xdcxJ<\xdd\xed\x92H#\xb7\x18UU\xd5\xfac\xf5q\n\xf9ƴ\x19\xb3\xe6*y\xc1\x97;\x10O\xb8\xf2\xb1\x1cω\x9ct\xa8\x8ezZR\x9bf\x9a-\xd6\xc1\x1f\xe9\\\x89\x16\x06`b\xfd\t\xa7\x82\n+JJőT\xe2q\xd3\xd4]\xc9+\xd4_\xe7\\\x9d\xa9|\x93\xe9\xccd6s\x99\xcfB\x16\xb33Kٕ\x03=\x98\xfdء\x18r8\xcb9\x9ac=\xde\x13\x89\x93iE\x8e\xab\xb8\x1e\xcc\x13\xdd\xce(\xb3,\xc9ZR\xd5WG\xdf7߅.vg\x97\xbaK\x18\x92d\xa97\xab\x03X\xd7\xe2\x0e\x9f\xe2C\xfd\"ҋ\U000ea28aږ\xd3b\xbd\"\"\xb2>\x95-PDDDDDDEDDDOUc\x81\xa5\x96\xb8\xd1rR\x01\x12%\x8cB\x9c\xc2\xd3g@\xb4\xb6Ѵ\xe0\x10:\x8a\xbc~\x8d45!\xe7\xa4҆\xdf8\xadVk\xca7a\xba3o\xcev\xce*JS\x9d\x142\xde\x1d\xb1\x1e \x1c\xcc.m\xba\xea\x89l\xb3\x92\r梔\xa6\xa1u!;,\x16\x16VVw\xdeb\xae_c1\xf5\x02c\x8e\x9bm3\xf6\xd6\xe5\x83\v\xb8\x97\xea3\x06\t\xf1b\xb1ɂ\"\xa9\x85*\xf4\xcea\xa0\xbc\xfeT\xce$F\x19,H\xa0\u05cf\x95.\xd4\xd3n\x13\xec!u\x1d\xfc\xfd\xe4\x80\x06n\xff\xee\xe3}\xb9\x0e\xbd\x9c\u007f\xff\b4~\xe9\x11*^\xb9m\xa5\xa5\x93M/g\xe3\x1d\xf3\xa8\xaf\uf62dt\xe0\xd2s\x90+G\x1f\xda\xd3>Qs\xe6\xa3s\xeaup\xe8\xf5FJ}qy(\xfa|\x84\xa3\f\xda\xc3X\xdf<\x86\x18\xf5d\xb3yd\xdc7\x81\xfd\x8e\xbf\xaa\xc4\u007f\xa7Iɴ\xf1\x8a\x91I\x85\b}\x9e#\xcc\n\\\x17;GL\xf0-\x0e\x83иT\xf2\xa7\x85\xc8c\x8e[\xa6^\xf3\xac\x8d\xd5خ(\xea8\x84\xbb\xd3\xd8/|\xf4\xfa@\xd8kp:H4\u052a\xe3\xf9$\xf1a\xf0\xd5\xd9\xefP\x98{\x95\x87B\xe2\xff/S|X<\xbdF\x1aB['p\xe4A\xe3\xb4\x11\x97_ri\xfc\x96\xaeƴ\xe0\xd9\xf08\xfb%\xad\xb4\x85FiryY\xa1҇_\xb4/DG\x94\x85\xffc|3\xb1\x12\x1b\x04\xe7\x96(\x8a!ɘ\xb9\x05,\xc4E\xb0Ӓ]\x0e:\xc4\xe0\xb0eG[\xa2\x8e_\xfe#n\xbc\x96[ޞo\xb2\xe1;\xb5\v\x87\xec\x9aU\xfa\xc2G}\xb7\xe1W\xfd\x9d\x06\xea{W\xf0)\x1c\xc5\xd2\xc8\x13L\xc5=ѡ\xf7ܕ\xbf\x8c\xf8\xcdi\x1a\xe6\x1f\x90,\x8el\xac\"\xfa\x11\xf2\xe3\x9a\xc0v\x01\xb0 \x00\x96Zk\x1b*\x80\x01\x9b\xf3\x19\xf9\x0e[:|\xf9\xf4vN\xb0\xe6j\x00\xe8\xeem\x000'F\x80%\v\xb4[֠\x87˄C\x00L\xc6\x01cR\x8e\xe2\u007fC\x90\x8b\xf2\x8bܝ\x13\u0096#N\xb9\xe73\x04\xe6\x9c\x1d\xe43\x9cYs\xa0\xda5\x0e\x9f湜\xfb\xbf\xae\xef\x9b\xff\xd7rK\xf6\xca\x1eY\x92\r2\xc0\xddE\xb0\x15b$D\x94\xf0\xf0*\x80\xb9\t\xc0\xc7\xd0\xecv5\xea\x18\xf6$`>c\x80O%\x16'\xbcCp\x97\x1cb\xde\xd2\xd2%\x8am\x1e\x1a\x87w4\xa5\x8bO4'\x8a\xb3\xa7\xa1\x9d/F\x9e\x1a;1H\xea-\x85p\xf1\x99\x97\x1f\x1b\x1e\xe9\xdci\xc1y\xedj\xc0\x92\x17\xc7l\xae\x1dY\xa7@\x92\x9bC\xa59\x90ֿh\xab&S5,1\xcfZm\xe5⁃B\xdaئ\xd87\b\xa4t\x9c\xfb\xe8\xdb-\xb4pf#\xc7b\x1a\x03!ě\xe8q\a\xdaW\x13\xdbO\xccl\xe7-\xac\xf3t\xc1ܦ-\xccf\x9f\b\xb8\xf6S\x1d\x91\xcbʎ[\xb3\t,\xd3X\xe3D\xcb9+X\xc3\xee\xe0\xfaM\xbe7\x99O\xcb\xf7!6YdJ\x00\x993M\xd9m\xdb.\n\xcb}S\x94\xa2\xe63 \v\xaff\xa8\f\x93u\xcf6\x1b\x0e\xc0\xd8\xdcGe6\xf5\xf0\xc0\x81\xc9\x1cK'#`\x90\"0o{\xa8\xc4p\xacl\xa6\xa4\xd6MB\xbb+\xcb\x05\x85\xed\u007f\xe0X\xce\xd7\x18\xd06\xf0R\x93\xe2\xf1\xc1h\xbd\xb9\xee\x18R\xaaM\x03\x8eS~\xb6ײ}\x82\xc4^\xbf\xfb\x92y\xf7\xb4&\x134U\xc1W\x16\x9fAשE\x89\xc8&\xab\xa5\xea\xaa\x1d\xfc\xac\f\x89\x05\xce\xe0\xc00˨qo\xe1\xacvw\xbbn{Ýb*\xcev\x02\x15\x8c\xd1\xe4c\xe9p~\x1a\xc3]\x9aKC\xa7oo\x82\x17\xbee{\xedƧW\xc1\xa2\x16\xa6\x92\r\xf3M\x152\u007fM\xd0r\xb3\x1f;1IR\xbd\x1a谼\xf8\xe0\xfd\x1a\x86P\xcb\xfb\x16\x97?\t>\xf8V\xdd4\xb0\xe8h*\x82ڥ\xb7r%-ݾNn\xdc<\v\x16\xeb\x03\xca\xccN\v,\xaa\xaf\xb7w\xc1\x03F\x84\xd8\x03\x19\x05Z\x06\x89\xea\xa5+\xb8k\b\xf5\xf9`\xa6\xe17\xebak\xce\xcc6\xfb\x1a\xd9U\x0e\x83vh\xbe\x95\xca\":\xc2y\xb4\xa9\xdc\xc2\n\x18\xf56\x8b\xb5\x88}\b\x8d}\xe5ޢ\x16|Y=QD\x17\x84\xcc\xc1\xba Jq\xe2\xd6y\xba\xae\xf2öZY\xa5m\x11`\xe5\xea<| \xe3t\xeeC\xe0\x80\x18J\x803\xab\x0fJ\xbe\x16\x06\x82r6\xf8\t\xf2\x9fi\xcf\n\xef\xf4\xe3\xed'G\xbbe\x14\xce+\x8di\xfcȲ-\xbc\x96\xceB^]\x12\xb2\x97M5͑\xecƅ:\xa7\x85\xd35\xd1\xf5\x13\xdcw\x90S\xb3\xe5\xeb;^\xb8.\xc1f\\]\xb8\x1f#\xc9>}~ľ\xee\xf3ܲ\xa7\x0f\xa5}e\xb7\x06q7\x8bw\xbd\xa2&\a\xb4\xb0\xf7\xf5\x1b\u007f\xa8E\x05\x15yUͦ\xfbM\xc4\x06\xc3\xcd\xe3\n\xd8\x1b\xf7\xdee\x9f\xb8aܒT\xbb\xff\x1a\xae s\xb3՛\xbb>\xbc\xd6\v[\x16t\xf6bD\xfc\x82\x10\xbc&\x14!\xa7\x83\xe6^(k䪴\xb8~\xb8\xbb\x9b\xaaT\xc2\x00\x056\xff\x10\x94\r\xe0N\xad\xbf\xfe\xc4\x04\x0e\xdb\xdd\xd67>\xf8\xc1\xb0\xe9r\xf2;\x8a\xbe\xbb\x97\x8b\x00\xc0\x027\x0f>\x06\x8cx\xe9\x93#u>5\xd1Ƀ]\x8e~\xd3\xeb;\xfd`-\xa7\x8e\xa9\xdcu^f\x90\xbd̤\x17\x19`e\xbevg\x18.\xeb\a\xbf\x1cޡ\xf3\t\x94\x9a\xe3\xab\a\u007fM]\xd7\xc2I\xaf\xb9\x9b$$씱\x8d9\xb0\xaa.,^E\x1b\xbc\u0590\xec\x115\xe2g*\xaf'6\x9e\xa3\xc2͜\x8a}\xddnI87\xc1\xe6\xe1\xe4\x00\x1dA\xf8\xcdZ\x82G\xc1\xcdY)\x9e\xb7\t\xadB\x8d\xf2\xb8*\xaf\xb0\xfe\xd8L*\xdeK\x0e\xda\u007f\xf6\x12**]-:\xa7\xbd\x06o\xa5\xc79\xae\x94\xd1\x14B\xac\x80\x9f\x0f\xeaC\xaap\xaf\xe9\x19\x9c\x1c\x14\xbd\ntϧ\x1bҴ\xb9\xa3\x84\x8b\x15\xa76\xa0\xb1d\x1f\x82{\x85D\xe1<\x9d\x1b\x8fM\x96\x05Ţ\xab\xb23!\x93\xf6\xd0V\xadR\xf3\xa8^¡u@(\"\x0f\xe3'o~\xe9;f\x9a ]\x10~\xbe\x8e\xf1E\xbd4\xd4l\xed\xe5\xc1x\xcc\xe6\xff>$\xfb(\xabƦG\x88\xd9\xdf\xf8\xf5\xc4\v\xc0\xe1\xd9}\xe6U=\xb7\xed%\xb7\xdb/*\x99\x81\xd8\x1b\x9f\xb0\u007f\xf3A0\xf0.iw\xa5\xa3%\xef\x80e\x8b\xf3\xed\a\xa0K8\xd6\x03Nv\xa5\x01\xe3\x0f\xdc\xfa\xf4)V\xf4\xd4\xf6X\xa6\x00\xc6d&Vi\xd6L\xd4\tۢ\x17\"F\xef\xb9\xd8\xca'\x9b^v\x80\xd2\xfdtt\xbf\xc7\xdd>wIJ\xd8ӹ\x01\xfb9\xca\x1b\b\x95?$\x12\x80\x14\xafH\xf4 PB\xa8\x81!\xa6E%\xd4\xfb\xfa\x99'US\x13\xa1\xcd1\xc8Z:d6\xd4B\xba;a\x14\n\xe3Xdv(\xe2\xf5\xfc\xa7\xd2\xee\xe4\x0e&\xb6\xd0\xde\x1c\xf2Iر\x10\xb7\xd7k\xd2b\xb9\xff\xa2\xedi3*Zs3\x83\xc1Q\x9cr\x05\x98\xb5\xe7\xbc\x1b\x86\xf6\xe4\xab\xfd\xb6\xfd?SrB\xa1\xcfa\xc5\xf4C\x0f\xddGC,\xc2\x108\x1f\xa6\x97\xeaЁg\x10(\x1f\xd24[\xdb\xec\x1c@y] 4W%\xd5\xfeq\xf6V\xa0\x04\xf89\t>\xa2U\xc4\xc9%\xb0\xa7מi\x13.\xf9\x95\x81*\x8f\x1f\xbd\u007f\x19U\u007f\xf0\xa1\xb6%0U\xd7kc[\xbc7\xa1\x83\x8bU\xde=\x870\x84\x14\x8b7\xb8\x154/\x83b\xe8cD\xd2\x10z\xd1@\xee\x93\xea\xe4\x84=\xc5VR\xa2K\xdc\x18a\txl9\xaa\xfd is\xb1\xbbc:\xd4\x1fm\xb2\x85\xae^\xf2\x1d$z>o\xed[\xab+\xa9@]\xe8k\xfa\x15\xec\xa3j{D\x0e\xdb\x14\x91\xf4\x03iwkq\x03\xb76\xd8\xd2>\xe3\xaeI\xe8\x16\xb2\xd9\a)\xaf\xc7\xc3\xeeucؼr=#c\x976n\x1a߸\xc7\x17\x8aa\x94T\xc6jg{\xca\xe1\xf1\xb9A(\xd5\xe0G\xbc~TF\x85\xeegzf\\\xd8A:\xb97\xee\x1e\xa7\xa0\xfb\xb2J\xb8y\x1aa\x9a\xd0\x18\x05\fڳ\xd7`\xdb\xca>\xc1\xe9͞\x9d$\x1e\x8c\xe6\"\x0fx\xf2\x12&\xab#\\:\xcdqm\xe7ݱL%d:}\x15\xef\x05i6\xd4\xc7hnc_\x96\xa8\xf6ě\xc5\xd7N\xf3\xe4\xae\"\x00Kb\x8b\xe3\xb19\xaaѲtu\xb3>\xac\x96\xc9C\x18\u007f\x18!\xb4\xb1)\xb2=6C\xcb\xf1\xb3\xebS\xe2\x0f\xc1A\xb4\xaf\xb1\x06\x1d\xfe\xffg\x86\x11\u007f\xfa\x94\xe0Jb[W\xe5\xfd\n8\xb1I'\xee\xd8\xea\xadq9\x1c)CG\xa6\"\xbb\xb8pc\xfd\xa8\x8e\xccn\t̤nZ\xbb\xfd\xbc\x02\aCp\xf6n\xe3\xd0j(It}w\xb7E\xde\xd8\xedY\xf8\xc5rCc\xf5\xb5\x95;M\x95\xfdGW.\xcc\xf4m\u052cJ\xb4\x04\xdf\x1c\x1a\x90\x17_\xc3ȴ\x9fk\x88\xef\xd3\xfd\xe3\xbb7o\xe6\r\x90\x1d\xaa\x17\xdeI\x0e\xe0ذw\xe3\x01+\x9f\xdb\xd3*N\x15\xef\xb0\xf1\x89\x82Pq\xe84\x9brah\x82;\xae\x98o\x821X\x95\x17\xf8\xa1~\xe6\xae\xf1C\x93\t\x8b\x9e\x98\x82\xf3\xc6;50\xcd\x1d\xf5[\r%\x04\xba!\x80/\xd8cQyr\x8eFR6\x8c\xdc\xf7>&\xff\xf2\x9d\t\xecB\xac\xec{\x17\xe1i\xd1\xce|@\x9b\x9f\xaai\xf5\xc5;/\x1c@\xd5>+\xfdh\xe6\xec\xdeq\xeau\xbe\xbc*\xc1\xb0\x17\xbc\xb1\xa9\xcet\xb8X\xe0\xf1\x18L\xd2\xde\xc8\xce\x1c\a&\xf3A\xf8\xb0\x8a\xf7\xaa[4\xe9m_YW\xf8\xc6Ŕ\xce\xd2\xd5\xceɽ`\x9fh\x0e\xccJ]1\xdd\x0e\xea\r\u007fi\xfeE\x10Z\x8cFU\x13\v\xbcAMr\xa8\xc0#\\R\xa3;]C\xec\xc8\xeb4\xbf\x8b\xb1\aXaVʙ\x01I\x89Ă\x91{\xd5:\x04\x86-\"\xed\xe2\xf9@\vX\x83\xa8\x8c\xe6\x01\x1b=y\x98\x11\x19\xfa\xc2\x06\xccC>\x8bg\x85\xd7\xe8\xd9!\a\xc3\xe0/JB\xb7_/\xb7\x85\xfd#&\xf5M\xf3\xc1\fxƭ$\x86\x9d\xcfݏ\x8bf\xb5]\xb5\x84B:\xae\xea\x82}\xde\xfb~\xf2K\xa7愄\x12*\xbc\x1c\x8a\xa5\xbd\xd3q&\xbd>\xaa\xcc\xc0mM\x85U\xe9\xa3H7\xea\xdbcמT#\x9a\x96\b\xf8\xb5k\x89\xb5\xcd+\x99\x10\xdd\xe7&\xcc.\xb0\x10\xb6\x9afq慍P]\xddݛ\xcf͡n\xf1\xe0&\xab8\xf8\xe0nhKC{̙\xa1j\x97\x91\xa6\x95\xcb8\xdb\x01\xe7HT.\xd2\xe3Ȳ\x0f\xecPK>\xd1Q\x80od\xc4+Kf\xb1\x1d,,\xfaK\va<Ũc)\xfb\x01\x99\xc6\x1a\\\x0e\xe5\x8b\xfe\xd0֮\xa5j\xe7V\x0fRR1\xa34\x16\xee\xf9͌\x1db\x9e\xfd\xb1\x13\x8d\xeb\u007f՚\x99\x06\xf0\x18\x0e״\xfb\xd9\xc0{c5\xfa\x8a3\xfaa&)\xea\x92zģ!Y\xb4K\xf1\x85W1w\xb3\"\xf3\xb2\x1fs\x98N\xc0\xef\x00\xb7\xa9ƌ\xb1\xffD\x9c\xcc\u0560\x19K\xf1f\xa5Nڭ;\xe7\x90\x1cѬAz\xfe\xec\xb6\xd4\xf9Iw\xb92=\xc8\xe4T\x94\xdd(\xdbݞ\xb4jE\xfb\xf6\xb5l>u-\x1ck\xa2n`\xad\x06\x02\xa4v\x17\f\xfcX\x94\xd7 ,\"\xff9\xa1\x93\xbcW#\x1fe\x1fYuI!\x9f\x8bŜ\x1a\x18\xc3ǁ\xe5B\xec\v7n\x80\xe1$\x89\xc60A\xc5yȍ\xc7,\xf4l\x9e\xd8ݴ\x970LM\\\xe4\fCHL\x12\x11\x10j\xec+o\xc5\\ɩ[\x97\xf4\x80\xad\xf2\x0f\xfc\xff3\xf4\x93w\x9fT\xe8\xb3 \x9c\a\xe9\x8fO\xf3\xf9?\xfaÓ\x84Þ\xfa\xf3\xb1\u007f\xe0\xe7?\xef\x06\xee\x17\fH\xfb\v͞\xd8F\n\x12\xcay!/\x8c\xae\xa3x\xeb\xef&\xad?\xcb]\u007f\x8b\xbd\xfe\xfdu\xe8o\xa3\u007f\xbe\xf3\xe3mX͌\x8bI\x1bn\x9d\xef\xe3w\x8f_\x9c\x0f\x19\xe6\x00E\xb8\xc2\rk\xd6el\x8e϶\xbcֳB^Sl\x9d\x05\x95\xfbLY\xacS\a\xe6\xf9\xe8\xa1Ą\xb2\xf6\xb2߄\xa9\xf0nnjZp)\x8b\x1bU\x97\x97:\x1e\x95n\xb1\xb5g\\jr(H\x83־\"\xb9\xf7#\u007f\f,L\x87\x0f\xb0Ӕ\xd8\xd2`\xa1۹\x13\xf3\xef\xfe\x99\xaf\xd7\r\xe0\x12:^I\x8a\x81\xf3A\xd0\x1c\xdc@$\x84Է\xfc\x95I\f\xae;ڠG\xee\x94T\xb6'(%-\t\xd2J\xccN\xbd>hA\\\xd1\x1a\x9f*m\x8b\x97U\x04/L0\x89\x87\f\x1f\xf0\x9a͍\xffc\xd2,\xaa\x9aU\x8bWMy_\x02\x15\x1e\xd4fj\x92#N\x93*j\xce\xc6\xf6\xea\xb9\x1bӒ\x02j\xa8Y\x99\xc9\a\xd2\xc2ľ\"[\x11\x96\x1fW\xa7\x97n\xc0\xa6J\x87Q:Al\x15\x8f\x1e\\\xc6\xcd\xca\x11N\x19[\xc3\xc9a#N\xec\xf8\x97\xc8\xc6\xf0&z\xe3\xf2\u007fS\xfd\xebе\xfe\xd3\u007f%\xa9J\xbeg?\xf5\xff\fr\xcd \xc8\xe2\x8a\\\xd8\xdf)\xae\xf7\x91\x8fk6G\xad\x8dxSu\x17)tG\xfc\xa9\xcfE\x16\x83\x8b\x8būť\xab\xa5E\xabE\xfaUpY\xfd\x84\xa8\r\xa9'\xd6u\xdf}\xeb\xd5\xee\xd1\x06m\xa8p\x03qs\u007f\xb9\xff\x05v\a-F\x1eF7\xc0\xa1\x06(\xfe\x13\x00\xa0\x8c\xae\ue2c0֜9\x99\xdd\xdd|,\xbb\xfeL\x1ct\u07fe\b\xd7\xea3'r\xba\x9b\x8ee7\x9e\x89w=\xb4\xef/\xc9\xdf\xf9\xba\xd4?\xff\xf9w\xe7\u007ft\xe1\xacDl\xa3\xf3q@\b\xe7{\x00\x96\x11\xdf(\x19Ы\xef\xfe\xfbf\xaek\xfd\x91\x9f\xab\xfe\x03\x99?ݒ\x95\x91\xe8y\x95\xdcbT9\x95#\x17r\x93G\x92\x92\x99s\x17/eq*\xa3\a\xe0\xf2\xbc\xd9\xe8\xca\xfc\x84\xf1\fi\xec\x96\xdc\rk\xd7%\xf1\xf6\xec\xb5\x13\x83\x84\xa7\xc5<|\x8d8\xb5,\x8ebo\xb6\x17zhq$\x9365X}\x9b2Vu\x03\xa1\b2\xffV\xe3\xafq˴\xc6HK}\xa9\xd1^\x02<6\x80%\"vz\xaauwrT\xb50V\x947\x17\xc3ϔ\xe5\xe1EN\f\xaf\xa9\x92\xcd\u05f6\xa9C\xcb\x11\x9dP\x9e~oH\xa3\x9c\xd6\x02\xa8\xdf\xe1\xf6\xe2\xb08\xd5-\xc4\x0f{\xbd\xc6\xf7\xde\xfbj\v\xbc\x8dȘ`KK\xf7\xe0\xb4\xeb_JA\xa0f6}2\xba\xba\x98:\xab\xaff\x1f\x1fj\xfb\x91\xde\x1bY`\xa9v\x92{\x87Z\xfb+\xe1j7\x8d\x1d\xb6\xab\xaf\xa8V\xbf\xb6d\xc8c\xa6'&\xb0\x8e\x1c\xf1\xd2\xc4쫫\xcf\xefb[\xc0\xd5\xe7\xb9܈9\xa9\xfdf\xa2\xe66m\x04\xea\b\xff\xcd\xff\x0f\xb9\xd8\nӽ\x8b\x92]\x8d\x9e3ɶ\t\x1d\xe4\x06\xa7\xbb\xc7f\x8c\xc7\xeb*#w\x16WS\x8eշ\xed\xa1\x15\xe9\x8f\xf1z\xb7\xf2?\xfe\xfd\x19\x8a\xe2c\xe7\x91A)\x90\xd7)C\xb8\xfe\n/f`\xa8\x9b\x96(\xe0\x10\xc0\x8d\xa4\x8e\xbd;L>\x9e\xf5\x13\u007f\x06\xa9\x92\x15\xe5$\xa6V\xc6lWP\x82\xf2\xe9\xd2\fj\xfd\xb6\"\xae$=\xb4\x92\xadd\x8e\xb5\xe6\x1e\x8c,\xac:\x1cި!\x8d(\xb9\x98jnF6{\xb3\x9d\x84\x95 \x0f( \xcbXm\xf5\xf2I\x8c\xb9\x96\x86t\xa7\xe0\xb8\x1eļ\\\xb5\x10\r\xa7\xe8\xe8\xb8\xc0\xb4CLb\xb0'\xd3\xe6\x87(\xbf\xf6\xcej\u007ft\xff\xee\x8eO\x1f*wlE\xfaW\xaf\x1f\x03\xe4/\x8c<\x97\x9b1'\xb4\xd3\x13շi\xa3PG\xf8\xaf\xce_\xd9b\xab\xe0\xee]\x94\xec\x1aɮ\x9e\xd66\xb4\x91\x8b\xcd\x18\xb3\x97\x9eٳ5\xf9\xd3(\xaa\xf7T3)\xf9\x8azW\xcc\xd33\x97@,\xe6\xeb͑\u007f\x9dL\x02\xc7\xde=7\xf5e\n\xf9\x85\xb21\x19\\\xa9m5\f-\xff\x1f?\x1e\x17\xd7\xe6\x06Lۃ\xee,\xdf9\x03v\xae.\xaf\x02ّ6\xb7\xb6\xb8\x8f\x15\xa0\xf2\xa95\x00\xf3\xa7\x9cP\x88\b͡2~\x1b\x1e#\xfe\x11\x18\xcdc'ӷE\x8bQ\xd9w\xe7\xb5\xc1zx\xb1sD\xce7\xf8Ҵ\xb8Ni\xa2\xa7:B,\xe2\x17\f\x93Vw\xc3␒\xb6\x17'9\x8a/\x90\x9c\\\xec\x8f\x17\xdd\xe8nj\xe0#\xca\xd1\xde\xd4t\x04ũlp\xa7=\xd2\xe7\a\x0e$8\xa5%J\xa6Î*r\xe3w\x967\xcc\xd0r\xb1\x19\x96r\xfb$\xe7#\x90)(\xda\f}\x81\xbf\xd3\xde?\xdcO\xe9XGo\xe1~\xdb\xd5s\x84Y\xb8\xe9e\xf6\x16\x83\xe0\xcf\xf5\xbd\xf4\x1f{\x86W8\x1dQ\x95\xb6\x15\xae%~\xfe\xa7\xd3PO\xf1m\x87\xbcrΝ\xc9u\xdb\xdf\x10\xfe8\xfb\xec\xe8\xb6c%\x8f\xe2Z\xb7YjO\x1f/\xb0\x8d\xf3C\x94pՉ\xe8\x8b\b\x06D\x1dl}7L\xe2\x15+$\x10\x85.:\xf0\x97F\xf9U\xab\x8c\xf6>t\xbc\xde\xfb\xa7\xe3O3\xd2\x12\x85\xaeB\x0f\xb5\xab\x86\x12~Ł\xe2H\xfd\xfb\x1aZ\x95\b\xac;\rK\xbd7\x10}RF\u007fn\x82wA\xb4P\x14!\xb7*H\x0ff\xf0\x91\xd5R-}K\x9b\xfeht\x95\x89\xb3\xa6|2&\t\xf2'\x1f-ֱ\xca\xc6b\x1b\r\xf1\"O\u007f(\x1b\x8b\x82R\x92%\xee$@\x14\xaf(\xd6\xd2T-\xb8\xe5\xf2J\xf4!yC/)S\xb1?H\x17`\xf2%\xcd3\xcb1\xc7\"vᏂ\xc9sI\xce\v\xb3\nȮo\xff/J\x15\xfe\x12\xa1\xc1i\xfe\x16\"f\xa2\x81\x88\xf8\xea\x88\xd6\xe5\xe4QHھV`jX\x9a4R\x1e>\xa8\xb6\x8e>\\\x87߫*\uf294\r-\x97\x9d?\xff\x01\x18qj\x02r\xfe\x83\x90?\xfc\xf7\xa1\x16\x02^\x15\u007f~\xd11\xfe\x83\xd6~\xfc\xf7'\nB\xc3i\xbf\x8fV\xfa4Az\xad\xf9\xd3\xffd.\x9dT\xb8-/i\xa0g\xcfz\xe9\x96+\x1f\x10TA9\u007fP\xfd\x16\xb1\xe99S\x8c\xf2\xd6\xf8åz\xf4~e\xeb03\x9b\x92j]M\x80\x02!\t\x84W\u007f\xaa\x8eh\xceoD\xfc\xa7\xf8p\xb0z\xb2w\x9fp\u007f\xf3{\xf3(\xc1\x9a]\xe4,\x1b7#\xa6\xc0\x1a\x1f\xc2w\xde\x01\x13&\x00\xd3>\xda8Z\x9bX\x87ڪ\x0eE\x02!\fd&g\x94K\xac\x1a\br\xba\xf6z\xd2G\xf9\xf7\xa3`\xb9`1@\x00\xf8\x11K\xd8zKS\xa5\xb1\x04b\x94d\xc4\x05t\xafy\xe8~\xe5\xac\xf3\xf9t3\xa8I\xfa\xec\xc7C\xe3w\xd0\x03\x90װ\x8d\xb8\xc6\xc6)F\xa9\x16\xd0\xe9Z \xdf\vz\xfc\u007f\x15Y\x9a\xf2\x8c\xb9\xc6$\x93\xc1\xe8\xbd_\x90\xba\xfd\x9f2\xcd\x0e\x89\x94moDYC\xb1u?\xe8\x02\x14\xea{\v|\xe8\xe4\x85\xdf\v\xe6\xe7\xfd\x80\xed\xb8\xc0wY\\\xe4C&\xcf\xfd\x9e\xbf0\xff[\xfe\x8e\xf3|H\x02\xd8Y\x1ed\x1055Re\x8a&\xaa\xa0\ta(/\x8f\x87H\xd0\xdcH\x95˚\xa9\xa2\xe6 \x03\xf0yb\x16\xdfޑ[YX\x95ܴۜ\x92\x19\xbd\xa7\xa3\xf3v\xf2\x0e\xa6\xc5\xe9t\xd80p5\xa1\xa3\"q\xbe\x9d/cՈ\x9b\xfc\x9dU\x9d\x02\x8cntКm. Y\x9bt\x18\x1c+n-\xce\xd1\xea\xaaeM\xc9݄\x9b\xdf\xceaw\xeb\xdb\xf7\xb8A\xb5\x94t)㏢}\xb2>%\xa34R\xe1a-\x8fÃ\xf8\x91\rjʖ\x96\xa2\xa3\xb1\xb5\xf9\xe71)\x18\xd8\xefYh\xad\x9f֙\x18\xcar\xb9Ă\xa3B\x9e\xcbC\xf5\xb7J\xa9Z-uP\xab&MT\xe7/F\xe6\x87km\xd5\xee\x9a@\x96\xe6\xe3\xf9$_6\x94a\x15\xc0\x17\x89\xe8\x81k\xb3\xa4\xa179A\x8d\xa2T-\xa1\x19\\+?Xl\xe4dT\x04\x04\x17\f\xfb\xcbh\xe7\xfa\x87~\x10\x0eu_\xe7\xf5\x0e0/\xd6\xed\x0e\x1f\x17\xfa\x96%\xb0\xd2\xfc\x12\xdb+\xc67\xad\a1\x9d\x86h/\x0f.\xf2\xaa4D۷\x97\x90\x1c\x92\x9d2\x1c3H\xa4h\xe7\x9c\xee\x94L\x06\x99;\xfaIm9'u\xb4\xb5\xb6\xe5O\xfe\b\x025*s\x05\xdf\xf3\\ct\x18n\xee`\xb7\x00\xf8\xbe_A\x1cx'\a\x97\xde!\xbey\xdf\bʨO\x81\xd9_O\u007fx\bL\xc5.r\x82F\b\"\xb1\xe4\xc1nރ\x1f\xaaN뀹\xb9\xa7\xc60\xec\xf3\xc5\xf0\x81\xdcF\xdb1H\xa7n\x06a\x8fh\xa2~\x11@\x8a\xefx\xa6\x8b\x942\xae\xe0/\xa8\xf4\x13Z\x80\x15\xe0\xb8\x93\xe2\x19\xe2BB2\xa81\x91h\xa6\x17\xcb[\x14\x01p\xb4r\xe9\x00;{$\xea\xd6\xe0p\xd4\xedܑ\x11\xb64\xd5J\x02M\x81\x1dz[!\xf0R8\xe7Z\x80\x90\x94\xda'\xb5\x84\xda\xf9\xb19\xa1\xa8 \xb4?#\xa5\x1b.Q5z\xedc>xMu\f\x89\xc3\xf3\xe2\x18O\x00\xf5G\xd5\xec\x14\x91`\xcfBK\xc2\xfb\xf34\xdb#\xf2]\xc5g\xf1Z\x0f\xe8\xc6m\x15>\xbb\x85\xcd\x01r.\xaeB\x98\xc1ݸ!\xffA\xd4X\xd9\x02\xbaT\x10UʊG)\xe8\x89\xf2\xa0\u05ec!K\x8a\x17\xcc?N\x06\x91\x05\xe6̕\xa5F\x0e\x14\xa8\xb7\x87\xea \x10\xb3\xf1\xfd\x1e\xb4\xbf\xdc\xf9_\x92 \xd1[\xb6\xca8\x89Y\xa8\xdc\x01ϻ\x89\x01fOr\x94T\r\x9d\x95\xa8Q\xa6&\xe4\xb1{@>a\xa0\x96SÏM\xc6\xe1Ҝ{\x14v=\xf4Ë\x10\x8f6V!\xe21\x0ex\xe98/3\xa5+$C\x13:\xa1\a\x96\xe1͘\x8dg\xaf\xc1\xe1'\xce\xcec\xd0\vgO\xc2\xe1\xd7\xcfnr~&!\xa4\x13@\xf6\x1b|\xb6\xad\xc7\xc7Wr\xb8\xfbu3gR\xe8\"\xe1V\x12\xa2K\xaa\xed\x896\xce-\xf8J\x1c\xe0\xfcl\xf1\x82C\x8a|\xec鮚L\xfb]UBU\xa75\x06\xfb\u007f\x81\xe7M\x00\x99B]\x9cY\x93\x14\xc3\xc3aå\xbfB\x0e:\xd6QⓃ\x14N!\xe5;\xe6\xb2\xc6\xeeu\xd4xaP\xea\xcb\xe6\xa0\xe1\xec\xad;DڪB\xe5ҤD\x19\xac\x1aA\x9cG\b\x1b\xa2Q\xd6)\x9d\xf0\xa2Ni\xfcl\xc5ϥ\xcd\xe7\xe7\xa5-\xcc\xdeQO\x17䩦/\xcc\xf63nu\x8c\x9d\xe4U\x17\x1f\xe1u\x8dqo\xf5\xf7\x153FOpk\x12\xb8]\xa3V\xf0&4Ҡ\x95D\xae\x031\x01Ű\xbf f\x13\xceW\xf4\xb0\x1d\xf9\xda\xec\x11)\xb3~,R\x18\x8a6\xbf}\x1cf[k\xc6\f<\xa0\xe2\x97HN\x00\x96\r-\x01\x9c@ru\x88\x98\x87\x89\xb1\xbe\xea\xf9\xd4\x19\xfb\xf7\x8d\xa8\x86I;N\xe0ȣ\x14\xac\xe0:\x91\x85\xeb\xa0QgJ\xc2O\xce,\x0f\x1c̖p\xfd\xf7Y\xe1\x0eB*\xcamNO\xa9\x85>\xa5\a\x1eߴ\x8aU{3(\b}r4!\xfdE\xf0\x9c}]$\x9d\x86\x97z\x80\xa8\x11\x9e<\xb1\xbdR6\x87\xd1E\xa6\x99\xa5\xd8'\xb9\x96\xfe\xb3N\xe5\xa7t\x93\xda\x06\xa2\xb6qL\xa0\xd0\xd3?\xcaC8~,X>\x89\x96\xec\x97\xc3\xe4\xc7V\x14&M 2\xb4\a\xb0\xe9X\xe5-\xae\u007f\x0f\x96\x9eP\x80\x80\xdc\x1f\xe6\"\xae0|J\xe3\x84I\x98\\\x90}\x1f+p\xde\xf6d\xa6ɋ\xcbn\xf4S\x8a\xc3\x06\xf5\x9e\xa3\xadsٲ7\xe7\xde;7\xb9\xf3\xed:W\xef\xef\xf7\xf6m|\x11\xd8\x14\xc1\xf7\t\x18\xa0\xbb}\xc9\xe8\xf8g\x9d˟p\xa7\xfa\u05fe\xd0\xea7\xfb\x04\xf4\xd3\xdd\xc0\\\xdaw*\xf0\x1b=\n\\\x8c\x0f\x97>\xceו6c\x9c\u070eȋ=\x03\x91\x97u\xddc\x9cT\x9b\xa5\xfd\xd3\x02\xae\x15\xc3\xfeI\x9d\xe4\x11\x9bҤs\xa3(\x83bD\xeeM\xb2\x9d\xa5\xdbZy\xfd\x01\xae\x1f\x82\xeb}hk.\x01t畢\xfd\xb8d?\xd53\x86W\x05<\x91\xca\xe1e\x9c\xe1\xaf\xf2\x87\"=\x04G\x98\xbbfM>~=\x97\x82z\xe5*\x8c\x19ٿ\xba\xff\\i¶\"\xcdF,\x98\xfb>\xc2S\xdd\xd7\xd246\xda\xd44\x9f\xed\x19\x1a\x91\xe1\xf1\xa6\xa9e\xdd`KK\x8fWND\x94g֖\x86\x86\xe1\xe1\xa6\x06\x83\xda3\"J\xed\xb6\xd0P7R\x18<,\xf5z^\x907\rN\xf5\nBҽ(\u07b4\x0f\xc1\xe5(tod\x10\xd5\x1b>\b\xf0OQ}QD\xe7\xbd>\xbb\xff\xb7\xde\xdf|\xfe{\xbe\xf7\xbe\xee\xff\xda[uJ\xfb:\xae\xcfE\xdcj+\xfc\xfe\x00\xf3\xb1\xc0`\b\x87\xffi\xf2\x05\xbd\x92K\x8e\xce\xcf\xe7\xaeC*s\xf6\x84\x88\x03\x84'\xa8\xbe\xfdD\xbe\x90\x8dŻ\xd7F\b\x10U\f\xcf\n\xaaH\x84\xd5&hiu\x15\xb7\x12t\x1cN|Ui\xca\x146\xbbh\x1eI\x0f\xbc\xa1Q\xc0\x95\x1er\xfb\xa0(\xb6\xe3\r\x94\xbb\xfb\xf3\v\x02\x14ٓ\xeb[\x9d(\x14\xa0\xb3\xef\xa7&\x96dV\x12\x9f\xbd>\x9er\xc5\x03F'\xa6\xe2\xe9n.\u007f\xb5\xbf\x01\xf6\x9d\x8d\xfe\"\x12\xce\xdb\x1b'P\xfbó\x04oAI\xc2Q?\u007f\x91 \xd1\xc7\v\x97\xb9\xe7\x05\x91\x89\xf3\x8a\x13\x92A\xe4\x97\xcf{卵wg\f\x17D\xb7{\xf7\x9f\xea\xad\xf4N*\xd8Nͯ\x0f_\xca\xcf\r]\xd4\xd5m\xa5\xe6\xa5l\xc2P\xfd\xd8s%\xd1Q\x04W]\xf8\xa9\xccٞ\x9e\xd9\xccS\xe1:]v\x9d\xcd\xec\xe9ɜ\r?\xa5{8c\xedl֩0c\xd8M<\x10\xf9\x1b\xc8mθ7=}/\xa3\xb9\xd9{\x9a\x9eθ\xd7\\|\x01\x01\xf3\xbf}\nkK\x12iD\x81\xc7\x06ߔ\xe88\xb4H\x10V\xe5\xc11V\x18'\xceD \xb3\xfcb\xc8]\x0f\xc6\bJ\x9a\x16Af\xc0\xb4\tG\xdd\xe3DO\x96Z\x9d\x10)\x9e\xb0\xc0\b\x04,\x00\b\xe3\xc2.\xfeRu[Y\xc4l\x16v\xfeO\xf7\xc2_\xbb\x00\x19|]\xd5\xdf[]\x06\xac\x94\x9et^!\xd9\xeb\xed\xd0\u007f\xfb\x848ѳu\x16\x86\xb9\xbe\x16[_\xcaZ\x8a\xff\xbb\u05efa\xde\xdba\xa7\x15))\ab\xb3b\x03Lϗ2\xc3\x1b\xd3\xd2\x1a#X\xac\xa6\bU\xdau\r\x93e?*US\x18\x8b\xd5\x18\x96\xa6\xc2\xefI\xa6o\xf5\xa6\xb50\xd8\xdaMݾ\x9b\x99xk\x98\xa8\x01\x17-\xdf\xee1\x166\x13v\xee:\x9c\x9c`w\xcf?\xf8\xe5n\x1c\x99\xfe\xde\x13\xf1\a\x12)E\xf2\xeb\xc6IQ\x93\xccB\x8a\x1e\xc6K@\xab\xe2C\xdc\x04\xe1\xc2\x14\xf9!]z&@M?\xa2\v4r/[\x14\x1be\xe6J\x0e\xa8\xdd\xd3{\x94\xd7\x0e'\xc1O\x92\x83\xda\tl\x0e-<\x1c\xfa\x9b\xea\x13\xbed\x87*\xecS\x82\x01\x8ap\fť\x04vk\xfb\xefo\x88\xcfMȃ\xf1b1\x12\x009\u007fǓ\x87\xe3\x86\bP\x82OR\xe8\xe4\xb0\xec\x97\x17#jp\xd3=\xb7\xa7\tc\x92bK\u007f\x85^o\x03\x17\xeb\xc4\xdc\b\x05\xf6\x98;\x1ab\x94\x12@\x9a\xc1\x02\x10\x00\xeb\x93\xc9$0\x13\xcc\xe2Ϭ\xa9\xd03\x92\x8c\t\xa2\xbe2\xf6T\xff\x18\xe9|U\xf5\fA\xab\x02\x15\xdf{\x8d\x17\xd9\xe4\x9f\xd1>\nf|\x15L\x86eZ_\x9a\u007f\xc2Ȧ\xda{/\xfa\a\xb2\xa3\x94\xb6I\x9b\xce,\xbd\x1a\xb9~\x1c0\xd9\xefOBl\xb0\xfeֲ\x00:\xb5*)N\xdf\x14\xfc\xfa\x14\xc0t\xa8d\xc1])\xaaJ<\x87^\x81\x17\xabB\xbad\xc5HD\xaa*\x1c\x97V\x86\x93\xa8p]\a\xa4\xda\bmB\\\x12\xaa\x8cP\xc7%\xd0\x06\xd0\x1f\xfb\x87\x9d7\x1ctь\xe6-\x94\x81]i\xdf'\xd0\xd2\xfd&\xb1\xf9\xf4\xed1)Ȝ\xbb\v١\xf2æ.\xa7\x8a\xeb\x92$z\xa8#\x13\x92\vZR\xf6\xf8\xc6:\x95-pQ\t`\xab\xae\xadW\xc6\"\u007f3\xc8\u05ef\t\x1f\xff\xfb\xa9_\x8e0S\xc5\xcf\x02F_D=\xdasޜ\xbd\xeb\xe0\x06qO쎁\xf5oMλ\xffމ\xd7\xc0\x11\x8c\u007f\x8f\x0eް)\x18\x8dGoZ\x8f\xc7\a\x87\xac\x1fߛ\x05\xa3\u05cd\xaf\xffn\xe2@s\x1c\xb3b.Mؙ\xa9Q\x05\x1d[\x8f\xdd\v\xe5\x04\x8c\xe4C/\x9a\xc9HnIS\xab\xe0\t`\xf9`H٥W4t\xd6m_\xdb\xcbv\xa2\xcb/>uJvƯ\u052e\a0~0J'\xee\xach?\xc9\x1f\xe8\v<\xaf\xfd\u007f\xe1\xb3Ɇ\x03䊚\xa8\xc5&\xae\xb8\xb6ZW\xa7\xcf\xcb)\xd7\x01\x0e\xed\x0e\t\x9d\x99\xc0\xe5D\u007fXp\x81\xb42\x85\xc4N;.)\x8c硌[z\xb5\xeb\x89z\xd8k\xdf\xe7f\x9a\x0f\xd75\xd9\x0e.\x90\xc5%x\xde\xe9a\xe3\xf7=\x11'\xf2\xa14d82\xe9\x1e+\xf4\xc7\x01d\xa2&)K\x9f\x9f\xa9\xd3\x11\xca\v\xd9;\xd6\xe6\x9d\n\xad,?\x86\xee\x101kA2\t\xd2\xfe\x93\x96\xa6\x9bI\xe1e\x11\xeb\\E\xb4:\xb4J\x1a\xbeʐ\xfd\xacokl9\xbeĞB}}\xfan\xb4,\xf8r\xa9j\xf7UH\xf9\xbf\x1fD\vw\xfc\xfd\x8b\xdek\xbd7$\t\xb7!c\x81\xe5\xd7L\xeb|\u052bٴ+\x8fI+\xd8\xcc8\xe4\u007f\xa3v\x90\xb9\xda9~\x82_U|\x84\xda\xd5\xca=^WG:V\xd12E\xc9\xca\xecN\xf8H\xf6\x94i'ȑχ\xb4\x9b\xff\x1dǹH\xf0d\x0e\"T\xaf\xea\x1fLt\xc9ų\xb9\xc8\xf0|\xe5\x14\x90i\xc6z\xec\xb9\xef\xd1\n\x11\xce\xcb\xcbW\x94\x87F\xe6\x89\x1d\xbc\xbc`b\xc5c\xb4\"\xa5\x8c\xa7ht\xf5r\xc0\xa0\x10\xa2˛\x8a\xceu\xfe\x14\x956\xa9\xa0\x16\xd1\xd8\xfc_\x86\x16\x99\xd1&\x94\xb9#\xf0\xfc\xed\x19\xf1Y\tL\xfa\xcd\v\x1dRk\x92\xc7JM\xf4\xc2T98K\x12X\xef3g@\x17\xab\xfd\x01o\xe7k\xc4\x02f\xb0\xdc=\xf2\x8ck\xd6P\xe4\xed\xec\xd1\xe7\xbbQ\x16\xa9\x800\x02\x85\\;\\<*\xd4\xe1\x02\x01X\x00B\xb7\x9b1\x19K\fXI\xbag\xbe[\xc0GJ\x84\xdc\xf1+\x94D\xcar%~W<\xb7$pI\x82X\x81PK\xdeFR\r\xca\xed\xf7\x04a3\xdfF!\xe4\xb4x%,\"҃\x90\xe2\xfc呑\xfe2R\x9c\x12\x16\x19\x1eO&-.H\xba5\x12+B\xa3EX\xec\x17<:\x05x\xccP|i\x87֛&\a\x95\x93\xb8\xb2\x80pT\x92/\x0f\xc1\x81\x05\xf8\x86^Xo\xcaG\x96\x93yҀ0d2\x8c\xbb\xa7\t\x10\x1ex\xaez\xbe\x95W\x9e\x06ωQ\xcao5uA\xd4b\xebC\xeb\xbaW\xbf\x8f\x18\xbe\xbe\x02\xb8\x87\x1b\x9e\xfd\x8c\xa8\ti \xd6v\xaf\xae\x12T\xedο\xc1\xbe'\x9f\xb1[?\x05\xb1\xae\xd9\xed!\x94\xdf\xd4݀G\xe0\x8er\xb7G\xd0\xf2\x84\xbbo\x9c\xb7\xb2\x94G.\xe0\x17d\xbd\x91b\xaay\\\x1d<>\xd6\xe7K\xe2\x06WK%\xe7K\xe2\xe9\xfcc\xe3\xb7\xc4\xe3a\xaag\xc2\xd5A\x16|\xfa*CA\r?a\xf9\xde\x06\xbf=l\x1c\u007f?\xde<\xd1Bz\x1b\xdaX0\x8d6R\xc1xh\x95b\xa0\xe9d\xeb\x8fS\x04j@\xbd\x80\xf2\x9d\xac@=\xbfR\xff\n\xb5\a\xaf\xd8-\xff\xb7\t)\a\xd9\xdf9K/~o\x8f\x16\x83D\xe0\xe09p\x9a_\t\xa0\xbea\x9d\xf0\xab\x11\xdd\xea\xf7\x98\xd2\xef\xb3\xf9\xe4\x83\xf4\x0e\x9e\x14\x81\xd1Mw\xcc\b \xf1\x8c\xc3\xcc?\xc0#y\xab\x1f\xd6\"\xddU\x88\xc0}\x0eH\xeepW\a\xeb\xaf3\xbc\xaf\xdf\v\xd7\xea\xf7Ú\xc9]X\x9d'\b\xa8\x1e\xa8\x89\xab\xae\x83\x18\xf4{\xc6H\xbf\xcf]\xfa\x03\x1eK\xee8\xc8Gqp\x8f\a\xfd\x1e\xef\t\xdcτ\xe4\x8e\x11>\x8eJ\x9d\xca\xd2\xd4S\xb1\xce\xf5|\xe8\xf4\x0eL\xef\x13\xd2\a\x01\x9d.\x1a3\n\xcfP\xadZDZ8\xba=\x89tz\xb3t\xfac\xd4\x1d\u007fV\xa5\x8b\xac\xbd#p_\x0f\xf4\xfbï\xb4\x1d'\x14J\xb5\x8cZ\xb4\xc8:\xa3\xb7:\xbdi:\xfdVg0\x86ꚓ\xb3\x99\x9eC\xa0\xe3Uh\x85\xb8H\u007f!4\xa6[B\xad\xd2魶z\xf7\x0e\x8f\n\x83}\xfa\xe8\x0f\xaf\xbeฮ\x00X\xcd̋tL\x11\x02\x88\xces\x86\x1c\u007f\x00O\xfd\xa4\xdc\xe3V\x00\xdc2\xbe<\xd1fj\xa5\xe1\xac\x18\x19B\xf4;w\x85G\x00*4v\x90\x93\x15/\xb8=4@\xdeE\x14\xc0~>_~\x02\xbb\xa8 \x02\xd0gu\xd22\xc0\x9d\xce{Vu\xf8\xae7\xe0JNC\xa2\xd4n\xdc\xc6\x05 E\x13\xf4\x0e\x84I\xb74\xff\x17\xf9!-Lk\xa75\xb9\xfc~r\x86\x10{\\\xcb\t\x84\xa2Ѻ\x80\xe4&\xe1\a\xf3\x16\x9a4\x02\x90\v\x85p\t\xe0\xaf\xe1\x1f\x19\xcfB\xc65\x00\xedxCZ^\xcb\xe2$\xa7\xebRM\xb8\xcc\x05\x10M@\xc5H\x80(ě\xfc\x17\x9c\xf8\xb8f\x89\x8d\xd6˰\x8f\xfb\x9d9b\x00\xf0\xe2\x84\xe4\u007f\xc0\xb2\xebI;\r\xf5҇\xdf\xed˭!F,\xc1\b\xb2?\x04\x10S\x83\xd5:\xfe˞\xf1\xaf\xc60\x17\xa4}\xb8s\x1b\xab\xb4\xdfr\xa2ÀU\x1d\xb5\xb7a\r\xf7-[\x8b\x1f\x9db\x00\xb9\x81xUI$'\xc8E$\x931\xe2\x85E/\xf2ٕ!\t\xa9R\xc4\xc7x4n\x82Kq\x1e\x1eɰ\xea!-d\x83\xec4\xbe\x13v\t\xf7\xe8=\xd3\\(\xc0\xcdòs\xa7\xdaM\xb0\xac\xf5\t\xa6\x15ܕ\"v-\x80\xf5\x86\"\xb3\xa1\xba\x9dH\x18K\xe6\xf9\x96#\x1a\xaa\xb5aDW\xf2Q\xce\xeaAQ\x02骅rG٭\x8f\xc0w\x80S\xd0;\x00\xba%\x82\xcc2$.\xb2\x81\xe8C\x95\xc2\xc2n\x9c\xfft\x9b\xe1\xd5F\xa4z\x8fk\xec\u007fLK\x12\xa6\xe9\xec\xa0\xd6\x1d\x94\xe6\x838\x13\xae\x91Rc\xab*w\xe9\xf2G\xf1\x0e\xdf\x03$\xe5\xe9\x15Z\xe6\x1az\xc1ֲ\xcaﰷ\x15\xc0A\xf0\xcbT\x00D\xb3\x00ݛ\xd5G\xaf\xf6\x16\"\xe5\x15s\xa8n\xfa6/$W;@h\b\xf3\"\x152\x16\xf5q\xd8't\xba\x8d\xaf\xd1\xaampt\xe0\x10\xa0}\x81\x1c\xea\xc1.\x85@\xea\xcb,\x00\xf4h\xd1m\xecU\x19\x12\xe7\xc1/(`\xeb\x18\xb0\xb5Y\xc6\x15C[\xb5\xda>\\A\xd0\xc0\x8f\xa6F\xec-\a\x11\xc1\xa3b\xac\x86#\xb4 \xc7\xdc7x\xf5\x98\xda΄\xa8q\xa9\xc0|\x80\xf8\xbb=\xb3\xb7\xf3\xdbl\xb5\xcbhAZ*\xe2\xbf\xec\x03p\x8e\xb5\xac\xd79jv\xc19\u03a2E\xe7D\xa6N<\x18\xa5m\r\xb4\xb2\xe2<-,%\xab\xa8\x9cR\xacג\x9334\xd9E\xf2\xec2\x18\x16;\xa5\u0381m棆\x95\xf2@\xe5wbq\xd1F\"\x84\xe0\x9f\bjԕy\x1bHX\x95@\xe4Bg#\xb1\xa2\xae\xe2錒\x18\x9c\xbaZ\n\xc9*\xc1\xb7F\xa3\xcb\x0e).\xcb\xc5\xe9\xf3$\xc0\xcbÕ\xe3xl*\x9d\x9f\xb2-\xc1\x12\x05\xa0\xbc\xdeQ\x12N\xcec\xf8W\vCXʉ\xf8\x1b6\xf9\xddԗO\xaeP\xaaԎN\xce\x10\xa8\x8b\xab\x9b\xbb\x87\xa7\x97\xb7\x8f/\xcc\x0f\xee\x1f\x10\x88\bB\xa2И`l\b\x0e/\xf3]\b\r\v\x8f\x88\x8c\x8a\x8e\x89\x8d\x8bOH\xfc\x89\xffۛB\xa5\xd1\x19L\x16\x9b\xc3\xe5%\xf1\x93\x05BQ\x8aX\"\x95\xc9\x15\xa9JUZ:\x11\x19\x99YK\xbeѡ\xd3\t\x1b\xbc\xec2\xa8\xcfV\v\xa6\x13\xce\xfa\x8d\xf9\xfce\xc0\xb8\xb5\xce\xdd\xff\xb4͢\xdf~\xfd\xfd\xff~\x85^\xf9v7\xb5f\x98\xf6?}=ݗ.߸z\xed\xfa\xab\x9c\x1fn~\xb7G\xee\xc7\x11?\xff\xf8\x93\xeeͻ\x1e\xf9y\x05\x85\xfa\xa2\x1d\x8aKK\xac\xe1\x8dPYQU\xfd\xba\xa6\xae\xb6\xbe\xb1a٤\xe6\xa6\x16\xad\u07be?\xea\xd6\xed{w\xf6;\xc0\xe0\xb0\xf3\a\x1dr\xa1\xdbI\xa7\x1cO\xf7\xa2<<\x9eL\xb3|Ve\x97\x95k\x8a˲mNqeYye\xc9i\xce+.\x03\x00") + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_700_woff2_bytes() ([]byte, error) { + return _third_party_swagger_ui_fonts_droid_sans_v6_latin_700_woff2, nil +} + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_700_woff2() (*asset, error) { + bytes, err := third_party_swagger_ui_fonts_droid_sans_v6_latin_700_woff2_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/fonts/droid-sans-v6-latin-700.woff2", size: 11480, mode: os.FileMode(416), modTime: time.Unix(1436317036, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_eot = []byte("\xf8U\x00\x00\x1eU\x00\x00\x02\x00\x02\x00\x04\x00\x00\x00\x02\v\x06\x06\x03\b\x04\x02\x02\x04\x01\x00\x90\x01\x00\x00\b\x00LP\xef\x02\x00\xe0[ \x00@(\x00\x00\x00\x00\x00\x00\x00\x9f\x01\x00 \x00\x00\x00\x00%\x83\xbcd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00D\x00r\x00o\x00i\x00d\x00 \x00S\x00a\x00n\x00s\x00\x00\x00\x0e\x00R\x00e\x00g\x00u\x00l\x00a\x00r\x00\x00\x00,\x00V\x00e\x00r\x00s\x00i\x00o\x00n\x00 \x001\x00.\x000\x000\x00 \x00b\x00u\x00i\x00l\x00d\x00 \x001\x001\x003\x00\x00\x00\x14\x00D\x00r\x00o\x00i\x00d\x00 \x00S\x00a\x00n\x00s\x00\x00\x00\x00\x00BSGP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00w\xa8\x009\x1e\x00NV\x00-\xd4\x12\xcd\xe9\x8a\xc8`\xd8W\xc9hKropq\"U:b,/\x962\xd9\xe3\xdb\xd3\xf0\xe0\xc6g@\x9e\xba\xd6$@6\xa4\x92\x9f\x83\xec\x03\x8a;\"\xf1\xa5\xb1\x18\xe0[E\xc7LFM\xad#^S[\xaf\xd0ϋ^>\x17\x1eZb\x91b\x92`x\xba)\xecQ,V\xd6\xc3<\xf0\x04\x96[\x9aU\x90\x83\xa4jmC\xccRQ\x98\xd6h\x92_\xa9\"\xc7\xfc\xc9\xe5\xech]\xa1,ΕIV\x9e\xca_\xb2\x02\xb9\x92\xa3\xb3\xbb\xb2\xb5\x99\x06\x16\x8e\xfaP\x80\xa0\x9ek\x85fb\xe9\xa3\xf5PAȡ\xa3\xed˹\\\xac*\xb8\x1e;\x02T\x1d\xfa\xaa\xc0\xf3'\xeaSg\x1fS\x11&\xc00\x06\xc1\f]E\xdd\xd9rY\xb9sE.yѝ\xf6]Xt5\x93\xa9\xfc\xe6l\xb1e\x06\x95\xa1\x10\xfa\x1d!LM\xe3\xd8m\xa3m\x0e$/re\xecV\xafP\xc1u\xe7O\x9d \x86\x129h\xa6\xe28v\xa96j\xbc\xb8ъ\xcf(iV\xb7\x88\xb4+\xdc(\x86\a\xc8\xd6|\xdc\xea\u007f\xaa\xfc\x9e\x90~\x99\xb6r\xaaT\xceC\xf7\xe6ݕ\v\xdc\uf863t3}\x05M\x96\xaaT\xf8D\xfc\x8eS\xbe\x14\x89\xc0J*+\xc9\xc4;\x90\xce\xcd\x02\x040\a7\r\xd0\xc0\xd3\xcd\x14\xa5\x8a\x87h\b;\xb9\xedJ\x1ajĤ\xc8\x1c*h\x86\x17\x91\x1a4\xa3%\x8c\n\x84\x91\x1en3\xccq\xaa/\x1d\xb8\x12\xdaV\x97\xe1\xec\xf0\xb0\x05,\xfb\x98\x19S^\x03\x03\x98\xd3\vG\x15\x89\x8f\x18PM\x1a^K,\xe5oe\xba\n\xc9\xdcEa+\x81\xc9\x06\x88\x1b\xf8\ay\xb6Ef\x14\x005\x84ԍ6\x13P+\x9a\xb0\x9a\x90lr\x843\x1c\x9a\xa9,15R\xfeR\x84I_\f\xb1\xedI\xfb\nYh.\xf8S.\x81[\xe3\x8aX\x88\x1d{aK\x16\x85\x8e\x06\x99\xcc\x1b}\x91KI\xca=sn3\xa8atJ\x01\",Q\xd2\xf7\x98\xa0\x06\x9fD\tfĔRr\x15\xdds\xd1HD\xd0\xc7\x13hcȒ#\xb0\x00\xc6c\xc29\xe650\"\x19|\xe3ޠ\xa7\x0e\x1fu\n[\x1cN3+\x89\xf1K\x86Y\x82D\xc0~`ja\x81\xa7\x89\x848\xe5\xe5\x98\x03V\x02K9K\r*\xccyT\x94S\x96)\xeb\x0fE\x9d\t\r\\\x17\xbc\x17\xdc\x17\xfc\x05\x9a\x1c4\x81\xc0\xe3[\x0eX\t\f*\xd62\x8f\x02N\x12V\x11\x9c40\xa7\xdd&7\xdeAnf\xb5\xe60\xb5\xc3\x16\vF\x01e\x82\xbc\x0681\xa0R\xdb\u009a\x00\xa6(di\x1be\xa0@,\xcb.\x81v\x00n\x84C^#\x8b\xceb\x80\x8e\x943l\xaf\xc2_ +\xc7\x0e\x02\\\xb7\x18.0\x9ca\xb9\x9a\xe3\x13\xd8\xcf\xcf+\x1c\xba\x81\xb5sl4S\xa1\xa3Ze\xc3\x0f\xc3\xfb\x95\x98\x03`d\x18\aq/\xe8ô\x8e\xe3\xd7B\x1f}~\xf0d\xa0B8M\x1e\xf5\xa3F\r\x1a=a\xd8\x0e\xc4h\xfe\x05A]B5\x10T}6\x19\x87\x17\x8b\xa0\xf7,)\x99\xdf8'\xdas\x98\xb0w\xad\xc8\x17\x84\x91\b\x06.\x1e\xc0\x9f\xac\x1e\tvH \xfd\xe1\xf0\f\xa0eC,\x12Y\xa4\x05\x87\x10\x13\xc4C2$\x18\xf4\x1c:\xb66\xbe\f\bFÔ\x18@\xec\ad!\xad\x85ҭ\xfe\xa3\u007f\xf7\xf0\x8b\v\b\"\xce|@A\xbd\xfa\xa2\xe3\x84\x1cth\n\xdd^\xe9\x8aq:b\x90\x03\xe6\x82\x01\x1a\xc2,\x1a\xc2Ԥh\x18G\xa0\xb1\x1c\xe8\nTF\x19\xa7\xd1k\xbdD1\xaa~D4K\x89\x18eč\x0e\xd8\f\x8d\xd7\x15\x00\x194<\xb32p[\xa7\xbb\x1cL\xa2\x95y\x0f\x81\xbcr\xa5\x10ܥM\xfc\x8a\x8co\x1aG\x17^5\xde\x05\x9c),\xf4\xaf\xb6\xfe\x97\xf4_ɭ\xe7~{\xf7\xcf\xfc\xffl\x92v\x95\xa1q\xaa\xdd\xff\xd8\x17hݡ6\x9c\xdb\xc6\xdev\xf5\xb7\xbd\x11Hzsm]\xf2;\xecߦ\xff!\xa9|\x97\xfde\fN#;\xb9\x18\xc6\xe3g\xa5\xa7\x9a\xe4)\xc4%6V\x12H\x82\x9e\x17#6\x15\x18\x8aC\xb7\xb8\x19\xcdijC\x0e\t\x89xN=,\x89\n\x9ec\x94\x88Q\x84\x86I\x91\x11bM\x88\x91$yi\x88OHN\x86\xe3\xceL\xe4\xeeG\x92\xfd\xf3\"\aAפ\xfd7\xea?U\xfb\x8e\xba\b\x90\x04\xb3.\x87Q!\v\xbar\xf7\xbf\x91\xcb\xfa\xba\n\xb7!FL\x93?r\x90\x18\xc1K\xfb\x84 \x88\xceP\x02\xd2\x1c\x81\xc8_\x89\xf8\xd0\xebo\xec\xfc\x87F\x8d|4j\xd1\xe2\x05\xaey\x04\x84F\xec̡P*@`^W8\xb9\xc9\xcents\xab\x95\\\xac\xa5u,/Z\xf6/۹i\"ڗ\x14\xba\xbd\xab\x04\xa7\xc2\x16)O\xaa\x02\\\xc7\x12\x98+.\xbcn\x04\x18\xec\x83V0a\x15\xc4\xc5k\xd6\bAdPb\x83T\x1c\xb2\v#\x01\xc0\xa1\x05\b\xa8Idͱ\xecA/\x02\x93C\xe9\xe4( 3vjD\xe4\x85xZ\x86+r\x81K\v\xfa9\x8f@\xbc\\\x91(\xf4\x91\n\xf9p\xe3k\v3$l\a\xadaL\xcc\xcaVegRU%RU\x12L\xd3\xeah\x12\xa0e\x03\xa8 \xc1&\n9\xa3\x9b9Ý6\xf3x9+\x028\xd0\xe7\x8e|\xea\r\xe4\xd2\xcc&\x82\xb6\x98P\xe5\x0f\xc1\xc7^\"ު\x88[\xa4\xb1\xc1\x93w\xf1_\b\xec\xe8\xf0\x0f\x05h\xacB\xcc\xd5{\xb8\x1a\xbc\xad\xaa\xdb\f\xcdXo10\xea\xfd\xd0\x14\x8a\xec\v&\x92\xbc\xc6\xc49\x87ƍc\x98X\x9d\x91ٝ\xa1\xda\xfa\xfbI\xd9!\x0eO\xbd'\xd4\x1f{c\xac\xa8]\xf4\x8f:\x1b\xf7a\u007f\x8c\xa21\xf3ν\x0f\b\xe6\x8a\x11^[\x13\a\x13\f&n\x97\a\xde\xc0\x03y\x10\"\x01r\xf3\x93v\x16i\x13\xa8.\v\x93\xb4\xf6O\x91\x90\x8a\xbd\x96\x8bS\x17\xb3SR@\xdb\xd0\x063P\xe3\x05\x8c\x97A\x92\xcbլ\xf1I\xa3\x99\x03\x860\xe7\xd6b\xfdX\x17\x8dm0\x842\xeeK\xab\x03\x1e\fzD\x8eC:O\x8c\xb9\x03\xe4W\xc1\xa4<\x1eh\nMj\a'\x86?\f\x9f\x89\xc5\xe4\xa1\xf8\x11\xfezh\xba\vf\xc7\x00\xfa\x93\x01{\xfcxk\xbe\xfb\"Pn\x02\xd9Ь\x96\x18k\x12\t\xf7\xa5\xe8\xe9\u007f)X%6\x1a\x82d\xa6\xd1\nU \xf2YXEE\x8fǢ\f \x86A_\x931B0`\xd4\xfbWm\xb3\t\u007f\xabO;a\xa2\x8aNu,\t\xa6/kn\"\xabڷ\xec\x9dj\xcduI\xadQ!\xe4Ŀ8\x9a\xae9$\xe2A\xfb\xe0\xc1B\xf5a\xc1M\x12\xd9mnU1d\xb1c\xe6+-\x15\xb5\x19\xfe\x8c\xf7\xe2hCd\xbef\x12\x05K\xf3\x83\x9eʲ\xd0p\x80\xa3\xe8C\x12\xa1\x11\x89FJ(\x1b\x155\x8a^0d\x18\fRLi\vd\x19\x11Nu%\x1cJ&&\x8c\v\xb5\x05-\u007f\xa2\x01\x99\x96\xad`\x9c\x99{!&\x1f\x16\x03ƂJpƹh\uf0b3\xab\xfclؼM\xef\x1e\xaf-\x1e\xbf\xca\xea\xcdGB\xddL%\xc7\x1c\xc3[-'I\x9d\x00tONV\xc9JH&\x15\x0f\xa6o\u007f\x1d\xbb\xab\xe7\x87y\xc9\x04\x96\xe8\xef'\xd4\u05f5YF\xa1\x15p\vq\xd1g\xa6\x84?Q,\x95<\xb6\xa1G\xd8\x02\x91\x9c\x97\xcc?\x9cpM\xb9R%~\u007fJ\xa7M\xa4\xa1H\x0f\x9e\xed\xa8\xf3\\\x8aZڰ&\x02\xb6\x1b\x9b\xf61u\xaf\x14>`\xd5r\xf13ߩ\x83k\x14\x151`\xb1~\v\xdfqZ\xba\x8b\x85\xe0(LC\xe3\xc3\xe6\x046\x9c\x1c\x12\x06\xe2s\b\xc5.\xa8\f\xaexi\xf4r]\x16S\xaa\xc7\xf4)\xc7P\t\xf0g\xa3\x88\xdd\xe1\xe9<\b\xd3Z\xae\xf7\x92k\f\"\x97\xc5\x18\xddb!V\xedHǒ\xa52\xfd\x83\xfbaE\x160A\xf1`\xad\xea>^\x81\xfd|d\f\xa0\xe2A\x9a\x9f8\xc0ᘄt\xd0\x0f\x8e\a\xc8\xd80q\x81\xb13\xc33s+\xe7\x02E_\xa8!\a\xa8)\x10R\xf8\x19\xd8\xea\xcf\xd91\x85\xe5)\x90\xe6~\b:u:\xe5\r\xdd\xfb\xceu\x15\xf0\t\x05\x96)\x0181T(\xadb\xc6\xfe\xac\x8c\t\xf7Ԥ\xa2\x1c\x99\x18\xdd\xe9\xa1 \x14f\x10\x92\x04\xc6Z\x0f\xc5\xcfU\x84|x\xda$f\xfaDKɦ\x81(o\xa4G\x1eD:\u007f\x13\x83\xedH\fizɨ@1\xac\xacZa\x80ˣ\x05\t\xfb9R\x92\x99<\x80\x8e1J\x10\xf8\xb4%\x06L\xbc\x81\x11a\x0eLD\x98\xc6&\xe8\rg\xca((\xd2\r\x89F\x9f\n+0j-\x88\x06'\xa6\xb0%\xd0,\x02\xa4\xc3)Q\xb7U\x12U%\x03\n+T8@\x89\xb0\xf3L\x98\x87\x11ʱ!S\x190\x0f\xd5\xd5\xe6\xd2V\xfe\xa5 l\xa71\"e\xac\xf6\x04\xa7ك3H\x11\x8a\xf1\xe8?r\xd0kw\f\x93\xa9\x01\x11U\x9b#\xe5Me\x9aJ\xb9\x9cJ\xac\xfa\x10\x0ek0\xc52\xa0j\fHABy\b\"%Fꇫ\xe6\x9e}\xbb~\xc4\x05B\xfaN\xb9\xc9\xf0nG0\x91\xe3aRع`\xcfD\x01\xe9>\x8dܝ\xa0N\xd0\xed\xd6\xd1m*\xd0\u00ad\x95@\xb6\xb1\x80D-\xa6,j\x9c\xb7\x86\x9eEF\xb8@\xfe\tC0\xf9\nkL5KՒ\xc9J\x9e\vTB\xc8\xf1\xe4&\xe4.\x8e\x92\x1e\x98\xa1\xcd\xc5\xd8\x06\xdbv\x03\xb7\xa0\xd9!\x0e\x017\tp\xd2{\v\x8a\xca\x13\xc5S\xb3\x01\xf7P\xabRMGS\xc78a\xdaE\x12\xe2\xa5D#\a\xbb\xe0U\x10\x9c@P\x98;LH\xb1\xf8\x18\x1e\xc8^\xcc^\x85\x8d\x16\xb4Q\xf9\x17O\xc1\xc8\x1dT2sN\xa1\a\xa8\x12\xae\xca\ri\xfd\x1ejȔO&h\x04R\x00\x8ai\x1c\xa2\x97͟\xf5\x93\xa5\x15\xfd\x9dD\x16\xa1\xb7\x90\xde\b\r\f\xd3\xc6_X\x19\xa4\x861\x93\xbdiЙ\xb35\x15\x02\xd4\x04\xc7\xfd\x12\xba\xbb-\xecN\x16\xc3\x1cZ8H\xfb\x94n\x91\x132&a\xb1\x18\xf9\x9c\xe6t\xf6\xf1Y\xa8\x95\x9c\xc7\xce\r\xc2\xe3@E3\xacc9:\xd3W\xd0~6\x12\x89\b\u07b8\xa8\xeeD6#y\v-\xa7\x98\x92\x88浕 !\x15J\xa6\x93Wb\xb0\x93`V\xc7:\x16\xed\xa5m\xa3\xf1\xec\xe8f&\xf9\x118O8\xf4\xa2 \x19\xc5\x12\x92\xc3@\xf8\xc0\xf2\x88\xe2\x1d̜\xbc\x9dH\x8a\x11\xcf\x04`\b\xed\xe1\x99Κ±\x92\x97\xa0\t9\xbaQBw\xd4\nM)\x84uQJ\xd5\x02\x99`4I\xe9]\xa1<\xa9܅\xbe\xab\x83\xdaK\xbc\x9d.\"d\bM\xb0\xab\xe2ư\xf5-CŇ\xf5\\\xb2\x13\a\xaa6{\vu\xd8(\x17\x02L$\x95(\x83\xdd2\xac\xd1G\x1a8\xa2\xf9$#\x13\x18R\xb0ƅ0M\xcam\xda\xc5H$\xa8Ӑ\xfan\xd9(\x13\xf6\x85\x19*\xb9\xa0\xe1%\xab0\xea/E\x19\xb9O\xfc\x94\x02A\x1cָ\x14\x99*4%0[\xb0&Zⲝ\xf8z\x17\x85K\x1a6\x9cTnة\xe8\v\x94\x9dp\v諞\x1e\x93\x06h\x19\xe2\xa0\xea.\xe5\xf6$\x85$je|A'\xbc\xd5\xfe\xc8\xf2\a!fF\xddһ\x91;\x95(\x1c\xa9\xa6p\x13W\x87\x12W9!\x8e\x95v\x17G\n\xadQ\xa0\xc8Z]\xf4\xec\xd6H\xf98\xa8\x98\x1cɥ.B\x88\xcd\xe36\x97\xdb\xe2j\x8ci\x06\xeb_\xb1\xd1P\xc8X']\xf4D\xd2N\xf7g\xf9#\xcf\xcf\x05e\x97\u007f4\x91\xd5\x03)\xbc\x811 %13i:f\xb2\xb2S\x13=\x02\x02\xda3\x0em\r\xf5bb\xec\x8c\xc2֧\xe0\x88\xd1<\x86\x83hf`\xd3\xdc&Ī^8 \xbbf\xddP&trHG\xe6{3؏\xaeisu\xe5L\xe6/\xa6c\xa3\xb3I\xdbV'\xael=\x99\xddz\xeau\xae\xf6\x9bڤ\xaa\xf6\bҔ^k\x138Փ,\x04MS+\xa3\xf9\xeefj\x8e^K\x99j\xa6\rOt\xa6\x1b\b\xcf&*.\xcd72\f\x94\xcau$\n\xaay\xaf\xdat\xb2\xf8\xac\x00k\"\xa3\a\xccT\xac\x03`\xca\xd8M\x8a\xe7s\x06\\+}\xeal\x91\xae\x82wM\x93ԺRh)|\xa9\x1dQ\xb5\a\xbc2Q\xb3Zg\xe9Bw#K]Du\xa8\xa1\xc6n\x18\x0f\xf6\x804\xb6\xe1\xb3\x13Ŝ\x10\x87*)\xd1\xd3\xd2E\xed\x88\r\v\x12\xb3nt\f^\xca\x04\xf7#\xe8\x19\xac0\x85\v<6\xba\x81\xael(\xb4\xcb2\xbelB\x1cC \x95\xbc\x9d{v\x12 \xaaR\xb2\x83I\xf0\xe8&\xbe\xf3F\x85VIuN+\x9b\xa7\xb5\x10\x153\x95\xd1[\xf0\xd6}\x05V\xf4\x18\xe8\xd8\x18\xb7*$\x13\x03\xb3CA\r\v=\x19o\xfa\xcch7\xf8\x8cEYY\xe8\xed\xf0\x83\xb2\xcd̗+\xcdg\x83`ߘ\xd6\xe4\xb6\xdajTI\xed֬L\xab7\x01\xe9\xc0GƠF3P:H#[\xc10\xe9\b\xffY0\f%0\xbeN\xfb\xf5U\xbf\xc8\x06\xc79\x82\x15\x83:x\xe7Ic\xa4)\x89\x03lZ98\x06ΰf\xe11V\xa6\xadJ j\xe2\xa3QU6y\\\xfcٿ\xa3\xc3nJ;dN湉\x89\xf5\x9c\xf7\xc4^ή\xf2\xcb\xed\xe8\xc64\x9fg\x8a\x9eR\r\xe6\xcc7\x18y1$נD\f\xedx\x8c;\xb7\x86G+\x86\re\xf9\bՂ\xb3\xcb2\x0f$\x8d\x10\xa6\xd4Ph1\xa0\x1e\\\xdb\x13z0\xb3A!NX\xd0\x13\xb6\x15\x1fN\xa27K\v\x80\xb6\v\x9c9\xba\xf7\x1a\x84%uш䧁`\xd8\xd6?\x8b\xccO\xd2\xc60'?m,\x8ec7\xcc\xef\x04\"R\x18s\xac\x13\xbe\xc4`\x1dȱ\xa9\x89 \xd7\xe0\x99N\xe6s\x89\xc0;Y\x1fC\x96\xc4\xf4\xb1\xfb\x95\x88(\xe7\xdd\x1d\xf2\x10&@\xc2\x1e!D´\x8f\fӔ\xe7\x90Np'I\xfb:>\x98\xd2h\xa5&\x86\xd3\xe8I줐\x02\xe6\b\x15Evg\xa6\xc0Y\xe65\f\x17Q\x9a\x82\x95\b0\x0fn\xfet\x1f놆Y(\x8da5\x86\x9f\xe9g\xff\xab\xfd\xfeHp\xc8V\xfao\xb1\bÂ\x961\f8\a\x92b\xfc.J\x9cWb\xf0\xdf\xeaϋ\x86\xfe,\xa7Ǔؼ\x93\x8a\t\x1d\x83\n\x1e\x0e\xa0Ү\x035\xd32\xac0\xa3<\"\xa7\xc4c\xeen\xf8ôx\xc38\x19\u007fgX\x84\xde?\xde\xf9X\x9dPL\xdcK\nlCЕ壦\xb6\xfeJ\x0e\x85=\xb1q0R\x9df\x9e\x90\xbfM\xacL\x94\xdeX\x0fa_ܶ\xf6L\x80\xff\x80ɰ\x1e\xa9\xf5\x93b\xa3\x03U\xe8?o\xb8H[\x88\xab\x10PN\xec\x87\"+\xdb<\xa7%\x11<8gBY \xb0Kr\xd8^z*\xb3ǮA\u007f\x03\xd0,C\xd7\x15F\x91\xfcä\x05\xfcଇ\xf1<\xc4\x05a\x1d\xa1\x994S\xacuQP\xdc1\xc1rp\x9d}\xcd\xf1\xfb\x9f\x89r@\x17\xe4y\xc0\xddM\xd8\xc3\"\x80\xf7\xd2\xeb\xa3\xc7c)\x15cc\xd6>\x94\x16\xd6\x11%d\x13S\x82\x1d\\\xb2%\xf7\":`\v\xb27X\xabl\x16\xb29\xb4T\x02\xba\xbc\x12\xc07\x98\xb7\x1d\xe5\xd21,l4`\x9b\x83\xdf\xd7~S*J\x89\x10\x15#W\xdc\x04#\xab\x98\xda\xf3\nf\xe5\xb3=\xfc\x12!L9\xb1\vD)\xc0\xb1O\x10\x14\x04\x1f\x05\xa9\x84\xb9\xe9\x12֔4+¡\f\x9aA\xb0\x19\xf0jٙlр\xb1#\xe4\x14\xf2\xfb?\x1b\x9c\xb5|\xee\\kê\xde״W4od\fu\x85Y\xaa\r'\x95=2UZ\xc6`q\xf33\xab#\xb1怅\x1b@Ӫ\xfa\xe4\x9cN\xf5c\xf6Ќ@\v\x94̗M^GS\xc2\xf5=\x12\xb7\x9f\x103)\xc8@\x1c\xf6\x12r\x88\xa3\xdfQܪ\xacIU\x16\v\x86\xd1B5\n\x82ͫ\xc9\xfe\vaQ\x83Xq0\x91h\x05\xd0\xe5M\x18\xae+\x8ah\xe8\xe0z8\xd5g.\x98\x06\xd6\xff\f\x01\xa1j=\xbf[4\x9a\xc2gz}\xcfg\x06\x14\x86k\xa7\xa7\xf0\x05\xf1TM\xf5v\x05f\x90\xf0\x84\xc7|\xb3\xd4!\xe9\xd6.\xa4g\x8e\x0e\xd1\xfe0\xa2m\xd3ȁ\x12SR\x8d\xec\xc8*v\x1bQ\x80fU\xfa\x1c\xbd\xe6\\\x93\U000a24a9\xa7\xea5h\x92\x18\x98\x1c\xd5\x05\x0fN\x8f\\.C\xaaCF\xc1$jZ*\x9e\xcd,q\x89\xbdmR\xc7+/C+\x86\x01e\x9d\x8a\xb3\xd1\x0e\xa1\x8c\xbb\x14\xb9\x02`m\xb4\a&\x87\xba\xe2^\n\xdf\n\x9e\xd0ѮU\x1b\xbfp\"\xa1d\xbd\xd1U\xd2\xc7gї>1\xe2\xf6\t\xf0\xa9\xddG\xb2\xac\xd0\xf5ꎾ\x96\x97\xae:\x96\x00\x17\xaf\xfe\x83\"\x8b*\x9b\xc42\xaau\x8a\x84?m\xec\x9c\x18ʓ'\x1e\xc4\xe4\xe4\xafӐ\x9aNA\x1dA\x18\xa4a.\xbd0\xd2FŤ\x04kz\xa59\xed\xa71Š\xf6\x19\xcfo\xee\x9d\n\xf1\xab\xf9\xc3\x10r\xbe\x1cLW\x9c\xc5\xcf]\xba2\xae2+\x81\xad\xdbr\xa9\r\x11\xa8\x84\v\xe0\x97\xf1\xfeM\xda.\x9d\x80\xee\xb6\xf5\xbcW\x05\xa7\x98\x85\x02[\x94\x95\x00\xad\xe2\x15\x92\xa2\x8f\xb8Z\x05\xb6\xa0n_\x915\xc3@,\x8d\xee\xda%\x91\xa4\\\xa8@p\xdbD\xb2!\xb5\\\xdd\xee7\x93xb]uւ\x8a\xe7\f\xb8$n\xb5\xdc\x1d\xbd5b&\x1ch~A\xea{\x01>\x8cP\xbdO\xbe\xb7^w\x80k\xca\f葾dr\x99\x1c\x13ϖCҜp\x9e\xe9-\xce?6E\x13\x94oj\xb9L̓\x03I\x995p\x05k\xd8bۇ2\x1eJ\xa4\xb9\xdc4\xe6I\xf5ٙw\xddXb\x0eFޯ\xa8\xc9\xee!\x02u\xc2\r\xb5\x04\"\x88\xc1\x95o_\r\x87E\xf0f\xf4\x913\xe8\x13A@\xab\xbf\xcdvr\x84\xc9\v9%\v\xe6z\xd8$-6\xd8\xe0&?\x02\x16\x9eh\xa5:\x10\xe2\t\x10\xa2$\xe6\xa4\x02R@\x1d\xdb@\x1cV@WwH\x14hF\xf5\xb6\x89\x86\x1eۧ\xd6\xcbH\x14A\xd8K\fY\xde0]\xca\xf3\x1c2?\xe4\\wM(\xa2\x0e\xc0\x8a9Tp\xef@t\xe8\xfa\xa4[\xd9>t\x83\xa9PgFûCk\xc6\xd5\x1b\x98a\x8bԳ@\xe4*\xfa\vV>\vp\t\xf1T=H*\xb7\x96\xa1\x12m4\x9b\x88Qi\x94\x8e\xf1@\x8c\xd8:\x9e\xdc\x11\xe2\xf1\xba\x00v\x9e\"\xa2\x95\xfd\xefi\xe0^hҪ\x04*\xba\xdb\xca'\xba=\xedB\x0e0eLݛ\xe6\x83Y1=\b\xafn\x0e\x90G\x9d\xd0j\xc0\xdb`\xae\x11\x06\xb0\xec[\xc6:Aa\a\xeeo\x06\x01G3M\xa1a\xf6\xea\xf1/^̲Ǭ\x9b5\x93\xc6\xf2@$\x90Q\x80\xb9\xbd\xc9\x12\b\xf0\xcfX\x95uvT\x00\x88ih\x8f\xec\x89\b\x88j5(6x\xf6\a\x98\xe0\x06\xad\xd4c\t8ru\x98\x0e,$=<淽\x98_XE\xddo\xe7\x02F\x8b\xba\xfe{\x8b^\xf3\x90\x06\xbf\x17U\x9f\xa1`;Q郈x\xe4\x87\x1bY\xfe?\a0r\xae\x9c\x10\xf9\x9b\x9aX!\xa5\xb1\x1d@M\x9c\x84ܦdU\x81 \x1e\x1a\xae\x9cp`\xa7\x0212\xbe\xf4\x86ӟ,]_\xeb\xce6\x04Ɍ\xff,\xe7L\"\tR*\xc1~\x06y&\xa7\xbc\xc5K\x16L\xc0\x130\xdb(y\xed\x13\xb1\u007f\xa9\xa30_\x85\x93RV\xee1\xbf\vF\xa0\x1d\xf4*;\x01\xa4@;\xb4`]\xbe㍨RB\n@C\xb26\xac\x19^\x85\x9a\x9aل\xc5\xd0qE\x17A\xed\xce3W\xa8\x9b<\xc5ǘ\x05/\xcd\x16\x88x\xd7bN)r\xf9\xce\xe4\xa2E=x\xaaU\xb8\xa43\x11\x96\xc1\x15\xd3\x11>E\x8d\x81\xff\x10\x1b/4\x90\xcc\xc3\xe7K2\x88\x87]1\xce\xdfN\x1f}G\xb2X\x8c>\x99\xdcc\xf1L\xccA\xcctz\x9c\xe9F\xee\x8b\xde1Gφ\x04\xfc\xea\x10\xa5\xe6\x1fY\x04J\xed\x06\xda\xf3#\x96\xf3y\nQKC\xc2\xc0\xe6(\x8f~\n\xfb\xff\rhTAbr\x8e9,'=\xd0\xd2T\xd5x\xa9NT,?\xa3\xe7'\xea\xa2\xees>`0~@N\x94\xa3\x12\x9e\xed\x86〦H9n\x9cjJ\x1f\x1dqP\xc6\x17/\x0f\x99\x18U.%\xd0\xc9\xccH\xd0oJGd:\x18\xb2\xe7^\x88\xeb\x83\xec\x0e\x1a\xdbY#\xb0`\xad\xb8V<\xaa\x93\xb9M\xaf\x10\f\x9b\x9c\a\xe6\r\x12\x13o`\x03\xe4kdZ\xeeCX\x94a\xe4!)\x12\xeab\xc3y\x90\x82\xc3lRJ\xe9\xf2;\x16ߪD/ң\x91-\xc7E\xb0\xb3\x94B\x18vf\xd4CZ\x88\xf4\xfc?K\xc5\r\xdb\xdcP\x0f<\x93\xce\x11\x91\x1bU\x84\xd6g\xb9\x86M\xc20\xe5\xf0\r\xc0My\xcb\x0e\x9c\xf3\x83\x18f\x87\x80ia\xfd\bE\x89Ъ!q\xb3+ɍ\xb9\xa9\x9b\xe3\x94\xd4\xf99b~\xadQ`\xea\x9b\xec\x86O\xda\xc34\u0098}TՆ?Sh]q\x87b\x89\xa0\x80J\xb1\\Qy\x05\x17j.\x05\x13\x1f\xd6\x03\b\u007f>\xb9u\xfb\x01\xf0\xf9Lm﹍\xfd\xbc\xa5T\xa4\xaf2\x1c\x02\xedؿ\xa4/dٿPV\xbdJPW\x1cHX7\xdfhUr\xf1\x06\xb9\xd4i\xcd\xc1\x10\xbaT\x83\x80\n\xfbI\x12\xc3ΛG\x1a\x84\x94\x9eڍ\a\xa1\xdc\xd1}ܒ\xc4f\n\xb0w\xbeC\xad\xa6\xe1v'RR\xa5D\xbd\xbe0\xd5\x03\"/2\x81~\x94\t\x96PP\u0089\x8a\xca\xe8\xa0\xe0l\xab\xfc\x9cd\xf21z\x05\x1e\xa4\xb3\x1c\xc1\xf4\xe2\xdck\x1b8\xa3\xbf\xca5pB\xb3yc\xab\xa9j\x19\xe1X!\xbb\xf6\x14;'s\x8eǎ\x81ƖŶ\x1fe\x8cJy\x1b\vo\xb8@\x85I\x0f>\xe5\x19\xf0\xe37\x9f\xf6\x9e \xd8`^\x847|i\xfc\xabj\x00\xd3\xd7\x16Q\xf9\xcf\xd59\xf5\x1e\x99\x84\x0f\xb6#\xa9\xf3\x12P`\xffq\xf4\f\xa3\x9dm\x1f6\xa8\xc1\v\x19\xf7\xeb\x8b\x00X\xfea\xa3(\xc3\xfd=\xb2\x84)D\x88\xe7\xc9#\x1d&\x87H\x1ca\te\x13G[\x18\xc5ʁ\x17\xc0Ʀg\x94X\xdf\x1d\xc1rZ\xaf\x87a\x8ee\xcb\x10\xfcf\x14`\xb0g$\x8e\xe3D\xeeRQPG\xd4}Ø\x86\xc9i\x912Źө\"\xdd,\xd8\xeb\x95`\xd6\xea6t\xf2\xb8\x1cU\xb6\xea=o\n\x8c\xac\xfe\x1f\xfe\x8d\x85E\x06#\x0eQn\x1eH\xe3#\xc5\xec\xdeI\x1aJz\xca_\x10\xe8\b܆r\x92\x02]\x15\x01\xa6E\x998\xa6\x19\xf4G\x0e!\xd71D-7\xcdt\x11\xd7\x13aXj\x8ds\x1b\xf8k$\x91 q\x998R \x88\xa7eq\xac\xdb\x03\x9ae\xb5\xa6^\xc5\a4\r\x1e\xdet&\xfc\xf1\xb5\xaeN\x1b(\x97c=|;p\x97\"T\xd1z\xae\xe4Ȅh\xb40N\xf1\x0f\x92\tq\x19h\xe1\x85\x16\xba\xa3\xe1\x06\xd2%\xda\x13y\xa9\x0fw\x03\v\xff`(\xa4w\xa4\x88\x19\xab\xb1p^A\xac\xf5&֬\tx3\xb3\x8c\x15\xbb{\b\xdc?r\x0e\xa386\x19{(/~\xa19\xf4\xc8\xc7Zr\xc9\xc7\xdcમSi\x8f\xf85\xe9\xfdfʶ\x89>@jag\x13z\xe9nCv\xac\x9b\x145\x15\xe6ֶ\x8bC\xe9\xb1}\xa0)'\xbb^8\xdbl\xa5\x82\xac\xe7\x97\x16hS\x8a\x8c\t\x99\x14$\x13e7]\x15m\xc2Mo\xe8;\xd6\xe6'\xdeQ\x16'N\xe0*\xe6\xcd6Oj\xf7\xf4\xb80\xb8j\xb8\xa7W\xd7֡\xaed\xb2\x98\xb9\xc5t\x06[\x9c\x93\xbd\xba\xca\x14\xd1\x14-\xa8\x06qQ\xe8\xf0\x9eu\x13\xb9\x92v\xa4\x88\xc5юe\x9b\xa9\xd8\xf8\x91\xc1\xbd\x8b\n\xd0!-\x1fɓo\x12Cva\xff?;!\xb4p\xe2\u007fs#ڏ˘\xbdx6\x19\xe6\x82\xf7l\xd5\xf1{\xff\xc8I\x92ݔ\x95\xde\x03;\xf6\x90\xb8\xa8r\xf5+n\xbb\xb9%\a\t\xa6\x03T\f\t\x12\xb8\xec\x8aN\xca#\xb0\xaf\x92*A!\x9e'\xfc>:[!l\u05f6H\xa4LY\xd9\xeb\xb9\f\x82\x9d\xef\x18e\x9f\x8d\xad5\xb8\x19=\x96\xf4\x11\x862䇄\xab\x11\x9b}t\xfdz9\f\xca\x00M\"eh\xc0\xecRI\xc5d\xf1]1\x96/B\x15S@\xe7\x02\x84\r\x91\xfa\xc9\xff\rj\xec\xe3\xb8H57\x8e\xda\x0fT[\xa3\x13\xf9T>\xe9y\x05\xf5`vF\xed\xa7:\xc5^\xf5\xb5\x03\x11\x8b\a\x96\x1c\x88nXI\xdb,\xbf:\xe0\x10@\x87\xc7E\xc6\x02e\\\n\x8f\x13ʠ\x99k\xd3cQ\xc94$\x87\xe7\x18\xfc\x92\xd9\u007f\x18'`\x92Z\xb4\xbe\xcd\xef\f8\xb4i)&\xb0[\xa9\"k\x85\xb5\xb2P\xedCy!C\xe9C\xbb\x01[\x1b\xc0\xdep\xa0\x9b\xc5wR\x0f\xa3\x83\xe9\xfbK9/\xc1D\r\xbb\xb2N\x86\xac\x9b\xad\xd41\x0f\xca\xdc\x19\xect\x15EiP\xea@T\xb1\x86\xa0\xdd\xe4߭\xc1fD7\xf7\xce3\x8c\xbaP#\xa6\x89\xa8\xafJ\xcf\xee\xa5\xd7}I)Q\x8e\x89\xde\x12\xc0\x0f<\xb64\xaa\xffC\xa25\xa0\xf2\xa7M\x92\x90D\x10\x82\x17-\xa3\a\x82]\xc2Xe\x02\xef\xe8\x89\x02\xb5\xfaɌ\x16\fϞnÃ[҃r\xe8xw5N\u06028\x80\x81\x00\x00)\x9a\x95Wn\xbf\xc4\xc4Ɔ\xe2\xae\x1c\x8c\xaeYuZ\xb9\xe2\x9b\xe6\x81\x0e\xc9)hm\x12\xfb\xb7d$s\xce\t6\x85\xe8\xfcᤚ)\x91\xea\xa1\f\xfc\xb3\x85@\x8d\b\x14\xef/\xa5\xdc\xd6^\x17\xe0\xcdn\xbc\x10\x99\x1a\xe4p\xd2\xe6\x06\xb2\xc2^>\xf4\x04;AD(\x98\xf0\xbe\x86CF̋^\xe7p\xcf5\x12J\xc1g\x10i\x97U\x1a\x10I\x8a\xcc\xcam\xc5\xee\xaf\xedt\fB\xfd\u007f\xcb\x14|ږL\xe5k\xb3\x15\x05\x8a\xbb3\xd2\xf4\xa8W\x94\xce\x11\x0fR\x11\xbdm\xc9\xc7\rжJ\x8cb\x10\f\xdd\xfeX\xdc`\xfd\xe5|\xa6\xfd\n\x86Y\x15\xbc\xca`\x86\xa0\b\xd4=\xa2\xcaF\x1b\xa6\xa3Ta\xf4\xd8S@ő%\xee\x9dI\xa0(\xe7vT\x01\xa9\xd8>d\x84qLX\xbdA+\x10\x01\xcaXY\xefݫ\x9a7B\xd2+\xac\x92V\x94\xa4\x87\x02\xa8dʅi\xb0:5\x9d\x1f \xad7\x81amP\x90\x1ez[\xc1n\xfc\xc8\xd5\xe0>\x01\x8a\xcevd\xaf(\xfb\xb0\xc8\xd8F\xccS\xa6i\x85\xeac\xb2\x9anr\xa5ȃ\x8ezʚ\xc4S\x95\xcdq\xbc6hâ\x99\xb8\x9cp\x98\xeb:\xd1\x04[\x83-V\xb1Z\x94\x93\x8a٘\xa9,K\xef\x00K\xdb+Όl>8^\xd2;D\xe4\xa1\f\x98\xf2\xb0:|r\x95\xebK\x11\xb5\xe9h&\x16\xea\x1c\xe9\x1e\x90\x19\x8f\ag#\xb5\x8d\xbeM\x88\xe9\x86\xee\xb5\x10\xaeW0TL\xa9\xcd;K\xb9\x95\t\xb7\xda\a\xca.C\x0ep\x12\x15\xd7U,\xf9\x1b6\xc1\x1f\x0f\xa7\xec\xc1J\xcfԊ\xc0\x00\xe0\xde\xc49r\x8a+\xbc\x12\"\xe6\x1dm\xe1\x1d\x8b\xa7\xd5\xc1\x8e6KF\xb5%\xab<\x90x\xfd*\xce\xf2D\xe1\xe8\xb5\xd1FY\x12Vx\xe2\x80\x1dT\xa5_|\a\x8dt\xd0a\xf4\xd0\aٞ\x86\x183C\x0e˜\xfc}\x97dž=\x18\xa4\xdc\x1c!\xc1\x0e\x12-\\\xd2\x1b\xa0\x83\r9\x1d\x94\xc8\x14$\x8d\x10\n\xf5\x17[\x9b\xeb\xf8\x06\xfa\x14\x11&Mpp=\xb3D\xb8\xd8\"25&` ]\x1f~\x99\xc3<\xfeY\x9e\x898\xe5\xe22\xc0\x06&\x86\v\x84\xb8\x17TZ/G(^\xaf\x8b\xa0\xe1\xd8\xe2\xb8\x1f\xb2\x80\x8fG\v\xa8\x91\xff\xbb]\x9a\xb5\xe2\x98\\\x1c\x0f\x8cw\x1ei\x182x\xad\xac\xcd\xd6\xcf:\x00֞\xe1+\xab\xfa\xdf\x15\x04\xd6\xd7\xe2\xa2J\x90\xe6\tw\x10*M\xb8\xbe\xb4\x9171Nt\xc6DF<\x02\n\xb5ġU (\x13\n\x13T\xa8\x84\xb8\xf5uA\x91U\xf4UB?\xe0%\x9e\x8d7\xff|\x05\x14\tn\xc7I\xda\xe3\r\x0e֯\xc5j=ԝ\x87\x88\xa8\xc8a\xea\xfc\xea\xbep\xea\xd6C\xef\xe9\v\xe9\xa8Նb\x88U\xae2^Ҏ$\x06\xec\x99\x01k\x11\xaf|\"\xeb\xd5'\xb1\x11\x13(\xb8\xfd\xac\x0fV\x10zʶ\xc1\xf1o*&\xb6\xec\xb52\xee\xecW4A/\xd1\f!\xc8H[\xe1[\xb5\x06\xd4Է\x8fs\x90\xb9\xfb1\x01\x18\x9cQ\x19\x80\xc3H+æ-}\x05\x80\xe1\x02sP\x06\xd5\v\xd6_\xcf\x17\xd0r\xdeN\x11\xb6B\x8b\x02\xfef\x89}~c\x82\xf87\xb8\xad\xb0&5,\xb4>3\xdb\xeeo\x81*\xceK\xb4%e\xd2q\x17n\xff\x15\xa5\xb2Ғ\x8e\xfb\x03\xc3\xef\x9cj5\xab\xd4\xfa\xe0\xc51B\xff\xf7\xbd\x0e\x10\x90\xd4|Vf\xbb\xa8\xc1\x96}\xa0\xa7:\x9e\x8c\xc4HVF\r\xe3P\xa8\xf7 \\-\xa5J\xd5\u009b\xe5\xc8\xed\x85ʉ\xd3\x11N!R\xab\x01H\xf2\xba\x15\x1fV\xb0\xc8ئ\x00\x1e\xbf:v7CJ\x1b\x8dS0[\x96*I\x88\x89\xbd\x04V\n\xa1\x83\x98t\xe2\xcc~|\xab\xca\xdf\xf3{H\x81H\xf5\xa7%\xb1\x1a\xd2d\xd8\x03\xb1\xd4\x05:\x02\xb4\tiw\x88\xab\xab\xbc\x84:\xa4\xb21,\x18\x06\xc6\xcf-\x81\xdf#\xec}Ǩ\x83\x9du\r\x87\xb2>\xbcͱ\x83\xe4\xdd\xf7\x11\x94\x17\xb3\x8f7\xf6\x0e\n\x9e\x15\xde\xe2\x03\x13{A\xae_\x8e\x9ds\x10M\xb1][&&C`*pQ\xfei\x14\xf6X@ƟM\x1c\x92&A\xc6q\x844\xbb\xc7\xeb\x14\xc8R\xcc/\xbd3\xa0\xb3\x1f\x99i\x93\xa2\xd3\r\u009d\x11I͋W\"\xa7\xd5\"\xd5z\xe5\xcdkӌ@\xf3\xd4\xf0ҡ\xae\xf1R\xa6\x956\x10\xffX\xa9\xc2j\x8f\xa4da\xe0\xcfϒA\xcc\xc8D\"K\x1c\xbf\x1e\x16E\xf4NjSUƪ\t\x9a8\xe7o\xe5x,\x83i\xd8\x14\x13\a\x1b\xe3,\xa9OI1{\x14T\x9a:\xbc+\xb0\xcd\x15SP\xa7\xceP\x80J\xcf\v\xc0\x12c9\xf8y\xa2ރm\xd4_ \xafU\xb68\x87G\x92\xb0\xc2\x1d)Y\fQ\x03\"\xb10\x85\aD\x8d\x06\xc3\xe95.\x13\x8c\xf4\xa8%\xa9\x9fY\xfa+\xf4\xd5\x10\x1e(>\xb9\xf1/-\x19\x8a\x1e'\"\r\xcdK\x8f\xaf\xa4\xc9\t\x91\x97\x8b\x1fm\x82\x9d':\xd7U\x8f\xe9\xa7\x01p\xa6{\x85`\xf2\xeaRM\x18\u007f\xb7\xc1h!\xb8|\x05\x84\x17\"\x1e\b\x1b\x1e\xc8\x04\xc9\xc0\xb8\x12⪂X/g\xb8\u007fw\xa42(\\\xe8\xabr\xa1\xea\x98\xf7\nf\xb2F\xc3/\x1d\xb9\x1c\x8a\aY\nQ\xc8^ɛ\xe3 6X\xa9n\xea\xb1L\xe8@A\xc7X\f\x8a\x05Ib{ʰE\x8d\xae0@{\xe6\xf3W\x1d\xc8^M\x00\t\xb7\xae\x82\xee\x8aQ \x92\xecw\xd9\r;\x96\x1f%M\xe1\x96\x10!\xfd'\x1c\xf2\xc2L\xbb\x83D\xdb\xd0*\xa9ɕ:K\xd6\x01\xe4XMzի\xf3\x0e}\xc96\x80\x87\x84\xbcD8\xb4\\\xa2R<\"/\x17E\xc21\x00ᩕ\x1a$\xf9\xd16\xd7\xf8gI\x94\xbe\x9b\xbdc\x1d\xf9OB\xb4g\axݍr\xc1\x86v\xc0\f3\rʉ\\$f\x83\x91\xe2\x1c:lQ<\t\xcf\x00\xebF\x8fW\x11b1\xa6(\x84B\xeb\xdb\x19A\xecl(\x89\x01\x85\xb8\xae\x966w\xf6A\xc0\xd8\xc8\xc5(ɩ\xea5\xf9=\x05\xcb\xe0QǴ\x9c\xfb\x17JN\xc4G\xd0e\x1c1H̑\x8eL\xdcF\xcec\x8c\x87790\xf6\x92C\x10\x8eM\xebZ\xcfY\xac\x12\x9cR\xd7\xe5\xe9\xf0\xb8pq\xed\x00Vy\xfdns\xb1\xc3V\x8d\xf5|F\xcd\xfbtI\xd7\xc9N0\xc8\xf9\xb4\xfd\xa6Gƅ\xab@\xd7\xd1s\x81\xc1\xd8H\xc4\xe2\xcf\x0f\x919$\x9d\x8a\xe2\x1c\x01\xe3\x9bb\v\xa2\xd7\x19 \xf2x\xa3g\x9b΅\xcf_]\\[\xf0\xb6\x8d\xe8\x06\xfc\x8b\x0e\xf0\aR\x1e\x94\xfa\x1bn\xdaq\x9b\xea\xbb\xf1\x1ea\xa2{\xff\xc1\x83\xd2\v4\xadJ&\x19\xae\xfc\xae\xed\x01\xa4:\xc4\xe6\xc2\t\x0e@U\xfa\xca\xfdL{\xae\xa75CxF\xc7 \x8c7\x96r\xc0\x8d\xf9\x9dh\x86\xf2\xf3N\xf0{\xe9\x9cް9\f\x87\x11\xa2\\>b\x95bH\x17\x82\x01D\xd2v\\P-\x81\r\xb6\x15\uf125\xee\x01\xb6W\xa1\xf6t\xab\x15\f5\x94\n\x15#t\x91%\x8c\xc0\x8e\x12}\xc2\x0e$\xe0\x91\xe5\x10\xdeg{P\x90X\xfd&`F\xa0\xbb\x89\xb3\xba\x84\x1b.\x066\xb49\xa2'\x94'T\x81סX`д\n\xf2@,\xdf}\x8f\x10\xc3z\xa8&\n\xf1\x9ak8?Z\xac\xf3\xa7\xcf\xc5\x16\xf2R\xf1\x02L\xf3k\x19\x10\xebps\x02]bY\u007f\xf6\xd6\r!\xba3-\x8fT\xaf\xad\r\x13^?\x1f\xba\xb43۴|}\xe0\\\xf9\xefGկ&d\xf2\xce\r\xc36\xda.\xf9\xbd\xbe\x00\xf4A\xf5n9\x96lr\xcd\"*L\xeb\x86\xfe\xc7C\x1c)\xa6bf/\f\xd0\xc53\x04\x87\x81\v\x97\x11c\x1f\xa0\xe7 \xa6Uu\xdf\x05\xe5\xaeԚM\x8fT%\xe4\x9b\xebě\x90\xc2\xe1'C\xc2\x05\x94\x01\x1bh/\bL\xbf\x83\x88w&\xff0\xea\x14?n\xda*\x95\xee\xa2\\\xd3\xffB\xb2\x12\xf1#\x05u_\t\xa2I虥\xc7k|\x82\x19\xd8\xe2ٲ\xc9\xcc\xcd\b\x0f\rL\x0f/t\xe6_\x86&\xbb\xf0\x04$\x1a\x88\x9dq;'\xd4\x05\x8eI\x02h\xe4<\xd0\x18\xa4lrP\xa2\x11:\xd8.\bk\xd1\xf8\xc7>p\x15\xa2^2\xf0\\\x12O\x99\xc6<\x80u\xf8\xd0%\b\x04!\xbe\xcd\x1f\x88\x94J\xd9\xfa2l\x1c\xd7\xf6\xf2\x87F0\"\xc0\xa4\x83\xe8\x90T\x9d\xc4\x15Z\xc9\xd0s\xf9\xfc\x9e\xe9B8Z\xba\x0f)2\x82h\x1b\x19\xb20H\xceFbud\xa7\x89\xa3\x83\v\x88\xf1{\xc4\\M]X\bP\xa8,o\xaae\xbfc]\xa9\x8b\x88\xab\xa2BY\xdeR$\xdeo\xda\f@p\x9c-\xf6\x02\xcf\x0e\a\x81\xaa\xb0\uf23cDg\xe2\x96\xc8<\xd1vPk\xc4T\x8b\xc8\xe1|\xed\x1a\xac\x96\xae\xfd\x0fd\xde\xd8\xd7\x02\x1av\x96\xa7Z|\b\xa8\blGF@\x10ro\xe1p\xb0\x96\f\x94y\xb1ឣ\r\x02\xf5\xbb\xd6G\x00?6;K\x91\xa8\xa2˭N)(^\xcc\x1b\x16\x11!\xc3aE\xba\xdb\n\xe1J\xe2\xc2\x03\ue4ce\x89\x0e>\xa8\x8e0\f\x97\xec\x1f_\x9b\xaa0O[\xce\xe0\xc4\x15\u007f\"\xc7\xd0\a\xffU\xb0.G\xf1:Q\xa4f\U00087098s,J\xbe\xa1\xe2h\x10\xec\xf8\xb0\x88\x96\x84\x84\xa2\xff\x96\xaf\x823S\xcd#\xc4Uw:Ζ\x8a܋\xed\xa6\xaf\x9cD\xe38\xf3YU\xc8\xd1O\xc2\xe00H陎p|\x99\b\x17TK\xf4v\xa3\xb4\x88]X\xa0\x9e\xaf\x89&U\x85\xeb\nK\x91a̔\x84\x1b\xaa'LD}\x89\xc6\xe8\xf5\xc3\x10k/\x16\xaa\x9b\xbd\xa2+$Ε\vV\x1c\xa8dW\x1e\x9d\u03a2REk~\x85{\v\xb9\x02\xc8\"G\x9a\x19 Y\xdb\n\xac*y\x15\f\u007f\xacy^\x88}\xf8N\x82\x17D\xe8\x00\xe3N\x8a\xfe\x05\x14S\xb9\xceY\x8el\xa4\xf7\x11;c>0f\xda\xea\xbd2\x95\xb2\xb05:\xb1\xfffQ\xeft\x9d\x06\x99\x821\x97\x8c!׳x&\x04\xe3\xf9JH\x1cs\xc7g\x18\xc32!X\x0f\x1a\x90\xe3\xcfu\x94\x00\x92\xa0\xd0Aѵ\xf9\x97ڙL\xc0`\x89)\x8a(\x9bB\u007f\x91\u05cax\xd3o\xe9Q\x93\xe5x\xe8\xa4;\xde\x0fOhTag\xa4\xba\x94EH\xf5\xdfO\x12\x17Q\x03\xec\t\x11\x0f\xd5{\xac\x00\xc2\xc8<&&Ruh\xf4\xe4X\x82\xf9\x1e\x8a\x9fqf\xfa-\xd2\x16\x8cF\xbd\xfe\x1a\xfb\x9f\xebڭ\x01J\\}\x9b\x9d\xdcR\x84_\xcb҂\x9eg\x12\x18\x9a\xaf\x16\xc5\xcbH\xbc\x17\x9d4>\x0f\xf3\xad\xb6%\x9a\x80\xe3\xee\x16\x86>m\x02\x05\x00ae\x930\x06\x800ڄ\xa5I4\u007f\xfd\xddi\xdci\xd6UW\xb4;\x17\xc8;\\A\xda\xf1iˏDNkDϓO\xe8߇M'&#]\xb9\"\x86\xb6\r\xe2l%\xbc\x10\x83^\t\xcc\xe9_5\xb8\xee8\xab\xb0\xb0\xf4!\x95\"\xb44,ufn;\x8a\xaf\xc52̩\xb6\x83ۂ\xec?'\xc6\xf7|oA\xffj\x02>ڎy6)\x87\x13\x1aEG\x8d\xc2\xff\v\xaf\xc0\xc6\x06\xed\xbd[\xee\xf0!\xd1\xf8p\xcc\xd2*\x8f\xdf\xd5`\x83b\xeb\xf0\x94\xf1\t\xd5B\xe9\x97\xf9\n\x94\x17`sy\x82\x97\xc8\xe9\x8b\x04ԋA\x8at\x86\xd6*Zc\x9a\x81\xb6=3Ȩ\xd6F\xac\x04'#ǃԹr\xe6\xf9\x9f\xf3\x86\n\x8b\xe8@l\x12\xe5e\x06\x98\x81\x9f\xd9\xc6\vNL\x06\xff\xa4\x03Rr\x00\x1c\xea\xff0\xc7\x04b8\x83\x116,\x9f\x83\x8c\xa8-\v\x13\x98\xd1~\xdd\x15ͣ\x9ct\xc7RP\xfd\x1d\xae#g<\xc5\xd0\xf9\x82\xf5\x80\xcdܳ#\x06\\k2\xf7>\x19\x86\xbb'^\xd8s\xf1\x05\xaf/Jزg\xa1\xf8P\x19\xf4YD@\xf4Ku\x89]\x84SnB\xc9e\tؽ\xb8\x85\xe7Dd\xcf\xe8y,\xd4yõ\xc6\"\b/\x1c@E\xe0Ԧ\xc1\x831\x02\x02\xc1ul~\xd81\xf7\xd1\xd6'\xf5p\xcf\xe2\x17>\xe2\xe1\x83\xe5{\xa8\xcd\xf3U\x1a\xef*\xe7ɰ\x19\x06\x17\xd5Σ\xaf\x99\xcb1b2\xeả\xee\x0eت*sf։\xb0\a[\xcc,\xc6\x16\xcfI\x1a\xe7\xd1\x19\xf8\xe5]\xb5&i\xd2\x1bqVT\x93\x89\xaf\xca8\xd7jY\x8d\x8d\"\\\x14\xd04b\x8c\"'\xff\xa0\x8f\xa6\x94\xfa\xfee\xf4S90\xbej\x1e\x8c\x15\x8e\x1dǖ\x9006\x19\xca8\x81\xeb\xf5L\xbe\x87\xf3A\x83\x9dB4\xd7x\xe9\xc1\x85\xf0\x89\x9e\x80\xc7a.a\xab\x8a\xc5\xca\xc7 \xfd\x9fw\xe5e\xdd@\xd5\x1b\x00\x03\x15xY\x16jʪ\x95j6閴\x8c\x93T\x87\x1a\x10\x16lV\x83b\x12ܦ@7\x19\x14\rv\xcbj\x99\xccY\r\xbb/\xfcT\xa8\xeei*\x14\x82\xc6\xe9\xe67 Ilte\xb5z\xe4\b&\x05\xcc%\b]?a\xbbe!P\ayᥙF\x00F\x96\xa7eYQcH\x99~\x8aC\x83\xd5w\x99\xe92\x03|\xd5\n{δ\x90\xbf\xfc\xfc\x9e\x1c\x89\xf8\x9c0S\xed\x18\xb2t\xe6Ӓ\xc4\xf0~_\f\xda\x1b\x8c\xc4\x1a\x00\x0e\xc8I\xba\x81\xca\x1e\xf1\x01\xc1\xc7\x13;bC\xb2br\xb6:̭\xfc$7))\xb5\xe5+\xebG`\x13\noo\x13@b\U000c1e0b\xd5\xf3\x1b\xb1'\x1f\x82(\x89\xaa\x04L\xc4_߈\x1f\xc2ƵI\xd5{Ra\x82\xbbT\x9b\xb4\xa7\x95\x00'R\xc1\x156n\x8f\x0e4d\x9b\xbd\x9a\xb4\xe8\xea/'\xfd\u0087\xd4\x17\xe0\xac\xe3\x15F]@\x89\x975\xf1B\x84\x92\x17#\x03\xda5\x1f\xe2뤝\xb4&\xee\xc5ǘ\xe5\aM\x0e\xf0>\x98,GK\x9c\xad\x84\x82Pdtv\xb6\xdbR\xe5I\xd1f$z\xa5\xe8<\xfe9\x84\xe4\xf7\x91\xe3\xc0\xf8\xd5kH\x04F\x80X\x18\x00<ϳ\x1e\xda\x0fw@\xdb4Pz\x19\x8fG\xe1T\x86'9\xc0\x86\xb3\x82\xd6Rj\x9dW\xb1\xcf\x13: \xee1\xc7^{\x9f'&\x9eϡu\xf2`$\xbb:t\xc4\x06\x949P\xe5A\x8f\xad\x9c?qm\x16@\x89\n3\x93I_\xb91\xe8\xc4`=_\x04\x98\xe4\xc0U\xa6Jl\b\x8fN:\xf8\x01C\xedk\xac\x9f\xe6\xab\xdcI\x04g\xd8'6\xee:\x00A\x1a\xc1;2\x87\x12\xee\xaeRP\xc3X\x9dg\xa2\xd2\x10v\x82R\xfa\xecͰ\x9a\xa8\x01W\"\xf6\xfe\xc14\xd4\xdd$\xeb\xe5]C:'\f\x844\xbf̫1f!4Rm\x93<\x85u{\x10X\x8b\"\xbd\x1e«a\x8e\x11_$\x9aI\xc1\x01\x1aO\x9f2\xc0\xd2E\x1a@\x1bF\xb8\xd0;x\x9b\x1c\x8e\xf9\xffu\xd9]\xcc@\a\xbd\xe4\xb6B\xad3\x0e\xb3\x92/3A\xb1\xfd\x14\x1c'C\xf4/\xb0\x9e\x17G\x12\xe2\x13ȱ\x05\x98`\x8d\f\x0f\x1ffv\x04L\x06\xee\x1cNa0U\xdcǥ\xbb\x83\u05cb¬9^\x15V\xda\x16#\xab\x18Ibg\x16\xc0(L\xd1M\xd7\xe6t\xea\"T\x90\xac\xb6\xcapА\x19\xdb\a+\x87ߗ~\xe5yT\x01z\xacF\x1e\xdbr\xc6\x028\x16I\x115h6\x0f7\xffL\xe3\xf6,\xbey\xb3\xab&x=\x96!%\x99\x83\t\xee\xef-b\x89b\x94z\x82j\u007f\xed?f\x828\vl\x04\x01\xc6L~\xb8\x16\xde\xcd7٬bJ\xaat\a\\\xe9\xf4>\x82\xba\xb6u\x15\x98\x19\xd4\xc6vA\xde\xde9U\xb1\xfef\xd5\xd4\\\xabkgQd\xf3\aITX[\f\n \"\xb8\x17\xc6k\x9d\\\n\x85]}\xce\xc0Ӆ\xb9\xb8{y\x9f`\xa3\xd4\x0e\xa94C\xccL\x17\x90βOZP\x13\x01\xee\x03\x988ݦs\xcd\xe9\xe2͎\xe1\xb1KL-\x97d\xb5~e\x035\xb1\x8a\x92\x87\x91\xa2\tz\x8d\x18{\x98`\x17\\ʰ/\x90Ζ\xf1\x19\xd7\xeeT\u007f\xeb\xe3\xeePؒKd\xad\x99\xa5GtLy\xe9\x13\x14\x81\xe5\xe2\xca\x14TR\x81\xfeT.\x04\xcb\xc2jH\b\x89n\xeb\x0e\xf9\x8cH\x12E\xfcϰq\x15\xff/J\x05\xf2'\x83Dbނ\xf2UNEK\x8d>E\x99Jh\xd3\xf2\b\x97*\xec\x1b\xca|\xff\xd3\x122,N\xcf\xf2\t\xf39\x19PQ}\x9a\xd1M|Y`\x00E\xb1R\xe1#-\x95\x00 \xbe\xee\xf2#\x83vN\x93\x9fN#P\xb4O\xc2^\x94=i%t\xdd\xefh\x93\xbdn\x81\x80\xb9[\\\x91n/@\xb9\b܋\x1a\x95ي/\x9c\xcaN\x84\x94\xb2B\xad\xc9p\xa5<ʖ9i\xbbw\x11;w\x89\x85\x1c\x9d\x1d\x8d\x15\xf0\x10\x87\xcc\u007f\xb7\x91\x87^\xdb\xfa\xfe%\xf0ą\xb4\xde>\xccT\x97\xf1\x1d*\xb7%\xb4\x8a\x8a\u07b3\x92&\xfd\x03\xfa-b\xe2\xa4\x0e\xb0\x87\xac[\x9e\xb5!\bc\x16\xd8xi\x107K7\b\xd4u!\x84.a4\xd8S\xc9\xc0\x82\x197\x90Cu\xe3\x99\xf2\xdcI\x04\xbb\xf3B\b\xb0D]\x14J\xb0\xee\"\xa7\x1c\xd1-|\x01\v\x1c)\xfa)l/\xe4\xff\xc3=n\x803\xfb\xe8Go\xf5ϹYd~ \xd4\xf1(2IB\xb9\a{\x80~o<,\x14\xe63\xb7\t\xa3g\x0f\xa5\nd)\xf1\xf6\xf5\xff\x11\xe1B\x9f/\b\xab\xe7\xa2\x10\x82K\x17\xc8R\x8f\n\x19\xb6\xe8,\xdb1\x05\xa5\x989\xc7缷\xecB\xd5?\xc2\xd7\xd8y\x8e\xa96n\xf3\xa0\u007f~n\x88\"\xadƅJ0_\x1a\xa2v\xc7 \xe8\xcf\xca*\xbe(\xe54\xe2\xec\xc5Xe|x\u0588\n\tWF\xe0S\bke\xa1c\x06B\x8f\xf7䧠ǥ\x01\x99\x81\xd8\x1bb\x04Y\x8ad\x0e\x02U`o\u058cG\x8b\x12\a\xbe\"\v\x02\x8f\x89)\xc8\xf8\xbe\x89\xb4.]\x01\xaa\x97.v\x8a/\x95\xcf\x11a\x83\x91\xa6NM\x98\xf0 \x97ܲX\nj\x8e\xdb\"_r\xece\n\xef&.\x05\xf4\x12\xf4\xf3\xf4Q\xd4*\x80&\xfaS(\x17\xb4\x9f\xdeu *\xeaG\x12$H\xb9Y/\xda\\\xbd\x12\xe4*yrH\xaf\x01\xd4\rS\xef\xf6'(\x16쪮E\xb5\xe1\x8dI\xee\xcbVN!\xc9[\vf\xe4s;\xcc\x0f)\x06r(.\xb2#\x86\x89D\xff\x99\xd5~\xb7~\xa4\xa2\x01V\x1eh\x10h\xb2E\xc8\x00\xa4\xd5\xdf\f\xb8%\x8c\x066\xe1\x94_\x97\xece\xb7c;\xcfT)t\x9a\xc8\xf0\xb1\x05\x94\xc8A\xa3\xee\xde]S\xcc3\xbe\xbd]\xeb\xd5\x0e}]@\xd1\xff\x83\xff\xd8\xf2\x85 \x9a.Qm\xc0\xf0\xa0\x04W\x8e\xcf\x03U\xb0 \x87\fF\xe4n\x82]R\x84\xc4\xc2\xf87\x89\x00\xfbJ\xf2Pq\x88֩b\xa4\x9eɻAx#\xf6\xd8\xe3::,\xd5\b<\x021\xec\x04\xad\x18\xae\xa2\xa84\xc2~\x94\x9b,2\x92\xaer\xe8\xd6\xc0\x1e\xad\xeb\x19\x0e>\xce3\v\x01\xf3\t\x03S\x89\xc2\x06Z\x11˅v\xb0w\xf4\xcf5u\xec3\xe9g\x13\xb7%\"\xe4\xf3\xdc\xc8\xfd\x0e\x1am\x18\xb8\xe6\xbaT=*\xb9\x12E\xa9r3e\x05l\xcdw+\x1f\"^*\x06e'u\xbd\x8e\x98\xb6\xe5!u\xe5\xc8\bDA2\xa8m\x18|\xd6jO\x8a\vf\xca{\x91\xeb\x9e\x1e6VX*XNXİ\x9b\xaf^\twa\\\xa2\xae\rj\xca\x19\xbb:\x99\n\x14\xfe,\xfc\x80p\xc6\x1a\x89z}\x84\xfa`v\xed*\xb7am<\x9b\xd8\xcb2(\xf7\xdb'\xa1$cv\x8d\x13\xf1<\x9b\x94\x15\x19\xd3\xe6\xc7e\xab\x88r\xb1\x9b\xffXM\xc7G4\x94\xea\x06\xb1\v\x12$\x8d\x8baE\x930\xbbjN\xfb\xb9\x92\xd0\nIL鐲Z\x91!a1ka\x15\xaf\x91n\xb1\x9e\x1d\"\xccfk\x91\xd1\u007f\xf6\xa2\xa0ة\x84\xbd#\xbf\xfe\xc4\xceK]\x14\xd2\xeaX\xc7\xf1E\xc8,\x93\x97\xbc\xefG\x8b\xd4oiN\x02\x06t\f\xe0\x14X&\xa0@\x88\x8b|GXh\xda\xc1P\x83\xe15\x02|1\xcaZ\xf9\\\x8a&\x06~\x0e\x12\b\x9f\x04k\nV\x10\xfb\xff\xccX\x84w\x84*\x84\xe9\x04_\x05~\x03\x97\xbdF\x9e\x84\x02\x01\x81\xc5,\x95\xf4:0\xaau\xd8\xd2@\xa4\x03\xbc\n(x?\xfd\x00/\x80Bi'\xba\x90\x18\x01\xa5\xb8t\xa5=\xe3\x06\x8e\xd3h\x02\xe8\x0e\x84s!0\x02\x90q㬣\x8a\xf9\xfec\xf3\xa1_\x91O\xfcU\x9f\xb1\xd8UF\x01\xfc\x81C\xfa\xd8H\bc3\xfb\u007f\xcb\xfe\x9f\xf1\xbc?\xf2=\xe2<\x1a\x1e\x0e\xbe\x18z\xf5\xeeX8\xf69^V^\xd6\xfa\x15\xf6\x11؇h\xba\xbe\xec+\xa7\xbe\x93\x81\x8b\xa8y\xed\xcc\x14|\x806!\xb2\vP\x82\x94\xa4xȂ\x19\xe0\x11H\b\t\x9d\xe2\x95\x04=\x9d\xc0\xa0hT\x15\xdbDQhA\xf9M\x8e\xa1G\x12\xd5?)Y+;\x88\x81\x0e,?\xb4+\xc7$\x93\xa1\x95\xb1\xa7\x0f4B'F\xb3\xd0\xc1\xd0vt\n\x81\x90\xd7b\xbcC\x04>\xf1\x0fD\xfd\xc9Z\xb3a\t\xbb\x81\xa0\x97ndk&b+\x1b\xfbq\x99\x93\xb8u\x0e쵈\\\xe8\xe9T\x8bH\x9f\xdf\xfe\xed\xfff2\xf19\x8c\xba\x9f\x85Љ30\x88N0r\xd9j\xf0U\xa9\xaf\xf5\x941\x1c\xf0G\x1c\xb0\x1d\xf9%_X\x19\xf5\xb2\xb9\xf3\xe6d\xdex|\x8d=9a\x8b\x83l]x\x9a\x80n\xe4\xed\x8d\xfa\xf2\x82P\xbc6\x92\xc8\x0f^|sE\x91\xb05\x85q◾\xe0\xc0\xcdm}\xb3\x93*\xe2\xf9\x98\xa2\xf9\xed\xcby\xa6\x9f\xd4&\xd7\x15\x0fo\xc5w\x18[H\x95\xa1\x85\xe0\x8fr\x85+Հ\x8b\xb7Q~\xa0q\xce\nL9>\xbbh\n\xf4\x9d\x13;\xc7\aJܐ\xbc\xf5\xb5P]\x19\x13r\xc0\xdeé\xb9\x15h[\xa2\xad\x05\xf8\"#^D(jJ\xe0\x9dX\x9cӬ\xb8*\x1eʍT\r\x8d\x95ڜ\xfe\x962Z\xa9I\xd1i\x9b\xdd\x1d\xbaSs\xa6֣(ɓ\xf1\x9d\xae;q\x14c\xdc@\x94\"Z\x01\x96df\x8c\x1dM\x04\xea\f3\x03n\u008dI\xb7N\x87\xfa\u009a$ա~`\xc8\f\x83~\x9e\x1f\x9a\xbf\x85\x17\xf4\x87*ō\xb6\xe0\xa9j]Z\xd7\xd69\xc1\x1b3\x06\xf1~9I\xcd\xd0PF\x88>\xf8Qx9x\xfc\xf1\x04\xe9\xd9\f,\xd5В\xe0npV\xa2%\x05\xab\xf9\x8b\rA\xcaP#\x96`*\xe3\x8ax\x89gE\xdcnz\xf2aO\x85\x89d\x8a><:۔\x04\xd2 e\xfe\xddN:\x85\x05cL\x862\x00`D\xd3ǐN\xfe\x06\x99u\x8duE\xae(\b\f!>\x82 >\x96A\xeeQ\xa2\x11r4:1\xd0-\xc4X\xedA\xe07\xbe\x9bF&L\x8a\x88a\xa3\xc1\xa4{\x03\xb2ȖP;J\xfe\xea\x84\xd7N\xae\x9e\x82\x8e\x95\xc4\xed\u008e\xd5l=\x0f\x87\x01\x99=\x98\xb7s\x905)\x84\x8d\x9c\xd1ڕ\aeB^\xb1\x0e\xbc\xd6\xef\x01,D\x91\xf98\xa1J#\x9bV\bT]\xc6vB\xe32d\xb6\xce$\x12DH\x84\x18tmj\x8cS5\x01\xc0\x14\xa0\xd2*\x89ѥ\x14\xc6\\\x8f8\x01\x80\x00\x0e6\v\x04\xa6\xb2\xd9}\xcc\xf4ʱ=\xdeX\xb0\xa9\x97\x8ayMI\x18\xe1\xad(t\x80U7s\xc6)\xadGc\x86\x98\xe1;\xecL\vP\x9cM\x9aqT\xe4V\xaaǜ\xa1\xad\x06\u052e*E\x02\xb2\"Ua\xccc\x15S\x80\xb8\xe6df\xb5\xcaAb\u007f#\xe2]\xc6yⲚ\xe8L\x87rQ\xdda\xb3@\xd4J\xd3\x1a\x9e\rH\xa2p\xf2\x83\b\x85\x14\xe0\xde\xe2\x89n:\\\xc1\xc6\x11\xa2\xb0\xc1\xb6\x04s\xb7\xc8p\xd7#\x86\x06\x97\x9e\x13\xa5_\xee0\xed\x82\xc0&4 \xc1\xa0\x1c\xa6\xb6\x89\x1b\xf0\xdaD5\u061dN\xad\fx\xb5\xe1\xf0\xbb\xb0\xc5\x1a\xaa\xee'\xe5\xa9\x1dS\xd10\x98\\%\x19\x90G\xab!\n\xad\x89\xe1\x1by\xe5HJ\x97\x00\x9c\x8c\xba\xd7\xdeې\x93N\xbf3iC\xd3\xd96)q\xc0\x1d\xc2I\xc8\xd2o\xfa\x0e\x80\xee\xd5*\xb1X\x02\x00\x04\xc0\xc1\xb8\xe4\x02CY\x91q\xd9S\x96\x19\xb4b\xf4\xde柃V(d\x04Y\xcd\v\xdb*4\x8a1\xe0\b\xe1\xc1\x03E\x16\xd22-\xc0\x1f<\xa3\f^\x9c\x94\xc7\xedŕQ\xf07@\xa6\xb6\b+\xd2p;\xe6T\x80\x9c\xd0\u1c9cz\xa7\x9e\xaf\x86\x9eYu\x1a\xaa\xa7UŚ\x82+C\u0086\x00\x02T\xa2\x05\r\x99S\x1d\x9f}%\x94\xa1\x12\x19\xf0\xbc\xf3\x18\xe2\x00H\x985\xfe`\xe1H'\xd1\xee\xf6c\x99\r\xd1`\x15a\xa8\x8b\x84Rg\x13\xcb\xde\x1f/p\f˜$\xe2l؟t\xd2!\xc7F\\\x9d\x1c`|\xb9\xb3\x06\xfcD77\x16\xc7i6k\xf3\x84%1猣\x04T\xd0\xfd\xb8\xc8\xe8\x1f\xf1Fې\xb9%~\xbd\x13\xdb{\x98\x91\xac\xa3\x02=\xe0\xa8º&\x91\xa3L\xe4N\xa7;\x05V(\x9c\x9c\xbei~\x87\f\xac\xaa]\r\x90C\xd5\x1c*U\x8f\x9ad\x99\xd7ÂU\xa3[\xc0\xe2\xa8Q\x89攔U/\x05\xdbd\x0e\x11ئ\f?ߡ\x05$\xa3\f\x14\x0f\x97\xe0p\xb2\t\xb7\x1a\xa0\x9e\xfaq\xa0ucFw\x04\xb8\xa1gf\xb2VO\x13\xf6ofc\x01\xdeA\x90ː\xcbc\xf259%\xcdTڮ\x1a\xaa\xe6\x81v\xcf\xf5\x05F\x003\x92\xc6\x13\x1dn\f\xd9.\xe2\x83w\xfc\xda2;\xe0>\x93\b\xf3y\x94+\xaf\xc4\xd86X\xa8S3\xbd\x0e\xc4|\xef\xdd\xfa\x1d\xa9\xed\x90d\xa3\xba\x83\x80\xa3_ȑN>\xc40\x89/\xaap\xd28\xc8\x1f\xee\f?\xee$\x05\x02\r\xaa\xc8\x02W7\xbee\xa8\ue942=xX3\xddǸ4\xc5\x0f\xbdn\x87\xe2\xe2n\f\x04\xe8\\\xad\xa2U\x86è\x94\xcb[\xe1:+\xc7\"R\x03\x13\xbfD\x9d)\xec\"M\xb1U[y\xa9\xa7\xce\x12e\x9e\x83\xdbu<\x8f\x1b\\\xc2p\x80\x9b\x90e\x97CV=\xa5\u007fN\xf2\xfc\xad\x1a\x0e\x9b\x90\xfeB\x1d\xd8!\x1dY\x11~\xc0\xfd;[68\x97\xda%c\x80a\t+\x1cT\xcaQ\xf3hԒ\xeeE\x9a?M\xe35\x1c\xf1\x97\xeb\x16D\x91\xdc\x1b\x81\x92\xf1\u007f\x11\x1e2\xaa\x9b\x1f\x8d\x14\x1bP(\xecdp\xf81\x94{YR\xd1\x02\x83>\f\x12g\xfd\xb2\xd40Z\xe8\xff\xa7\x1e\x8f\x037B\x13\x06\tB\xbcƨ\x00죠\n\xb58\x00i\xa9\xbe\x1a\xfc\x8a\xd7ұm\xe5x\xad\x1b$~\x81L \x10=pY\xcf\xd3^lp\xcct\xf0\x1e\v\xa8\x8d}^\xec\\X;\xa0\xa3zD\xde\xe5\xe7\xeb\xac\xcd\xf1c\x86'>$V\x91Y\xe3f\xbb\x9e\x81\xe0\xb4\xfaM\x17\xa9\x9c\xff\xd9S\x1fH#\xbf\xd6#7\x96Q\x85\xe5\x82\x0f\xff\x82D\xc3\xe0\xc8D\x0fQ\x06\xbe)\x06\xc3ø\x1f'^\xa2S\x84\xbblS>X\xf7\xee\x83\x18\xb2\x81\xe71HG\uf260\xd5\xd0opm\xe9O\x05G\xf8R\xe4\x116\x97sf\x92?\xf9v\xe1\xcf\ft\x114:\xf2\aP\x120!Re\x88\xc6M\xe4\xd7\xd8\"\b\xb5I\x86\x90\x88\xf0\xa3\x17\xcf#;@0\xe9\x946\xd86ԡ\x02\x861#O\x103*\"k\x19\xa4{\xb0\x91\x1a\x92`\xa0O^\xfcg\xe7\xe3ȉ\xed,\x04$\x87\x1c\x8dE\xa2A\a\xd7u\xeec\xa4\xe7\xe3\x9eA\xf2[t&v\x94F\xc1\xaaA1]\a\xe7\xd0A\x1cx \x1b\xadz\xc0^Օ\x82\x0623ym\x16f\xac=\x1ce(5\x91A\x92\x87fBFv^^\x8a\r\xd5\xe8#\x95(+&\xae?\xb4\x1b\r*\xe1\xa8I\xc2Bx*\x16\x84\x11\xe0l\x80Ld\n\xbc\x19\xea\x1ebj\xa2\xddP\x0e\xa7|\xbb\x95![\x96\x85\xf4\xaeͱ\xaa\xd2E\xb2\xc2\xda)xeAF+\xbc\x06\x84\x16\xb9\x1aP\xc3TQ\x13lk1\x86\xe3\x8a\xcc\xd7\xd3\bb:\v<\xda\xd0O\xf0:\xfc\x87\xb4'\xf0\xf4\x04\xdc\x18T\xe4\xf9\x00\xb8\xb9\xe1\xf3|\x18#\xf7\x9d\x00n\v3\x95\xb7\xa5\x9a\xfbP\xc0Jw\x9b\xd5˽\x85\xaa\xcb4/\xfc\x85;JI\xa3\xa2\xe6\xf2f\x8e\x87\a|6`\xab\b\xb3u\xb2\xc41\xdda\xc6\x130\x1e\r\xb9\xf3.y\xa7=\xe9\xaa[6\xc3\x1d\x15!\u05fcS,\x02\xf8\xe29\xea\xe0/\xb7\xe8\xec~\x907\x13<\x86\x80\xb7D\xc8ˇ\xfe\xaa\xbf*\xabٱU\xeb\xb0\xeaԢN\x03\x85`\xbc\a\xbd\xacg-5o\x14\xa1\xdc/\xce\xd2A\xbeRn\x0eE\xff\xad,\xc9\x00%\xfcb\xe7=t\xe7\x9ak\t\x16\x1a\xf0\x0f\xceG卂:\xe2\x1a|7\x04\"0\x84\x0e\xb6@\xaa\x83\u007f\x19\xc2+> ۘ\x92B\xd8\\T\xfeoB\x85\xc8g\xdcqK,\xef\xb4H\f\xeaK\x06\x01\xb5\x03\x1d&<̅\"\x9ah7\x85u\x8c\xa6\x02Q\xe9'\xe1\xe8/\x16\x13\xae\x1b\xd64\xd7\xd2=Bg\xcf5\x89g7Ջ\xb0Am\x1d*o\xcfٷ\xd6W\xaf\xec\x96C\x8e\x91\xa8\x18\xc8)\xb4\xf1\x11\x92l\x1e\x84\xfe\x03\x8a4\x1c\xa2\x12~\x95\x88\x8b:\xd8$\x80\xb5z\xe6\xa9J\xdcq\x13\x82\n\xbd7\x85\x110\n\xad\x0f\xb8\xba\xa6\x9e\x06\xa1\xc1\x81\xb1l\x1b\xb6V\xa4\xa2-\x03\xb80v\xdc9\x00\v\xa4\x19\x8c\x97\xaa\xd4\x15*\xc0\xac\a\x1d\x8f\x1a\x9a\x1e:\x86\x9a;A\n\xce?\xb2\xa1\x80\xcf\xd1C\xa8\xb4Nj\xd0 4\xa5\xf3\xf2\xca\x1ak-r\xd6\xfc\\\xbe\xf2C\xe4,Et}\x17\xb8\xd5\x12\xd5e\a9\xb2\x90\xd6l\":6\x86e\x16\x01bb\xca\xff:'Ee\v\x9ch\x95\xa6\x14\x13\xe6\xb0\xc2\x14\xd4Z\f\xac\x92\bc\x9f\xa0\x89П\xefiR\xd3Q\xc98[\x0e\xc7E\xf7\x12\xc1\x83؎\xf0\x95\x89\x14\xc1\xd0:\x01\xdcz\x83C\xb4c\\ݖ\xeeI5\xd0\xe8\x88Y\xcaZi1\xb2@z\xf8mS\xfb\xf4\xc7R\x12ϝ\xaa\xff\xa6\xaeD\x8c\xe2\xfbJe/p\x11D\xf3l\xa7\xa8\xca\xfa)<\x03GA)Q:ڲ\xb2\xc6\x1e\xd2]t\r\xe0osC!\x9c\x92\xf2\x87\xf4\xc5\xea!\x05Pζ\xd8\x10E\x88\xf2\xa5a\xa4\x00\xd2>7\x12\x80\x15\xf9\x8c\xa3W \xe2N\x04\xe2\xb4;aZ\xb0qK,\x80\\\xa2\x88\x88Ii\xa5\xc2\x1f\x97(\xd18\xaaӑ\xdf\xc0\xf2\x18\xb8\x1exd \x8c\x10\x0f\xb8p\xf0\x99\xfaծ\x94\x8c\xe4\xb5+\x19\xfdt\xb9\xe5\x01\xd1cX\x1eO\x99\x16V\x8aM{\x182\x8ab\b\x82\xd72\xaa\x86\xd4\xeb\xf0\x81r\x8b4\xb4\x87\x95P\x0fh\x03 l\xb4o`\"\xa1q.{f\xacm.\xd7RP\f\xa5\xccZ\x1d\x87m<\x00\xaf\xd8Z\x974'\x19\xe7\x9d\xf2z0$\x06A.\x82\xef\xeb\x9dr\xa2\xcbÂTp\x16۵\x9f\xef\x99H\xdc'\xe8Z\x19\xab\x1eٸ[xd\x84\xc4\xfd\xaa)zY̤C\u007f.\xd9\xf4\xc4\xe0!_\xf2\x05\x06\x88詗qe-\u007fŃ\xe5L\x0f\xe9\xf9\xe2y\x82\xa6\x80\xa7\x99\xfe\x01?Y\x18\x166\x800XW\xaf7̐v\x8b#2\x90j\x87ZK\xb6&\xb0\xbe\xffl\x9b\x87\xd0EO\x85k\x11\xc4\xfc\x06\x96\\\x01;{l\a\x1b\xf2\x91\u2b91\xfb\xa4\xcc\x18\xd6&:\x91\xb9?\f\xa2\xad\f|\xd5\x13\xed|\xd0I׃\xd3\xf2 {mJ\xe0\x1eS\x8d\xe1\xc2\a\xc0֨Vv#zo}\xecNO\xbb\x19\x98\xe3\xdb9\x9c\xd1<\r2V,y\xe8\x18\xfe\x98\xfd\xb3ڵ\nO\xa4\xbbBV\xf57\xec\xae\x19;\x82''\x89.\xaf\xa1j\xadU\xef\x8bX\r\x8c\xdbo:TބS\x8bhg\x06\xc8\x03\x92\xec\xd5\x04\xe3\xb2ܞN\f\xab,\x94g\xfcyzl\xda\"V\xe8=˻\b\xb6+0\x05\x85\x9bB\x04\n\x9b\xebF\xf1\x92\xf69pI\\r\xa0A\a\xf8\x9d\xe85\x98n\xc0\xe1Z\b^n\x97\xe4\x11zGzd\x1bj\x9d\xdbU!;i4 \xd6\xf1\xd5\xc3\x03\x9a\xe6S\x03\xac1\x80\xd8\xf8\u007f\xa8\t\"P)+\xa9\xb2Zb\xb0\xe2\xc5~%t\x99\xd0\t\xd9\x017\x00\x88\x12\xad'\x85r\xaco<Ň\x00\xffL\xbf\xc9y\xe7Ķ\xc1\xb8\x04\x97 \th\xaaJЗ\v\x16H&\x83\xb6\x12'\xb0\xe0L\xaa\v\xc0\xe7O\xc3/\x81\x06\xbbSҬH@\x82\a\x1f\xc3U\xb9^\xf0#\xa1ʐ\xc7T\xf3g\x84UM\xe5\xe1/\u007f>\x12\x1a\x84x\xcbs\xd3\xf0\x8f\xf04\x9c}\x88\x06\u007fI\xb1\x1b\xfeB8p\xaf\xa9x3b\xee8\xb0;/\x10\xb4A\xfex\x18\x10\x90O>i\xf0\xb7\xa2\xc1X\xceT\x9a\x01-\x16k\xbd\xef\xb16D\xef\xc2i~M\x88\x9a8HV\x1a+\fYn\x88\x01\x98x\x9e\xf7ؽ#\fI\x17V,\xb1\b\x06\x03\x1f\xcd\f\x01\xe0!\nϾ\x1bD{\x14\xfa\x10\xc8\xc0\x81.\x13G\xdc\xd9%k\xcbY#\xaet,1xdnA\xe2.f$\xa1\x06֑8\x1e*Jv\xffT\x80Fgd\x1d\xa9M\xd6?\x19\b\x9d\xee\x19\x03\x9c\x8d>\xf1\xb4v8\xf1+Z3\tB.\xe4\xb2sz\xda\xfc\x01\xde!\xc4$Du\x00\x02\xf8\vo\xe1cQ\xbc\xef'(-+\xe0\x94-a0\x92V\xff\x1d\x95ظ;\xfd\x90\x9agW\xf8B\xe3=\xd4c)\n\xa89\xad\xda\x1d%O\tx\xa2\x97\xd1\x1f\x05\x19Em9<\xeeK\xa4'\xde?Tk%\x11u\x85\\\x18\x82C\x9c\u05cbO\x13\n\xec1\xee\xccL\x13\x04ᆭF\xca\xf4\x05\b\x16\xa2\xc9\xe2N\x90L\x90-\xd6\xd1䢏\u038d\xb6h\x15%.4ⶍ\x18\xa7\xd2[\xe3+M\x13\x9f\xfb|I\xb1\x98w\x1bٟU\x06\x81\xd1Ċ;Ɲ\x04$*\x96k\xc0\x1a\a\xb1r\xa6Ml\xa9J\xeb\xd2;;,P\x17\x00F\xa4ҦT\xc1s\x83\xbf\xc2\xfa\x81\xff\xe8\x95\xed!\r\"\x84Ӝ$1\xe1s(.ɗ\xf6\xd4E\\5\vX5}H_\x12\xa0\xaf(\x1eC\xf7d\xd8\b&\x93&\x12\x92%\xd0.\xcdؙB\x99{\x05\\\x06\xe7Ёn\x01\x18(\x11\x9f\xfb\xaas#\x83d\xe2x\xf6U\xb8JG\x8c\x1d\xc9:\xc9z'3\xbc\x1e\xa1@\xf0\x17\xe5\xeb\x91\x06\xfc\x00\xf9\xa7\x9c1\x80\xdf\n\xda\x12Br5N\t\xb8\x8d\x01\xa0\u007fO\n\xa1fN\xc6\xdb9\x1eݩ\x80v\x8b\xa0\xa60}\xa8\xb8ė9\x18\x06\x94\u007f\xe7\"\x1bU2;\xb3)\xda\x05\xd4\x00\xf6\xd9\xf4W\x89~\xca4\x0e\xe1\xe4\x1a\x1c\xe9\xf3\xd0i\x14\xb9A\x99@\xeeP3\b\xdc7a\n\x01\x8f=\x88DW\x04\vx\xda\xcd\xc4\xce\xd5|\fK\x00p\x151\x93\x00ĵ\xe7ְ\xc2N\xc3g\xf8\x99@\xb3\x0f]E\x81}G \xa7֓\xb95\t\xc3\xec\x14tMм&;\x067q\x11-jv\x84\x8czT3\xbaT\a\xb0\x8e\xd5\xd2k\xaf?\x9e\xe3\xb9n+\x88\xe1\xd1bY%\x93\aH\"&\x80\x9d\x90)\x10\xf3\x1c\xf9JՋq\xa4\xaa`\x0e\v\x9a`#/\x90\x00\xe5\x9eb\xc6\xd8\xd3\xf9\b\x95\x019\xe1\xefSA\xb4\xd7JwE6O(\x1d\x118NOL\xd3d\xb6#\x1f\x10a\xb8M\x93\xed\xa9\xfd\xefs\xd5@\xb5|:\xd4D\x83\xa36Zt\xdbYf\nzip\xddZ\xc7\xd0ؠ\x02\x01X\xbc\x022\x0fꢂ7\xda\xc4\x05!\xac\xc7\xe45\x00@\xf6D\r\xc6\xea\xde0\xdeD|\xa1\xb6b\xc0W\xf1\x1d\x02\\\v\xed\x19C\xdc8 vPUP\x18d\xbb\xaf\x9eL\xdf%O<\xd0\xf9\f\xeb|\x94\xd1_ɀ)\x83\xfe\x81\xf9\xb9\xe4,$,\rB\x82_OE \xfa\x814\x9d\xbe\xa4oa\n\x10y\xe1\xe0Ў\x89\x80j\x8a\xf2\x16\xcceX\x06SN\x9d\x85C\xfb\xeb\x06\xcdr\x83\x06O+\xa6\x8f\xbb\x16\xeb`H\x9e\x1a\x84`܋\xbc\x06bIg\x96\xc9P\xf1:n\xec\x83)\x8b\xd8$\xaf\f\xd2\xed\xecfYr\xee\x17\r൨`\xf8Mׂ\x15\x0fɢ\xc1R\r&\x14\xe2_I\x0eZ\xd0\xdb-\xd2@$\xe6-\xf3>\x18\x83W.\xde\x1c\x14\xb9@\xbc\"m\xa0{\x987r\xe5\xf8&\xfc\xa0\xc4\xe2^\x10\xad\xac\x8a.\a\x1a\xf3h\b|\x8a\xb5,s\x1b\xf7\xb6\x89<\xa3\xa96\x14Q\xa8\xa1\"\x8b\x99M\t\xc8\"LX\x0e\xf0\x15\rc\xf7\x90bD\xf4n\xa7\xad& 4\x91ec\xc4 \x92qA\xebJ\x98SE\x87\x10\x9a\"\x80\x10\xec\x02\x14\x05\x15n\"@S\x85\xdf\x05\xa8\xa8\r\xe6i\xeem\xf9\xae\x142\xfdy\xbd6\x15\xe7\xe8\xcde4\x91d\x1d\xc28J\x1a\x93\xbc\xe8\x16I\xb1\xb8\xe5֭Ƀ\x8f\xc3\xf1\x1a\v\xf6\r\x83\xe7\xcaZ߃\xbe\xf5\b3\x15A\xf8ܹ\xa6\xd0\x10,\x15g\xc0\xf8,x\xfb\xc9B܅\xfe\x9bC\xbcX\x82ť\x80u\x0f\xd3d\xaa\xef\x04\xc1<\x83-\xb4\x05\x15\x1a\xf2\x925\xb5\x93\xd7m\xd7\x03\x85g\xad\x06s\xc6D\xf7u\xb1\xec\xc0\xa2\x10ll\xe8],eZ\xd1e\x84O\xe4\x85N2\n{\xe4\xc2#b\x8aL\x0ezH/\xa0B\xf0\x12,s\xc3\xef\x15\a\x9b\xfe\x932cf\x9ec\x84\x94\x17\xbdLL\xb3H\xf4\xea^h\xb1v\xd70ě\x98\x00\x9bK>\xac\x03f\x17;6U\x82PpM\x81\xa3u\x1d\xbe\x10\x10\xd1\x1b1\xc2!\x8e?\xc1\xaaQ\xadF\xd2r\x11\xe5Ҍ\x87z\xe0^\xd0>$\xe1\xa0b\xf8\xbb\xf4IFݚ\xe0c~\xd4}pT\xa8\xa3\x01\xba\xf4\x1c\x06EQ\x8f\x97\f\xd1\xfe4D\xacY\xf9[f^\xac\xb6\x03\tZ\xd0\x0e6&\xfe\x8ap\x06DD\x8e!\x041p}gŠ\x1a\x91\xb7c\xb8ʷ\xa2\v\x02\xc5R'\f\xa9h~\v\x88\xa5(B1\x18\xd4\xdaѴ\x89\u007f\f\x88B\xc7a\xc2\xf9\x96n؇\xb1\xd1\x1aV\x04F\xbd\xe9\x1cF.N\xfb\xa1\x91N\x8a4\xf5ۅnZ\xc97\x14\x02O͔\x1d\xdf\xdf\xd8w\x18/bèm\xdf\xf8\x00\x91@l\x81˔\xc6\xdbB/\xea\xcc)\xed\x80\xfd\x90\xc9!s\x1a\xa8`0\t\xbb]\xc2\xff\xbd\xa5Rr[e\xf8\n\bs$\x88\xf0^\x00q\xb4\u007f\x83]\xa1 }H\xc1\t\xeb\x1a\x99/\x8e1l\x97\x01)\xf8\xaf$\x1e\xac@\xae\xe9\x80*R\xf4\xf0ه\xf0\x93u\xb9\xfc9,\x1d\x03!\xb8\xa0G\x94\xbe<7O\xa4\xf9\x86\xb7\xc9\xe2V\xea0\xe0\xc0TZ|\xb1\x0e\xe5\x01?9\xe5]\x04z\xb3\x02wK\x8d\x84\xf6\x11g\xf2\xcb\x89\xc6;\x88pD5\x83/.L\x18\x8a-gX\x03\x0e\xe0\xa0Y\x98D^\x0e\x94\xe8\x85\f\x87_\"\x1c$>\x01\xd0\xc15\xe6\xc2\xcc\xe3l\x1fz!\x81\x041>\x1a\x03/\x15\x90\x89!%\x99\xcd\bj\xe3\xb2=\x1a \xe1k\x17\xca\xd3\x05qq\x1fD%\x8bif\x96\x00\x94\x8f\xd4$\f\xc4\xf5`\x1d\x04 A\xb2P\n\xf3\x12\xe6\xa2)*Oj\n3\xc0NL\x8aR\x8e*\xcab`\x8c\x94@C\x05\x12]\xc1o\x8d!\xc9h\xa2Õa\x00\x844vx۠\xb6#\xc0s$\xe2\x10\xdb\x02<~\xe8\x02s^6U^Ld\x91\x8a® \xe1\xfe%\x14\xbf\x0409.\x98w\b\xea\xb9\x1d\xb6m\x14\xe1\x16f1\x84\xda~\xa4s\r\xa4\t\xe8\n*\x86\u007fI\x13\xb6\x1aVʊZJ_\xc6Zª\"tf\x90~\x15m\xd8`G\xca?\xa4M\xc1\xa8\x8b\xb2v\x05\x92 \xa3I\xa4\x98\x93\x8d\xb7\xb4\x989t\x8dV@\x15\x98\x1c\a\xcb<=\x87\x19\xa4\xf4\xda=\x9cj\xeeHy\x8cNau\x89vI\x8d\x90\x1c\xe3;\x96\x17\x87G[$\x98\x97k*\xcd\xdd\x1d\x97D\xbeIH\xdd3\x9bbb\x14\x19\x199!\xa7l\x8a\xd1\xf1\x0f\xabo\xa7w\x9b\x9d\xab\b\r\xd3\xfd\xa9\f@נ(\xa9J\x97OS\x8f9\x04\xb5\xa4\xd8\xff(\xf3C\xdc\xc4̅\xb4\xc4|\aP\nC\x91\x9e\u007fwUk\x1e*\xd4\xc6\x14`\xf4\xf0\x16\xfbo\x11\xfa-`n\xaeASZ\xeab\xa4\xe1b\x9dmX\xa3\x14ז\xd5\xcew\xae\n_\xbe\xc9\xe9~\x15\xf9e\xa6\xf4\xc1}\x80\x9d\xf0\xe0\x1e\x99{\xdf\x15Q\xc7h\x80\x1f\u0090\x00\x15\xbf\xa2\xaa\xb6\xeeĦ\x04̶a*\x19\b2\x00\x93\xe3\xc6\t\x98\x01_\fu\xc2x\xaaFw\x12\x89ԣ@n \xe9\xf3\xaa\n\xb3\x0f`\xb1\xa01\xb4ǃ4\x8d݊\"\x85q\xa8\x1b\xee\x1f\x85\xd88\xff\u0091\xdd\x1c-{ȅ\xb1\xd6_\x02\x91\x9b'\x1fX\xe3d\xb9\u007f1\x9fA\xfes\x8b!;%0\x89q\x80\x1f\xc8\x12\r\x8a^&\x91t4w\xacX$\x02\x96\xbf \xba\n\x06\x83\xb9z\"\xc0\x85-\x8c\x885\x10\xadu\xd3\xd0\xcf&\xda\xef^\xf4\xf9s0\x10j7o\xc2;;^\xb7\xd2\x13r\xb3\x91g\v\x81\x90\xc0Bɛ\xe7\xdd\"\xb0\xf7f\xf3\x0f\x10\xa5B.QH\x8e\xe7=\xe0HO\xb1\x96\x06\x8c\xfc\"5`\x81\r\xf7`\x16\x88J\x8d\xa7\x0e\x814D\xf6\x1c\xe10\xda\x1a9K\xdcJ\f)\x00\xaaѠmd\xe3I\xa8պ\a6Z\rJ;\xa7\x013QF\x023\xcfk\x1f\x88\x16r\x8a 1ʠ\xd4֬\xa6\xddZ8\x80r\xc45C\n\xfb\xc9\xf6\xef\xcc\xd6\n\tQq\x93\x99ZNQ\x83\x80\xa6@z\x81\xa7\xe2\x85\xc0\xaa[\xf3Ѧ0u\xe0\xe6\xccp\xac\x9c\xd8j+^])\x86\xa8\xa5\x86\xaea\xe8F\x8a\xb57\xc8aW\xac\xe8\x12Ʋ\x19AM]s0\"\xc0\x15)\x16\xa4\xbc\xc7`[\xf2\x8c3\xf0,\xaf\x17\x8d\x95\x98\x0e\x1a\x82\u007f)l\\A'@.\xf4\xd1,f\x01\xc5,I\x8f\xa9l\xe3\xe1/5\xeen\nIJʐ\x97\xc7r\xb4f6|\x873\xb9\xb7܃k\x17\xec\xd6\x05]\x0f\xd2H\xbd]\xa3\xf5!1|\"J\x94\x0e\xb4E\xfd\x8b\x8a^W\x00\x1b4&\xb1\x1e\x8fN\x12\x0ej\xf2ztɡ}\u05f6\x00\xdepp\xe1\xa5w\xb0Z\xc3HAI\\\x06\xea\x1e\x12,f8\xc6K\x9f\xb6\x11\x81lγ\xf0\xc9eM>F/\xee\xec`\xa2\xe0\x03L\xeb\xdb(\xa4;\b\xc0\xa5\x1c\xe0\x15\xcd\x0f;\x139\x06\xd0@U=\x85o\xfe\xc7\x01]\x98\vX\xd7s=X\x82\xeea\x8d\x03`F8\x10\xab8\xb0\x1f8\x89\xe2\xc9\xc5rh}u\x12\xb2G6\x87\x1f:\x8e\x16\x99\xb1\x14X\xaa\x80\xf1Q\v\x9c\x1aV`\x86\f\x94%\x10\xef\xf00\xfcAFUL\xc0\x99\xda,\xae\x04\xb8\xfep<[\xb6 ;\xbe\xa7\xcfa\x84:\x037P\x92\n\x12u\x80\x85)3\x8f\x84\xc1\x83\x18\x84~\x84\x04\x96\x9f\xfd:(\x927͒\xd9K\b\x00\xd5N:\x18=(\xa7ۦ\x14\xd0\b\x1a\x9dЫ\xa1\xd3Ѩ\xc8T\xa9\bP\xc0*\x01{/\f\xb3\x9eԿ\"\xcbK\x15\xb6\xdb\x0e\x96\x8e[l\x93\xb0\x1a\x11\xee0ʧU\x01y\x92K\xf1\x1a\xf2Z2\xbcC\xf8&V0\xb6\xd8\x15\x96\xbe\"1B\xe6.!\x96lWsa%ra\x10\xea\x8e\x0eȆ\xd0e\xb8\x86(T\xb1FV\xda\xd7Mzfr\x14\x85\x9d\xc1X\xbe\f\xb08\xf4\xa8\xe1\x01\x90\fɖ\b\xf24R9V\xcc396P(\xbd\xd4\x1f\x00\a\xf2\x9a\xaeY\f\x91\x87a\xec\x17\xc1\xd5Ѥth0˷\xb6jUk\xf2\x88s\xdc?D\xfcR\xa8H\xddd\v\r\xb8\xb9s\xc0\xb0\x89\xc6\xea\xb5Ld\xeb\x1f\xce]\x8c\x19B\xe0Up8U\x1e\x1c\xf1\xc9\xc09\x17T\xcd\x0eY\xf4\x10\x8eD\xb2'&\xa0\x87\x14ļ\x9f;\xe8\xabZ\x94\x8f\rv\xee\xc1\x83[ĉRiWH\xf4@\x97\xe5\xca:\xc3*\x1e]\xb2ֱ\a\x05\xba\x951\x11\xac\xc2h\xceOn\x8d\xe1\xa2\x1a\x8a\xfbڂ\\\xf2}`d\xd7\x16.)\x81E\xb5ΛKVMÔl7P\xa7\x1a\xef\xdaW\x04\x9d\x80\x82\xc4\xe6ݵa\x1e\U000107c7\xb4$\xa4\xc1$\xdb\x1d\xees\xb3K\x9f\x1a0L\xd7\x17\x91\n\xad|<\\\xa2\xe0$/7\x1e\x0e\xe6h\x91n\x0fL\x8av[\xa6\x1ab\x96\xabL'\v\xae\\1aER->ð\x8dc\x9c\xc7h\xf5\xb1\nP\x00\xd7Y(\xffe\x80Y\xb7գ\x84inǞ\x89\x0e\x99O\xe3e\x8d4I\xd0") + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_eot_bytes() ([]byte, error) { + return _third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_eot, nil +} + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_eot() (*asset, error) { + bytes, err := third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_eot_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/fonts/droid-sans-v6-latin-regular.eot", size: 22008, mode: os.FileMode(416), modTime: time.Unix(1436317036, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_svg = []byte(` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`) + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_svg_bytes() ([]byte, error) { + return _third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_svg, nil +} + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_svg() (*asset, error) { + bytes, err := third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_svg_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/fonts/droid-sans-v6-latin-regular.svg", size: 72148, mode: os.FileMode(416), modTime: time.Unix(1436317036, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_ttf = []byte("\x00\x01\x00\x00\x00\x11\x01\x00\x00\x04\x00\x10GDEF\x00\x10\x00\xd2\x00\x00\x85\xbc\x00\x00\x00\x16GPOS\xf2ZM^\x00\x00\x85\xd4\x00\x00\x12\xc0GSUB\x00\x15\x00\n\x00\x00\x98\x94\x00\x00\x00\fOS/2\xa0ӵe\x00\x00u\xb4\x00\x00\x00`cmapmag\xda\x00\x00v\x14\x00\x00\x00\x8ccvt 9~>L\x00\x00\x80\x94\x00\x00\x01\xfcfpgms\xd3#\xb0\x00\x00v\xa0\x00\x00\a\x05gasp\x00\x04\x00\a\x00\x00\x85\xb0\x00\x00\x00\fglyfI;\f|\x00\x00\x01\x1c\x00\x00o(head\xf5\xf5 \xd3\x00\x00r\f\x00\x00\x006hhea\r\xc4\x05\x8a\x00\x00u\x90\x00\x00\x00$hmtxmsT\xd3\x00\x00rD\x00\x00\x03Lloca\xbe\x8aۈ\x00\x00pd\x00\x00\x01\xa8maxp\x03i\x01\xd3\x00\x00pD\x00\x00\x00 name\x14\x910\xde\x00\x00\x82\x90\x00\x00\x018post\xa2\xc2\x0f;\x00\x00\x83\xc8\x00\x00\x01\xe7prep\x82\xdc!\x13\x00\x00}\xa8\x00\x00\x02\xec\x00\x02\x00\x93\xff\xe3\x01\x91\x05\xb6\x00\x03\x00\x17\x00:\xb9\x00\x01\xff\xf0@\x13\n\x14H\x10\x19\x80\x19\x90\x19\xa0\x19\x04\x03\x0e\x9a\x04\x02\x02\x04\xb8\xff\xc0@\n\a\nH\x04\x01\t\x9b\x13\x02\x03\x00?/\xf5\xce\x01/+3/\x10\xe12]10+\x01#\x033\x034>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x01Py3\xdf\xf0\x14\".\x1b\x1a/\"\x14\x14\"/\x1a\x1b.\"\x14\x01\x9e\x04\x18\xfa\xb9&5!\x0f\x0f!5&%5\"\x10\x10\"5\x00\x00\x00\x00\x02\x00\x85\x03\xa6\x02\xb2\x05\xb6\x00\x03\x00\a\x007@#\x04\x98\a\a\t\xd0\t\xe0\t\x02/\to\t\u007f\t\x03\x00\x98\x00\x03\x10\x03\xe0\x03\xf0\x03\x04\x03\x06\x02\x02\a\x03\x03\x00?33/3\x01/]\xe1]]\x129/\xe110\x01\x03#\x03!\x03#\x03\x01J)s)\x02-)r)\x05\xb6\xfd\xf0\x02\x10\xfd\xf0\x02\x10\x00\x00\x02\x003\x00\x00\x04\xf8\x05\xb6\x00\x1b\x00\x1f\x00\x99@X\x03\x03\x1a\x1a\x18\x16\x1e\x1d\a\x04\x06\x17\x17\x06\x19\x00\x01\x04\x04\x05\xb1\x18\x18!\x15\x1f\x1c\b\x04\t\x14\x14\x12\x0f\x0e\v\x04\x13\xb1\nP\x10\x01\x10\x10\f\f\tP\n\x01\n\x1c\x01H\r\x01\r\xae\f\b\x04\f\x1f\x00\x10\xae\x11\x19\x15\x11?\x11O\x11\xdf\x11\x03\f\x11\f\x11\x05\x17\x13\x06\n\x05\x00/3?3\x1299//]\x1133\x10\xe122\x1133\x10\xe1]22\x01/]33/3/]\x10\xe4\x1792\x11\x12\x179\x113/\xe4\x17923\x11\x12\x179\x113/3/10\x01\x03!\x15!\x03#\x13!\x03#\x13!5!\x13!5!\x133\x03!\x133\x03!\x15\x01!\x13!\x03\xd7?\x01\x18\xfe\xcdR\x93T\xfe\xddR\x90N\xfe\xfe\x01\x1dA\xfe\xee\x01+R\x93R\x01%T\x90T\x01\x06\xfc\xeb\x01#@\xfe\xdd\x03}\xfe\xb8\x89\xfeT\x01\xac\xfeT\x01\xac\x89\x01H\x89\x01\xb0\xfeP\x01\xb0\xfeP\x89\xfe\xb8\x01H\x00\x03\x00{\xff\x89\x03\xd9\x06\x12\x00-\x006\x00?\x00\xb4@34/)\x01)/!\x01!\x06p/<\x01\x02753\x15\x1e\x01\x17\a.\x01'\x11\x1e\x03\a4.\x02'\x11>\x01\x01\x14\x1e\x02\x17\x11\x0e\x01\x03\xd92]\x85T\x8a2f`T !W`e/Y\x83V*1[\x81O\x8ad\xa9CB8\x8cJX\x87[.\xb0\x14+F3][\xfe\x12\x11(B1YS\x01\xbeFrT7\f\xe6\xdd\t\x12\x1a\x11\xac\x10!\x1a\x11\x01\xb2\x1eBUnJCoS5\t\xb4\xb0\x05*\x1f\x91\x19)\x06\xfeZ\x1fBSkH!7-&\x12\xfe\x8b\x0eb\x02\xa3$9/&\x11\x01q\x10Y\x00\x00\x00\x00\x05\x00f\xff\xec\x063\x05\xcb\x00\t\x00\x1d\x00'\x00;\x00?\x00]\xb2<\x10>\xb8\xff\xf0@3<><>(\x14\x1e\xb42\xb5#\xb4(A\x0fA\x01\x05\xb4\n\xb5\x00\xb4\x10\x14 \x140\x14\x03\x14?\x06>\x18%\xb67\xb7!\xb6-\x19\x03\xb6\x0f\xb7\a\xb6\x19\a\x00?\xe1\xf4\xe1?\xe1\xf4\xe1??\x01/]\xe1\xf4\xe1]\x10\xde\xe1\xf4\xe1\x11\x1299//8810\x13\x14\x1632\x11\x10#\"\x06\x05\x14\x0e\x02#\".\x0254>\x0232\x1e\x02\x01\x14\x1632\x11\x10#\"\x06\x05\x14\x0e\x02#\".\x0254>\x0232\x1e\x02\t\x01#\x01\xfaGP\x9c\x9cPG\x01\xc7$JsOIpL&#IqNKqM'\x01\xacGP\x9c\x9cPG\x01\xc6#JsOJpK&#IqNKqL'\xff\x00\xfc՞\x03,\x04\x02\xa5\xa5\x01J\x01H\xa3\xa5l\xacv??v\xacll\xaau>>u\xaa\xfdJ\xa5\xa4\x01I\x01H\xa3\xa5l\xabv??v\xabll\xaau>>u\xaa\x03\x92\xfaJ\x05\xb6\x00\x00\x00\x00\x03\x00m\xff\xec\x05}\x05\xcd\x00\x11\x00!\x00S\x00\x80@M'\x18\x17J\x04I,IH\nG6AGB B\x016B6B\x1d\x05;\x0354.\x02#\"\x06\x132>\x027\x01\x0e\x03\x15\x14\x1e\x02%4>\x027.\x0354>\x0232\x1e\x02\x15\x14\x0e\x02\a\x01>\x0373\x0e\x03\a\x01#'\x0e\x03#\".\x02\x01\xa6\x10!4$;V8\x1c\x19/B*Vd\x87:bTH \xfe}4P7\x1c#B`\xfe}(MoG\x1f<-\x1c2^\x8aXS\x83[02Tm<\x01`\x1b+\"\x1b\n\xb8\x0f)5A'\x01\x15\xe1\xa81`l|Ni\xa7s=\x04\x8d\"AAC%#>@F)$=,\x19Y\xfb\xaf\x17(6\x1f\x01\x97!?HU86[A$\xf0NzdV*$MWc9KwS++SwK@m]O$\xfe\x8c\x1d\x0273\x06\x02\x15\x14\x1e\x02\x17#.\x03R$JqN\xac\x8c\x91%GjE\xaaNqJ$\x021}\xf3\xe5\xd3]\xc1\xfe2\xf4w\xec\xe2\xd4^Z\xce\xe1\xf0\x00\x00\x00\x00\x01\x00=\xfe\xbc\x02\x17\x05\xb6\x00\x13\x00\x1c@\x0e\x06\x0e\xf2\v\xf0\xb0\x00\x01\x00\x15\x0e\xf8\x05\xf9\x00??\x01\x10\xde]\xe1\xe4210\x01\x14\x0e\x02\a#>\x0354\x02'3\x1e\x03\x02\x17$KqN\xaaEjH$\x90\x8d\xacNqK$\x021|\xf0\xe1\xceZ^\xd4\xe2\xecw\xf4\x01\xce\xc1]\xd3\xe5\xf3\x00\x00\x01\x00R\x02w\x04\x14\x06\x14\x00\x0e\x00$@\x15\x1f\x10\x01\x00\x98\x00\x0e\x80\x0e\x90\x0e\x03\b\x0e\x1f\x06\x01\x06\x06\x00\x00\x00?2/]\x01/^]\xe5]10\x01\x03%\x17\x05\x13\a\v\x01'\x13%7\x05\x03\x02\x98+\x01\x8d\x1a\xfe\x86\xf5\xb2\xb0\x9e\xb8\xf2\xfe\x89\x1d\x01\x87+\x06\x14\xfewo\xc1\x1c\xfe\xba`\x01f\xfe\x9a`\x01F\x1c\xc1o\x01\x89\x00\x00\x01\x00f\x01\x06\x04\x02\x04\xa2\x00\v\x00)@\x18\x10\r\x01\x06\t\xaa\x03\xef\x00\x01 \x00`\x00\xa0\x00\x03\x00\t\x00\xad\x06\x03\xb3\x00?3\xe12\x01/]]2\xe12]10\x01!5!\x113\x11!\x15!\x11#\x01\xe9\xfe}\x01\x83\x96\x01\x83\xfe}\x96\x02\x87\x96\x01\x85\xfe{\x96\xfe\u007f\x00\x00\x00\x00\x01\x00?\xfe\xf8\x01y\x00\xee\x00\f\x008@\x14\xcf\x0e\x01\x10\x0e\x90\x0e\xa0\x0e\x03\x1b\f+\f\x02\f\x01\x97\x06\a\xb8\xff\xc0@\r\x10\x14H_\a\x01\x10\a\x01\a\x06\x9c\f\x00/\xed\x01/]]+3\xed2]]]10%\x17\x0e\x03\a#>\x037\x01j\x0f\x0e'/3\x19\x8a\x0f\x1d\x1b\x16\b\xee\x176z|{8=\x84\x83}5\x00\x00\x00\x00\x01\x00R\x01\xd1\x02B\x02y\x00\x03\x00\x15@\t\x02\x05@\x00\x01\x00\x00\xb9\x01\x00/\xe1\x01/]\x10\xce10\x135!\x15R\x01\xf0\x01Ѩ\xa8\x00\x00\x00\x01\x00\x93\xff\xe3\x01\x91\x00\xfa\x00\x13\x005@\x1b\x80\x15\x90\x15\xa0\x15\x03\x11\x15\x01\n\x96\xc0\x00\xd0\x00\x024\x00D\x00d\x00t\x00\x04\x00\xb8\xff\xc0\xb6\a\nH\x00\x05\x9b\x0f\x00/\xed\x01/+]]\xed]]1074>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x93\x14\".\x1b\x1a/\"\x14\x14\"/\x1a\x1b.\"\x14o&5!\x0f\x0f!5&%5\"\x10\x10\"5\x00\x01\x00\x14\x00\x00\x02\xe7\x05\xb6\x00\x03\x00\x1e\xb1\x01\x02\xb8\xff\xf0@\t\x02\x03\x00\x10\x00\x05\x01\x00\x03\x00?/\x11\x01382/8310\t\x01#\x01\x02\xe7\xfd\xe0\xb3\x02!\x05\xb6\xfaJ\x05\xb6\x00\x00\x00\x00\x02\x00b\xff\xec\x04\b\x05\xcd\x00\x13\x00'\x00&@\x15\x1eo\x00)\x10)\x01\x14o \n\x01\n#s\x0f\a\x19s\x05\x19\x00?\xe1?\xe1\x01/]\xe1]\x10\xde\xe110\x01\x14\x02\x0e\x01#\".\x01\x0254\x12>\x0132\x1e\x01\x12\x05\x14\x1e\x0232>\x0254.\x02#\"\x0e\x02\x04\b3q\xb2\u007fv\xafs93o\xb1~w\xb0t:\xfd\x13\x1eBkMMlE\x1f\x1fElMMkB\x1e\x02ݱ\xfe\xe8\xc2ff\xc2\x01\x18\xb1\xb1\x01\x18\xc1fe\xc1\xfe貖\xe0\x95KJ\x94ᗖ\xe0\x94JJ\x94\xe0\x00\x00\x01\x00\xb2\x00\x00\x02\xc7\x05\xb6\x00\x10\x005@!@\x12\x01\x0f\x01\x0e\x0e\x00n\xbf\x01\xff\x01\x02~\x01\x01\x00\x01\x10\x01 \x01@\x01\x04\x06\x01\r\x0f\x06\x00\x18\x00??\xcd\x01/^]]]\xe13/\x113]10!#\x114>\x027\x0e\x03\x0f\x01'\x013\x02ǰ\x01\x03\x03\x01\x11\x1a\x1b\x1e\x15\x94`\x01\u007f\x96\x03\x91+baY\"\x12\x1a\x18\x1b\x12y{\x01+\x00\x00\x00\x01\x00`\x00\x00\x03\xf0\x05\xcb\x00#\x00<@ #\bo\x1b\x1b%\x10%\x01\"o\x01!\x01\x11\x11 \x01\x01\x01\b\"\x10\rs\x16\a\x02\"t\x01\x18\x00?\xe12?\xe13\x129\x01/]3/\x113\x10\xed]\x113/\xe1310)\x015\x01>\x0354.\x02#\"\x06\a'>\x0332\x1e\x02\x15\x14\x0e\x02\a\x01\x15!\x03\xf0\xfcp\x01^KvS,\"?V5_\x99Ef(\\jvA`\x9bl;5]\x81K\xfe\xe7\x02\xb1\x9c\x01}Q\x86\x80\x81L;Z? M\x0254.\x02+\x01532>\x0254.\x02#\"\x06\a'>\x0332\x1e\x02\x03\xc1.StG\xb1\xb8A\x84ʊm\xc1UW\xcb]\\\x86W)5b\x8dY\x85\x85Q~U,$B\\8k\xa3J\\&]n}Fl\xa3n8\x04`IxX9\f\x06\x16\xb5\x91`\xa0t@\"-\xaa.2(JlCDa?\x1e\x97(Jf=4R9\x1eC6}\x1f6)\x186a\x85\x00\x02\x00\x17\x00\x00\x04?\x05\xbe\x00\n\x00\x18\x00N@,\tV\x00\x01\x00\x00\x02n\x11\f\v\a \x03\x01\x03\x03\x1a\x10\x1a\x01w\x18\x87\x18\x02\x18_\x05\x01\x05\t\x06\x18t\x01\x05\x05\x02\x11\a\x06\x02\x18\x00??3\x129/3\xe122\x01/]3]]\x129/]3333\xe12/]210\x01#\x11#\x11!5\x013\x113!\x114>\x027#\x0e\x03\a\x01\x04?հ\xfd]\x02\x97\xbc\xd5\xfe{\x03\x04\x05\x01\t\a\x15\x19\x1a\v\xfee\x01H\xfe\xb8\x01H\x9f\x03\xd7\xfc0\x01d8{uf\"\x1411.\x10\xfd\xa0\x00\x00\x00\x00\x01\x00\x83\xff\xec\x03\xf6\x05\xb6\x00*\x00N@\x18&\x1ao\x05,\x10,\x01'$$(h#\x01Y#\x01##\xf0\x0f\x01\x0f\xb8\xff\xc0@\x12\b\vH\x0f\x1ds\x00\x00\x15't$\x06\x15s\x10\n\x19\x00?3\xe1?\xe1\x129/\xe1\x01/+]3/]]33\x113]\x10\xde\xe1310\x012\x1e\x02\x15\x14\x0e\x02#\".\x02'5\x1e\x0332>\x0254&#\"\x0e\x02\a'\x13!\x15!\x03>\x01\x02!c\xab\u007fHD\x86ŀ3c[R!!Ybc*O|V.\xb0\xa8\x1b??9\x15Z7\x02\xb2\xfd\xec' i\x03\x817l\xa0ir\xb6~C\n\x13\x1e\x14\xac\x17$\x18\r%NvQ\x8f\x97\x05\b\t\x049\x02\xb0\xa6\xfe]\x06\x0e\x00\x00\x00\x00\x02\x00q\xff\xec\x04\n\x05\xcb\x00+\x00?\x007@ 1n\f\"A\x10A\x01\x17;o\x00\x00\x10\x00 \x00\x03\x006u\x1d\x1d\a,s'\x19\x10s\a\a\x00?\xe1?\xe1\x119/\xe1\x01/]\xe12]\x10\xde2\xe110\x134>\x0432\x1e\x02\x17\x15.\x01#\"\x0e\x04\a3>\x0332\x1e\x02\x15\x14\x0e\x02#\".\x02\x012>\x0254.\x02#\"\x0e\x02\x15\x14\x1e\x02q\x155\\\x8eƅ\x13./+\x11#X+Z\x89dC*\x14\x03\f\x149L_;_\x9al;>t\xa4fd\xaf\x80J\x01\xdbn\xd5#\x01\xcc#\x01\xba#\x01##\x10\x19 \x19\x02\x19\n\x1eh8\x988\x02Y8\x01(888H8\x038\x93C\x01&CVC\x02CC\x00-s\x14\x19;s\x00\a\x00?\xe1?\xe1\x119/]]\xc1]]]99\x01/]3/]]]\xe1\x10\xe1]\x10\xce2/]]\xe1\x129\x10\xe1\x11910\x012\x1e\x02\x15\x14\x0e\x02\a\x1e\x03\x15\x14\x0e\x02#\".\x0254>\x027.\x0354>\x02\x03\x14\x1e\x0232>\x0254.\x02/\x01\x0e\x01\x01\"\x06\x15\x14\x1e\x02\x17>\x0354&\x025T\x95qB(F`8:oW5Cy\xa9fn\xabu=-Lh:1V?%Cr\x95\xc7 DhHFkH$'If?\x1e~\x80\x01\x16j}#>W30U?$~\x05\xcd,X\x84XClWE\x1c\x1fL_vI\\\x95h86e\x92\\Kx`J\x1c\x1fIZmBW\x83X,\xfb\xa65Y?##A\\84TH@\x1f\x0e<\x9b\x03Tje9R@3\x18\x164BT6ej\x00\x00\x02\x00j\xff\xec\x04\x04\x05\xcb\x00)\x00=\x005@\x1e9\x15o\x00?\x10?\x01/n\f\x10 \x02 4u\x1b\x1b\a*s%\a\x10u\a\x1a\x00?\xe1?\xe1\x119/\xe1\x01/]3\xe1]\x10\xde\xe1210\x01\x14\x0e\x04#\".\x02'5\x1e\x0132>\x027#\x0e\x03#\".\x0254>\x0232\x1e\x02\x01\"\x0e\x02\x15\x14\x1e\x0232>\x0254.\x02\x04\x04\x155\\\x8eƅ\x13..,\x11#X+\x87\xaef+\x05\r\x148L`;_\x9al;?s\xa5fe\xae\x80J\xfe%.\x1a;r\xa5jr\xb7\u007fDN\xa0\xf3\x01G(T\u007fWFoN*/K`0C\x85kB\x00\x00\x00\x00\x02\x00\x93\xff\xe3\x01\x91\x04f\x00\x13\x00'\x00>@\x1c\x10)\x80)\x90)\xa0)\x04\x1e\n\x96\x14\xc0\x00\xd0\x00\x024\x00D\x00d\x00t\x00\x04\x00\xb8\xff\xc0@\v\a\nH\x00#\x9b\x19\x10\x05\x9b\x0f\x00/\xed?\xed\x01/+]]3\xe52]1074>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x114>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x93\x14\".\x1b\x1a/\"\x14\x14\"/\x1a\x1b.\"\x14\x14\".\x1b\x1a/\"\x14\x14\"/\x1a\x1b.\"\x14o&5!\x0f\x0f!5&%5\"\x10\x10\"5\x03\x91'5!\x0e\x0e!5'%4\"\x10\x10\"4\x00\x02\x00?\xfe\xf8\x01\x91\x04f\x00\f\x00 \x00a@/\x10\"\x80\"\x90\"\xa0\"\x04\x17\x96\xc0\r\xd0\r\x02d\rt\r\x02P\r\x01D\r\x01;\r\x01\x1f\r/\r\x02\r\r\x1b\f+\f\x02\f\x01\x97\x06\a\xb8\xff\xc0@\x11\x10\x14H_\a\x01\x10\a\x01\a\x1c\x9b\x12\x10\x06\x9c\f\x00/\xed?\xed\x01/]]+3\xed2]3/]]]]]]\xe5]10%\x17\x0e\x03\a#>\x037\x034>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x01j\x0f\x0e'/3\x19\x8a\x0f\x1d\x1b\x16\b\x11\x14\".\x1b\x1a/\"\x14\x14\"/\x1a\x1b.\"\x14\xee\x176z|{8=\x84\x83}5\x02\xed'5!\x0e\x0e!5'%4\"\x10\x10\"4\x00\x00\x00\x01\x00f\x00\xee\x04\x02\x04\xdd\x00\x06\x00N@0\x00\b@\b\x01@\x01\x01\x01\x02\x01\x05\x05\x03\x06o\x00\u007f\x00\x020\x00\x01\x00\x00\x04 \x03\x01P\x03p\x03\x80\x03\xd0\x03\xf0\x03\x05?\x03\x01\x00\x03\x01\x06\x03\x00/^]]]q33/]]2\x129=/33\x01\x18/]]\x10\xce10%\x015\x01\x15\t\x01\x04\x02\xfcd\x03\x9c\xfd!\x02\xdf\xee\x01\xa8f\x01\xe1\xa0\xfe\x94\xfe\xbe\x00\x02\x00f\x01\xba\x04\x02\x03\xe9\x00\x03\x00\a\x00\\@=\a\x02\t@\t\x01\x04\xc6\x00\x01\xbb\x00\x01\xa9\x00\x01\x86\x00\x01{\x00\x01h\x00\x01B\x00\x019\x00\x01\x00\x04\xad\x1f\x05/\x05\x02\u007f\x05\x01\x00\x05\x10\x05\x02\x06\x05\x05\x00\xad\xf0\x01\x01\x0f\x01o\x01\x02\x01\x00/]]\xe13/^]]q\xe1\x01/]]]]]]]]3]\x10\xce210\x135!\x15\x015!\x15f\x03\x9c\xfcd\x03\x9c\x03T\x95\x95\xfef\x96\x96\x00\x00\x01\x00f\x00\xee\x04\x02\x04\xdd\x00\x06\x00N@0\x05\b@\b\x01@\x06\x01\x06\x05\x04\x01\x01\x03\x00o\x06\u007f\x06\x020\x06\x01\x06\x06\x02 \x03\x01P\x03p\x03\x80\x03\xd0\x03\xf0\x03\x05?\x03\x01\x00\x03\x01\x06\x03\x00/^]]]q33/]]3\x129=/33\x01\x18/]]\x10\xce10\x13\t\x015\x01\x15\x01f\x02\xe0\xfd \x03\x9c\xfcd\x01\x8f\x01B\x01l\xa0\xfe\x1ff\xfeX\x00\x02\x00%\xff\xe3\x03%\x05\xcb\x00'\x00;\x00>@!2\x9a(('F\x00\x00\x14\vF\x1c=/=\x01\x14\v\x17\x0f\x00\x01\x06\x00\x00-\x9b7\x13\x10Q\x17\x04\x00?\xe13/\xe52/^]\x129\x01/]\x10\xde\xe1\x119/\xe13/\xe110\x0154>\x027>\x0354.\x02#\"\x06\a'>\x0132\x1e\x02\x15\x14\x0e\x02\a\x0e\x03\x1d\x01\x034>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x01\x19\x0f'B20D+\x15\x1e9U8S\x96F?Q\xbca]\x95h8\x1b6P64B&\x0e\xbb\x14\".\x1b\x1a/\"\x14\x14\"/\x1a\x1b.\"\x14\x01\x9e%9\\PM*)CEO50O9\x1f4\"\x91*;3`\x8bWCiZT/-C?B,\x12\xfe\xd1&5!\x0f\x0f!5&%5\"\x10\x10\"5\x00\x00\x00\x02\x00m\xffJ\x06\x81\x05\xb6\x00W\x00h\x00o@?X\x17`'\x1f\x17\x01\u007f'\x01\x17'FF'\x17\x03N1 \x00\x01\x00j@j\x01;@N\x01N,\f[\x12\a\x12d\x1c\x0f\x12\x1f\x12\xbf\x12\x03\x06\x00\x1c\x01\a\x12\x1c\x12\x1c@6S\x03@EI\x00/3\xc1?\xc1\x1299//^]^]\x10\xc1\x113\x10\xc122\x01/]\xc1]\x10\xdeq\xc1\x11\x179///]]\x10\xc1\x10\xc110\x01\x14\x0e\x04#\".\x02'#\x0e\x03#\".\x0254>\x0232\x1e\x02\x17\x03\x0e\x01\x1c\x01\x15\x14\x1e\x0232>\x0254.\x02#\"\x04\x06\x02\x15\x14\x1e\x0232>\x027\x15\x0e\x01#\"$&\x0254\x126$32\x04\x16\x12\x01\x14\x1632>\x02?\x01.\x01#\"\x0e\x02\x06\x81\x13%9La:-I4!\x06\x04\x126GY5MwR+;o\x9eb-ZRE\x17\x17\x01\x01\x15\"+\x17.F/\x18V\x98\xd1{\xa9\xfe\xfe\xafZO\x99\xe3\x93=wod+V\u0602\xb3\xfe\xe7\xc3fv\xdb\x017\xc1\x9c\x01\x06\xbfj\xfc\x15eU7N2\x1a\x04\x0e\x1cM*Je?\x1c\x02\xdb>}qaH)\x1e2A#%B1\x1c8e\x8eVe\xa8zD\b\x0e\x11\b\xfe`\x16\x1b\x10\b\x035D(\x0f=h\x8cN\x8eݘOo\xc7\xfe\uf897\xea\xa0R\x0e\x18\x1f\x11\x8d&,f\xc3\x01\x19\xb3\xbc\x01E\xee\x88e\xbd\xfe\xf1\xfeՅw-SsE\xfd\b\r:^x\x00\x00\x02\x00\x00\x00\x00\x04\xdd\x05\xbc\x00\a\x00\x14\x00\x84@$\x06\x05F\x02\x01F\x14\x01\x02\x14\x03I\b\x01I\x01\x01\b\x01\x00\x0e\x0e\x03\x00\x00\x10\a\x01\x80\a\x90\a\xd0\a\x03\a\xb8\xff\xc0@\x18\x06\nH\a\x10\a\a\x16\x0f\x16\x1f\x16/\x16\x8f\x16\x9f\x16\xdf\x16\x06\a\x03\x04\xb8\xff\xf0@\x11\x04\x02_\x0e \n\x0eH\x0e\x05\x14\x14\x05\x03\x04\x00\x12\x00?2?9/\x129+\xe1\x01/83^]\x113/8+]q3\x11\x129=/\x1299]]\x1299]]3310!\x03!\x03#\x013\t\x01\x03.\x03'\x0e\x03\a\x03\x04\x1f\xa0\xfdߢ\xbc\x02\x19\xaa\x02\x1a\xfeg\x94\x06\x11\x12\x12\b\a\x12\x12\x11\x06\x91\x01\xc5\xfe;\x05\xbc\xfaD\x02j\x01\xa8\x124\v\x1eZ\x05\x06\x01\xe5\x06\xf5\x06\x02\xd6\x06\x01\x06\x06$*[p\x11\x80\x11\x02\x11g1\u007f1\x8f1\x02\x101\x01\x18$Z\x17d0\v#`y\x18\x01\v\x18\x01\b\x18\x18\x00$`\x17\x12\"`\x00\x03\x00?\xe1?\xe1\x119/^]]\xe19\x01\x10\xf6\xe12]]\x10\xf6]\xe1\x129/]]q\xe1210\x13!2\x1e\x02\x15\x14\x0e\x02\a\x15\x1e\x03\x15\x14\x0e\x02#!\x1332>\x0254&+\x01\x19\x01!2>\x0254.\x02#\xc7\x01\x8f\x80ÃB'JmEEyZ4A{\xb0o\xfe\x1b\xba\xf4TrF\x1f\x9a\xa6\xdf\x01\nXwI !K|\\\x05\xb6'W\x8dg>lR7\t\n\f-OxVd\x9dm:\x03J\x1e;Y;xh\xfd\x97\xfd\xf0(He=8^C%\x00\x00\x00\x00\x01\x00}\xff\xec\x04\x98\x05\xcb\x00#\x00L@\x14\xaf\x0e\x01\x0e@\x15\x18H\x0e\x0e\x18\xba \x01` p \x02 \xb8\xff\xc0@\x18\x06\nH %\xaf%\x01\x05[\x18f$!\x00_\x1d\x04\r\n_\x13\x13\x00?\xe13?\xe13\x01\x10\xf6\xe1]\x113/+]]\x129/+]10\x01\"\x0e\x02\x15\x14\x1e\x023267\x15\x0e\x03#\".\x01\x0254\x12>\x0132\x16\x17\a.\x01\x03\x19k\xae{C;v\xb0vY\xa0N'NUa;\xa4\xf0\x9dLW\xa9\xfa\xa2l\xc4ON?\x94\x05'Q\x98ډ\x8dۖN#\x17\xa2\x0f\x17\x0e\al\xc6\x01\x16\xa9\xa6\x01\x14\xc6n,*\x9c .\x00\x00\x00\x02\x00\xc7\x00\x00\x04\xfc\x05\xb6\x00\f\x00\x17\x00&@\x15\r[\x00g\x19\x10\x19\x01\x14Z\x06d\x18\x13`\a\x03\x14`\x06\x12\x00?\xe1?\xe1\x01\x10\xf6\xe1]\x10\xf6\xe110\x01\x14\x02\x06\x04#!\x11!2\x1e\x01\x12\a4.\x02+\x01\x113 \x00\x04\xfc`\xb6\xfe\xf7\xa8\xfe\x92\x01\x97\x99\xf8\xae_\xc5B~\xb8uɢ\x01\b\x01\f\x02\xe9\xb9\xfe\xe9\xbb^\x05\xb6\\\xb5\xfe\xf4\xb6\x92ՊC\xfb\x89\x01$\x00\x00\x00\x00\x01\x00\xc7\x00\x00\x03\xbe\x05\xb6\x00\v\x00B@&\x14\b\x01\b\b\x01\x04\x00g\r\x06\nZ\x01d\f\t_O\x06\x01\x0f\x06\xaf\x06\x02\b\x06\x06\n\x05_\x02\x03\n_\x01\x12\x00?\xe1?\xe1\x129/^]q\xe1\x01\x10\xf6\xe12\x10\xe62\x119/]10)\x01\x11!\x15!\x11!\x15!\x11!\x03\xbe\xfd\t\x02\xf7\xfd\xc3\x02\x17\xfd\xe9\x02=\x05\xb6\xa4\xfe<\xa2\xfd\xf8\x00\x00\x00\x01\x00\xc7\x00\x00\x03\xbe\x05\xb6\x00\t\x00p@\x11\b\b\x01\x0f\x03\x01\xff\x03\x01\x80\x03\x90\x03\xd0\x03\x03\x03\xb8\xff\xc0@8\a\nH\x03\x03\v\x0f\v/\v\x8f\v\xaf\v\x04\a\x06\x00Z\x01d\n\t_\x0f\x06\x01\x0f\x06?\x06o\x06\xff\x06\x04\b\x06@\x1a\x1dH\x06@\x10\x15H\x06\x06\x00\x05_\x02\x03\x00\x12\x00??\xe1\x129/++^]q\xe1\x01\x10\xf6\xe12^]\x113/+]]q\x129/10!#\x11!\x15!\x11!\x15!\x01\x81\xba\x02\xf7\xfd\xc3\x02\x17\xfd\xe9\x05\xb6\xa4\xfd\xfc\xa4\x00\x00\x00\x00\x01\x00}\xff\xec\x04\xf2\x05\xcb\x00+\x007@\x1e++\f)Z\x14\x02g-\x10-\x01\x1f[\ff,+_\x00\x00$\x1a_\x11\x04$_\a\x13\x00?\xe1?\xe1\x129/\xe1\x01\x10\xf6\xe1]\x10\xf62\xe1\x119/10\x01!\x11\x0e\x03#\".\x01\x0254\x126$32\x16\x17\a.\x03#\"\x0e\x02\x15\x14\x1e\x0232>\x027\x11!\x03\x0e\x01\xe47pv\x82K\x9d\xf2\xa6V_\xb6\x01\v\xabo\xccXH$SX].z\xbc\u007fB7x\xbe\x86,I>7\x1a\xfe\xd5\x03\x04\xfd3\x12\x1c\x13\ni\xc3\x01\x17\xae\xac\x01\x16\xc3i,*\xa2\x11\x1e\x17\x0eQ\x98ډ\x82\u061cV\x05\b\v\x05\x01\xb4\x00\x00\x01\x00\xc7\x00\x00\x04\xd5\x05\xb6\x00\v\x00=@#\t\x01Z\x00e\r\xc0\r\x01\xbf\r\x01 \r\x01\b\x04Z\x05d\f\x03_\x0f\b\x01\b\b\b\n\x06\x03\x05\x00\x12\x00?2?39/^]\xe1\x01\x10\xf6\xe12]]]\x10\xf6\xe1210!#\x11!\x11#\x113\x11!\x113\x04պ\xfdf\xba\xba\x02\x9a\xba\x02\xaa\xfdV\x05\xb6\xfd\x98\x02h\x00\x00\x01\x00R\x00\x00\x02d\x05\xb6\x00\v\x00W@&\v\r+\r\x02{\r\x9b\r\xab\r\xfb\r\x04T\r\x01+\r;\rK\r\x03\x1f\r\x01\x02\b\v\nZ\x05\x02\xc9\x03\x01\x03\xb8\xff\xf8@\x10\r\x10H\x00\x03\x01\x06\x03\t\x04\x06\x03\x03\n\x00\x12\x00?\xc12?\xc12\x01/^]+]\xc12\xf1\xc12_]]]]q10)\x0157\x11'5!\x15\a\x11\x17\x02d\xfd\ueb2c\x02\x12\xac\xacf)\x04\x98)ff)\xfbh)\x00\x00\x00\x01\xffH\xfe{\x01s\x05\xb6\x00\x13\x00/@\x1c\xdf\x15\x01`\x15p\x15\x02/\x15\x01\x0fZ\f\x03\x03\x00\f\x10\f\x02\a\f\r\x03\a_\x00\x00/\xe1?\x01/^]3/\x10\xe1]]]10\x03\"&'5\x1e\x0132>\x025\x113\x11\x14\x0e\x02\x1d3L\x1c\"N-%K=&\xbb;i\x93\xfe{\r\v\xa0\t\v\x132XD\x05\xb6\xfa^i\x9ae1\x00\x00\x00\x00\x01\x00\xc7\x00\x00\x04\xa2\x05\xb6\x00\f\x00d@-\x02\ff\f\x01\f\x00\n\v\x10\v\v\x01\x00\x00\x10\x00\x02\a\x00\x10\x00\x00\x0e\xb0\x0e\x01/\x0e\x01\x10\x0e\x01\b\x04Z\x05d\r\x02\x10\v\x10H\b\xb8\xff\xf0@\f\v\x10H\x02\b\x05\n\x06\x03\x00\x05\x12\x00?3?3\x1299++\x01\x10\xf6\xe12]]]\x113/8^]33/83\x119]\x11310!#\x01\a\x11#\x113\x117\x013\x01\x04\xa2\xd3\xfe=\x8b\xba\xbay\x01\xc4\xd1\xfd\xf8\x02\xbar\xfd\xb8\x05\xb6\xfd%\xa8\x023\xfd\x83\x00\x00\x01\x00\xc7\x00\x00\x03\xbe\x05\xb6\x00\x05\x00#@\x13\x04\a\xaf\a\x01\x10\a\x01\x03Z\x00d\x06\x01\x03\x03_\x00\x12\x00?\xe1?\x01\x10\xf6\xe1]]\x113103\x113\x11!\x15Ǻ\x02=\x05\xb6\xfa\xf0\xa6\x00\x01\x00\xc7\x00\x00\x06/\x05\xb6\x00\x19\x00\x8b@\x136\x19\x019\x00\x01\x17\x0e\b\f\x0fH9\x0e\x01\x0e\x11Z\x19\xb8\xff\xf8@\x1c\f\x0fH\x19\x00\b\f\x0fH\x00\r\r\f\t\x10e\x1bO\x1b\x01 \x1b\x01\x0f\x1b\x01\b\v\xb8\xff\xf8@\x1a\f\x0fH&\v\x01\v\x02\bZ\td\x1a\x18\x01\x01\x10\t\x12H\x01\x0e\v\x03\x11\f\xb8\xff\xf0\xb6\t\x12H\f\b\x00\x12\x00?22+2?33+\x113\x01\x10\xf6\xe122]+^]]]\x10\xf6\x1199\x113+3+\xe12]+210]]!\x01#\x16\x17\x1e\x01\x15\x11#\x11!\x013\x01!\x11#\x1146767#\x01\x03#\xfeE\b\x06\x04\x04\x05\xac\x01\x14\x01\x9c\x06\x01\x9e\x01\x14\xba\x04\x03\x04\x03\b\xfeA\x05\x00JI?\x8b9\xfc\x96\x05\xb6\xfbX\x04\xa8\xfaJ\x03w4\x86=GI\xfb\x02\x00\x01\x00\xc7\x00\x00\x05\x0e\x05\xb6\x00\x17\x00Q@)\x0e(\x01\x01\x01\x15Z\x00e\x19\xb0\x19\x01\x8f\x19\x01\x00\x19\x10\x19\x02'\f\x01\f\x03\tZ\nd\x18\x16\x02\x10\x06\x18H\x02\v\x03\r\xb8\xff\xf0\xb6\x06\x18H\r\n\x00\x12\x00?22+?3+3\x01\x10\xf6\xe122]]]]\x10\xf6\xe12]210!#\x01#\x16\x17\x1e\x01\x15\x11#\x113\x013&'.\x035\x113\x05\x0e\xd7\xfd1\b\x06\x04\x04\x05\xac\xd5\x02\xcc\a\x03\x04\x01\x03\x03\x01\xae\x04\xbaMLA\x8e9\xfc\xe7\x05\xb6\xfbLLJ CC>\x1a\x03 \x00\x00\x00\x00\x02\x00}\xff\xec\x05q\x05\xcd\x00\x13\x00'\x004@ \x1e[\x00g)\xc0)\x01\xbf)\x01p)\x01/)_)\x02\x14[\nf(#_\x0f\x04\x19_\x05\x13\x00?\xe1?\xe1\x01\x10\xf6\xe1]]]]\x10\xf6\xe110\x01\x14\x02\x0e\x01#\".\x01\x0254\x12>\x0132\x1e\x01\x12\x05\x14\x1e\x0232>\x0254.\x02#\"\x0e\x02\x05qQ\xa0훣\xef\x9dLL\x9e\xf0\xa3\x9b\xeb\xa0Q\xfb\xd14k\xa5rr\xa5k22j\xa4rr\xa6l4\x02ݩ\xfe\xea\xc6ll\xc6\x01\x17\xaa\xaa\x01\x15\xc4kk\xc5\xfe뫉ۙQQ\x99ۉ\x8aڗQQ\x97\xda\x00\x00\x00\x00\x02\x00\xc7\x00\x00\x043\x05\xb6\x00\x0e\x00\x19\x00F@,\x15[(\x008\x00H\x00\x03\x00g\x1b\xcf\x1b\x01@\x1b\x01\x0f\x1b\x01\x06\x0f\aZ\bd\x1a\x0f`0\x06@\x06\x02\x06\x06\a\x19`\t\x03\a\x12\x00??\xe1\x119/]\xe1\x01\x10\xf6\xe12^]]]\x10\xf6]\xe110\x01\x14\x0e\x02+\x01\x11#\x11!2\x1e\x02\x0132>\x0254&+\x01\x0437~Ϙ\x96\xba\x01j\x86\xc2~<\xfdN\x81]\x8b[.\xa4\xae\xa0\x04\n[\xa8\x81M\xfd\xc7\x05\xb69m\xa0\xfeg GqQ\x8e\x89\x00\x00\x00\x02\x00}\xfeb\x05q\x05\xcd\x00\x1d\x001\x008@\"([\x00g3\xc03\x01\xbf3\x01p3\x01/3_3\x02\x1e[\x14f2-_\x19\x04#_\x05\x0f\x13\t\x00/?3\xe1?\xe1\x01\x10\xf6\xe1]]]]\x10\xf6\xe110\x01\x14\x0e\x02\a\x1e\x01\x17\a.\x01'\x0e\x01#\".\x01\x0254\x12>\x0132\x1e\x01\x12\x05\x14\x1e\x0232>\x0254.\x02#\"\x0e\x02\x05q1_\x8e]+\x89Zyg\xad3\x11)\x12\xa3\xef\x9dLL\x9e\xf0\xa3\x9b\xeb\xa0Q\xfb\xd14k\xa5rr\xa5k22j\xa4rr\xa6l4\x02݃ⵄ&^\x8f<\x8eI\xc6\u007f\x02\x02l\xc6\x01\x17\xaa\xaa\x01\x15\xc4kk\xc5\xfe뫉ۙQQ\x99ۉ\x8aڗQQ\x97\xda\x00\x00\x00\x00\x02\x00\xc7\x00\x00\x04\xa0\x05\xb6\x00\x0f\x00\x1c\x00\x82@V\t\x0f\x19\x0f\x02\xf9\x0f\x01\x0f\b\v\x0fH\x0f\f\t\f\x01\a\f\x01\x16[\b\a\x18\a\x02\a\a\t\x0e\x01\xe9\x0e\xf9\x0e\x02\x0e\b\v\x0fH\x0e\r\x10\r\x1e?\x1e\x8f\x1e\x9f\x1e\xbf\x1e\xdf\x1e\x05 \x1e\x01\x10\x01Z\x02d\x1d\f\x03\x10`\x00\x00\x01\b\x00\x00\x01\x1c`\x03\x03\x0e\x01\x12\x00?3?\xe1\x119/^]\xe1\x129\x01\x10\xf6\xe12]]\x10\xce82+]q2/]\xe1\x129^]\x113+]q10\x01\x11#\x11! \x16\x15\x14\x0e\x02\a\x01#\x01'32>\x0254.\x02+\x01\x01\x81\xba\x01d\x01\n\xfe1Qh7\x01\x8e\xdb\xfe\xa1\xe5\xa4Z~Q%)S\u007fW\xa0\x02\\\xfd\xa4\x05\xb6\xce\xd1W\x82]>\x14\xfdq\x02\\\x9e#EgEHd@\x1d\x00\x00\x00\x01\x00h\xff\xec\x03\xc9\x05\xcb\x003\x00B@'Y#\x01#\x11Z\x00g5\xbf5\xff5\x02`5\x01?5\x01*Z\t\x1bf4\x11*\x05'_$ \x04\x0e`\t\x05\x13\x00?3\xe1?3\xe1\x1299\x01\x10\xf62\xe1]]]\x10\xf6\xe13]10\x01\x14\x0e\x02#\"&'5\x1e\x0332654.\x02'.\x0354>\x0232\x16\x17\a.\x01#\"\x06\x15\x14\x1e\x02\x17\x1e\x03\x03\xc9E\x80\xb8so\xc1A\"W`f2\xa0\x99\x1dIz]Y\x83U)@t\xa1aw\xbeJCA\xa5Xz\x86\x1eFsT[\x89\\/\x01\x87a\x99j7#\"\xb2\x10\x1f\x18\x0fxp6PC?%#Sh\x84TX\x8a_2-#\x9c\x1d+q`9SC;!$L`~\x00\x01\x00\x14\x00\x00\x04\x12\x05\xb6\x00\a\x00^@2\x0f\t\x01\xd0\t\x01O\t\xcf\t\x02\x10\t \t0\t\x03\xaf\x06\xef\x06\x02\x84\x06\x01\x06\x06\aZ\x02@\x03\xe0\x03\x02\x0f\x03\x01\b\x03\x03W\x02g\x02w\x02\x03\x02\xb8\xff\xc0@\v\a\nH\x02\a\x03_\x04\x03\x00\x12\x00??\xe12\x01/+]3/^]]\x10\xe12/]]]]]q10!#\x11!5!\x15!\x02q\xbb\xfe^\x03\xfe\xfe_\x05\x12\xa4\xa4\x00\x00\x00\x00\x01\x00\xb8\xff\xec\x04\xdd\x05\xb8\x00\x17\x00/@\x1c\x16Z\x01e\x19\xb0\x19\x01o\x19\xaf\x19\x02\x10\x19\x01\x0eZ\vd\x18\x11_\x06\x13\f\x00\x03\x00?2?\xe1\x01\x10\xf6\xe1]]]\x10\xf6\xe110\x01\x11\x14\x0e\x02#\".\x025\x113\x11\x14\x1632>\x027\x11\x04\xddB\x85Ɉ\x80ąD\xbb\xad\xafY\x80R(\x01\x05\xb8\xfcLrĐRM\x8e\xc7z\x03\xae\xfcH\xaf\xc06b\x88Q\x03\xb8\x00\x01\x00\x00\x00\x00\x04\x8b\x05\xb6\x00\f\x00l@\x10\x03\x02\t\t\x04\x00`\x01p\x01\xb0\x01\xf0\x01\x04\x01\xb8\xff\xc0@\x16\x06\nH\x01\x10\x01\x01\x0e/\x0e\u007f\x0e\xbf\x0e\x03\x0e@\x06\tH\x05\x04\xb8\xff\xf0\xb4\x04\x05\x04\x03\t\xb8\xff\xe0\xb3\n\x11H\t\xb8\xff\xf0@\n\x06\tH\t\x02\x03\x12\x00\x01\x03\x00?3?33++?3\x01/83+]\x113/8+]3\x129=/3310\x013\x01#\x013\x01\x1e\x01\x17>\x017\x03\xc5\xc6\xfe\x17\xbb\xfe\x19\xc5\x01'\x1d*\x11\x0f.\x1f\x05\xb6\xfaJ\x05\xb6\xfca[\xa9JJ\xa9a\x00\x00\x00\x01\x00\x14\x00\x00\x06\xfe\x05\xb6\x00*\x00߶\x10\b\x15\x18H\x10\x0f\xb8\xff\xf8\xb5\x15\x18H\x0f\a\x01\xb8\xff\xf8@\x12\x15\x18H\x01\x00\b\x15\x18H\x00\x16\x1d\b\x15\x18H\x1d\x1c\xb8\xff\xf8@/\x15\x18H\x1c%\x14\a\x01\x04\aD\a\xb4\a\x03\a\x04%\x14%$%D%T%\x05\a\x16%%\x16\a\x03\x1e\r\x00\x0e\x01p\x0e\x80\x0e\xc0\x0e\x03\x0e\xb8\xff\xc0@\x18\a\nH\x0e\x10\x0e\x0e,o,\u007f,\x02 ,0,\x02\x0f,\x01\b\x1f\x1e\xb8\xff\xf0@\x13\x1e\x16 \n\x11H\x16\x10\x06\tH\x16\r\x00\x1e\x03\a%%\xb8\xff\xe0\xb3\n\x11H%\xb8\xff\xf0\xb6\x06\tH%\x10\x1d\x12\x00?33++\x113?333++\x01/83^]]]\x113/8+]q3\x12\x179=///]^]q\x113+3+\x113+3+\x113+3+10\x013\x13\x1e\x03\x17>\x037\x133\x01#\x03.\x01'&'\x06\a\x0e\x01\a\x03#\x013\x13\x1e\x03\x17>\x037\x03)\xc5\xe5\x0f\x1d\x19\x13\x06\x04\f\x10\x13\v\xc8\xc7\xfe\x91\xbc\xfe\x0e\x1a\v\f\v\v\v\n\x19\x0e\xf2\xbc\xfe~\xc5\xdf\f\x14\x11\x0e\x05\x05\x0f\x14\x17\r\x05\xb6\xfc\xa88pi^&&Zcg1\x03r\xfaJ\x03\xaa3l/7437/p6\xfc\\\x05\xb6\xfc\x87.cb[&%blo1\x00\x01\x00\x00\x00\x00\x04`\x05\xb6\x00\v\x00\x81@\x1d\t\n\x10\n\n\x007\v\x01\v\b8\x05\x01\x05\x02\x02\x01\x00\x00\x01p\x00\x80\x00\xc0\x00\x03\x00\xb8\xff\xc0@\x14\a\nH\x00\x10\x00\x00\r\x0f\r\x1f\r/\r\u007f\r\x04\b\a\x06\xb8\xff\xf0\xb3\x06\x06\x03\x04\xb8\xff\xf0@\x10\x04(\x02\x01'\b\x01\x02\b\x04\t\x06\x03\x04\x00\x12\x00?2?3\x1299]]\x01/822/83^]\x113/8+]q39=/3]33]\x113\x18/8310!#\t\x01#\t\x013\t\x013\x01\x04`\xd3\xfe\x9e\xfe\x91\xbc\x01\xc5\xfeZ\xc6\x01L\x01N\xbe\xfe[\x02{\xfd\x85\x02\xfc\x02\xba\xfd\xd1\x02/\xfdL\x00\x00\x00\x00\x01\x00\x00\x00\x00\x047\x05\xb6\x00\b\x00s@\x19\xef\n\x01\n@\t\fH\b\xab\a\x01\x98\a\x01@\a\x01\x1b\a\x01\x0f\a\x01\a\xb8\xff\xf0@/\a\a\x05\x01\x80\x02\x01O\x02\x01\x1b\x02\x01\x02\x10\x02\x02\x00\x04Zw\x05\x87\x05\x97\x05\x03O\x05\x01\x00\x05\x10\x05\x02\a\x056\x00\x01\x00\x01\x04\x12\a\x01\x03\x00?3?\x129]\x01/^]]]\xe192/8]]]3\x113/8]]]]]3+]10\t\x013\x01\x11#\x11\x013\x02\x1b\x01T\xc8\xfeB\xbb\xfeB\xcb\x02\xd3\x02\xe3\xfc\x83\xfd\xc7\x02/\x03\x87\x00\x00\x00\x00\x01\x00R\x00\x00\x03\xfe\x05\xb6\x00\t\x008@ \t\t\x03\ag\v\x0f\v?\vO\v\x9f\v\x04\b\b\x04\x04\x01f\n\a\x04_\x05\x03\x02\b_\x01\x12\x00?\xe19?\xe19\x01\x10\xe62/2^]\x10\xe622/10)\x015\x01!5!\x15\x01!\x03\xfe\xfcT\x02\xc7\xfdM\x03\x83\xfd:\x02ۑ\x04\u007f\xa6\x91\xfb\x81\x00\x00\x00\x00\x01\x00\xa4\xfe\xbc\x029\x05\xb6\x00\a\x00&@\x17\x04\x00\xf3\x06\xf1\x00\x01\x10\x01\xb0\x01\xc0\x01\x04\x01\x05\xf5\x02\xf8\x06\xf5\x01\xf9\x00?\xe1?\xe1\x01/]\xe1\xed210\x01!\x11!\x15#\x113\x029\xfek\x01\x95\xdf\xdf\xfe\xbc\x06\xfa\x95\xfa1\x00\x00\x01\x00\x17\x00\x00\x02\xe9\x05\xb6\x00\x03\x00!\xb7\x02\x01\x01\x10\x01\x05\x00\x03\xb8\xff\xf0\xb4\x03\x02\x01\x00\x03\x00?//\x01/83\x1138\x11310\x13\x01#\x01\xc9\x02 \xb2\xfd\xe0\x05\xb6\xfaJ\x05\xb6\x00\x00\x01\x003\xfe\xbc\x01\xc9\x05\xb6\x00\a\x00$@\x14\x03\x00\xf3\x01\xf1`\x06p\x06\x02\x06\t\x00\xf5\a\xf9\x03\xf5\x04\xf8\x00?\xe1?\xe1\x01\x10\xd6]\xe1\xed210\x173\x11#5!\x11!3\xdf\xdf\x01\x96\xfej\xae\x05ϕ\xf9\x06\x00\x00\x01\x00)\x02%\x04\x19\x05\xc1\x00\x06\x00\x12\xb6\x03\x03\b\x00\x00\x01\x06\x00?\xcd\x01/\x113/10\x13\x013\x01#\t\x01)\x01\xcbf\x01\xbf\xa1\xfe\xaf\xfe\xa3\x02%\x03\x9c\xfcd\x02\xdf\xfd!\x00\x01\xff\xfc\xfe\xbc\x03N\xffH\x00\x03\x00\x12\xb6\x00\x00\x05\x01\x01\xba\x02\x00/\xe1\x01/\x113/10\x01!5!\x03N\xfc\xae\x03R\xfe\xbc\x8c\x00\x00\x00\x00\x01\x01\x89\x04\xd9\x03\x12\x06!\x00\r\x00\x16@\n\x00\x06\b\x80\x0f\x00_\x00\x02\x00\x00/]\x1a\xcc\x01/\xcd10\x01#.\x03'53\x1e\x03\x17\x03\x12x#RM?\x10\xdb\x10+.0\x15\x04\xd9\x1cSXQ\x1b\x15\"QQL\x1d\x00\x00\x00\x00\x02\x00^\xff\xec\x03\x9c\x04^\x00#\x002\x00T@\x11\x10\x01)G#U4\x0f4o4\x02\x060H\f\x1a\xb8\xff\xd0@\x1e\r\x11H\x1a\x10\t\fH\x1a\x1a\fV3\x19\x16P\x1d*R\x10\x10\x1d\x10$P\x02\a\x16\x00\x15\x00??3\xe1?9/\xe1\x10\xe12\x01\x10\xe62/++\x10\xe1^]\x10\xf6\xe12210!'#\x0e\x03#\".\x02546?\x0154.\x02#\"\x06\a'>\x0132\x1e\x02\x15\x11%2>\x02=\x01\a\x0e\x03\x15\x14\x16\x03\x19%\b!BN`?EtU0\xe7\xec\xb8\x1d7Q4S\x8fB@J\xb6df\x95a0\xfe/=hL+\x8fZzI a\x98-A*\x14'Q{T\xa4\xb0\b\aECZ7\x180\"\x89(8)Y\x8ab\xfd\x10\u007f&MuOc\a\x04 9Q3\\V\x00\x00\x00\x00\x02\x00\xae\xff\xec\x04?\x06\x14\x00\x1f\x00/\x008\xb5-H\x05W11\xb8\xff\xb8@\x17\nI\x15\x10%G\x12T0\x13\x00\x12\x15*P\x0f\n\x16 P\x1b\x00\x10\x00?2\xe1?3\xe1??\x01\x10\xf6\xe122+\x10\xf6\xe110\x012\x1e\x02\x15\x14\x0e\x02#\".\x02'#\a#\x113\x11\x14\x06\a\x06\a3>\x03\x17\"\x0e\x02\x15\x14\x1e\x0232654&\x02\x9e^\x9am<\x0232\x16\x17\a.\x03#\"\x06\x15\x14\x163267\x15\x0e\x01\x02Re\xb0\x82JL\x85\xb2fN\x9526\x178<:\x1a\x9d\x90\x91\x94Q\x8366{\x14?\x89Ֆ\x9dۉ>\"\x19\x9a\n\x13\x0f\t\xc9\xd4\xd3\xc3%\x19\xa2\x1d\x1e\x00\x00\x00\x00\x02\x00q\xff\xec\x04\x02\x06\x14\x00\x1f\x000\x004@\x1d&\x00\x1bG\x1eU2\x102\x01.H\vV1\x1f\x15\x1c\x00+P\x16\x10\x10 P\x01\x06\x16\x00?3\xe1?3\xe1??\x01\x10\xf6\xe1]\x10\xf6\xe12210%#\x0e\x03#\".\x0254>\x0232\x1e\x02\x173&'.\x015\x113\x11#%2>\x02754.\x02#\"\x06\x15\x14\x16\x03T\b\x16;M`<]\x9an<\x0232\x1e\x02\x1d\x01!\x1e\x0132>\x027\x15\x0e\x03\x03\"\x06\a!4.\x02\x02`n\xb6\x83HBx\xa7ec\x9en;\xfdL\x05\x99\x973WQL'(MQW`r\x85\v\x01\xec\x1b9X\x14J\x8e҇\x88֕NG\x81\xb5nq\xc1\xb6\n\x13\x1d\x12\xa2\x13\x1c\x12\b\x03ۜ\x95DqP,\x00\x00\x00\x01\x00\x1d\x00\x00\x02\xf0\x06\x1f\x00\x1b\x00p@N\xcf\x1d\xdf\x1d\x02`\x1d\x80\x1d\x90\x1d\xa0\x1d\x04\x1f\x1d?\x1dO\x1d\x03\x1b\x1b\u007f\x10\xbf\x10\x02\x10\x10\x1a\x02G\x03\a\x03\x0f\x05\x1f\x05/\x05\xaf\x05\x04\x05\x05\x00\x03\x10\x03 \x03\x80\x03\x90\x03\xa0\x03\x06\x06\x03\x01\x05O\a\x00\x1a\x01\a\x1a\x0f\x14P\r\x01\x02\x15\x00??\xe1?^]3\xe12\x01/^]3/]\x113\x10\xe122/]9/]]]10\x01#\x11#\x11#5754>\x0232\x16\x17\a.\x01#\"\x0e\x02\x1d\x013\x02\x8b\xf5\xb7\xc2\xc2-U|N;c'/\x1fI((:&\x13\xf5\x03\xc1\xfc?\x03\xc1KD`k\x8dT#\x17\x0e\x8d\v\x11\x130SAh\x00\x00\x00\x00\x03\x00%\xfe\x14\x03\xfc\x04^\x00?\x00R\x00^\x00\xa7@\x19\r2\x05SG7\x12/`7p7\x807\x037/7/'H\x1dYG\x05\xb8\xff\xc0@M\a\nH\x05\x05\x01\n\x1d\x01\xfd\x1d\x01\xb0\x1d\x01\x88\x1d\x01 \x1d0\x1d@\x1d\x03\x1d\x1d`\x1f`\x01\xbf`\xdf`\x02\xa0`\x01@'@\f\x0fH'\x02\x052\r\x04\x027.\x015467.\x0354>\x0232\x16\x17\x01\x14\x1e\x0232654.\x02+\x01\"\x0e\x02\x13\x14\x1632654&#\"\x06\x03\xfc\xc5\x1c&/_\x8c]\x16,\x0e\x11!\x1b\x11\x18)8\x1f\xb0]\x80Q$A\x86͋k\xa0j5'BW/*6@E+G1\x1b2b\x92a%O\x1b\xfe@\x1a;aH\xba\xb9\x187ZA\xb0#L?)\\lcdgidcj\x04Jq\x1b#mEL\x81^5\x01\x03\n\x19 (\x18\x1b!\x12\x06/Pm=X\x8ca4*PqG<[B*\v\x13R5=Y*\x12?Q`3Y\x8cb4\v\t\xfb\x02%@.\x1bsl.:!\f\x10,M\x03`spow{tx\x00\x01\x00\xae\x00\x00\x04\x12\x06\x14\x00\x19\x002@\x1d\x00G\x19U\x1b\x10\x1b`\x1b\x80\x1b\x03\x0f\x0e\nG\vT\x1a\x10\x04P\x15\x10\f\x00\v\x00\x15\x00?2??\xe13\x01\x10\xf6\xe122]\x10\xf6\xe110!\x114&#\"\x0e\x02\x15\x11#\x113\x11\a3>\x0332\x16\x15\x11\x03\\ipQnC\x1d\xb6\xb6\b\n\x19ER\\0\xb7\xb9\x02Â\x824f\x94`\xfd\xc7\x06\x14\xfe2\x90+?*\x14\xbf\xd2\xfd3\x00\x00\x00\x00\x02\x00\xa0\x00\x00\x01u\x05\xe5\x00\x03\x00\x11\x00%@\x14\x10\x13 \x13\x02\f\x00G\x04\x01T\x12\aS\x0f\x0f\x02\x0f\x00\x15\x00??3/\xe5\x01\x10\xf62\xe12]10!#\x113\x034632\x1e\x02\x15\x14\x06#\"&\x01d\xb6\xb6\xc4=-\x16'\x1d\x11?,-=\x04J\x01)<6\r\x1c+\x1e:98\x00\x00\x00\x02\xff\xbc\xfe\x14\x01u\x05\xe5\x00\x13\x00!\x00.@\x19\x10# #\x02\x1c\x0fG\f\x14\x03\x03\fT\"\x17S\x1f\x1f\r\x0f\aP\x00\x1b\x00?\xe1?3/\xe5\x01\x10\xe62/2\x10\xe12]10\x13\"&'5\x1e\x0132>\x025\x113\x11\x14\x0e\x02\x134632\x1e\x02\x15\x14\x06#\"&B0?\x17\x1a6#\x1b.#\x13\xb6\"Hm\x13=-\x16'\x1d\x11?,-=\xfe\x14\x0e\v\x94\n\v\x0f'A3\x04\xf4\xfb\x18M{W/\a_<6\r\x1c+\x1e:98\x00\x00\x00\x00\x01\x00\xae\x00\x00\x03\xf0\x06\x14\x00\x0e\x00^@\v\a\x04\x04\x02\x03\x03\x06D\x05\x01\x05\xb8\xff\xc0@\x17\a\nH\x05\x10\x05\x05\x10\x0f\x10/\x10\x02\a\r\tG\nT\x0f\v\x00\x00\xb8\xff\xf8@\x10\f\x0fH\a\b\f\x0fH\x00\a\x03\x06\n\x15\x03\x0f\x00??3\x1299++?\x01\x10\xf6\xe12^]\x113/8+]33\x1139\x11310\x017\x013\t\x01#\x01\a\x11#\x113\x11\x03\x01V\x87\x01%\xd3\xfeo\x01\xac\xd1\xfe\xb0m\xb4\xb4\x10\x027\xaa\x01i\xfe%\xfd\x91\x01\xf8R\xfeZ\x06\x14\xfd6\xfe\xed\x00\x01\x00\xae\x00\x00\x01d\x06\x14\x00\x03\x00\x1a@\x0e\x10\x05 \x05\x02\x00G\x01T\x04\x02\x00\x00\x15\x00??\x01\x10\xf6\xe1]10!#\x113\x01d\xb6\xb6\x06\x14\x00\x00\x00\x01\x00\xae\x00\x00\x06\x87\x04^\x00,\x00e@?#\nG\xb9\v\x01\x96\v\xa6\v\x02\x89\v\x01g\vw\v\x02\v\v\x16\x00G,U.\xf0.\x01\xcf.\x01 .P.\x02\x0f.\x01\b\x19\x15G\x16T-#\x1a\x1a\x04\x0fP(\x1f\x10\x17\x0f\x16\v\x00\x15\x00?22??3\xe122\x113\x01\x10\xf6\xe12^]]]]\x10\xf6\xe1\x119/]]]]\xe1210!\x114&#\"\x0e\x02\x15\x11#\x114&#\"\x0e\x02\x15\x11#\x113\x173>\x0332\x16\x173>\x0332\x16\x15\x11\x05\xd1diIfA\x1e\xb7ciMh?\x1b\xb6\x94\x1a\n\x18BOY.x\x9f&\b\x1aIW`2\xaf\xb1\x02Â\x82/[\x87X\xfd\xa2\x02Â\x824f\x94`\xfd\xc7\x04J\x94+?*\x14X^/D-\x16\xbf\xd2\xfd3\x00\x00\x00\x01\x00\xae\x00\x00\x04\x12\x04^\x00\x18\x000@\x1c\x00G\x18U\x1a\x10\x1a`\x1a\x80\x1a\x03\x0e\nG\vT\x19\x0f\x04P\x14\x10\f\x0f\v\x00\x15\x00?2??\xe13\x01\x10\xf6\xe12]\x10\xf6\xe110!\x114&#\"\x0e\x02\x15\x11#\x113\x173>\x0332\x16\x15\x11\x03\\ipQnC\x1d\xb6\x94\x1a\n\x19ER\\0\xb7\xb9\x02Â\x824f\x94`\xfd\xc7\x04J\x94+?*\x14\xbf\xd2\xfd3\x00\x02\x00q\xff\xec\x04-\x04^\x00\x13\x00\x1f\x000@\x1d\x1aH\x00W!@!\xd0!\xe0!\x03\x0f!\x01\x06\x14H\nV \x1dP\x0f\x10\x17P\x05\x16\x00?\xe1?\xe1\x01\x10\xf6\xe1^]]\x10\xf6\xe110\x01\x14\x0e\x02#\".\x0254>\x0232\x1e\x02\x05\x14\x1632654&#\"\x06\x04-C}\xb2og\xae\u007fGC|\xb3og\xae\u007fG\xfd\x00\x89\x9a\x9a\x87\x89\x9a\x9a\x87\x02'\x89ՑLL\x91Չ\x88ӑKK\x91ӈ\xd1\xd3\xd3\xd1\xd1\xcf\xcf\x00\x00\x00\x00\x02\x00\xae\xfe\x14\x04?\x04^\x00\x1f\x000\x006@\x1e.H\x1bW2\x102\x01&\x10\x06\fG\rT1 P\x11\x16\x10\x0e\x0f\f\x1b+P\x05\x00\x16\x00?2\xe1???3\xe1\x01\x10\xf6\xe1222]\x10\xf6\xe110\x05\".\x02'#\x16\x17\x1e\x01\x15\x11#\x113\x173>\x0332\x1e\x02\x15\x14\x0e\x02\x03\"\x0e\x02\a\x15\x14\x1e\x0232654&\x02\x9e;`M;\x17\f\x03\x03\x02\x04\xb6\x94\x1a\b\x17:M`<^\x9am<\x02754.\x02#\"\x06\x15\x14\x16\x17\".\x0254>\x0232\x1e\x02\x17373\x11#\x1146767#\x0e\x03\x025LiA\x1f\x02\x1bAlQ\x87\u007f\u007ff]\x9an<\x03\x02\x89\x1dH\x1a\x18\x1c;\x1a?hK)\xb6\x94\x16\b\x199GX\x04^\x05\x05\xa8\x05\a3_\x85Q\xfd\xb0\x04J\xc9+P=%\x00\x01\x00Z\xff\xec\x03?\x04^\x005\x00H@-%\x13G\x90\x00\xa0\x00\x02\x00W7?7_7\x9f7\x03\x107\x01,G\t\x9f\x1d\xaf\x1d\x02\x1dV6&)P\x13,\x05\"\x10\t\x0eP\x05\x16\x00?\xe12?\x1299\xe12\x01\x10\xf6]2\xe1]]\x10\xf6]\xe1310\x01\x14\x0e\x02#\"&'5\x1e\x0332>\x0254.\x02'.\x0354>\x0232\x16\x17\a.\x01#\"\x06\x15\x14\x1e\x02\x17\x1e\x03\x03?:m\x9a`m\x9c;\x1fLTY,A[9\x1a\x145\\HHsP+7d\x8cVa\xa1H?A\x89Gfb\x178^FHqP*\x01-PxQ(#\"\xa6\x10\x1f\x18\x0f\x16);$\x1f212\x1f\x1f#4./\x1d\x1e\x027\x15\x0e\x03#\".\x025\x11#5?\x013\x15!\x15!\x11\x14\x16\x01\xfa\x12-*#\t\r(04\x19>jM,\x9b\x9bNi\x01\x14\xfe\xec?\x81\x04\x06\b\x03\x8a\x06\f\t\x05 N\x85e\x02}QN\xe6\xfc\x89\xfd\x83ab\x00\x00\x00\x01\x00\xa4\xff\xec\x04\b\x04J\x00\x1a\x000@\x1c\x01\x17G\x1aU\x1c\x10\x1c`\x1c\x80\x1c\x03\x0fG\fT\x1b\x18\r\x0f\x12P\x02\a\x16\x00\x15\x00??3\xe1?3\x01\x10\xf6\xe1]\x10\xf6\xe1210!'#\x0e\x03#\".\x025\x113\x11\x14\x1632>\x025\x113\x11\x03u\x1b\n\x19ER\\0[\x8a\\/\xb6joQnC\x1d\xb6\x93+?)\x14.b\x98i\x02\xcd\xfd=\x82\x824e\x94`\x02:\xfb\xb6\x00\x00\x00\x00\x01\x00\x00\x00\x00\x03\xd5\x04J\x00\x11\x00m\xb9\x00\x11\xff\xf8@\x0f\n\x0eH\x11\x00\b\n\x0eH\x00\t\t\x01\x0f\x10\xb8\xff\xc0\xb3\x12\x15H\x10\xb8\xff\xc0@\x1c\a\vH\x10\x10\x10\x10\x13\xbf\x13\xcf\x13\xef\x13\x03P\x13\x01\x0f\x13/\x13O\x13\x03\a\x02\x01\xb8\xff\xf0@\n\x01G\t\x01\t\x0f\x01\x0f\x00\x15\x00??39]\x01/8\xc1^]]]\x113/8++\xc1\x129=/3+3+10!\x013\x13\x1e\x03\x173>\x037\x133\x01\x01w\xfe\x89\xbc\xc7\v\x1e\x1e\x19\x04\a\x05\x18\x1e\x1e\vǼ\xfe\x89\x04J\xfd\x9d!hl`\x19\x19`lh!\x02c\xfb\xb6\x00\x01\x00\x14\x00\x00\x05\xe3\x04J\x00/\x00ù\x00/\xff\xf8@\f\n\x0eH/\x00\b\t\x0eH\x00' \xb8\xff\xf8@\x12\t\x0eH \x1f\b\t\x0eH\x1f\t\x10\b\n\x0eH\x10\x0f\xb8\xff\xf8@\t\t\x0eH\x0f\x18T'\x01'\xb8\xff\xe0@\x15\a\nH[\x18\x01\x18 \a\nH'\t\x18\x18\t'\x03\x11-.\xb8\xff\xc0\xb3\x12\x15H.\xb8\xff\xc0@\x13\a\vH.\x10..1 101\x02\x0f1\x01\a\x12\x11\xb8\xff\xf0@\x16\x11-\x1f\t\t\x01\t\x11\x0f'\x19\x06\x19f\x19v\x19\x03\x19\x00\x10\x15\x00?33]\x113?3]33\x01/83^]]\x113/8++3\x12\x179=///+]+]\x113+3+\x113+3+\x113+3+10!\x03.\x03'&'#\x06\a\x0e\x01\a\x03#\x013\x13\x1e\x03\x173>\x037\x133\x13\x1e\x03\x173>\x037\x133\x01\x03\xf0\xa8\x04\f\f\r\x06\x0e\x0f\x06\x0e\r\v\x19\v\xac\xd3\xfe翃\n\x14\x12\x0e\x04\x06\x05\x11\x15\x16\n\xb3Ĭ\t\x17\x16\x12\x04\x06\x03\r\x12\x15\v\x89\xba\xfe\xe4\x02h\x12-24\x19:>?:2j%\xfd\x9c\x04J\xfd\xb8-ig[\x1d\x1aWa_!\x02k\xfd\x95\"\\_X\x1d\x1aWhm/\x02H\xfb\xb6\x00\x00\x00\x01\x00#\x00\x00\x03\xdb\x04J\x00\v\x00\xe5@\xa1\x89\t\x01\x86\x03\x01\x06\x04\x01\xf7\x04\x01\xe5\x04\x016\x04\x01\x04\x05\xe8\x06\x01\x06\x03\xe7\x00\x01\x00\t\t\x02\x01\xf8\x02\x01\xea\x02\x019\x02\x01\x02\x01k\x05{\x05\x02W\x05\x01:\x05J\x05\x02d\x01t\x01\x02X\x01\x015\x01E\x01\x02\x05\x01\t\x01\t\x05\x03\v\x06\b\x01\xf7\b\x01\xe5\b\x016\b\x01\b\a@\x16\x19H\a@\x0e\x11Hk\a{\a\x02W\a\x01:\aJ\a\x02\a\r\x10\r0\r\x02\x90\r\xb0\r\x02\x0f\r\x01\x06\xd9\n\x01\xc8\n\x01\xba\n\x01\t\n\x01\n;\vK\v\x02(\v\x01\x05\v\x15\v\x02\v\a\x15\x01\x0f\x00??\x01/]]]\xc1]]]]^]]q\x10\xde]]]++\xc1]]]q\x12\x179=/\x18//]]]]]]\x10\xc1]]]q\x113]33]\x10\xc1]]]q10\x00]]\t\x013\x1b\x013\t\x01#\t\x01#\x01\x98\xfe\x9f\xcf\xfa\xfa\xcf\xfe\x9d\x01u\xcf\xfe\xf4\xfe\xf2\xcf\x023\x02\x17\xfef\x01\x9a\xfd\xe9\xfd\xcd\x01\xb4\xfeL\x00\x00\x00\x00\x01\x00\n\xfe\x14\x03\xdf\x04J\x00\"\x00d\xb6\"\x10\b\b\x00\x0e\x0f\xb8\xff\xc0\xb3\x12\x15H\x0f\xb8\xff\xc0@\x1d\a\vH\x0f\x10\x0f\x0f$\xbf$\xcf$\xef$\x03P$\x01\x0f$/$O$\x03\a\x18\x01\x00\xb8\xff\xf0@\f\x00\"\x10\b#\x1cP\x15\x1b\x0e\x00\x0f\x00?2?\xe1\x11333\x01/8\xc13^]]]\x113/8++\xc1\x129=/3310\x133\x13\x1e\x03\x173>\x037\x133\x01\x0e\x03#\"&'5\x1e\x0132>\x02?\x01\n\xbd\xd7\x0e\x1d\x19\x12\x04\x06\x05\x16\x1b\x1d\vǼ\xfeN\x1cAVtP4L\x1b\x15@#0F4%\x0f9\x04J\xfd\x9b(XXR#\x19Va^!\x02c\xfb'Q\x81Z1\v\x06\x91\x05\a\x17,@)\xa0\x00\x00\x00\x00\x01\x00R\x00\x00\x035\x04J\x00\t\x00l@\v\t\x97\x03\x01\x03\b\t\rH\x03\a\xb8\xff\xc0@\x11\a\nH\a\a\v?\v_\v\u007f\v\x03\x98\b\x01\b\xb8\xff\xf8\xb5\t\rH\b\x04\x02\xb8\xff\xc0\xb7\x12\x15H?\x02\x01\x02\a\xb8\xff\xf0@\x12\a\fH\a\x04O\x05\x0f\x02\x10\a\fH\x02\bO\x01\x15\x00?\xe12+?\xe12+\x01/]+33+]]\x113/+3+]310)\x015\x01!5!\x15\x01!\x035\xfd\x1d\x02\x18\xfe\t\x02\xb0\xfd\xf4\x02\x1e}\x03D\x89\x92\xfc\xd1\x00\x00\x00\x00\x01\x00=\xfe\xbc\x02\xa2\x05\xb6\x00'\x00@@%\x1a\x05\x05\xf7 '\xf1#\x13\x0f\xf6\x10\f\x01\f#\x0f\xf5\xd9\x10\x01\x0f\x10_\x10\x02\x10\x10)\x1a\xf5\x19\xf8\x05\xf5\x06\xf9\x00?\xe1?\xe1\x129/]]\xe19\x01/]\xe633\xf12\xe2/210\x05\x14\x1e\x02\x17\x15.\x035\x114ᒑ\x114>\x027\x15\x0e\x03\x15\x11\x14\x06\a\x15\x1e\x01\x15\x01\xf4\x18-A(M\x83_6\x83}}\x836_\x83M(A-\x18wssw\x100=#\r\x01\x96\x01!GnN\x01NgV\x9bVg\x01MNnG!\x01\x95\x01\r#=0\xfe\xb4i{\x14\f\x14zj\x00\x00\x01\x01\xe9\xfe\x14\x02\u007f\x06\x14\x00\x03\x00-@\x1f\x00\x05\x010\x05@\x05p\x05\x80\x05\x04\x02\xaa\x00\x03\x10\x03@\x03\x80\x03\xc0\x03\x05\a\x03\x02\x1b\x00\x00\x00??\x01/^]\xe1]q10\x013\x11#\x01閖\x06\x14\xf8\x00\x00\x00\x01\x003\xfe\xbc\x02\x98\x05\xb6\x00)\x00@@%\r$$\xf7\a\x00\xf1\x1a\xf6\x14\x03\x90\x1d\x01\x1d\x04\x1a\xf5\xef\x19\xff\x19\x02\xd9\x19\x01\x19\x19\x0e$\xf5#\xf9\r\xf5\x0e\xf8\x00?\xe1?\xe1\x119/]]\xe19\x01/]33\xe6\xf12\xe2/210\x134675.\x015\x114.\x02'5\x1e\x03\x15\x11\x14\x1e\x023\x15\"\x06\x15\x11\x14\x0e\x02\a5>\x035\xe1wssw\x18-A(M\x83_6!A`>}\x836_\x83M(A-\x18\x01;jz\x14\f\x14{i\x01L0=#\r\x01\x95\x01!GnN\xfe\xb34H-\x14\x9bVg\xfe\xb2NnG!\x01\x96\x01\r#=0\x00\x01\x00f\x02J\x04\x02\x03Z\x00#\x00<@\r\x1d%\x10%\x01\x10\n\x01\n\x17\xad\n\x1f\xb8\xff\xc0@\x16\x10\x13H\x1f\x1f\x05\xad\x1c\x0f\r\x1f\r?\rO\ro\r\x8f\r\x06\r\x00/]3\xf1\xc8/+2\xe1\x01/]]\x10\xce10\x01.\x03#\"\x0e\x02\a5632\x1e\x02\x17\x1e\x0332>\x027\x15\x06#\".\x02\x02\x12%7-)\x16\x1c<;8\x19d\x94\x1d27C/%7/(\x16\x1c<;8\x18c\x95\x1d27C\x02\x8b\x10\x16\r\x05\x13!,\x19\xa2l\x05\r\x19\x14\x10\x16\r\x05\x13!,\x19\xa2l\x05\r\x19\x00\x00\x00\x02\x00\x93\xfe\x8b\x01\x91\x04^\x00\x03\x00\x17\x00A\xb9\x00\x00\xff\xf0@\x13\n\x14H0\x19\xa0\x19\xb0\x19\xc0\x19\x04\x02\x04\x9a\x0e\x03\x03\x0e\xb8\xff\xc0@\x0f\a\nH\x0e\x00\t\x9b\x13\x00\x02\x10\x02\x02\a\x02\x00/^]/\xf5\xce\x01/+3/\x10\xe12]10+\x133\x13#\x13\x14\x0e\x02#\".\x0254>\x0232\x1e\x02\xd5y3\xdf\xef\x13#.\x1b\x1a.#\x14\x14#.\x1a\x1b.#\x13\x02\xa4\xfb\xe7\x05H&5!\x0f\x0f!5&%4\"\x10\x10\"4\x00\x00\x01\x00\xbc\xff\xec\x03\xba\x05\xcb\x00%\x00Z@%\x12\x03F\x0f\x04\x04\n%\x15'@'\x01\x1eH\x00\n0\n@\n\xd0\n\x04\x06\n\x1bs\x0f\x12\x0f!s\x05\x02\x05\x0f\xb8\xff\xc0@\f\x0f\x12H\x0f\x05\x0f\x05\x03\x10\a\x03\x19\x00??\x1299//+\x113\x10\xe1\x113\x10\xe1\x01/^]\xe1]\x10\xc62\x119/3\xe1210$\x06\a\x15#5.\x0354>\x02753\x15\x1e\x01\x17\a.\x03#\"\x06\x15\x14\x163267\x15\x03vnL\x89W\x8ab45a\x8bV\x89H\x88.5\x178<;\x19\x9d\x90\x91\x94Q\x836\xd4\x1e\x02\xc8\xce\rK\x85lj\x8dˈK\r\xac\xa4\x03!\x17\x9a\n\x13\x0f\t\xca\xd4\xd2\xc3%\x18\xa1\x00\x00\x01\x00D\x00\x00\x04#\x05\xc9\x00(\x00u@\x11\r\x11o#\x0f\x0f\x1f\x0f\x02\a\x1f\x0f\x1f\x0f\x19\x03\x17\xb8\xff\xc0\xb3\n\x0eH\x17\xb8\xff\xc8@0\x06\tH\x17\x17*\x10*\x01!\x19@\v\x0eH\x19\x19)\x10!u\r/\"\u007f\"\x8f\"\xaf\"\xbf\"\xdf\"\xff\"\a\"\"\x00\x16t\x19\x18\as\x00\a\x00?\xe1?\xe1\x119/]3\xe12\x11\x013/+3]\x113/++3\x1299//^]3\xe1210\x012\x16\x17\a.\x01#\"\x0e\x02\x15\x11!\x15!\x15\x14\x0e\x02\a!\x15!5>\x03=\x01#53\x114>\x02\x02\x9aj\xaeBB8\x8dK0RY@+\x10\xa6\x9a\v)DaCՉ\x01DW\x89_2\x00\x02\x00{\x01\x1d\x03\xec\x04\x8b\x00#\x007\x00\x86@#\x0e\x8f\x16\x01\x16\x16.\xab\x15\x0f\f\x18\x06\x1e!\x03\b\x00p\x12\x01\x12\x129\x109\x01\x04 $\xaa\x80\x00\x01\x00\xb8\xff\xc0@1\x06\nH\x00\x008\x17\x80\x1f\x01\x1f\f\x06\x18\x1e\x0f\x06\x04\t)\xae\x00\x1b\x01\x1b\r\x053\xae\xcf\t\xef\t\x02\x90\t\xa0\t\xb0\t\x03\x1f\t?\to\t\x03\t\x00/]]]\xe1\xc62/]\xe1\x12\x179\x113\xc6]2\x11\x013/+]\xe1\xc62]\x113/]\x12\x179\xf1\xc0/]210\x13467'7\x17>\x0132\x16\x177\x17\a\x1e\x01\x15\x14\x06\a\x17\a'\x0e\x01#\"&'\a'7.\x017\x14\x1e\x0232>\x0254.\x02#\"\x0e\x02\xba#\x1f\x81b\u007f/l<\x027.\x0154>\x0232\x16\x17\a.\x01#\"\x06\x15\x14\x1e\x02\x17\x1e\x03\x15\x14\x0e\x02\a\x1e\x01\x15\x14\x0e\x02#\"&'5\x1e\x0332>\x0254.\x02'.\x037\x14\x1e\x02\x1f\x01>\x0354.\x02'\x0e\x03\x89\x1a-:\x1fKU7d\x8cVa\x9dH8A\x8cGcf\x189_FHqN*\x18)4\x1cEL;l\x9b`l\x9c;\x1fLTY+E]7\x17\x113^LIsP)\x9a\x1c?eH#\x14)!\x15\x1aAlR\x19/&\x17\x03)3S@-\x0f&rT=bD%( \x8b\x1c';9\x1b.,/\x1d\x1cANa>4UD1\x10&mNGoM(! \x9e\x0f\x1e\x17\x0e\x18'3\x1b\x1d--1\x1f\x1f>NdY%?:7\x1e\x0f\r$.8\"&@;9\x1e\b\x1f-:\x00\x00\x00\x00\x02\x013\x05\f\x03j\x05\xd9\x00\v\x00\x19\x005@!\f\x86\xaf\x14\x01\x14\xc0\x06\x86\x00\x00\x10\x00@\x00P\x00\x04\x06\x00\x0f\x03\x91\x17\x9f\t\xcf\t\x020\t\x01\t\x00/]]3\xe52\x01/^]\xe1\x1a\xdc]\xe110\x014632\x16\x15\x14\x06#\"&%4632\x1e\x02\x15\x14\x06#\"&\x0138('::'(8\x01w8(\x13#\x1a\x10:&(8\x05s6015522560\f\x19&\x1b522\x00\x00\x03\x00d\xff\xec\x06D\x05\xcb\x00%\x00A\x00U\x00j@C\x05\xc5\x1a\x0f\x0f\"\x1a\"\x1a\"&L\xc3\x004\x01\xc04\x014WB\xc3&\n\xc9\x15\x00\xc9\x1f\x0f\x15\x1f\x15/\x15\u007f\x15\x8f\x15\x9f\x15\x06\b\x00\x1f\x10\x1f`\x1fp\x1f\x80\x1f\x05\x15\x1f\x15\x1f-G\xc8;Q\xc8-\x04\x00?\xe1/\xe1\x1199//]^]\x10\xe1\x10\xe1\x01/\xe1\x10\xde]q\xe1\x1199//\x113/\x10\xe110\x01\"\x0e\x02\x15\x14\x1e\x0232>\x027\x15\x0e\x03#\".\x0254>\x0232\x16\x17\a.\x01\x014>\x0432\x1e\x04\x15\x14\x0e\x04#\".\x047\x14\x1e\x0232>\x0254.\x02#\"\x0e\x02\x03{=^@!\x1d=_C\x17698\x19\x1815<#f\x98e36i\x99d?\x84;>4a\xfc\xbe6a\x8a\xa7\xc0hh\xc0\xa7\x8aa66a\x8a\xa7\xc0hh\xc0\xa7\x8aa6me\xafꅅ\xea\xafee\xafꅅ\xea\xafe\x04\x1d,SxKNxR+\a\f\x11\t\x83\v\x12\x0e\aBz\xaage\xa7xC!\x1d\u007f\x1a\x1c\xfe\xbeh\xc0\xa7\x8aa66a\x8a\xa7\xc0hh\xc0\xa7\x89b55b\x89\xa7\xc0h\x85\xea\xafee\xafꅅ\xea\xafee\xaf\xea\x00\x00\x00\x00\x02\x00D\x03\x10\x02B\x05\xc7\x00\x1e\x00-\x00N@/-\x01\x0f\xe0\x00\x1d\x10\x1d\x02\x1d/\x0f/\x1f/O/\u007f/\xaf/\x05$\xe0\v\x17\x17`\v\x01\v.-\xe4\x0f\x0f\x1a\x01'\xe4\x00\x06\xc0\x13\xe4\x1a\xde\x00?\xe1\x1a\xdc\xc4\xe19\x119/\xe1\x01\x10\xc6]2/\x10\xe1]\x10\xd6]\xe12210\x01'\x0e\x03#\".\x02546?\x0154&#\"\x06\a'>\x0132\x16\x15\x11\x03\x0e\x03\x15\x14\x1632>\x02=\x01\x01\xe7\x1c\x12'/8#+H4\x1d\x8d\x8fc=80Z*03u<}w\xc93D)\x122*\":+\x19\x03\x1dR\x16#\x19\r\x1a3M3fl\x05\x04\x1fH9\x1d\x16d\x1a$jz\xfe:\x019\x03\x12\x1e+\x1d3-\x15,A,1\x00\x02\x00R\x00s\x03\x93\x03\xc7\x00\x06\x00\r\x00`@\x11\x02\x04\r\xeb\nP\x04`\x04\x02\x04\n\x04\n\x06\v\t\xb8\xff\xc0@!\t\fH\t\x0f\x0f\x0f\x9f\x0f\xaf\x0f\x03\x06\xeb\x9f\x03\x01\x03\x06\x00\x03\r\a\n\n\x05\x03\x03\x01\f\x05\b\x01\x00/3/3\x129=/\x129\x1133\x1133\x01\x18/]\xe1]\x10\xc6+2\x1199//]\x10\xe1\x11310\x13\x01\x17\x03\x13\a\x01%\x01\x17\x03\x13\a\x01R\x015u\xee\xeeu\xfe\xcb\x01\x97\x016t\xed\xedt\xfe\xca\x02)\x01\x9eN\xfe\xa4\xfe\xa4N\x01\x9b\x1b\x01\x9eN\xfe\xa4\xfe\xa4N\x01\x9b\x00\x01\x00f\x01\x06\x04\x02\x03\x1d\x00\x05\x009@$\x02\xaa\x01\a\x10\a\x01\x96\x04\x01\x8b\x04\x01y\x04\x01V\x04\x01K\x04\x018\x04\x01\x12\x04\x01\t\x04\x01\x04\x04\xad\x05\xb3\x00?\xe1\x01/]]]]]]]]]\x10\xde\xe110\x01\x11#\x11!5\x04\x02\x95\xfc\xf9\x03\x1d\xfd\xe9\x01\x81\x96\x00\x00\x00\xff\xff\x00R\x01\xd1\x02B\x02y\x12\x06\x00\x10\x00\x00\x00\x04\x00d\xff\xec\x06D\x05\xcb\x00\b\x00\x1e\x00:\x00N\x00\xc2@}\xa4\x16\xb4\x16\xc4\x16\x03\xb4\x17\xc4\x17\x02\x17\x16\x01R\x15\x0e\x17\x0e\x16\xc5\x15\x0e\x14\x15\x15\x0e\x0e\t\x00\x19\xc5\x1a\t\xc5\x04\x15\x04\x00\x1a\x01\x00\x1a\xc0\x1a\xd0\x1a\x03\a\x8f\x04\x01\x1a\x04\x1a\x04\x1fE\xc3\x00-\x01\xc0-\x01-P;\xc3\x1f\x0e\x18\xc9\x00\x00\x16\x1b\x16\x15\x1a\b\xc9\x1b\x00\x1a\x01\x0f\x1a\x1f\x1a/\x1a\u007f\x1a\x8f\x1a\x9f\x1a\x06\b\x00\x1b\x10\x1b`\x1bp\x1b\x80\x1b\x05\x1a\x1b\x1a\x1b&@\xc84\x13J\xc8&\x04\x00?\xe1?\xe1\x1199//]^]q\x10\xe1\x1133\x11\x129\x10\xe12\x01/\xe1\x10\xde]q\xe1\x1199//]^]q\x119\x10\xe1\x10\xe12\x119\x87\x10+\x10\x00\xc1\x87\x05+\x10\xc4\x01]10]\x0132654&+\x01\x05\x14\x0e\x02\a\x16\x17\x1e\x02\x1f\x01#\x03#\x11#\x1132\x16\x014>\x0432\x1e\x04\x15\x14\x0e\x04#\".\x047\x14\x1e\x0232>\x0254.\x02#\"\x0e\x02\x02\xe7H[OSYF\x01\x92\x1b-9\x1fC5\x17*!\n\n\xb3\xce_\x9d騞\xfb\xeb6a\x8a\xa7\xc0hh\xc0\xa7\x8aa66a\x8a\xa7\xc0hh\xc0\xa7\x8aa6me\xafꅅ\xea\xafee\xafꅅ\xea\xafe\x03\x00HEJ;\x810K9(\rnW%G8\x11\x11\x01`\xfe\xa0\x03}\x82\xfe\xc3h\xc0\xa7\x8aa66a\x8a\xa7\xc0hh\xc0\xa7\x89b55b\x89\xa7\xc0h\x85\xea\xafee\xafꅅ\xea\xafee\xaf\xea\x00\x00\x00\x00\x01\xff\xfa\x06\x14\x04\x06\x06\xa0\x00\x03\x00\x12\xb6\x00\x00\x05\x01\x01\xba\x02\x00/\xe1\x01/\x113/10\x01!5!\x04\x06\xfb\xf4\x04\f\x06\x14\x8c\x00\x00\x00\x00\x02\x00{\x03V\x02\xf2\x05\xcb\x00\x13\x00'\x00C@,\x1e\xab\n)\x9f)\x01\x14\xaa0\x00@\x00\x02\x00\x19\xae\x10\x0f \x0f\x02\xe0\x0f\xf0\x0f\x02o\x0f\x01\x00\x0f\x10\x0f \x0f\x03\x06\x0f\x0f#\xae\x05\x04\x00?\xe13/^]]]q\xe1\x01/]\xe1]\x10\xd6\xe110\x134>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x027\x14\x1e\x0232>\x0254.\x02#\"\x0e\x02{2UsAAsV22VsAAsU2{\x1e4F((F5\x1e\x1e5F((F4\x1e\x04\x8fAsV22VsAArU11UrA'E4\x1e\x1e4E'(G5\x1f\x1f5G\x00\x00\x00\x02\x00f\x00\x00\x04\x02\x04\xa2\x00\v\x00\x0f\x00:@!\x10\x11\x01\x0f\b\b\x06\t\xaa\f\x01\x01\x03\xef\x00\x01 \x00`\x00\xa0\x00\x03\x00\r\xad\f\t\x00\xad\x06\x03\xb3\x00?3\xe12/\xe1\x01/]]33\x113\xe122\x113]10\x01!5!\x113\x11!\x15!\x11#\x015!\x15\x01\xe9\xfe}\x01\x83\x96\x01\x83\xfe}\x96\xfe}\x03\x9c\x02\x87\x96\x01\x85\xfe{\x96\xfe\u007f\xfe\xfa\x96\x96\x00\x01\x001\x02J\x02m\x05\xc9\x00\x1e\x00@@\x15\b\xe1\x00\x17 O \u007f \x02 @\x06\nH\x1d\xe1\x01\x0f\x0f\x01\xb8\xff\xc0@\x0e\x15\x18H\x01\b\x1d\v\xe5\x12\xde\x1d\xe5\x01\xdd\x00?\xe1?\xe1\x129\x01/+3/\x10\xe1+]\x10\xde2\xe110\x01!57>\x0354&#\"\x06\a'>\x0132\x1e\x02\x15\x14\x0e\x02\x0f\x01!\x02m\xfd\xc4\xd19H(\x0fB63]-N6\x85RUC;\"A@2&^0A!?[92VU[7\x9d\x00\x01\x00\x1f\x029\x02h\x05\xc9\x000\x00a@<\x03\x00\x19\x19\x0e\x06\x1e\xe1\x00\x00\x15\xe1\x062_2\x8f2\x022@\x06\nH''\x0e@\x19 H\x0e\x03\x19\xe4\x0f\x1a\x1f\x1a/\x1a_\x1a\xdf\x1a\x05\b\x1a\x1a\x12&#\xe5,\xde\x12\xe5\x0f\v\xdf\x00?3\xe1?\xe13\x129/^]\xe19\x01/+3/+]\x10\xde\xe13/\xe1\x11\x129/\x12910\x01\x14\x06\a\x1e\x01\x15\x14\x0e\x02#\"&'5\x1e\x0132654&+\x01532654.\x02#\"\x06\a'>\x0332\x1e\x02\x02NQEXX(S~VF{9?\x845bXk`bb\\T\x14#/\x1b;a3E\x1d=DL,EiF#\x04\xe7Nj\x18\x17jN\x0373\x15\x0e\x03\a#\x01\x89\x16//*\x10\xdb\x10?MQ#y\x04\xf4\x1dLQQ\"\x15\x1bQXS\x1c\x00\x00\x00\x00\x01\x00\xae\xfe\x14\x04\x12\x04J\x00\x1d\x007@\"\r\tG\nU\x1f\x10\x1f \x1f`\x1fp\x1f\x80\x1f\x05\x14\x1dG\x1cT\x1e\x1a\x1b\x03P\x11\x16\v\x15\x1c\t\x0f\x00?3??\xe1?\x01\x10\xf6\xe12]\x10\xf6\xe1210\x01\x14\x1632>\x025\x113\x11#'#\x0e\x01#\"&'\x16\x17\x1e\x01\x15\x11#\x113\x01djoRnC\x1c\xb6\x93\x1b\n0\x90gHj#\x01\x02\x02\x01\xb6\xb6\x01\x87\x82\x824e\x94`\x02:\xfb\xb6\x93ST.*&(#U*\xfe\xc0\x066\x00\x01\x00q\xfe\xfc\x04f\x06\x14\x00\x13\x007@!\x04\x99\x00\x050\x05@\x05P\x05\x04\x06\x05\x05\r\x01\x99\x00\x15\x10\x15\x01\x00\r\x10\r\x02\r\x03\x12\x00\x05\x00\x00/2?\xc1\x01/]]\x10\xd6\xe1\x129/^]\xe110\x01#\x11#\x11#\x11\x06#\".\x0254>\x023!\x04fx\xcfy=U_\x9bm\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x93\x14\".\x1b\x1a/\"\x14\x14\"/\x1a\x1b.\"\x14\x02\xd3&5!\x0f\x0f!5&%4\"\x10\x10\"4\x00\x00\x01\x00#\xfe\x14\x01\x98\x00\x00\x00\x19\x009@\x1f\x14\x13\x13\x15\u007f\x12\x8f\x12\x02\x12\x12\x06\r\x84\x00\x1b\x06\x1a\x12\x8c\x15@\t\x0eH\x15\x15\x13\n\x8d\x03\x00/\xe1/9/+\xe1\x01\x10\xc6\x10\xd6\xe1\x119/]33\x11310\x01\x14\x06#\"&'5\x1e\x0132654.\x02'73\a\x1e\x03\x01\x98\x8d\x96\x16-\x0f\x0f1\x10GP\x1a.?%Zy9\":+\x19\xfe\xe1al\x06\x03l\x03\x03+1\x18#\x1a\x13\t\xb0s\b\x1a):\x00\x00\x01\x00?\x02J\x01\xba\x05\xb6\x00\x0e\x004@!O\x10\u007f\x10\x02\x10@\x06\nH\x0e\x0e\x02\xe1\x00\u007f\x03\x8f\x03\x02 \x030\x03\x02\x03\x02\xdd\r\t\xe5\x00\xdc\x00?\xe1\xcd?\x01/]]3\xe13/+]10\x013\x11#\x114>\x027\x0e\x01\x0f\x01'\x013\x87\x91\x01\x03\x03\x01\x0e&\x16^J\x05\xb6\xfc\x94\x02\x04\x19<<8\x16\x11(\x11I`\x00\x00\x00\x00\x02\x00B\x03\x10\x02\x8b\x05\xc7\x00\x13\x00\x1f\x00.\xb2\x1a\xe0\x00\xb8\xff\xc0@\x14\t\x0fH\x00!\x0f!\x01\x14\xe0\n \x17\xe4\x05\xc0\x1d\xe4\x0f\xde\x00?\xe1\x1a\xdc\xe1\x01\x10\xd6\xe1]\x10\xd6+\xe110\x01\x14\x0e\x02#\".\x0254>\x0232\x1e\x02\x05\x14\x1632654&#\"\x06\x02\x8b)MmD?jN+)LmD>kN,\xfe:KVUKKUVK\x04mS\x82Y//Y\x82SS\x81X..X\x81Swyywxss\x00\x00\x02\x00T\x00s\x03\x96\x03\xc7\x00\x06\x00\r\x00V@/\x0f\x0f\x9f\x0f\xaf\x0f\x03\a\xeb\x04\x02\n\x02\n\x02\x03\v\t\x0e\x00\xeb\x9f\x03\x01\x10\x03 \x03@\x03\x03\x03\r\a\n\x06\x00\x03\n\x03\n\x03\x01\f\x05\b\x01\x00/3/3\x1299=//\x1133\x1133\x01\x18/]]\xe1\x10\xc62\x1199//\x113\xe1]10\t\x01'\x13\x037\x01\x05\x01'\x13\x037\x01\x03\x96\xfe\xcat\xed\xedt\x016\xfeh\xfe\xcbu\xee\xeeu\x015\x02\x0e\xfeeN\x01\\\x01\\N\xfeb\x1b\xfeeN\x01\\\x01\\N\xfeb\x00\xff\xff\x00?\x00\x00\x05\x8b\x05\xb6\x10&\x00{\x00\x00\x10'\x00\xd1\x02J\x00\x00\x11\a\x00\xd2\x02\xfc\xfd\xb7\x000@\x1d\x03\x02\x16\x18\x03\x02\xbf\x16\x01\x8f\x16\x01?\x16\x01\x16\x01@\x11\x01\x00\x11\x01\x11\x00@\x00\x01\x00\x11]5\x11]]5\x11]]]55\x00?55\x00\x00\xff\xff\x00,\x00\x00\x05\xa0\x05\xb6\x10&\x00{\xed\x00\x10'\x00\xd1\x025\x00\x00\x11\a\x00t\x033\xfd\xb7\x00(@\x18\x02\x14\x18\x02\x00\x14\x01\x14\x01\xb0\x11\x01@\x11\x01\x11\x00p\x00\x01@\x00\x01\x00\x11]]5\x11]]5\x11]5\x00?5\x00\x00\xff\xff\x00\x1f\x00\x00\x05\xce\x05\xc9\x10&\x00u\x00\x00\x10'\x00\xd1\x02\xa8\x00\x00\x11\a\x00\xd2\x03?\xfd\xb7\x00<@'\x03\x028\x18\x03\x02p8\x01P8\x018\x01\xb43\x01\xa43\x01\x843\x01d3\x01P3\x0103\x01 3\x013\x0fL\x01]\x11]]]]]]]5\x11]]55\x00?55\x00\x00\x00\x02\x00D\xfew\x03D\x04^\x00'\x00;\x00D@\x122\x9a(('F\x00\x00\v\x14=\x0f=\x01\b\vF\x1c\xb8\xff\xc0@\x10\x0f\x1bH\x1c\v\x17''-\x9b7\x10\x13\x10Q\x17\x00/\xe13?\xe52/\x129\x01/+\xe1^]\x10\xce\x119/\xe13/\xe110\x01\x15\x14\x0e\x02\a\x0e\x03\x15\x14\x1e\x023267\x17\x0e\x01#\".\x0254>\x027>\x03=\x01\x13\x14\x0e\x02#\".\x0254>\x0232\x1e\x02\x02P\x10'A20D+\x15\x1e9U7T\x96E@R\xbca]\x95g8\x1b5Q64B&\x0e\xba\x13#.\x1b\x1a.#\x14\x14#.\x1a\x1b.#\x13\x02\xa4%:[QL*)CEO50O9\x1f3#\x92*:3`\x8aXDhZT/-C>C+\x13\x01/&5!\x0f\x0f!5&%4\"\x10\x10\"4\xff\xff\x00\x00\x00\x00\x04\xdd\as\x12&\x00$\x00\x00\x11\a\x00C\xff\xbd\x01R\x00\x15\xb4\x02\x15\x05&\x02\xb8\xff\x9c\xb4\x1b\x15\x04\a%\x01+5\x00+5\x00\xff\xff\x00\x00\x00\x00\x04\xdd\as\x12&\x00$\x00\x00\x11\a\x00v\x00\x8d\x01R\x00\x13@\v\x02!\x05&\x02l\x15\x1b\x04\a%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\x00\x00\x00\x04\xdd\as\x12&\x00$\x00\x00\x11\a\x00\xc3\x00\x1f\x01R\x00\x15\xb4\x02\x15\x05&\x02\xb8\xff\xff\xb4\x1d\x15\x04\a%\x01+5\x00+5\x00\xff\xff\x00\x00\x00\x00\x04\xdd\a5\x12&\x00$\x00\x00\x11\a\x00\xc5\x00\x06\x01R\x00\x13@\v\x02\x1d\x05&\x02\x01\x1e,\x04\a%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\x00\x00\x00\x04\xdd\a+\x12&\x00$\x00\x00\x11\a\x00j\x00!\x01R\x00\x17@\r\x03\x02\x1e\x05&\x03\x02\x01\x15)\x04\a%\x01+55\x00+55\x00\x00\x00\xff\xff\x00\x00\x00\x00\x04\xdd\a\x04\x12&\x00$\x00\x00\x11\x06\x00\xc4\x1f}\x001@ \x03\x02\xef\x1a\x01\xdf\x1a\x01P\x1a\x01@\x1a\x01 \x1a\x01\x10\x1a\x01\x00\x1a\x01\x1a\x03\x02\x00\x1f\x15\x04\a%\x01+55\x00\x11]]]]]]]55\x00\x00\x00\x00\x02\xff\xfe\x00\x00\x06V\x05\xb6\x00\x0f\x00\x13\x00\x84@*\x06\x13\n\x0eZ\x01\x11\x01\x10\x03\x04\x13\xa9\x13\x01$\x134\x13T\x13\x03\x10\x01\x01\x14\f\x01\x13\x01\f\f\x01\x13\x03\x05\b\x00g\x15\x04\x05\xb8\xff\xf0@ \x05\t\x13_\x06\x03_\x10\r_\nO\n\x01\x0f\n\xaf\n\x02\b\x10\n\x10\n\x06\x03\x04\x0e_\x05\x01\x12\x00?3\xe1/?99//^]q\x10\xe1\x10\xe1\x10\xe12\x01/83\x10\xe62\x11\x179///]]]]}\x87\xc4\xc4\x11\x013\x10\xe12\x11310)\x01\x11!\x03#\x01!\x15!\x11!\x15!\x11!\x01!\x11#\x06V\xfd\b\xfe%˺\x02\x8f\x03\xc9\xfd\xc3\x02\x16\xfd\xea\x02=\xfbu\x01\x93l\x01\xc5\xfe;\x05\xb6\xa4\xfe<\xa2\xfd\xf8\x01\xc6\x02\xa8\x00\x00\x00\xff\xff\x00}\xfe\x14\x04\x98\x05\xcb\x12&\x00&\x00\x00\x11\a\x00z\x01\xfc\x00\x00\x00\v\xb6\x01O*$\x18 %\x01+5\x00\x00\x00\xff\xff\x00\xc7\x00\x00\x03\xbe\as\x12&\x00(\x00\x00\x11\a\x00C\xff\xb7\x01R\x00\x15\xb4\x01\f\x05&\x01\xb8\xff´\x12\f\x01\x00%\x01+5\x00+5\x00\xff\xff\x00\xc7\x00\x00\x03\xbe\as\x12&\x00(\x00\x00\x11\a\x00v\x00?\x01R\x00\x13@\v\x01\x18\x05&\x01J\f\x12\x01\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\xc7\x00\x00\x03\xbe\as\x12&\x00(\x00\x00\x11\a\x00\xc3\xff\xf1\x01R\x00\x15\xb4\x01\f\x05&\x01\xb8\xff\xfd\xb4\x14\f\x01\x00%\x01+5\x00+5\x00\xff\xff\x00\xc7\x00\x00\x03\xbe\a+\x12&\x00(\x00\x00\x11\a\x00j\xff\xf5\x01R\x00\x17@\r\x02\x01\x15\x05&\x02\x01\x01\f \x01\x00%\x01+55\x00+55\x00\x00\x00\xff\xff\x00>\x00\x00\x02d\as\x12&\x00,\x00\x00\x11\a\x00C\xfe\xb5\x01R\x00\x15\xb4\x01\f\x05&\x01\xb8\xff\xa8\xb4\x12\f\x01\x00%\x01+5\x00+5\x00\xff\xff\x00R\x00\x00\x02\x8a\as\x12&\x00,\x00\x00\x11\a\x00v\xffx\x01R\x00\x13@\v\x01\x18\x05&\x01j\f\x12\x01\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\x11\x00\x00\x02\xa9\as\x12&\x00,\x00\x00\x11\a\x00\xc3\xff\x0f\x01R\x00\x13@\v\x01\f\x05&\x01\x02\x14\f\x01\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00@\x00\x00\x02w\a+\x12&\x00,\x00\x00\x11\a\x00j\xff\r\x01R\x00\x17@\r\x02\x01\x15\x05&\x02\x01\x00\f \x01\x00%\x01+55\x00+55\x00\x00\x00\x00\x02\x00/\x00\x00\x04\xfc\x05\xb6\x00\x10\x00\x1f\x00]@:\x1a\x1a\x0e\x11[\bg! !\x01\x18\x1cZ\x0e\x10\x10\x01\x0ed \x1b\x10_\x18\x0f\x00\x01\x0f\x00?\x00o\x00\xaf\x00\xdf\x00\xff\x00\x06\b\x00@\x1a\x1dH\x00\x00\x02\x1c`\x0e\x12\x17`\x02\x03\x00?\xe1?\xe1\x119/+^]q3\xe12\x01\x10\xe622/\x10\xe12]\x10\xf6\xe1\x119/10\x133\x11!2\x1e\x01\x12\x15\x14\x02\x06\x04#!\x11#%4.\x02+\x01\x11!\x15!\x113 \x00/\x98\x01\x97\x99\xf8\xae_`\xb6\xfe\xf7\xa8\xfe\x92\x98\x04\bB~\xb8u\xc9\x01P\xfe\xb0\xa2\x01\b\x01\f\x03%\x02\x91\\\xb5\xfe\xf4\xb0\xb9\xfe\xe9\xbb^\x02\x83`\x92ՊC\xfe\x0e\xa2\xfe\x1d\x01$\xff\xff\x00\xc7\x00\x00\x05\x0e\a5\x12&\x001\x00\x00\x11\a\x00\xc5\x00\x8b\x01R\x00\x13@\v\x01 \x05&\x01\n!/\n\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00}\xff\xec\x05q\as\x12&\x002\x00\x00\x11\a\x00C\x00T\x01R\x00\x15\xb4\x02(\x05&\x02\xb8\xff\xab\xb4.(\n\x00%\x01+5\x00+5\x00\xff\xff\x00}\xff\xec\x05q\as\x12&\x002\x00\x00\x11\a\x00v\x01\x02\x01R\x00\x13@\v\x024\x05&\x02X(.\n\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00}\xff\xec\x05q\as\x12&\x002\x00\x00\x11\a\x00\xc3\x00\xae\x01R\x00\x13@\v\x02(\x05&\x02\x050(\n\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00}\xff\xec\x05q\a5\x12&\x002\x00\x00\x11\a\x00\xc5\x00}\x01R\x00\x15\xb4\x020\x05&\x02\xb8\xff\xf0\xb41?\n\x00%\x01+5\x00+5\x00\xff\xff\x00}\xff\xec\x05q\a+\x12&\x002\x00\x00\x11\a\x00j\x00\xaa\x01R\x00\x17@\r\x03\x021\x05&\x03\x02\x01(<\n\x00%\x01+55\x00+55\x00\x00\x00\x00\x01\x00\x8d\x01-\x03\xdd\x04{\x00\v\x00\x87\xb9\x00\x06\xff\xf0\xb3\x14\x17H\x06\xb8\xff\xe0@\x18\x0f\x12H\x00\x10\x14\x17H\x00 \x0f\x12H\t\x10\x14\x17H\t \x0f\x12H\x03\xb8\xff\xf0\xb3\x14\x17H\x03\xb8\xff\xe0@0\x0f\x12H@\r\x01\a\x05\x05\x03\v\x01\x01P\x03\x01\x03\b\n\n\x04\x02\x02 \x00\x01\x00\x00 \x00P\x00p\x00\x80\x00\xa0\x00\xc0\x00\xd0\x00\xf0\x00\t\x06\x00\xb3\x00\x19?^]q2\x1132\x113\x01/]3\x113\x113\x113]10\x00++++\x01++++\t\x017\t\x01\x17\t\x01\a\t\x01'\x01\xcb\xfe\xc2i\x01=\x01Bh\xfe\xbf\x01?f\xfe\xbe\xfe\xc3g\x02\xd3\x01?i\xfe\xc2\x01>g\xfe\xbf\xfe\xc0f\x01=\xfe\xc5g\x00\x00\x00\x00\x03\x00}\xff\xb4\x05q\x05\xfc\x00\x1a\x00&\x001\x00\\@:)\x1f*\x1e\x04\x1b'[\x01\x19\v\x0e\x04\x11\x04g3\xc03\x01\xbf3\x01p3\x01/3_3\x02\x1b[\x11f2\x1f)\x1e*\x04-\"_\x19\x01\x0e\v\x04\t\x1a\x16\x04-_\f\t\x13\x00?3\xe1?3\x12\x179\xe1\x11\x179\x01\x10\xf6\xe1]]]]\x10\xf6\x11\x179\xe1\x11\x17910\x01\a\x16\x12\x15\x14\x02\x0e\x01#\"'\a'7&\x0254\x12>\x0132\x16\x177\x01\x14\x16\x17\x01.\x01#\"\x0e\x02\x05\x10'\x01\x1e\x0132>\x02\x05\x14\\[^Q\xa0훽\x85N\x89Za[L\x9e\xf0\xa3^\xa1BP\xfc\xb7.0\x02C0rGr\xa6l4\x03jX\xfd\xbe/rEr\xa5k2\x05\xae\x95c\xfe\u07b7\xa9\xfe\xea\xc6lG\u007fN\x91d\x01*\xbe\xaa\x01\x15\xc4k*&\u007f\xfc\xe1\x83\xd1N\x03\xb1\x1d Q\x97ڊ\x01\x01\x97\xfcT\x1c\x1eQ\x99\xdb\x00\x00\xff\xff\x00\xb8\xff\xec\x04\xdd\as\x12&\x008\x00\x00\x11\a\x00C\x00=\x01R\x00\x15\xb4\x01\x18\x05&\x01\xb8\xff\xc0\xb4\x1e\x18\v\x00%\x01+5\x00+5\x00\xff\xff\x00\xb8\xff\xec\x04\xdd\as\x12&\x008\x00\x00\x11\a\x00v\x00\xc5\x01R\x00\x13@\v\x01$\x05&\x01H\x18\x1e\v\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\xb8\xff\xec\x04\xdd\as\x12&\x008\x00\x00\x11\a\x00\xc3\x00y\x01R\x00\x15\xb4\x01\x18\x05&\x01\xb8\xff\xfd\xb4 \x18\v\x00%\x01+5\x00+5\x00\xff\xff\x00\xb8\xff\xec\x04\xdd\a+\x12&\x008\x00\x00\x11\a\x00j\x00}\x01R\x00\x17@\r\x02\x01!\x05&\x02\x01\x01\x18,\v\x00%\x01+55\x00+55\x00\x00\x00\xff\xff\x00\x00\x00\x00\x047\as\x12&\x00<\x00\x00\x11\a\x00v\x001\x01R\x00\x13@\v\x01\x15\x05&\x01c\t\x0f\a\x02%\x01+5\x00+5\x00\x00\x00\x00\x02\x00\xc7\x00\x00\x043\x05\xb6\x00\x10\x00\x1b\x00<@!\x17[\x00g\x1d\x9f\x1d\x01\x10\x1d\x01\x11\v\aZ\bd\x1c\x11`\x06\x1b`\v\x06\v\x06\v\a\t\x03\a\x12\x00??\x1299//\x10\xe1\x10\xe1\x01\x10\xf6\xe122]]\x10\xf6\xe110\x01\x14\x0e\x02+\x01\x11#\x113\x1532\x1e\x02\x0132>\x0254&+\x01\x0437~Ϙ\x96\xba\xba\xb0\x86\xc2~<\xfdN\x81]\x8b[.\xa4\xae\xa0\x03\x0e[\xa8\x81M\xfe\xc3\x05\xb6\xfc9m\xa0\xfeg GqQ\x8f\x88\x00\x00\x01\x00\xae\xff\xec\x04u\x06\x1f\x00K\x00m@H\aF.5G\x00\x0f\x19\x1f\x19/\x19\x03\x19@\r\x13H_.o.\x02\x0f\x00\x1f\x00/\x00\x03\b\x19.\x00\x00.\x19\x03A$G\x11WM\x10M M\xc0M\x03@GATL$\a5\x03\x16:PG\x01A\x15\x1fP\x1a\x16\x16\x00?3\xe1??\xe1\x12\x179\x01\x10\xf6\xe1]\x10\xf6\xe1\x12\x179///^]]+]\x10\xe1\x10\xe110\x01\x14\x0e\x04\x15\x14\x1e\x02\x17\x1e\x03\x15\x14\x0e\x02#\"&'5\x1e\x0332>\x0254.\x02'.\x0354>\x0454.\x02#\"\x0e\x02\x15\x11#\x114>\x0232\x1e\x02\x03\xf2+?K?+\x0e'F98X=!8e\x8dUa\x8b5\x1aAHL%8Q4\x18\x11+H8?U5\x16)>H>)!W~Q'#\"\xa6\x10\x1f\x18\x0f\x19-@($;8:#(DCF*6O?6:C,*>)\x13\x130SA\xfbN\x04\xb0h\x8dU%&Lt\x00\x00\xff\xff\x00^\xff\xec\x03\x9c\x06!\x12&\x00D\x00\x00\x11\x06\x00C\x94\x00\x00\x15\xb4\x023\x11&\x02\xb8\xff\xe5\xb493\f\"%\x01+5\x00+5\x00\x00\x00\xff\xff\x00^\xff\xec\x03\x9c\x06!\x12&\x00D\x00\x00\x11\x06\x00v5\x00\x00\x13@\v\x02?\x11&\x02\x8539\f\"%\x01+5\x00+5\x00\xff\xff\x00^\xff\xec\x03\x9c\x06!\x12&\x00D\x00\x00\x11\x06\x00\xc3\xe2\x00\x00\x13@\v\x023\x11&\x023;3\f\"%\x01+5\x00+5\x00\xff\xff\x00^\xff\xec\x03\x9c\x05\xe3\x12&\x00D\x00\x00\x11\x06\x00Ž\x00\x00\x13@\v\x02;\x11&\x02)\x0132\x16\x17>\x0132\x1e\x02\x1d\x01!\x1e\x0132>\x027\x15\x0e\x03#\"&'\x0e\x03#\".\x027\x14\x1632>\x02=\x01\a\x0e\x03\x01\"\x06\a!4.\x02^\xe7\xec\xb8\x1d7Q4S\x8fB@J\xb6d\x83\xa6+3\xa6ga\x9al9\xfd`\x05\x93\x931UNJ%'KOU1\x8a\xca>\"L_tJG{Z4\xbdaO=hL+\x8fZzI \x03\x85n\u007f\v\x01\xd7\x1a7T\x013\xa4\xb0\b\aECZ7\x180\"\x89(8U]U]G\x81\xb5nq\xc1\xb6\n\x13\x1d\x12\xa2\x13\x1c\x12\brs6U;\x1f'Q{R\\V&MuOc\a\x04 9Q\x02c\x9c\x95DqP,\x00\xff\xff\x00q\xfe\x14\x03o\x04^\x12&\x00F\x00\x00\x11\a\x00z\x01B\x00\x00\x00\v\xb6\x01/& \x05\r%\x01+5\x00\x00\x00\xff\xff\x00q\xff\xec\x03\xe1\x06!\x12&\x00H\x00\x00\x11\x06\x00C\x94\x00\x00\x15\xb4\x02(\x11&\x02\xb8\xff\xb9\xb4.(\x05\x0f%\x01+5\x00+5\x00\x00\x00\xff\xff\x00q\xff\xec\x03\xe1\x06!\x12&\x00H\x00\x00\x11\x06\x00vR\x00\x00\x13@\v\x024\x11&\x02v(.\x05\x0f%\x01+5\x00+5\x00\xff\xff\x00q\xff\xec\x03\xe1\x06!\x12&\x00H\x00\x00\x11\x06\x00\xc3\xde\x00\x00\x13@\v\x02(\x11&\x02\x030(\x05\x0f%\x01+5\x00+5\x00\xff\xff\x00q\xff\xec\x03\xe1\x05\xd9\x12&\x00H\x00\x00\x11\x06\x00j\xda\x00\x00\x17@\r\x03\x021\x11&\x03\x02\x00(<\x05\x0f%\x01+55\x00+55\x00\xff\xff\xff\xde\x00\x00\x01g\x06!\x12&\x00\xc2\x00\x00\x11\a\x00C\xfeU\x00\x00\x00\x15\xb4\x01\x04\x11&\x01\xb8\xff\x9a\xb4\n\x04\x01\x00%\x01+5\x00+5\x00\xff\xff\x00\xae\x00\x00\x02B\x06!\x12&\x00\xc2\x00\x00\x11\a\x00v\xff0\x00\x00\x00\x13@\v\x01\x10\x11&\x01t\x04\n\x01\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\xff\xbd\x00\x00\x02U\x06!\x12&\x00\xc2\x00\x00\x11\a\x00\xc3\xfe\xbb\x00\x00\x00\x13@\v\x01\x04\x11&\x01\x00\f\x04\x01\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\xff\xee\x00\x00\x02%\x05\xd9\x12&\x00\xc2\x00\x00\x11\a\x00j\xfe\xbb\x00\x00\x00\x17@\r\x02\x01\r\x11&\x02\x01\x00\x04\x18\x01\x00%\x01+55\x00+55\x00\x00\x00\x00\x02\x00o\xff\xec\x04-\x06#\x00'\x009\x00t@F\x12(H\x00# \x16\x19\x04\x1c\"\x18\x1c\"\"\x1c\x18\x03\n\x00W;@;\xd0;\xe0;\x03\x0f;\x01\x062H\nV: \x19#\x16\x04\x17!!\x1d-P\x0f\x12\x0f\xaf\x0f\xbf\x0f\x020\x0f\x01\x17\x0f\x17\x0f\x1d\x017P\x05\x16\x00?\xe1?99//]]\x113\x10\xe1\x113\x11\x12\x179\x01\x10\xf6\xe1^]]\x10\xe6\x11\x179///\x11\x12\x179\x10\xe1210\x01\x14\x0e\x02#\".\x0254>\x0232\x16\x177.\x01'\x05'7.\x01'7\x1e\x01\x177\x17\a\x1e\x03\a4.\x02#\"\x0e\x02\x15\x14\x1e\x02326\x04-C}\xb2oh\xaf\u007fG?v\xa8if\x9a+\b\x1fxZ\xff\x00J\xd9(U/FAz;\xe3J\xc3CoO,\xbc\"FnKMmF!!GmL\x9a\x87\x02=\x8eܘOB\u007f\xb9ww\xb8~A;<\x04v\xc0Q\x99r\x83\x1c7\x1a{ H,\x8aquA\x9c\xbbݰ8kR2.X\x83UL}Z1\xc7\x00\xff\xff\x00\xae\x00\x00\x04\x12\x05\xe3\x12&\x00Q\x00\x00\x11\x06\x00\xc5\xf9\x00\x00\x13@\v\x01!\x11&\x01\x02\"0\v\x17%\x01+5\x00+5\x00\xff\xff\x00q\xff\xec\x04-\x06!\x12&\x00R\x00\x00\x11\x06\x00C\xd8\x00\x00\x15\xb4\x02 \x11&\x02\xb8\xff״& \n\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00q\xff\xec\x04-\x06!\x12&\x00R\x00\x00\x11\x06\x00vP\x00\x00\x13@\v\x02,\x11&\x02N &\n\x00%\x01+5\x00+5\x00\xff\xff\x00q\xff\xec\x04-\x06!\x12&\x00R\x00\x00\x11\x06\x00\xc3\xfb\x00\x00\x15\xb4\x02 \x11&\x02\xb8\xff\xfa\xb4( \n\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00q\xff\xec\x04-\x05\xe3\x12&\x00R\x00\x00\x11\x06\x00\xc5\xe2\x00\x00\x15\xb4\x02(\x11&\x02\xb8\xff\xfd\xb4)7\n\x00%\x01+5\x00+5\x00\x00\x00\xff\xff\x00q\xff\xec\x04-\x05\xd9\x12&\x00R\x00\x00\x11\x06\x00j\xf9\x00\x00\x19\xb6\x03\x02)\x11&\x03\x02\xb8\xff\xf9\xb4 4\n\x00%\x01+55\x00+55\x00\x00\x00\x00\x03\x00f\x00\xf8\x04\x02\x04\xac\x00\x03\x00\x17\x00+\x00`@\x150-\x01\"\xaa\x18\x18\x0e\xaaV\x03f\x03\x02(\x038\x03\x02\x03\x00\xb8\xff\xf0@(\t\rH\x00\x04'\xad\x10\x1d\x01\x0f\x1d\x01\x1d\x1d\x01\t\xad\x00\x13\x10\x13 \x13`\x13\xb0\x13\xc0\x13\xd0\x13\a\a\x13\x13\x00\xad\x01\xb3\x00?\xe13/^]\xe1\x113/]q\xe1\x01/3+3]]\xe13/\xe1]10\x135!\x15\x014>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x114>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02f\x03\x9c\xfd\xbf\x12\x1f)\x18\x17* \x12\x12 *\x17\x18)\x1f\x12\x12\x1f)\x18\x17* \x12\x12 *\x17\x18)\x1f\x12\x02\x87\x96\x96\xfe\xee#/\x1e\r\r\x1e/#!/\x1f\x0e\x0e\x1f/\x02\xdb#/\x1e\r\r\x1e/#!/\x1f\x0e\x0e\x1f/\x00\x00\x00\x00\x03\x00s\xff\xb4\x04/\x04\x91\x00\x1a\x00$\x00-\x00\\@;'\x1f(\x1e\x04\x1b%H\x17\x18\x16\x15\a\b\n\t\b\r\x00W/@/\xd0/\xe0/\x03\x0f/\x01\x06\x1bH\rV.(\x1e'\x1f\x04+\"P\a\n\x18\x15\x04\x05\x16\x12\x10+P\b\x05\x16\x00?\xc6\xe1?\xc6\x12\x179\xe1\x11\x179\x01\x10\xf6\xe1^]]\x10\xf6\x11\x179\xe1\x11\x17910\x01\x14\x0e\x02#\"'\a'7.\x0154>\x0232\x16\x177\x17\a\x1e\x01\x05\x14\x16\x17\x01.\x01#\"\x06\x054'\x01\x1e\x01326\x04/C}\xb2o}bD\x83P?FC|\xb3o?q1D\x83P>E\xfd\x00\x13\x16\x01\x8d\x1dK-\x9a\x87\x02D'\xfer\x1fH-\x9a\x87\x02'\x89ՑL5mJ\x83HՉ\x88ӑK\x1d\x1clI\x81IцT\x833\x02\x87\x11\x12\xcfџc\xfd{\x11\x10\xd3\x00\x00\x00\xff\xff\x00\xa4\xff\xec\x04\b\x06!\x12&\x00X\x00\x00\x11\x06\x00C\xa3\x00\x00\x15\xb4\x01\x1b\x11&\x01\xb8\xff\x9b\xb4!\x1b\f\x19%\x01+5\x00+5\x00\x00\x00\xff\xff\x00\xa4\xff\xec\x04\b\x06!\x12&\x00X\x00\x00\x11\x06\x00v`\x00\x00\x13@\v\x01'\x11&\x01W\x1b!\f\x19%\x01+5\x00+5\x00\xff\xff\x00\xa4\xff\xec\x04\b\x06!\x12&\x00X\x00\x00\x11\x06\x00\xc3\b\x00\x00\x13@\v\x01\x1b\x11&\x01\x00#\x1b\f\x19%\x01+5\x00+5\x00\xff\xff\x00\xa4\xff\xec\x04\b\x05\xd9\x12&\x00X\x00\x00\x11\x06\x00j\x02\x00\x00\x19\xb6\x02\x01$\x11&\x02\x01\xb8\xff\xfb\xb4\x1b/\f\x19%\x01+55\x00+55\x00\x00\x00\xff\xff\x00\n\xfe\x14\x03\xdf\x06!\x12&\x00\\\x00\x00\x11\x06\x00v\x0e\x00\x00\x13@\v\x01/\x11&\x01g#)\x00\x0f%\x01+5\x00+5\x00\x00\x02\x00\xae\xfe\x14\x04?\x06\x14\x00 \x001\x008@\x1f/H\nW3\x103\x01' \x1f\x15\x1bG\x1cT2\x1d\x00\x1b\x1b,P\x15\x0f\x16!P\x00\x05\x10\x00?3\xe1?3\xe1??\x01\x10\xf6\xe12222]\x10\xf6\xe110\x01>\x0332\x1e\x02\x15\x14\x0e\x02#\".\x02'#\x16\x17\x1e\x01\x15\x11#\x113\x11\a%\"\x0e\x02\a\x15\x14\x1e\x0232654&\x01d\x17:M`<^\x9am<\x0373\x1e\x03\x17\x03\x9ay3l46j3y\x1aDC;\x10\xc0\x10;CE\x19\x04\xd9\"a77a\"\x1b\x1dLQQ\"\"QQL\x1d\x00\x02\x01m\x04\xd9\x031\x06\x87\x00\x13\x00\x1f\x00@@-\x14\x83\x0f\x00?\x00O\x00_\x00\x04\x00\x1a\x830\n\x01\n\x17\x8c\x0f\x0f\x1f\x0f?\x0fO\x0f_\x0f\xaf\x0f\xff\x0f\a\x06\x0f\x1d\x8c\x0f\x05_\x05\x02\x05\x00/]\xe1\xd4^]\xe1\x01/]\xe1\xd4]\xe110\x01\x14\x0e\x02#\".\x0254>\x0232\x1e\x02\a4&#\"\x06\x15\x14\x16326\x031#=T12R; ;R20T>#u?12?981?\x05\xb23Q8\x1d\x1d8O33O8\x1d\x1d7O45<<55<<\x00\x01\x01\x02\x04\xd9\x03\xd1\x05\xe3\x00\x1b\x008@#\x0f\x17/\x17\x02\x17\x00\t \t\x02\a\t\x16\x05\x8f\x0e@\x10\x13H\x0e@\a\vH\x0e\x0e\x13\x8f\t\x0f\x00\x01\x00\x00/]2\xe13/++\xe13\x01/^]\xcc]10\x01\".\x02#\"\x06\a#>\x0332\x1e\x0232673\x0e\x03\x02\xfe(OLF -0\x0eh\x05!5J.*QLE\x1d-.\x0fi\x05!5J\x04\xdb#+#5>\x0373\x0e\x03\a%\x0e\x0e'.4\x19\x89\x0f\x1d\x1a\x16\b\x03\xc1\x166z|{8=\x84\x83|5\x00\x00\x00\x01\x00\x17\x03\xc1\x01P\x05\xb6\x00\f\x00%@\x17_\x0e\x01\x06\a\f\x98\x0f\x01_\x01o\x01\xbf\x01\xcf\x01\x05\x01\x06\x9c\x00\x03\x00?\xe5\x01/]\xe1/3]10\x01\x17\x0e\x03\a#>\x037\x01B\x0e\x0e'/3\x19\x89\x0e\x1d\x1b\x16\b\x05\xb6\x167y}z8<\x84\x84|5\x00\x00\x01\x00?\xfe\xf8\x01y\x00\xee\x00\f\x005\xb9\x00\x0e\xff\xc0@\x14\n\x18H\f\x98\x0f\x01_\x01o\x01\u007f\x01\xcf\x01\x05\x01\x01\x06\a\xb8\xff\xc0\xb7\x10\x15H\a\x06\x9c\x00\xa8\x00?\xe5\x01/+33/]\xe1+10%\x17\x0e\x03\a#>\x037\x01j\x0f\x0e'/3\x19\x8a\x0f\x1d\x1b\x16\b\xee\x176z|{8=\x84\x83}5\x00\x00\x00\x02\x00\x17\x03\xc1\x02\xd1\x05\xb6\x00\f\x00\x19\x00b@H\xbf\x1b\x01\x90\x1b\x01\x0f\x1b_\x1bo\x1b\x03\x13\x0f\x14_\x14o\x14\u007f\x14\xbf\x14\xcf\x14\xdf\x14\a\x14\x14\x19\x98\x0e\f\x98\x00\x01P\x01`\x01p\x01\xb0\x01\xc0\x01\xd0\x01\a\x01\x01\x06\x0f\a_\ao\a\xbf\a\xcf\a\x05\a\x19\f\x9c\x13\x06\x03\x00?3\xe52\x01/]33/]\xe1/\xe13/]3]]]10\x01'>\x0373\x0e\x03\a!'>\x0373\x0e\x03\a\x01\xa6\x0e\x0e'.4\x19\x89\x0f\x1d\x1a\x16\b\xfd\xb8\x0e\x0e'.4\x19\x89\x0f\x1d\x1a\x16\b\x03\xc1\x166z|{8=\x84\x83|5\x166z|{8=\x84\x83|5\x00\x02\x00\x17\x03\xc1\x02\xd1\x05\xb6\x00\f\x00\x19\x00b@H\xbf\x1b\x01\x90\x1b\x01\x0f\x1b_\x1bo\x1b\x03\x13\x00\x14P\x14`\x14p\x14\xb0\x14\xc0\x14\xd0\x14\a\x14\x14\x19\x98\x0f\x0e_\x0eo\x0e\xbf\x0e\xcf\x0e\x05\x0e\f\x98\x0f\x01_\x01o\x01\u007f\x01\xbf\x01\xcf\x01\xdf\x01\a\x01\x01\x06\a\x13\x06\x9c\r\x00\x03\x00?2\xe52\x01/33/]\xe1/]\xe13/]3]]]10\x01\x17\x0e\x03\a#>\x037!\x17\x0e\x03\a#>\x037\x01B\x0e\x0e'/3\x19\x89\x0e\x1d\x1b\x16\b\x02H\x0e\x0e'/3\x19\x89\x0e\x1d\x1b\x16\b\x05\xb6\x167y}z8<\x84\x84|5\x167y}z8<\x84\x84|5\x00\x02\x00?\xfe\xf8\x02\xfa\x00\xee\x00\f\x00\x19\x00~@Q\xd0\x1b\xe0\x1b\xf0\x1b\x03\xa4\x1b\xb4\x1b\xc4\x1b\x03\x90\x1b\x01\x02 \x1b0\x1b@\x1b`\x1bp\x1b\x80\x1b\x06\x13\x00\x14P\x14`\x14p\x14\xc0\x14\xd0\x14\x06\x14\x14\x19\x98\x90\x0e\xe0\x0e\xf0\x0e\x03\x0f\x0e_\x0e\x02\x0e\f\x98\x0f\x01_\x01o\x01\u007f\x01\xcf\x01\xdf\x01\x06\x01\x01\x06\a\xb8\xff\xc0@\n\x10\x18H\a\x13\x06\x9c\r\x00\xa8\x00?2\xe52\x01/+33/]\xe1/]]\xe13/]3]_]]]10%\x17\x0e\x03\a#>\x037!\x17\x0e\x03\a#>\x037\x01j\x0f\x0e'/3\x19\x8a\x0f\x1d\x1b\x16\b\x02H\x0e\x0e'/3\x19\x89\x0e\x1d\x1b\x16\b\xee\x176z|{8=\x84\x83}5\x176z|{8=\x84\x83}5\x00\x00\x01\x00\x96\x01\xe5\x02m\x03\xf2\x00\x13\x00F@$/\x15_\x15o\x15\u007f\x15\xcf\x15\xef\x15\xff\x15\a\x10\x15\x01_\no\n\x9f\n\xaf\n\xdf\n\xef\n\x06\n\xd0\x00\x01\x00\xb8\xff\xc0@\f\a\nH\x00\x1f\x0f\x01\x0f\x10\x05\x01\x05\x00/]\xc5]\x01/+]\xc5]]]10\x134>\x0232\x1e\x02\x15\x14\x0e\x02#\".\x02\x96$?V21V@%%@V12V?$\x02\xecGd?\x1c\x1c?dGFd?\x1e\x1e?d\x00\x00\x00\x01\x00R\x00s\x01\xfc\x03\xc7\x00\x06\x00<\xb1\x04\x02\xb8\xff\xc0@\x1f\t\fH\x02\b?\b\x9f\b\xaf\b\xdf\b\xef\b\xff\b\x06\x06\xeb\x9f\x03\x01\x03\x06\x00\x03\x03\x01\x05\x01\x00//\x129=/33\x01\x18/]\xe1]\x10\xc6+210\x13\x01\x17\x03\x13\a\x01R\x015u\xee\xeeu\xfe\xcb\x02)\x01\x9eN\xfe\xa4\xfe\xa4N\x01\x9b\x00\x00\x00\x01\x00R\x00s\x01\xfc\x03\xc7\x00\x06\x00?@(\x00\xeb\xdf\x03\xef\x03\xff\x03\x03\x10\x03 \x03\x02\x03?\b\x9f\b\xaf\b\xdf\b\xef\b\xff\b\x06\x04?\x02\x01\x02\x06\x00\x03\x03\x01\x05\x01\x00//\x129=/33\x01\x18/]3]/]]\xe110\t\x01'\x13\x037\x01\x01\xfc\xfe\xcbu\xed\xedu\x015\x02\x0e\xfeeN\x01\\\x01\\N\xfeb\x00\x00\x00\x01\xfe\xa0\x00\x00\x02h\x05\xb6\x00\x03\x00\x1d\xb1\x01\x02\xb8\xff\xf0@\t\x02\x03\x00\x10\x00\x01\x12\x00\x03\x00??\x01/82/8310\t\x01#\x01\x02h\xfc՝\x03+\x05\xb6\xfaJ\x05\xb6\x00\x02\x00\f\x02J\x02\x8f\x05\xbc\x00\n\x00\x15\x00F@*\t\x02\xe1\v\a\x03\x03\x17_\x17\x8f\x17\x02\x17@\x06\nH\x15\xe1\x05\x01\x04\xe5\t\x0f\v\x1f\v/\v\x03\b\v\v\x02\x0f\xe5\a\xdc\x02\xdd\x00??\xe1\x129/^]3\xe12\x01/\xe1+]\x129/33\xe1210\x01#\x15#5!5\x013\x113!5467\x0e\x03\x0f\x01\x02\x8f}\x8f\xfe\x89\x01y\x8d}\xfe\xf4\x03\x03\x05\x14\x16\x18\t\x9b\x03\n\xc0\xc0o\x02C\xfd\xcd\xc3*c1\v%*(\x0f\xf0\x00\x00\x00\x00\x01\x00\x00\x00\xd3\x00i\x00\x05\x00S\x00\x04\x00\x02\x00\x10\x00/\x00Z\x00\x00\x02\x1f\x00\xe5\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00F\x00x\x01\x00\x01\xba\x02J\x03\x02\x03&\x03V\x03\x86\x03\xbc\x03\xea\x04 \x048\x04r\x04\x92\x04\xe4\x05\x1e\x05t\x05\xf2\x06F\x06\xae\a\"\aJ\a\xf4\bf\b\xbe\t\"\t^\t\xa0\t\xdc\nP\v\x1a\v\x86\v\xfe\f\\\f\x9c\f\xd6\r$\r\x82\r\xb8\r\xfc\x0e6\x0e\x84\x0e\xa4\x0f\x18\x0fj\x0f\xc4\x10\x12\x10|\x10\xee\x11X\x11\x9a\x11\xd8\x12,\x12\xe2\x13B\x13\x94\x13\xc8\x13\xee\x14\x0e\x142\x14P\x14h\x14\x8e\x15\x02\x15d\x15\xaa\x16\n\x16h\x16\xcc\x17\xa0\x17\xe2\x18\x14\x18`\x18\xb0\x18\xca\x19<\x19z\x19\xc4\x1a&\x1a\x88\x1a\xce\x1b>\x1b\x94\x1b\xd6\x1c.\x1c\xdc\x1dn\x1d\xd8\x1e&\x1e\x80\x1e\xa4\x1f\x00\x1fT\x1fT\x1f\x9c \x00 v!\x0e!\x82!\xb2\"l\"\xb0#Z#\xc4$\x18$F$N%\x1e%6%\x92%\xce&\x1e&\x94&\xba'\x04'B'|'\xc2'\xfa(B(\x92(\xbc(\xe2)\x12)\x88)\xa0)\xb8)\xd0)\xe8*\x02*(*\x92*\xa6*\xbe*\xd6*\xee+\b+ +8+P+j+\xce+\xe6+\xfe,\x16,.,F,`,\xc6-H-`-x-\x90-\xaa-\xc2.\f.\xa8.\xc0.\xd6.\xec/\x02/\x1a/2/\xe2/\xf60\x0e0$0:0R0j0\x820\x9a0\xb41D1Z1r1\x881\xa01\xb81\xd22D2\xbe2\xd62\xec3\x023\x1c323\x983\xb03\xca4\x004P4\x984\xb44\xd04\xfc5(5\\5\xb86\x146~6\xc26\xf67,7J7\x94\x00\x01\x00\x00\x00\x01\x00\x00d\xbc\x83%_\x0f<\xf5\x00\x1f\b\x00\x00\x00\x00\x00\xc8\x17O\xf6\x00\x00\x00\x00\xc8\\\x86Y\xfe\xa0\xfe\x14\a\xae\as\x00\x00\x00\b\x00\x02\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x14\x00\x00\x02'\x00\x93\x037\x00\x85\x05+\x003\x04h\x00{\x06\x9a\x00f\x05\x9e\x00m\x01\xcf\x00\x85\x02h\x00R\x02h\x00=\x04h\x00R\x04h\x00f\x02\x00\x00?\x02\x93\x00R\x02%\x00\x93\x02\xfc\x00\x14\x04h\x00b\x04h\x00\xb2\x04h\x00`\x04h\x00R\x04h\x00\x17\x04h\x00\x83\x04h\x00q\x04h\x00Z\x04h\x00j\x04h\x00j\x02%\x00\x93\x02%\x00?\x04h\x00f\x04h\x00f\x04h\x00f\x03h\x00%\x06\xee\x00m\x04\xdd\x00\x00\x04\xf8\x00\xc7\x04\xd3\x00}\x05y\x00\xc7\x049\x00\xc7\x03\xee\x00\xc7\x05\x85\x00}\x05\x9c\x00\xc7\x02\xb6\x00R\x02+\xffH\x04\xa2\x00\xc7\x03\xee\x00\xc7\x06\xf6\x00\xc7\x05\xd5\x00\xc7\x05\xf0\x00}\x04\x9c\x00\xc7\x05\xee\x00}\x04\xb8\x00\xc7\x04'\x00h\x04'\x00\x14\x05\x96\x00\xb8\x04\x8b\x00\x00\a\x12\x00\x14\x04`\x00\x00\x047\x00\x00\x04P\x00R\x02m\x00\xa4\x02\xfc\x00\x17\x02m\x003\x04B\x00)\x03J\xff\xfc\x04\x9e\x01\x89\x04?\x00^\x04\xb0\x00\xae\x03\xb4\x00q\x04\xb0\x00q\x04H\x00q\x02\xa2\x00\x1d\x04%\x00%\x04\xb6\x00\xae\x02\x12\x00\xa0\x02\x12\xff\xbc\x03\xf8\x00\xae\x02\x12\x00\xae\a+\x00\xae\x04\xb6\x00\xae\x04\x9e\x00q\x04\xb0\x00\xae\x04\xb0\x00q\x031\x00\xae\x03\x9c\x00Z\x02\xb6\x00!\x04\xb6\x00\xa4\x03\xd5\x00\x00\x05\xf8\x00\x14\x04\x00\x00#\x03\xe9\x00\n\x03\x87\x00R\x02\xd5\x00=\x04h\x01\xe9\x02\xd5\x003\x04h\x00f\x02\x14\x00\x00\x02'\x00\x93\x04h\x00\xbc\x04h\x00D\x04h\x00{\x04h\x00\x1d\x04h\x01\xe9\x03\xe3\x00y\x04\x9e\x013\x06\xa8\x00d\x02\xa6\x00D\x03\xe5\x00R\x04h\x00f\x02\x93\x00R\x06\xa8\x00d\x04\x00\xff\xfa\x03m\x00{\x04h\x00f\x02\xa6\x001\x02\xa6\x00\x1f\x04\x9e\x01\x89\x04\xc1\x00\xae\x05=\x00q\x02%\x00\x93\x01\xa4\x00#\x02\xa6\x00?\x02\xcd\x00B\x03\xe5\x00T\x05\xe5\x00?\x05\xe5\x00,\x05\xe5\x00\x1f\x03h\x00D\x04\xdd\x00\x00\x04\xdd\x00\x00\x04\xdd\x00\x00\x04\xdd\x00\x00\x04\xdd\x00\x00\x04\xdd\x00\x00\x06\xd1\xff\xfe\x04\xd3\x00}\x049\x00\xc7\x049\x00\xc7\x049\x00\xc7\x049\x00\xc7\x02\xb6\x00>\x02\xb6\x00R\x02\xb6\x00\x11\x02\xb6\x00@\x05y\x00/\x05\xd5\x00\xc7\x05\xf0\x00}\x05\xf0\x00}\x05\xf0\x00}\x05\xf0\x00}\x05\xf0\x00}\x04h\x00\x8d\x05\xf0\x00}\x05\x96\x00\xb8\x05\x96\x00\xb8\x05\x96\x00\xb8\x05\x96\x00\xb8\x047\x00\x00\x04\x9c\x00\xc7\x04\xd1\x00\xae\x04?\x00^\x04?\x00^\x04?\x00^\x04?\x00^\x04?\x00^\x04?\x00^\x06\xaa\x00^\x03\xb4\x00q\x04H\x00q\x04H\x00q\x04H\x00q\x04H\x00q\x02\x12\xff\xde\x02\x12\x00\xae\x02\x12\xff\xbd\x02\x12\xff\xee\x04\x9e\x00o\x04\xb6\x00\xae\x04\x9e\x00q\x04\x9e\x00q\x04\x9e\x00q\x04\x9e\x00q\x04\x9e\x00q\x04h\x00f\x04\x9e\x00s\x04\xb6\x00\xa4\x04\xb6\x00\xa4\x04\xb6\x00\xa4\x04\xb6\x00\xa4\x03\xe9\x00\n\x04\xb0\x00\xae\x03\xe9\x00\n\x02\x12\x00\xae\x04\x9e\x01\x02\x04\x9e\x01m\x04\x9e\x01\x02\x04\x00\x00R\b\x00\x00R\x01f\x00\x17\x01f\x00\x17\x02\x00\x00?\x02\xe7\x00\x17\x02\xe7\x00\x17\x03\x81\x00?\x03\x02\x00\x96\x02N\x00R\x02N\x00R\x01\n\xfe\xa0\x02\xa6\x00\f\x00\x01\x00\x00\as\xfe\x14\x00\x00\b\x00\xfe\xa0\xfe\xa2\a\xae\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\x00\x03\x04&\x01\x90\x00\x05\x00\b\x05\x9a\x053\x00\x00\x01\x1e\x05\x9a\x053\x00\x00\x03\xd0\x00f\x01\xf2\x00\x00\x02\v\x06\x06\x03\b\x04\x02\x02\x04\xe0\x00\x02\xef@\x00 [\x00\x00\x00(\x00\x00\x00\x001ASC\x00@\x00 D\x06\x1f\xfe\x14\x00\x84\as\x01\xec \x00\x01\x9f\x00\x00\x00\x00\x04J\x05\xb6\x00\x00\x00 \x00\x02\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x14\x00\x03\x00\x01\x00\x00\x00\x14\x00\x04\x00x\x00\x00\x00\x1a\x00\x10\x00\x03\x00\n\x00~\x00\xff\x011\x02\xc6\x02\xda\x02\xdc \x14 \x1a \x1e \" : D\xff\xff\x00\x00\x00 \x00\xa0\x011\x02\xc6\x02\xda\x02\xdc \x13 \x18 \x1c \" 9 D\xff\xff\xff\xe3\xff\xc2\xff\x91\xfd\xfd\xfd\xea\xfd\xe9\xe0\xb3\xe0\xb0\xe0\xaf\xe0\xac\xe0\x96\xe0\x8d\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@EYXUTSRQPONMLKJIHGFEDCBA@?>=<;:9876510/.-,('&%$#\"!\x1f\x18\x14\x11\x10\x0f\x0e\r\v\n\t\b\a\x06\x05\x04\x03\x02\x01\x00,E#F` \xb0&`\xb0\x04&#HH-,E#F#a \xb0&a\xb0\x04&#HH-,E#F`\xb0 a \xb0F`\xb0\x04&#HH-,E#F#a\xb0 ` \xb0&a\xb0 a\xb0\x04&#HH-,E#F`\xb0@a \xb0f`\xb0\x04&#HH-,E#F#a\xb0@` \xb0&a\xb0@a\xb0\x04&#HH-,\x01\x10 <\x00<-, E# \xb0\xcdD# \xb8\x01ZQX# \xb0\x8dD#Y \xb0\xedQX# \xb0MD#Y \xb0\x04&QX# \xb0\rD#Y!!-, E\x18hD \xb0\x01` E\xb0Fvh\x8aE`D-,\x01\xb1\v\nC#Ce\n-,\x00\xb1\n\vC#C\v-,\x00\xb0(#p\xb1\x01(>\x01\xb0(#p\xb1\x02(E:\xb1\x02\x00\b\r-, E\xb0\x03%Ead\xb0PQXED\x1b!!Y-,I\xb0\x0e#D-, E\xb0\x00C`D-,\x01\xb0\x06C\xb0\aCe\n-, i\xb0@a\xb0\x00\x8b \xb1,\xc0\x8a\x8c\xb8\x10\x00b`+\fd#da\\X\xb0\x03aY-,\x8a\x03E\x8a\x8a\x87\xb0\x11+\xb0)#D\xb0)z\xe4\x18-,Ee\xb0,#DE\xb0+#D-,KRXED\x1b!!Y-,KQXED\x1b!!Y-,\x01\xb0\x05%\x10# \x8a\xf5\x00\xb0\x01`#\xed\xec-,\x01\xb0\x05%\x10# \x8a\xf5\x00\xb0\x01a#\xed\xec-,\x01\xb0\x06%\x10\xf5\x00\xed\xec-,F#F`\x8a\x8aF# F\x8a`\x8aa\xb8\xff\x80b# \x10#\x8a\xb1\f\f\x8apE` \xb0\x00PX\xb0\x01a\xb8\xff\xba\x8b\x1b\xb0F\x8cY\xb0\x10`h\x01:-, E\xb0\x03%FRK\xb0\x13Q[X\xb0\x02%F ha\xb0\x03%\xb0\x03%?#!8\x1b!\x11Y-, E\xb0\x03%FPX\xb0\x02%F ha\xb0\x03%\xb0\x03%?#!8\x1b!\x11Y-,\x00\xb0\aC\xb0\x06C\v-,!!\fd#d\x8b\xb8@\x00b-,!\xb0\x80QX\fd#d\x8b\xb8 \x00b\x1b\xb2\x00@/+Y\xb0\x02`-,!\xb0\xc0QX\fd#d\x8b\xb8\x15Ub\x1b\xb2\x00\x80/+Y\xb0\x02`-,\fd#d\x8b\xb8@\x00b`#!-,KSX\x8a\xb0\x04%Id#Ei\xb0@\x8ba\xb0\x80b\xb0 aj\xb0\x0e#D#\x10\xb0\x0e\xf6\x1b!#\x8a\x12\x11 9/Y-,KSX \xb0\x03%Idi \xb0\x05&\xb0\x06%Id#a\xb0\x80b\xb0 aj\xb0\x0e#D\xb0\x04&\x10\xb0\x0e\xf6\x8a\x10\xb0\x0e#D\xb0\x0e\xf6\xb0\x0e#D\xb0\x0e\xed\x1b\x8a\xb0\x04&\x11\x12 9# 9//Y-,E#E`#E`#E`#vh\x18\xb0\x80b -,\xb0H+-, E\xb0\x00TX\xb0@D E\xb0@aD\x1b!!Y-,E\xb10/E#Ea`\xb0\x01`iD-,KQX\xb0/#p\xb0\x14#B\x1b!!Y-,KQX \xb0\x03%EiSXD\x1b!!Y\x1b!!Y-,E\xb0\x14C\xb0\x00`c\xb0\x01`iD-,\xb0/ED-,E# E\x8a`D-,E#E`D-,K#QX\xb9\x003\xff\xe0\xb14 \x1b\xb33\x004\x00YDD-,\xb0\x16CX\xb0\x03&E\x8aXdf\xb0\x1f`\x1bd\xb0 `f X\x1b!\xb0@Y\xb0\x01aY#XeY\xb0)#D#\x10\xb0)\xe0\x1b!!!!!Y-,\xb0\x02CTXKS#KQZX8\x1b!!Y\x1b!!!!Y-,\xb0\x16CX\xb0\x04%Ed\xb0 `f X\x1b!\xb0@Y\xb0\x01a#X\x1beY\xb0)#D\xb0\x05%\xb0\b%\b X\x02\x1b\x03Y\xb0\x04%\x10\xb0\x05% F\xb0\x04%#B<\xb0\x04%\xb0\a%\b\xb0\a%\x10\xb0\x06% F\xb0\x04%\xb0\x01`#B< X\x01\x1b\x00Y\xb0\x04%\x10\xb0\x05%\xb0)\xe0\xb0) EeD\xb0\a%\x10\xb0\x06%\xb0)\xe0\xb0\x05%\xb0\b%\b X\x02\x1b\x03Y\xb0\x05%\xb0\x03%CH\xb0\x04%\xb0\a%\b\xb0\x06%\xb0\x03%\xb0\x01`CH\x1b!Y!!!!!!!-,\x02\xb0\x04% F\xb0\x04%#B\xb0\x05%\b\xb0\x03%EH!!!!-,\x02\xb0\x03% \xb0\x04%\b\xb0\x02%CH!!!-,E# E\x18 \xb0\x00P X#e#Y#h \xb0@PX!\xb0@Y#XeY\x8a`D-,KS#KQZX E\x8a`D\x1b!!Y-,KTX E\x8a`D\x1b!!Y-,KS#KQZX8\x1b!!Y-,\xb0\x00!KTX8\x1b!!Y-,\xb0\x02CTX\xb0F+\x1b!!!!Y-,\xb0\x02CTX\xb0G+\x1b!!!Y-,\xb0\x02CTX\xb0H+\x1b!!!!Y-,\xb0\x02CTX\xb0I+\x1b!!!Y-, \x8a\b#KS\x8aKQZX#8\x1b!!Y-,\x00\xb0\x02%I\xb0\x00SX \xb0@8\x11\x1b!Y-,\x01F#F`#Fa# \x10 F\x8aa\xb8\xff\x80b\x8a\xb1@@\x8apE`h:-, \x8a#Id\x8a#SX<\x1b!Y-,KRX}\x1bzY-,\xb0\x12\x00K\x01KTB-,\xb1\x02\x00B\xb1#\x01\x88Q\xb1@\x01\x88SZX\xb9\x10\x00\x00 \x88TX\xb2\x02\x01\x02C`BY\xb1$\x01\x88QX\xb9 \x00\x00@\x88TX\xb2\x02\x02\x02C`B\xb1$\x01\x88TX\xb2\x02 \x02C`B\x00K\x01KRX\xb2\x02\b\x02C`BY\x1b\xb9@\x00\x00\x80\x88TX\xb2\x02\x04\x02C`BY\xb9@\x00\x00\x80c\xb8\x01\x00\x88TX\xb2\x02\b\x02C`BY\xb9@\x00\x01\x00c\xb8\x02\x00\x88TX\xb2\x02\x10\x02C`BY\xb9@\x00\x02\x00c\xb8\x04\x00\x88TX\xb2\x02@\x02C`BYYYYY-,E\x18h#KQX# E d\xb0@PX|Yh\x8a`YD-,\xb0\x00\x16\xb0\x02%\xb0\x02%\x01\xb0\x01#>\x00\xb0\x02#>\xb1\x01\x02\x06\f\xb0\n#eB\xb0\v#B\x01\xb0\x01#?\x00\xb0\x02#?\xb1\x01\x02\x06\f\xb0\x06#eB\xb0\a#B\xb0\x01\x16\x01-,z\x8a\x10E#\xf5\x18-\x00\x00\x00@\x10\t\xf8\x03\xff\x1f\x8f\xf7\x9f\xf7\x02\u007f\xf3\x01`\xf2\x01\xb8\xff\xe8@+\xeb\f\x10F\xdf3\xddU\xde\xff\xdcU0\xdd\x01\xdd\x01\x03U\xdc\x03\xfa\x1f0\xc2\x01o\xc0\xef\xc0\x02\xfc\xb6\x18\x1f0\xb7\x01`\xb7\x80\xb7\x02\xb8\xff\xc0@8\xb7\x0f\x13F\xe7\xb1\x01\x1f\xaf/\xaf?\xaf\x03O\xaf_\xafo\xaf\x03@\xaf\x0f\x13F\xacQ\x18\x1f\x1f\x9c_\x9c\x02\xe0\x9b\x01\x03+\x9a\x01\x1f\x9a\x01\x90\x9a\xa0\x9a\x02s\x9a\x83\x9a\x02\x05\xb8\xff\xea@\x19\x9a\t\vF\xaf\x97\xbf\x97\x02\x03+\x96\x01\x1f\x96\x01\x9f\x96\xaf\x96\x02|\x96\x01\x05\xb8\xff\xea@\x85\x96\t\vF/\x92?\x92O\x92\x03@\x92\f\x0fF/\x91\x01\x9f\x91\x01\x87\x86\x18\x1f@|P|\x02\x03\x10t t0t\x03\x02t\x01\xf2t\x01\no\x01\xffo\x01\xa9o\x01\x97o\x01uo\x85o\x02Ko\x01\nn\x01\xffn\x01\xa9n\x01\x97n\x01Kn\x01\x06\x1a\x01\x18U\x19\x13\xff\x1f\a\x04\xff\x1f\x06\x03\xff\x1f?g\x01\x1fg/g?g\xffg\x04@fPf\xa0f\xb0f\x04?e\x01\x0fe\xafe\x02\x05\xa0d\xe0d\x02\x03\xb8\xff\xc0@Od\x06\nFa_+\x1f`_G\x1f_P\"\x1f\xf7[\x01\xec[\x01T[\x84[\x02I[\x01;[\x01\xf9Z\x01\xefZ\x01kZ\x01KZ\x01;Z\x01\x06\x133\x12U\x05\x01\x03U\x043\x03U\x1f\x03\x01\x0f\x03?\x03\xaf\x03\x03\x0fW\x1fW/W\x03\x03\xb8\xff\xc0\xb3V\x12\x15F\xb8\xff\xe0\xb3V\a\vF\xb8\xff\xc0\xb3T\x12\x15F\xb8\xff\xc0@mT\x06\vFRP+\x1f?POP_P\x03\xfaH\x01\xefH\x01\x87H\x01eH\x01VH\x01:H\x01\xfaG\x01\xefG\x01\x87G\x01;G\x01\x06\x1c\x1b\xff\x1f\x163\x15U\x11\x01\x0fU\x103\x0fU\x02\x01\x00U\x01G\x00U\xfb\xfa+\x1f\xfa\x1b\x12\x1f\x0f\x0f\x01\x1f\x0f\xcf\x0f\x02\x0f\x0f\xff\x0f\x02\x06o\x00\u007f\x00\xaf\x00\xef\x00\x04\x10\x00\x01\x80\x16\x01\x05\x01\xb8\x01\x90\xb1TS++K\xb8\a\xffRK\xb0\x06P[\xb0\x01\x88\xb0%S\xb0\x01\x88\xb0@QZ\xb0\x06\x88\xb0\x00UZ[X\xb1\x01\x01\x8eY\x85\x8d\x8d\x00B\x1dK\xb02SX\xb0`\x1dYK\xb0dSX\xb0@\x1dYK\xb0\x80SX\xb0\x10\x1d\xb1\x16\x00BYss^stu++++++++\x01_ssssssssss\x00s+\x01++++_s\x00st+++\x01_ssssssssss\x00+++\x01+_s^stsst\x00++++\x01_sssstssssst\x00stt\x01_s+\x00st+s\x01+_sstt_s+_sstt\x00_ss\x01+\x00+st\x01s\x00+st+\x01s\x00s++s++\x01+sss\x00+\x18^\x06\x14\x00\v\x00N\x05\xb6\x00\x17\x00u\x05\xb6\x05\xcd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04J\x00\x14\x00\x8f\x00\x00\xff\xec\x00\x00\x00\x00\xff\xec\x00\x00\x00\x00\xff\xec\x00\x00\xfe\x14\xfe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\xac\x00\xb6\x00\xbc\x00\x00\x00\xd5\x00\x00\x00\x00\x00\x00\x00U\x00\x83\x00\x97\x00\x9f\x00}\x00\xe5\x00\xae\x00\xae\x00q\x00q\x00\x00\x00\x00\x00\xba\x00\xc5\x00\xba\x00\x00\x00\x00\x00\xa4\x00\x9f\x00\x8c\x00\x00\x00\x00\x00\xc7\x00\xc7\x00}\x00}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\x00\xb9\x00\x8a\x00\x00\x00\x00\x00\x9b\x00\xa6\x00\x8f\x00w\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00i\x00n\x00\x90\x00\xb4\x00\xc1\x00\xd5\x00\x00\x00\x00\x00\x00\x00\x00\x00f\x00o\x00x\x00\x96\x00\xc0\x00\xd5\x01G\x00\x00\x00\x00\x00\x00\x00\xfe\x01:\x00\xc5\x00x\x00\xfe\x01\x16\x01\xf6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xee\x00\x00\x00\x96\x00\x88\x00\xae\x00\x96\x00\x89\x01\f\x00\x96\x01\x18\x00\x00\x03\x1d\x00\x94\x02Z\x00\x82\x03\x96\x00\x00\x00\xa8\x00\x8c\x00\x00\x00\x00\x02y\x00\xd9\x00\xb4\x01\n\x00\x00\x01\x83\x00m\x00\u007f\x00\xa0\x00\x00\x00\x00\x00m\x00\x88\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x93\x00\xa0\x00\x00\x00\x82\x00\x89\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\xb6\xfc\x94\x00\x11\xff\xef\x00\x83\x00\x8f\x00\x00\x00\x00\x00m\x00{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbc\x01\xaa\x03T\x00\x00\x00\x00\x00\xbc\x00\xb6\x01\xd7\x01\x95\x00\x00\x00\x96\x01\x00\x00\xae\x05\xb6\xfe\xbc\xfeo\xfe\x83\x00o\x02\xad\x00\x00\x00\a\x00Z\x00\x03\x00\x01\x04\t\x00\x01\x00\x14\x00\x00\x00\x03\x00\x01\x04\t\x00\x02\x00\x0e\x00\x14\x00\x03\x00\x01\x04\t\x00\x03\x00*\x00\"\x00\x03\x00\x01\x04\t\x00\x04\x00\x14\x00\x00\x00\x03\x00\x01\x04\t\x00\x05\x00,\x00L\x00\x03\x00\x01\x04\t\x00\x06\x00\x12\x00x\x00\x03\x00\x01\x04\t\x00\x0e\x00T\x00\x8a\x00D\x00r\x00o\x00i\x00d\x00 \x00S\x00a\x00n\x00s\x00R\x00e\x00g\x00u\x00l\x00a\x00r\x00A\x00s\x00c\x00e\x00n\x00d\x00e\x00r\x00 \x00-\x00 \x00D\x00r\x00o\x00i\x00d\x00 \x00S\x00a\x00n\x00s\x00V\x00e\x00r\x00s\x00i\x00o\x00n\x00 \x001\x00.\x000\x000\x00 \x00b\x00u\x00i\x00l\x00d\x00 \x001\x001\x003\x00D\x00r\x00o\x00i\x00d\x00S\x00a\x00n\x00s\x00h\x00t\x00t\x00p\x00:\x00/\x00/\x00w\x00w\x00w\x00.\x00a\x00p\x00a\x00c\x00h\x00e\x00.\x00o\x00r\x00g\x00/\x00l\x00i\x00c\x00e\x00n\x00s\x00e\x00s\x00/\x00L\x00I\x00C\x00E\x00N\x00S\x00E\x00-\x002\x00.\x000\x00\x02\x00\x00\x00\x00\x00\x00\xfff\x00f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\a\x00\b\x00\t\x00\n\x00\v\x00\f\x00\r\x00\x0e\x00\x0f\x00\x10\x00\x11\x00\x12\x00\x13\x00\x14\x00\x15\x00\x16\x00\x17\x00\x18\x00\x19\x00\x1a\x00\x1b\x00\x1c\x00\x1d\x00\x1e\x00\x1f\x00 \x00!\x00\"\x00#\x00$\x00%\x00&\x00'\x00(\x00)\x00*\x00+\x00,\x00-\x00.\x00/\x000\x001\x002\x003\x004\x005\x006\x007\x008\x009\x00:\x00;\x00<\x00=\x00>\x00?\x00@\x00A\x00B\x00C\x00D\x00E\x00F\x00G\x00H\x00I\x00J\x00K\x00L\x00M\x00N\x00O\x00P\x00Q\x00R\x00S\x00T\x00U\x00V\x00W\x00X\x00Y\x00Z\x00[\x00\\\x00]\x00^\x00_\x00`\x00a\x00\xac\x00\xa3\x00\x84\x00\x85\x00\xbd\x00\x96\x00\xe8\x00\x86\x00\x8e\x00\x8b\x00\x9d\x00\xa9\x00\xa4\x01\x02\x00\x8a\x01\x03\x00\x83\x00\x93\x00\xf2\x00\xf3\x00\x8d\x00\x97\x00\x88\x00\xc3\x00\xde\x00\xf1\x00\x9e\x00\xaa\x00\xf5\x00\xf4\x00\xf6\x00\xa2\x00\xad\x00\xc9\x00\xc7\x00\xae\x00b\x00c\x00\x90\x00d\x00\xcb\x00e\x00\xc8\x00\xca\x00\xcf\x00\xcc\x00\xcd\x00\xce\x00\xe9\x00f\x00\xd3\x00\xd0\x00\xd1\x00\xaf\x00g\x00\xf0\x00\x91\x00\xd6\x00\xd4\x00\xd5\x00h\x00\xeb\x00\xed\x00\x89\x00j\x00i\x00k\x00m\x00l\x00n\x00\xa0\x00o\x00q\x00p\x00r\x00s\x00u\x00t\x00v\x00w\x00\xea\x00x\x00z\x00y\x00{\x00}\x00|\x00\xb8\x00\xa1\x00\u007f\x00~\x00\x80\x00\x81\x00\xec\x00\xee\x00\xba\x00\xd7\x00\xd8\x00\xdd\x00\xd9\x00\xb2\x00\xb3\x00\xb6\x00\xb7\x00\xc4\x00\xb4\x00\xb5\x00\xc5\x00\x87\x00\xbe\x00\xbf\x00\xbc\x01\x04\auni00AD\toverscore\ffoursuperior\x00\x00\x00\x00\x02\x00\x05\x00\x02\xff\xff\x00\x03\x00\x01\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x00\x00\x00\xd1\x00\x01\x00\x00\x00\x01\x00\x00\x00\n\x00\x1e\x00,\x00\x01latn\x00\b\x00\x04\x00\x00\x00\x00\xff\xff\x00\x01\x00\x00\x00\x01kern\x00\b\x00\x00\x00\x01\x00\x00\x00\x01\x00\x04\x00\x02\x00\b\x00\x01\x00\b\x00\x01\x00\xd6\x00\x04\x00\x00\x00f\x01p\x01p\n\xea\x02\"\x11\xdc\x02\"\x02x\x02\xde\rX\x02\xf8\x03R\x0e.\x03\xac\x03\xea\x0e\xf8\x04P\x04\x92\x04\xec\x04\xf2\x06\x1c\x06F\a@\b:\b\xbc\x0e.\n\xea\x10\xea\x10\xea\x10\xf0\x10\xea\t\xce\t\xf4\n\n\n\x10\x10\xea\x10\xea\x110\n\"\x10\xf0\nT\nj\nj\n\x80\n\xb2\n\xc8\n\xea\v\x86\n\xf0\n\xf0\v\x86\f$\f\xba\rX\r\xe4\r\xa2\r\xe4\r\xe4\x0e.\x0e.\x0e.\x0e.\x0el\x0e\x8a\x0e\x8a\x0e\x8a\x0e\x8a\x0e\x8a\x0e\xf8\x0fR\x0fR\x0fR\x0fR\x0f\xa0\x10\xea\x10\xea\x10\xea\x10\xea\x10\xea\x10\xea\x110\x10\xf0\x11\x02\x11\x02\x11\x02\x11\x02\x11\f\x11\x1a\x11\x1a\x11\x1a\x11\x1a\x11\x1a\x110\x11:\x11:\x11:\x11:\x11D\x11\xbe\x11\xdc\x11\xdc\x11\xe2\x11\xe2\x00\x02\x00\x19\x00\x05\x00\x05\x00\x00\x00\n\x00\v\x00\x01\x00\x0f\x00\x11\x00\x03\x00$\x00'\x00\x06\x00)\x00)\x00\n\x00,\x00,\x00\v\x00.\x00/\x00\f\x002\x005\x00\x0e\x007\x00>\x00\x12\x00D\x00F\x00\x1a\x00H\x00K\x00\x1d\x00N\x00N\x00!\x00P\x00R\x00\"\x00U\x00W\x00%\x00Y\x00^\x00(\x00\x82\x00\x87\x00.\x00\x89\x00\x92\x004\x00\x94\x00\x98\x00>\x00\x9a\x00\x9f\x00C\x00\xa2\x00\xad\x00I\x00\xb3\x00\xb8\x00U\x00\xba\x00\xbf\x00[\x00\xc1\x00\xc1\x00a\x00\xc6\x00\xc8\x00b\x00\xcb\x00\xcb\x00e\x00,\x00$\xff\xae\x00,\x00)\x007\x00R\x009\x00R\x00:\x00f\x00;\x00)\x00<\x00R\x00=\x00)\x00F\xff\xc3\x00G\xff\xc3\x00H\xff\xc3\x00J\xff\xd7\x00R\xff\xc3\x00T\xff\xc3\x00W\x00)\x00Y\x00)\x00Z\x00\x14\x00\\\x00)\x00\x82\xff\xae\x00\x83\xff\xae\x00\x84\xff\xae\x00\x85\xff\xae\x00\x86\xff\xae\x00\x87\xff\xae\x00\x88\xff\\\x00\x8e\x00)\x00\x8f\x00)\x00\x90\x00)\x00\x91\x00)\x00\x9f\x00R\x00\xa8\xff\xc3\x00\xa9\xff\xc3\x00\xaa\xff\xc3\x00\xab\xff\xc3\x00\xac\xff\xc3\x00\xad\xff\xc3\x00\xb4\xff\xc3\x00\xb5\xff\xc3\x00\xb6\xff\xc3\x00\xb7\xff\xc3\x00\xb8\xff\xc3\x00\xba\xff\xc3\x00\xbf\x00)\x00\xc1\x00)\x00\x15\x00&\xff\xc3\x00*\xff\xc3\x002\xff\xc3\x004\xff\xc3\x007\xff\x9a\x008\xff\xd7\x009\xff\x9a\x00:\xff\xae\x00<\xff\x9a\x00\x89\xff\xc3\x00\x94\xff\xc3\x00\x95\xff\xc3\x00\x96\xff\xc3\x00\x97\xff\xc3\x00\x98\xff\xc3\x00\x9a\xff\xc3\x00\x9b\xff\xd7\x00\x9c\xff\xd7\x00\x9d\xff\xd7\x00\x9e\xff\xd7\x00\x9f\xff\x9a\x00\x19\x00\x05\xff\xae\x00\n\xff\xae\x00&\xff\xec\x00*\xff\xec\x002\xff\xec\x004\xff\xec\x007\xff\x85\x008\xff\xec\x009\xff\xc3\x00:\xff\xd7\x00<\xff\x9a\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\x9b\xff\xec\x00\x9c\xff\xec\x00\x9d\xff\xec\x00\x9e\xff\xec\x00\x9f\xff\x9a\x00\xc9\xff\xae\x00\xcc\xff\xae\x00\x06\x00,\xff\xec\x007\xff\xec\x009\xff\xec\x00;\xff\xec\x00<\xff\xec\x00\x9f\xff\xec\x00\x16\x00\x0f\xff\xc3\x00\x11\xff\xc3\x00$\xff\xec\x00,\xff\xec\x007\xff\xc3\x009\xff\xec\x00:\xff\xec\x00;\xff\xec\x00<\xff\xd7\x00=\xff\xec\x00\x82\xff\xec\x00\x83\xff\xec\x00\x84\xff\xec\x00\x85\xff\xec\x00\x86\xff\xec\x00\x87\xff\xec\x00\x88\xff\xc3\x00\x8e\xff\xec\x00\x8f\xff\xec\x00\x90\xff\xec\x00\x91\xff\xec\x00\x9f\xff\xd7\x00\x16\x00\x05\x00=\x00\n\x00=\x00\f\x00)\x00\x0f\xff\x9a\x00\x11\xff\x9a\x00\"\x00)\x00$\xff\xd7\x009\x00\x14\x00:\x00\x14\x00<\x00\x14\x00@\x00)\x00`\x00)\x00\x82\xff\xd7\x00\x83\xff\xd7\x00\x84\xff\xd7\x00\x85\xff\xd7\x00\x86\xff\xd7\x00\x87\xff\xd7\x00\x88\xff\xc3\x00\x9f\x00\x14\x00\xc9\x00=\x00\xcc\x00=\x00\x0f\x00\x05\x00)\x00\n\x00)\x00&\xff\xd7\x00*\xff\xd7\x002\xff\xd7\x004\xff\xd7\x00\x89\xff\xd7\x00\x94\xff\xd7\x00\x95\xff\xd7\x00\x96\xff\xd7\x00\x97\xff\xd7\x00\x98\xff\xd7\x00\x9a\xff\xd7\x00\xc9\x00)\x00\xcc\x00)\x00\x19\x00\x05\xff\x9a\x00\n\xff\x9a\x00&\xff\xec\x00*\xff\xec\x002\xff\xec\x004\xff\xec\x007\xff\x85\x008\xff\xec\x009\xff\xae\x00:\xff\xc3\x00<\xff\x9a\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\x9b\xff\xec\x00\x9c\xff\xec\x00\x9d\xff\xec\x00\x9e\xff\xec\x00\x9f\xff\x9a\x00\xc9\xff\x9a\x00\xcc\xff\x9a\x00\x10\x00\x0f\xff3\x00\x11\xff3\x00$\xff\xae\x00&\xff\xec\x00;\xff\xec\x00<\xff\xec\x00=\xff\xd7\x00\x82\xff\xae\x00\x83\xff\xae\x00\x84\xff\xae\x00\x85\xff\xae\x00\x86\xff\xae\x00\x87\xff\xae\x00\x88\xffq\x00\x89\xff\xec\x00\x9f\xff\xec\x00\x16\x00\x0f\xff\xc3\x00\x11\xff\xc3\x00$\xff\xec\x00,\xff\xec\x007\xff\xc3\x009\xff\xd7\x00:\xff\xec\x00;\xff\xd7\x00<\xff\xd7\x00=\xff\xec\x00\x82\xff\xec\x00\x83\xff\xec\x00\x84\xff\xec\x00\x85\xff\xec\x00\x86\xff\xec\x00\x87\xff\xec\x00\x88\xff\xc3\x00\x8e\xff\xec\x00\x8f\xff\xec\x00\x90\xff\xec\x00\x91\xff\xec\x00\x9f\xff\xd7\x00\x01\x007\xff\xec\x00J\x00\x05\x00R\x00\n\x00R\x00\x0f\xff\x9a\x00\x10\xff\x9a\x00\x11\xff\x9a\x00\"\x00)\x00$\xff\x85\x00&\xff\xc3\x00*\xff\xc3\x002\xff\xc3\x004\xff\xc3\x006\xff\xec\x007\x00\x14\x00D\xff\x85\x00F\xff\x85\x00G\xff\x85\x00H\xff\x85\x00J\xff\x9a\x00P\xff\xae\x00Q\xff\xae\x00R\xff\x85\x00S\xff\xae\x00T\xff\x85\x00U\xff\xae\x00V\xff\x85\x00X\xff\xae\x00Y\xff\xc3\x00Z\xff\xc3\x00[\xff\xc3\x00\\\xff\xc3\x00]\xff\xc3\x00\x82\xff\x85\x00\x83\xff\x85\x00\x84\xff\x85\x00\x85\xff\x85\x00\x86\xff\x85\x00\x87\xff\x85\x00\x88\xffq\x00\x89\xff\xc3\x00\x94\xff\xc3\x00\x95\xff\xc3\x00\x96\xff\xc3\x00\x97\xff\xc3\x00\x98\xff\xc3\x00\x9a\xff\xc3\x00\xa2\xff\x85\x00\xa3\xff\x85\x00\xa4\xff\x85\x00\xa5\xff\x85\x00\xa6\xff\x85\x00\xa7\xff\x85\x00\xa8\xff\x85\x00\xa9\xff\x85\x00\xaa\xff\x85\x00\xab\xff\x85\x00\xac\xff\x85\x00\xad\xff\x85\x00\xb3\xff\xae\x00\xb4\xff\x85\x00\xb5\xff\x85\x00\xb6\xff\x85\x00\xb7\xff\x85\x00\xb8\xff\x85\x00\xba\xff\x85\x00\xbb\xff\xae\x00\xbc\xff\xae\x00\xbd\xff\xae\x00\xbe\xff\xae\x00\xbf\xff\xc3\x00\xc1\xff\xc3\x00\xc6\xff\xae\x00\xc7\xff\x9a\x00\xc9\x00R\x00\xcc\x00R\x00\n\x00\x0f\xff\xd7\x00\x11\xff\xd7\x00$\xff\xec\x00\x82\xff\xec\x00\x83\xff\xec\x00\x84\xff\xec\x00\x85\xff\xec\x00\x86\xff\xec\x00\x87\xff\xec\x00\x88\xff\xd7\x00>\x00\x05\x00R\x00\n\x00R\x00\x0f\xff\x9a\x00\x11\xff\x9a\x00\"\x00)\x00$\xff\xc3\x00&\xff\xd7\x00*\xff\xd7\x002\xff\xd7\x004\xff\xd7\x00D\xff\xc3\x00F\xff\xc3\x00G\xff\xc3\x00H\xff\xc3\x00J\xff\xc3\x00P\xff\xd7\x00Q\xff\xd7\x00R\xff\xc3\x00S\xff\xd7\x00T\xff\xc3\x00U\xff\xd7\x00V\xff\xd7\x00X\xff\xd7\x00\x82\xff\xc3\x00\x83\xff\xc3\x00\x84\xff\xc3\x00\x85\xff\xc3\x00\x86\xff\xc3\x00\x87\xff\xc3\x00\x88\xff\x85\x00\x89\xff\xd7\x00\x94\xff\xd7\x00\x95\xff\xd7\x00\x96\xff\xd7\x00\x97\xff\xd7\x00\x98\xff\xd7\x00\x9a\xff\xd7\x00\xa2\xff\xc3\x00\xa3\xff\xc3\x00\xa4\xff\xc3\x00\xa5\xff\xc3\x00\xa6\xff\xc3\x00\xa7\xff\xc3\x00\xa8\xff\xc3\x00\xa9\xff\xc3\x00\xaa\xff\xc3\x00\xab\xff\xc3\x00\xac\xff\xc3\x00\xad\xff\xc3\x00\xb3\xff\xd7\x00\xb4\xff\xc3\x00\xb5\xff\xc3\x00\xb6\xff\xc3\x00\xb7\xff\xc3\x00\xb8\xff\xc3\x00\xba\xff\xc3\x00\xbb\xff\xd7\x00\xbc\xff\xd7\x00\xbd\xff\xd7\x00\xbe\xff\xd7\x00\xc9\x00R\x00\xcc\x00R\x00>\x00\x05\x00f\x00\n\x00f\x00\x0f\xff\xae\x00\x11\xff\xae\x00$\xff\xd7\x00&\xff\xec\x00*\xff\xec\x002\xff\xec\x004\xff\xec\x00D\xff\xd7\x00F\xff\xd7\x00G\xff\xd7\x00H\xff\xd7\x00J\xff\xec\x00P\xff\xec\x00Q\xff\xec\x00R\xff\xd7\x00S\xff\xec\x00T\xff\xd7\x00U\xff\xec\x00V\xff\xd7\x00X\xff\xec\x00]\xff\xec\x00\x82\xff\xd7\x00\x83\xff\xd7\x00\x84\xff\xd7\x00\x85\xff\xd7\x00\x86\xff\xd7\x00\x87\xff\xd7\x00\x88\xff\xae\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\xa2\xff\xd7\x00\xa3\xff\xd7\x00\xa4\xff\xd7\x00\xa5\xff\xd7\x00\xa6\xff\xd7\x00\xa7\xff\xd7\x00\xa8\xff\xd7\x00\xa9\xff\xd7\x00\xaa\xff\xd7\x00\xab\xff\xd7\x00\xac\xff\xd7\x00\xad\xff\xd7\x00\xb3\xff\xec\x00\xb4\xff\xd7\x00\xb5\xff\xd7\x00\xb6\xff\xd7\x00\xb7\xff\xd7\x00\xb8\xff\xd7\x00\xba\xff\xd7\x00\xbb\xff\xec\x00\xbc\xff\xec\x00\xbd\xff\xec\x00\xbe\xff\xec\x00\xc9\x00f\x00\xcc\x00f\x00 \x00\x05\x00)\x00\n\x00)\x00&\xff\xd7\x00*\xff\xd7\x002\xff\xd7\x004\xff\xd7\x00F\xff\xec\x00G\xff\xec\x00H\xff\xec\x00R\xff\xec\x00T\xff\xec\x00\x89\xff\xd7\x00\x94\xff\xd7\x00\x95\xff\xd7\x00\x96\xff\xd7\x00\x97\xff\xd7\x00\x98\xff\xd7\x00\x9a\xff\xd7\x00\xa8\xff\xec\x00\xa9\xff\xec\x00\xaa\xff\xec\x00\xab\xff\xec\x00\xac\xff\xec\x00\xad\xff\xec\x00\xb4\xff\xec\x00\xb5\xff\xec\x00\xb6\xff\xec\x00\xb7\xff\xec\x00\xb8\xff\xec\x00\xba\xff\xec\x00\xc9\x00)\x00\xcc\x00)\x00D\x00\x05\x00R\x00\n\x00R\x00\x0f\xff\x9a\x00\x11\xff\x9a\x00\"\x00)\x00$\xff\x9a\x00&\xff\xd7\x00*\xff\xd7\x002\xff\xd7\x004\xff\xd7\x006\xff\xec\x00D\xff\x9a\x00F\xff\x9a\x00G\xff\x9a\x00H\xff\x9a\x00J\xff\x9a\x00P\xff\xc3\x00Q\xff\xc3\x00R\xff\x9a\x00S\xff\xc3\x00T\xff\x9a\x00U\xff\xc3\x00V\xff\xae\x00X\xff\xc3\x00[\xff\xd7\x00\\\xff\xec\x00]\xff\xc3\x00\x82\xff\x9a\x00\x83\xff\x9a\x00\x84\xff\x9a\x00\x85\xff\x9a\x00\x86\xff\x9a\x00\x87\xff\x9a\x00\x88\xffq\x00\x89\xff\xd7\x00\x94\xff\xd7\x00\x95\xff\xd7\x00\x96\xff\xd7\x00\x97\xff\xd7\x00\x98\xff\xd7\x00\x9a\xff\xd7\x00\xa2\xff\x9a\x00\xa3\xff\x9a\x00\xa4\xff\x9a\x00\xa5\xff\x9a\x00\xa6\xff\x9a\x00\xa7\xff\x9a\x00\xa8\xff\x9a\x00\xa9\xff\x9a\x00\xaa\xff\x9a\x00\xab\xff\x9a\x00\xac\xff\x9a\x00\xad\xff\x9a\x00\xb3\xff\xc3\x00\xb4\xff\x9a\x00\xb5\xff\x9a\x00\xb6\xff\x9a\x00\xb7\xff\x9a\x00\xb8\xff\x9a\x00\xba\xff\x9a\x00\xbb\xff\xc3\x00\xbc\xff\xc3\x00\xbd\xff\xc3\x00\xbe\xff\xc3\x00\xbf\xff\xec\x00\xc1\xff\xec\x00\xc9\x00R\x00\xcc\x00R\x00\t\x00\x05\x00f\x00\n\x00f\x00Y\x00\x14\x00Z\x00\x14\x00\\\x00\x14\x00\xbf\x00\x14\x00\xc1\x00\x14\x00\xc9\x00f\x00\xcc\x00f\x00\x05\x00\x05\x00)\x00\n\x00)\x00J\x00\x14\x00\xc9\x00)\x00\xcc\x00)\x00\x01\x00\n\xff\xc3\x00\x04\x00\x05\x00)\x00\n\x00)\x00\xc9\x00)\x00\xcc\x00)\x00\f\x00\x05\x00f\x00\n\x00f\x00D\xff\xec\x00J\xff\xec\x00\xa2\xff\xec\x00\xa3\xff\xec\x00\xa4\xff\xec\x00\xa5\xff\xec\x00\xa6\xff\xec\x00\xa7\xff\xec\x00\xc9\x00f\x00\xcc\x00f\x00\x05\x00\x05\x00R\x00\n\x00R\x00W\x00\x14\x00\xc9\x00R\x00\xcc\x00R\x00\x05\x00\x05\x00R\x00\n\x00R\x00I\x00\x14\x00\xc9\x00R\x00\xcc\x00R\x00\f\x00\x05\x00)\x00\n\x00)\x00R\xff\xd7\x00\xa8\xff\xd7\x00\xb4\xff\xd7\x00\xb5\xff\xd7\x00\xb6\xff\xd7\x00\xb7\xff\xd7\x00\xb8\xff\xd7\x00\xba\xff\xd7\x00\xc9\x00)\x00\xcc\x00)\x00\x05\x00\x05\x00=\x00\n\x00=\x00I\x00\x14\x00\xc9\x00=\x00\xcc\x00=\x00\b\x00R\xff\xec\x00\xa8\xff\xec\x00\xb4\xff\xec\x00\xb5\xff\xec\x00\xb6\xff\xec\x00\xb7\xff\xec\x00\xb8\xff\xec\x00\xba\xff\xec\x00\x01\x00-\x00{\x00%\x00\x05\xff\xae\x00\n\xff\xae\x00\r\xff\x85\x00\x0f\x00D\x00\x1e\x00D\x00\"\xff\xc3\x00&\xff\xec\x00*\xff\xec\x002\xff\xec\x004\xff\xec\x007\xff\x85\x008\xff\xec\x009\xff\xc3\x00:\xff\xd7\x00<\xff\x9a\x00=\x00;\x00I\xff\xec\x00W\xff\xec\x00Y\xff\xd7\x00Z\xff\xec\x00\\\xff\xd7\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\x9b\xff\xec\x00\x9c\xff\xec\x00\x9d\xff\xec\x00\x9e\xff\xec\x00\x9f\xff\x9a\x00\xbf\xff\xd7\x00\xc1\xff\xd7\x00\xc9\xff\xae\x00\xcc\xff\xae\x00'\x00\x05\xff\xae\x00\n\xff\xae\x00\r\xff\x85\x00\x0f\x00D\x00\x1e\x00D\x00\"\xff\xc3\x00&\xff\xec\x00*\xff\xec\x00-\x00^\x002\xff\xec\x004\xff\xec\x007\xff\x85\x008\xff\xec\x009\xff\xc3\x00:\xff\xd7\x00<\xff\x9a\x00=\x00;\x00I\xff\xec\x00W\xff\xec\x00Y\xff\xd7\x00Z\xff\xec\x00\\\xff\xd7\x00\x82\xff\xd7\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\x9b\xff\xec\x00\x9c\xff\xec\x00\x9d\xff\xec\x00\x9e\xff\xec\x00\x9f\xff\x9a\x00\xbf\xff\xd7\x00\xc1\xff\xd7\x00\xc9\xff\xae\x00\xcc\xff\xae\x00%\x00\x05\xff\xae\x00\n\xff\xae\x00\r\xff\u007f\x00\x0f\x00D\x00\x1e\x00D\x00\"\xff\xd7\x00&\xff\xec\x00*\xff\xec\x00-\x00^\x002\xff\xec\x004\xff\xec\x007\xff\x85\x008\xff\xec\x009\xff\xc3\x00:\xff\xd7\x00<\xff\x9a\x00=\x00;\x00W\xff\xe5\x00Y\xff\xd5\x00Z\xff\xe5\x00\\\xff\xdb\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\x9b\xff\xec\x00\x9c\xff\xec\x00\x9d\xff\xec\x00\x9e\xff\xec\x00\x9f\xff\x9a\x00\xbf\xff\xdb\x00\xc1\xff\xdb\x00\xc9\xff\xae\x00\xcc\xff\xae\x00'\x00\x05\xfff\x00\n\xfff\x00\r\xff\u007f\x00\x0f\x00D\x00\x1e\x00D\x00\"\xff\xd7\x00&\xff\xec\x00*\xff\xec\x00-\x00^\x002\xff\xec\x004\xff\xec\x007\xff\x85\x008\xff\xec\x009\xff\xc3\x00:\xff\xd7\x00<\xff\x9a\x00=\x00;\x00W\xff\xe5\x00Y\xff\xd5\x00Z\xff\xe5\x00\\\xff\xdb\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\x9b\xff\xec\x00\x9c\xff\xec\x00\x9d\xff\xec\x00\x9e\xff\xec\x00\x9f\xff\x9a\x00\xbf\xff\xdb\x00\xc1\xff\xdb\x00\xc8\xfff\x00\xc9\xff\xae\x00\xcb\xfff\x00\xcc\xff\xae\x00\x12\x00\x05\x00)\x00\n\x00)\x00\f\x00)\x00&\xff\xd7\x00*\xff\xd7\x002\xff\xd7\x004\xff\xd7\x00@\x00)\x00`\x00)\x00\x89\xff\xd7\x00\x94\xff\xd7\x00\x95\xff\xd7\x00\x96\xff\xd7\x00\x97\xff\xd7\x00\x98\xff\xd7\x00\x9a\xff\xd7\x00\xc9\x00)\x00\xcc\x00)\x00\x10\x00\x05\x00)\x00\n\x00)\x00\x10\xff\xd7\x00&\xff\xec\x002\xff\xec\x004\xff\xec\x00\x89\xff\xec\x00\x8b\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\xc9\x00)\x00\xcc\x00)\x00\x12\x00\x05\x00)\x00\n\x00)\x00\x10\xff\xd7\x00&\xff\xec\x002\xff\xec\x004\xff\xec\x00\x84\xff\xec\x00\x89\xff\xec\x00\x8a\xff\xec\x00\x8f\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\xc9\x00)\x00\xcc\x00)\x00\x0f\x00\x05\x00)\x00\n\x00)\x00&\xff\xec\x00*\xff\xec\x002\xff\xec\x004\xff\xec\x00\x89\xff\xec\x00\x94\xff\xec\x00\x95\xff\xec\x00\x96\xff\xec\x00\x97\xff\xec\x00\x98\xff\xec\x00\x9a\xff\xec\x00\xc9\x00)\x00\xcc\x00)\x00\a\x00$\xff\xec\x00\x82\xff\xec\x00\x83\xff\xec\x00\x84\xff\xec\x00\x85\xff\xec\x00\x86\xff\xec\x00\x87\xff\xec\x00\x1b\x00\f\xff\xd7\x00\x0f\xff\xc3\x00\x11\xff\xc3\x00$\xff\xec\x00,\xff\xec\x00-\xff\xf6\x006\xff\xec\x007\xff\xc3\x009\xff\xd7\x00:\xff\xec\x00;\xff\xd7\x00<\xff\xd7\x00=\xff\xec\x00@\xff\xd7\x00`\xff\xd7\x00\x82\xff\xec\x00\x83\xff\xec\x00\x84\xff\xec\x00\x85\xff\xec\x00\x86\xff\xec\x00\x87\xff\xec\x00\x88\xff\xd7\x00\x8e\xff\xec\x00\x8f\xff\xec\x00\x90\xff\xec\x00\x91\xff\xec\x00\x9f\xff\xd7\x00\x16\x00\x0f\xff\xc3\x00\x11\xff\xc3\x00$\xff\xec\x00,\xff\xec\x007\xff\xc3\x009\xff\xd7\x00:\xff\xec\x00;\xff\xd7\x00<\xff\xd7\x00=\xff\xec\x00\x82\xff\xec\x00\x83\xff\xec\x00\x84\xff\xec\x00\x85\xff\xec\x00\x86\xff\xec\x00\x87\xff\xec\x00\x88\xff\xd7\x00\x8e\xff\xec\x00\x8f\xff\xec\x00\x90\xff\xec\x00\x91\xff\xec\x00\x9f\xff\xd7\x00\x13\x00\x0f\xff\xd7\x00\x11\xff\xd7\x00$\xff\xec\x000\xff\xec\x00=\xff\xec\x00D\xff\xec\x00\x82\xff\xec\x00\x83\xff\xec\x00\x84\xff\xec\x00\x85\xff\xec\x00\x86\xff\xec\x00\x87\xff\xec\x00\x88\xff\xd7\x00\xa2\xff\xec\x00\xa3\xff\xec\x00\xa4\xff\xec\x00\xa5\xff\xec\x00\xa6\xff\xec\x00\xa7\xff\xec\x00R\x00\x05\x00R\x00\t\xff\xc3\x00\n\x00R\x00\f\x00=\x00\r\x00)\x00\x0f\xff\x9a\x00\x10\xff\x9a\x00\x11\xff\x9a\x00\"\x00)\x00$\xff\x9a\x00&\xff\xd7\x00*\xff\xd7\x00-\xff\xbe\x000\xff\xc3\x002\xff\xd7\x004\xff\xd7\x006\xff\xec\x007\x00'\x009\x00)\x00:\x00\x14\x00@\x00=\x00D\xff\x9a\x00F\xff\x9a\x00G\xff\x9a\x00H\xff\x9a\x00I\xff\xe5\x00J\xff\x9a\x00P\xff\xc3\x00Q\xff\xc3\x00R\xff\x9a\x00S\xff\xc3\x00T\xff\x9a\x00U\xff\xc3\x00V\xff\xae\x00X\xff\xc3\x00Y\xff\xd7\x00Z\xff\xec\x00[\xff\xd7\x00\\\xff\xec\x00]\xff\xc3\x00`\x00=\x00\x82\xff\x9a\x00\x83\xff\x9a\x00\x84\xff\x9a\x00\x85\xff\x9a\x00\x86\xff\x9a\x00\x87\xff\x9a\x00\x88\xffq\x00\x89\xff\xd7\x00\x94\xff\xd7\x00\x95\xff\xd7\x00\x96\xff\xd7\x00\x97\xff\xd7\x00\x98\xff\xd7\x00\x9a\xff\xd7\x00\xa2\xff\x9a\x00\xa3\xff\x9a\x00\xa4\xff\x9a\x00\xa5\xff\x9a\x00\xa6\xff\x9a\x00\xa7\xff\x9a\x00\xa8\xff\x9a\x00\xa9\xff\x9a\x00\xaa\xff\x9a\x00\xab\xff\x9a\x00\xac\xff\x9a\x00\xad\xff\x9a\x00\xb3\xff\xc3\x00\xb4\xff\x9a\x00\xb5\xff\x9a\x00\xb6\xff\x9a\x00\xb7\xff\x9a\x00\xb8\xff\x9a\x00\xba\xff\x9a\x00\xbb\xff\xc3\x00\xbc\xff\xc3\x00\xbd\xff\xc3\x00\xbe\xff\xc3\x00\xbf\xff\xec\x00\xc1\xff\xec\x00\xc9\x00R\x00\xcc\x00R\x00\x01\x00\n\xff\xd7\x00\x04\x00\x05\x00=\x00\n\x00=\x00\xc9\x00=\x00\xcc\x00=\x00\x02\x00\x05\xff\x98\x00\n\xff\xd7\x00\x03\x00\x05\xff\x98\x00\n\xff\xd7\x00\xcc\xff\xd7\x00\x05\x00\x05\xffo\x00\n\xffo\x00I\xff\xdb\x00[\xff\xd7\x00]\xff\xec\x00\x02\x00[\xff\xd7\x00]\xff\xec\x00\x02\x00\x05\xff\xbe\x00\n\xff\xbe\x00\x1e\x00\x05\x00=\x00\n\x00=\x00\x0f\xff\xbe\x00\x11\xff\xbe\x00\"\xff\xb4\x00F\xff\xf6\x00G\xff\xf6\x00H\xff\xf6\x00I\x00\x14\x00J\xff\xf6\x00R\xff\xf6\x00T\xff\xf6\x00W\x00\x06\x00\xa8\xff\xf6\x00\xa9\xff\xf6\x00\xaa\xff\xf6\x00\xab\xff\xf6\x00\xac\xff\xf6\x00\xad\xff\xf6\x00\xb4\xff\xf6\x00\xb5\xff\xf6\x00\xb6\xff\xf6\x00\xb7\xff\xf6\x00\xb8\xff\xf6\x00\xba\xff\xf6\x00\xc9\x00=\x00\xca\xff\x8d\x00\xcc\x00=\x00\xcd\xff\x8d\x00\xd0\x00\f\x00\a\x00\x05\x00=\x00\n\x00=\x00\x0f\xff\xbe\x00\x11\xff\xbe\x00I\x00\x14\x00\xc9\x00=\x00\xcc\x00=\x00\x01\x007\xff\x9a\x00)\x00$\xff\xae\x00,\x00)\x007\x00R\x009\x00R\x00:\x00f\x00;\x00)\x00<\x00R\x00=\x00)\x00F\xff\xc3\x00G\xff\xc3\x00H\xff\xc3\x00J\xff\xd7\x00R\xff\xc3\x00T\xff\xc3\x00W\x00)\x00Y\x00)\x00Z\x00\x14\x00\x82\xff\xae\x00\x83\xff\xae\x00\x84\xff\xae\x00\x85\xff\xae\x00\x86\xff\xae\x00\x87\xff\xae\x00\x88\xff\\\x00\x8e\x00)\x00\x8f\x00)\x00\x90\x00)\x00\x91\x00)\x00\x9f\x00R\x00\xa8\xff\xc3\x00\xa9\xff\xc3\x00\xaa\xff\xc3\x00\xab\xff\xc3\x00\xac\xff\xc3\x00\xad\xff\xc3\x00\xb4\xff\xc3\x00\xb5\xff\xc3\x00\xb6\xff\xc3\x00\xb7\xff\xc3\x00\xb8\xff\xc3\x00\xba\xff\xc3\x00\x01\x00\x00\x00\n\x00\n\x00\n\x00\x00") + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_ttf_bytes() ([]byte, error) { + return _third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_ttf, nil +} + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_ttf() (*asset, error) { + bytes, err := third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_ttf_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/fonts/droid-sans-v6-latin-regular.ttf", size: 39072, mode: os.FileMode(416), modTime: time.Unix(1449047502, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_woff = []byte("wOFF\x00\x01\x00\x00\x00\x00a$\x00\x11\x00\x00\x00\x00\x98\xa0\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00GDEF\x00\x00\x01\x80\x00\x00\x00\x16\x00\x00\x00\x16\x00\x10\x00\xd2GPOS\x00\x00\x01\x98\x00\x00\x06$\x00\x00\x12\xc0\xf2ZM^GSUB\x00\x00\a\xbc\x00\x00\x00\f\x00\x00\x00\f\x00\x15\x00\nOS/2\x00\x00\a\xc8\x00\x00\x00^\x00\x00\x00`\xa0ӵecmap\x00\x00\b(\x00\x00\x00j\x00\x00\x00\x8cmag\xdacvt \x00\x00\b\x94\x00\x00\x00\xf3\x00\x00\x01\xfc9~>Lfpgm\x00\x00\t\x88\x00\x00\x04'\x00\x00\a\x05s\xd3#\xb0gasp\x00\x00\r\xb0\x00\x00\x00\f\x00\x00\x00\f\x00\x04\x00\aglyf\x00\x00\r\xbc\x00\x00J\xeb\x00\x00o(I;\f|head\x00\x00X\xa8\x00\x00\x003\x00\x00\x006\xf5\xf5 \xd3hhea\x00\x00X\xdc\x00\x00\x00\x1f\x00\x00\x00$\r\xc4\x05\x8ahmtx\x00\x00X\xfc\x00\x00\x01\xe6\x00\x00\x03LmsT\xd3loca\x00\x00Z\xe4\x00\x00\x01\xa8\x00\x00\x01\xa8\xbe\x8aۈmaxp\x00\x00\\\x8c\x00\x00\x00 \x00\x00\x00 \x03i\x01\xd3name\x00\x00\\\xac\x00\x00\x00\xb0\x00\x00\x018\x14\x910\xdepost\x00\x00]\\\x00\x00\x01Y\x00\x00\x01\xe7\xa2\xc2\x0f;prep\x00\x00^\xb8\x00\x00\x02k\x00\x00\x02\xec\x82\xdc!\x13\x00\x01\x00\x00\x00\f\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x00\x00\x00\xd1\x00\x01\x00\x00x\x01<\xcc\x03\xac\x1cQ\x14\x80\xe1\u007f\xb0\x1a\xed\xdcF\xb5m۶m\xdb\x0ek\x86\xb5\x19\xa7\xb6m\xc6\rj\x04ճ\xc2}g\x99\xef\xf2\b\r\xb0\xa8LC\xb4\x85\xd3V.&\x8c\t\x10\x8b\xa1\x01ڂY\xcb%\x06\xc9\x1f&\xba\xfc\xb4\xc4\xfe ?\x98\xad-ՖZ\u007f\xf4\xea\xea\x93^]_\xab\u007fq\xc7\xea%\xc6p\xaf\xb1q\xd6\xf8㕘C\xcd\xdd\xe6?3/X1\xd8;\xd45\xdc>|\xd7kl\xfd\xf1\xe3r\xfc?\x91\xf7\x91\x02˲\xfc\xf8_5\xb5\xaa\xfb9\xd6Hk\xbeXo]\xb6^Z\u007f\xec\xadV\x8e\x95couj:\xb7ݱ\xee/\xf7\xb4\xec_^㔅\xdeΌ\x92\xe8\xf0\x94\x932-C5\xf5s\x94\x9e\xe2\xa8\xf2\x19MU\xfb\x94\x9e\xea\xbe\xfa$~\xa8\x1f\xe8\x94# \xc0\xc2F#\x8a\u00a0&u\bROX4\x146\x8di\x82CsZ\xe1цΔ\xa1'\xbd)O_\x06P\x89\xc1\xa2\x1aC\x19NuF1\x86Z\x8cc2u\xd9\xc86\x1a\xb3\x83ݴd/\a\xe9\xccaNЃӜ\xa3\x1fW\xb8)շy\xc0\x04\x1e\x89i<\xe7%\xd3y#fѐ\x9a\xb1\xf3r֣\r\xc3i'\xbb=\xb3\xe9 \xff\x8e\f\xa7\xb4\x1a\xbb\x86\x8e\xe3Z\x03\x00\x85\xd13A\x89XF\xa5\xbajj\x82\x1f\x06\xb3\xc5Z\xe3\xd4QO\x03\x8d4\xb1 :;X\xa6~9+X\xc9vc7\x1b\xa3\x85V\xdah\xa7\x83N\xba覇^\xfa\x19\xd1\xef ?\n\xfe\xec\xe8\xef\xfc\x9fGy\"\xba)xRfSĩ\xce8]\\\xa8|\rkY\xc7z6\xb0\x89\xcd\xdana+\xdbخ\xbd\xfb\xa3g\x84?G\xbf\t\xfe\xce\xffy\x94'\xa2\r\xc1\x93\xe2\x14=\xa7j\x1d\x1b\xdd\xf1\x1aֲ\x8e\xf5l`\x13\x9b\xd9\xc2V\xb6a\xf4\xe0\xa4q?d|\xf0\xcf؈Fc\x1a\xd3\xd9Ώ\x83t\xe3g\xf2GG\xb1V~O\x11\xa7\x86-\xcf\x043\xc4Zꨧ\x81F\x9aX\xa0\xfd2q9+X\x19\x1b\xf9\x8c\x91ǹ\x8f\x11\xd2\xcc^\xbal2\xf9\xbd\xdf\u007fT;\xc5ݛ\xcat\x9eV6\x9fZ\xe5u\xd4\xd3@#M8C\xb0]\xbb\x93\xc6\xfa\x90t#[\xbb\xfcY\xed\xdf\xf9?\x8f\xb2\x905\xace\x1d\xeb\xd9\xc0&Nj\xff!f\\\x1e\x11\x92\xcf\xf8ۮ\xfe\xf0\xad\xcd8\x1f\x92\xe1\x8a\x1f\t2\xb1Ν)\x9cusy&Ś}\xc9\x19Sߙ3\x97\xee\x8cx\xabwfTl%\x14\x9aŒ B\xfa\xf7\xf9^u\u007f\x1a\x12\xd6\xfa\xe3z<\xe1\x1ed\xab\xcb%\x8f|\n\xf5)\x92y1%\x8eK\xc52\xb1\\\xac\x10\xab\xc4j\xfdk\x98\xc5l\xe6P\xab\xae\x8ez\x1ah\xa4\t3\x90\xf2\x19ڥ\xcdn\xf6\xb0\x97}짙\x16Zi\xa3\x9d\x0e>p\xfeN\xb1\x8bnz襟\x01u\x83\f1̈\xf1\x0fr\xcc\xef\xe3\xae뤹\xf9\x90H\x90n\xd62\xf9c\xaa\xd9V?3\x9cӫ\xd7\xfbᄕ\x9a\xad\xec\xaa7\x1cEʋ)\xf1\xbbT,\x13\xcb\xc5\n\xaa\xa8u\\G=\r4\xd2\xc4\x02גj\xe5\xef\xd2f7{\xd8\xcb>\xf6\x93\xecM\xf8\x81>\xc9ކ\x03\xea\x06\x19b\x98\xf8\x1c\xb9r\xef\xef\b\xe9f/\x13Oy\xc2\x13\x96\xad,\x97<\xf2)TVD1%\x8eK\xc52\xb1\\\x8c]\xb58\x87\xe4o\x86\xb7S>\xa1\xbb\xb4\xd9\xcd\x1e\xf6\xb2\x8f\xfd4\xd3B+m\xb4\xd3\xc1\a\xfat\x8a]t\xd3C/\xfd\f\xa8\x1bd\x88aN\xba\xd6\x0f\xf9mҷQ\xaeVy\xe4SB\x19\xa9\xeeS\xb3\xfa\x16Zi\xa3\x9d\x0e:颛\x1ez\xe9'\xfeF\xcbN\xb2\xea6%d\xf4\xb8^\xd9\xcas\xc9#\x9fB\x8a\xdc\xd9bJ\xfc.\x15\xcb\xc4r\xb1\xc2\xfcV\x89\xb3\xf4\x9d\xado\xec\xc9UWG=\r4҄'7\xf5*\xd4f7{\xd8\xcb>\xf6\xd3L\v\xad\xb4\xd1N\a\x1f8W\xa7\xd8E7=\xf4\xd2π\xbaA\x86\x18fDn\a\x89\xaf\xc6I\xe1j\xac\xf6Ϊa6#\x1c$~\xe7ƅw\xaePi|6G\x05\x11#\x8e\x8dׅ\xe5i\xe1\x98\xd9\xceT\xc8.v\xb3\x87\xbd\xecc?\xe1\xf8\xe1}\xa94~<\xb7˥\x05W\x94\xa6\x85\xe7+\xb9\xb4>\x93\xad\xc30\x9fq\xe1\xf7\xb5\xe0\x8ao\xe3\xc4\xd8JkN\xbej\\\u07ff\x82W\x82?\x85\xfb\x8e\xc9\xde\x1e\xe9\xd6Я\xf9\xbd+\xbf\x91}\xc8\f;\xb5\x02e\x95T+\xab\x11g\x8b7\xfb\xb5\x1c\xd1\xe7 \xf1}\xca_\xae\x97\x95\xdc\xe7\xdeRf\xb5w \xbb+\xe7\xec\x8d+\xb2;s3\xd9\xc9\xec\xa2̾\x92\xd9E\x99]\xb8\xf9\xac\xf49ȕsf]\xf2 duB\x1e2\vN\x8b\x1f\x8aY\xe1\xbaNK|C\x86;\xbf\x1bٷe\x84#e\\\xba\xb6\xf0\x9a\x16\xb28y\xbe\xe1\bYIG\xa8g!\x8bX~\x03#\x85;Є\xa7e\xe1\r\xf4\x9e\x90r\x1f\xf1\x8b -z&a\xef\xf7\xaf\xe8\xb7\xc1\xe3)\xf6\x80O\x8b\xf3I\xb1;I\xb2K\xbf\xf5]f\xea\x91\u007fpծ\xe9\xbf\xcc ;\xf5HIߩޛL\x92[DL\xb3f'\x9b\xc9p\xe7\x9a\xf8\xcd3c\xc3\xcez\xf8\xaao\xdf\x13\x9e\x96)\xdaM\x8d\xfd\xd51#\xe1[X`\xf5\xdf\xc0\xf70|\xb3\\\xf9]\x9co\xbc\x87\xe1\xdb\xe8\xfb\xe6\x8cc\xe3_\x8f\xf0\xcb1\xda{dC\xacnL\xf8\xebC\xc69z!\x88P\xe0\xf9vŁ\x1d\x99\xd6\xe1/\xf5\xc3A\x84_\x87c\xa6;\xca\xe4\xf7\xd1\xce \xd7\xca\xcd#\x1f_\xaa\xa0P,\xa1\x8cJ\u007f\x996\x8b-\xb4\xd2F;\x1dt\xd2E7=\xf4ҏ|\x83Sѥ\xb1\x9c?\x12?\xb5\x1a&$\x9c9\xfc&\xe2\xef\x1d\xf3\xf2\xb7;\xf1\x1f\x87\xbb\xfd߆\xef\x00\xe8\x11\x93\xb0\x00\x01\x00\x00\x00\n\x00\n\x00\n\x00\x00x\x01c`fQc\x9c\xc0\xc0\xca\xc0\xc1:\x8b\u0558\x81\x81Q\x0eB3_`Hc\xfc\xc4\xc0\xc0\xc4\xcd\xc6\xc6\xcc\xc1\xc2\xc4\xc4\xf2\x80\x81\xe9\xbd\x03\x83B4\x03\x03\x83\x06\x03\x10\x18:\x06;3\x00\x05\x14\\\xd8\xe4\xff\x890\xb4\xb0\x173\xbeQ``\x9c\x0f\x92c\xf1b\xdd\x06\xa4\x14\x18\x98\x00\x9b\xf6\x0e\x82\x00\x00x\x01c```\x02bf \x16\x01\x92\x8c`\x9a\x85\xa1\x02HK1\b\x00E\xb8\x18\xea\x18\xfe3\x1a2\x1dc\xba\xc5tGADAJANAI\xc1J\xc1\xe5\xff\u007f\xa0\x1a\x05\x86\x05p9a\x05\t\x05\x19\xa0\x9c%H\xee\xff\xe3\xff\x87\xfeO\xfc\xfb\xf7\ufaff/\x1fl~\xb0\xe1\xc1\xfa\ak\x1eL{\xd0\v\xb4\x01'\x00\x00;\x03#\xf4\x00\x00x\x01\xad\xc6\x03l\x9dq\x1c\x05\xd0\xf3\u007fo\xb6\xbd\xd86\x16kނy1f۶m۶\xbdF\r\xcbX\x8d\xaa\xb0Q\xf3\xa1f\xdc\xf3ý]\x06\xe9ij\xe7\xf7\x86Z\xd7\xf9}\xe7\\\xcd\xe94\xd1 '\x88\xcbh\xfe\xa3A\x91\x8e\xd4\rx꽯\xc8\a̰\xc7\x057lS\xe2y:\xabҁ\xcf\xfe\xa5\aw\xddp\x14䤳-\x9d\xe6^\xfa\xe40\xb8\xe2\xbe\x136hr^\x93E\x96;\xe9\xad_\xf2\x01̷\xc2F\xe7\xfd\x94\x1f\xc6\x01\xa20\xc6?\x1b\xd3\x1c\x12\xaa\xb4\xaf\x02\xe7\x1d\xf4\xdcy\x87B/\xe7\xc30\xb2\xa3\x9d\xcd̵;{\x1e\x0f\x1d\x85\xcc&\xc5ކ\x1e\x84=\x96\xd9\xe1&\xa4\xed\xa0\xd6θ\x89\xdd\x0e\x01\xd0\xf9}\xf5Y\xfd\xe3J{\x9c\x00\xcbl\xd1\xe4kx\x9c\x9d^\u05fc\x0f\x85\xe1\x1c\xce\a\x9ew~\x1f}\x8dVD{\xac\xc8U.\xbeP\xe9\x9bJJ\xe5\xb4\xe5\xf0\xcb\xecr\xab\xc9\x05p\xfc5\x00>\xb1\xee\xf7%\xc5?\x05\x10q\\7\xf1-\x1dS\x87\x01\xdb\bx\x1eupQ\x1f\a\x1c\xad\x98\v\f?\x19+\x11\a\xa4\x97n\xd9܁\xce\xc2\xe6V\x93\xa5\x9b\xb7P\xb8\x85\"<\b˩u\xf0\xaae\x02\xfb\xa0\xb8\x98ڬ\xbaM/\x8b\x85\x86H\xe6\xf1n_\x8a\xc0\xf5\xbc\xa8\xd5\xec\xe1v\bL\x8au\x8c$\x96;X1\x92|Io\x9d\xad\xf2\xb4\xf9H=\x988l6>\xb2u\x1e\xe6\x93\xf7$\x16\x12\xeaU\x05\xa1\xd4\xf7X;\x82\x87 \xc0C_\xfc]\xa7\x93/`\x13\x02\x81G\xb4\xea\x8d\xc1\x93un<]\xd2\xc2R\xc3\x01\xae6\x18\x1d\a\xd6\xffy\x9eI2\xa6\xdcp6\x98\x0eC\xb2W\xa9\x10x\xa8b\x95L\x1e\xaf\xcc\x02w@\xa5[\xb7\xaaeA\x0e\xb3\xbb\x12-\xe2\u007fYu1|\x10\xa1\x13\x8f\xad\x8b\xd9a\xc3\xc1\r\xdc\xd5\u007fW\xa2\xdd\b\xf98!\x86>\xd7\xc0;\xefz\xb5(\xaf\xb9\xfb\xba4##\xb0\xac=\xf5<}\xf0Չ\xcff\t\xe0J_N1g\xb3\xeeC\xe6\x1f;\x12\xa1\x1d\xeḅ<\xb3\xe7\x9eά\xe4\x99'\xed1xd\xc6P*,6z\xf3 \xc8\xe3\xd5\x04Wf\x91'\x1f\xe9Q\x80\x83\xdb\xffu=P;k\xfc±\xc8\xd4r,P\xed\x12\xc7\xd2\f\x96u׳\rtSt\x8br4\xa0`\xfa\xb7\xee*J\xd4v\xf2\v@2ZG\x80\x88\xb3\xcf'\xe3:\t\xf0V\x13\xbbG\xa6\xa3\x1fI\xf4\x03\n\xfc$\x9b\x91H\x8f\x1f\xa3\x8e$\xa6\x11-\x05f|x\f\x96q7\xb4\xf3y\x9am\x89\xa5\xa14-Y\x1b\xee\xee \x8b\xe7\xb2.<&\x02\"\x81\v\x15\x9b@\xc4Z\v\xfar\x8d\x9dz\xfc{z\x9a\xbb?\x9fb\xa7Y\x14\xe8\xe2\xbd\x1d\x89\x85\x19\xa1\xe4\xfc\"\xbe\x19\xbb\xf3\xc8\xe3E.]\x0f\xfd\x88\x06\x1c\x81\\\x88\xf0\x90q\xe8\xd0ﴜgVD\xbb3\x927\x86p\xa3\u007f_\x9e7\x1b\xc9\x13$G\x1e\x8b\x17d@\xbaS\x19\xbarXmT\xb9\xb4\xddBD\x85\x0e\x11<\xa4\x00ڗ\xe9\x17+\x8d*}\x1d,OY:\x11%\xb8\xb4\\\x96W\xd36\xf0\x10\x17\vAVG\xf8yQ\x02\x85F\xa7\x9b\xab\x955$\x9dN\xd7\xf5\"o\xfaj5mJ\xf3la\xea\xa8jS\xbbY\x8ab\x8eE\xe2l\x92є\xf1\xb2\xae\xef<\x97\xb0\x00\x11\x8c9\xfaw%\x9d\xcd\xd8c\\\xce\xcc0\x9eg\xb3\x1a=\x87r\xb3\fB\xe6QZ\x83\xdcL\f\x8f\x10z\x06_'\xfc\f쾐\xee\xe5i\xae\xaapc\xa8\xb48d\x82\x8cv\xdeC\xa6\xaf\xb0\u007f\xbe\xe6j\xca\xd2\xcf3\x84\tp\x87\x87\xd3\xe7Y\xa5\xbe\xaf\x9f\xe5\xf1E-\x02\xbdy\x05Cy\xd9T\xdf\x18ȯ\xdd/(\xc0\x9d\xec\x86uc\xd4n5S\x9b\xb5S\xb0~觾\xf5\xc3\xf0\xbe\\s\x18\xe3?\x8c\xe4C۲;q;J\xf7SN\xaeq\xc6|\xc3ښ%\xd2\x00\xae\x81V\x1a\x10\xa8\x9azw\xcdgl\xc5d\x8b\x860xnb1\xc3Us\xcebs\x13{\xca99g\x13W\x9cr\xbe\xe1\xf4\x8b\xa6T\x1f\x93\xc7\x12\xb8\xe0\xf3z>_Ec\x15G\x81v{/9B\x1f\v-\xb8J\xee\xc0\xd5Բ\xcb[q3,\xb4q\v\xb45\u007fM\xf3צ|Y\xf3\x15h\xa3\xb5\xd7j5\xbfP\x8e\x80\x8dz\xeb?v\xf0\xf3\xb7\x00\x00\x00\x00\x02\x00\x05\x00\x02\xff\xff\x00\x03x\x01\x8c|\t\\SW\xbe\xff\xf9\x9ds\x97\xec\xb9\xd9\x13B6B\b\x18 \x90\xcb*K\x02\xa8DD\x04ET\x14\x88\x16q\x17\xadmm\xebX\xb1\x8em\xe9\xaaS\xb5\xd6.\xe3t{]\x1c\xb5\x1bc\x1d\xffUg\xb1\xb3\xb7}\x1dg\xeb\xa7\xcf\xfa\xe6u\xfa\xfa^\x17;\xd3\xcet\x91\xf8?\xe7&(u\xfeˋ\xdcܛ{!\xf7\xb7\xff\xbe\xe7{\xce\x15at\xff\xa5?\xc3n~\f\x11\xe4@S_Ap\xe9B¢\xb1&%\u05c8k\x97렋#\x86\x03\x1c\xc6ܱK\xa7\x12\x1aQ\x93\xe4@\xfd\x90\x05\x13\x14\x8f~\xfe\x1b\x88F\xe4\xa8t>\xd6W^\x16\x81 \x91IE#\x96c^l\xb3\x1ap0\xaf\x14C\xe7M\xf2\xb9\vּRwN4\xcfj͋\xe6\xb8K\xf3\xac\xf0(\xe7\xfcꕂʀ\xd1\x18\xa8,\bU\xe6IR^%\xa2/\x8cv\x92\xa7\xf0\v\x8a,\"\xaaN\x04\xb9\xfd\xa2\xa8~C\xfd\xae\x1aG\xd5\xc3\xeamj\x82\xf6#\"\x91w\xc9\x05\xc2\x11\x01c\x91P9d9*C\xb4\xef|_\x9f\xb96z\xbe\xbc\fH\x90\x04\xe8\x06mE\x9b\x8apIѵE\xfc\xd8\xf8\x05,\xb1\x8d\xddCF\x88\xfb\x82\xdeÍ|\xe8\xc1\xc4BBrr\x9cv\xafG\xe4\x04\x87Cp!\xe08\xfey\xa73`\xf3\xe5\xaa8\xb5\xd5j6\x1a\xb4\x9c\xe5yM\xa7\x04\x92\xa4ө;5\xa0Ʌ\xa4\x1e\xf4\x87u*N\xe7C\xd2a\x93\xcbf\x8a\x9b\xe6\x98Ι\x88Τ3\xf1\x0e\x8b\xa0\xe1QT\x8e\xcb\xe6\xda\xdah\xb4\xcf$\xcb\xd4H1e\xd7\x17\x8bQa\x99\xcc\xd1>\xe9?\x1c\xb51\x93\xd9Qk\x92\xa3\xecPV\x8e\xd9%\xa6E\xc0F\xb5\xb0([e@\xd9d\xa2l6\xa0\x1f\xc9\xef\xe3\xe0L\xffz\xee\xfd\xdd\xe9w\xe6\xee\xeaH\xa7\xc1Ӕ\xfe\x18\"s\xef\x9f\v\xa1\xee]\xdd \\\xfco\b&\xd2\uf42d\xe9c\xa3\xe9n8ĶQH\x8e\xc2\xd1t'\xdbF\xd3\xc7 \x89\b\xdari\x94\xfcQ0\xa3\x12T\x85\xe2襄\\\x11-\x82\xa2h\x00\x02\u0086h=\xd4G\xbd\xe0\xb5\xf8E\x10E}\xe9\xfa|#\xb5\x1a\xbe\x84\x00!\x10Q\x93^_\xbd\xfe]\x17\xb8Xp\x94\xa9\xb4I\x97l\xb5\x16\xd5\xd7[J\xabU\r\x85\x9b\x02\xa1\x80/a0%}>og\x00\x8c\x01_\x00\xab\x02\x15\x96M*\x83J\x10x\x15\xfd\xa3\x175\xfa\xa4\n\xd1(\x92\xa3&f\xa3h\u007f\x1f\xb3MD\xf9@\x8dg\xa6\xe6\x90M2\x8d\xb3\xbe\xf34\xd2~G/o\x8c\x9d7\xd52\v\xf6\xc9}4\xf2\xe8?j,\x1ao\xa2-XIC.\\\xe9%\xb2\xa9\x94T\xd2P\xac\xae\x94m^p\x88\xa5\x106y\x89XA\xaf\x9a\x1a\x01\xac^\xec0\x19\x80\xfc1ַ\xb3\xfb\xce\xd8P\xaa\xdb\x1fX\x90Z\x1e]\xb4\xa3gJ\xf9\xe2\xeds\xee\x1c|\xa6\xa5\xb9\xe6\ued85\xb7/.=j\x8d̐\xfb\x16\xa7ͦ\xc2\xe6\xf2E\xf3\xe0\xd5\x19\xd7vW\xeb\xfe\xf2\x8eڜc:$\x05rL\xf0\x82\xb7y\xfe\xfa\xb6\x96\xe1y\x95ꗎ\xf2S|\xbb]EB\xba\xd7\xd7\x9b\x9a\x95\xd9T\x85\xff\xeb\x155\x04\xe1\xab\xd6·\x1f\xeel\x85\xd7\xf2\xdb6͙\xb9\xa1\xbd 8scǬ\x8d\xb3\xc3p({\xe5L\x90^i\xdb0+{\xa5=|\t]<\xfb()\xe6\xf0\x93OB\x1b$\x1f\u007fr\xed\xa1\x1b\xe2\xf1\x1b\x0e\xad]\xfb\xec\xf5\x8d\x8d\xd7?;\xde\xf6\xe4\x130S\xb9\xf0\x1c\xbb\xf0\xdc\xc4\x05\xf2\x9d\xaf\xdahZ\xd2\x17A\xeb.}\xc8o\xe5\u007f\x8dL(\x80桑\xc4\xec\xb0\xd3\xd1\xc6\xcd,\x9e\x99ԴV5\xb56\xfb\x9b\xa1\xaa\xb9\xaa\xd9\xc3\xd7շrIԊ\x8a\xa5b\xac*.\xce\xf3'!9ߓ\x94\xf2\xfcy8\xaf\xa9\xa9\xdc\xdc5Ǯ\xfc\xad\xb1\xb5\xbe\x8e\xe7\xcagڌ\x9d\xe5(z>N3\x8d\xfe\xc4Y,1\x93Ig\xfa\xe8Q\u007f\xdfy\x13=\xaf$\xadt^:oR2\x92\xc6\x17\v\x99FRI#\x88\x1a\xca\x12\xa3\xa1\x05\x06b\xa3gC,\xccX\xb4])|\"4\x92j\xd9@D\b\x86\rD)\x83OI\x81\x8a\xfc\xba\x9e\x9a\\W\xb4yJ\xcf\xe0\xedS\x97u'\xfd\xe9\xad\x15\x9dչ\xc1\xe6Tzk\xe1\xec\xe1V_}In\xac\xff΅\xf3v,.\x8bu\xaf\xab\x87\x94;\x92\xe7\xd6\x1c3\x16U6\x85\xc1v\xfe\xe9\xf2\xd4\xdaou\xac\xfa\x97M\r\xdc=T\xb3\x96P\xb011\xa3(\xbf\xa1ص\xe8\xeb#\x8e\xc2*\x1f<\x10\x88'\xe7\xd7T-nʿ\xd0q\xf3`ϔ\xfc\xd9\v\xae\xa9\x9d\xb5y^$2o\xf3\xacĺ\xbe9\xf9\xe9\xbb=\xf5\xd3:\xa2\xcd\xc3\xcb\xe6\x17\xa5\xdf>T\xd2Z\xee\xae\\\xb1\x17\x01\xab\xbb\xc0\x8cOДD\xee)\xfe\r\xfe]\x9eD\xf9a\x1eO.\xb8\x18+\xe56\x9a)\xb6\xd4*\xd9\"\x9b\xad\xaf\b\x01\x9a\x9b>\x81#\xf4{,('\xa1\x17\f\u007fU_\x88ӳ\x86/\xf9/P&4\xff#F\x83\x91\xd9L\x160\xb3\x9f#XJ\xe6\xe6\xb7m\xec8t\xf7\xeeP\xeb\xea\xe9\xcfvll\xcb\xc7\xe5[\xff\xf6\xde[}\xa7ӱ\xcf6\u007f\xf8\xef\xbf\xed\xef\xfd\xcd\xf9\v,.\x005\xd0\xefw(ߟ\x9b0\xd0\xef\xd7^8J\xcf\xda\f_\xf0_\xb2\x1bH\xff\xc6\ue42d\aA\xe60\x1c\x96\xbd\x04;\xf2gm\xecxv\xfa\xead\xfe\xae{\x0eul\x9cE\xef\xf0\xad\v\xe7\u007f\xd3\xdb\xff\xdb\u007f\xffp\xf3g\xf0\x9b\xd3}o\xbd\xf77E~\xbc\x99\xb3\nVd@\xf9\t\x9bO\x02\xaa\xbeaİ\xcb@T\x06\x9f\x00\x82@\x85\x88Ǣ}@#\xe5=\xc5\x02!\ao\x11\xb5\x10\xb6\x84\xaay\x82\xf7G\xe0\x9e\x9c\xf4m\x9f\xbfp\xf4\xd1c\u007fM\x8fz\xe0\xf6\x88`Mo\x1e>\x9d\x9b>\x9e\x82\xa1\xf4\x81\x14\xcc\xc8==\f\xa3\xec^C p\x98{\fiQQ\xc2)\xe9AP?K>A\xe0G)t\x10\x11\xa4F\xdf\x17ȋ\xd4\xde\xe7Y\xc3\xe8\x8b)]\x16h\x1b\xa0AI[\x83)\b\x1f\xa4\xb7\u008e}\xb0#\xbdu\x1f\xbe}\x1f\xecLoٗޖ\xb1S<\xfd\x05܄>F:T\x93\xb0\xben\x00\x89\xeap\xd0@ܺ\x88\x0e\xeb\xe0\x01Ad\x05[/Y\x93\x03\"H\"\x88\xc2\xc3:\x14\xfd\x88\xdd'\"\u007f\x14\xebc\xde\r9\f\x84ٰ\x1aV\x1b\r\xe1\xa8\xec\xba\xd3\xe8q\xdbU\x1f;\xaan\xfe֖\x9a\x86o\xef\xd8\xcaz6\xb3\x19\xbc\x89\x9b\xf1MTd[B\x8d\xf9\x04\xeb\r\xaf\x00\xcd1\xa0I\xf4\x1b\xea\xeeʀm.\\\x807\x9f~\x9a\xfd~\x06o\xa0\xaf\xa8\x0f+\x13\xee\x11\xdb.\xdbA\x1b1\xd9@\xb3\xef\x14z\x03\xe1\n4\r\r\xa2\xeb\x10\x87\xa8\x88c\x14o \xfe!\xa3\"[\xa4\xaf\xef#&X\xf5U \xe3\xfe\xab\xf1\xc5\xf0\xd5\xd0\x02\x90\x15!\xfc\xbe\x12\xdf\xde\xe7\x01\xb32\xab\xc6\x04I\x88\aĐ\x8c\t\xe4\x9aX\xb4F./cE\x0f\xbf?\xfe\xee\x8b8\xc0\x8fM\xd4#\x8c\x96]\xfa\x90S\xd1zdAaT\x90\xb0y\x87Q\x91T\x04\xd6a?\x05\x03\xc1MFѵ\x89w\xd1\"\x1cg:+ŗ\xc5 6\x00\x95\x0fhA57\x02\x95\x18\xcc<\x8dx\x99V\x8fL!1`N%o|a\xdb\rG6\xd5\xca\xc3\xcf߲\xf9\xe8uS\xc7-\xde\xe65\xb3g\xaf\x9d\xee\xf3M_;{\xf6\x9af/~\xe7\xf9\xf4\u007f\xfehh\xe8G\xe0|\xfeyp\x9e\x1eZ~:\xfd\x9f/\xec{wזּ=\xe7\x1f\xd8\xf7\ue7b6\xb6=\xef2\xbb\xbe@\x05}\x8d\n,Q\xbb\x06\x12f0\x82\xc1\x80֟\x84K\x80o\x01@ \x81\x1f\x12\xc0\t\xa07\n\xc8I\xd3\xe5\xd7\xc0\xda-k\xac&\x99\xda5\x104\xb1\xc44\x10#\x84AƯ\x1d\x05B\xc0\x94\xe3\xf6\xda\xf6\xa4`\xdb>\xb2;\xb2l\xe9\xa2\u007f\xac\xe9ۿ\xb8s\xdd\xe9\xf9\v~ٷ\xe4\xb6\x05E\x95\xcb\xeeY\xb4sg\xd7-\xf3\x8b\xf3\x9b\x97\u052cy\xbcmIA\xdf\xfa\xad3\xd6>\xbe\xbe\x86Kͼqa\xadN\xb0\xbf\xbc;u\xf0\xbaD^ɳ\xa5\xb1¶\xb5-Ӗƽ\x0f\x14\xb6\r5Ṱ\xf5\xb6Tm\xf5U\x159\xab\x96\xeeD\x189\x10\xe2\xe2\xfc\xabHC\x1dߑ(V\xf7\xb0$\xc5\xebM:\xad\xe8'@!\xb0\x94\x03\x9b\x9d\xb7;\xb1s\x80\a^-8\xaf\x03\x9e\xc7&Q\xc0,P\x98\xba\xf2\xf9\fpe(\x9b\xee苢5\x05{\x05MAS\xa0\x12(L\v(\x01\x14d\xad\x8f\x8b\x9f=:އ\x1f8q6\xbd\x85p<\xa8E\x9b+G\x9b^\x0eI\x86:\xbfK~\u007f\xb1\f\x06k\xb6\\?\x94g-//\x95\xc6\x0ff|\xb4\x83\xfa\xe8\xef4\x86\xa7P)\x9d\x059\xc3<\xed\xeb\x10\xce\xcf/\\\x19\x84EA\b\x06/\x18\xc1\xc8\x1cf\xa6`\xd3\xe8ل\x90-|]\xbe`\xdb$i\xb2\xaeac\x00V+d\xea\x18\x06\x1dY2\xd2\b\x83I\x05\x83AČ\x1b\nX\x12\x8aa\v\x83ڍ\x80\x03\xd7<\xb7-9\xed\xb6\x9f\x8e\xc8\xd7,\x9e\x1b\b,Zv͔9\xdf\xea)=\xfa\xb4;\x1e\xaf\xb5\xf5V\xe3\x17\xc6?\f\xfbW\x91\xed\xd5k\x0f\xae\xbav\xec\x96\x16\x8d\xc5k=\xe4\xc8w\xeaC\x1d7t\xdd\xf7\x00\xafRs\xb5\xf8\xe8S\xe9>\xc1\x90\xa9\x1b\x1bi\xdd\xd0И\x8b\xa08\xaaN\xf8\xcb\xd7\xeb\xf2\x9a\xa4&p\xd4\r#$!?\"\xa8\xeaz\x8fG,\xde\x14vI\x9bD\x86\xe3\x14D\xa2\x94\x91\x18\x15=v^i\x97\x1c\v\x13\x87\xad\x14\xa8\xbc\x9c(7\x92o\f\xb2&\x15\x14\xd6R7\xda*\x97\xdc{f\xa7\xa5\x94\xc2\xe8\xe0\xc2H\xef\xe8`\xcb\x14+\xd1Yk\xdb\a\xea\x06\x0e\xac\xadk\xbc\ue261\xc1##m\xf0v\xfd5\xc9p\xa0\xf9\x9a\xe6\x96\xe1\x8eHh\xe6z\xbcq\xd5\x1b'\x9f\xb8i:\xe6E\xfe!\x9d.ұvǞ\xce\xfcxINݵO\xae\xbevl۴\x8e\x83\u007fM\xbfR4oۂ\x19\xc3\x1dS\xa2\xb3Re-;W\xb7(~\xebE\x88\x13\xa8\xdf\x04\x14y^@\xac\xbe\x9a\xa8\xfa\xc0\xab$\x15`\x1e_G2\x15\x87\xb52\x96.TEVl\x03\xac\x9d\xd9\x00\\X\x1e7\x90C\xe3gy\xe9\xa9\xdd_\xbd\x8d\bZM\xed\x86h\xbd\r\xa3\xa9\xa8\x8d\xe2\xbfy\xde`l\xbdQS\x98\\\xffc\xfe-\x1e\xbf\xcc\x03\xcf\x1bۥv(\\\xefj\\\u007f6\b\xbf\n\xc2q\x16\x1f\x92\xcb\xef\xc2.\x8dwe\xcd\xfe\x1a\xbc\xa8\x06\nkjj\x925\xa4\xe6\xfe\x16(h\xe9i\xc1--\xa8d\x93\xd5U\xb7\t]6v_\xdfi\x9aʵ\x99\xaaD\x8fh&3\x19\xe9!\r&\x96\xd5W\xa2G\xf4\x92\t\xabW^\x01~dRU\x8f\x82\x01 O\xb0M\x00\xc6\x02\\ٽwcs\xe1\x8cT\xcd\xd4\xe1\x05\x95-7=3\xb4\xfe\xb9\xeb\x1bJ\xdaWN-\uf247Z\xae\xdd\xfb\x9a\u007f\xda\xca\xe4\x8c5\xc9\xfc\xf0̡\xb8\xf7\x96\x11\xb0\xaf\xde\x1al\\ \x97͏\xe7\xdf\xc2\xff\xbax\xe1\xb7\x17\xb6\xac]0=\xd7\xd7>p\xc3\xcc%{W\xd6T-\xffΒY7\xa6\xdar}3{\xd75/ر\xb0\xf8\xeb\xa7*\x17Ń\xc1\xa6%5\x15\xddɄ\xcfP\xff\x10\xe9^\xbd\xbcvnBv\xda+\x9a\xbb\xab\x96\xafF\b+6\xe5h,\x16\xa1\x06\xda!\xbc\xb5\xb6a\x14\x97(([\xaf\x93\xfc~?\xf6W\\\xefv\x8bS6\x85D\xe9z1gr$\xcaJG\xcb\xc0*\x8e\xe9\x9e-iJ\xaa\u007fc,\xc1\"p\x9218n\"\x14K\x8bY(\xde~x(\xc2\xeb\xad5\xed)%\x10㛞\x1cZ~x\xa4-\x1d\x9a\b\xc4i\xeb\x95@$3V\xbd\xf9\xea\x937\xd2@\x14\xf8\x87\xf5\xba\xfe\xef\xfdns~ci&\f\u007f\xc0\xc2\xf0o\xd0Z\xd8=9\f\xd74#\x84&\xb8\fnH\xe9Ӎ\x89\\\xa9h\xa4hW\xd1\xc1\"Ϋ\xd9g\xfdgx\x91\xd02|\x11|\xc8%e0F<\x832\xe4\xf7b\xff\a\x98a\xfa\xff\xc1\x8e\xff\x1f\f!\xbbÕ\x01\x83!P\x19\x0eU\xb0\x13\x15\b3\x9c\xa6ȫC~\xb44\x11\x95\xf2F\xf2v\xe5\x1d\xcc\xe3\x1c\xfbN\xe9\xdf\xd0\xe3A\xfduzܩ\x87iz\xa8ӃO\x1f\xd5c\xbd\xfe\x9b \xcet\x05\xc4\xe5>d\x962@.~\x05ʱ\x90f\xaf\xf7&C\xba\xaby\x9aoB<\xd3՚L\xc6|\xf8\xa3\xab\xb5P\xb0,\xfa\x98\xc3\xdc;H\xa0u\xbb\f\xa9\x12*H\x00\x00\x06\x9e'\xc20چp\x19k8\x9c\x9f@'\xd9@F\xc8\x1bt@\xc1\xc7\t \x02\x02AJ/\xdd(3Q頿!*\xcb\xe0T\xfa*\x15\x19*\xc1\xa6\x06\x0e_\x1c$\x0f\x8f\a\xf0\xb9\x8f\xe1\xe9!8\u007f0\xbd'\xfd*\xc2h\b\x8es\x98|\xa0\xf0EK\x12\r\"V'\xe8/\x9fA\xf0C\x04\xcf \xb8\r\xc1\x16\x04+\x114#\xa8E\x80\xb8\xef\xfb\xf8(\x8f\xb7\xf1\x80x\x89\xc7\x02ϣ\xef_\x000\xc20`@Q\x05\v1\x8a\x81E}\xf6%\xb3Z\x90\x01\xb1@\xb7!\xf20\x93\x84t\xefݛ\x1eڷ\xef\x9ft\xe7\x15\xdd\x05\x10x\x0e\x80\xa0aa\x9b\x80\xcb\xd8\xc8\x01\xff\xffu\x97\xaf\xd2ݢf\xca\xc3\x10~w\xdc\xcfn\v\xf7A3\xac=\x98\xf6\r\xa5\x17R\xddC\x97\xfeLB4\x9f\x19\u007fИ\b\xc4\x0e\x14\x16\x86g d\xd5\xce\xc8m\x886\x80U\xeb0\"\x10\x10*y\xa8\xda\"u98\x14\xa7ڽ\xc7(\x16\x86\xc0XJ\xb3\xfc\x96\x15\xcaL\xa9f\x93\x11\x18\\F`\x06\xe2\x81\u007f\x8a\x17\x971\xdc\x1c+\x9b\x16\xb1yk\xe7\xd7\xcc\xdb7#\xdeubi\x1f\xadM\xee\xaaΪ\x8a\xe6\x02\xc3\x0f\xff\x99\xf3\v\xd5.\xe9\x9c=\xa5\xa8e\xfa\x9cʲ9\xb5\xbe\x8a\xbc\xddS\xea\xe4\xd4]\vZV\xf5vGKZ\xe2\xcd\xc5\xe6\xf4\x9b\xffL\nb\xca\x05\xb4\t\xdbioY\x80V\xa2\xe1D|\xa1#\x15\xf69`[\x18\x1c\xe1\x193\xc2\x0e\xd2Q\xeeG\x80V'VC]\xa2\x03:\x8au\x8b͢y0\xd7h\xf6\x99O\x9a\x89\x80rA4\xe7\x9as\x13U\xf3Hb\xfaL\x14\x95O\xc7O\xb3!>5D\u007f\x9ft\x9a\xe2\xd0\xd3\n\xb49MM\xb2\xf1\xb4\xc9A\xaf0\a\x9c\x96NO\xaaxWU:\a1@.ؾ\x01\xe79\xe1r\xf1\xab\xb61\xe8\x9f_\xc0\xa0\u007fU\xbe\x1c\xe3\xecf\xb0\xdaٕ8(M\x1c\v\xdb-\xa1\xda\xf6\xa5SKfV\x04\x04\xce\\պ\xa8r\xf6湑\xba\xe1G\x97\x95\xf4Ν\xeep\x00\xd8\xf2\"\x8e\xd2\x19Qg\xcf\xfe7\xb7<\x93N\x1f\xe9\x9d\xf3\xe0\x9f\xefo\xd8<<\x18\xe9\xf9í/\xa6\xdf\xff\xf1\xd0\roC\xf5\xe9\x87A8\xb9\xfa\xa2m\xf9\xfc\xea\x8eX\x0egȝ=\xa5my<\x17\xbfݸu\xe3\xd2d\x917\xd6\x14\f5\x97\xe7\xd6,\xbf\xb7g\xf9\xd37OS\x19L\xaat\xca\xee\x96T\xa4rZ\xa1\xb1a\xe5\xdd\x1d\xf7\xbe\xb3\u007f\xce\xf0k\xe9O\x1e{\xe0\xbf\x0e\xce58}\xa6{\n\x8a\x87~\f\xae\x17O\xc0\xf4\x8f\xefX\xfe\xbfҟ\xa6\xcf\xee\xdc\\2o\xd3\xf4q\x95~j\xff\x8d\xcc'\xf4Žß@\"\xb2\xa2o'\xf2\x05~\x06\x86\x19V\xc0V2S\x053\x01T\x80\f\x06\x82\x90$\u0088\xb8K|C$J\xb1r\n\x9a\xa4(\x89\xa2\xddh\xf7٣\xf6\xfb\xecߵ\x9f\xb3\v\"\xe1\x14\xc4\xc0\xe1\x01\x83_cH\x1ax\xab\x95'\x1c2\xa3x,^\x1b5\xd7F\xce\x03\xc5\v\xfdl\xb4P\x13\xa1\xc9bb\x89B]\xd8ק\xbc\xc9\fJ0\xca\x17d5\x90RBY\x17\xfa\x95\xbe\x83\xe3\xe7\x1e;\x81]\xcf\xe2\x9c\xf4\x8a=\x82\xc9lV\x89f\xb3I\xd8\r?M\xd7\xf1'\xbe\x9a\x86W\xc3\xd3\xe6\x8a\xfa&\x9f\xaf\xb9A6єB\x04\xbdF\xf5\xba\x9dƚ\x03\xe5\xa1(Z\x96h\xd4z{y\x01\xde\x13>\x17\xf0\xefX\x1a\xe7OY\xbc\xc14b¦\x15\xe5\xdb\xca\xef+\xc7R98\xf3{\x1d\x83e\xda`\xea&'h\x9d\xa0r:Q~\xcaa\xceK!2\xd1E\xfb\x95\xa1\x82\xf4w\x8a\xe9\xfa\xa4\xbf3\\\x11eU\x86Օ\xc0D\x9eٲ\xd8\"`\xc9b\xd3\b\xb8 0\x11]\xaf\xc1}#?\xde\xd1\x1cn[7}\xfaM\xbd\x15M[\x8e\x0e\xa7\xdd\xc7?\xeb\xbev\x86\xef\xc0S\xe7@\xb3p\xf3L\u007f`ַ\x96\xf0c\xe1\x05\xf7\xach\\;\xb7Z\xadѕ̹\xb1g\xf0\x91uSI\x9b\xb7nQݍ+\xc7\x1f\x18\xbfP\x98\\\xdeP\xd3\xdf\x12\xca\xe0\xed\xad\x14\x13\xecW\xc6o\xed\t\xeb\x11\x03\x18\x126g\xd2`p\x1e\xf7Cʿ\x81\x82\x82\t\xaf\xf9\xfd\xa1#!\xe0\x17;\x87\xf2\x03h\xc0\xc3\xe95\x03\x16\v\xab(\xf4\x87)\xc6|\x13a\xfe\xa0\xef\xe5eW\x80@\x15M\x052y\x14lgt,q\xad9\xbc\xa5\xa5\ue1a37,:\xd8\x11\ue63f\xb4\xee\x89\v\x8f\xb4/x\xe6\xab\xc7\xd6\xfedNG|\x0f\x1f\xee\xda\xff\xa7\xd1{\xde\xde\xd7\x11tq%\x96\xfe\xaa\x8cu\xaa\x13\xdeHDW\xd4k\xc5+J\xa4\x12\xf0-\xd6\r\x15G\x06\x10\xca\xcf\x190q\xf9\x03\xa2e\xc2\x0e\x13\xaeQ(}\xc6n\x99\xae\x04KU~&XHp\x12\xaa\xac\xa6\xd61\xc0\u007fTo\xb8\xe1\xd6Y\x8f\xfc\xf5\xa9\x9e\x811\xd0>7\xfc\xab\x85\xc9\xfcy\v\xfbJo>\xb1\xad\xb9\xfa\xc6Wo+\x9e\xd9X\x9d\x93>K\xb8qٜkѬ\xfa18\x0e\x1f\x02\xfb\x8fW\x15Oy\xcc\xe4u\x18Xp\xdd\xfa\x87\x87{x\x95\x96\x87\x97\xb2v\xe5\xce*\xbelH\x04\xd5Ћ\x96\xebO\xe9\xe1\xa4\x1e\xfczPq\xbd\xfc\xa0\x8e\f\x18\x99\x8bU\x1a\x81\xf0\x99\xca$+|q6\xab\xfb\xd8.c$:\x1c\x96\xe9\xbb̝=>>t\xfc8>p\x1c?;\xdeC9\xd2\xfdx\xa5\xc2C \x84\a\x95{-H\x14h\xf5\x11=ޢ\u007fH\xff\x9c\xfek=\u05ed\x87\x88\xbeN?KO|z\xc0*\xad\xa6\x97\xc7?'@\x1d\xfbEB\xd2KI\x052\xa89\x81\x10\r\x93\xe1t\x8c\xfe\x00\x95\"\xd2w:\xf6\xe9\xe9\xd8@\x1f\xc3\x12\n\x9dRm\xa20\xcd&\x9a\x1cxp\xfc\xe3C\x87\xb0\xf9С\xa1\"n\u007f\xd1\xd0P\xd1\xd7+\x8b\x98\xbf.%\xd3[`\x93¥F\x13\xb9\xe7l\x90\xb2m\xb0\xe1\xa8\r\x8c\xbd:B\x90N\xd2aQ\xa7'\xe2\x00bt9\xbb\v\x9b\xeaS\xa8A2\x89\u0560\xbaҪ\xe5\x91\xdbs\xf3:JB\xb3\x1a\n~X\xb7\xea\xfe\xf4\x16\xbd\xf6\xa0Zk\x89-\x9cFY\xb4\xfeU\a\x96\x97_\xce\x19\xee1%g\a\x13%X7\xa4\x03\x1d\xd2h%\xad\x16\xd8x\x18\x8b\xf4\r\x19\x8e\x1a \xca(ˌ\xe5\xf5X\xd2JI\x15\xeb\x10:z\x80U\xb9&\x16[\xfdĵ\xd7>\xb5\xb6\x02\xbf\xf3L\xfa\xbfά\xa5\xfd\xcb\xf1\xec\xb3`\xfbɚ5?M\xff\xf7s\xa3o?\xd8\xd5\xf5\xe0ۣw\xfe遮\xae\a\xfe\x84.\xf73\x99\xda\xd5@#hF\xa2ض\xb8\x10\xd5 6A\xbb\xc2\xfd\xba\x1b\x12J\x90\bF\xb1W5\x98cL\x95\t\t\x01\v\x82\xe8J\xa9\x89H-\x9aa2&\xeav\x06ed\xa8\xc8\b0׳\xd1\xf9e\\\xc1\xc9շ\xbc\xbe\u007f\xdfqX}ۏn\xa9\x1f\xef\xd8\xdew\xd7\xe2\xd2'\x0e\x1f\xe44\x8b\x9f\xde>{\x9c\x92ȵ\xeb\x0e\xa6W\xf8[7v\xdd;\x9a\xb1gz\x99bO\x0f*G5\x89\xbcBjO\xf9\x94\f'e\xd8 CT\x1e\x90\xb1w\xb1u(V2\xe0\xe2\x82\x03\xbcѢFѸ\xfc\xcf6\xa50';\x1f\xfb?0n\xf9\xc0\xbd}\x91\xd1ޛV|_6\x15\x99\xff\u007ff\xde\xf1\xef/\u007f\xbb\xa0\xff\xbe\xfa{g\x9eن\xf1\xff\xd4\xde\a\xa9\xbd\x8d(\x17ݚ\xe8Q\x1b]F\xfc\xa5\x11\x8c*\xad1i\u0529u \xea\xc0\xbeX%:E,\x8aj\x03|`\xf8Ҁ\r\xec\xaaA/\xe9\xbdq\xef}\xde\xefzOz\xcfyy\xbf\x17$\xe8Ń\x1e\x1d\x91R\b\x81\x8an\xb9)B\f\xc0\xaaT\x16\x01\x9e7_F\x80\xbf\xa9\x89Q\x18\x1b\x8b\xb2s\xac\xc9FX\xd1V\xdc\xe4\xb7gX\xef \x84\xafpƴ\xdf\xc2 h\xd2\xe5]+\xab\xe1\u07b7\xd3\xdf{\xef\x89\xde[\xbaB\x8c\x88;\x88\x97\x8c?\xc1\x8f\xfd\xe6\xcd\x05\xb7\xf65Z\xc77\xe2%\x8f\x06\xa7\xaf\x98\x9e\x1cLx\x94z\xbb\x92\xf2\xa9?\xa7=Y\xa6\x18%\xcc\bTS/ZQy\xb2\xf2R%NUB\xbc\x12\xa6\xf4\xaa\xddC\x15\xa6)|x \xdf\xcf\x19Rj\x96\f\xf2y\xfaCK+\xb0Μ\xf1\x9e\xccP\xddeB\x9bP`\xc7d\vg\b\xb0,\xaa\vfy//!?\x9f>rl\xd3\xf0馼\x05\xa9\xa1\xd8\xc1\a=3o\xee[\xb4c~Q\xe2\xba\xef-\xdd\xfcj[Kӓ\vo\xbe\xcd;cS\xf7\xe2\xd1%Q\xb8}郫\xab\x83y/H>\xa7\xf1\xc6\rU\x9d-\xf1Pp\xde\xcaow/\xbcs V\x12|\xd8\x13٘\xaa\x9d\xd7R\x17\xc8oOݒ\x99\xcb\xe1\xcc\xd4o\"\xeaOČjxC\rs\u052f\xab\xb1\xa4\xf6\xab\xcb\xd4\xe4\x88\xf0\x89\x80\xbfͰ\xba؋\x13\xe4]\x82\x8d\x04T\x84,\xc0+\xf0fL\xf0\x04\xef\x83E2\xc0eqO,\xc3\x18\xd3đ\x18\xab\x9dm\xa3\xac\x99\xd3&\x1a\xc0\x1b\u007f\x98\xee'\xe9\xf4\x00o~\"\x8bu\x8eQ\xac\xf3\x0e\u007f\f9X\xef\xb4\xf7\x82R\xfe\x86]G\\Xr\x81\xa1W;\xe84\r\b\x16\x1d\x1b\x01\xc42Yp9\tL\x13\x04\"\xeb\x9c\xf6\f\xa8\xe1\xdei\xde\xf9\xf3;F~\xb2s\xda\x0f\xbf\u007fd\xd1\xc8\xdcB\xe0\x8f]l\xbf\xf6'\xbb\xe6ξ\xf7\xb5\x9b\xc9\xe1\x8b\xc9#\xa7\xaa\x96\xdd\xd1E\x8e!P\xc6^w)=tmB\"X\xad\xe6P\n6\xc0Q\xb8\x00\x1c0\xed\xec\x14\xaf\x83\x04`\x88\x1a\xb6\x19N\x1a\x88!!\xa8\x93<\x1bc\xbd\xc4\xf1\x1cQ\x1f\xbb\xf4\xee\x8b\x1aSR\xcdZ\xaa\x86^RcbF@\x94\x86*Ghef\xa3\xadHv\xb4\x95%%\xa8\xe02\x04\xe9F\xf3\xb7\x11\xaa\xc9OϤ\x1d?L\xbb~\na\xcf\x14\x93\xb1ԗ\x99,\xbb\xb8t\xf13mm\xcf,E(\xe3'!\xadp\xf9\xe7\xc6$\x15\x1dWHF\xda\xe1^\xa6\aF\x11X\xaf3\xd3C@\xec\n\xb2{\xd8Γ\xcbNG\xe9Qn\xc8*\x02'N\x13_\x12\x89ȅ\xac\xa1\xfcдPw\x88\x17\xed\xa1\x90]$^=2\xc0\x06:3{\x8aj\xa7\fQ\xa8C\r\x92\xc1P<\\\xbc\xad\x18\xfb\x8bˊ\xb1\xb1\x18T>/\xd3\xd1\xe2\xb5\xfb\xa9\xbev\x89\xeaj\xd7#/\x11C\xa1\xac\rB\xac-\xd1\xd3!\xc9cF\x8a\xf6&j\x04\xb6ό8\xfb.\x8f9\xcd\x0ej\aJ\fPt\xac4\xd8+o\xcc4\x16/qP^\xcdBmDhu+\b\v\xa2\x01D\x12\xbc|\x85\x14\xfd\xf4=\xa3\xc7e\x118\x9dd\xd1\xfe\xec\xb5\xf4\xee\x13iC\x8eV\xa7\xd5j5.\xc3_O\xa4o\xf9\xe99\x9d\xd5d\xe0y\xa3ա\xa7\x96|\xbafê\xfe\x82\x82\xdekV\x94\x93ki\x03~V^\x1b\xad\xae\x90\xab\xa3\x1b\xaa..\xa1\xd7o/\xbdf\xd9\xe2\x82в\xb5\xc3\xe5٘H)\x98r{£\xd6H\x1a\r\xaa\xa6(BU\xc3\x03\x8f1\xbd\x0e\x1b\xd0\b:\x85\x88B{Z\xa9\xb5\x18\xd2\xd2\x1b\xf5\x8cSܦ\xe7T\xa2@-\xf1\xa2 d\x86\xe2\x12W\x88!\xac\x02\xac\xe2\xd4\xf4T\x16\xef*\x83mj\x97X\xec\x1b\xa3q\x16\x1f\x14u\xd1\xcf\xce\f\xabO\x014\xfd\x91\xe9\x0fp\xa9\xb7ҏR]阻\xf7\f\xb4Cǫ\xe9\xc5x\xcb\xf8N|\x11\x1f\x1f\u007f\x13G\xc7ۙ\xe8\x19\xf9\xab\xa9\xfc*\xb4)\xe1\xfaD\x03\x9a\x84Z\x97T='\xc2~\x11\x12\"\xb8E\xa01#2Ѣ\xa2\xc8\xc3\b\x869\x18\xdc\x18\xb0\x841\xe2z7\xf3\xb7\xf3\x0f\xf0dN\x96\xd7\x13\xf9*\x04\b8\xb3\x98\x89jsm\x1fd\xe7\xeb\xa8\xe8t/3\xd93\xf4\x1e\x1b\xae2Ii\xf5\x05\x19\xbb\xa1\xfbg\xe9\xe6\x1f\xa6\x9b\u007f\x89\xdf\xc2\u007f\xbe\xb8c\xfc5\x1c%\xb7#\x94\xc5\xed\x84ų\x9av@\xbf\x9a\xb6\xdd\x15t\x94\x15\xd7\xce\xd1~W˩T\x1c\aC\x1a\x91\x1b\xe0\tVeFz\xb5\xf4\a\xe8\x00/J\x1b2\xddŢ\n0\xcfLy\x04H\xfab7~m|6\xd91>\x15\xbf\xbd\x9b\xdb\xf6\xd4\uebf7g\xee\xf3D\xfa\x04\xaeU\xea[A\xc2\xc1\xa1\xbf\t\x9f\"\x90hz\x9f\x02\x0e\xf8\xcf\xf1\x17\xc2\xe7\xf0\xe5\xe5\xb9\xe6\x8f\x18-\xcfFe\x14/\xe1\xda\xf4\x1a\xd8{\xee\\\xfa\x84\xf0\xd5ޯ\xca\xd9w9\x10\xc2\x1f(\xf3ށ\x1f`\x00\txDX\x1d 83\xf1\x1d\x05\x06\x96\xe5\x1a\x86h-\x10\x84\x9fc\xff\v\xe3\xefN\xcc|\x03\x92\xd3'\xe0\xe7\x8a,\xf9\t+A\u007f\x83OS\xc2\x06\x01\vj\xf4\xb9\xf8%\xf9\x9c\xfb\"\x8b\x9c~\x97\x91\xc4!\x9b\x82\x95T\x1a\xf9\xdc9ؗ^}\x98\u007f}\xef\x97\x02\xfb\x9e\"\x1c\xe2\\\xfci$ \xf3\x18!\xac?\n\x88\xcd?S?\xb0\x1b\xcb,`\x8a\xe0\x97Cp\xf2{\xe9#\xe9\xc7q\x88\xf1\x9d\xf8\xdcx\x00\xc1\xa5\x8b\xe9\x13\xa4\xe3\x12\x03Bf*\x13\x0fp\x1c\xd3\x11I\xf6o\x999I\xc7\xc5Ä\xae<\xb9[\xb1\x1f\x8cr\u007f$f!\x80\xf4Ȟ\xd0 A5bD\x03\x8c\xb2\x8a\xf6\xe5\xfc\n\xa2\xbff3\x96\x94#\xaa\x94ib\x12\xf3\x8d\xc1\xb9\xb3\xe3\xd2\xdbR\xa4\xb4\xcc\xc6\xfd1w\xde\xc2.\xb7-\xaf\xab\xabݓ\xc1\b\xfd\xb4\u007f>\xcc\xf5\xa3 \x8a\xa1nʵCQkp~\x85\xb1b\xb8\x02\veI]αKo$\xbczS2G\xa2\U0005a4e3\xeb\x91]\xf6Nϔ\xb9\x92\xe4\x91\xf2;\xb1hG6\x14g\xfd\x94\x8euikQ\xc2!\x12\x91\xce\xf7+\x88\x97\x01\xde+\xccbU\x1c\xae\xe6_M!\xda\x1e\x1a@dk\x8d\xec\xc4\x15R\x05\x9a;R\xf1\xe9\xd7\xcd/{\xff\xc3c\x9e\uab8ay\xf75'\xda\xc6\x06\x87\xf6.-KG\x1bV\xb6G\xee\xeb\xbdy\xa6\u007f\xe9\xfe\x92\xa6)\xd6pז\xee'\x8e\xaa\xc4\xe9-\xbd\xd5β\xbc\xd1\u009a\xa2Ew.\x1b\x97\xb6\x15̾~\xce5\"\xe7\xaf풗\xf4d\xf4\xa4OY\xa7J\x11c-\x03\x8dٵ\x10\xc1\xca\xeao\x80b\x03\xd5X\xc6w}\xfe\x83\x1f\xfd\xa8d\xfe\xb7:\xea\xae\tG}3\v\v\xa7\x16X>'\xa7/\xc6\xc9\xe9Y\xd3Rk\xee\xe9\x0e:\f\xf7hM\x96\xb2yM+3k Ci+\xb9H}\x10\xa7\x9d\xa7\x1f\xfdK¥\x8f\xf1\xf3Z\xab\xcd\xd1T\xf5\x86\xea\x91jR\x1d\xad\x8e\x86\x93\x9eE\xad<\xeb\xe9\xb3iO\xe7y\xd0x`\xdc\x03G=p\x87\a\xfc\x9e2O\xc2C<\x9e\x94/\x05'S\xe7R\xf8`\n\x12\xe1\x04\xa5\x18\u0098\x8f\xe9\xb9\xfa\x9e\xb9\x1aMޒ9\xf5\xc5\x1dF\a\x88\x0eG\xbd4}N\x9e\x1b\x90\x91rKTS\xe6\xc8ӵ\x922OD\xf1\x0f\x05ɧ\xfb\x18\xfcaʲ\xc6\xcfV\xbd\x9cVV\xbd\xb0\xe9\x126b\xa0&\xb0\x89W\x96\xbed\xd6Hֱ\xc2\xf0\xcdys\xa0\xe5\xae\xfa\xca\xe0\x01\xb2\xa5A\x19\xeeP\x83Y\x94\x1cT\xd6g\b\xe4\xe2Os\v\xa2\x03w\xf7ً\r\xa6\x80\xdb\xe4,\xaa\xf1\x1d\xed\x1b\xe9\xcao\xba\xed\xd7w\xad9\xb8\xba2ܼ :\xa5*1=BW3Ɩ}gih\x8e;\x9dȩ[\x9a<\xfe\x8a\xb3\xba\xb7\xe9h\xb0=^\xb4d\xed5\x83+V\r^\xb3\x9ak\xdb\xe8\x0e\xae\x9b\u07be\xbd\xbf\x12\x88\xc6\xe5/t\xba\x03f!ڹ\xaea\xe1\xddK+\xa6tnl\xad_\xdc\x92r3\xcb?\x11\x86\x96ɷn.\x8b;r\xaa\x82\xeeҠe,/\xb9\xcerE\x8a\xb4\u0560ݣ\xd1\x1a\xc3M2\xf7\xd9\xd7\xce\xd9[\x16DŁ\xc9be\xecO.(k6\xfb\xe9\xc0\x8e\xe30!\xc24\x1e\x94\xacp\xb0\xac\x90x^2JQ\t\x8bzu\xab\xa6ۨEH!Ti\x1e\x88\n\x15'\x12Ac#4賄b\xa6\xabe\x11\xae\xb2\x8a\xa8\x96\x01$\xa8f\xa0v\x82R$\xd0s;\x84\xdeJ\x0fá7\xd3G\u05fd\xf4\x92\x84\xab\x9f\x85U\xe9\xd0\xf8n\xf8bn\xbaW\xb0\x8eW\xa5?\xca\xc8\a\x83T>\x82r\x12\x06\x89\xf7\xf3\x18\xb5B7\x87\x91\xe2\x13v\xab\xac;\x98\a\x04\xeb\x84N\xc2\xed4\xf7\x8b\xd1\xf2D<\xa8i}E\v\xfb\xb4Oi\xf1\xa8\x16Vh7k\xb1VkG\xad\xc5\xf3K/\x94\xc2\xeb\xa5\xe0/\xed,\xc5\xc6RP\xb9l\xad\xf6\xee\x92`N\x0eg\xec,\xf4I\x0e\xa3=\x13u1\xaa\x99\xf2\x00BF\xb1,#\xc3\x18#\x06\x8ac\xdf\b\xc3\xc9\x01\xe9\xc8ģc\",\xf97\aW\xcd\x1cj\xf2\xfe\xe0\x9aU\xb3W\xc6\xddc{r4\xce\xe69\x8bJo\xfcn\x81*g\xe6\x82T\xec\xc8\xf3,L\xa3\x8bo_8\xfeؕ\x80\xe5\xda\xf6\xb0p]\xd8\x1f\x9dVb\xcf\x06m6o\xa8\x8eNT\x96\xc8E\xad\xce\xf99RN*g$\x87(i\xe32r\x9dVꢫ\xd2\xe6\xff\x905\x8e\xab\x93\x86\x8959i&\xcb\xc0\xee\x9e\xed\xcf%\xf4\xde\x16֡\x13\x9e\x9c$Z\x10H\x04\xde\b\xbc\x1b \xc6\x00\b֤\xa6\xc7\xef\xe94J\x8eN\xfe\nd\xe8\x9f\xc4cM\xee\xcd\xfc\xa4jŕ\xb4l}ax\xc5\xe1m\xad-\xdfzQُ\xa3\xd1\x03\ang\x1b\x0e\x8f\x9e\xdd\xdd\u07be\xfb\xec\xe8\x1do\xed\x9e5k\xf7[w\xbc\xf9\xd6[o\xbe\xf9\xfa\xebY\f\x96\xb6rq\x05\xbf\x94\xa1\xaa\x84\xb74\xe9^\xc0\x10C\x81$\xe8Z\xf5\xdd\xe5\xfeN\x93]2\x18u\xeeH'\x8f\xec\n\x00c^\xcd\x14\x93\xac]x\x86\xbc\xae\x10\xa8\x8eI+\xc5\b5\x978\x19\x82e\xa0\x17\xc3\t\xd4^\n\xf4\xaaϢ\xb2\xed\x93A\x835'\x92\x05\n\xe9\x88P\xb5'\xd8P➀\x10\xe4\xed\xd2\xfe\xbb\a\x8a\xae\x800jW\xaa\x03\xa6:H\x19\x1d\"~>Ԛ\xaf\x00\x1fC\xd2\xdeS\x9e\xef\xce3\xfa\xb5\x9dn\xa9\x18Qm\xb2Ӈ\x93\x81\x0f\rū\xe0\x8d\xe3\x1b \xa8Z\xbe\xcc_\x1b\b\xae\x9c,\xe8\xd0\x15\x00\xd4^\xefP\xb9\xef\x1fc\x95@ǰ\xd17\xe1\xcd\x03\x13\xf2\xbbK\x1a\x82{\xbe\xfc\x05\x9c\xb5Lu\xfb\xf3\x188\xba\\OTT\a;jJh_ ?\"\x98\xa1\x01\xac̮\xb9Em\x92\x10g\x99\xb3\xd39\xe2$&}\xab\xa1\xdba4\xeamf\x91a\xe2\xd3Y]bى3\x1a*Y\x1009V\xf1\xa8'\x99\xe3̭ˉ\xaf\x9cU4\xb6Ǯrն.\xe4\xfay\xfei^\x94\avv\x8d\x1f\xe5\xda~\x1e\xe9l\b\xb1uu\x14\x87\xb1\x98\xa8D\xc9DI\xc8Һ\x8bVz\x8c\x16Tǫ\a\xaa\xbf[M\xa4j(nU\u007f\xd7să==U\x05E\x9d\x96b>OR\x1b2Q\x1b\x8b\xd3jv>F%\xa2\xab\xc73\x94\xb0|\x15\x8b\u05c8\xff\x9f4^|\xea\xba\x03\xa9u\x0f\xd7\xf9ڻ\x17\x157-\xaeͱV.I&7uF\xaa\a\xef\xeeY\xfa\xbdd\xbci\xb4uh\x99\xa3\xa6\u007fFrc\xe7\x14(鼱\xab0\x98\xf7\x14#\xf2\xecEu\xf9\xbeXy\xcc\xe7\xabo[\xda2c][\xc1\x94\xbc\xfb=\x91\x96\xc6`Ei\xd4㭟\x95b\xb6\x0e\\\xfa\x10\xdf\xc7\xcf@\x1e\xd4\xf9\xbcK\xa9\xd8Q\x95\x96\xe2\x18_\xdc7LJ\x1d\xeeV\xab\xcf\x1c5c3\x92(X\x96\x8eJ\xa7\xa47$A\x90r\xccs\xecV\x87\x11uj\x95씳\x80\xecL\x9f\x9c\xb5\xbd\x9c\x8d#[\x16ESP\x16\a\xd9\xc6&0\xadv\xf8\xca\\2%\xa8\xd6\x17\x96U\xb8\x1aW\xcf.~衎U`M\u007f\x18\xdf\xce\t*r\xa7\xa0S\xf3\xfe\x8e\x9d\xcb\xf1֮\x8e\xbf\\\x1c\x1d߱tYf\\\xceրsm(\x87\xd5+p\xb4\xe6\xccϕrS\xb9#\xb9\x846\xc1n\xb7So4O\x1e\xf1\xc9\x13Q=y\x94w\x85\xf4cG\xe4zw\xa6P-\xbesItl\xf5\xb0R\xbc\xee\x8fċ\xac\xa5\xcb\xf6\xaf¿\x1eo\xa0\xd5k\xf9\x9e\x14\x9e\xfa\xf5\xd8e\x9e\x84\x9c\xa52\x98кW\x90\x8961\xa3Ɛ4!\x15}Gj5\x18%\xf6L\x95ٖd\xfbD.\rV\x89\xbe,'-\xaf[>\xb1\x90N\v\x18-Q\xcb\x1c\v\x111(\xe4\x1f\xb4\xaaAm\x84,:\xa8e\xe4\xce\xe9ˤW\xe44\xa3\xfe2\fW Cd\xc9\x19\x8e\v6\xa7GO\xbc\xa6\xf5z]\x9c\xc8;\xbd^\xedk'ң\\\xdb\xf8#\x81\x95kS.Wj\xed\xca\x00\xbe\x86\x8a\xacp\x80\xfc\x9f\xa9\xbcQ\xf4\xe3WP\x94ʫ\xa3\x92F\x91JM\xe5\r\xfb\x15\x12\x90\x1e\xfa}\xec\x84O-1=$#;\xad\xa6'\x8c\xce\xee0\x84)S\x97\xb0\xd16\xbe\xd8\tN?݇\xd5N\xa7:LL%\xa5Y]\xd9>a\xa1\xba\x96J\xa5\xa5\xe5\xfe\xf2\xb2rl,\a\xd1lb*\xdaM%>5U\xd2d\f\xbb\x04א\xeb\x06\x17q!\x89\xaa\xab\x10VqF\\e\x98\xbe\xac\xce\x13<_\x84Ω\xfe\x13\xcf\x17 \xa5\x84r{\xc1\xc9\xe4^\xc6&\x93\xadC.<\xcd\xe9tz\xc1`\x14\fz\xadK{\xe8\xad\xf4\xfb'wh\xacf\x03'\xf0&\x9b]\xf3\xe2O\x0e\xa9\x1dv3'\x10\xbd٦\x1d=\x9e\xfe\x0f\xbc\xd2\\\x12\xabpMm\x8cO\x8d\xad\x0e\x8d?Lmy\xacdՊŞ\x9c\x05K\a\x02x\xcd\xf8\u07bc%\x03\v駕\xeb\xa28\xa9\x04\x03\xa0 B\xe4mj[-z/\xf1\xbdQ5\xdcF@\xe0\xe0\x1f\x1c\xbc\xc7A\x15\a\x1c\xff\x9f\x02\b\xe4}\x04420|\x81\xe1\xbf0\xd4b\xc0\xb0\x86\xdf\xc2\xe3\x05L\xf1\x17Y\xfe4\xf6\xefQ\xc6\xccX\x95%裸a\xbd\xcaX\x15\xddg\n\xba\x8d\xb1J\xec\xf9\n\xf8\xccY\xd2T8{\xc7@Վ\xad[wT\r\xec\x98]\xd8T\xe2ܼi\xd3f\xa9\xac!\xa8\x87}\x10h]\xdf\x01\x1d+z\x1e\xeaY\x01\xb3;ַ\x06`/\xe8\x83\re\xe9\x97Vm\xb1\xea\xac7\xafF\b\xe0\x83\xb4\x15oS\xf0yI\u0087x(\xe3\x13\xfc\x06~\x84\xe7\xf0\xb3\x8c\x0eH\x90\x11r\x8a\xf0\"\xc1n\x84X\x14\xb3\xb9<\x16\x92 \xb3g\xb0\xf6\xed\x13\xac_\xa0,W\x8a\xf7S\u074b\x98\xee\xfa\xfc\xfc\u007f\x88\xe8Ӝ\xbf[\xc9.\x0fx\xb8\x9c\xcf?q]r\xe1?\xba\xc0\xe52\xe4\u007f\x1e\xfcR\xff\xb9!C\x9e\x9a.\xeb.\xcb\u007f\xc9\xean\xa1\x10\xa7\xb2\x14\xa8\xca\xcabk\xa62\x05n6ژ\xd9\xd8I\xacl$\x95癞\x13\xfa\a\x9aR\x8dW,\x00u\xabo\xa6\xdamY\x05\xed\x8a\x1d\xf6*vH\xbfX\x91,\xb1RK\xa4_P,\xb1O\xb1\x04[\xbf\x8a\xdb8Lz\x95gx\xf4\x1e\xf6\xfc\x8eD3\xd8\xf1}\x8dO\x99L\x92,I\x9f\x8f\xff~.\x9b+\x88\xeb\xe7\xe8\x87\xf5\xf7\xe9\x05=\xa2\xe2~\xfa\xb3h$v\x1e\xb2\xabU\x81\xb1gL86\xb0c\xe8!C\xf1\b\xac\xedas\xa8\xba\xa4Ȟ[_W\xe3\x1a\xdc\xe3\x89U\xb7DC\xd5\xd1B\xe5\x84\xf3\x9a\xbd\xec\x04\xbeK\xb2\xebyK\xa0\xd8\xf5\xd8Z^\xef\xb2~\xe3Sfmw\xfa.\xd8\xcd\xf5+ϩ7\xbd\x82P\xf69\xf52\xd7A:\xf9v\xca\xc5a\ue001d\xa6\x83\x8cl:\b\xd1\xe7\xd4\x11\x9b \x101\xa2>\xbb\xeaqu\x9a\xaaA\xcbU@\xfe,}Z\xfd\x13K\x90\xae\\-\rZ\xad\xc1\xd2\x1c6\"\xc5O|\xfd>\x9f\x9cX\x98zy\xd13\xa0\x13\x14\x9b\x1d\xa7s\xab!ԛ\b\x99\xc9\f#\xc7iB\xb6p\"\f\xde$Ҕi\x12\x9a74\x9c\xa0qo2\x9a\x8d\x81M<敒\xa33\x9a\x93F\xde\xc8\x13I$.\x14g\x10-\x9ayޙm\x99\xe0\x92ΰUa2C\x0e\xf9\x02{\xae\xf9\xaaǙ'Ӕ\xe4\x86\xf5\xed\xa3\v\xee\\VQ\xb9\xf4\xae\x9e\xd1\xe4\x1d\xa5\x95\x94\xa8\xacse\x89\xca\xdfz\xf1\xcf~\xa3\x9f\xb5\xf3\xb5\xd1{~y\xc7,\xfd\xa1'H\xc0\xa1\xf0\x95\xbf\xf8\xed\xbf\xfe8\xe4\xfc\x1e\xd3c\x1aB\\\x90\xff9*D\xd7'Lz\xd3p\xd0h\xf4\x19\xb1\xe8\xa3\xef.\xe2`\xe5\x92ve\xba\xffY\xa2LP'\x1d\x8e)\xd2\x14\b\xb8\x12ZC\xd2\xe5*\x92\x02\xd7\xeb\xa3y\xdb\xf2\xee\xcb;\x92w2\xef\\ޥ<1/\x0fٯs9\xc5ɏXP]L\xc0J\x89RP\"\xb2\xa2\xb5B\xa4]\x85\x91)BSf\xc0\xe9\x9eFy\x03\x04+e\x96\xf7\xf8\xc0\xea\xc3\xcd\xcd5\xf7\xcc*\x9b[\x1f\x80\x1b\xd3w8²\x1b\u007fx1P<\xb3\xd2{\xe6Lݪ\xef\xf0?/\t\xee\xf2D\xdcu\xfd\xcd\xe9?\x8e\xbeո(\x11\x91\x9e:\xa0-\x9a\xb6\xb4\xe5\xec(L[0:\x10C\x18m\x01\x0f\xf9\x90\xbb\x8b\xc6z5\xba-\x114\xdcg\a\xbb\xbd\xf49\x9bQ\xe7\x14\xbc\x01\xa2B\x1b\xcc`6\xd7J\xb5\xc0\xf9\xf3\x9f\x1dA\x80\x98\xcf\xca\x05M\x12\xa1\x1aLj\x0f|:\xc1\xe95\n\x9c\xba\xe80r\x83[\xcfˇ_W\u007f\xa2ƻ\xd4\a\xd5G\xd5ħ\x8e\xab\x87\xd5D\x8d\x94\aI\xce(S\xfc\xec\x91\xfe3}\x19\xfd\xd99f\x82>z\xf6\xd3SѾl\xae\x87\xab\x1d\x99\xb5\x91\xd5\x0e\x85=\x13D\x87\x186\x00\xeb\x1fb\x98\xb2e\xd5W-\x8e8\x1e\xf4m_\xb6-\xba\xb6\xbe~M\xe9\xf6kn\xf5\x85\x82\x81m\xa9\xed\xa5k\xe8\xf8\xa7d[j\x9b/xg\xa8iAL^\xd4\\PмH\x8e-h\n\xe1\xb7\xea\xd6D\xb7/\xdb\xee\xcf\xcf\xf7o\xa7\xbf\xba\xee\xf2\xaf\xe6\xfb\xe9\xaedm=\xfd\xad\x82\x82+\u007f\x85&\xb8T\xae\x9d\xd64;ڛX\\\xe6\xa4\xf8Qg\u007f\xd6\x066\x9b\x8eS\xc1s\x18|\x180ޏ\x00\x89D\xdb\xfb\xa0\x15\xac\x92Q\aw\xea\x80-\x1fK\xe8\x88H\xe1^\xca(\x1a\x05s\xcaB\x90e\x9b\xf1>#.\xb3\xc0\x1b\f\x88\xfa,\xd8h1Z@k\xb6eg\xe9XL\xd0f\x9f\xfd\xef\x12L\xec\r\x94\xe5\xf82{\xd2?V\xdbǦ\xe6\xe5\xdaZf?\x16A\x99\x99;\x06\xeb\xd9?\xe5)4\xf6\x8f\x9e\xc12\xf4\x9eLo\xfb$\xfd\x16\x94\xa4\xdfz!\xb3\xfb\xaf\xf4\xceSlV\x8f\x0e\xc8\x1f\x1dM\u007f\x05\x02\xdd\x11\xc4^xR\x0f\x10Qm\"\x84\xd4P\xa6N\xa87\xa8G\xd4\x1c\x16\x9e\xa5g%1!\x8e\x88\xa7D^\x14\xd9\x1a)!\xd3\v\x98Đ\x89㉎`\xcav\x05\xd6\x17.~\x96v]\xfc\x1b\xab]7]\xfa;\xe9\x17|h:\xeaE7%f\xf1=\xaa\xae\x19\x81\x82\xf6\x19\xf53\x8a\x8a\xa4K\x018\x188\x1a\xc0\x81%\xa7\x96\xc0\xc9%0c\x06r\xccP\xa9\x94\t\xc0*\x00\x8fn\xb3\x1d'\xb9#X\x91Ț%\xd4\x1f\xd9\x04;\u007f \xa7O\x91MJ+ݣ,\x9e\xb3W*օC\r\r\xa1p\x1d\xdcW\x176\x05\x9c\xfa\x86`\xb8\x8e\xdb]SY\x15\x8bE\xa3\xb1\x9a\xca\xa25\xea\x94mW=\xbay\xe3\x97\xf6\xb7\xfb݇\x9d\x05\xa9\x97\xe7\t\xb9a},\xb6\xfe\x06|\x9c\x91B_\xe9^Y\a\xd23m\xdck\xc0\x03\xcaA\x8f$\x94C\xfe\x1cp\xeb\u074c[\xe0\x05\xaf\xd0+\x1c\x16N\b\\\xd19\xad\xcd6\xa2\x85ڊ\xf2\x8fx\xde\tC\x1f\x01ń\xe9#\xe7\x87 \x8e'\xf8\xf4T\xbd\xcco\xd3c\xdbL\x12[q\f[\x8e\xbc\xc1М\x80l0\x13\x8f%;\x8cr V\x94\x83\xb2\xf0'\x05ƐP\x17\x88$\xaaݷ\x8c]\xd6\\W\xb9\xaa\xb4R\xdc\xd3th\xdf\x1bbG\x891ZZ\xd8\x10q \xf7\x805\xe0\xc8w\x8aK\xc4M[9֛\xa8w[78\x8bF\xafJ5\xc0zd\xf4D\xdcb\xb9\xa5\xac\xb5\xac\n0`\x00\xecFw\xa0׀\x02\xe4\x83\x11\xc9\xc0\xb0\xf9\xbf\xd0\xf4\xb1#,\xc3jX\x8dB\xab&w\"?\x0e3\xaby\x9e?Ο\xe0\x91\xe2\x17\xc7\x11D\n\x80\xf2\x95\x1a\r\x87\x10\xcc\xe3T\x10\b\xa2 \xe7\xec\xd0\xd2)\x94ND\\\x8aH4\x1d\xa9\x99\xa2\xc1~\x1b2)a1}\x19\x80\xb1=\xbf\xfe\xf5\x9e\xd4[\xf0Ӱ\xe6\xca_\xfd\xea\xcaԛL\t|\xb0'\xf5\xf9\xd4\xe7{\xe0}\xae\xd9Ù\xba\x05\xc8\r8l)\x8b\x98G!\xb6\x8d\xf0\x18\vob\xe1\x01\x16\x0e\xb1p\x11\v\xebXhd\xa1\x9a\x85,\xfb\x04\xf7\x14\x88g\xf1\xa9\xd2\xe4x\xd9Z\xb3\xcc]\x17\xfe\x86\xdc\x17\u007f\x06\xaf9\x06\x00\x98\x9e\x9e\xa9%`T\x00=\x00\x80ͬ\x1d\x15\xf0\x80\x06\xd0\x03^\x91\x0e}\xde:n=mE\xe3\xb6\xd36\xc6f\x85\x03\x16\x9dMg=cљ-\x16\x9dN\r\x1cg\x9c\xea3\xac\x85\x05N\b\x9c\x13\xce\xf7\x9dH9\xc6B'\xebd\xbd\x9d\x93\xa0\x1cN\x94\xc3\xf2\xbe\xc6I\xaf\xce\xfe\x06\x00V\x97\xd5\xe2T\xbd\xe1\xc2`\xde\xe9u\n\xce\xc3\xce1\xe7q'^vr\fg\a\x8e\xe2pN\x97\xd3\x15\x94^\xaf6-|=\xc8\x12k\x9bYw;\xc9P\x122\n\xb9Qe\xad?\x9a$\x84\xdf%w\xb1\xfa\xa3\xfa\x88\x1e|\xf5(\x17џ\x86\xf8\x8e5\f\xe9\x06l\x04r\xc4\xe2ZmIJ\x06\x10\t\x1cb\x15\xfbwk\x92\xf9Ibu\xefҕ]\xf0vWy\xbd\xb7=f+\xf5k4O\xbd\x9b|\xe0g\x0f?\xf8\x8f_\xfcǫ\x11\x81D\xe7\xc2\xc6k*\x17Շ\xf3\xafX^\xdc]g0\xc0\x91\xd4C\xe8\xd0'S\x93\xff\xc5:\x84\xd3\u007fW\x98Y\x85\xe2\xa1\u007f\x9e\x9b\xc1*\xfe\xf1G6Oa\xbe\x99\xaeۃh\x88!\x1c\x01\x93\x1cW.\xf3<\xa6)9^\x02͏V\x02\t0\xc0\xf1\xa4\x9e\xf7\xf1\xcc9\xfe\xb7<\xb3\x9d\x87\x80'\u007f\"\x05\xcf\a\x9e\xe4d\xfa\x1a%\xcb\x11m\"\xeb\x95p\xa3\xe7\x11\xd3\xe6\x8f\xd7\xc1\xe8\xe0\xee\xd6\xd6\xddC\xd1\xe8\x10y\x1d\x8c\x1e\xf4Tw\x85\xc3]1\x8f'F^\xab=\xecX\xe6\xbfw\rVU\r\xeej\ruV{<՝\xa1pw\xcc\xeb\x8du\xcb\xed\xde\x04@\xbaV\a\x0f\x1a$\xbf\xde\x00y\x95\n\x17\xecȃ0\xbbfG\xfe\x13yYe;\xc8@\xc8$t\x1a{\x99W\xbe\x83\xb0\t\xb3Kx\xa4\x0e\xa1\xfbg\xcbx\xa4\xfe\x8eɅ\x10T1\v\x99m\xd87\xf7\x00I\xb2\xa8\xa6\x80\xcd\xd7\xeb;\xecc|\x12\xf6H\xddS\x90\xe7\xe5\x84?\x1dI\xa4S\xb9\xb5\xe7\x8d\x1f\xba\xcf\xc3\x1f\xa6\x1f\xa9\xd3O;\x11J#'ז\x19~\xc1y\x04?\x1e\xfa\x99m\x17O\u007fP\x9f\b\xf3m5\xe2pyO\xcdu\x03M\xeb:\x8a\xdd5\xbd\xe2\x9d\xf0ffᎏZ\x06\xdb\x1b\v[\xa5hpme\xab?\xbe\xba>:4\xb8\xba\xf6\x01\x00\x81\x97\xa9g\xb6\xe0\xf6U\x82uR\x13\x02\xf8\xd9V\xe1\x99\x02\xc02\xa5\x88&\xa3cQ&J\x1a\x1a\n\xe9$\x87/\xa1C\x8e\x8f\xe8*K:\xcf:9\x95\xd3i\f\x06Η}h<\xcfk\xcff\xd7U\x98\xa2M'\r'\xf4D\x03~\xd3(\x17\xe1Q\xccqP2\v)\x86\x0f\xe6\x95T`z\xfa;W\xac\b/\xbdz\xa8\xeb`}\xfc\xda\xd8\xfa\x15\x1f\x1bY\xbf~\xcd2s@p5\xae\x13;\xdd\xcd\x1d\x8b\xcb:/\xef\n\xb0?\xe9\x19\xb5\xdbF{\x9a6t\x87\x1d\xde\xeb\n\x83K\x17,l\xdf\xd9+\xf9\x04\x8f\xae\xa4x\xc4V\xec\xe4\v\x9b\x96^\x9aW\xa4\x00\xdc'\xf8\xbc$&Ⓖ\xa2wh^\x11\xa9\xfbc!\x14[x\x83U\x10JqZQ|I\u007f\xe0\x00\xfbG\xf7\xe2\xfe\xfeB\x8b\xab\u007f\xc5\xd2\x02@dɱ\x1e#\xbb\x10\xb8A\xadTH\xa2\x92\x83\xf8>\uf6f9ӛ\xdd\xdd\x05\xcb\x88\xa6\xb7\x94\xe1\xec6r \x14\xa0\xcf\n\xb3\x99\xf3\x1bF\xb7\x0f\\\xd1^\xf0\xf4\x1d.M孛\x13\xa3\x01\xc80\xf0\xe9\xa7\xe1\xd1\xd9\r\xe5;\x96.\xab(\r\x86\x03\x83\xa5\xa9\tE\rɟI]`7)\xcc\xc0\x84\xdb\xe4g\xef\x01\x1c\xd9\b\xe9\xe3X\x05\xc7\xe5\xc3{\x80Eo\x81 _\x8f\x19\xc8\xc8\b8\x00\x04L>\xc1\xfaM\xd6!\x9d\xb5\x99\xf4\a\x83\"\xe3h\xf8\xd9M\xfb\xdf;\xd0<\x98\xbco[S\xeb\xbe/n`\xc4\xd4\x05Ł\xbf\x1dE\xa2Q\xdc;q\xf3\x1dg\xf6G\x01\xad\t\xc3$\xe0m\x88\xc4\xe3Dɩ\xb7Ъ0\xec\xbf.\ts>]\x12\x86X\x81\u007fC\xcdf\xbe\x9e\xe3\xf9=\x902û\x01\x00\x0eP/y\xcd&\x93\xe5\xb0q\f\x87\x1a\x8c\x8a\xfck\x81K\xe14\xdel\x91Ժ\x84\xc5b\xd2܂\xb0Q\x13p\xba\x03\xf1\x16p\u007fi\xfd1\x83(\xebd\x962\x12\xef\xb8VTz\x10\xbc\xfb\x96c\xd6r\x9e\xaf\xd2w\xf79+\xe2ū\x0e\xd4\x13o 5\xb5n\xab\x02mE(Re\x0f8MꓻUΒ\x06Җ8\xb3\x10>/\xe7\xfeWK\xfe^\xfda=\xa3'+G\xa7c\xa6\xc0a4\x86\x18\x1f\xaaD\fb~\x98\xaf>\x0f~\x80\x17\xf8;qٶL\x91eB\x1f\xa7\xf1\xd9>\xe2&\xdc\x1f\xf1\xd4W\"\x19~\xc4\xe2F\xfeM\xf4\x93\xb9\xa9dɶ\x8e\xf8hO\xa4d\U00076396\x8f\xf5\x94\xa5\x1a\x16\r\r.Z48\xb4\x88ݶ\xf4\x93+\x05a\xe5'\x97.\xbdfEEŊk\x96\xee;p`\xdf\xfeݻI{\x96\x81\xdd\xe8X\xda\xf7\x19\x92\x84\xb4\x83\xa3\xfc\x05\xcbh\xf0\x17Ҫu\x808;$9GB\x888<\n\x804\xf8+\xdb\xe7!\x1b\xf7\xb3>\x0f\xa9\xe9\x15M\xbb\xc3S\xf2\xa3bȄj!G_бԛ\xc4с5\xa9-\xa9\xb7\x88\xf3\x03c\x8c.\xb5\xb1\a\xae\x81kzR\xeb]\xb3\x87\xc4/\x89\x03\xc0\xe1\x9ck}\x10\x1c\x04@\x1f\x02\x1f0\v\x010(\xc1ט\v\x17\x9f!\x11d\xc4X\xed\x889e\x85cV\x18\xb7B+\x94\f\x10\x18\xa0\x01H\x00\xbf\x0e\xc7\f\xc3\xe4\a\xff\x8a\x818\xfe!2˰̇\xa8\xcc_Q\x991Y\xe6\x95H\xc42Ò\x9d1\xdb\x19`\x86fx\xd2\x00%\"k\a\x80T܌<\"L\x96\xe5Ų\xde\xe5\xde\xc0\xb2\xf6\xa4\xdb\xf70m\x1f\x8acYMR\b1u\xb8};\xea`_\x1d\xac\x83\xe3\"\xfc\xbc\b\xaf\xc5vB\x84}\"\xac\x14\xa1O\x84\"\xbf\x18\x0e\x1b\xa8\x93F\x84g\xdaJ\xfc\xed\xd4>\xd4\xc1\xae\x95\xb9\xe6\x1d\x92q\x86k\xae57\xf3\xcdP\x85\x19\xe7D\u007f\xf4\xbc+Q\xa0\xb5\x85B\x98q\xae'\x94s \xe0\xe8.f\x9c\xcb\xf7\"\x92\xf3\xf8\xee,\xe3<\xcd-\x9fa\x89\xdat0\x93\x1dD\xf6}\xe6o\xcd1}\xfaP\xeb\f\xe3\xbcvٱNi\x800\xce7\u05f9b\xfd2\xe3\xfc\xf9K\xf7\xed\x8a\x1bV\xf7/\x9ee\x9c\x8b\x81\xdbK\x1bđ\x1bWtl\x91\x19\xe7-\xed\x11\x13\x14\xe6\x19\x86\xe9i\xcamV\xee6\x06A\x91<\x86\xed\xd3/\xc1\x01`\x19g,\\\x10o\xa2\xdf?\uecb0\xcab\x18\x89\x01\xfc\x9d\x03\xbf\x17܂\xf1&I\xcb\xf8\xf1\t[-\xaeY4ȅ\x9f\x04\xdel\xf9\xd3\xe3\xee\\\xf2c\x19\xfc\x19\xa0H\xcbw\xe3\x13\xa0\xa7,\xa7\xfcH\x06?\n\xfc\x18o\x93\xf2\x11\xe3ႈ\x81\x96\x12z\x069e\xce9l\xfa\x1c\x058\xed=\x04\xaa$\x1fb~\xe3\x84g\x9d\xb0\xcf\t%'\xf49\xa1\x9ex\xd5Љ\x18\xe0\xb5\xcc\bɨ\f\xad\xe19\x9d\x02@1$\xf3jL\x98\x1b^\xaa0it\xab\xa0\x01\xea\x11kz\xc4\x04\x8bLզe&\xa4\x87М\aM0\x0f\xffB\x9c\nl\xb6\xb0\x1c\tJ\xf88\xb5)\xa9@I}~Rӫ\x81\xbc愆Q\xe9qB\xbb\x02\xb1\xba$'\x13j\xa6\x04y\xe3Ev\xc0\xc9W\x94D\xf0\b\x93\x96R\xf7\xf1\xbfCGO\x9f6@\xba\x8bD\xb9\xb5(\x003\xfcZB\x82S\f]T\xa5\x8a\xdfz\x9e\x19Co`*\xab\xf5\xe2ϙ\xe6\u007f\xec\x81wl\x95\xd9\xe1i\xca-|\x95y\x98\x8eϡ\x94\x99\xf0\xa4\xf1\xf8\x04\xe51\xbd\n^\x00\x00h\x9f\x86\xbd\xa5Ev\x1f\x19\a\x8a\x939\x84\xf2܆Ӻ\xf3\x8c<\xb7\xd8L\x05\xb1\xab\xf6ʸ1\x0f\x82\xf4l\xe5\xc2\xef\x05q:\xb7ЎOX\x98g̠s˟\x9c\xfe]\xb6\xfc\x8b\xe3\xe6\\\xf2#\x19\xfc\xe8\xf4\x9f\xa8.0\x90h\x1b\x84y>\b\xe6\xe9B\v\xe1\xa7\xca\xd7(\xa3}H\xfdo\xf65\x1e\x9e\xdf\a\xc2g\xbd1\v\xbfwz\u007fV\x1fF/\xe9\x83\x01\xe3\x1f\xc9\xe0I\x1fx\x8a\x97/\xc0\x98\xf3\xe6\xe1%\x8c\xdf'\xf7\xa1,݇\xfc9}\x00\xf3\xfb\x00\x18 \xa4\xb9\xe1z\xe0\x05\xc3R\x83ө3\xacVm\xf6\xfb\xfc\xd0^\xb0J\xa7\xd7C\xdd\x06\x9fK\x9f\xb4\xf3\x80D&\xc1vp\x02\x9c\x05\xd3d\x8b\x8d\x10\xa3\xb1\x84\x82\x11\x9d\xd16\xc2d\x18\xfc\x84\x15-\xd2\x04\xee\xa8\x1cB\xa0\xf9S$\xe4G\xd9\xe4\x964\xb5R\x93#59B\xcfR\x10\"\xa0J\xfc\x86\x94\x0f\x95\x1c\x87\xb4\x10\xf6\x91h\xaaF\xc32\x8c\x0f@\x00|\xa0\x0f\x10\xe2\xccC\xe9-X\xb5\x02<\x05\x1cq\xb2\xc9j\x10\xf17\t\xda\x19\xe4/\xec`\x80\b\xfe\a\xc9/5\xacUC\x9b\x1a*\xb1\xcf\x01\xdfJ\xbdr9l\x86m[R\xa7`|S\xea\xe5\xd4\xe4f\xe6\xeb0~y\xea\x15ز9u*5\xb1\t6\xa7\xcel&}A\xb8\xff\xe3\x98gy\x018\x01\x99\xf15RC\x89\xb7\xd4úB\xab\xa1C\xabc\r\xec|֥k\xb5aS\xd4[\xe2)e\xcb\v\x93\x0e\xa8Ӳj\xa7\x95-O\xe6\xa9)\x8d\x8f\xa43\x90\x92\xa6\xb3\xe8A_v\xfb0\x1b\xf3F\b?}aY\x81\a\x134\x89\xcePn\x9d\xaccui\x1dn\xa6\xf6\xc7.۟\x89q\x8f]\x9bљ\x1c\xf8\xbd\xe0Lz\x8d\x14\xe1\x13\x12v\xcf,:\xa7\xfcIp [\xfe\xc5q_.\xf9\x91\f~\x14\x1c\x9a\xb1?~ن\xda˴ \xc7\xfd\xb4V\xbeFS\xbaMU\xb4M\xc4d\xc1\xcbԼ\x92ɴ)\x9b\xfb\xab\a.\xd0$\xf9m\x98_\xeb>\xee\x86z74h\t\xe3\xb7\xc00\xa2p\x8dh\x15\xf8KI\t\xbf4\\Bn\u007f3,j\xfc\x92\xc5\xf8\x15-bN\xce\xef\xf3'\xe7r~\x91\x8ep~S\x93\xf89!C\xfa\x1d\xbb\x1e\x00(sB\xf6(\xbc`\x11\xd8&%\x94]\x15\xb1n\xc0;\xbc\x0e\xc1\x81\x1cR\xbe)\x91\xac\xd8^\xc1\xf02c\x04\xa9\x1c\x15\x00T8PkQ\xb7a\xf9\x12\xfd\x12ߒ\x89%H\xean]\xb6\xb8H\x19Cֆ\xben\xd8j\xf1\xf69\xad\x94\xec \a,\xd2\xe9Q\xe4P\x10\xc8\xc6N\x84ld\xd3.\xb0YA\x89\x9c\xb9j,9\xcc$\xacR\x17\x11\xfd>\x12_\x14\x8f\xe8B]\xf5u+\x9a\xfdu\x1bo\x19\\wS\xccٚX\\\\\xd7_m7D\x12u\xf1\xc1\x98\xb5\xa4%\xd1R\xe2oZ*V\xadh\v=\x1d\xdfy\xff\x9a5womb\u007fY\xbfrASm\x8d\xc7\xe2\x0fU\x05\x8b\x13\x03#-˯\xee\x0f\xa5\x93\xd9\x1c\xe5R\xb8\xa8\xb1\xae!\x10\xeeh\xef*\xad\xe9\x8d\xd74\xb4\x97\x95\xb6\x94\x98H\xba\xfe?zؓ[n\x19,\x0e.\xbe\x92\xcc=\xe5\f)\xfcx\xee;d_\xaa\xfdN@,\x9eh \x16\xef\xfcx\xbd\x98W\x98\x99\xfb\\\xf8\xbd1 [\xd48>\xe1:\xb1>\x83΅\x9d\xfc\x11\xc5\x12\xe1b\xa3x)\x96\xfbq\x06{\xe6%\x8am\xc4ؒ\xa6\x859\xb0\xdf\xcd`G?\x04\xd4\xea6\x19\x82\x88\x11\xc4n\x8a&\xf0\xacv\x1c\xcd\xe0O\xcf\xe0\xeb\b\xbe\xb2Y\xcc\xc6\x03D\xf0\n\xf2\xfcP\a\xbaA\x1f\xf8\xb8\xe4\x14̋<\t\xb6\xad\r,N\x04\xdd\xcb\az\a\x92\x03\xc7\aP}\x02\xc8\xf1\xfb\nL\xa9\xd2\xe9\xc0P?hӷ1m\x9e>\xc0\xeaY\x86\xe5\x17y\x171\xcaE\x8b\f\xfe\xa6>\xa1\xacڊ\xab\t\xf4\xd9\xcc\x06=\x903\x00\xa3\xf2oµ\xa6\x95\xc1)\xd9J\x9f\xa9/\x82\xff\x93\x06\xc1\xe6\x13\xad\xac$\xf8:\x9f\x98B\xe9\vtO\xd1*\xceа`\x9a\xae\xb2v.\xff\xea\xc8\x17#\xe2\x177\xaf\xbbwk\xfd\xc5\x11\xee\x8e;\xaa\x06{\x16\x16\x87\x16\xf5\x0eV\xdd\xf8fK\xe1\xe2\xe4\x95\v\xbb\x0f\xae\xaa~i]o\x86\xa1\x85\xae\xbb\xe2\xb0\x16~\xdbY\xbb\f\x8as9Z\x83Ã\xc3\xf3\xe9-\xbbv\xd7\f6z1\x9fk`\xcd\xd0,y\x8b\xb9,Mz\x99\x9e&y\xb7\x84\xfb\x84\xe7\xa3+ퟶQ\xffT\b\xfa\xb8\xfc\x8c\u007fJy=\xb2\xfe$\xb2u3,\xeb\xe6s\xd8;\xe0x\x19\xfc\xcf\xf0{\a\x00\xf5\x0e\xf0\t{\xc3\x15\x19t.\xec\xe4\x87\x14K\x84\xa3\xca\xf0\xa5XY\xdf(v\xf4{ }\x97'\xfa\x03\xc2M2:\xa3o\xd3X\x14\xdc,\xcb~%\xed\x93\x0e\x02\xd2r\xc8\x1a\x88;w\\\xc3\xce\xf1I\x9f\x04\x80i\xcb\xc2\uf76e\x04rk\xa0\x1e\x9fp%\xab\x99\xebcN\xbf\x84\xf1\x83Y\xf8\xc9\xd4\v\x14/_\x00\xe4\xb1\xf3\xf0\xbf\xc6\xf8b\xb9\xfd\x14?J\xf0\xf4\x9e\x90\x8f\xbb\v\x01k\xbf\xc4'\xddNr\xce\x15\x01\x10\x02\xf5\xe0J\xa9\xcb\x18N\x80\x80\xcf\xea`\v\n\xed\x05\x85\x85\x05v\xa4\x01\xcb\x1b\xa5\xc6\xf7\x1b\xcf5\"\xbe\x11*\xa28\t\xbd\xc1\xe7\bXY\x9b\xdf\xef.\xef\xe3\x8dxO\xe4\x14\xcfT\xf2\xd0\xc6\xdbx7\xacM'\xa7Ӹ\xb9A\x9c\xa9W>\x93\xab\xfe\u007f\U0010123c\x85=\xd6y;7\xe4>\x8f\x99\xca\\\x88\xfc\xae\xf5@\x92z@k\xe4\af\xb9z4\x9b}ˉ\xc3\xdd\xf1\xbd\x0f_\xbe\xe9ވʻ\u007f\xd54X\xf8\xdd\xf0\xa0\xd0\xd5zU\xe3\x8f\x17N\xb6o\xef-{\xb1\xb0\xeb\x8aEK\xb6u\xf9\xfd\xdd\xdb\x16\xe3<\xf7\xe6\xff\xf9\xc1ݽm\x87\x9f۷\xef٫[\x1b\x9bؽ\x13\xfd\xf7\xec:RP\xeb<\xe8K\x94ݸsO\xeb\xfd/\xfc\xf0d\xdd\xc7\x06\xa2\x15+\x8e\f.>\xb4\xaa\xea\xb5\xf4\x9c\xb1F\xd9^\xf5S{\xf57:\x03~<\x03La\xa5\xd66W\u007f\xd8ry\xbe\x06\xa8\x1e\u007fG\xd6c\x9f\xac\xc7\xdf\x1e\x0f\xfa\xe6\xf9\xa1\xf3\xf1{\xfb\xa8n\x96\xe1\x13z|\xc1\f:\x17v\xf2\x1fY\xb2\xff>\x1e\xce)[n7ş\xf9Q֚\xba8^R\x9b\x13\xff\xdd\f~\x14\xf7\xd3\xf14bJ\xb0\xea\xe33\xfe6\ueade\xe7\xe3\"\xb0\t\xfc\x95e\xd8\xc7\xe5ܧ\b\x18\x91,\x95\xe5\xb0\xf0Q\xbb]\xf7\xe8\x10ڄ\x980\xaaC\f\x92\x13\x16\xc38\xd9\x0f\xb0\xa1'\xf4n\x88U\xc4\xed\x86\xea'\x80\x89\xf0\x81FL'M\x13\xa6\xf7MJ\xa5\xc9\x04\x9e\x80O\xa5\xe3)Xg\x04\x12R!\x89\xber\xfdé\xe1t\xb5\xc3\u007fS\xff\x12WB\xbcx\xca\xe8-\xb1\xdbJ}F\xa3\xaf\xd4f/\xf1\x1a\xe7\xff\x8d\x03\x1a\xc7R\xbf\x0e\b\x9e\xfc|\x8f\x10\xf0\v^\x9d\xce+0ߟ\xf7\x06\xed\xe3\xee\xe9qV`o\x03NP\x04ʱ\xef\xdb\x18\xf2\x86\xb1\xef[\x9c\xb0٭\x16\xa5J\xa3V\xe5\x83\xe5\x82$\xbc/\x9c\x13\x10/@\x85+\x91?T\x11\xf6\x84\xbcl\xa4\xb0O\xa9\xb1[X\xcej\xd4G\xfaTdY\xbc:\x15\u007f5\xcb\xf9];\xd7\xf7\x95\xfb@sk\xc8jȤ\xdep3\x9e\xaf\x82\xab\xa6\x8eo\r+\x905ph}Ǒ\xbex\x17!u\xc4wV\xe1\xe3\x96\u038b\xc0d\x85\xb7\xb8\x17\x95c\x8d\xef\b\xa5vy\x13\xe53\x1c\x8fض\x85G\x12\x94\xe6\xe1.غ\xe0\x9a\x05\x1f|j\xd9\x11\x919j0\xbe\xf7\xc1\xf1\xcb.\x1e4\xe8\xbfNuA\xce)\x97um\x05\xd5\xe3\xcf\xc9V\xcd%[\xb5\xfb\xc6\xfd\xae\xafŅc\x1dQ7p\xb9\xca\xfa,\xbc\xd5\xdf\a8\xfd\x1cFl4\xc3n\x81s\xeb\x1eg\x11]\f\xca\xe2\xb9\xf4\x16\xb8!\x9bв6\x8b\xeb\xf2\xb4\nz\xb3i#\xe8\xe9lJK\x16\xd9E\x05R5w\x1a\xe61\\2\xfd\x96NJ\xf6{\xf4\xcbi\xcbO\x8c\t\xd4\x06jAֽk\x86\xff\xc5.\xbc\x84\xff\xc5\xe7\xe2\u007fa\x1c\x86C\x06ǚ\xeeU\xf8\x81\x19\x14K\x06^Dz`B\xc5\xea>\x01xURŨH\xf2\xa5\xf3\xed(\x14\x9co\xcf\x14\xe7\b\xc85u\x94\x81\x18\x89A\x11f\xfb\xbd\aĭ\xd55\xa3\xe2\x01gG{#\xe6M4\xb6w:\xd8\xef\x16\xae\xab\xad]W\xe8\x92\xe3Q\x94\xe6\xce\xc0m\xf8ZU\x8a\xa3r,@\x92\xca\xcdGȎH/H\x02\x168\x8fT\x92TЛIF`\x9c\xef\xe5\x93\xf8\x0e4\xcd+\x15\xbc\xfbf\x9eKr\f\x87[2\xf5\rR\x05\x8d\xbc\x0e\xe7\x88\x0e(\xab\x83\x99dET\x15h^V\x15\x1dh\xf4\xf9\x1a\a\xa2\x95\xcbZ\x02{\xe2U\xb8\xc2c]U\x9c\xfb\x8a\xd8_\xe7v\xd7\xf5\x8ab/~\xad\xed\xad\x8e55\xc5\xf0Oz,>\xe0~\f\\Xs\x02\xbcM\xb016\xa0\xf6\xa9\x19\xa5\xdaʍ\xe9$\x9c\x9f\xaa\x93p\x1e\xb7Ng\x1aS\xf3\x00\x12\xa2\u007f\x94\xc4E\"S\"\xc9\xe5y[\xaeGHݽ\x00\xd5 \xb2\xe1,\xea\x10\x93\n\xf7.\xee\xf2\x95W\xea\xb6p\xfe\xd8\u008a\xd2\xfeŝ\xee\xf2\n\xfer\xf2\x17\xfb\xfd@\x04\x8ff\xd3\xfa\xce\xe2@i\xa0\xba\xa5i]g0\xf3y\a\xe8I\xf9\xf3\x0e\xdc\xcf\x01\x86T\x93Q\xe4%\x18\x86\x03\xe09\xf8\x12\x88\xd3\xe8w$\xfd\xb9\ah\r\xfd\xdc\x03z\x9e\xf2?>O9{\x9e\r}\x15\xf6\xc9\xf5^\x8a%[RGj@%\x95ە\xa7\x94\xef)9e\xde\xdd0\xef~\x05\x02q\x1c\r\x9b\xc2jA\x8cw(\xfd\xf9#\xc5:]\xa8\xa2\xdaq\x03\xefvZU\xe8\xab֙\"\xbe\x1f\x8f喋\x85\xf10\t\xb7\xc3S\xf0=\xc8A\xc5\xfd\x80\xca\x1d\x9e\x12\x88\\\x98\xf9@\x886\x9d\\-\xf8\x06\x1d\xa9\x16\xcc=m\xad=p誺\xa6k\xaf%\x82\xb3?{\"\xf6\x1cБ\xb8\x92ƞ\x98\x11}X\x16\x8d\xafE\xb2\xd1\xf5\x96\x84\x12_\xe5a\xf9*\x11Q\xc4W\x8a\xfc\xa7\x1f<\xc1\x90\xf63\x1f\xc8\xedw\x80\xf5R\xe2\x94\v\xdeJjd%]\xdb]\xc8ě\x93\xe6\xed\xe6\xc3\xe6S\xe6\xf7\xccg\xcdJ\xb3\xd9q\xb7.\xefn\x00\xfb \xad\x953\x01߇J8w,\x1dy\xf7\x9b\xc8P\xcaI`\xc3rsp\xb4\x02\x1fQn\xf7̰\xfag\x0e\xe0\x17\xb3\a\xf8\xe2\xb3\xfft\xb8\xb3\x0e\xff]\xbb\x81\xb9\xcfi\x9e0\xbfO\xdb\xcd뒺\xed\xb8\x8e\xcf{:N\x97\x19Fy\x8e\xce\xca]P\x9a\x14\xf7\xe7˕\x86H\xbbi\xb3\x87\xd3\xedΞ6\u007f\xee\xf9c\x12\xfft6\xb3\x0ei\xadj\xe6\xef\xf2\xbc:\xc0\xd5R\xff\xfb\xaes\xaeߺ\xd0\xe7]\xe3\xae\xd3.\x84\xbb\xc0\xf8\\\x95.)\x9d6\xa3\xc8\xf4\x84\xf4CA\xfaq\xab\xee\x9c\xee\xb7:D\xba\xc3du\x84tC\x91\xd6\bI\xa3\xb7'h\u007f\x1eN\xf7'\x92\xeeP\xbaGɹ\x9fM\xe2ϥ+\xd9}\x9a\xaf9s\x94\b\x82c\xf0<\xb3\r\xfd\x1e\x98@\x97T$X\x92\x96\xed8\xf3\xee=\xcbo,\xd3\x16\xa5\xde\x02\x93\x9a\xed\x9a\xe38\x04qV\xf3\x1b\x8dB\xf3~:\x1d8\x8f\x84\xa0\xbd<\xe4\xf5\x1c$\x16\xf0\xcc0n&\xfe=\x9c#\x14}\xac(>\x14\xad\x1a\x92\x8a\x8b\xa5\xa1\xaa\xe8P\xbc\x88\xf9e\xf7\x86xAA|Cw׆\xb8\xc7\x13ߐ\xe6t\xec\x86\x17\xe4\xf8gӗe\xa6\x86\xe4U\x13NF\\u\\uBuV\xf5\x1bմJ\x91\xc9\xf7B\x90\x83@\x10d\xd6IV~\x17\xbe\xfa\xbct\xae\xec$\xaey\u05c9Ka\xf0\x8b\xb3\xe87h\x1a\xc95\x0f\x184\xe7Z,\xa1\x89五8L\xe6\"\x13N\x85\x17p\xf4\xf4W\xbf\x9a\x17=%\xd7J=\x04\x00\xb3E\xae=\xe3\x9e\xf3\x99+\xd0\b\x90̣\xc8\xfeȕ-\x17\xbe\xf9\x00\x8a\xa4\v\xcf0 \x8fYȌ\xe1\xfa\xc2\x1a`\xc13S\xaaf\xa6\xb4J\x84lI\xdb\x18\xb6\xfe$2n\x99\xe2 {^\xcdk\xbdZA\x8bTZ-ßW\xfe\x80\xf9a\xba\x8ai\xbaF\x03N~\xc1\u007f\x884\xcd<\x80\x13\xe9ӟSA\xaa\x0f`M\x84\xccء\xb1\xd4\r\xf0\xc0-\x87R\u007fD\b;\u007fv\xf5}H31\xb1\x9di\xbf\xf8\xced\xe9eU\xda\xe2\xd20O>{\xe8\xff\x01|@\x8d\xa4\x00x\x01c`d`\x00\xe1\x94=ͪ\xf1\xfc6_\x19\xe49\x18@\xe0\x84\xb8\xff70\x1d\xd3\x16\xf9o\xc1?\x11\xf6u\xec\xc5@.\a\x03\x13H\x14\x009l\v\xa1\x00x\x01c`d``/\xfe'\xc2\xc0\xc0\xc1\xf0o\xc1\xbfE\xec\xeb\x80\"\xa8\xe02\x00\x87\x94\x06]\x00x\x01m\x91\x03\f\x1eA\x14\x84\xe7\xf6\xbd\xbb\xda\nj۶\xed6\xa8m\xb7Am#\xa8m\xdbf\xf4\a5\xc2\xdan\x83\x1a\xd7y?\xeaM\xbe\xccz\xf7\xcd$E\xac\x99\xba\x8c\xa4\x00\x16H\x05L\U000cb80c\xf6Ř`9z\xfb\xab1Ȼ\x84i\xae/Z\x93jJ%\xbd\x1dP\xc3-\xe0\\>,p_\x91\x91s\xdd\xc9~ҕ\xb4&Y\xc8\x142\x8c\xb4'\xfd\r\xdbOj\xd8\x1d\t\xa4/\xf2\x05\xaf1Ho\x01\xfa\x11\x11\xbd\x86q\xfe(j%D\xe45\"\xfe4\x8eW\"\xe2\x0e\xf3\xbd\"aC]\x1f\x9b\x0f\xdes\xed\x06y\x8bq\xba\x92\xfa\x9az\x94\xe7\n\xa0/\xc9\xe8/\xc6Q\x9d\x03$I\xcf\xffu\x05\xb4\x02i\xc9;\x06a#\xff\x9c\x85ZFk\xa3\x904\x0e\xbf\xeajo\x96\xd6@g\u074b\xddr\x90\u007f\xdeK\x1ab\x98[\x8fl\x9a\x0f\xf9\xf40v\xbb\xf4X\xeb҇'\xe5c\xb4\xbf;I\x11\xec\xb6y]m\xfb\xa9<#\xa5x~%\xda\U000efe78\xb6Qn\x00\xfeG\xbe\x0f\xe4\x91gH.3\xf8\xfe\r\xf3\xd1{F5\x9f{'\xbcg\xff$\xa9KƐl\xb6G\x1e`\x14\xffV&؊\x9en3\xeaʣ\xb8\xff\xf4\xde\xe6\x14\xe1g\x19\x14\xddߛ\xeb\xa5H\x8eh-\xe7\xb0ۯ\x86a混\x11y8_\xc3]@m\x9eo\xeb?B\rR\x94\xe4\x10\xbeg\xbe\xff\x8f\xe0J\xf8ݲ\xb0\x1c~\xc3r\xa8NZ\x93t\xa4&\xb3*\x91\xc8\xe1o\xf8\xafy\xa6\x96\xc5\xefD\xb3`fz\x05\xbb\xcd\xf7\xff\x11lG\xe7h\x16\r\xff\x84\x19\xdc6\xff\xa9\xa7\xc8k\xfa?$\x91\xc3?\xd0\x17\xea\b\xcb\xe2w,\x8bh\xd6T\xbb\x8b\x9e92(\xaa@\xeb\xa4\xc4\xeb\x8d,\x86\x03\xbd{B%2\t5\xc4a\xb1k\x8eֆ\x97\xfc\xfbZz\x9b\xf2\a1\r\xcf=\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00F\x00x\x01\x00\x01\xba\x02J\x03\x02\x03&\x03V\x03\x86\x03\xbc\x03\xea\x04 \x048\x04r\x04\x92\x04\xe4\x05\x1e\x05t\x05\xf2\x06F\x06\xae\a\"\aJ\a\xf4\bf\b\xbe\t\"\t^\t\xa0\t\xdc\nP\v\x1a\v\x86\v\xfe\f\\\f\x9c\f\xd6\r$\r\x82\r\xb8\r\xfc\x0e6\x0e\x84\x0e\xa4\x0f\x18\x0fj\x0f\xc4\x10\x12\x10|\x10\xee\x11X\x11\x9a\x11\xd8\x12,\x12\xe2\x13B\x13\x94\x13\xc8\x13\xee\x14\x0e\x142\x14P\x14h\x14\x8e\x15\x02\x15d\x15\xaa\x16\n\x16h\x16\xcc\x17\xa0\x17\xe2\x18\x14\x18`\x18\xb0\x18\xca\x19<\x19z\x19\xc4\x1a&\x1a\x88\x1a\xce\x1b>\x1b\x94\x1b\xd6\x1c.\x1c\xdc\x1dn\x1d\xd8\x1e&\x1e\x80\x1e\xa4\x1f\x00\x1fT\x1fT\x1f\x9c \x00 v!\x0e!\x82!\xb2\"l\"\xb0#Z#\xc4$\x18$F$N%\x1e%6%\x92%\xce&\x1e&\x94&\xba'\x04'B'|'\xc2'\xfa(B(\x92(\xbc(\xe2)\x12)\x88)\xa0)\xb8)\xd0)\xe8*\x02*(*\x92*\xa6*\xbe*\xd6*\xee+\b+ +8+P+j+\xce+\xe6+\xfe,\x16,.,F,`,\xc6-H-`-x-\x90-\xaa-\xc2.\f.\xa8.\xc0.\xd6.\xec/\x02/\x1a/2/\xe2/\xf60\x0e0$0:0R0j0\x820\x9a0\xb41D1Z1r1\x881\xa01\xb81\xd22D2\xbe2\xd62\xec3\x023\x1c323\x983\xb03\xca4\x004P4\x984\xb44\xd04\xfc5(5\\5\xb86\x146~6\xc26\xf67,7J7\x94\x00\x01\x00\x00\x00\xd3\x00i\x00\x05\x00S\x00\x04\x00\x02\x00\x10\x00/\x00Z\x00\x00\x02\x1f\x00\xe5\x00\x03\x00\x01x\x01m\x8e\x83n\x03P\x18\x85\xbf\xd9^\x8c!\x9c\x15.\x9aW\xdbQm\xfb\x19\xfa\xd4=5\xf3\xfb \xf7\x02[\x84Xce}\x87\x15\xcea\xb8\xafr\xa8k\xb0\xafq\xcb\xf5p_\x9f\xd2lp\x8fe\xb8orJ{\xb8\x1f\xe2\xa1\xc3\x0fUJd\x88s\x89\x9b\bEj\xb8H\x90\xa2A^w\x95O!1!Ei\x12\xba/yP-\xf3\xf9\xfa|MhI\xf7%/<\xf2\xac\xb8$JCh^j\xa1\x8a\xb7\x89\u007f\xecNSW\x94y\xe7I\xd1\xeaǣ\xb8\xb2*&6\xa1\xab$WJl\x9e\xcc\xf0O5\xf5\x9a\x10\vF\xbe\xf9ņ[\xfd\x81W\xf4v\x17Y\xd5)\x8cx\x01l\xc1\x83\x01\x03A\x00\x00\xb0\xf4k۶\x8d\x91n\xe1\x0e\xd4.\xf0\x89\b\xf8\x05A\x9c/\x12\"I)i\x19Y9y\x05E%e\x15U5u\rM-m\x1d]=}\x03C#c\x13S3s\vK+k\x1b[;{\aG'g\x17W7w\x0fO/o\x9f\u007fA\xf0\xb0\x05\x05\x00\x00\x00p2?foٶ\xed\xb5\xbd\x9b\xf5\xb2]\x97l۶\xf92\xcf\xf9\xe5s3\x9ah\xaa\x99\xe6Zh\xa9\x95\xd6\xdah\xab\x9d\xf6:訓κ誛\xeez詗\xde\xfa諟\xfe\x06\x18h\x90\xc1\x86\x18j\x98\xe1F\x18i\x94\xd1\xc6\x18k\x9c\xfd\xb6\x99m\x8e\v\xd6\xfah\xae\xa5\x16\xd9`\x8f\xed5jZX\xa3\x96YV\xf9\xe5\xb7%֙\xef\x9a\x0f~\xdah\xaf\xbf\xfe\xf8g\xab\x03\xee\xb8堠\x90\xe5\xc2\ue278\xed\xaeG\xee{\xe0\xa1O\xa2\x9ez\xec\x89Cb~X\xe1\x85g\x9e\x8b\xfb\xe2\x9b\x05\x92\x12R2Ҳ6\xcb)\xc8+*\xa9(\xab\x1a\xef\xb3\t&\x99h\xb2\xa9\xa68m\x8b香a\xa6\xaf\xbe;\xeb\xa5W\xde{\xed\xa8cN:\xe5\xba\xe3N\xb8a\x9e\x8b.9_\xa3v\xbdJ6\x11\b\xb4\xeb\xd8 W\x8d\x14K\xa1\\1\xd28\x9a\xab\x14K\x95|\xa4\x98\xc8\x15\xff\x03@m\nTz\xe0\xcd\n<\xc0\xca\x027\x03\xf7\xf5l\xf8\a\x80\x95\xfb\xbdf×\xe4\xc1~`TUU\x1d\xdfa\xb6\x06+hO\x15\xb9}\x05\x1cF\xd4\xf6\xc1\xb6\xe9\xb0|\x94\x11\r\x87:ՐNs\x95\xac'\x84aiY\xa3\xb6Qwg9>\xc9zGvȐ|\x80䠝\xa7\x84m\x17d(!K\xb1\x03\xcfv\x1e\xfdE\xa7\xcc\x1b\xd3\xc6\xc3ޮ\xdd\a\x82\xb5ò\x94\a\xfa\xb9R\xbf\xa2\xae\x90_V\xaep-K\xbb\x93\xcbd&\xf3/\x13}@\xf2\x17\x00r\xd9\xc3l\xc9\x01+4\xe6\xa6\nU\x15\x81\xaa0\xae\xb6\xc2\x16\xfe\xcd\xff\xad\x9c\x14X\xb1\xf6]:\xc1\xb2\xae_&\xebP\xad\xe9\xdf\xff\f1\xe4\xa6\x1e\xb2\x98\x95Y\xd4a\xe7.c\xc3\xc80i\xa1G\x88\xf8U\x05\x8c8\x10\xe2-\xfcv\x87\xe7\xdb\xd61\xbf@&\x87\xef[Q\x04\xacd\xa8\u07fc\xf9\xe1\xb0\x03\xcb\xee\x9b\xdc\x1a+\x8bB8\x16>\x88\xca:(\x94\xb4j\x9dW\x119\xf9\xa3w_\xac\x8e\xcfFU\xf1_I6\x16\x9c\x96ӫ\xb1\u007feI\xee\xcfo\xf2\xa7ƫ\xe1\xb4\xe8\xed\xc8\xf6\x89\xb1\xe0Ϋ\t0~\xa0t㭡ϝ\xcaO\xdc\xd6p\xc0[#'>t\xe4\x9d\xfc\xa2\xd6\xd89Թ\xd2y5\xbc\xad\xf3\xc1خ1\x82\x1e96\xaa\x1a\xfdmtVٿ\x8d\x05\xbb\xd1\xfa\xb1Α\xff\x1a\x971f&^-䀷\xff0\x19G\x8f8\xcc=\x12\xef\x109\x95\xedp\xca\xc1\x92\xfc\x11\x9fs\x99rg\x11\x1f0\xd8Ą\x1d\af\xd0\xfcX\xc2\xd9Pk\x81\x89o\x12,\x04\x04\x11\x85\xad04\x8e\xd88<\xf0\b\xc1\x88\xed\xd1F\x90\x90ARP\xc2H\x92\x0e\xabY\xbb`\x06\xfd\xc8\x06\x8d\xa2\x197\x8de\xd6~|\x87\x1d\xa5p\xcci)\xce\xda\xd2\x19.\xb9\"\xd3uw\xad@\xe8\xeb\xee8ad\"\\;R\x8e(8\xd1d\xa81f\xe2,\x17\xbc$\x88\x8cRNP\xc2IJɠRIͷ\x05\xad\xdal\xd3\xde\xdb\xf1\xb1\x1d\xce\x0e\xbd\xfaz\xa7\xc9,\xec>\x8b\x96,\xdbo\xcd:\xa3#\x8e:\x16N\xc39\xbb\xb53\xdf\xf5GD>;l\xc2\x13A\x88\xbc\xd1ɰ\x83\x182lĨ\xf10\x91ISv\xceM\xcfK3w\xad\t\xbe\xf5GD~_\xd8\to\xc4F\x06\x99$I\x92\\oyu\xe2Ƅ\xa5@\xb4\f)\x18\xd5,\xb8\xb6.\xec\xd6aіG\xaco\x1d\x19\xb5\x05\xea\xefknK[\xdb\xd6mmo\xc7n\xef\x8e\xf6\xb6\x0f\x15\xc9\x02\x95\xb5z!p\xec\xc6am$\x1ct\"@E\x9a\xe8\xfdt8\x9a\"\x02\x00\"\xe8gO\x83\xba\x8a\xea\x86Oر\x90m\x9f\xe0\x1b\u007fDd\x06\x83\x86\f\x1b1j\xbc\xbcZ\xca\x1bdlˮ\xc6\xee\x8d\xcaٱg\xf7\xe6@\x85.k\xc1\xf8\xbdR#\xd7\xf8\xea\xacuQ6\xb2w\x88&\"8_\xc0\x82\x8c5\xd9\xeer\xfbҬ<2_;\xb4a\xa0؇#\\\xbc^\xc1H\xae\xf4\xe3d'\xe1ʏ\xe2&\x1e\xd5M\xdad\x93\"UZh>-Z\xb5٦=Ͼe\xffI\xb3g\xb7\x9b\xb3Ǽ\xbd\x16\xec\xb3hɲ\xfd\xbd\x87\xef\xdaYgt\xc4Q\xc7\xc2\xf1{\xc2I\xa7\xc2\xe9=\xfb\xeaK\xf7\xf2\\%s\x83\x9fd\x17\a\xba\xa3\x99\xbe\x1d\x1a\x9fWp\xb9rEZ\xb2\xbdA\xddN\xae$R\x95\x9bH\x88ҝ\xafHbT\x82QUU\xed>\xa2/g\xddns\xf6\x98\xb7\xb7\x95\n\xde\xdb\xe1(\u1acf\xe7DN\xe6T\x8f\xca\xf9ɲ\x96#\xc7\xc6\xed\u137d+\xb1\xc3\x01\x17\xefk\x04\x95TZY\x12*\x8f\xa2\x19O\x98\xc6w+U辽\xf3\xcf\xccfw\xe6\xb2'\xf3ٛ\x85\xec\xcbb\x96\xb2\x9c\xfd9ܵ\xc9ǭǘ#9\x9ac9\xde\x13=iN\xa5\xabr\xdcP7\xb8\x12\x9d\xcf)\xb7\xbc̲\xac\xba[\xa2\x0f[\xe8\xbe.v\xa9\xcb\xddO\x86$Y\xabf]\x88l\xc1\xed8\xf1ο\x88T齪\xa2\xa26\xeb\xb5ب\x88\x88lLk\x01\x8a\x88\x88\x88\x88\x88\xa8\x88\x88\x88\xaa\x96Ei\xd6\nq\xf3Ʃ$P\x930\x06uZ3gA\x8b\xb6\xd9\"\n<9\x93\xa7\x9b\xb0^s\u007f\xe1\xab!x\x13\xec\x8a֬\xdda\xae{\x9e\x9e\xef^Q\xd4\x16%\x89J\xec\x9c\xd6\x15\x9em\xf95\xa4\xabJ&\xcdV\x12,\xa8\xd4\x16\xb5u)\xbf.F+\x91u]\x10\xcc\xe9&\x82\xd4\xc1X\x8e\xb3m\x8e\x1d6oE\xa4J\rhA\x02U\x14>eHJrL\xad\xbd\vX)O?\x9d\xb3\xb1(\xc3\xf9\x11z\xf0A\xd2I]\xeffbW\xf1\xb5\xed\xfb\xda\x01}\\~\xae=}:\xb7?\x1d\xff\xfeAh\xe2ʃTy};\x1b\u05ce\x92^\r\xa2\x8e\x85\xe9\x1bز\xd5\xf6(\x95}^\x85{YC\xbb\xa8\x16ɻ\xe6X\xb5\"T\x9dFi \x94\xab\xccgCO\x19\u0590\xd8}\x83\x85\x18\xf3\x04Yk\xb4}\x93A\xff\xedW\x1b\xfb\xb7\x9f\x1e\x8d\xc4[\x1a\x93\x16\x06}\x91\rf\v\xd4\xe5N\x86\t>C\x18\x86\x8d\xd2JO\xa3L1'6\xaej\x1e\x84\xa0\xb2i(\xba\xd8\u05fb\xfdA\xbfx\xf3zE\xdd\xeb\xb3\xdfO\x04\\\xd3qg:j\x1d\xbc\u007f\xfe\xab\x16\x16\xaarUM\xfc\xffiP\xeb\xe2~\x13֩my\xe5(\xaa\xc6~S\x16?\xfe\xda\xec2݈E\xf2\xeck\x82\xff\xccV\xdaR\xd3q\xb2\xf8\xb5\xf6\xda\x1b\xaf\xb4K\xa5\x83\xf7\"\xaf\xc9\x1aN\xfd\a.^\xe2\x83\x12l\x93\x95r\x92\x92\xa5\xbb\xb0\xaf\\Ēe\xfb\xadYgt\xc4Q\xc72\xaa\x13\xd7j\x0f\xdd\xf2f<\xff\x0e\x88V\xa7W\xd5\xc3hҤ_\u007fܻ\r\u007f\xf5;\r\xf9{W\xd9\xe1\xebHy\xf1\xd0\xcbB\xbb\\SH\xbb\xa3\xf3\x9f\xff\xac\xf4\x96\x9c\xf0\t\x88\xc3M\x93\xda9\xc6\xfc\x15\xd3\x1f7\x04\xf0\xdd\x01\xb0$\xc0J\x1b%\xb0\xc0\xbe\xcf^\x90m\xe93\x98\xa9\xb5U\x1e\xde,\bLn\x04ț\x15#`A\xa6\x1c\xa7\x84\x99=\x86\xf1\xef2w\x12`n\xf6\xcct\x9c\xe1\x9b\t!^\x8aO\xe2\x8f\vB\xe1\x1epo8\x12\x1e\tg\u007f\xfb\x96\\\xcf\xf8\x83\x13\xdc\r\xee\xb5.\xe2\xc8\xfam=W\xfb\xfe\xafo{\xf5\xff\x97/\xf4\x17\a^\xac\xbeX~1\xfc\xa2\a\xbd%\x1c\x1f\x15\xb1\xa6\xf2z\xde\x05\bw\x00\xbe\x06&w\xde\x18}k=o\x15\xbeK\xc8\x0e'\x92B\xbe\x11\x96\xc6\xf3\xec\xb8J\xb1Ž<튘\xc6\xdd\"\xb2\xe5a\x81\x94#/\x0f\xb1\x03%V\x02W\xd1\x18\x89~R-q\xaeċ\xf5\xa08\x9f\xf7\xd6\xfc\xc1\xdb\\\xf2\xdf,̇\x8b\xd4\x1d\xc4\bZ#k\rٺ\x98j| \b\xf4qtγi\xa0*\xae \xb3\xc4\xc3:#\xcb\xf97\xa4ݞf]\x17[\xab\xc6H\x03\xac\xc4\xc7\u007f\xdf\xc6v\x88\xf3\x18\xc4\xefE\x18u\xb7}\x81\xe7d\x18\xb2\x88wێ;q\xde\xc2\x1a\xa4y\x1e\x17\xe3q9r\xfd\x9f\xf7\xd2Eb\xca8t(\xe3\xb2\x1c\xb7\x9e\xc3^u\x96\x99_c;\a\x97\x9b9[\xc0\xaf\xb3\xa4e\x9f\xa3l\x8cv\xcd\xfe:h\vF\x974$\xbb\x99b\xae{\xbd\x05\xca,=\te\xf8\x8b\xc8n]\x1d\xdf~ϻI\x89\x06\xa5\x0făd\xd0\x18@\x15\xa2V,Q1\xd1Cv\x9b̀*Z\xf1\x1b\xad\xb4\x82\xdab*\xd6xZ\x8eCG@\x1e,\xf4\a\xa7?\x85\x92\xa8^\xcd\xe2\x18|\x0e\"\x90\x97iL5\xac\xe3P\x1c\xafC\xe0dm\x06$\x1eJ*/I3^l\xcfH\xa2\x05\xc9d\xbc\xa9\xd5X\r\xa0\xd1|\x16\xdf\xf9\u0528\x9ad8\x022\xb0\xf1\xeb\b0\xa6\x11w\xf35,)\x80#\x94\x1c#\xfa\xa6\xfbN\x8dߘ\x9e\xc8Y\xbe\xd6Wi\xc5Z\x8d\x91\xea\x10D\xb0\xa7\"CH*\x98!ʴ\\\xb5\x92_\x01\xb9o\x17\x8e\xeb\xdeOJԞvE\xa4I\xa4:\x87\xeb\\\x14\x914\xa0zT\x80\x80\x1c\xa2\xb6\xf0#s4\xd0t\x81)M\xb6ɵ\x19\x8e*\xcau{\x1eSG\xa6:q7|'g\x16b\xbc\xd6/U\xb5\x9e\xb0}\xa6ل\x90\x95\xd1t\xe7\x040\xea\xc0r\x16B˵E\"\x16A\x164EF\xbf\xb2\xe4r\xc1\x17\xd5\u008ej\xb8@G\xee\xcc0_~\x06\xf1Z\xac\x8f雅\x97\x99\x14\xbeI\xe3\xd6\x00\xb2\x05$4Z\xd59i\xb2\x8ex\xf6\f\x17ʡe^\x94\xa0\x053\xbf\xee\v\xd5\xd4\xf5\xf6{\xc8\x1a\x9f{\xd6\x02\x8b\xedR\xd6\f<\xd7K)\xff\t\x91\xf0\xe6\n>\xaa}ϟ^g\xb7\xd0r\r\xa7\x02X\xc8̠\x14 9\x85|\\\x8e\x95\xd7S\x16\xf6\xac\xaf\xad\x89\xc6#\xd3\xdd\xe2;\xbcQ\xd7\xd2#pL\x9f $\xfe\x89\x8f4o|\xe6dz\xea\xe4\xb3\xca̻!2E\x1d\xf9>M\xa3\xec\x1e\b(\xdd*v\x00\x97)\xa4\u007f2O\x86ę`Y\x0eπ2\xec\xd91\x11\x01\xf0\xc3r\x01S\x02\xff\xe0C\xbai_\x04\xa8ZY\xea\xfeKw\xeb\x14T\x8f\x8fUu`\xbeD\x93u'\xfd\xa6\x9e\xf5z¡\x84/t\x14\xc1\nv\"z\x86Q\xe9Y\xf2ư\xc8\xd8O\xbd\x10tK_\xb5>\xd2w\xa4\x8d\xe2{\u07fcȁ\x12\x82(dKŶ\xe5\xf4)\f3)\xf1z\\'\f6\xfc\x1a\x03\xb1;f\xceҎ\x1d*\xe1\xcd\\\bL+D\xe8\xf0\xc8\xda+X\xa0\x904D\xa3a\xfa\xfcZd*\xd8\x06_@*\x9dz9\x1b\xa5\xa1Q+\xc3T\xc4\x12E\x88\xe0\x17P\xddc\x14\xa5\xcc\x1b\x06?\x1a\x8c0F\xf9c\xa33ÓP8\xc7<8\x01\xac\xab҆\x9d\x1d\xd4\xdeIm\xff\xe4*Z\x98*\x955}\xda\xc7i\xf7\xc3fGX\xff\x88#,\x05\xc4\bNd\xd7\xd2\x06\x04\x1eQ\x0580\xfaמ\r\x9b\xa8]\xb8\xcaW\xb8\x1d\xf6\xc7\xc0t\nY\xf3s\xeft\xf9\x1c\x12>rY\xde{5\xca\xefk\x181\xe4\x0e\xf4\xe7\xa8\x00\x1a:g\x81\x936bj\x15\xa3\x9c\xca^_\x9b\b\\]\x92Y\xdc\n\x9eI\"\xbeU\xd1\xce\xde\xf1C)(B\xde+\x98\x8f\xc0\xad7\xb5*)\x81\xb9\x89X\xf7(\b\xe1/\x03q\x10\x9bpLc\x06A_X\xdad2\xb6\x93>\x04`T\x82\xecW\xe0\r\xb3:\xe7\x9c\xf3k\xd17n\x02\x97W\x84g\xed\xd84\x8brS`yY1V\v\xba5Њ:B\x19\x1e\xd7e}>w@z\xea\xcd.\x04\x88\x83BC\xd22Ir@^\x98\xa3|R\xc32/nD\x97\x11\xab#\xe5d9\xecI`\xd9!\x86\ae\xe3\x93@>\xd9ʚn\xa5\xa6vl\x91FJI\xa1\xf3\xaad\xea-\xbfrr\x14\xf0I\xf98\x15 _\x93\xa7\x04\xb2\x1a\x04\xe5\xba\n\xa6\r约z\xc6\x0e\x84\xd8`A\r\xe2\xfd\x14\x95\xc4\xcfA\xb7\xfc\xb4w\x1a\xf9\xad\x8e\x94\x14\xa1\x99\x1c\xf5M\f\xb9\x1cs;\x9e\xac\x9cJJfX\x94\xd3\x06\x93l\xa5\x97\xb0\x95éF\x1bp\x03:̚\xeb\xb2\xeb*]`\xd00\xe2w\xefuAК\x84\x8dA2\xc6tU\xe0\xb3\x17\xb2R\x10|\xb9\xb2N\x89\xaac\u1ad8E\x13\xacTA-\x15\xfd\x9a\xbe\xdd\xc3\xc3\xde\xd9Q\xeb\x99\xe1\x19ȭ\xc2I_\xa8 \x16\xfbT\x16\x80\x83p\xd3\t\x1a\xe9\x9d\xc7\xd5\x18\xbd\x875\xf9\xe5\xa2@\xfb\xae\t\xdb\xe0%*_\xdf\xd4YۄN\xee\x91\x10\xf2.~\xb7\xb2\xca\xf0凊\x18݈\xc0\xe8-+$\xcc\x00Eւ\xd4\x0eWXN\x81\xd7|l\x9d\x80\xb4\x1e!\b\xac\xf1\x89.}W\xb31\v\xe6\x89=>V\xc2\x0fX\x89+ٵ\x92\x89\x9amʽ\xbcԈ\a\x88\xc9k}\x8a\x99\x99\xd7B\xb3\xb4>\n\xff\xad\xa9\xafv4hWʚx\xbd\x01\x1d\xfc\xca\x17X\xb7\xb65\xc2\xe9c\xa9\a\xec\x85\xd7T\n:\x02鰩\xa9\x84\x1b\x94\x0e\xb6\x18\v\xbbt\x10s\x1fZo \xd5: R\xb7Tz\x89X.\x06EMj#\x9a\xa8-\\\x9fi\x83\x036P\a1\xa5\xd1x\xbeF\x9c\xf0\xc5KE \xf4\xbc\xb9\x12\xd0 ^K#9\xc0\x14F\x03n\x8bZ,V+\x92~\x9f\x0e\x85\xf4;X%\x1a:~e\xb1\xa9\x84U\x89;~\xee\xdbi^\xec\x9c{U\xe07 u\xf3P!\xe6җ\x97\xa8\xb4\xe5\\\x92w\x03g\xc6U\x9d;\n\xadk\xe9\xe2j\xc6\xef\xd0X\xf1\x9e\u007fq\xf5lX\xa7\xba\xd2\x00\xe3}\f\x89k\xd1\x05\xc7E=a赭AX)[\x1a\xdfVg\v\xcd\n\xa8JV+\xea\x1d\xf0\x89\xfbB\x17\xf1,\xeb+&\xa3\x81\x19\xea\xc2Ŝ\xcd;p\xe2\x8d?B\x04g\xe2\xdbJ\xcfU\x88\xa3\xbc\x8a\xe6}\xc9&\xf0E\x95-\xe8'\xd3n\xf68q\xaf\xba\xa3pU\x0e\xe9p\f\xc4\x1f\\\x06\xe5Zj\xe0\x14\xc9k\xddL\xb2\xa8\xf9\x12?\x1fAC\t\x8c\x9e\xb8\x13s#ց\n)\x94\xf5\x0f\x0eLanʋ\x90\xb1\xadP\x9e{\xe3\x98\xee\xdam\x9fu\xa1\xb1\xe0g\x9b\f%\xc4\xd5Ͷk\x92+\x99#\x15XY\xa9\xe4\xa8\xda\xfaF\xfd\xfb\x05\xbf\xa5q\xaf\xd3\rx\x93*\xe4usݛ\xf0\xef(Ͳ\r\x0fd\xa5\xa3\xb0\x80\xf4\x16\xd9\xef\xf8BA/D\\\x93E[\xd3\x0e\x91\xfd\x16\xadf\xc9I!Sj\x1bg\x02-\x1d\xf8\xa3oC\x90\xa1\xc1\xf1&\xd6Ztsk\xd0\xd4,[\x9a\x14K\x94<\x81(\xc7S\xcbM\u1aba\x18#\xe5\xcd\xdbr\xdf\u007f\xb9z\xc3|ܶ\t\xda\xec\x18Ŧ\xdb\x1b\u007f=\x05\xbd\x13@\xeb\x8d\xff}\x94\xe2\tQ\xddv+|\xc0!=\xfa\xbc\xc6\x10{\x8a\xadB\x8d5\x14\xe1\xd0u\x1bn\xae\x94\xfb@S\xe2\xa6\xc3\xcc\xd7t\xdf\x0e\xc9\x1f\xfa\xfc\xaak\x1ce\x93\xba\xf5\vZu w\xce>\xdb\xd8\xc6S\xe0\x8b\x92\xa6\xbb+\xbb\xaeZ\xae\xcf]pJ\x97\xb7\r]N\x002dʽ`\xa4;\xcfz\x8cM\x9ds\xae\x8dDΛ=\t\xc1\xff\x9by\xd5̓5dc\x95\x84-@\x14\xc6[\xddu\x0f\x16\x84\x00G\xcd\x18\xe0\x9a\xf1*2\x01\xffq\x87\xdfԈXE,\x83\xae`\xd5xY%k\xb8p\xa7\x84Ĺ\xb0b%\xb4{\xe2\xc7\xf6E.\x13\x16\xdccXh\xe9\x1e\xa5\x90\xffWyﲼ\xe7\x84碇\xe7a{\x0f\x9ak\x97j\x06\xcb\xe72\x95\xa93:\xd0\xce]\x05\x85\xb5\xdb\xd0I\x16\xf5҄!\x15jp\xb9\xdeGPy\xfcT\x8d\x19\xd9\xed\xb9\x9d\x05חl\xff\xcc\xd3\xe3\x9d=\xd9\x17Cvx\xef\xe1\xf9܁\f\xa0{\x8cX\xbb\xf2\x81\b\"\xca:\x03\xd4\xf2z\x1a\xd7\xcd@\x92.\r$,\xa1\xbc\x93^ђ\xa9\x01F\xe7:\x1a\x02\xa4\xab\x03\x8c\xc1$\xac\xf6\xfe\x8cY\x1a\bR\x1e*\x86\x8b\x02ԾS\x1cBijBkh¦?:\u007f\"j\xad\xd4\x0e\t\xee\x1f\x96\xed\x12\xe1j\xf7l\xfb\x90C*X3C!\xc7\x15\nX\xf8\xa2dY#Yl\xb5\xd6y \xcb|\x01\xc7\xf4f\xf4Vwa\xf9\x16\xf3J\xefm\x8cdYP\xfe*\x17\x11\xba\x1c\x8bAT\xa6\xda$g\xbc\x9dE[500\xa0Te'S\x01\xbb9\xd3=\x9c9ɤ\x807)\xe1\xcc^\x9aӣ\x96\xf8\x95e\xb4x~1MX\x89Y\xd2\xeb\x91\xfbx\x15%T\xb1P\x11\xe3*|\xf7-\xad\xef\xe1\xec\x96L\xf2\x11\x89\x8b\xfd\x91\x8b\x84\x80\x10\xecm\x91\x14\xfe%ٛ\xf5\x13\u05cd;\x89\x17\x8d\xc0#\xbb\aiG\x87\xd3IL\U000ac0ba\xd53\x16\x87\xee~\xcdp\xf3O\xccRYUH#3\\\x12\xfc\xb8\x94\xaf\xcd\xc3\xf8xZ(\xd7S\x1e\x1e\x8aLf\xd0ը\xe6\xf7b\xbf\x9a\xb8\xac\"\x05\a\xf7kT\\`\xa2gf\x84\x90\xb09Mf\xf0W%4\xfb'\xcbB6\v\xe9\x04}B\xaa^|X]\xbe\xbc\x84`y\x88v\x1e\xb5e`\x8b\xc8\x19ty\x1e\x14\xfd\xf3\xde%\x88\x9aS\x80\xaf\xf3\x10\xeb\xe6\xc9e9у>\xb9π\x14_\x8f\xfa\xc4\xff\xad\xe4\x9e(u\xb9\xebL\xef\xcd\xcfU\xe8ל\x9fu\xf7\xd9i\x8b\xa2\xa4\x92\x9b\xfc\x17\xa8\xf7\xc0\x05\xbf\xa5\xa7\n^\xa4g\x83(i\x91\x9d\xa6\xbb\xcf\xfd\x19\xf5\xba\xea\xf3\x8e\x9b\xddg\x96E\xa9%\xf7\x04\xbf\xa1>\x01\xdc6\xa8\u007fGw\x91\xfb\x99\x1a\xcfϱ\aի\xda)\xed\x11\xf5\xc7\xd8͞^g\xb6\x15\xf9w\x80\xbf\xb6\xb5\f\xb7\xb4\r\xb7\x1d~m\x9b\x86\x81\xee\xed\x1f\x0f}k\x82눵\xed\x9do\xbcZ<\x9a\x9d\xb7T\x00靅\x0f\v\xa0\xbf\bB\t\x06W\x1a\x91\x16\xc6\xc3Hg#\xa07\xfft\x90\xe8Zr\xf6\xa0v\xeb\xe6\x15m\xd9\xd9p׃\xbfw\xbf\xe8\xcc\x01mC\xd5jv\xf9\x99\b\xd77\a\x87a\xcf\x19\x83\xf9\x02\x91\xa8\x80\xcf\x1e\xf0~12\xec\xfd\x92KG\xa5\x84\x93}\x13x\\m̻\x9fꣶ\x8e;d\xc9\xf3\xb7\xe8\xb4\x11H\xe9\xf3D\xa2\xdf\x179\x89\xc6@%\x84j\xebu\x80\xce\xe2\xe8\xa0w\xc8'S\xe6z\xaaT\x9d\xfa`\xdd|2\xf0\xbd\xb6\xefO\xd3\xce\xf8?4\xdflC\xaa\x17\x06\xa9՜B3\x965\xcc\xf5\xd3\xee\xd7}b\xfaq\x13h\xec\xc2S\x91_\xc8\b\xe3\xe8}~d\xfb\x9f<=\xdeړ\xfd0\xa4nhR*\x1c\xa3\xfd\x1b\xa4\xeat\xf8\xbb\x88\xb3\v9R\xffy\xe6Y\xc13\x06\xb0̒\x0fM\x0e\xadj\x8e\x0fO\x0e\x03\xed\xf6\xe7\x8c\xe7\x053#`\xf4G\xe0\x02p\x9b\xc2Dئ\xf8\xc63\xe2\xdc\xff\xc5b\xfa\xb3\xa1b\n\x1e&\xee\x90{\x87'\f\x91b\x11\xb4^\t\xeexdF\xda 1W\x1d֩dy\xe4Q\x92\xe2\x13ң\x19\xcct\x1bF\xa0\xe3\xb1\xe0(\xfc\xbb\x9d\xe7\xf8\x94\xe6\xb8\x05]\x12\xae\x11r5\xd8\xc8 \x1e\x8c\x00\xa7\xcb̡\x14\x06\a\xaf\xf2\xa4\x886\x87I\xb3\xfd{\x12\xb5\x91{\x8bj\xa6X\x99AI\x1b\xa46l\xfb\\\x97i\vk{+3\n\x94\xe5\xba\xe2\x1a\x1c\x1b\x1d\x9c\xeb\xda%ޮ\xf8qd䎸n\x9fI\xd1ʥ$\xfb\xd9\x11\xf1\xcb\xd1\xe9_\xa4\x03\xd4\xcd6E\xaeY\xde3=\n\xcc\x0f\x01͇ݴ\xe7\xcei\xdd\x0e\x9f\xa8d?y\x1b\xda~v\xd2\"\xfb\xd4\t\x9d-\xed`@[\xf6fE\x98(\x90 s/3c\xdb\xfc\xefH\x1c\xc4_\x90\x1dAK\xf3\xac\x02>\xd3\x17\x10\xbb.\x9c@\f\x9dXD\xec91\xaa\x1b\xd8+5\xf3t\xa07\x1490\xbcL%\xbd{\x01\xa4Ҹ\xa3\xf1\x9cos\"\xa3;\x97\xe6[\xc0\x97\xeaC\xd9V\xd6\xecMՁIbRUb6}\xb2V\xbf\x1aQ\xfc\xfb\xa6݂\xcf\xf2RXl\xa4\xb7*\xaaz,w\xfe\xb9\xce\xdeZ\xb5ُ\xc7\xf5ɦ\x86\xc3\xd3\x04\xcc:t* \f\xf9\x15\xec\x97Ԏ0\x1f\x1bz\"\x9fT\x0e\xad\x8a6'M#E.\xb3Ez\x16\xc9m4\x96Qs\xef\v&/\n\x9d\xf7\xcd'\xae\xab\xd3\xc2\xc5\u007f\x17\x8e\xe9\xcf\a\x88q\xa2\x89\xf7\x03A \xfe\xf9\x97\xae\xdae\xfd\x98U\xda]E\xc0\xe2\xe1+\xc7\xfe\x99\xb2\xb8\x96\xbe\xe9\xc2C\xfd\x8c\a\xf5\x86#\xccʫU\xb3V\x9b\x16q\xeaω\xa8\u007f\xceQ/\xb8κZQ5=\xfe\xf3\xb9f\xeez\x9b+\xc0\u007fk\xfe\xdb\xeb3Ş\x95\x8eM\x1b\x85\xd7\u007f̛<\xa7pYYNw^?g\x9dwo\xf1\xf5\x00%\x8e\xbb\v/\x83)]Rm\x82O9.i\x18a<64\x87\xfeP5qR\xb1-\x90g\xfeCwp-\x00<||q|\x91\xa1\xea\xe3y\xe3y\xc7@\xf6T\xce\x12vt\x10s\xde\xe3\x01^瑄\x90c7\xb9\x89\xac\x83\x16\x138\x9e\xc5(\xd9h\xdb\xf2\x04\xb0\xb8\xe5\xd0ȱ\xbc\xec~<\x98}\xf7\x9c\xdbY\xca\xc3o\xe5i\xf5\xde\xf3Ļ\x1b\xb6\xb8\xfc+>`1\xb1\xde<@ͳ\x0e\xacʦ\x89\xb7\xbey\xe7Z\xf9˽ug\xba_X\xb0t\xffn\x01:c\x8f\xab\xc8fm˚q\"\xaf\xed\xf9\xdb\x00H\xbe\x02?,\xfb\xf9q+\x8b-\x920\xd1s\xcfZ\xee\xb3U\xdf\x0f\xbc\xb0\xb6\xc1t\xf3\xc4\x1f7\xedrd\xb6.\xd8x\xc0W\x80\xd2\xf1\xa7}\x12\xe7\xe9\x8bo\n&v\xbf\xb3\xf0\xee\xb9,.\n\xa1S\xe7\xdf\xe7\xef\x9ex\x95?}A\x02e~X2\x90\x9e\xe4\x8f\xceI\xca\xca\xc4Fm\xc2\x13C'\xe1\xb1nx\xf7\xef.\xed\x91\x14\f\x93\x1e\x03\x16\xd9:%to}㭸\xd1\xe1\x88\xc5w9\u007f\xdb\xd9\xf0z}\xd4\xf4\xc1Ѧ\xb4N\x9fdNP\x89t\xfb\ue395\x19^߃Z\xf0?\xa7\xaf\xf5\xb4\xa4b\xfc\xd3:\xb6'EU\xf7\xe4\xf0\x96/\x12\x1bL\x9a\x1c\xb9\xdb\x1bSRy\xbe\xfa\xb3\xad\xff\u007f\xf3\xae\x12\xdf$Iͥf\xf5l\u007f\x9a\x9e\xf2\xdb\xd4\\r=R\xc2F\xe7\xc4\xf2\xb0Y\xb2\x98\x06/\xe5\f\x88\xeek\xcf`\x8fn\xd5\x1d\t-/>\x83\xa4#\x0e\xf9$z\xab]\xb5\xb6ęѪ\x92P\xafx\x1e\xb3\x86\x96\x8aV!\x9be\xf2\\l\x8b\\\x15c(O\x9f!dT\x9d\f\xe4#\x99\x13t\xaf9\u007fUm[\x89&\x02&\x8be\x96E&\xc0\x93Q\x1d\xc2\x14up=(\xf5\xe5ϰ\xe3\xec\xdc\u05f9]@\xf4\xf0\xe8Z\x19\xe3|\xeb\x8e+Ҏ\xc6\xf3q\xad=\xacK\xb5+Ǐ\xae.\x9f^M\xdaJ\x91f\xf8\xb5\x03\xc9Q\x10\xad0^\xf2\x1e2\x1e\xf7\xae4^t,\x9f\xf6J\xc6-\xd8\xf3m\xf9A\v\xde\x1a@\x1c2^\xb4MP\xfd\xe9U\xec\x96瘦\xa0lJ\xb2\xcbt̙\x81\xb4\x94]v\xf9b\x03\xe4\x9fٲn\x19 \xa0\x1fW\aHeqJ\x9a\xe4?\xceO?\xfaK\x82$!\xc28\xe0\x8e\x1e\xb1\x820\xe8\"\x9c\xda)&\xb6\xdcW.\t\xa9\x95\xcf\xfd\xb1\xfaz\xc3\x11\xcbX\x96WR\x8eĕ\x9c\xb0=\x84\x81\x88\xe9\xe0\xfb\xad\x87e\xe7\fb5\xaa\x10C\xe6\x87;\r\xd4\\\x8c0ο\">k\xbbe\x92Y\x8d\x0f/\xdcGBJ\xd5(\x00\xee\xb7\xf4\xf41\xae~{\xd4\xfd\ue7a8\a\xfa\x1dc\xbc\xf4(\xb5{\x9c\xff\xbeb\b\xba\xb3\rXB\x8d\xd5(\x13\xb1\x83\xab\xb3U\xe6D\\\x80\xda\xcdmX\x92O\xa4\xe7zL\xe8\xb4\x1e\x93\xf4\xfc\x82Pi{\xc3\xe3\x1bw\x1an\x02\xa9\x93v\x95\xd6\xd4\xc1\u007f0<%|\xd2\xdaq\x90\x9a#\x1eD\xb8Q\xc6te\xa7F=\xa6v\\yp\xbf\xf7ܠ\xb4\x19\xad\x9aM\xad\x85\x01\n}\xc0J\xba\x1cW\xd9*\xa2\xd3Ƹ\x85\xa2\xc9B]\x9aV\xc0wj\b\"\xd8=\xd4~\xcd(\xfe\xf5\rD4\xc7\xe5\x05\xd1Ӽ\xbbM\xd2p\"\xff%\xdem\xb8\x01\xd4NE\xeb\xfc\xe6\x1e\xee\xdd\xf6\x0e\xc6ê\xc1y\x916k\x8aS\xd8\x18\xb9VT\xbaf~ft}\x84\x94\xb8Q\xe2(\xf4ʘ-\x8b\xf3\x948$n\xb04{Z\xd6:a=\xf8\x86\xedQ\x12\x1e\xaf\f\x00\xd8\xc3g\xbf\x9c=r\xf6\x9c\xf0,KS\x10\xd8-O\xa8C\x8a\xa32]\n\x83\xbb_\xf1\xec\xb1\xd8~\xb5\xa3\x8c\x91\xf7\x05\xc4\\\x10\x94^\xf08\x84\xd1\xc7\xc6\x057\xa6\xa7u\x91\xd3\\\x0e\xdc\xdbh\xb5\xba\xc1ͳ\xa4\xa9\xd2\xe7@\xda\xd6`I\xd8$L\x8d\x14\x87\"t\xdb>t\xde\xfd\xde'KEw\xe3\xff\xa9\x97ί\t\xc8[\xbbf\xe7\xc8\x12\x8a\x8a\x05\xa4ك\xf3\xcdYM:\xbe6\x98\x9e\fۦJ?\xb2Jdz\x88rI4\xad\x87D{&\x18t\x1f\xb0\xaee\xd0\xc48\x95Sp\xf5N%&\xcdj'\x8f\x95\x18\xa8v&\xae\x1eX\x9cO\x15\xd0l\x1fw\t0\xef\xf9\x19\x98\x0f\xb6\xf4\x1e\x14\x16\xe7\xee\x176\xf4r\x1e\x18:\x98\xf7\xeaz\x0f\t~\x93\x02\xb5{\xdf\xda\xdf:\ue34fo\xbbw7\xc8[ŷ\xc0\x94\x13x\x11xȇ8\x94\xe9Ͳʜ\x8c\vT{D\x87\xba\xd3\xca\xe9)\xacζ\xcb$\x1d\a\xe70ɐ;\x06\xed0dk{\b\x03OdZy\f\f\bk`|\xe8\x14\xe1\x11\x82\x80\x04x\x9c\x00;\x98\x0e\xe3\x9a\x0f\xba\xee]\x9b\xf7\x88\xc9%\nұ\xadR\x89\xb6\xf7\xb9\xf3\x15\xab\xce\xf0xN\xb4\x12\rH\x1f\x83$\x11Uzɘ\x9f\xa6p\x02\xb9k\xfe\xbd\xd0;\xc1Ue\x1b\xc8Hv\xee\x18G\xfb\x10\xe8)\x04\xaew{ޖE\xc5\xf3\xfd5,N\xa8>\x8b߅I̘\xc1q\x11\xb4~\xbaW\x87?7'\x83ICeէ\x05\xc7zqa%Q\xf1\x02\x9c\x1a\xfc\xbb\xd1\xf1\xfc\x90_s\xe7ź\u007f\x91\xddi:\x9f8Q@\xab\xb6~B:.\x12\xfd\xae\xfb\x1f\xd4\x00\xadϠe\x80J\x8f~\xab\x93\xd8\r\xa5\x18\x82R2\x82Gt7\x8d\xe7\x9buMž!\xb3\x89Ȅ@\x95k\x85\xe0ĥ\xcc\x19X.\xac\xc0M\x1d\xbe\x81窆\x15\xc0rg.e\x9e\x18|D\xc0\x87\xbd\xb9\x84\x13\aϫ\x1e\xbf\xb5|vu\x97\x9bnn)\xeb\xbeà\xe0t3w\xd2)\xcf!\xc7&9ڒo\x9f\b\xd58\xa7\xf7\x1eʿ\xb0\x94\xdf䒲\xd8\r\ae\xb4%\x0e\x92\xe8\x90\vկtp\x8f9\\\x10\x80\x11\xa2\xc6\xe5\xf6\xbf\xc7Z7\xdc\xcet\xa9 \xde\xdet\xd8\xe6\xf0m\x00\xe6\x04'\xbf\xcf\xfe\xc1K\xe9\xa6t\x90\x12-y\xce\n\x0f\x8dW\x1a\xdfg\x86'\xfa\xb1\xa7\x84ިH*\xc7K\xc8B\xeb=}\xec\u007f\xa0\xf0\xb1.Q\xf1\u007f\x85O\x03ʯ\xf5\x88\x16\xf6?\r\a\xfa\xe1\xe7C\xbc\xc1\xdfv\xf8-\xe7萒\xe18\xd0\fO\xe7P\n\xc5\xe2\xdcfE\xfdD\xfd4\xf6\\\x91\x98R\x98\xcei\x86ǁ\xa1\x12:\xe4\xdc\x16|\x9cnsm\x99\x13d\xe8tsM>\xf0\x1e\x86\x0e\x03+0\f^D\xe6g}\x9f\xb3V\x17G\xab\xf7P}\x11\x19Υ\a\xaa\x9dh\xf4z\x8b{\f\xdf\x19\xe0\xd0\x19\xaf\xbd\xd1\xea\xe2uh\xa6\xaec6\xab`\x10\x06\xd53\xdd~\x8d\u05fe\xa9\xe8N\xaf\x9e\xcd,\x18\xb2\x81\a\xbb\xa1\u007f\xd3X\xca\xfd\xb9\xed\x17\x9e/\x1a\xde\xf7\x19|(\t\xf1\xbc\xdb\x10(\x10\xa6yP\b\x13\x9a\\1\xdcJ\x84\x18\x04\x19\xad\xa4K\x86\x1e\xf25M{\xa7@\x01Y6\xccqM\xdb\xe1ߓ_y\xfb,\xb8\x8f\xb8\xfa\xe0\xde3N\xb9\v\xe0\xf6\\\xcd\xdf\x1f\xc8@2vl\xe9\xf6\x17\xe2\xa5\x1e\xc7\xe0iL\xbfjC\xf5}\x9a\x8b\xaa\xd8\u007f\xf5\xcd\xfc\xf3\xf1c<,\x99\x05Ӱ\x1e^2\\b+#;\xf2\xd2\xfb\b\xe0\xbb{Q\xc8\xfcw\x8d\x8d\x19\x19\r\x8d\xef\vP\xd1\xd1\xf9\xa8\xe7i\xceLuS\xe3[\xb4^\x15Y\bihP\xab\x1b\x1b\x9e\xe5#mX!\xea6\xab\xddx\x83,0D\x11щtz\"\x86HRa\x18tL\"\x91\x84Q1\x18۱\x91\x88\x89\x18:\x03\xad\x02\xc4{I\xbf\x9cn\xfc\xf6\xde\x10\xb43\x88\x02\xcbC\x96A\x97\x05\xafj\xbf\xeds.C\x86\xff\x1c\xb6y\xe2\xbbo\x00Gr\x97ݞ|'\xc0\xc8K\xe5\xd4Ќ,V\x17:1cҏ\xeb\x13\xd6I\xf7\xec\xf2\xe5fi##<\xeb\xbb\xd2\xc5\xff*\xf2,\xa1\x8bD8\r\x8d\xec*Dz\xf0\xa7\xd9\x18\xa1^̍,*\x88\x1b\xf7\xd3\xe8'\xe0\xab;\xdf\ta\t.*\x9b\x80\xd8TלLO\x18$0\r/\xf4\xe6y\x97Q\xe3Eج1\x15\xe6\xd4\xcb\xecP.,8\xbaņ\x8b\xf4\x9d\xa6G\x16\x84Ka\xed\xefRu\xc0\x89R\x1e\xba\x15\xeb\xd7K\xa1\xf4\xf9aK7\x93WJ\x9d\x02F\xbfx\xae\x11\xbe\xcf\x18\x10\xb6\x06\xa3\xa6\x05\xc4\xc8\xe0UB\xb1WU\xb44ݟ\xe5\x9a\xd8U\xd2\xd1Rk\"2\xa2rF\x18\xd95\xc4}\xb9Z\xca|^\xd5(3;\xa6\x05\xbeLm\x88\xd0x\x92\x11\xcc<\xcaiͼ\xc10\xaf9M\xc9\xcbS\xc3y\x8d\xc1\xa0\x99\xa7\x9c\xce\xf3M\xed\x98\xcf8M\x9eF\x9e\xcf0t\xa8\xe9s۲_\xae\xac\xbc\xccnk\x83\x8d\x93\xcb~\xd9v\xfb>\x05\xde\xe50O\xa9s\xe6_[P\x01\\w\xa2W\x1c)\xa7\xbb`\xbby\xa6\x99\x0f\xefJ8>\xc9k($\vW\x81\x8a\xae߬\xab\xbd4\x8e\x1d\xc9r\xe8\U0004047c\xfc\x91\x83Kn\xf4@\x02\x90b\xc9S\x15\xa2\xb7)\x8d\xed*\xe4\xf1a\x0f\xcaT\xb9\xe8M\n,\xffۢ\x8c\x03:\n\xe6:Դa\x18\xfc\xf1\x8dW\x10sc\x05\xb2\xc8$N_#\xdeCp\xa5\xfd\r\xb3\x90\x9c\x98\x8d\xe3l\xe8\xed=|S\xad\xd6Lտ\\\x98jbH\xa9\xcf.\x16z\x0e\x14\xd2\r\x01\x1b\xd6v0\berE\x19\x81\xc1P\xaaB\xb1\xac\xc5`\x10J\x15\xf22|\xce\xc2\xcb\xe5\xa8\xd4\xd2\xfd`lR,\f\x8b-\x1a,\x18\xdb\x0f\v\xe3\xb0c\xbdw\xceƛ\f\xe3\xbc/di\xb6hb\u007fm\xf4\xfc\xe8\x92;\xbf\xf6p\xe5~\xccWH\x06#&\x94\x94J\xb8(JS\xb7\xd2e\xd1\x1a8\x8b\x86N\x8e z)(rq\xb2\xef\xc0d7@\xff\xf6\xa7X\xa2c\xbb*]\x94:q\xb4_\x96\x8cS\xef\x97ȩu\xdb\xde\xf6s\v\x86\x92\xa9\x94\xf1\x05\xf9LMfifj\x82\x98\x0e\xa7\x05\xbd\xf5\x8c\xd2`\xd1l\xf5\xa5\v,w\xa1m`l\x1e\"6\n)\x03n\x83\x9fS\x83\x85\xc1\x12\x9c\xe4\xe3S\xe7\xff\xd0$qJ\x89,\xa0\x06dmSa\x9d\x8c\xf2\xc4\x03\xaf\xd5\x1b\f\x91\xedH\xc8\xe5\xe9\x82p\x16\xae\x8e@\xeeO\xafq\x1aF\xc0\xc90\xaf\x06\x965\xb7\xf5\x99א\xce*\xd4\xe3D}e\xd4\xe9\xee\x01\xfa\x85\xcdU{\b\xd9$\xd0\u007f\xaf\xa1Ni\xe8\x9f\xd9#\xde\x19\xdd\x05\xc3.\x17\u007f\xd1\xffbjύ\xf7'\xb8\xf5?\xfe\x85\x97\xb4Z\x13^\xe6:\xb6`\x00N\x14c\xbb\xbe\xcf\xdc}S\x80\x9f\xc5\x1d\xa5$\x8d\xc7W\x03\xecZa\x02\xb6)N\xa5\x0ff\xc7\xe8\x82E*\xff\xa6\x84\x04l\xa3P\xa9\x0fb\xd3\xf2\x83DʀƋ\xb2*Au\xa2\xc2稪\x16\x94)\x12\xd7\x02\x11\x99\xb6\x94\xf1\x97P\xd5Ԉ\ay\xfc\xe5\xf5\x86M\x81\xf8ݘH\xbbd_\t]\xf8^\xc1\x92a\xf2!\"\x1b\x19\xd3'\xd5E\x109\xcb\x19Tl\x8f\\r\x92䚦\xd90\x03\x9c\xbe\v~\x95\xd4\xd6j\xc0\xbf.>\xbdv\xe1\x18\xf6\x8b\xf1\xea7\xd7!.\xb3.\xed\xf2\x0e^\x1aX\xd0\x03ӿ1\xa3}\x83]\xf3\x81\x06\xb8\xaae\x94ިi|\xcb>If%\xbf6\xbf\x0fŵ{TZ^^4\x95A\xcd͡\xd3i1Z-#::2'\aHcWR\x06\x933\xd3\x10G\x9a\xc0-\x9cl\xe5\x88\x1a:DI\xbd\x9a\xacdܩ\xa1\xd8\x0e\xa6\xcf\xe0\x18\x98\xf8\x06\xf2\x80OKН\x10\x11\x9at\xf0\u007f\xa5>\xdc\xe1\x97F\xe8L\xa7\x89r\x18\x8dZ~\xfdM\x9f\x1b\xa2\x95\x00\x16ɶ%\xd4QO\xe1\xd8\xff\x0ė\xee\xd8\xdd\xfe\xd3k\f\xfaM\x84\xbf\x8a\xc6\x13D\xfd\xb1\xc7\xdb\xf9NN,\xbe\xadBɍ\x96\xc2\xd54eX}\xb6\xa4\xdbW\x91щd\xccOӽ\xb9\xaeB[L\xb8\xc4-z+\x16m\xbd\x97\x1bQ\xf6\xa7\x9cEf:\xc7\xfa\x87b\xc5ϸ\x94\xc7Xf\xa0\xbf\xcdQ\x99#\x89\xa0td\x84\xf8\xca\x18\x87\x8c\a\xf2h\xdd%\xf2\ttj\xea\b\xa6@\x18U\x0e҈Î\xf5\xb6\xec\x91\xfb\xa7\x12\xe5{Rs3\xc68\x05\x8d\xe1\x9fĉ\x99$\xf6\xaf\x0f\x10\xe4\x1bE\x8e|/\xc6l\xa6\xc0S\xec \xdf@\xe8\xbcZJ-s\x16\xca\xd3#˽\x05\xdcZ\xdf$\t\xa9\xf6\x84\xf4{I\xa8\xf8vE\x0e\x97e\x155|n\x9f\xf3\a\xb6{Ih|«\x83y\x8f\xba\x9d\xf9t\xcb\xc8Aqq\xde\n\xa3\xa1\x81s\xb4\xaa\x8av\xa4\xb2~/]\x973\x16\xb9T\xee]Yu:I\xf2\xad\xc7X\xfa\xa8\xb9N\x1e\xfa2\xb6\x96\xeeS\xc3]\xfa\xc5\xc0/9W\xc1\xa4\x17M\x88\x8f\x05\xfcx|5\x1c\x91\x1f+-$\bE\xfa\xa0X%\xbcY\x11\a\xdf\xc2I\xd2\xe3\xe3Ʒ]\x02\xdam\xaf\x05_2\xba\x03\xca\xfbm\xb7\x9b\xeb\xe4\xff\xbdQ\b\xef\xafS\xaen=?\xde_\t\x98\x9f6W_S}\x06\x99\xa5o\xe6\xc77Ӹ\xb1\x1b\xad\tĔxmb\x154\xf3\xe2\x1b\xa9<\xf6\x9domıYֽ\xed\x8c\xc0!蓫\x06\xebδofO\xeb\v\xae]\xba\x14\xf7\x80\v˸\xc9\x1a4\xdd\xdeފV\xc0xmÀ\xb7\xfd\x86,l\x05b\a\x11s\x8fk\x10\x05\x9a\xd0D\x9d\x06Q\xe39\xc9\x122%\x91\x92\x8fg>NcfJ\xd8\xc2n@r>DBJ.'x\x11\x88R\xaf\xbd>b\x12Iƽ\xf3G\x93\x88O\xf0\xfa\x1e)\xae Q^\x13\x88\xaf)\x94\xaep\xc2k\x10\xf2\xdbS\xac\x18)\xf4\x8fE\u0083>\xff\x94'\x84g\x90\xd9|t\xb0\xbf\x04)ı\x91\xb0\xa0\xfbouB\xb8\xe6 \x8fyWݒ\xbap\x9a\xbc\xca3`\x01\x98&\x9c}\xf8з&\xa8\x8eT\xdbnx\xe3\xd5\xe2\xde\xec\\W\x01\xa0\x18\xe3\xc3\xef}\xab\x83\xb7\x10k\xda\rO\u007f\u007f\xe4[<\xfeߤ?&\x8f\xac'\x1f.<\xbcÃ\xb7\x8a\f\xde\xf7w\a\x1a~T\x03C\xaf\xb9\xa8\xacw\nO\"AS\xbe\xa2\xb3\xb8\x9c)_\x150\x02\xf1\xe4ޡ\xb2\xaa\xa5=\x96\x00\xff\x1dU6\xb8\x8d\xc0\xe6{\xbe\xf1?\x18<\xa5d\x01쯝\xfagw\x9c\xa9*\xaef\xf6\x90\xea!\xe7\x976\xcd\\P@N\xaa\xda>\xfe\x1e86`\x02\x98X\x1c\xd4uy\tp7\n³\x05މ\xff\xfb\xff\x88\xa7\xc7\xf7\x14e\xf8\xf1\x894\xa0\x12\xb50\xe4\x9d\x14C8\n\xe8[\xb37\xa9'\x83\x8c\xd9\xd3\xc3\xec\xf3aN\xb3\x1c݅\x90\x1b0k\x81\xfa\t\v\xf4N=%\xfc\x0f\x10\xb1\xd8ԑ\x9eXe\x8cc\xf6\xeb\xc7\xe1\xdd\xfa\x85\ndn2Xg\xf6j5\xb3_\xed\x86w\xab\x8dH HuQ\xc4\x13oB\x0e\xcc^0e\xf6\xad0\xa7\xd5\x0f\xefZ\x14\xc3\x11\x94T`\xf6\\dQր\xd8\x051:\x8a\"\bl~Vnr\x19\xb8\xa6\xf5V\xa3\xf5\xbf\xa1M?E\xeb~A\xcd\x129\xdcRL%\x0fQ\xefh\xbd\xedh\xfd/\xfau?\x11\xcfqz$\x85\xd8eQ\xe2\x81\xd9\x1f\x1c\xef\xd95II\xe9\xfe\xa6dG\xda\xe4\xa3\x1dZo\x1bZ\xff_\xda\xf47B7\xad\x87Jq+\xd0z\xfb\xd0\xfa\xed~\xddH\xa5\xd2zc'\xed\xde\a\x0ej\x05\xe9\x993\x9fO\xc7(\x01:\x00l\x17\xeeFf\xac\x11\x03\x94K\x8bQ Pn\n\xd2\xe7\xad\xf2F\xb9\n\x90?o\x95\x8fzRz\x06\xac\xcf\xdc\xe9\x03h\x1e-\xd7]\x06\xd2\xd3g9\b\xe0\x1e\x80\x06\xc4\x032\x96A^B:\x90w\x93t|\xe2I\xf2e\xa0\x8c]\xb5\\_ߔR\x0e\xc2\xd8\x00C\x02\x1cE\x94\x0e\x82\xd3S\xc3k\x14\"\xfd\\\xa4\xc9\x06\xe1\x06\x96_\xc5ǰn\xbb\xd2\x014\x15\xe7t\x00\xf8\xe7\xb3\xf8w\xb5\xca\xe3\xfa\f\b\a\xfb\x94\x15 n,Փ\x90;\x92\uf30b\x01}R\x9e\xbajpִ\x1c\x94\xe2\xe7\xdc\x10\x02\x05\xcfAZ\xf9\x1f\x03\xb0\xf5]\xc2\xcfAc\x90\xed&\x1co\xd3/w\xb0\x92\xd4vѿ\xf3i\x03xi\x8e\xf8\u007f\xb8\xfa{lK\xfa:\xf3\x15\xdaZ)[v[\x14\x82K\xeb\xb8\x01\xf4\x0e8\x81\x98\xf5\xfd\v\xa1\x81\xef\xb6\xe1\xb9\x14g\xee%\xba\x84\x81\x14\xa6\xad\x04 \xe5\xf9[\x8f\x11\x1d\x88\xfc\xccG;-\xfa\xe2\xb3\u007f\xfc'X\xe4\xab.[\x91)K\xaf\xec\u007f\x01{\xbc\xaf\xb8\xe6\xb6\x1bn\xba\xe5\xa5\x1c\xf7\xddqת\\\x1f\xf4\xf9\xde\x03\x0f\xe5y\xed-\x83|:\x05\n\xe9\x15\x99Q\xacT\x89\x04^\t\x95*lV\xe5\x95j\xb5j\u0529\xb7\xc5Q\xbbl\u0560Q\x937\xde9\xe6\a\x8f\xfc\xe4\xb1C\x0e3:\xe2\x825\xeb.jw\xcai'\x82y\xeb\x84#ux<\x83\xbd\xb1\xb8\xa7-+\xcf*.\xd3Br\x8e\xaa,+\xaf,Y\xb5\xeb\x8a\xcb\x00") + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_woff2_bytes() ([]byte, error) { + return _third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_woff2, nil +} + +func third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_woff2() (*asset, error) { + bytes, err := third_party_swagger_ui_fonts_droid_sans_v6_latin_regular_woff2_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/fonts/droid-sans-v6-latin-regular.woff2", size: 11304, mode: os.FileMode(416), modTime: time.Unix(1436317036, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_images_explorer_icons_png = []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01,\x00\x00\x002\b\x06\x00\x00\x00\xe6\xd6\xe6*\x00\x00\x00\tpHYs\x00\x00\v\x13\x00\x00\v\x13\x01\x00\x9a\x9c\x18\x00\x00\nOiCCPPhotoshop ICC profile\x00\x00xڝSgTS\xe9\x16=\xf7\xde\xf4BK\x88\x80\x94KoR\x15\b RB\x8b\x80\x14\x91&*!\t\x10J\x88!\xa1\xd9\x15Q\xc1\x11EE\x04\x1bȠ\x88\x03\x8e\x8e\x80\x8c\x15Q,\f\x8a\n\xd8\a\xe4!\xa2\x8e\x83\xa3\x88\x8a\xca\xfb\xe1{\xa3kּ\xf7\xe6\xcd\xfe\xb5\xd7>\xe7\xac\xf3\x9d\xb3\xcf\a\xc0\b\f\x96H3Q5\x80\f\xa9B\x1e\x11\xe0\x83\xc7\xc4\xc6\xe1\xe4.@\x81\n$p\x00\x10\b\xb3d!s\xfd#\x01\x00\xf8~<<+\"\xc0\a\xbe\x00\x01x\xd3\v\b\x00\xc0M\x9b\xc00\x1c\x87\xff\x0f\xeaB\x99\\\x01\x80\x84\x01\xc0t\x918K\b\x80\x14\x00@z\x8eB\xa6\x00@F\x01\x80\x9d\x98&S\x00\xa0\x04\x00`\xcbcb\xe3\x00P-\x00`'\u007f\xe6\xd3\x00\x80\x9d\xf8\x99{\x01\x00[\x94!\x15\x01\xa0\x91\x00 \x13e\x88D\x00h;\x00\xac\xcfV\x8aE\x00X0\x00\x14fK\xc49\x00\xd8-\x000IWfH\x00\xb0\xb7\x00\xc0\xce\x10\v\xb2\x00\b\f\x000Q\x88\x85)\x00\x04{\x00`\xc8##x\x00\x84\x99\x00\x14F\xf2W<\xf1+\xae\x10\xe7*\x00\x00x\x99\xb2<\xb9$9E\x81[\b-q\aWW.\x1e(\xceI\x17+\x146a\x02a\x9a@.\xc2y\x99\x192\x814\x0f\xe0\xf3\xcc\x00\x00\xa0\x91\x15\x11\xe0\x83\xf3\xfdx\xce\x0e\xae\xce\xce6\x8e\xb6\x0e_-\xea\xbf\x06\xff\"bb\xe3\xfe\xe5ϫp@\x00\x00\xe1t~\xd1\xfe,/\xb3\x1a\x80;\x06\x80m\xfe\xa2%\xee\x04h^\v\xa0u\xf7\x8bf\xb2\x0f@\xb5\x00\xa0\xe9\xdaW\xf3p\xf8~<\xdf5\x00\xb0j>\x01{\x91-\xa8]c\x03\xf6K'\x10Xt\xc0\xe2\xf7\x00\x00\xf2\xbbo\xc1\xd4(\b\x03\x80h\x83\xe1\xcfw\xff\xef?\xfdG\xa0%\x00\x80fI\x92q\x00\x00^D$.Tʳ?\xc7\b\x00\x00D\xa0\x81*\xb0A\x1b\xf4\xc1\x18,\xc0\x06\x1c\xc1\x05\xdc\xc1\v\xfc`6\x84B$\xc4\xc2B\x10B\nd\x80\x1cr`)\xac\x82B(\x86Ͱ\x1d*`/\xd4@\x1d4\xc0Qh\x86\x93p\x0e.\xc2U\xb8\x0e=p\x0f\xfaa\b\x9e\xc1(\xbc\x81\t\x04A\xc8\b\x13a!ڈ\x01b\x8aX#\x8e\b\x17\x99\x85\xf8!\xc1H\x04\x12\x8b$ Ɉ\x14Q\"K\x915H1R\x8aT UH\x1d\xf2=r\x029\x87\\F\xba\x91;\xc8\x002\x82\xfc\x86\xbcG1\x94\x81\xb2Q=\xd4\f\xb5C\xb9\xa87\x1a\x84F\xa2\v\xd0dt1\x9a\x8f\x16\xa0\x9b\xd0r\xb4\x1a=\x8c6\xa1\xe7Ыh\x0fڏ>C\xc70\xc0\xe8\x18\a3\xc4l0.\xc6\xc3B\xb18,\t\x93c˱\"\xac\f\xab\xc6\x1a\xb0V\xac\x03\xbb\x89\xf5cϱw\x04\x12\x81E\xc0\t6\x04wB a\x1eAHXLXN\xd8H\xa8 \x1c$4\x11\xda\t7\t\x03\x84Q\xc2'\"\x93\xa8K\xb4&\xba\x11\xf9\xc4\x18b21\x87XH,#\xd6\x12\x8f\x13/\x10{\x88C\xc47$\x12\x89C2'\xb9\x90\x02I\xb1\xa4T\xd2\x12\xd2F\xd2nR#\xe9,\xa9\x9b4H\x1a#\x93\xc9\xdadk\xb2\a9\x94, +ȅ\xe4\x9d\xe4\xc3\xe43\xe4\x1b\xe4!\xf2[\n\x9db@q\xa4\xf8S\xe2(R\xcajJ\x19\xe5\x10\xe54\xe5\x06e\x982AU\xa3\x9aRݨ\xa1T\x115\x8fZB\xad\xa1\xb6R\xafQ\x87\xa8\x134u\x9a9̓\x16IK\xa5\xad\xa2\x95\xd3\x1ah\x17h\xf7i\xaf\xe8t\xba\x11ݕ\x1eN\x97\xd0W\xd2\xcb\xe9G\xe8\x97\xe8\x03\xf4w\f\r\x86\x15\x83Ljg(\x19\x9b\x18\a\x18g\x19w\x18\xaf\x98L\xa6\x19Ӌ\x19\xc7T071\xeb\x98\xe7\x99\x0f\x99oUX*\xb6*|\x15\x91\xca\n\x95J\x95&\x95\x1b*/T\xa9\xaa\xa6\xaaު\vU\xf3U\xcbT\x8f\xa9^S}\xaeFU3S\xe3\xa9\tԖ\xabU\xaa\x9dP\xebS\x1bSg\xa9;\xa8\x87\xaag\xa8oT?\xa4~Y\xfd\x89\x06Y\xc3L\xc3OC\xa4Q\xa0\xb1_\xe3\xbc\xc6 \vc\x19\xb3x,!k\r\xab\x86u\x815\xc4&\xb1\xcd\xd9|v*\xbb\x98\xfd\x1d\xbb\x8b=\xaa\xa9\xa19C3J3W\xb3R\xf3\x94f?\a\xe3\x98q\xf8\x9ctN\t\xe7(\xa7\x97\xf3~\x8a\xde\x14\xef)\xe2)\x1b\xa64L\xb91e\\k\xaa\x96\x97\x96X\xabH\xabQ\xabG\xeb\xbd6\xae\xed\xa7\x9d\xa6\xbdE\xbbY\xfb\x81\x0eA\xc7J'\\'Gg\x8f\xce\x05\x9d\xe7S\xd9Sݧ\n\xa7\x16M=:\xf5\xae.\xaak\xa5\x1b\xa1\xbbDw\xbfn\xa7\ue61e\xbe^\x80\x9eLo\xa7\xdey\xbd\xe7\xfa\x1c}/\xfdT\xfdm\xfa\xa7\xf5G\fX\x06\xb3\f$\x06\xdb\f\xce\x18<\xc55qo<\x1d/\xc7\xdb\xf1QC]\xc3@C\xa5a\x95a\x97ᄑ\xb9\xd1<\xa3\xd5F\x8dF\x0f\x8ci\xc6\\\xe3$\xe3m\xc6mƣ&\x06&!&KM\xeaM\xee\x9aRM\xb9\xa6)\xa6;L;L\xc7\xcd\xcc͢\xcd֙5\x9b=1\xd72\xe7\x9b\xe7\x9bכ߷`ZxZ,\xb6\xa8\xb6\xb8eI\xb2\xe4Z\xa6Y\uedbcn\x85Z9Y\xa5XUZ]\xb3F\xad\x9d\xad%ֻ\xad\xbb\xa7\x11\xa7\xb9N\x93N\xab\x9e\xd6gð\xf1\xb6ɶ\xa9\xb7\x19\xb0\xe5\xd8\x06ۮ\xb6m\xb6}agb\x17g\xb7Ů\xc3\ue4fd\x93}\xba}\x8d\xfd=\a\r\x87\xd9\x0e\xab\x1dZ\x1d~s\xb4r\x14:V:ޚΜ\xee?}\xc5\xf4\x96\xe9/gX\xcf\x10\xcf\xd83\xe3\xb6\x13\xcb)\xc4i\x9dS\x9b\xd3Gg\x17g\xb9s\x83\U000c82c9K\x82\xcb.\x97>.\x9b\x1b\xc6\xddȽ\xe4Jt\xf5q]\xe1z\xd2\xf5\x9d\x9b\xb3\x9b\xc2\xed\xa8ۯ\xee6\xeei\xee\x87ܟ\xcc4\x9f)\x9eY3s\xd0\xc3\xc8C\xe0Q\xe5\xd1?\v\x9f\x950k߬~OCO\x81g\xb5\xe7#/c/\x91W\xadװ\xb7\xa5w\xaa\xf7a\xef\x17>\xf6>r\x9f\xe3>\xe3<7\xde2\xdeY_\xcc7\xc0\xb7ȷ\xcbO\xc3o\x9e_\x85\xdfC\u007f#\xffd\xffz\xff\xd1\x00\xa7\x80%\x01g\x03\x89\x81A\x81[\x02\xfb\xf8z|!\xbf\x8e?:\xdbe\xf6\xb2\xd9\xedA\x8c\xa0\xb9A\x15A\x8f\x82\xad\x82\xe5\xc1\xad!h\xc8쐭!\xf7\xe7\x98Α\xcei\x0e\x85P~\xe8\xd6\xd0\aa\xe6a\x8b\xc3~\f'\x85\x87\x85W\x86?\x8ep\x88X\x1a\xd11\x975w\xd1\xdcCs\xdfD\xfaD\x96Dޛg1O9\xaf-J5*>\xaa.j<\xda7\xba4\xba?\xc6.fY\xcc\xd5X\x9dXIlK\x1c9.*\xae6nl\xbe\xdf\xfc\xed\xf3\x87\xe2\x9d\xe2\v\xe3{\x17\x98/\xc8]py\xa1\xce\xc2\xf4\x85\xa7\x16\xa9.\x12,:\x96@L\x88N8\x94\xf0A\x10*\xa8\x16\x8c%\xf2\x13w%\x8e\ny\xc2\x1d\xc2g\"/\xd16ш\xd8C\\*\x1eN\xf2H*Mz\x92쑼5y$\xc53\xa5,幄'\xa9\x90\xbcL\rLݛ:\x9e\x16\x9av m2=:\xbd1\x83\x92\x91\x90qB\xaa!M\x93\xb6g\xeag\xe6fvˬe\x85\xb2\xfe\xc5n\x8b\xb7/\x1e\x95\a\xc9k\xb3\x90\xac\x05Y-\n\xb6B\xa6\xe8TZ(\xd7*\a\xb2geWf\xbf͉\xca9\x96\xab\x9e+\xcd\xed̳\xcaې7\x9c\xef\x9f\xff\xed\x12\xc2\x12ᒶ\xa5\x86KW-\x1dX潬j9\xb2\x15\x89\x8a\xae\x14\xdb\x17\x97\x15\u007f\xd8(\xdcx\xe5\x1b\x87oʿ\x99ܔ\xb4\xa9\xabĹd\xcff\xd2f\xe9\xe6\xde-\x9e[\x0e\x96\xaa\x97\xe6\x97\x0en\r\xd9ڴ\r\xdfV\xb4\xed\xf5\xf6E\xdb/\x97\xcd(ۻ\x83\xb6C\xb9\xa3\xbf<\xb8\xbce\xa7\xc9\xce\xcd;?T\xa4T\xf4T\xfaT6\xee\xd2ݵa\xd7\xf8n\xd1\xee\x1b{\xbc\xf64\xec\xd5\xdb[\xbc\xf7\xfd>ɾ\xdbU\x01UM\xd5f\xd5e\xfbI\xfb\xb3\xf7?\xae\x89\xaa\xe9\xf8\x96\xfbm]\xadNmq\xed\xc7\x03\xd2\x03\xfd\a#\x0e\xb6\u05f9\xd4\xd5\x1d\xd2=TR\x8f\xd6+\xebG\x0e\xc7\x1f\xbe\xfe\x9d\xefw-\r6\rU\x8d\x9c\xc6\xe2#pDy\xe4\xe9\xf7\t\xdf\xf7\x1e\r:\xdav\x8c{\xac\xe1\a\xd3\x1fv\x1dg\x1d/jB\x9a\xf2\x9aF\x9bS\x9a\xfb[b[\xbaO\xcc>\xd1\xd6\xea\xdez\xfcG\xdb\x1f\x0f\x9c499\xe2?r\xfd\xe9\xfc\xa7C\xcfd\xcf&\x9e\x17\xfe\xa2\xfeˮ\x17\x16/~\xf8\xd5\xeb\xd7\xceјѡ\x97\U000974ffm|\xa5\xfd\xea\xc0\xeb\x19\xaf\xdb\xc6\xc2\xc6\x1e\xbe\xc9x31^\xf4V\xfb\xed\xc1w\xdcw\x1d\xef\xa3\xdf\x0fO\xe4| \u007f(\xffh\xf9\xb1\xf5SЧ\xfb\x93\x19\x93\x93\xff\x04\x03\x98\xf3\xfcc3-\xdb\x00\x00\x00 cHRM\x00\x00z%\x00\x00\x80\x83\x00\x00\xf9\xff\x00\x00\x80\xe9\x00\x00u0\x00\x00\xea`\x00\x00:\x98\x00\x00\x17o\x92_\xc5F\x00\x00\v\xaeIDATx\xda\xec\x9cmpT\xd5\x19\xc7\u007f\xe7\xeeK\xb2y\x01$\x8a6@\xa5\xb5\"H\xaaA\x89B\x03\xd9\xf0\x12,\x90\x88\xa6R\xb4\xd3\xc1\x8c\xad\xf8\xa5\xadL\xedX\x1c:\x05fDtZ\xa7\xe2\ai\x99\xa9\x03\f3\xca\xe8(B\x80vx\x19\x92@-\x06\x92H\x11\xa6(H\xd0RP\t\x91d\xb3I\xf6\xde{\xfaa\xc9%$\xbb!{w!\xd8y~3w\xd8\xecٽ\xff{\xee\xb9\xe7\xbf\xcf\xf3\xdcsQ\xc4a\xdeRMS\x1aih3\x03\xc3\xdbi\xf9\b\xef\xf9\x8d\xb2\xe3}^kM\"(\xa5b\xbe\u007fxޝd\xb7\xe6\xa4i\xc3\xc80,:\xbd>\x15\xceݲ\xe7\xaa\xeb\xea\xc3\xf3\x80\xce4\xd0\x19\xa0;i'\xac&lI\x99\xae \b\xc9\xd3k\xf6N\u007fA\xa7)eWhX\x02\x8c\xec\xd6d\xa3\xf5:`\xc5\xe4\x0e\xcf\xf1\xe5\xcbUJ\x8d\xa3q֔4\xad\x8c\nb\xe8*Xg+\xbdbԄ\xea\xe3jyj\rK\x1f\x9e\x9b\x86eW\xa0\xf4\x12P\x97\xf7W\xb1\x0e[\xad\xe0\xae\xf1\xc7U\x0fa\x17\x86\xa5\xdf{\xef=>\xfa\xe8#\"\x91HR\x83\xa6\xb5ƶm\x94R\xce6P\xe4\xe6\xe6RPP@~~~\xcc멿]jhh\xa0\xb6\xb6\x96ӧO\x0fX_\xb4\xd6\xcef\x18F\xd2\xe7\xd5\xe7\xf31n\xdc8\xe6Ν\x9b̹\x11b\x1a\x96\xd6L{\xd1.T\xb0\x13H\xef\xfbK\xfau\xdb\xf2,\xdc\xfd;e%k\x1c\x1ah\x9cST\x88VW\xd4\x05\xfd\xba\xc7\xf0.\x1cY\xb9;y]\r\x1c*+D\xd1/]l\x16\xaa\xfcJ\u05fa\xeb֭ӦiRTT\xc4-\xb7ܒԠ\x9d9s\x86\xea\xeajgBx<\x9e\x01\xbb\x80Ο?ϑ#G\xc8\xcb\xcb#\x18\f\xba\x9a\x94UUU\xfa\x95\x86\xb1\x9ck\xb10큛\f\xa6\r\x83\x03\x8aGoܚ\xd2q\xf2z\xbd<\xfe\xf8\xe3bX)\xc0\xdb\xf5b\xdaJ\xfb\x11\xa5x\xab{c\xc1w\x15\xc3\x06E_\xef>\xa2\tw^\x9c\xac\xa8'\x94\xc7\x1eW\xfc\x92.\xdc\xf3\xdbK\xa6\xe5\x86\xc6Y\xc1GЗ\xeb\x06\xee\xbd\x0f\xcfM7\x03\x10\xaaډ\x0e\x87\xbb\xec\xe6\t۶\xc6}>\xab\xb8p\xc4\xf6=I\xe9\xf2\xaf\xb2G\xe8\xd1_\xb2\xef\x01\xdfM\xd1\xd7\xcdU`\xb7;\xba\x18\x8c\xd3\x1f\xce*Twow\xa5{\xf2\xe4I***hkk\xe3\x93O>I\xea\xd0\xd3\xd3ә1c\x06o\xbc\xf1\x06\x93'O&\x10\b\f\xc8\xc5\x13\x89D\xf0\xf9|dddPSSC0\x18t\xb5\x9f\xda\xdaZζ\x8d\x06 \xcdw\xe5ymY\x1a[\x83\xad\xc1P\xd1\xcd\xe3Q\x97\xb5\xf5|\x0f.\xfd\x1dw2X\x9a\xfc\xf6\x97\x991\xe3\x17)\x1d\xa7\xb5k\u05caӤҰf\xbchަ{\x98\x06\xc0C\xf7\xc2\xfd\xb7E\a\xb9\xf6\xc4%ú\xc8\xfd\x1e\xdbZ\xbdt\xa9^\xd83=\xec\xbfY\x15ݦUo\xdd\xec\xb2r\x02\x05\x13\x01h?\xf8\x01\xa6cX\xa0\xe1~\x13\xbdZ/ea\xcf\xf4\xb0ߡ\xff\x87\xa5\xb7Ao]rJ!{B\xf4uK]7Ê\xf6\x17\xe5]\xad\xf5҅ʥpkk+\x96e%\x1d\x11E\"\x11Z[[\xe9\xe8\xe8\xc0\xb6\a.$\xf1\xf9|\f\x192\x84H$B(\x14r\xbd\x9fP(\x04\xaa\u007ff\x1515\x9d\x16XvԄ\f\x15\x8d\x9a\xfdZc(h7\xa3m\x1eC\x91N\xf43\xe1H\xf4\xdf@?L+\x8b\xac\x94\x8f\x93\x90BÚ\xf9\a\x8de\xdao\xbb\xcc(\x9f\xacI\xb3^\x04N$\x1c.ϜI\xbb\xeap\xa7\xabx\xf2Tm\xf0E\xa8:\x91\xb8Y\xcd\x04\xa5\\\xf6\x97'9\\窿]\xa4:}\xeb+5\xad\xab\xabc\xef\u07bd455\x01\x90\x97\x97Gii)\xe9\xe9\xe9\x1c=z\x94\x8d\x1b7\xf6\xfa\xce\xfc\xf9\xf39}\xfa4555\xbd\xda\x16/^Lz\xfa\xe5ٳa\x18\xd7\xecb\xb5,M\x87\xa9\x99w_&\x8b\x1e\x18\xe4\xbc\xff\xca\xdf/\xf0\xd6\a!|\x1eEŔ,~\x1e\xccb\xf5\xae\x166\xee\x0fa(X]\x91÷s\xbc\x94\xaf\xfa\x02\xcf\x00\x8d\x93\x90\"òLk\x14\xa8\xfcX\x8d\x9b\x0e\xc2?>\x8eN\x88\x96\xf6\xd8;0\x14K\x80\x9f%*\xdc\xeei\x8f\xabۯ\x89\x8av\xa5\x8b\xf2\x8f\x02\xf2\x93p\bw\xba\x80\xd7\xeb\xbdf\x03[__\xcf\xe6͛\x99={6\xc5\xc5ń\xc3a֬YÚ5kX\xb4h\x91c4s\xe6\xcc!\x18\f:\xc5f۶9s\xe6\f\x00O?\xfd4Ç\x0fw\xde7M\x13˲\x06\xecb5mX\xf4\xc0 J\xf2\x02\xfc\xfe\x9df\xf6\x1f\xef`\xe4P\x0f\u007f|l(\xe3\x86\xfbX\xbe\xe9k糏M\xca\xe4\x9d\x03mD\xach\xe4կK\xe3b}\xf3Z\x8e\x93\x90\x18\x86\xd6jN\xbc\xc6\xe3g5u'\xa3[\xa7\x19\xcf8Ԃ\xe9/\xe8\xc4sB\x15_\xb7\x9f;X\xf0\xf9\xc3\xd3]\xe4\xa2\xc9\xeb\xea\xa3\x0f\xbbʁ}>_J\xb7\xb8\x83j\x18\xd4\xd4\xd40b\xc4\b\x82\xc1 \xa6i\xe2\xf7\xfb\x99={6MMM\xd4\xd5\xd59\x86\xa5\xb5\xc64Mg\xd3Zw\xbb1\xa1\x89D\"\x8eQ\rd\xfa\t0$\xc3`\xde}\x99l:\xd8\xc6\xfe\xe3\x1d\x00|\xd6d\xb1a_+%y\x01\x06\a\x94\x13q\x9e:g\xf2ؤLL;Z\xeb\xeaJ\x1f\xaf\xa7q\x12\\\xfc\xe8+\xa5\xf3\xe3\xddq}f\xb6rjX?y\xcd\xe6\x8b\v\xb1\xf7ax\xc8\x00\x12-b\xe4'{\xecv\x875 \xba\x98\xaet\xaf\xd9/wGG\aMMM\x8c\x1e=\xda1\"\xa5\x14\xb9\xb9\xb9\x00477\x93\x99\x99\t\xc0\xb6m\xdbضm\x9b\x932Ο?\xdf\xd9ϫ\xaf\xbe\xea\xbc.))\xa1\xb0\xb0p@/\xd6qã\x93\xff?\xe7/\x8f\xf2\xba\xfe\xbes\xb8\xbf[v\xd0Ưf\x0eb\xfd\xde\xc4kH\x12a]dž\x05jhҵ\x05\xdb\xf4\x837\xa1\t\xacah\xb2\xf7ymm\xfb\x137\x0e=4\xe9%1\xb6v\xa1{u&\x82\xdf\xef\xc7\xef\xf7\xc7Lm\x94Rx<\x9e\xcb\"\xa6\xae\b\xac\xebX\xca\xcaʘ:u*Zk,\xcbr\xd6 \x01<\xf3\xcc3\xe4\xe6\xe6:m\xb6mǬ\x99\xa5\"\x8a\xf0\x1aѭ\xcf\xc8ǫz}Ǵ/EN~\x0f\xf8.\x16\xd5O|a\xd2\xd0\xd8\xc9O\v\xb3\xbaեT\x9f\x1a\xa6\x18\xd67°>\x81\xe4Vm{\xb57\xe1ɫ \xee=\xe3\xce\x13\x1f\xa3.N\x02\x1d\xe9\x8co=^\xc3ͭ\xa9\xf8\xf7\xaa\xc3'@]\x9c|\xba\xb3\xaf\x83wuK\xecj\xa4\a\xb1\f+\x10\b\x90\x93\x93ñc\xc7\xf0\xfb\xfdN*\xf7\xd9g\x9f\x010l\xd80ǔ\xba/<\xed2\xb7\xeem\x86a8ib\xbc\x02\u007f*\xfa\xa5\x94r\xcc&\xf6o\x04\x9c\xfc2j)cs}\xec8\x1c\xc6\xf6(\"\x96\xe6\xe6\xc1\xd1\x02\xf9ɯLn\xbf%z,^\x0fl\xdc\x1fb\xe5\x8fo\xa0\xb9\xedR*۷\x86\xbej\xe3$\xa4Ȱ\xb4ֻ\x95\xe2Y\xf7W\x1a\x8d\xe7\x86ҙ\xf8\xd7\xf4n\x8d\x8a\xa9ۼ\xfe\xaf\xfd\xd9E\xe3\xb9\xd3-\x9d\xa3\x12?\xe0\xdd\x10\xa7\xbfg7\xf4K\x97\xceou\xba:\xd9\xd7\xe8\x97[kMII\to\xbe\xf9&[\xb7ne֬Y455QYYINN\x0e\x93&M\xa2\xa1\xa1\xc1\xf9l\xbc\xc8ɶ\xed\xb8mW\x03\xfb\n2_\xb6Xl\xfd0L\xf9\x84\f\x0e~\xdaAͱ\x0e\xa6\x8cN㉢,\xaa\xff\xddNS\xc8v\"(\xa5\x14\xa7\xceE\xa3\xac\xe2\xb1\xe9\x8ei\xf5\xa5\xd1\xd5&\x11\xd6u]\xc32\xf6B\x12\xc5T\xcd\xf3\a\x9fR\xb00\xc1\x15\xe7\xb6ګ\xe3\x84\xe7ic\xf3\x18\xfa\xd4/i?T\xcf\xf9\xd7\xff\x1c\xfb\xfbZ=?\xe1\xe0\xc1\xc4cC\x83\xbdq\xbb\x9b1\x06r\x17B\xeb!8\xb36\xde\x1e\x9eW\x13֠\xf5_\xae\x8b\b+\x9eaM\x9c\x18]Ƕc\xc7\x0ev\xed\xda\x05\xc0\xf8\xf1\xe3)++\xbb\xa6&\x94\xc81[\xfd\xb8\f_\xdby\x81\xff6[\xac\x98w\x83\xf3\xde\x1b\xff\f\xb1a_\xebe\x8bE\xd3.\xa6\x8fo\u05f6Q<6\xddI9-[_7\xe3$\xb80\xac]ϩ\xd0\xf4\x95\xf62\xd0\xcbz6֞\x80\xafZ..\xbe\x8b\x1dS\xb4\x19\x1ec\x9d\x1b\xe1o\xff\xad*trv\xd12P\xbdt\a\xcd\xfd\x11\xfe\xdb\xef\xc0\u007f\xfb\x1d\\xg#V\xf3\xf9\x18\xba\xb6+]\x95\xb7%\xa4\x0f\x95-\x03z\xe9r\xe3\x83\x10\xf8^t\xfbj\x13\x98ͽt\xb1\xf4:\xd7'\xfb*\xfcr\xc7zޭ+j\x9a8q\"\x93&M\x8a\x195\xdd}\xf7ݬZ\xb5ʩOu\xa7\xb4\xb4\xd41\xb6\x81\\\xc6\x10\x8fw\x0f\x84x\xf7@\xec\xac|þV6\xec\xbbTh\xff\xf4\xcb\bs^>{]F\u0082\xab\x1a\x16X\xa8\x15\x06\xfaQ\x05c\xba7n:\xa8\xaf\x14]M\xdb\xf1\xacr\xfd$\xaf\xb6\x8d\x15\xcaЏ\xd2C7\xb4g'\xe9\xf7\x14\xd0\xdeP\x87\xf5\xf5\xd71\xa2+\xa6\x8d\xac\xacv\xff\x04\xb1\xe5Y\x81\xc7\xea\xa5Ks\x15d\x8d\x87\xd6\x0f\xc1\xfc:\x96=LS\xe3\xb7D\xdc\x1aK[[\x1b\xd9\xd9\xd9)\x19\xb8\x96\x96\x16\x02\x81@܅\x9b]QTwC늪\xba\xa7\x81]\xdbe\x91\x8ee\xf5Y\xb3\xeaI8\x1cN\xaa_\xd9\xd9٨\xf0\xf5\xf1\xa8\xdd\x05.\xa4|\x9c\x06\xf2\xe1\xf4\xff7\x9c39\xed%\x9d\x89m\x1f\xe8iZ}P\xbe\xeb9ϻ='C\xa2\x91\xc1\x89\xd2)\x99\x86m\x1c\xa0\x9f\xba\x1aU\xfe\x9dm{\x92\xd6\xd5\r\xa5\x99\x18\xaaߺ(]\xae\xbe_\xe9Zw\xfd\xfa\xf5\xda0\f\x1e|\xf0A\xb2\xb2\xb2\x92\x1a\xb4\xd6\xd6V6o\xde\xcc\xe0\xc1\x83),,\x1c\xd0\x14\xa6\xa5\xa5\x85\xca\xcaJƌ\x19CQQ\x91\xab\x99Y]]\xad\xffT?\x86\va\xbb\xdfk\xa5\xae\x06\xa6\r\x99~ţ7\xefH\xe98ٶ͂\x05\vĵRiX\x00\xc5+\xb5׃^\x12+=\xecF\xbdR\xba|\xe7b\xefɞi\x88\xdbT\xe6\xd3\x1f\x16{\x95a/\x89\x95\x1evs\xaaz\x94.\x1f\xb5\xad:e\xba\xba\xfe!/\x1ekI\xcc\xf4\xb0[\u007f\x81ruז\xa4t\x01\xbdv\xedZ\x1a\x1b\x1bSR\xef\xe9\xba\xf3\x97\x8a\xff\x06%\x19233)((\xe8z\xf0\xd9\xf5\u007f/SUUEmmmR\xcf$^\x8f\xe7\xf5\xd6[o\xa5\xa2\xa2\"\x99s#\xc43\xac.\xa6\xae\xd4\x19J\xdbS\f\xa5\x826z\xacB\x9fU\xa8z\xad\xf4\xf66\xbf\xe7\xd4\xfb\xbf\x8e]7I\xb6\xf6\xd28gj\x06ښ\x02*\xa8a,p\x16\xa8\xd7Jm\xf7ft\x9c\x1a\xf9\xd6\xfbWEW\x1f\x9a\x93\x01\xc6\x14 \b\x8c\x05}\x16\xad\xeaAm'\x94vJ\xfd\u0b64u\x05AH\x81a\xc9\xc4\x13\x04ᛂ!\xa7@\x10\x041,A\x10\x041,A\x10İ\x04A\x10İ\x04A\x10İ\x04A\x10\xc3\x12\x04A\x10\xc3\x12\x04A\x10\xc3\x12\x04A\fK\x10\x04A\fK\x10\x04A\fK\x10\x041,A\x10\x041,A\x10\x041,A\x10İ\x04A\x10İ\x04A\x10İ\x04A\x10\xc3\x12\x04A\x10\xc3\x12\x04A\x10\xc3\x12\x04A\fK\x10\x04A\fK\x10\x04A\fK\x10\x041,A\x10\x041,A\x10\x041,A\x10İ\x04A\x10İ\x04A\x10\xc3\x12\x04A\x10\xc3\x12\x04AH-\xff\x1b\x00\x87\xb8\x03\x91\xabyŦ\x00\x00\x00\x00IEND\xaeB`\x82") + +func third_party_swagger_ui_images_explorer_icons_png_bytes() ([]byte, error) { + return _third_party_swagger_ui_images_explorer_icons_png, nil +} + +func third_party_swagger_ui_images_explorer_icons_png() (*asset, error) { + bytes, err := third_party_swagger_ui_images_explorer_icons_png_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/images/explorer_icons.png", size: 5763, mode: os.FileMode(416), modTime: time.Unix(1436317036, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_images_logo_small_png = []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x1e\x00\x00\x00\x1e\b\x06\x00\x00\x00;0\xae\xa2\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq\xc9e<\x00\x00\x02\xa4IDATx\xda\xc4W=l\xd3@\x14~q\x97\x8a.fh\xc5\xe8\x11A\aW\x02)\x12C͂@]2t(S\x9c\xad\x88\xa1\xc9\u009avCbH;\xc0\xc2P3\xb1 \x91HT\xea\x16g\"R\x87zi\xd5\r\x8fUY\\\tPG\xde\xe7\xdeU\x96}g'\xb5\x03/:\x9du~\xb9\xef\xfd|\xef\xe5\x85\xe8?I\xed6_Z\xef\x92ɛ\x9d8\x8a\xbe\xecP0\x13`\x06sxk\xf2\xc2ni\xd4\x00\xfe\x89W\x9f\r\tK\x01\v\xc0\xae\x00\x9cF<^;:\x03j\x05\xa0=\xde\xda%R\x19\xf1\xea0\xb87\x11\xb0\xc8\xe10\x95\xc72\x02Ϸ\x93\as*\xad\a\x0e}\xaf\x10\x14\xe2𝗧#\x1a\xcb\x03C\x13^{\x06\x15\xd4\x13|ɆZ\xbc\x18\xaa\xbeeݳiaޤ\xf0<\xa0\xdfW\x91\xf2\xe6EӢ%^\x17QH?#%\xa7p\xb8\xc2a\x8f\xd2\x1ewU\xda\xee\xf3\x1e\xbd\xdb<\xa6mw\x18\x1b\xa0\x93\xa7\xb6\x1b\xeb|h\xff\xa0\xc7\xf7\x1bJ\xfb%Y\x8d\x94\xb7ʒY\xab\xb7c/\xbd\xc3N\xec\xb1N\x86\x81G~\xe0\t#\x9a:\xb5\xadt\x8e\x9by\t\x02\xe0\xc1xW\x1bf\b\xc2\xfb\xbeߊ\x9f\xef̛:5\x93\x9dl$\x81\x1d\xfaw\xb2j$\xea\xd6\xd2\x11\xe66\xb2\xa0\xf7\x18bK\x8f\xb5\x8cY\xab\xc7)\xa1\xd3pt\xc3\xee\xa4Q\x12\x00\xbb|\x87\xb4\xe0\xf9\xa1\xa5\r\xa23'\x1a\x06\xdcr\xd3o\xc1\xd0'\xcb\x1b\xf1E\x1f\xbf\xbd\xa2g\x8f6\xe9\xcd\xc6W\xfasuIѯ\xf3\x98\xbdx\u007f0ޣ\xce\xfagf\xff.\x1d\x9d\rb#\xc1j\xe8\xd7\xf8s\x12\xfa\x19d\xa3\x88PҳEQ\x9f\x10\xb9\x83LR\ag \x1e\x96\xacy<_\xa8\xeb\xf9\xba\x81\xe45\x0e\x87k\xf3uc?f4\xcai\x12A$`\xa8\xfb\xf6\xae\xae\n\xae\x1b\bw\x12_w\xc9\xd1Y?\x93\xdb\"\x01(\u009bSz\x81\x91\xfa\x11\xcfH^ݖ\x90Q\x12د\xe2Ƃ2\x92\xd2O\x02\xef\xe9\xb4\xe05J\x03\xb9Ϋk\xe8\xa0\x12$\xf1t\x9c\xc5|v\x03,F\x14O9\xc30\xa9\x00\x0e\xa2-\x15\x00\x83\v\x00E\x89\xe9\x86\x02\xd5\xcf\"n=F?\x9dQ\xab\f\xd8\xc1\x95L\x1d\v\xaf;3\x02\x05K[\xdaчǓ@t\xb2\xaa\xa7\x90\x17\xec\xd88w\xe6b\xf0\x01\x83#\xdc\xf5\x8a<}ɠ\x87ӌ\xb7\x18#\xf6K\xe4\x1c\xbd\xa1\xa5\xfa\x971\xc9@o\x8aqek\n\x03B1\xd2zU\xfc\x85\x01(\"\xb0*\xf2\x9f\xe6\x80/<\x1c\xe4\xb5`)\u007f\x05\x18\x00\x9a \xff\xd6\t\xfa6\xef\x00\x00\x00\x00IEND\xaeB`\x82") + +func third_party_swagger_ui_images_logo_small_png_bytes() ([]byte, error) { + return _third_party_swagger_ui_images_logo_small_png, nil +} + +func third_party_swagger_ui_images_logo_small_png() (*asset, error) { + bytes, err := third_party_swagger_ui_images_logo_small_png_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/images/logo_small.png", size: 770, mode: os.FileMode(416), modTime: time.Unix(1436317036, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_images_pet_store_api_png = []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\b\x06\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\b\b\b\b|\bd\x88\x00\x00\x00\tpHYs\x00\x00\v\x12\x00\x00\v\x12\x01\xd2\xdd~\xfc\x00\x00\x00\x1ctEXtSoftware\x00Adobe Fireworks CS5q\xb5\xe36\x00\x00\x02\xb2IDATH\x89\xad\x95Ih\x14A\x14@_Uwg\xa6b\x96\x89b\x16\xf0`ԈN\xc6\xfd\"\b\x19\x0f\xde\x04\x83\x97\x80\v\xb8&FQ0(Q\xf1,.\xd1Q\b*\".\aQ\xf0\"\xe8A\x04\x97\xe8Q\xc4\x05\xb2\b\x031\xb8`\x12\x93\xc9b\x92\xae\xccd\xba\xbc\x98[\x06\xa7\x9d\xfck\xfd\xffޯ\xcf/J\x004\x9f8\xe5\xc4\xe3\xf1\x13\xae\xeb\x1e\xf6\x1d\xaf\u07fc\xe1\xe6\xcd[u\xbd\xbd\xfd\x9f\xa5\xd6n}8\xbc|\xd6\xe0\x00њ\x1a\xaa\xc3a\xb4v\x0fH\xcf3\x15\xa1Ph\xd6\xe0\xd3\x11\n\x85\xf0\xe0\x90a\x8b\xc4\xd2M\xe0h\x90C\xe0X0\x05\xa2p\x92\x82\x15+}\xc13\xde\xc0\xd9\u0604\b\x95\xe3ş\xa1\x9f=\xc1P\x02c`\xc64\x94\xfa\x13d|\a\xf6\xeamX\xd5[\xc1M\xc0x\x1f\x04%\"\xcf\xf1G\xe7\x1fkj\x85kQ\r1\xd2]m\xd8\x1b\xf6 \x17\xac\xfd?\x81\x10\x99'\xebD\x9bp\xa2M\xbe\xc1\xd3L)\xa5\xf8\x99H\f\xfa\x06\xfc+\x86\x12\t\xa4\x94}R)\xd5\xda\xd1\xd9ūWm\xb3\x06ok{M{G'J\xa9Vq\xecx\xb3\xd3\xd3\xd3s'\x10\f\xecX\x11\x89PTT\x94\x13|ttt\xfaӿ\xbfd\xc9\xc2]\x02\xe0\xd8\xf1f\xa7\xbb\xbb\xfb\xb4\xeb\xea#\x9e\xe7\xcd\xcdE \xa5L(\x15l\xad\xac\xac<\x13\xbbԒ\xfa\x03\xb1\xb9\xfa\x1cZ7\xefV\x00\x00\x00\x00IEND\xaeB`\x82") + +func third_party_swagger_ui_images_pet_store_api_png_bytes() ([]byte, error) { + return _third_party_swagger_ui_images_pet_store_api_png, nil +} + +func third_party_swagger_ui_images_pet_store_api_png() (*asset, error) { + bytes, err := third_party_swagger_ui_images_pet_store_api_png_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/images/pet_store_api.png", size: 824, mode: os.FileMode(416), modTime: time.Unix(1436317036, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_images_throbber_gif = []byte("GIF89a\x80\x00\x10\x00\x84\x00\x00|~|\xcc\xce̤\xa2\xa4\xec\xea씒\x94\xb4\xb6\xb4\xf4\xf6\U0010c28c\xe4\xe2䄆\x84\xdc\xdaܬ\xaa\xac\xf4\xf2\xf4\x9c\x9e\x9c\xbc\xbe\xbc\xfc\xfe\xfc\x84\x82\x84\xd4\xd2Ԥ\xa6\xa4\xec\xee씖\x94\xbc\xba\xbc\xfc\xfa\xfc\x8c\x8e\x8c\xe4\xe6\xe4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\xff\vNETSCAPE2.0\x03\x01\x00\x00\x00!\xf9\x04\t\x04\x00\x0f\x00,\x00\x00\x00\x00\x80\x00\x10\x00\x00\x05\xfe\xe0#\x8edi\x9eh\xaa\xael\xeb\xbep,\xcftm\xdfx\xae\xef|\xef\xff4\xcbd0IY\x06\x03FʀT\xa2\x18HC\n:\xb0\xa0\x84Dcs\xb9}F\xa7H\xab\xc9\xe2 $(\x111iR\xb8$\x04\n\xf5\b\xb18\x1c\x16\x98\x93B\x90\xb8\x14\x9c$\x16\x11f\x04\x0er\"dfh\x87\x0flnp\x8ctvxz|~\x80#\x82\x84\x86&\x11\x00\x10\x9f\a\n&\x15\xa0\xa0\ry$\x06\v\xa0\x9f\x12\x98\x0f\x03\r\x9f\x9f\x05&\n\x17\xb4\x10\x01\x9d\xba\xa2\xa4\xa6\x00\xa8%\xab\xad\x10\xaf%\xb2\xba\xb6%\xb8\xba\xbc%\x12\xb4\x9f\x15&\a\x00\xd8\x10\x10\x11\xc9\t\xba\t\x03%\x11\xc1\x10\a&\x0e\xad\xd8\x02&\xd2\xc6\xd5%\xd7\xd9\xdb\xdd\xdf\xe1$\xe3\xbe\xe7\xe9\x00\xeb\xd1\xe4\xef$\xae\x19\xe3Fb@\x82t\xe0\xc4\x19\x03`\xaeD\xa9O\xa0\xfa\x91\x90\xf0\xcfڴy\x05\x0fb\x03\x90\xf0\xde\u0086$\x1ej\xe3g\"\x80\xb1\v\xa3\x94J\x14\b6\x8c\x04\x03\x8a\x10\x91\x91\xc0\xd0\xc0\x183\x12\xb8\x8cA#a2\x1bJ\x13+i\xb5\x1c\xf1\xd2\xd41X4m\u07ba\xa0sL\x05\x02\x10\b\x04`\xc4\xe6\xc0\x9b8&$%X\x80`\x8c\x82\x06v\n\xd8\v\x14\x80P\x05F\x16\x9eF\x9dj\xa2\xea\xd5Hu\xb6v-a\xe1kر\x99\xca& p\xf6\xc4\x11\fE\xae\f\xc0\xc0\x80\x91\b\x06\x18\x92LI,\x05Ő*\x82\x01k!l\xf8\x01b\xc5O\x18\xa7x\\\x19\x88\xe7ϠC\x8b\x1eM\xba\xb4\xe9ӨS\xbb\b\x01\x00!\xf9\x04\t\x04\x00\x16\x00,\x00\x00\x00\x00\x80\x00\x10\x00\x85\x04\x02\x04\x84\x82\x84\xc4\xc6\xc4LJL\xa4\xa2\xa4\xe4\xe6\xe4\x1c\x1e\x1c\x94\x92\x94\xdc\xda\xdc\xf4\xf6\xf4\xb4\xb6\xb4\f\x0e\fdfd424\x8c\x8a\x8c\xcc\xce\xcc\xec\xee윚\x9c\f\n\f\xac\xaa\xac$&$\xe4\xe2\xe4\xfc\xfe\xfc\xbc\xbe\xbctvt\x04\x06\x04\x84\x86\x84\xcc\xca\xccTVT\xa4\xa6\xa4\xec\xea\xec$\"$\x94\x96\x94\xdc\xde\xdc\xfc\xfa\xfc\xbc\xba\xbc\x14\x12\x14464\x8c\x8e\x8c\xd4\xd2\xd4\xf4\xf2\xf4\x9c\x9e\x9c|~|\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\xfe@\x8bpH,\x1a\x8fȤr\xc9l:\x9fШtJ\xadZ\x8f\x9b\x88\x86\x80\xb8z\xbf\xe0+\n\xf3YHH\x8d\x8e(\xccn\xbb\x89\x15\x0e`N\xcf`PE\x91ǃG&\xf6}G({\tI\x83\x1ekG\"\x10\x1e\x10Iz|I\u007f\x91H\x87\x85\x95{\x89B\"&\x12t\x9f$\nD\x10\n&[\b\x9aC\x15\x13\x0e\x0e\x13\x05G\b\x04\x1a&\n\x81C\"'\a\x1a\a\x17\xa9\x9b\x17\xbb '\xbf\x16\xa4\xa6\\ū\xad\xaf\xb1\xb3\xb5\xb7\x9b\xba\xbc\xbeC\x05\x03\x9f\xd9\fD#\x01*\xde\x11\xb0D\t\x13\xde\xdf\x1d\xd1\x1e\x11\xdfߢE\b&\xec\x01\x0fF'\xf2\x0e]E\xdd\xec\xe1E\xe4\xe6\x01\xd0\x15Q'\xcf\x1d\x11x\xf2\xe8\tA@!ۧ\x06D\x1c\xa8\x98\x18 \xc0\x89\x81\x1a\xe4i\xf0P\xe4DFo\x01\x1c\x18\xb9`n\"\x01#\x1d\xd8}\x1baD\"E\x8b\x185r$\xe2\xf1\xdeȒ*N.l\xe8p\x0e\xd3\xc4!\x12\xcdi\xb8Hă\x86\x92\x1b;\x02T!R\x1fHo:\x89t\x00\xb9\xb2\xa5ʡ\x18\x91\xce\x1crbiSnOsZ\xc3\xd6\x13\xc0\xb6!\n\xa8\xf6#\x82b귀\xd1\n\xa4\x00hp\b<\x80\n\x89<\x00h\"\x1f\x91\xb4\xfc\xc4\ri\xfbT \x11\xb9t\x8dܥ\x98\x97\x93'\x87\v\xea\x92rpJ\x19+\r\x13*\x18\x11\x81 E+\x05[q=\xd8u`D1\x11#\x0e\x048\xf0\xa0\xd8\xe4\xcaF\x96a֜\xa7\xf3\xe7ЛF\xf32\rGN6;\xd1\xf4\x14@QL\b\x8a\x02\x94\x04!\xbft\x84\x11\"$\xc2\x1dA\xf70\xbc\xb8\x85\xe3ɍ`gnĹk\f\x06\xcc,h\x90\x82\xfb\x9b\xf3\xe8\xab\b\xd8\x15\xc1o\xfa\xf7\xf0\xe3˟O\xbf\xbe}7A\x00\x00!\xf9\x04\t\x04\x000\x00,\x00\x00\x00\x00\x80\x00\x10\x00\x85\x04\x02\x04\x84\x82\x84\xc4\xc2\xc4DBD\xa4\xa2\xa4\xe4\xe2\xe4$&$dbd\x94\x92\x94\xb4\xb2\xb4\xf4\xf2\xf4\x14\x16\x14\xd4\xd2\xd4TRT\f\n\f\x8c\x8a\x8c\xac\xaa\xac\xec\xea\xec424|~|\x9c\x9a\x9c\xbc\xba\xbc\xfc\xfa\xfc\xdc\xda\xdc\xcc\xca\xccLNLljl\x1c\x1e\x1c\\Z\\<:<\x04\x06\x04\x84\x86\x84DFD\xa4\xa6\xa4\xe4\xe6\xe4dfd\x94\x96\x94\xb4\xb6\xb4\xf4\xf6\xf4\x1c\x1a\x1c\xd4\xd6\xd4\f\x0e\f\x8c\x8e\x8c\xac\xae\xac\xec\xee\xec464\x9c\x9e\x9c\xbc\xbe\xbc\xfc\xfe\xfc\xdc\xde\xdc\xcc\xce\xcc\\^\\\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\xfe@\x98pH,\x1a\x8fȤr\xc9l:\x9fШtJ\xadZ\xa7\x16\x01\xe2\x93\x10]\xbf\xe00\x93\xd2\xe8\xcc*\xc6H\xe6\x94r,\x06/\xb1|~\xbd\x80\x00x\x80\xe7\xe0\x15\xa2$yy\x0e\x14F&\x11\x11\nI\n\x87&\x8a\x87\x16H\x16,\x11,I\x16\x87\x89H\x86\x88\x8e\x11\x8dH\x8b\x11\x90D,\a\x81x\x0e\x1f\x90\n\x1a\xa7y\v\x11D\x05\x10\x0f\x0f+\x05G\x17\x04\x1f*%\x99D\x16\f[\b/\xa3\xc0/[$\f\xc6C,\t*\x1f\x04\x17\xccB\xb3\xb5\xb7\xb9\xbb\xbd\xbfC\xc1\xc3\xc5D\x02\x06\xae\x00-10\x18\x80\xe4\x00$C&\x10\x01\x13\x13\x01!\xdc0\x11\x14\xf3\xf3\tF\x17*\xfa\x01d\x18a\x00\xf0\xc1\x05#\x15\xe2ţ\xd0\xc7\x1d\xb8\xbc=\"\n\xd0'YO\xdf\x02\x89\x06\x12pP\x82\x11\"\xac\x91\x82\ao\x94@\x1f\x1d\f6H\x85\t\x15@\x03\x9d\x83\x14Vh\xe1\x85\x18f\xa8\xe1\x86r\x02\x04\x01\x00!\xf9\x04\t\x04\x00\x1c\x00,\x00\x00\x00\x00\x80\x00\x10\x00\x85\x04\x02\x04\x84\x82\x84\xc4\xc6\xc4DBD\xa4\xa2\xa4\xe4\xe6\xe4dbd424\x14\x12\x14\xd4\xd6Դ\xb2\xb4\x94\x92\x94TVT\xf4\xf6\xf4trt\f\n\f\x8c\x8a\x8c\xcc\xce\xccLJL\xac\xaa\xac\xec\xee\xec\x1c\x1a\x1c\xdc\xdeܼ\xba\xbcljl<><\x9c\x9e\x9c\\^\\\xfc\xfe\xfc\x04\x06\x04\x84\x86\x84\xcc\xca\xccDFD\xa4\xa6\xa4\xec\xea\xec464\x14\x16\x14\xdc\xdaܴ\xb6\xb4\x94\x96\x94\\Z\\\xfc\xfa\xfc|~|\f\x0e\f\x8c\x8e\x8c\xd4\xd2\xd4LNL\xac\xae\xac\xf4\xf2\xf4\x1c\x1e\x1c\xe4\xe2伾\xbclnl\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\xfe@\x8epH,\x1a\x8fȤr\xc9l:\x9fШtJ\xadZ\xafI\x98\x89\x05\xb9P\xb0\xe0\xf0\x15fI!E,I\xc6\xf11~\x06\x95գ\xc2H\x88\xef\xf8\xa5l\xf3\x00<\x02\"E\x111\x00\x85\x00+\x010C\x1f}\x86\x85+mE0\"\"\rY\x94fG)\x14\"_H)\x94\x8aH\r\xa1\x97\x95\xa6\x99D2 \x8e\x1d\x18\x81B2\x03\x8e\x8f\x13B\"\xb3\xb4\x85\x03\x9eB%\x04\x1e,&\xa2D)-\v\x1e\v3\xa9C)3\xc8'-\xccB\x14\n,\x1e\x04%\xd3\x1c2\x13\x10\x10/2G\xbf\xc1\xc3F\xc6\xc8\xca\xd3,+\xb4$\nC\x1a\b\xba\x00\x12B\n$\xf4\x00\xefC\"\x1a*\x01\x00\xc2+R\x82\x05@\x80\x11\x8c\xb48\x18\x00B\t#\x17\x02\x06\xd4P\xa0H\x83\t\x01\x01\x86 v\xeb\x9fāD\n2L8\x04\xc6\x06z\x1e29зB\b;}\x0f \fi!\x11\xa0\xcc\"32\xaaPA\xc0H\xfe\x88\x83\x00/\x18\x81\xb0\x13`\x80\x16EDx`\xe8\x01\xd6̚\r\x8d\xe4,ʓ\x88I\x94\x99h\xb0\x14\xb2\xa0\x91\xae\x1579\xd0\xdc\x190쐈F\xab\x16\t\x01Uh\x11\xa2\x19\x8f&\xf5\xa0\xb3i\x91\xb1\a\xcd\nA\x1b `O\"\x10\xbc\x16\xe2'D\x1e={\x1c\xf0\xe9#̡\x80\x86\xb8 \x87\x14\x8cK\x92H\x84\xb8,\x1e\x161Q\x93\xa2$\xb6F7\x16q\f\xd9\xc8d\xb2\x95}\xb12\xd4\xc1@\xc5X\x19\xbe\xda\xe2 b\xb5\xae\x01NS\x94\xd0\xf0MA\xaff\x11\xd2]ؖ\xe2\u0082\x00\v\"l\xabv-۶n\x10\x87\xf7\"p$\t|\x96ȇI\x16%\x14h\x84\b\x10\x80p\x00\r\xed\x15!\xc0\x00$\xc8A\x82\v\xa9\xe5\xa1\xe1\x86X\x13T\x13\x9d\t\xf9q(\xe2\x88$\x96h\xe2\x89(\xa6\xb8D\x10\x00!\xf9\x04\t\x04\x000\x00,\x00\x00\x00\x00\x80\x00\x10\x00\x85\x04\x02\x04\x84\x82\x84\xc4\xc2\xc4DBD\xa4\xa2\xa4\xe4\xe2\xe4$\"$\x94\x92\x94dbd\xb4\xb2\xb4\xf4\xf2\xf4\x14\x16\x14\xd4\xd2\xd4424\f\n\f\x8c\x8a\x8c\\^\\\xac\xaa\xac\xec\xea윚\x9c|~|\xbc\xba\xbc\xfc\xfa\xfc\xdc\xda\xdc<:<\xcc\xca\xccLJLljl\x1c\x1e\x1c\x04\x06\x04\x84\x86\x84DFD\xa4\xa6\xa4\xe4\xe6\xe4$&$\x94\x96\x94dfd\xb4\xb6\xb4\xf4\xf6\xf4\x1c\x1a\x1c\xd4\xd6\xd4464\f\x0e\f\x8c\x8e\x8c\xac\xae\xac\xec\xee윞\x9c\xbc\xbe\xbc\xfc\xfe\xfc\xdc\xde\xdc<><\xcc\xce\xcc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\xfe@\x98pH,\x1a\x8fȤr\xc9l:\x9fШtJ\xadZ\xaf\xd6P\"p\x10\x98\xb0\xe0\xf0U\x12K^\x02\x83A\xe0R\xb4D0\v\x87\x8aCR\x88\xefwKK\xd2B\n4\x1d\x00\x06\x13}D\x04\v\x00\x89\x00\v\x04D\x13\x0e\x8a\x8a\r(F\n\x12\x12\x16Hz|I\x16\x97vH&\x9fI\x96\x12_H\xa5\x99F\x16/\a\x1e#\f\xaaC\x17\x1c\x91*\x0f\xa70\x17\x88\x91\x8bl0!\xbc\xbd\x1d\x1b\xa00\x16\f\xae\a/\xb2C\xac\xae\xb0\xcdB-\t+\x1e\x04\x17\xd20\x05\x11\x0f\x0f,\x05G\x17\x04\x1e+%\xc6\xce\xc9\x1e\xcb\xda\f\x14\x01\xef\x0f\xbfC\x1b\xbd\x00\"\xf3\x14\xf6\x89\x14B\x13\xfb\x89\x1ad\x98\xb5\xe2ݻ\x19F\xdc\xc1\v \xcfH\x85\x85\x14&\x84(b\"\x02\xbcw \xd0\xc1\x900\xc1`\x80\x04F.\x14\\\x88\xb0\b\b\x83\xef*\x10i\xd1`\x1fH!\x03\x00\xca\x10R\x0f\xe0\x82\x970^\\\xa4@\xa1\x91\xfeI\x94\x01T\x16y\xc0\xf3]\x00\x06E$x\xf0\xe8AB\x11\x06K\x17>0\xa2\xb3hO# \x06]\xd9\xd2^\x89!2\x00\x0e\xa0\tp\x11·F\xaf\x9a\xd4*\x94\bы\x1e\x90\x12Q\xba\xb3\xe9Ӌ\xf0\xa6\x16A\x1b\x00\x9eO\"3\xf0\xae\x98'\x04\x81=\x03\x94\x84\xe8\xdb\xd7\x0f\xc6?\x80\x02\t\xe2-\tX0a!% J,\xa2 \xabьEB\xb8\xc0\x8bS2\xcf\x00\x94\x9dU8\xc0e\x864\x06'l\x050\xb6\xcbނ\xc4\xc1\x00\xd6q6CY\x05m\x16V\xb7\xd6F\xcd\x1a6m\xdc\x1ex\x88\x10\xae\xcd\x05\x17\xde\x12\x14\"b\xa1\xf7\xba\xdfG<\x85\x98^\xe4ŇD\x1cFp\x87\xe1B\xd8\"\x17\x8e\x02\xf5\x9add\x0f&M\x12\xb6w\x8a\xaf@\x9b\x10\x05!$h$\x82\xdfT\x12\xf7\xf6=!\xc1eD\\@\x01\x06\x18P\x90\x983\x11\xa4\x10\x87\x03'@0\x1e\x1e\x14V\x18F\x01 \x94\xf3B.\x16\rv\xe8\xe1\x87 \x86(\xe2\x88$&\x11\x04\x00!\xf9\x04\t\x04\x00\x19\x00,\x00\x00\x00\x00\x80\x00\x10\x00\x85\x04\x02\x04\x84\x82\x84\xc4\xc6\xc4DFD\xa4\xa2\xa4$\"$\xe4\xe6䔒\x94\x14\x12\x14dfd\xb4\xb6\xb4\xf4\xf6\xf4\xdc\xda\xdc424\f\n\f\x8c\x8a\x8c\xcc\xce̬\xaa\xac\xec\xee윚\x9ctvtTVT,.,\x1c\x1e\x1c\xbc\xbe\xbc\xfc\xfe\xfc<><\x04\x06\x04\x84\x86\x84\xcc\xca\xccLJL\xa4\xa6\xa4$&$\xec\xea씖\x94\x14\x16\x14trt\xbc\xba\xbc\xfc\xfa\xfc\xe4\xe2\xe4464\f\x0e\f\x8c\x8e\x8c\xd4\xd2\u052c\xae\xac\xf4\xf2\xf4\x9c\x9e\x9c|~|\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\xfe\xc0\x8cpH,\x1a\x8fȤr\xc9l:\x9fШtJ\xadZ\xaf\xd8%\x83\x10\x98t\xb2\xe0p\xd5`HvH\x1a\x8f*T\\\x1c,\bG\xaa\xa0Z\x88\xefM\x93$$I\x9aB!-G&,(\x00\x00(,&D-\x01)\x87\x87\x17_B-$\x1b\x90\x90\x15'Ez|~\x80\x82H\v\xa0I-\x80vH\xa6!\x8bF&\x18\a\x1c\"+\xacD\x12\n*\x1c\x04\f\xb4B,#\x98#\x11D,\b\x98\x87\x1a\x9b\x19%\xc5\xc6\x00\x0e\a\xb4\xae\xb0\xb2\xbcB\xb6\xb8\xba\xd5\x19'\x11\x0f\x0f,\xc9E[\x1c*\n\xa1D&+\xb0\a\x18\xda+/\x01\xf0\x0f\fF%\xf1\xf1\x13eD\r\xcd(D\x1e\xcd\x00 p!$A\xc0C\x03\xf4exwo^\xbd{/\xf2\xb5\x89\x10\x0fއsBBL\x80\aO\x81\x11\x06*8\x06\x80`\xe4\x03Gx%\x8c_\x0f\xb8\x050`b2i\x9a\x1f(R0b\xf9\xb6p\xbe\x81\v\x8fq\x81\xb8q\xe4D.\xccHg\x817w\t\"\xc0\x9bC\xaf\xc0|\f\x05\"H\xa5\x8a\x8fJr\x00G\x1e88\x1f\x82a\x80'\x0f\a<É6\x1e\x84\xc2X|\x00\x11\x01\xdf)I\xec!\x81{xXa\x02\x06\xea\x85GB\x03\x13pP\x82\x11\"\x1f\xacpB\n\x1e\xd4Q\x02\x84\x11\x96h\xe2\x13&X`Kp'\xb6\xe8\xe2\x8b0\xc6(c\x8cA\x00\x00!\xf9\x04\t\x04\x00\x1c\x00,\x00\x00\x00\x00\x80\x00\x10\x00\x85\x04\x02\x04\x84\x82\x84\xc4\xc6\xc4DBD\xa4\xa2\xa4\xe4\xe6\xe4dbd424\x14\x12\x14\xd4\xd6Դ\xb2\xb4\x94\x92\x94TVT\xf4\xf6\xf4trt\f\n\f\x8c\x8a\x8c\xcc\xce\xccLJL\xac\xaa\xac\xec\xee\xec\x1c\x1a\x1c\xdc\xdeܼ\xba\xbcljl<><\x9c\x9e\x9c\\^\\\xfc\xfe\xfc\x04\x06\x04\x84\x86\x84\xcc\xca\xccDFD\xa4\xa6\xa4\xec\xea\xec464\x14\x16\x14\xdc\xdaܴ\xb6\xb4\x94\x96\x94\\Z\\\xfc\xfa\xfc|~|\f\x0e\f\x8c\x8e\x8c\xd4\xd2\xd4LNL\xac\xae\xac\xf4\xf2\xf4\x1c\x1e\x1c\xe4\xe2伾\xbclnl\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\xfe@\x8epH,\x1a\x8fȤr\xc9l:\x9fШtJ\xadZ\xaf\xd8,\x16fbA.\x14\xadx\x1c\x85YRH\x11K\x92q|\x8c\x9fAe\xf5\xa80\x12\xe4a\x8a\"\n#S\"\"0I\r\x81\x83H0\x81\rI\x89\"hF2\x1b\x0f\x00\x0f\x01\"E\x111\x00\x9b\x00+\x01\x87\x1c\x1f\x93\x9c\x9b+oE\x8d\x8b\x88\x81\x8fE)3\v\x1e'-\xadC\x14\n,\x1e\x04%\xb5B2\x13\x10\x10/2G%\x04\x1e,&\xa0z-\xb1\v3\xbd2 \xa4\x1d\x18\x97\xbe\x03\xa4\xa5\x13B\"\xd9ڛ\x03~B\xc6\xc8\xcaF)\xcd\x1eϽ\x1c-*\x01\xf0\x10%F\x17\xf1\xf1\x1a\x05E\r\x13\xf1\xf0!\xcb8\x88\xd0\x00\x0f\x9e\x02#%X\x14\f\x10\xa1\b\x8b\x15\xdaH\x1c\x14\xa2\x01\x018\x00\x12\x84( q\x11\x80\xc4!\x03\x17N$\x92pa\xc3\"!\n»`\x04\x82\x8a\x97\x01\x02\xb4(\"\xc2\xc3B\x0f׆\xb4\xb8'\xcf\xc8\xfe\f\u007f/\t\x10\x81\xb1ᢇG\x0e:\xae\x10\xf2\xb0\xe3\x03\b:y\x06\x80Z\xe4\xe7ˠFBHeYĥ?\x994=\x00\xc5Yd'L\x15T\x89\u0603\x17O\xe8\x10\xa2F\x1f\xd1P*d\xc1(p+Қ-\x98v\xc8ژ*\xdc\x12\x89\xf0\x95\x05\xbd\"&x\xe6C\xa5\x95-\xc0\"\x054|\x1d9$\xe1דC \xdc\xdd\xf4\x91\xa2Ep\x199l\xecؙC\xe4\xc9\bY\\FwaA\x80\x05\x11\xda\xddʵ\xab\xdd/\b\x1e&\x10sUBC0\x05\xe3\xf4Dpv\xa1W\x89i\x9c:\x18\xd0\xe7+\x03^n\x02\x91\x83\x1b\x903E\xef\xdf\xc1\x85\xa4\x18\xbe\xae\xf8\x11@\x05\xb2\x13\x01\x0f\xa3\x9d\x10\x18\x05\x041J\xaf\xea\b\x1fGG,0\xe8@I\x05\xf3!\x1f4q\xf24N\xc0\xe6R\x02\x18\x81\x9e\b\xed\x19\xf1\x9eyy$\x88J\t\x05\x12!\x02\x04 \x1c@C\x80E\b0\x00\tt\x90\xe0\x02f\n\x15v\xe8\xe1\x14\xb7\xe0f\x82x\x1f\x96h\xe2\x89(\xa6\xa8b\x13A\x00\x00!\xf9\x04\t\x04\x000\x00,\x00\x00\x00\x00\x80\x00\x10\x00\x85\x04\x02\x04\x84\x82\x84\xc4\xc2\xc4DBD\xa4\xa2\xa4\xe4\xe2\xe4$\"$\x94\x92\x94dbd\xb4\xb2\xb4\xf4\xf2\xf4\x14\x16\x14\xd4\xd2\xd4424\f\n\f\x8c\x8a\x8c\\^\\\xac\xaa\xac\xec\xea윚\x9c|~|\xbc\xba\xbc\xfc\xfa\xfc\xdc\xda\xdc<:<\xcc\xca\xccLJLljl\x1c\x1e\x1c\x04\x06\x04\x84\x86\x84DFD\xa4\xa6\xa4\xe4\xe6\xe4$&$\x94\x96\x94dfd\xb4\xb6\xb4\xf4\xf6\xf4\x1c\x1a\x1c\xd4\xd6\xd4464\f\x0e\f\x8c\x8e\x8c\xac\xae\xac\xec\xee윞\x9c\xbc\xbe\xbc\xfc\xfe\xfc\xdc\xde\xdc<><\xcc\xce\xcc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\xfe@\x98pH,\x1a\x8fȤr\xc9l:\x9fШtJ\xadZ\xafجv\x19J\x04\x0e\x02\xd3vLvJb\xc9K`0\b\\\x8a\x96\bf\xe1PqH\x8a\xa9\xa5%i%-\x12\x12yH&\x81\x83G\n\x81bH\x89\x12\x16H{}H\x02\x1a\x1d\x00\x06\x13~D\x04\v\x00\x9e\x00\v\x04D\x13\x0e\x9f\x9f\r(F\x8d\x8fF\x16/\a\x1e#\f\xacD-\t+\x1e\x04\x17\xb4C\x05\x11\x0f\x0f,\x05G\x17\x04\x1e+%\x87C\x16\f\xb0\a/\xbcB\xae\xb0\xb2\xd1\x17\x1c\xa6*\x0f\x8b0\x17\x9d\xa6\xa0o0!\xdf\xe0\x1d\x1b\x87\xcc\xce\xd0F\f\x14\x01\xef\x0f\xe2D\x15\xf0\xf0\x13!E&\x11\xf0\xef \xca0$Lx\xf7.\x81\x91\v+\b\x06\x98\xd1N\xa1\xbc\"\x1b\xc0\x01\x101\x8f\x82DO\x14\x84L\xb8\xe8\xa9A\x86!\b\x152,\x02\x82\xe0\xbb\nF\x1ePX\x19 \x00\x83\"\x12<(\xf4 \xa1\b\x03\x99\xf6\x1e\x18y\xd1o\xa5\xfe(\x92&\x03\xa0\x1cҢ\xc1E\x83B\x06p\x94!$\"\xc7\x05Ha\xf0\\\xe9\xd3\b\b{'S\x9a\xf4\xf0\x92H̞4m\xf6\x83\xa7\xb3H\xbdw\xf0~\x12\xb9\xaap\xa8\x90\xa2\x17K\f\x91\xc1q@S\x8e\xa0\xa2\x9emIA\xed\x90\x19cW\xcc\x1bR\x02+\xbe\"\nض\xfcW$\x84\x8b\xb1QA\xae\x18;\x92\b`\x96\x82\x8b \x90h \x95\x10\x8b\x173\xc2\xd8\xc8ѣdʭ*\x1c\xf82#\x1a\f[\xb8t\xb9\xf6\xf5\xc0C\x84ap.\xb8\x00\x96@\x13\x11\v3\x9cUpmA5\xebh\fNd\vpț\xc4\x05\x9e\xc9qij,\xb8\x87\x03Ï\x00\nổ\x84\x10\n\\\vQ\x10BP\x12\xf2\x12\xb8\x19\xe1\xe3\b\xd2\xf7\xeeD^|\xf0\xc4a\x04|\x17\xe5@\xb9\x18e\t\x1c\xaa\xf5\x81\x88WƀRH0X\x11\x17P\x80\x01\x06\x14x\xb6L\x04)\xd0\xe1\xc0\t\x10\xc0G\xe0\x85\x18FQ\x00\b\xc7\x0e\xbc\xa0^\x86 \x86(\xe2\x88$\x96(D\x10\x00!\xf9\x04\t\x04\x00\x19\x00,\x00\x00\x00\x00\x80\x00\x10\x00\x85\x04\x02\x04\x84\x82\x84\xc4\xc6\xc4DFD\xa4\xa2\xa4$\"$\xe4\xe6䔒\x94\x14\x12\x14dfd\xb4\xb6\xb4\xf4\xf6\xf4\xdc\xda\xdc424\f\n\f\x8c\x8a\x8c\xcc\xce̬\xaa\xac\xec\xee윚\x9ctvtTVT,.,\x1c\x1e\x1c\xbc\xbe\xbc\xfc\xfe\xfc<><\x04\x06\x04\x84\x86\x84\xcc\xca\xccLJL\xa4\xa6\xa4$&$\xec\xea씖\x94\x14\x16\x14trt\xbc\xba\xbc\xfc\xfa\xfc\xe4\xe2\xe4464\f\x0e\f\x8c\x8e\x8c\xd4\xd2\u052c\xae\xac\xf4\xf2\xf4\x9c\x9e\x9c|~|\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\xfe\xc0\x8cpH,\x1a\x8fȤr\xc9l:\x9fШtJ\xadZ\xafج\x16\xcb \x04&\x9d\xadx\xfc4\x18\x92\x1d\x92ƣ\n\x15\x17\a\v\u0091*\xa8\x16F\x93$$I\x9aB!-I\v\x80\x82H-\x80x\x87\x80&Hz|~\x85G&,(\x00\x00(,\x8dC-\x01)\x97\x97\x17aB-$\x1b\xa0\xa0\x15'D&\x18\a\x1c\"+\x9bD\x12\n*\x1c\x04\f\xb3C'\x11\x0f\x0f,\xabF]\x1c*\n\x86\xac+\xaf\a\x18\xbbB\xad\xaf\xb1\xce\x19\xb5\xb7\xb9\xce,#\xa8#\x11D,\b\xa8\x97\x1a\xc2%\xe0\xe1\x00\x0e\a\x8a\x19+/\x01\xee\x0f\fF%\xef\xef\x13gD\v\x11\xef\xee\x1f\xc8B!&\xb8s\xa7`\x98\x8a\x81\x01 \x18iW/\u07bcz/\xee\x15ip\x0e\x05\x11\x0f\xe7\x00 p!$A\xc6K\x1e\xf0e\xf80\xd0]\t#\x0f^\xa8\f\x10`E\x91\x10\x1c\x10rpCdĔF0\xf0SI\xc0\b\xfeI~\x01N\x16I\xb9\xb2%\x91\x10\x1fE~\xcaHB\x88\xa5\x8f \\\n\xf9\x001(ʒ\x1c\xa4\x0e\x81\xb9sf\x91\x15@_\xc3\xf6\ue4fe\x877\xa2\x8f\"\x00\v,\xd2w\x8a.\x8a\x13\x85S\xe1\xa8Bƀ\x04\xf2r\x9b\x11\x02\x90`\xc1\x00\x0f\x88D\x8a\n\x16\xa4@\xc7\x05\x1c\xfcS\xe0\x85\x18>\xc1\xc0\x04\x01\r\x88 @\x86 \x86(\xe2\x88$\x8e\x11\x04\x00!\xf9\x04\t\x04\x00-\x00,\x00\x00\x00\x00\x80\x00\x10\x00\x85\x04\x02\x04\x84\x82\x84\xc4\xc2\xc4DBD\xa4\xa2\xa4\xe4\xe2\xe4$\"$dbd\x94\x92\x94\xf4\xf2\xf4TRT\xd4\xd2Դ\xb6\xb4\x1c\x1e\x1c424\f\n\f\x8c\x8a\x8cLJL\xec\xea윚\x9c\xfc\xfa\xfc\xdc\xdaܬ\xaa\xac|~|\\Z\\\xbc\xbe\xbc<:<\x04\x06\x04\x84\x86\x84\xcc\xce\xccDFD\xa4\xa6\xa4\xe4\xe6\xe4$&$dfd\x94\x96\x94\xf4\xf6\xf4\xd4\xd6Լ\xba\xbc464\f\x0e\f\x8c\x8e\x8cLNL\xec\xee윞\x9c\xfc\xfe\xfc\xdc\xde\xdc\\^\\\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\xfe\xc0\x96pH,\x1a\x8fȤr\xc9l:\x9fШtJ\xadZ\xafجv\xcb\xedz\xb7\x05I\xd2\xf4\xd2(X\x94#e%Y%)\x12I\"I\x8aϑ\x898)\x99\x97\xa4\xd5lnHprtvG\t\x13\x06\x00\x1b\x11\x02E \a\x0f\x00\x94\x00\x1e\x15E\x14\x19\b\x1c#\v\u007fD+\f)\x1c\x04\x15\xa0C\x05\x16\x10\x10\x16 G\x15\x04\x1c)\fwD\x14\v\x9c\b\x19\xa8B\x9a\x9c\x9e\xbd-\xa2\xa4\xa6ª\xac\xaeE$\x10(\x95\x00\r\x98\xbe\x1c\x93\xcf\x00\a\x82B\v\x17\x01\xdc\x10\xd2D&\xdd\xdd\x13\xafD$\x16\xdd\xdc\x1f\xb6B\x12\x13\xdc\xdc\fF\x15)\xf1\x01\x1dF\xdb\xe3\xdfF\xe2\xf1喥\xbb\xb0\xae]\x85\x10\xd6\x00\x88\x18\xe2BCB\x00!2\x10\xf9\x10\x8f\x9b\t#\x10\br\v\xb0\xa0\x88\x04\x0e\xf78\x88!\xb2\x00$?#\x19\xd4\x11$`\x84\xa2\xba\x00\x17\x8bd$\x18\x80\xa3G\x93\xdcD\x12a\xf0Ё\xe6\xa0\x05լm`9\xe4\xc38\x8b\x18+r\xe8H\xe4\xa3J\x9d$_^\x80\xe0o\\7\xa2\x13\x8f\xc2L\xaan\xe9ͧ#\x85\xf0L\xe8`d\x87\xa0φ\x12\xe9\xf02\x05\xb8!\f\x8e\x06$\x92\xc0\xe8Fv\x90X\xbc\x9cW\xa4\xde\xcb|E\xd8\xd2tk$.@sC\xeaZ\xc5;\xa4\xc2\"k/R\x9dxh@\xe2\x10\n&\x10\x04@\xd0A\x98(\b\xa5N\x19A\xc6\xc1B\x01#\x14*\xb0`\xc5 \xec\xe5\x0e\xbaL\bì\x99\xb3g\x06\xa0\x8d\x8d^U\xfa4\xdd\x00\xce*5\x00܂\x045\xa1\a\\\xb7\x80\x03\"\x1bj\t \x12\b\x13\x92\x00D!<\xd6\xf7 a\xe3g\x10t癠K\xc7~\xbdȊ\x11\r(y\xb0\x9c\xea\xc0\x86g\x03J|\x99O\xdfI\x05ą18P1bz\xfd\xff\x00\x06(\xe0\x80\x04\x16h\xa0\x80A\x00\x00!\xf9\x04\t\x04\x00\x16\x00,\x00\x00\x00\x00\x80\x00\x10\x00\x85\x04\x02\x04\x84\x82\x84\xcc\xce̤\xa2\xa4DBD\xec\xea촲\xb4\x94\x92\x94dfd\x14\x16\x14\xdc\xde\xdc\xf4\xf6\xf4\f\n\f\x8c\x8a\x8c\xac\xaa\xac\\Z\\\xbc\xba\xbc\xdc\xda\xdc\xf4\xf2\xf4\x9c\x9e\x9c|~|\xe4\xe6\xe4\xfc\xfe\xfc\x04\x06\x04\x84\x86\x84\xd4\xd2Ԥ\xa6\xa4LJL\xec\xee촶\xb4\x94\x96\x94ljl\x1c\x1a\x1c\xe4\xe2\xe4\xfc\xfa\xfc\f\x0e\f\x8c\x8e\x8c\xac\xae\xac\\^\\\xbc\xbe\xbc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\xfe@\x8bpH,\x1a\x8fȤr\xc9l:\x9fШtJ\xadZ\xafجv\xcb\xedz\xbfH\x91B\x92\x14q\n\x9cr\xa1@F.\xd6\xed\xa3d\xbdH\xce\v\xa2\xf09\x1d\x86'\xdflvtG\x05\x18\f\x00\x17&!F\"'\a\x18\x1e\x19yE\x1c\x06$\x18\x03\x11\x93D!\x0e\r\r%\x8bF\x11\x03\x18$\x1dqC\"\x19\x8f\a'\x9b\xaa\x8e\x90\x92F\x95\x97\x99\xb0B\x9d\x9f\xa1G\xa4\xa6\xa8E\x05\x1f\x17\x00\xc6\x00\x04\xa2C\x19\x14\x01\xcd\r\x11F\x10\xce\xce\x13\x15E\v\x0e\xce\xcd\x1a\xa9\x16\x05\x13\xcd\xcd\x06\xa3$\xe2\x01\x02F\xcc\xd4\xd0\xd2\xd4\x14\xd6\xd8\xda\x14\xdc\xde\xe0\xe7\xe4D\x06 \xc7\xc6#$E4\x88k\x06\xc1H\x03z\xcd\x02d\x10\x86\xe1\x1c\x86\x02E2\xbc\v\xd0\xc0ȉm\xf4\x06\x18\x11\xb8-@\xc1\"\a\xe9\x05P\xc8\xd0!D\"\x12\xcfUT\x85\xa1߱\a\xa94L\xfcH\xe4`DžD\na|\x18\xb1#ƅ\x95D\xa6%\xa4\xa01\xe0L\x83\x03I\xe6İ\xf3\xe42\x9f@-\x88h\xe0\xd2\u0603:C\x04t$\x11\xadH\x87w\xf1\x88H\x90\x99\xb0[\x91\n\x13:\xe6#\x12\x81D\xc7tE\xb4\x8a\xe4j䫸\xb0C\xc6R\v`\x96\bZ\xb5EJ$py!\xaaT\b\a\x02\x1c\x10\x90˂-L\x9a\x8c\xec\xc2\xe0@\x99\xaa\b\x13>\x19\xe0CD\x84\x80V\x10\x1a\x8b@\xac\x98q-K\x90\x1bO\xae\xcc\b\xb3f\xceB\n (v\x8c\x80\x02F\x05*\xc0.\"\"\xb7\x84\xc6B$T\b\x84Dx\x01\xacG\xce\xe0\xe9\xa3[M\x85\xdfŇ{\x13;\x1c9\xa7\x00\x87.<\xb8\r\xa6\xbb\xf7+\v\"\xec\xfeN\xbe\xbc\xf9\xf3\xe8ӫ_\xdf=\b\x00;") + +func third_party_swagger_ui_images_throbber_gif_bytes() ([]byte, error) { + return _third_party_swagger_ui_images_throbber_gif, nil +} + +func third_party_swagger_ui_images_throbber_gif() (*asset, error) { + bytes, err := third_party_swagger_ui_images_throbber_gif_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/images/throbber.gif", size: 9257, mode: os.FileMode(416), modTime: time.Unix(1436317036, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_images_wordnik_api_png = []byte("\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\b\x06\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\b\b\b\b|\bd\x88\x00\x00\x00\tpHYs\x00\x00\v\x12\x00\x00\v\x12\x01\xd2\xdd~\xfc\x00\x00\x00\x1ctEXtSoftware\x00Adobe Fireworks CS5q\xb5\xe36\x00\x00\x03NIDATH\x89\xa5\x95Mh\\U\x14\xc7\u007fg&\x8d\x19'3\xa3\xb4\x04\x85Lg\x92n\x94,\xac\x1b\xed\xa2\xad\xa8\x11E\x8cZZ\x17\xd6D\x90\xba(EGĕ\xb6\xd8\tU\xb0 h\xa5\xf8Aj\xc1\xa6_\v+\xa5\xadH@\x02~!-\n\xe2\xc2n\xecL҉؈U'\x93\xe9K\x9c{\xdfq\x91\x99\xc9}\xf3\x01\x03\xfe\xe1\xf0\xee\xfd\xbfs\xfe\xe7\u070fw\x9e\xd0!\x96\x8e\xf6o\x03\xc6\x00\x8bp\xb8繹\xaf:\x89\xeb\xea4\x01\xca\xf3\xc0\xa3ձ\at\x94 ԉ\xd3ґ\xfe\xb5\xf8\f\xe3C\xd5F\x96\x8e\xf4'\x1a\xfdn|\xd6/\x8d\\`\x05\xde\xd1d\x17˜\x01<\x94\xb7#{\n?\x00hEv\x03ݎ\xeb-\xc0\v\xc0\x9b\x00\xde\a\xc9\a\x81q\xe6)y\x1f'\x1f\x8f\xec*Tj\x8e\xf5\x8cޡd\x940\xe7\x80\a\x1c\xa13@\x12\xb8\xa7\xcd\xe2.\x02\xbf\x03O:ܗXF\"/\x15\x96\x82+\x10\xbaղ\xb1A`{\x1b\xe1\x1a\xeem\xc1m\x10Y\xdd\xfa\xfa \x92)\xfc\x8d\x95q\xac\xf0\xff\x8cl$S\xb8є\x00\x00\x13\xfa\x10\xcb\x15,\x04\xccT\xad\xdd|\xd5.˿k\x8e\xbb\x92\x81C6\xb7\xffS\t\xff\x16_\xae\x13\n\"|\n\\\x06\xb6*\xdc\a +W\xf4k\xe0NUv\xac\x9e$a\xfff/p\x93\xea\x93\xf2[\xa9\b\x86\x17\x81\x83\xab\xf2:\x1a\xddw\xf5d\xdd\xe7\x8d\xf5\x93\x00\xd1}W\xc7\x1cn'\xc8qG\xebUB\x1c\x8e\xbe6\xbb\b \xe5lj30\x82\xf0\x140\xe0$\xff6\xba\u007fv\x8b[M9\x9bN\x00D\xb33\xc5\x00?\x9e\xfa\x06\xd8\xecP\xd7P\xce\x02S]\xf8L(\xdc!+%\xbb\x98\xa3\x01\x8d\xc25\xa8\r\xfa\n\xdc\x06\xec\x06\x1e\n\xa9\x95(VЦ\x83\x95\xe1Ž\xe9\xbeV\x82.\x16\xf7\xa6\xfb02\xecƪ\x05\xb5\x82Z\xe9\ta\xf9\x04˯\x18\x01\xe3\\7\x9fuXN\x97\xb3\xe9\xb6\xfd\xca{'\x1d\xc2rrŷ\x1aW\xd7\xe1G,\xef\n\xc0\xe2+\xe9\x1e\xc2lQ%\x03<\x16\\?\xa7\x88\xeahl|\xd6w\xe9\xd2\xfeT\x88\xb2L\"\xecl\xc8\xfb\xb9\b\a)\xf2]\xefG3~\xe0J\x952\xe9\x18a\xf2\xc0چ\xa0S\xf4\xcah\xec@\xde\a(\x1dH\v\u007fq\x02x\xba\xc1\xef:>\x83\xb1C3\v5\xa2\xa9\xfb\x952\x03\xdf\x03\x9bZ\xec\xc8\x05\xe9b\a\x80\x1aN\x13\xec?5\xfc\x14{/\u007f\xb7K\x04\x12,\xec\x19؊0\r\x84[\x04\x03LU\x9f\x0f\xb7y\xaf(O\xc4\xdfϟoJ\xb0\xb0k \xc4\x1a\xa6\x80\xe16\xc1\x9d\xe2g,\x9b\xe2\x13y\x0f\xdcV\x11\x92^,\x83N- \xfc\tĀ\x9bZ\xd4\n\xc22PBY\xe7\xecE\x1f\x90\x00\x91\xbbִE5\x14\x9f\x1d\\\x0f\xf8\x89c\xb9\xfa\xd7Y\x1c\x1bL!\xe4܂P\x86\x12\x93\xb9_\x1c\x9f[\x11\xba\x13\xc7r\xf3\xae^S\x82v(>\xb3\xe1\v\xe0\x91\xea\xf4R\xe2ĕV?\x9b&t\xf4\xd3\aP˴\xd6\xdb\x00ӝ\xc6\xfd\a\xed\xe8X\x9d\xcd\v]\xf3\x00\x00\x00\x00IEND\xaeB`\x82") + +func third_party_swagger_ui_images_wordnik_api_png_bytes() ([]byte, error) { + return _third_party_swagger_ui_images_wordnik_api_png, nil +} + +func third_party_swagger_ui_images_wordnik_api_png() (*asset, error) { + bytes, err := third_party_swagger_ui_images_wordnik_api_png_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/images/wordnik_api.png", size: 980, mode: os.FileMode(416), modTime: time.Unix(1436317036, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_index_html = []byte(` + + + Swagger UI + + + + + + + + + + + + + + + + + + + + + + + + +

+ +
 
+
+ + +`) + +func third_party_swagger_ui_index_html_bytes() ([]byte, error) { + return _third_party_swagger_ui_index_html, nil +} + +func third_party_swagger_ui_index_html() (*asset, error) { + bytes, err := third_party_swagger_ui_index_html_bytes() + if err != nil { + return nil, err + } + + info := bindata_file_info{name: "third_party/swagger-ui/index.html", size: 3561, mode: os.FileMode(416), modTime: time.Unix(1458347707, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _third_party_swagger_ui_lib_backbone_min_js = []byte(`// Backbone.js 1.1.2 + +(function(t,e){if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,s){t.Backbone=e(t,s,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore");e(t,exports,i)}else{t.Backbone=e(t,{},t._,t.jQuery||t.Zepto||t.ender||t.$)}})(this,function(t,e,i,r){var s=t.Backbone;var n=[];var a=n.push;var o=n.slice;var h=n.splice;e.VERSION="1.1.2";e.$=r;e.noConflict=function(){t.Backbone=s;return this};e.emulateHTTP=false;e.emulateJSON=false;var u=e.Events={on:function(t,e,i){if(!c(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,r){if(!c(this,"once",t,[e,r])||!e)return this;var s=this;var n=i.once(function(){s.off(t,n);e.apply(this,arguments)});n._callback=e;return this.on(t,n,r)},off:function(t,e,r){var s,n,a,o,h,u,l,f;if(!this._events||!c(this,"off",t,[e,r]))return this;if(!t&&!e&&!r){this._events=void 0;return this}o=t?[t]:i.keys(this._events);for(h=0,u=o.length;h").attr(t);this.setElement(r,false)}else{this.setElement(i.result(this,"el"),false)}}});e.sync=function(t,r,s){var n=T[t];i.defaults(s||(s={}),{emulateHTTP:e.emulateHTTP,emulateJSON:e.emulateJSON});var a={type:n,dataType:"json"};if(!s.url){a.url=i.result(r,"url")||M()}if(s.data==null&&r&&(t==="create"||t==="update"||t==="patch")){a.contentType="application/json";a.data=JSON.stringify(s.attrs||r.toJSON(s))}if(s.emulateJSON){a.contentType="application/x-www-form-urlencoded";a.data=a.data?{model:a.data}:{}}if(s.emulateHTTP&&(n==="PUT"||n==="DELETE"||n==="PATCH")){a.type="POST";if(s.emulateJSON)a.data._method=n;var o=s.beforeSend;s.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",n);if(o)return o.apply(this,arguments)}}if(a.type!=="GET"&&!s.emulateJSON){a.processData=false}if(a.type==="PATCH"&&k){a.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var h=s.xhr=e.ajax(i.extend(a,s));r.trigger("request",r,h,s);return h};var k=typeof window!=="undefined"&&!!window.ActiveXObject&&!(window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent);var T={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};e.ajax=function(){return e.$.ajax.apply(e.$,arguments)};var $=e.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var S=/\((.*?)\)/g;var H=/(\(\?)?:\w+/g;var A=/\*\w+/g;var I=/[\-{}\[\]+?.,\\\^$|#\s]/g;i.extend($.prototype,u,{initialize:function(){},route:function(t,r,s){if(!i.isRegExp(t))t=this._routeToRegExp(t);if(i.isFunction(r)){s=r;r=""}if(!s)s=this[r];var n=this;e.history.route(t,function(i){var a=n._extractParameters(t,i);n.execute(s,a);n.trigger.apply(n,["route:"+r].concat(a));n.trigger("route",r,a);e.history.trigger("route",n,r,a)});return this},execute:function(t,e){if(t)t.apply(this,e)},navigate:function(t,i){e.history.navigate(t,i);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=i.result(this,"routes");var t,e=i.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(I,"\\$&").replace(S,"(?:$1)?").replace(H,function(t,e){return e?t:"([^/?]+)"}).replace(A,"([^?]*?)");return new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var r=t.exec(e).slice(1);return i.map(r,function(t,e){if(e===r.length-1)return t||null;return t?decodeURIComponent(t):null})}});var N=e.History=function(){this.handlers=[];i.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var R=/^[#\/]|\s+$/g;var O=/^\/+|\/+$/g;var P=/msie [\w.]+/;var C=/\/$/;var j=/#.*$/;N.started=false;i.extend(N.prototype,u,{interval:50,atRoot:function(){return this.location.pathname.replace(/[^\/]$/,"$&/")===this.root},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=decodeURI(this.location.pathname+this.location.search);var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.slice(i.length)}else{t=this.getHash()}}return t.replace(R,"")},start:function(t){if(N.started)throw new Error("Backbone.history has already been started");N.started=true;this.options=i.extend({root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var r=this.getFragment();var s=document.documentMode;var n=P.exec(navigator.userAgent.toLowerCase())&&(!s||s<=7);this.root=("/"+this.root+"/").replace(O,"/");if(n&&this._wantsHashChange){var a=e.$('