From 4594238a9f5803d250eb4f63b28b55f88f169bcc Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Thu, 20 Dec 2018 22:25:35 +0100 Subject: [PATCH 1/2] Config with nodes list --- cmd/kind/create/cluster/createcluster.go | 10 +- pkg/cluster/config/encoding/scheme.go | 70 +++--- pkg/cluster/config/encoding/scheme_test.go | 118 +++++---- .../encoding/testdata/invalid-apiversion.yaml | 2 +- .../encoding/testdata/invalid-kind.yaml | 3 + .../testdata/v1alpha2/valid-full-ha.yaml | 10 + .../v1alpha2/valid-minimal-two-nodes.yaml | 6 + .../v1alpha2/valid-with-lifecyclehooks.yaml | 13 +- pkg/cluster/config/fuzzer/fuzzer.go | 16 +- pkg/cluster/config/helpers.go | 229 ++++++++++++++++++ pkg/cluster/config/helpers_test.go | 185 ++++++++++++++ pkg/cluster/config/register.go | 10 - pkg/cluster/config/types.go | 91 ++++++- pkg/cluster/config/v1alpha1/conversion.go | 72 ++++++ pkg/cluster/config/v1alpha1/register.go | 10 - pkg/cluster/config/v1alpha2/default.go | 16 ++ pkg/cluster/config/v1alpha2/register.go | 10 - pkg/cluster/config/v1alpha2/types.go | 38 ++- pkg/cluster/config/validate.go | 61 ++++- pkg/cluster/config/validate_test.go | 179 ++++++++++---- pkg/cluster/context.go | 30 ++- 21 files changed, 986 insertions(+), 193 deletions(-) create mode 100644 pkg/cluster/config/encoding/testdata/invalid-kind.yaml create mode 100644 pkg/cluster/config/encoding/testdata/v1alpha2/valid-full-ha.yaml create mode 100644 pkg/cluster/config/encoding/testdata/v1alpha2/valid-minimal-two-nodes.yaml create mode 100644 pkg/cluster/config/helpers.go create mode 100644 pkg/cluster/config/helpers_test.go create mode 100644 pkg/cluster/config/v1alpha1/conversion.go diff --git a/cmd/kind/create/cluster/createcluster.go b/cmd/kind/create/cluster/createcluster.go index 8769595c48..2817ea8972 100644 --- a/cmd/kind/create/cluster/createcluster.go +++ b/cmd/kind/create/cluster/createcluster.go @@ -74,10 +74,18 @@ func runE(flags *flagpole, cmd *cobra.Command, args []string) error { return fmt.Errorf("aborting due to invalid configuration") } + // TODO(fabrizio pandini): this check is temporary / WIP + // kind v1alpha2 config fully supports multi nodes, but the cluster creation logic implemented in + // pkg/cluster/contex.go does not (yet). + // As soon a multi node support is implemented in pkg/cluster/contex.go, this should go away + if len(cfg.AllReplicas()) > 1 { + return fmt.Errorf("multi node support is still a work in progress, currently only single node cluster are supported") + } + // create a cluster context and create the cluster ctx := cluster.NewContext(flags.Name) if flags.ImageName != "" { - cfg.Image = flags.ImageName + cfg.BootStrapControlPlane().Image = flags.ImageName err := cfg.Validate() if err != nil { log.Errorf("Invalid flags, configuration failed validation: %v", err) diff --git a/pkg/cluster/config/encoding/scheme.go b/pkg/cluster/config/encoding/scheme.go index ec4f58fa03..3d8ff2acd3 100644 --- a/pkg/cluster/config/encoding/scheme.go +++ b/pkg/cluster/config/encoding/scheme.go @@ -48,56 +48,42 @@ func AddToScheme(scheme *runtime.Scheme) { utilruntime.Must(scheme.SetVersionPriority(v1alpha2.SchemeGroupVersion)) } -// newDefaultedConfig creates a new, defaulted `kind` Config -// Defaulting uses Scheme registered defaulting functions -func newDefaultedConfig() *config.Config { - var cfg = &v1alpha2.Config{} - - // apply defaults - Scheme.Default(cfg) - - // converts to internal cfg - var internalCfg = &config.Config{} - Scheme.Convert(cfg, internalCfg, nil) - - return internalCfg -} - -// unmarshalConfig attempt to decode data into a `kind` Config; data can be -// one of the different API versions defined in the Scheme. -func unmarshalConfig(data []byte) (*config.Config, error) { - var cfg = &v1alpha2.Config{} - - // decode data into a config object - _, _, err := Codecs.UniversalDecoder().Decode(data, nil, cfg) - if err != nil { - return nil, errors.Wrap(err, "decoding failure") - } - - // apply defaults - Scheme.Default(cfg) - - // converts to internal cfg - var internalCfg = &config.Config{} - Scheme.Convert(cfg, internalCfg, nil) - - return internalCfg, nil -} - // Load reads the file at path and attempts to convert into a `kind` Config; the file // can be one of the different API versions defined in scheme. // If path == "" then the default config is returned func Load(path string) (*config.Config, error) { - if path == "" { - return newDefaultedConfig(), nil + var latestPublicConfig = &v1alpha2.Config{} + + if path != "" { + // read in file + contents, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + // decode data into a internal api Config object because + // to leverage on conversion functions for all the api versions + var cfg = &config.Config{} + err = runtime.DecodeInto(Codecs.UniversalDecoder(), contents, cfg) + if err != nil { + return nil, errors.Wrap(err, "decoding failure") + } + + // converts back to the latest API version to apply defaults + Scheme.Convert(cfg, latestPublicConfig, nil) } - // read in file - contents, err := ioutil.ReadFile(path) - if err != nil { + // apply defaults + Scheme.Default(latestPublicConfig) + + // converts to internal config + var cfg = &config.Config{} + Scheme.Convert(latestPublicConfig, cfg, nil) + + if err := cfg.DeriveInfo(); err != nil { return nil, err } // unmarshal the file content into a `kind` Config - return unmarshalConfig(contents) + return cfg, nil } diff --git a/pkg/cluster/config/encoding/scheme_test.go b/pkg/cluster/config/encoding/scheme_test.go index c3c8f4d68d..6d98e2fd6e 100644 --- a/pkg/cluster/config/encoding/scheme_test.go +++ b/pkg/cluster/config/encoding/scheme_test.go @@ -17,76 +17,108 @@ limitations under the License. package encoding import ( - "reflect" "testing" ) -// TODO(fabrizio pandini): once we have multiple config API versions we -// will need more tests - func TestLoadCurrent(t *testing.T) { cases := []struct { - Name string - Path string - ExpectError bool + TestName string + Path string + ExpectReplicas []string + ExpectError bool }{ { - Name: "v1alpha1 valid minimal", - Path: "./testdata/v1alpha1/valid-minimal.yaml", - ExpectError: false, + TestName: "no config", + Path: "", + ExpectReplicas: []string{"control-plane"}, // no config (empty config path) should return a single node cluster + ExpectError: false, + }, + { + TestName: "v1alpha1 minimal", + Path: "./testdata/v1alpha1/valid-minimal.yaml", + ExpectReplicas: []string{"control-plane"}, + ExpectError: false, + }, + { + TestName: "v1alpha1 with lifecyclehooks", + Path: "./testdata/v1alpha1/valid-with-lifecyclehooks.yaml", + ExpectReplicas: []string{"control-plane"}, + ExpectError: false, + }, + { + TestName: "v1alpha2 minimal", + Path: "./testdata/v1alpha2/valid-minimal.yaml", + ExpectReplicas: []string{"control-plane"}, + ExpectError: false, }, { - Name: "v1alpha1 valid with lifecyclehooks", - Path: "./testdata/v1alpha1/valid-with-lifecyclehooks.yaml", - ExpectError: false, + TestName: "v1alpha2 lifecyclehooks", + Path: "./testdata/v1alpha2/valid-with-lifecyclehooks.yaml", + ExpectReplicas: []string{"control-plane"}, + ExpectError: false, }, { - Name: "v1alpha2 valid minimal", - Path: "./testdata/v1alpha2/valid-minimal.yaml", - ExpectError: false, + TestName: "v1alpha2 config with 2 nodes", + Path: "./testdata/v1alpha2/valid-minimal-two-nodes.yaml", + ExpectReplicas: []string{"control-plane", "worker"}, + ExpectError: false, }, { - Name: "v1alpha2 valid with lifecyclehooks", - Path: "./testdata/v1alpha2/valid-with-lifecyclehooks.yaml", - ExpectError: false, + TestName: "v1alpha2 full HA", + Path: "./testdata/v1alpha2/valid-full-ha.yaml", + ExpectReplicas: []string{"etcd", "lb", "control-plane1", "control-plane2", "control-plane3", "worker1", "worker2"}, + ExpectError: false, }, { - Name: "invalid path", + TestName: "invalid path", Path: "./testdata/not-a-file.bogus", ExpectError: true, }, { - Name: "invalid apiVersion", + TestName: "Invalid apiversion", Path: "./testdata/invalid-apiversion.yaml", ExpectError: true, }, { - Name: "invalid yaml", + TestName: "Invalid kind", + Path: "./testdata/invalid-kind.yaml", + ExpectError: true, + }, + { + TestName: "Invalid yaml", Path: "./testdata/invalid-yaml.yaml", ExpectError: true, }, } - for _, tc := range cases { - _, err := Load(tc.Path) - if err != nil && !tc.ExpectError { - t.Errorf("case: '%s' got error loading and expected none: %v", tc.Name, err) - } else if err == nil && tc.ExpectError { - t.Errorf("case: '%s' got no error loading but expected one", tc.Name) - } - } -} + for _, c := range cases { + t.Run(c.TestName, func(t2 *testing.T) { + // Loading config and deriving infos + cfg, err := Load(c.Path) -func TestLoadDefault(t *testing.T) { - cfg, err := Load("") - if err != nil { - t.Errorf("got error loading default config but expected none: %v", err) - t.FailNow() - } - defaultConfig := newDefaultedConfig() - if !reflect.DeepEqual(cfg, defaultConfig) { - t.Errorf( - "Load(\"\") should match config.New() but does not: %v != %v", - cfg, defaultConfig, - ) + // the error can be: + // - nil, in which case we should expect no errors or fail + if err != nil { + if !c.ExpectError { + t2.Fatalf("unexpected error while Loading config: %v", err) + } + return + } + // - not nil, in which case we should expect errors or fail + if err == nil { + if c.ExpectError { + t2.Fatalf("unexpected lack or error while Loading config") + } + } + + if len(cfg.AllReplicas()) != len(c.ExpectReplicas) { + t2.Fatalf("expected %d replicas, saw %d", len(c.ExpectReplicas), len(cfg.AllReplicas())) + } + + for i, name := range c.ExpectReplicas { + if cfg.AllReplicas()[i].Name != name { + t2.Errorf("expected %q node at position %d, saw %q", name, i, cfg.AllReplicas()[i].Name) + } + } + }) } } diff --git a/pkg/cluster/config/encoding/testdata/invalid-apiversion.yaml b/pkg/cluster/config/encoding/testdata/invalid-apiversion.yaml index 9f08271ed8..b5f797a633 100644 --- a/pkg/cluster/config/encoding/testdata/invalid-apiversion.yaml +++ b/pkg/cluster/config/encoding/testdata/invalid-apiversion.yaml @@ -1,3 +1,3 @@ # this file contains an invalid config api version for testing -kind: Config +kind: Node apiVersion: not-valid diff --git a/pkg/cluster/config/encoding/testdata/invalid-kind.yaml b/pkg/cluster/config/encoding/testdata/invalid-kind.yaml new file mode 100644 index 0000000000..6f58a2c941 --- /dev/null +++ b/pkg/cluster/config/encoding/testdata/invalid-kind.yaml @@ -0,0 +1,3 @@ +# this file contains an invalid config kind for testing +kind: not-valid +apiVersion: kind.sigs.k8s.io/v1alpha2 \ No newline at end of file diff --git a/pkg/cluster/config/encoding/testdata/v1alpha2/valid-full-ha.yaml b/pkg/cluster/config/encoding/testdata/v1alpha2/valid-full-ha.yaml new file mode 100644 index 0000000000..38507f7b77 --- /dev/null +++ b/pkg/cluster/config/encoding/testdata/v1alpha2/valid-full-ha.yaml @@ -0,0 +1,10 @@ +# technically valid, config file with a full ha cluster +kind: Config +apiVersion: kind.sigs.k8s.io/v1alpha2 +nodes: +- role: control-plane + replicas: 3 +- role: worker + replicas: 2 +- role: external-etcd +- role: external-load-balancer diff --git a/pkg/cluster/config/encoding/testdata/v1alpha2/valid-minimal-two-nodes.yaml b/pkg/cluster/config/encoding/testdata/v1alpha2/valid-minimal-two-nodes.yaml new file mode 100644 index 0000000000..44704c330e --- /dev/null +++ b/pkg/cluster/config/encoding/testdata/v1alpha2/valid-minimal-two-nodes.yaml @@ -0,0 +1,6 @@ +# technically valid, minimal config file with two nodes +kind: Config +apiVersion: kind.sigs.k8s.io/v1alpha2 +nodes: +- role: control-plane +- role: worker \ No newline at end of file diff --git a/pkg/cluster/config/encoding/testdata/v1alpha2/valid-with-lifecyclehooks.yaml b/pkg/cluster/config/encoding/testdata/v1alpha2/valid-with-lifecyclehooks.yaml index 5b1521ddbf..13ed32c209 100644 --- a/pkg/cluster/config/encoding/testdata/v1alpha2/valid-with-lifecyclehooks.yaml +++ b/pkg/cluster/config/encoding/testdata/v1alpha2/valid-with-lifecyclehooks.yaml @@ -1,8 +1,9 @@ kind: Config apiVersion: kind.sigs.k8s.io/v1alpha2 -nodeLifecycle: - preKubeadm: - - name: "pull an image" - command: [ "docker", "pull", "ubuntu" ] - - name: "pull another image" - command: [ "docker", "pull", "debian" ] \ No newline at end of file +nodes: +- nodeLifecycle: + preKubeadm: + - name: "pull an image" + command: [ "docker", "pull", "ubuntu" ] + - name: "pull another image" + command: [ "docker", "pull", "debian" ] \ No newline at end of file diff --git a/pkg/cluster/config/fuzzer/fuzzer.go b/pkg/cluster/config/fuzzer/fuzzer.go index 4eae9a09f8..c31a243bbb 100644 --- a/pkg/cluster/config/fuzzer/fuzzer.go +++ b/pkg/cluster/config/fuzzer/fuzzer.go @@ -28,12 +28,24 @@ import ( func Funcs(codecs runtimeserializer.CodecFactory) []interface{} { return []interface{}{ fuzzConfig, + //fuzzNode, } } func fuzzConfig(obj *config.Config, c fuzz.Continue) { c.FuzzNoCustom(obj) - // Pinning values for fields that get defaults if fuzz value is empty string or nil (thus making the round trip test fail) - obj.Image = "fuzzimage:latest" + // Pinning values for fields that get defaults if fuzz value is empty string or nil + obj.Nodes = []config.Node{{ + Image: "foo:bar", + Role: config.ControlPlaneRole, + }} +} + +func fuzzNode(obj *config.Node, c fuzz.Continue) { + c.FuzzNoCustom(obj) + + // Pinning values for fields that get defaults if fuzz value is empty string or nil + obj.Image = "foo:bar" + obj.Role = config.ControlPlaneRole } diff --git a/pkg/cluster/config/helpers.go b/pkg/cluster/config/helpers.go new file mode 100644 index 0000000000..1849227c7e --- /dev/null +++ b/pkg/cluster/config/helpers.go @@ -0,0 +1,229 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "fmt" + "sort" + + "github.com/pkg/errors" +) + +// IsControlPlane returns true if the node hosts a control plane instance +// NB. in single node clusters, control-plane nodes act also as a worker nodes +func (n *Node) IsControlPlane() bool { + return n.Role == ControlPlaneRole +} + +// IsWorker returns true if the node hosts a worker instance +func (n *Node) IsWorker() bool { + return n.Role == WorkerRole +} + +// IsExternalEtcd returns true if the node hosts an external etcd member +func (n *Node) IsExternalEtcd() bool { + return n.Role == ExternalEtcdRole +} + +// IsExternalLoadBalancer returns true if the node hosts an external load balancer +func (n *Node) IsExternalLoadBalancer() bool { + return n.Role == ExternalLoadBalancerRole +} + +// ProvisioningOrder returns the provisioning order for nodes, that +// should be defined according to the assigned NodeRole +func (n *Node) ProvisioningOrder() int { + switch n.Role { + // External dependencies should be provisioned first; we are defining an arbitrary + // precedence between etcd and load balancer in order to get predictable/repeatable results + case ExternalEtcdRole: + return 1 + case ExternalLoadBalancerRole: + return 2 + // Then control plane nodes + case ControlPlaneRole: + return 3 + // Finally workers + case WorkerRole: + return 4 + default: + return 99 + } +} + +// Len of the NodeList. +// It is required for making NodeList sortable. +func (t ReplicaList) Len() int { + return len(t) +} + +// Less return the lower between two elements of the NodeList, where the +// lower element should be provisioned before the other. +// It is required for making NodeList sortable. +func (t ReplicaList) Less(i, j int) bool { + return t[i].ProvisioningOrder() < t[j].ProvisioningOrder() || + // In case of same provisioning order, the name is used to get predictable/repeatable results + (t[i].ProvisioningOrder() == t[j].ProvisioningOrder() && t[i].Name < t[j].Name) +} + +// Swap two elements of the NodeList. +// It is required for making NodeList sortable. +func (t ReplicaList) Swap(i, j int) { + t[i], t[j] = t[j], t[i] +} + +// DeriveInfo populates DerivedConfig info starting +// from the current list on Nodes +func (c *Config) DeriveInfo() error { + for _, n := range c.Nodes { + if err := c.Add(&n); err != nil { + return err + } + } + return nil +} + +// Add a Node to the `kind` cluster, generating requested node replicas +// and assigning a unique node name to each replica. +func (c *Config) Add(node *Node) error { + + // Creates the list of node replicas + expectedReplicas := 1 + if node.Replicas != nil { + expectedReplicas = int(*node.Replicas) + } + + replicas := ReplicaList{} + for i := 1; i <= expectedReplicas; i++ { + replica := &NodeReplica{ + Node: *node.DeepCopy(), + } + replica.Replicas = nil // resetting the replicas number for each replica to default (1) + + replicas = append(replicas, replica) + } + + // adds replica to the config unpdating derivedConfigData + for _, replica := range replicas { + + // adds the replica to the list of nodes + c.allReplicas = append(c.allReplicas, replica) + + // list of nodes with control plane role + if replica.IsControlPlane() { + // assign selected name for control plane node + replica.Name = "control-plane" + // stores the node in derivedConfigData + c.controlPlanes = append(c.controlPlanes, replica) + } + + // list of nodes with worker role + if replica.IsWorker() { + // assign selected name for worker node + replica.Name = "worker" + // stores the node in derivedConfigData + c.workers = append(c.workers, replica) + } + + // node with external etcd role + if replica.IsExternalEtcd() { + if c.externalEtcd != nil { + return errors.Errorf("invalid config. there are two nodes with role %q", ExternalEtcdRole) + } + // assign selected name for etcd node + replica.Name = "etcd" + // stores the node in derivedConfigData + c.externalEtcd = replica + } + + // node with external load balancer role + if replica.IsExternalLoadBalancer() { + if c.externalLoadBalancer != nil { + return errors.Errorf("invalid config. there are two nodes with role %q", ExternalLoadBalancerRole) + } + // assign selected name for load balancer node + replica.Name = "lb" + // stores the node in derivedConfigData + c.externalLoadBalancer = replica + } + + } + + // if more than one control plane node exists, fixes names to get a progressive index + if len(c.controlPlanes) > 1 { + for i, n := range c.controlPlanes { + n.Name = fmt.Sprintf("%s%d", "control-plane", i+1) + } + } + + // if more than one worker node exists, fixes names to get a progressive index + if len(c.workers) > 1 { + for i, n := range c.workers { + n.Name = fmt.Sprintf("%s%d", "worker", i+1) + } + } + + // ensure the list of nodes is ordered. + // the ordering is key for getting a consistent and predictable behaviour + // when provisioning nodes and when executing actions on nodes + sort.Sort(c.allReplicas) + + return nil +} + +// AllReplicas returns all the node replicas defined in the `kind` Config. +func (c *Config) AllReplicas() ReplicaList { + return c.allReplicas +} + +// ControlPlanes returns all the nodes with control-plane role +func (c *Config) ControlPlanes() ReplicaList { + return c.controlPlanes +} + +// BootStrapControlPlane returns the first node with control-plane role +// This is the node where kubeadm init will be executed. +func (c *Config) BootStrapControlPlane() *NodeReplica { + if len(c.controlPlanes) == 0 { + return nil + } + return c.controlPlanes[0] +} + +// SecondaryControlPlanes returns all the nodes with control-plane role +// except the BootStrapControlPlane node, if any, +func (c *Config) SecondaryControlPlanes() ReplicaList { + if len(c.controlPlanes) <= 1 { + return nil + } + return c.controlPlanes[1:] +} + +// Workers returns all the nodes with Worker role, if any +func (c *Config) Workers() ReplicaList { + return c.workers +} + +// ExternalEtcd returns the node with external-etcd role, if defined +func (c *Config) ExternalEtcd() *NodeReplica { + return c.externalEtcd +} + +// ExternalLoadBalancer returns the node with external-load-balancer role, if defined +func (c *Config) ExternalLoadBalancer() *NodeReplica { + return c.externalLoadBalancer +} diff --git a/pkg/cluster/config/helpers_test.go b/pkg/cluster/config/helpers_test.go new file mode 100644 index 0000000000..0f1b5d17ac --- /dev/null +++ b/pkg/cluster/config/helpers_test.go @@ -0,0 +1,185 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "testing" + + utilpointer "k8s.io/utils/pointer" +) + +func TestDeriveInfo(t *testing.T) { + cases := []struct { + TestName string + Nodes []Node + ExpectReplicas []string + ExpectControlPlanes []string + ExpectBootStrapControlPlane *string + ExpectSecondaryControlPlanes []string + ExpectWorkers []string + ExpectEtcd *string + ExpectLoadBalancer *string + ExpectError bool + }{ + { + TestName: "Defaults/Empty config should give empty replicas", + ExpectReplicas: nil, + ExpectControlPlanes: nil, + ExpectBootStrapControlPlane: nil, + ExpectSecondaryControlPlanes: nil, + ExpectWorkers: nil, + ExpectEtcd: nil, + ExpectLoadBalancer: nil, + ExpectError: false, + }, + { + TestName: "Single control plane Node get properly assigned to bootstrap control-plane", + Nodes: []Node{ + {Role: ControlPlaneRole}, + }, + ExpectReplicas: []string{"control-plane"}, + ExpectControlPlanes: []string{"control-plane"}, + ExpectBootStrapControlPlane: utilpointer.StringPtr("control-plane"), + ExpectSecondaryControlPlanes: nil, + ExpectError: false, + }, + { + TestName: "Control planes Nodes get properly splitted between bootstrap and secondary control-planes", + Nodes: []Node{ + {Role: ControlPlaneRole, Replicas: utilpointer.Int32Ptr(3)}, + }, + ExpectReplicas: []string{"control-plane1", "control-plane2", "control-plane3"}, + ExpectControlPlanes: []string{"control-plane1", "control-plane2", "control-plane3"}, + ExpectBootStrapControlPlane: utilpointer.StringPtr("control-plane1"), + ExpectSecondaryControlPlanes: []string{"control-plane2", "control-plane3"}, + ExpectError: false, + }, + { + TestName: "Full HA cluster", // NB. This test case test that provisioning order is applied to all the node lists as well + Nodes: []Node{ + {Role: WorkerRole}, + {Role: ControlPlaneRole}, + {Role: ExternalEtcdRole}, + {Role: ControlPlaneRole}, + {Role: WorkerRole}, + {Role: ControlPlaneRole}, + {Role: ExternalLoadBalancerRole}, + }, + ExpectReplicas: []string{"etcd", "lb", "control-plane1", "control-plane2", "control-plane3", "worker1", "worker2"}, + ExpectControlPlanes: []string{"control-plane1", "control-plane2", "control-plane3"}, + ExpectBootStrapControlPlane: utilpointer.StringPtr("control-plane1"), + ExpectSecondaryControlPlanes: []string{"control-plane2", "control-plane3"}, + ExpectWorkers: []string{"worker1", "worker2"}, + ExpectEtcd: utilpointer.StringPtr("etcd"), + ExpectLoadBalancer: utilpointer.StringPtr("lb"), + ExpectError: false, + }, + { + TestName: "Full HA cluster with replicas", + Nodes: []Node{ + {Role: WorkerRole, Replicas: utilpointer.Int32Ptr(2)}, + {Role: ControlPlaneRole, Replicas: utilpointer.Int32Ptr(3)}, + {Role: ExternalEtcdRole}, + {Role: ExternalLoadBalancerRole}, + }, + ExpectReplicas: []string{"etcd", "lb", "control-plane1", "control-plane2", "control-plane3", "worker1", "worker2"}, + ExpectControlPlanes: []string{"control-plane1", "control-plane2", "control-plane3"}, + ExpectBootStrapControlPlane: utilpointer.StringPtr("control-plane1"), + ExpectSecondaryControlPlanes: []string{"control-plane2", "control-plane3"}, + ExpectWorkers: []string{"worker1", "worker2"}, + ExpectEtcd: utilpointer.StringPtr("etcd"), + ExpectLoadBalancer: utilpointer.StringPtr("lb"), + ExpectError: false, + }, + { + TestName: "Fails because two etcds Nodes are added", + Nodes: []Node{ + {Role: ExternalEtcdRole}, + {Role: ExternalEtcdRole}, + }, + ExpectError: true, + }, + { + TestName: "Fails because two load balancer Nodes are added", + Nodes: []Node{ + {Role: ExternalLoadBalancerRole}, + {Role: ExternalLoadBalancerRole}, + }, + ExpectError: true, + }, + } + + for _, c := range cases { + t.Run(c.TestName, func(t2 *testing.T) { + // Adding Nodes to the config and deriving infos + var cfg = Config{Nodes: c.Nodes} + err := cfg.DeriveInfo() + // the error can be: + // - nil, in which case we should expect no errors or fail + if err != nil { + if !c.ExpectError { + t2.Fatalf("unexpected error while Deriving infos: %v", err) + } + return + } + // - not nil, in which case we should expect errors or fail + if err == nil { + if c.ExpectError { + t2.Fatalf("unexpected lack or error while Deriving infos") + } + } + + // Fail if Nodes does not match + checkReplicaList(t2, cfg.AllReplicas(), c.ExpectReplicas) + + // Fail if fields derived from Nodes does not match + checkReplicaList(t2, cfg.ControlPlanes(), c.ExpectControlPlanes) + checkNode(t2, cfg.BootStrapControlPlane(), c.ExpectBootStrapControlPlane) + checkReplicaList(t2, cfg.SecondaryControlPlanes(), c.ExpectSecondaryControlPlanes) + checkReplicaList(t2, cfg.Workers(), c.ExpectWorkers) + checkNode(t2, cfg.ExternalEtcd(), c.ExpectEtcd) + checkNode(t2, cfg.ExternalLoadBalancer(), c.ExpectLoadBalancer) + }) + } +} + +func checkNode(t *testing.T, n *NodeReplica, name *string) { + if (n == nil) != (name == nil) { + t.Errorf("expected %v node, saw %v", name, n) + } + + if n == nil { + return + } + + if n.Name != *name { + t.Errorf("expected %v node, saw %v", name, n.Name) + } +} + +func checkReplicaList(t *testing.T, list ReplicaList, names []string) { + if len(list) != len(names) { + t.Errorf("expected %d nodes, saw %d", len(names), len(list)) + return + } + + for i, name := range names { + if list[i].Name != name { + t.Errorf("expected %q node at position %d, saw %q", name, i, list[i].Name) + } + } +} diff --git a/pkg/cluster/config/register.go b/pkg/cluster/config/register.go index bb1cb78497..fef52b593f 100644 --- a/pkg/cluster/config/register.go +++ b/pkg/cluster/config/register.go @@ -36,16 +36,6 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Kind takes an unqualified kind and returns a Group qualified GroupKind. -func Kind(kind string) schema.GroupKind { - return SchemeGroupVersion.WithKind(kind).GroupKind() -} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - func init() { // We only register manually written functions here. The registration of the // generated functions takes place in the generated files. The separation diff --git a/pkg/cluster/config/types.go b/pkg/cluster/config/types.go index f0a7113c53..096e302c11 100644 --- a/pkg/cluster/config/types.go +++ b/pkg/cluster/config/types.go @@ -23,12 +23,36 @@ import ( // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// Config contains cluster creation config -// This is the current internal config type used by cluster -// Other API versions can be converted to this struct with Convert() +// Config groups all nodes in the `kind` Config. type Config struct { + // TypeMeta representing the type of the object and its API schema version. metav1.TypeMeta + // Nodes constains the list of nodes defined in the `kind` Config + Nodes []Node `json:"nodes,"` + + // DerivedConfigData is struct populated starting from the node list. + // It contains a set of "materialized views" generated starting from nodes + // and designed to make easy operating nodes in the rest of the code base. + // This attribute exists only in the internal config version and is meant + // to simplify the usage of the config in the code base. + // TODO(fabrizio pandini): as soon as possible this should became an + // internal of the kind library and be removed from the config + // +k8s:conversion-gen=false + DerivedConfigData +} + +// Node contains settings for a node in the `kind` Config. +// A node in kind config represent a container that will be provisioned with all the components +// required for the assigned role in the Kubernetes cluster. +// If replicas is set, the desired node replica number will be generated. +type Node struct { + // Replicas is the number of desired node replicas. + // Defaults to 1 + Replicas *int32 + // Role defines the role of the nodw in the in the Kubernetes cluster managed by `kind` + // Defaults to "control-plane" + Role NodeRole // Image is the node image to use when running the cluster // TODO(bentheelder): split this into image and tag? Image string @@ -44,6 +68,24 @@ type Config struct { ControlPlane *ControlPlane } +// NodeRole defines possible role for nodes in a Kubernetes cluster managed by `kind` +type NodeRole string + +const ( + // ControlPlaneRole identifies a node that hosts a Kubernetes control-plane + // NB. in single node clusters, control-plane nodes act also as a worker nodes + ControlPlaneRole NodeRole = "control-plane" + // WorkerRole identifies a node that hosts a Kubernetes worker + WorkerRole NodeRole = "worker" + // ExternalEtcdRole identifies a node that hosts an external-etcd instance. + // Please note that `kind` nodes hosting external etcd are not kubernetes nodes + ExternalEtcdRole NodeRole = "external-etcd" + // ExternalLoadBalancerRole identifies a node that hosts an external load balancer for API server + // in HA configurations. + // Please note that `kind` nodes hosting external load balancer are not kubernetes nodes + ExternalLoadBalancerRole NodeRole = "external-load-balancer" +) + // ControlPlane holds configurations specific to the control plane nodes // (currently the only node). type ControlPlane struct { @@ -75,3 +117,46 @@ type LifecycleHook struct { // the boot process will continue MustSucceed bool } + +// +k8s:conversion-gen=false + +// NodeReplica defines a `kind` config Node that is geneated by creating a replicas for a node +// This attribute exists only in the internal config version and is meant +// to simplify the usage of the config in the code base. +type NodeReplica struct { + // Node contains settings for the node in the `kind` Config. + // please note that the Replicas number is alway set to nil. + Node + + // Name contains the unique name assigned to the node while generating the replica + Name string +} + +// +k8s:conversion-gen=false + +// ReplicaList defines a list of NodeReplicas in the `kind` Config +// This attribute exists only in the internal config version and is meant +// to simplify the usage of the config in the code base. +type ReplicaList []*NodeReplica + +// +k8s:conversion-gen=false + +// DerivedConfigData is a struct populated starting from the nodes list. +// All the field of this type are intentionally defined a private fields, thus ensuring +// that derivedConfigData will have a predictable behaviour for the code built on top. +// This attribute exists only in the internal config version and is meant +// to simplify the usage of the config in the code base. +type DerivedConfigData struct { + // allReplicas constains the list of node replicas defined in the `kind` Config + allReplicas ReplicaList + // controlPlanes contains the subset of node replicas with control-plane role + controlPlanes ReplicaList + // workers contains the subset of node replicas with worker role, if any + workers ReplicaList + // externalEtcd contains the node replica with external-etcd role, if defined + // TODO(fabriziopandini): eventually in future we would like to support + // external etcd clusters with more than one member + externalEtcd *NodeReplica + // externalLoadBalancer contains the node replica with external-load-balancer role, if defined + externalLoadBalancer *NodeReplica +} diff --git a/pkg/cluster/config/v1alpha1/conversion.go b/pkg/cluster/config/v1alpha1/conversion.go new file mode 100644 index 0000000000..cea60b9cee --- /dev/null +++ b/pkg/cluster/config/v1alpha1/conversion.go @@ -0,0 +1,72 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "fmt" + unsafe "unsafe" + + conversion "k8s.io/apimachinery/pkg/conversion" + "sigs.k8s.io/kind/pkg/cluster/config" + kustomize "sigs.k8s.io/kind/pkg/kustomize" +) + +func Convert_v1alpha1_Config_To_config_Config(in *Config, out *config.Config, s conversion.Scope) error { + if err := autoConvert_v1alpha1_Config_To_config_Config(in, out, s); err != nil { + return err + } + + // converts v1alpha1 Config into an internal config with a single control-plane node + var node config.Node + + node.Role = config.ControlPlaneRole + node.Image = in.Image + node.KubeadmConfigPatches = *(*[]string)(unsafe.Pointer(&in.KubeadmConfigPatches)) + node.KubeadmConfigPatchesJSON6902 = *(*[]kustomize.PatchJSON6902)(unsafe.Pointer(&in.KubeadmConfigPatchesJSON6902)) + node.ControlPlane = (*config.ControlPlane)(unsafe.Pointer(in.ControlPlane)) + + out.Nodes = []config.Node{node} + + return nil +} + +func Convert_config_Config_To_v1alpha1_Config(in *config.Config, out *Config, s conversion.Scope) error { + if err := autoConvert_config_Config_To_v1alpha1_Config(in, out, s); err != nil { + return err + } + + // convertion from internal config to v1alpha1 Config is used only by the fuzzer roundtrip test; + // the fuzzer is configured in order to enforce the number and type of nodes to get always the + // following condition pass + + if len(in.Nodes) > 1 { + return fmt.Errorf("invalid conversion. `kind` config with more than one Node cannot be converted to v1alpha1 config format") + } + + var node = in.Nodes[0] + + if !node.IsControlPlane() { + return fmt.Errorf("invalid conversion. `kind` config without a control-plane Node cannot be converted to v1alpha1 config format %v", node) + } + + out.Image = node.Image + out.KubeadmConfigPatches = *(*[]string)(unsafe.Pointer(&node.KubeadmConfigPatches)) + out.KubeadmConfigPatchesJSON6902 = *(*[]kustomize.PatchJSON6902)(unsafe.Pointer(&node.KubeadmConfigPatchesJSON6902)) + out.ControlPlane = (*ControlPlane)(unsafe.Pointer(node.ControlPlane)) + + return nil +} diff --git a/pkg/cluster/config/v1alpha1/register.go b/pkg/cluster/config/v1alpha1/register.go index caabb403ab..01b71cf72f 100644 --- a/pkg/cluster/config/v1alpha1/register.go +++ b/pkg/cluster/config/v1alpha1/register.go @@ -36,16 +36,6 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Kind takes an unqualified kind and returns a Group qualified GroupKind. -func Kind(kind string) schema.GroupKind { - return SchemeGroupVersion.WithKind(kind).GroupKind() -} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - func init() { // We only register manually written functions here. The registration of the // generated functions takes place in the generated files. The separation diff --git a/pkg/cluster/config/v1alpha2/default.go b/pkg/cluster/config/v1alpha2/default.go index d41d7c0a00..b969166664 100644 --- a/pkg/cluster/config/v1alpha2/default.go +++ b/pkg/cluster/config/v1alpha2/default.go @@ -30,7 +30,23 @@ func addDefaultingFuncs(scheme *runtime.Scheme) error { // SetDefaults_Config sets uninitialized fields to their default value. func SetDefaults_Config(obj *Config) { + if len(obj.Nodes) == 0 { + obj.Nodes = []Node{ + { + Image: DefaultImage, + Role: ControlPlaneRole, + }, + } + } +} + +// SetDefaults_Node sets uninitialized fields to their default value. +func SetDefaults_Node(obj *Node) { if obj.Image == "" { obj.Image = DefaultImage } + + if obj.Role == "" { + obj.Role = ControlPlaneRole + } } diff --git a/pkg/cluster/config/v1alpha2/register.go b/pkg/cluster/config/v1alpha2/register.go index b985435d5d..dbab5a1ab6 100644 --- a/pkg/cluster/config/v1alpha2/register.go +++ b/pkg/cluster/config/v1alpha2/register.go @@ -36,16 +36,6 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -// Kind takes an unqualified kind and returns a Group qualified GroupKind. -func Kind(kind string) schema.GroupKind { - return SchemeGroupVersion.WithKind(kind).GroupKind() -} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - func init() { // We only register manually written functions here. The registration of the // generated functions takes place in the generated files. The separation diff --git a/pkg/cluster/config/v1alpha2/types.go b/pkg/cluster/config/v1alpha2/types.go index be5937f60d..0bf6d0e919 100644 --- a/pkg/cluster/config/v1alpha2/types.go +++ b/pkg/cluster/config/v1alpha2/types.go @@ -23,11 +23,25 @@ import ( // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// Config contains cluster creation config -// This is the current internal config type used by cluster +// Config groups all nodes in the `kind` Config. type Config struct { - metav1.TypeMeta + // TypeMeta representing the type of the object and its API schema version. + metav1.TypeMeta `json:",inline"` + // nodes constains the list of nodes defined in the `kind` Config + Nodes []Node `json:"nodes"` +} + +// Node contains settings for a node in the `kind` Config. +// A node in kind config represent a container that will be provisioned with all the components +// required for the assigned role in the Kubernetes cluster +type Node struct { + // Replicas is the number of desired node replicas. + // Defaults to 1 + Replicas *int32 `json:"replicas,omitempty"` + // Role defines the role of the nodw in the in the Kubernetes cluster managed by `kind` + // Defaults to "control-plane" + Role NodeRole `json:"role,omitempty"` // Image is the node image to use when running the cluster // TODO(bentheelder): split this into image and tag? Image string `json:"image,omitempty"` @@ -43,6 +57,24 @@ type Config struct { ControlPlane *ControlPlane `json:"ControlPlane,omitempty"` } +// NodeRole defines possible role for nodes in a Kubernetes cluster managed by `kind` +type NodeRole string + +const ( + // ControlPlaneRole identifies a node that hosts a Kubernetes control-plane. + // NB. in single node clusters, control-plane nodes act also as a worker nodes + ControlPlaneRole NodeRole = "control-plane" + // WorkerRole identifies a node that hosts a Kubernetes worker + WorkerRole NodeRole = "worker" + // ExternalEtcdRole identifies a node that hosts an external-etcd instance. + // Please note that `kind` nodes hosting external etcd are not kubernetes nodes + ExternalEtcdRole NodeRole = "external-etcd" + // ExternalLoadBalancerRole identifies a node that hosts an external load balancer for API server + // in HA configurations. + // Please note that `kind` nodes hosting external load balancer are not kubernetes nodes + ExternalLoadBalancerRole NodeRole = "external-load-balancer" +) + // ControlPlane holds configurations specific to the control plane nodes // (currently the only node). type ControlPlane struct { diff --git a/pkg/cluster/config/validate.go b/pkg/cluster/config/validate.go index 87773a5e5d..93cdd84a02 100644 --- a/pkg/cluster/config/validate.go +++ b/pkg/cluster/config/validate.go @@ -26,12 +26,58 @@ import ( // with the config, or nil if there are none func (c *Config) Validate() error { errs := []error{} - if c.Image == "" { + + // All nodes in the config should be valid + for i, n := range c.Nodes { + if err := n.Validate(); err != nil { + errs = append(errs, fmt.Errorf("please fix invalid configuration for node %d: \n%v", i, err)) + } + } + + // There should be at least one control plane + if c.BootStrapControlPlane() == nil { + errs = append(errs, fmt.Errorf("please add at least one node with role %q", ControlPlaneRole)) + } + // There should be one load balancer if more than one control plane exists in the cluster + if len(c.ControlPlanes()) > 1 && c.ExternalLoadBalancer() == nil { + errs = append(errs, fmt.Errorf("please add a node with role %s because in the cluster there are more than one node with role %s", ExternalLoadBalancerRole, ControlPlaneRole)) + } + + if len(errs) > 0 { + return util.NewErrors(errs) + } + return nil +} + +// Validate returns a ConfigErrors with an entry for each problem +// with the Node, or nil if there are none +func (n *Node) Validate() error { + errs := []error{} + + // validate node role should be one of the expected values + switch n.Role { + case ControlPlaneRole, + WorkerRole, + ExternalEtcdRole, + ExternalLoadBalancerRole: + default: + errs = append(errs, fmt.Errorf("role is a required field")) + } + + // image should be defined + if n.Image == "" { errs = append(errs, fmt.Errorf("image is a required field")) } - if c.ControlPlane != nil { - if c.ControlPlane.NodeLifecycle != nil { - for _, hook := range c.ControlPlane.NodeLifecycle.PreBoot { + + // replicas >= 0 + if n.Replicas != nil && int32(*n.Replicas) < 0 { + errs = append(errs, fmt.Errorf("replicas number should not be a negative number")) + } + + // validate NodeLifecycle + if n.ControlPlane != nil { + if n.ControlPlane.NodeLifecycle != nil { + for _, hook := range n.ControlPlane.NodeLifecycle.PreBoot { if len(hook.Command) == 0 { errs = append(errs, fmt.Errorf( "preBoot hooks must set command to a non-empty value", @@ -41,7 +87,7 @@ func (c *Config) Validate() error { break } } - for _, hook := range c.ControlPlane.NodeLifecycle.PreKubeadm { + for _, hook := range n.ControlPlane.NodeLifecycle.PreKubeadm { if len(hook.Command) == 0 { errs = append(errs, fmt.Errorf( "preKubeadm hooks must set command to a non-empty value", @@ -51,7 +97,7 @@ func (c *Config) Validate() error { break } } - for _, hook := range c.ControlPlane.NodeLifecycle.PostKubeadm { + for _, hook := range n.ControlPlane.NodeLifecycle.PostKubeadm { if len(hook.Command) == 0 { errs = append(errs, fmt.Errorf( "postKubeadm hooks must set command to a non-empty value", @@ -61,7 +107,7 @@ func (c *Config) Validate() error { break } } - for _, hook := range c.ControlPlane.NodeLifecycle.PostSetup { + for _, hook := range n.ControlPlane.NodeLifecycle.PostSetup { if len(hook.Command) == 0 { errs = append(errs, fmt.Errorf( "postKubeadm hooks must set command to a non-empty value", @@ -76,5 +122,6 @@ func (c *Config) Validate() error { if len(errs) > 0 { return util.NewErrors(errs) } + return nil } diff --git a/pkg/cluster/config/validate_test.go b/pkg/cluster/config/validate_test.go index 5d359fd7d0..e0c92f55fc 100644 --- a/pkg/cluster/config/validate_test.go +++ b/pkg/cluster/config/validate_test.go @@ -23,29 +23,29 @@ import ( ) // TODO(fabriziopandini): ideally this should use scheme.Default, but this creates a circular dependency -// So the current solution is to mimic defaulting for the validation test, but probably there should be a better solution here -func newDefaultedConfig() *Config { - cfg := &Config{ +// So the current solution is to mimic defaulting for the validation test +func newDefaultedNode(role NodeRole) Node { + return Node{ + Role: role, Image: "myImage:latest", } - return cfg } -func TestConfigValidate(t *testing.T) { +func TestNodeValidate(t *testing.T) { cases := []struct { - TestName string - Config *Config - ExpectedErrors int + TestName string + Node Node + ExpectErrors int }{ { - TestName: "Canonical config", - Config: newDefaultedConfig(), - ExpectedErrors: 0, + TestName: "Canonical node", + Node: newDefaultedNode(ControlPlaneRole), + ExpectErrors: 0, }, { TestName: "Invalid PreBoot hook", - Config: func() *Config { - cfg := newDefaultedConfig() + Node: func() Node { + cfg := newDefaultedNode(ControlPlaneRole) cfg.ControlPlane = &ControlPlane{ NodeLifecycle: &NodeLifecycle{ PreBoot: []LifecycleHook{ @@ -57,12 +57,12 @@ func TestConfigValidate(t *testing.T) { } return cfg }(), - ExpectedErrors: 1, + ExpectErrors: 1, }, { TestName: "Invalid PreKubeadm hook", - Config: func() *Config { - cfg := newDefaultedConfig() + Node: func() Node { + cfg := newDefaultedNode(ControlPlaneRole) cfg.ControlPlane = &ControlPlane{ NodeLifecycle: &NodeLifecycle{ PreKubeadm: []LifecycleHook{ @@ -75,12 +75,12 @@ func TestConfigValidate(t *testing.T) { } return cfg }(), - ExpectedErrors: 1, + ExpectErrors: 1, }, { TestName: "Invalid PostKubeadm hook", - Config: func() *Config { - cfg := newDefaultedConfig() + Node: func() Node { + cfg := newDefaultedNode(ControlPlaneRole) cfg.ControlPlane = &ControlPlane{ NodeLifecycle: &NodeLifecycle{ PostKubeadm: []LifecycleHook{ @@ -93,39 +93,134 @@ func TestConfigValidate(t *testing.T) { } return cfg }(), - ExpectedErrors: 1, + ExpectErrors: 1, }, { TestName: "Empty image field", - Config: func() *Config { - cfg := newDefaultedConfig() + Node: func() Node { + cfg := newDefaultedNode(ControlPlaneRole) cfg.Image = "" return cfg }(), - ExpectedErrors: 1, + ExpectErrors: 1, + }, + { + TestName: "Empty role field", + Node: func() Node { + cfg := newDefaultedNode(ControlPlaneRole) + cfg.Role = "" + return cfg + }(), + ExpectErrors: 1, + }, + { + TestName: "Unknows role field", + Node: func() Node { + cfg := newDefaultedNode(ControlPlaneRole) + cfg.Role = "ssss" + return cfg + }(), + ExpectErrors: 1, + }, + } + + for _, tc := range cases { + t.Run(tc.TestName, func(t2 *testing.T) { + err := tc.Node.Validate() + // the error can be: + // - nil, in which case we should expect no errors or fail + if err == nil { + if tc.ExpectErrors != 0 { + t2.Error("received no errors but expected errors for case") + } + return + } + // - not castable to *Errors, in which case we have the wrong error type ... + configErrors, ok := err.(util.Errors) + if !ok { + t2.Errorf("config.Validate should only return nil or ConfigErrors{...}, got: %v", err) + return + } + // - ConfigErrors, in which case expect a certain number of errors + errors := configErrors.Errors() + if len(errors) != tc.ExpectErrors { + t2.Errorf("expected %d errors but got len(%v) = %d", tc.ExpectErrors, errors, len(errors)) + } + }) + } +} + +func TestConfigValidate(t *testing.T) { + cases := []struct { + TestName string + Nodes []Node + ExpectErrors int + }{ + { + TestName: "Canonical config", + Nodes: []Node{ + newDefaultedNode(ControlPlaneRole), + }, + }, + { + TestName: "Fail without at least one control plane", + ExpectErrors: 1, + }, + { + TestName: "Fail without at load balancer and more than one control plane", + Nodes: []Node{ + newDefaultedNode(ControlPlaneRole), + newDefaultedNode(ControlPlaneRole), + }, + ExpectErrors: 1, + }, + { + TestName: "Fail with not valid nodes", + Nodes: []Node{ + func() Node { + cfg := newDefaultedNode(ControlPlaneRole) + cfg.Image = "" + return cfg + }(), + func() Node { + cfg := newDefaultedNode(ControlPlaneRole) + cfg.Role = "" + return cfg + }(), + }, + ExpectErrors: 2, }, } for _, tc := range cases { - err := tc.Config.Validate() - // the error can be: - // - nil, in which case we should expect no errors or fail - if err == nil { - if tc.ExpectedErrors != 0 { - t.Errorf("received no errors but expected errors for case %s", tc.TestName) + t.Run(tc.TestName, func(t2 *testing.T) { + var c = Config{Nodes: tc.Nodes} + if err := c.DeriveInfo(); err != nil { + t.Fatalf("unexpected error while adding nodes: %v", err) + } + + // validating config + err := c.Validate() + + // the error can be: + // - nil, in which case we should expect no errors or fail + if err == nil { + if tc.ExpectErrors != 0 { + t2.Error("received no errors but expected errors") + } + return + } + // - not castable to *Errors, in which case we have the wrong error type ... + configErrors, ok := err.(util.Errors) + if !ok { + t2.Errorf("config.Validate should only return nil or ConfigErrors{...}, got: %v", err) + return + } + // - ConfigErrors, in which case expect a certain number of errors + errors := configErrors.Errors() + if len(errors) != tc.ExpectErrors { + t2.Errorf("expected %d errors but got len(%v) = %d", tc.ExpectErrors, errors, len(errors)) } - continue - } - // - not castable to *Errors, in which case we have the wrong error type ... - configErrors, ok := err.(*util.Errors) - if !ok { - t.Errorf("config.Validate should only return nil or ConfigErrors{...}, got: %v for case: %s", err, tc.TestName) - continue - } - // - ConfigErrors, in which case expect a certain number of errors - errors := configErrors.Errors() - if len(errors) != tc.ExpectedErrors { - t.Errorf("expected %d errors but got len(%v) = %d for case: %s", tc.ExpectedErrors, errors, len(errors), tc.TestName) - } + }) } } diff --git a/pkg/cluster/context.go b/pkg/cluster/context.go index 81e6a3d7b4..dbb2dcb4e7 100644 --- a/pkg/cluster/context.go +++ b/pkg/cluster/context.go @@ -140,7 +140,11 @@ func (c *Context) Create(cfg *config.Config, retain bool, wait time.Duration) er cc.status.MaybeWrapLogrus(log.StandardLogger()) defer cc.status.End(false) - image := cfg.Image + + // TODO(fabrizio pandini): usage of BootStrapControlPlane() in this file is temporary / WIP + // kind v1alpha2 config fully supports multi nodes, but the cluster creation logic implemented in + // in this file does not (yet). + image := cfg.BootStrapControlPlane().Image if strings.Contains(image, "@sha256:") { image = strings.Split(image, "@sha256:")[0] } @@ -148,7 +152,7 @@ func (c *Context) Create(cfg *config.Config, retain bool, wait time.Duration) er // attempt to explicitly pull the image if it doesn't exist locally // we don't care if this errors, we'll still try to run which also pulls - _, _ = docker.PullIfNotPresent(cfg.Image, 4) + _, _ = docker.PullIfNotPresent(cfg.BootStrapControlPlane().Image, 4) // TODO(bentheelder): multiple nodes ... kubeadmConfig, err := cc.provisionControlPlane( @@ -200,7 +204,7 @@ func (cc *createContext) provisionControlPlane( ) (kubeadmConfigPath string, err error) { cc.status.Start(fmt.Sprintf("[%s] Creating node container 📦", nodeName)) // create the "node" container (docker run, but it is paused, see createNode) - node, port, err := nodes.CreateControlPlaneNode(nodeName, cc.config.Image, cc.ClusterLabel()) + node, port, err := nodes.CreateControlPlaneNode(nodeName, cc.config.BootStrapControlPlane().Image, cc.ClusterLabel()) if err != nil { return "", err } @@ -218,8 +222,8 @@ func (cc *createContext) provisionControlPlane( } // run any pre-boot hooks - if cc.config.ControlPlane != nil && cc.config.ControlPlane.NodeLifecycle != nil { - for _, hook := range cc.config.ControlPlane.NodeLifecycle.PreBoot { + if cc.config.BootStrapControlPlane().ControlPlane != nil && cc.config.BootStrapControlPlane().ControlPlane.NodeLifecycle != nil { + for _, hook := range cc.config.BootStrapControlPlane().ControlPlane.NodeLifecycle.PreBoot { if err := runHook(node, &hook, "preBoot"); err != nil { return "", err } @@ -285,8 +289,8 @@ func (cc *createContext) provisionControlPlane( } // run any pre-kubeadm hooks - if cc.config.ControlPlane != nil && cc.config.ControlPlane.NodeLifecycle != nil { - for _, hook := range cc.config.ControlPlane.NodeLifecycle.PreKubeadm { + if cc.config.BootStrapControlPlane().ControlPlane != nil && cc.config.BootStrapControlPlane().ControlPlane.NodeLifecycle != nil { + for _, hook := range cc.config.BootStrapControlPlane().ControlPlane.NodeLifecycle.PreKubeadm { if err := runHook(node, &hook, "preKubeadm"); err != nil { return kubeadmConfig, err } @@ -314,8 +318,8 @@ func (cc *createContext) provisionControlPlane( } // run any post-kubeadm hooks - if cc.config.ControlPlane != nil && cc.config.ControlPlane.NodeLifecycle != nil { - for _, hook := range cc.config.ControlPlane.NodeLifecycle.PostKubeadm { + if cc.config.BootStrapControlPlane().ControlPlane != nil && cc.config.BootStrapControlPlane().ControlPlane.NodeLifecycle != nil { + for _, hook := range cc.config.BootStrapControlPlane().ControlPlane.NodeLifecycle.PostKubeadm { if err := runHook(node, &hook, "postKubeadm"); err != nil { return kubeadmConfig, err } @@ -356,8 +360,8 @@ func (cc *createContext) provisionControlPlane( } // run any post-overlay hooks - if cc.config.ControlPlane != nil && cc.config.ControlPlane.NodeLifecycle != nil { - for _, hook := range cc.config.ControlPlane.NodeLifecycle.PostSetup { + if cc.config.BootStrapControlPlane().ControlPlane != nil && cc.config.BootStrapControlPlane().ControlPlane.NodeLifecycle != nil { + for _, hook := range cc.config.BootStrapControlPlane().ControlPlane.NodeLifecycle.PostSetup { if err := runHook(node, &hook, "postSetup"); err != nil { return kubeadmConfig, err } @@ -426,8 +430,8 @@ func (c *Context) createKubeadmConfig(cfg *config.Config, data kubeadm.ConfigDat // apply patches patchedConfig, err := kustomize.Build( []string{config}, - cfg.KubeadmConfigPatches, - cfg.KubeadmConfigPatchesJSON6902, + cfg.BootStrapControlPlane().KubeadmConfigPatches, + cfg.BootStrapControlPlane().KubeadmConfigPatchesJSON6902, ) if err != nil { os.Remove(path) From c8ff127d5ce05602e78c2ee6ba276407feb02182 Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Thu, 20 Dec 2018 22:25:53 +0100 Subject: [PATCH 2/2] autogenerated, deps --- Gopkg.lock | 9 + Gopkg.toml | 4 + .../v1alpha1/zz_generated.conversion.go | 35 ++- .../v1alpha2/zz_generated.conversion.go | 51 ++++- .../config/v1alpha2/zz_generated.deepcopy.go | 56 +++-- .../config/v1alpha2/zz_generated.default.go | 4 + pkg/cluster/config/zz_generated.deepcopy.go | 159 ++++++++++++-- vendor/k8s.io/utils/LICENSE | 202 ++++++++++++++++++ vendor/k8s.io/utils/pointer/pointer.go | 86 ++++++++ 9 files changed, 551 insertions(+), 55 deletions(-) create mode 100644 vendor/k8s.io/utils/LICENSE create mode 100644 vendor/k8s.io/utils/pointer/pointer.go diff --git a/Gopkg.lock b/Gopkg.lock index 57e0fe4ddb..1aa25c4221 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -697,6 +697,14 @@ pruneopts = "NUT" revision = "3a9b63ab1e397dc12a9764df998f99bc59dfd9ae" +[[projects]] + branch = "master" + digest = "1:ff54706d46de40c865b5fcfc4bde1087c02510cd12e0150de8e405ab427d9907" + name = "k8s.io/utils" + packages = ["pointer"] + pruneopts = "NUT" + revision = "0d26856f57b32ec3398579285e5c8a2bfe8c5243" + [[projects]] branch = "master" digest = "1:9dbda414956b2af5c2e24cbc54074ed7158902912bde8c0a0ec7f7344a309720" @@ -765,6 +773,7 @@ "k8s.io/code-generator/cmd/conversion-gen", "k8s.io/code-generator/cmd/deepcopy-gen", "k8s.io/code-generator/cmd/defaulter-gen", + "k8s.io/utils/pointer", "sigs.k8s.io/kustomize/k8sdeps", "sigs.k8s.io/kustomize/pkg/commands/build", "sigs.k8s.io/kustomize/pkg/fs", diff --git a/Gopkg.toml b/Gopkg.toml index c1d3fb5374..319d882178 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -52,3 +52,7 @@ required = [ name = "k8s.io/code-generator" branch = "master" + +[[constraint]] + branch = "master" + name = "k8s.io/utils" diff --git a/pkg/cluster/config/v1alpha1/zz_generated.conversion.go b/pkg/cluster/config/v1alpha1/zz_generated.conversion.go index f2b35f698a..dff2e73a5c 100644 --- a/pkg/cluster/config/v1alpha1/zz_generated.conversion.go +++ b/pkg/cluster/config/v1alpha1/zz_generated.conversion.go @@ -26,7 +26,6 @@ import ( conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" config "sigs.k8s.io/kind/pkg/cluster/config" - kustomize "sigs.k8s.io/kind/pkg/kustomize" ) func init() { @@ -76,35 +75,33 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*config.Config)(nil), (*Config)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_config_Config_To_v1alpha1_Config(a.(*config.Config), b.(*Config), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*Config)(nil), (*config.Config)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_Config_To_config_Config(a.(*Config), b.(*config.Config), scope) + }); err != nil { + return err + } return nil } func autoConvert_v1alpha1_Config_To_config_Config(in *Config, out *config.Config, s conversion.Scope) error { - out.Image = in.Image - out.KubeadmConfigPatches = *(*[]string)(unsafe.Pointer(&in.KubeadmConfigPatches)) - out.KubeadmConfigPatchesJSON6902 = *(*[]kustomize.PatchJSON6902)(unsafe.Pointer(&in.KubeadmConfigPatchesJSON6902)) - out.ControlPlane = (*config.ControlPlane)(unsafe.Pointer(in.ControlPlane)) + // WARNING: in.Image requires manual conversion: does not exist in peer-type + // WARNING: in.KubeadmConfigPatches requires manual conversion: does not exist in peer-type + // WARNING: in.KubeadmConfigPatchesJSON6902 requires manual conversion: does not exist in peer-type + // WARNING: in.ControlPlane requires manual conversion: does not exist in peer-type return nil } -// Convert_v1alpha1_Config_To_config_Config is an autogenerated conversion function. -func Convert_v1alpha1_Config_To_config_Config(in *Config, out *config.Config, s conversion.Scope) error { - return autoConvert_v1alpha1_Config_To_config_Config(in, out, s) -} - func autoConvert_config_Config_To_v1alpha1_Config(in *config.Config, out *Config, s conversion.Scope) error { - out.Image = in.Image - out.KubeadmConfigPatches = *(*[]string)(unsafe.Pointer(&in.KubeadmConfigPatches)) - out.KubeadmConfigPatchesJSON6902 = *(*[]kustomize.PatchJSON6902)(unsafe.Pointer(&in.KubeadmConfigPatchesJSON6902)) - out.ControlPlane = (*ControlPlane)(unsafe.Pointer(in.ControlPlane)) + // WARNING: in.Nodes requires manual conversion: does not exist in peer-type + // INFO: in.DerivedConfigData opted out of conversion generation return nil } -// Convert_config_Config_To_v1alpha1_Config is an autogenerated conversion function. -func Convert_config_Config_To_v1alpha1_Config(in *config.Config, out *Config, s conversion.Scope) error { - return autoConvert_config_Config_To_v1alpha1_Config(in, out, s) -} - func autoConvert_v1alpha1_ControlPlane_To_config_ControlPlane(in *ControlPlane, out *config.ControlPlane, s conversion.Scope) error { out.NodeLifecycle = (*config.NodeLifecycle)(unsafe.Pointer(in.NodeLifecycle)) return nil diff --git a/pkg/cluster/config/v1alpha2/zz_generated.conversion.go b/pkg/cluster/config/v1alpha2/zz_generated.conversion.go index ab004cf311..b30b5515fb 100644 --- a/pkg/cluster/config/v1alpha2/zz_generated.conversion.go +++ b/pkg/cluster/config/v1alpha2/zz_generated.conversion.go @@ -66,6 +66,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*Node)(nil), (*config.Node)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_Node_To_config_Node(a.(*Node), b.(*config.Node), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*config.Node)(nil), (*Node)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_config_Node_To_v1alpha2_Node(a.(*config.Node), b.(*Node), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*NodeLifecycle)(nil), (*config.NodeLifecycle)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha2_NodeLifecycle_To_config_NodeLifecycle(a.(*NodeLifecycle), b.(*config.NodeLifecycle), scope) }); err != nil { @@ -80,10 +90,7 @@ func RegisterConversions(s *runtime.Scheme) error { } func autoConvert_v1alpha2_Config_To_config_Config(in *Config, out *config.Config, s conversion.Scope) error { - out.Image = in.Image - out.KubeadmConfigPatches = *(*[]string)(unsafe.Pointer(&in.KubeadmConfigPatches)) - out.KubeadmConfigPatchesJSON6902 = *(*[]kustomize.PatchJSON6902)(unsafe.Pointer(&in.KubeadmConfigPatchesJSON6902)) - out.ControlPlane = (*config.ControlPlane)(unsafe.Pointer(in.ControlPlane)) + out.Nodes = *(*[]config.Node)(unsafe.Pointer(&in.Nodes)) return nil } @@ -93,10 +100,8 @@ func Convert_v1alpha2_Config_To_config_Config(in *Config, out *config.Config, s } func autoConvert_config_Config_To_v1alpha2_Config(in *config.Config, out *Config, s conversion.Scope) error { - out.Image = in.Image - out.KubeadmConfigPatches = *(*[]string)(unsafe.Pointer(&in.KubeadmConfigPatches)) - out.KubeadmConfigPatchesJSON6902 = *(*[]kustomize.PatchJSON6902)(unsafe.Pointer(&in.KubeadmConfigPatchesJSON6902)) - out.ControlPlane = (*ControlPlane)(unsafe.Pointer(in.ControlPlane)) + out.Nodes = *(*[]Node)(unsafe.Pointer(&in.Nodes)) + // INFO: in.DerivedConfigData opted out of conversion generation return nil } @@ -149,6 +154,36 @@ func Convert_config_LifecycleHook_To_v1alpha2_LifecycleHook(in *config.Lifecycle return autoConvert_config_LifecycleHook_To_v1alpha2_LifecycleHook(in, out, s) } +func autoConvert_v1alpha2_Node_To_config_Node(in *Node, out *config.Node, s conversion.Scope) error { + out.Replicas = (*int32)(unsafe.Pointer(in.Replicas)) + out.Role = config.NodeRole(in.Role) + out.Image = in.Image + out.KubeadmConfigPatches = *(*[]string)(unsafe.Pointer(&in.KubeadmConfigPatches)) + out.KubeadmConfigPatchesJSON6902 = *(*[]kustomize.PatchJSON6902)(unsafe.Pointer(&in.KubeadmConfigPatchesJSON6902)) + out.ControlPlane = (*config.ControlPlane)(unsafe.Pointer(in.ControlPlane)) + return nil +} + +// Convert_v1alpha2_Node_To_config_Node is an autogenerated conversion function. +func Convert_v1alpha2_Node_To_config_Node(in *Node, out *config.Node, s conversion.Scope) error { + return autoConvert_v1alpha2_Node_To_config_Node(in, out, s) +} + +func autoConvert_config_Node_To_v1alpha2_Node(in *config.Node, out *Node, s conversion.Scope) error { + out.Replicas = (*int32)(unsafe.Pointer(in.Replicas)) + out.Role = NodeRole(in.Role) + out.Image = in.Image + out.KubeadmConfigPatches = *(*[]string)(unsafe.Pointer(&in.KubeadmConfigPatches)) + out.KubeadmConfigPatchesJSON6902 = *(*[]kustomize.PatchJSON6902)(unsafe.Pointer(&in.KubeadmConfigPatchesJSON6902)) + out.ControlPlane = (*ControlPlane)(unsafe.Pointer(in.ControlPlane)) + return nil +} + +// Convert_config_Node_To_v1alpha2_Node is an autogenerated conversion function. +func Convert_config_Node_To_v1alpha2_Node(in *config.Node, out *Node, s conversion.Scope) error { + return autoConvert_config_Node_To_v1alpha2_Node(in, out, s) +} + func autoConvert_v1alpha2_NodeLifecycle_To_config_NodeLifecycle(in *NodeLifecycle, out *config.NodeLifecycle, s conversion.Scope) error { out.PreBoot = *(*[]config.LifecycleHook)(unsafe.Pointer(&in.PreBoot)) out.PreKubeadm = *(*[]config.LifecycleHook)(unsafe.Pointer(&in.PreKubeadm)) diff --git a/pkg/cluster/config/v1alpha2/zz_generated.deepcopy.go b/pkg/cluster/config/v1alpha2/zz_generated.deepcopy.go index 08cc10d471..5d41f916eb 100644 --- a/pkg/cluster/config/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/cluster/config/v1alpha2/zz_generated.deepcopy.go @@ -29,20 +29,12 @@ import ( func (in *Config) DeepCopyInto(out *Config) { *out = *in out.TypeMeta = in.TypeMeta - if in.KubeadmConfigPatches != nil { - in, out := &in.KubeadmConfigPatches, &out.KubeadmConfigPatches - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.KubeadmConfigPatchesJSON6902 != nil { - in, out := &in.KubeadmConfigPatchesJSON6902, &out.KubeadmConfigPatchesJSON6902 - *out = make([]kustomize.PatchJSON6902, len(*in)) - copy(*out, *in) - } - if in.ControlPlane != nil { - in, out := &in.ControlPlane, &out.ControlPlane - *out = new(ControlPlane) - (*in).DeepCopyInto(*out) + if in.Nodes != nil { + in, out := &in.Nodes, &out.Nodes + *out = make([]Node, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } return } @@ -107,6 +99,42 @@ func (in *LifecycleHook) DeepCopy() *LifecycleHook { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Node) DeepCopyInto(out *Node) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } + if in.KubeadmConfigPatches != nil { + in, out := &in.KubeadmConfigPatches, &out.KubeadmConfigPatches + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.KubeadmConfigPatchesJSON6902 != nil { + in, out := &in.KubeadmConfigPatchesJSON6902, &out.KubeadmConfigPatchesJSON6902 + *out = make([]kustomize.PatchJSON6902, len(*in)) + copy(*out, *in) + } + if in.ControlPlane != nil { + in, out := &in.ControlPlane, &out.ControlPlane + *out = new(ControlPlane) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Node. +func (in *Node) DeepCopy() *Node { + if in == nil { + return nil + } + out := new(Node) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NodeLifecycle) DeepCopyInto(out *NodeLifecycle) { *out = *in diff --git a/pkg/cluster/config/v1alpha2/zz_generated.default.go b/pkg/cluster/config/v1alpha2/zz_generated.default.go index 54567365ae..175007271e 100644 --- a/pkg/cluster/config/v1alpha2/zz_generated.default.go +++ b/pkg/cluster/config/v1alpha2/zz_generated.default.go @@ -34,4 +34,8 @@ func RegisterDefaults(scheme *runtime.Scheme) error { func SetObjectDefaults_Config(in *Config) { SetDefaults_Config(in) + for i := range in.Nodes { + a := &in.Nodes[i] + SetDefaults_Node(a) + } } diff --git a/pkg/cluster/config/zz_generated.deepcopy.go b/pkg/cluster/config/zz_generated.deepcopy.go index 17bec49089..bb06f85865 100644 --- a/pkg/cluster/config/zz_generated.deepcopy.go +++ b/pkg/cluster/config/zz_generated.deepcopy.go @@ -29,21 +29,14 @@ import ( func (in *Config) DeepCopyInto(out *Config) { *out = *in out.TypeMeta = in.TypeMeta - if in.KubeadmConfigPatches != nil { - in, out := &in.KubeadmConfigPatches, &out.KubeadmConfigPatches - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.KubeadmConfigPatchesJSON6902 != nil { - in, out := &in.KubeadmConfigPatchesJSON6902, &out.KubeadmConfigPatchesJSON6902 - *out = make([]kustomize.PatchJSON6902, len(*in)) - copy(*out, *in) - } - if in.ControlPlane != nil { - in, out := &in.ControlPlane, &out.ControlPlane - *out = new(ControlPlane) - (*in).DeepCopyInto(*out) + if in.Nodes != nil { + in, out := &in.Nodes, &out.Nodes + *out = make([]Node, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } + in.DerivedConfigData.DeepCopyInto(&out.DerivedConfigData) return } @@ -86,6 +79,65 @@ func (in *ControlPlane) DeepCopy() *ControlPlane { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DerivedConfigData) DeepCopyInto(out *DerivedConfigData) { + *out = *in + if in.allReplicas != nil { + in, out := &in.allReplicas, &out.allReplicas + *out = make(ReplicaList, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(NodeReplica) + (*in).DeepCopyInto(*out) + } + } + } + if in.controlPlanes != nil { + in, out := &in.controlPlanes, &out.controlPlanes + *out = make(ReplicaList, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(NodeReplica) + (*in).DeepCopyInto(*out) + } + } + } + if in.workers != nil { + in, out := &in.workers, &out.workers + *out = make(ReplicaList, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(NodeReplica) + (*in).DeepCopyInto(*out) + } + } + } + if in.externalEtcd != nil { + in, out := &in.externalEtcd, &out.externalEtcd + *out = new(NodeReplica) + (*in).DeepCopyInto(*out) + } + if in.externalLoadBalancer != nil { + in, out := &in.externalLoadBalancer, &out.externalLoadBalancer + *out = new(NodeReplica) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DerivedConfigData. +func (in *DerivedConfigData) DeepCopy() *DerivedConfigData { + if in == nil { + return nil + } + out := new(DerivedConfigData) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LifecycleHook) DeepCopyInto(out *LifecycleHook) { *out = *in @@ -107,6 +159,42 @@ func (in *LifecycleHook) DeepCopy() *LifecycleHook { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Node) DeepCopyInto(out *Node) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } + if in.KubeadmConfigPatches != nil { + in, out := &in.KubeadmConfigPatches, &out.KubeadmConfigPatches + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.KubeadmConfigPatchesJSON6902 != nil { + in, out := &in.KubeadmConfigPatchesJSON6902, &out.KubeadmConfigPatchesJSON6902 + *out = make([]kustomize.PatchJSON6902, len(*in)) + copy(*out, *in) + } + if in.ControlPlane != nil { + in, out := &in.ControlPlane, &out.ControlPlane + *out = new(ControlPlane) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Node. +func (in *Node) DeepCopy() *Node { + if in == nil { + return nil + } + out := new(Node) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NodeLifecycle) DeepCopyInto(out *NodeLifecycle) { *out = *in @@ -150,3 +238,46 @@ func (in *NodeLifecycle) DeepCopy() *NodeLifecycle { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeReplica) DeepCopyInto(out *NodeReplica) { + *out = *in + in.Node.DeepCopyInto(&out.Node) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeReplica. +func (in *NodeReplica) DeepCopy() *NodeReplica { + if in == nil { + return nil + } + out := new(NodeReplica) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in ReplicaList) DeepCopyInto(out *ReplicaList) { + { + in := &in + *out = make(ReplicaList, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(NodeReplica) + (*in).DeepCopyInto(*out) + } + } + return + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplicaList. +func (in ReplicaList) DeepCopy() ReplicaList { + if in == nil { + return nil + } + out := new(ReplicaList) + in.DeepCopyInto(out) + return *out +} diff --git a/vendor/k8s.io/utils/LICENSE b/vendor/k8s.io/utils/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/vendor/k8s.io/utils/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/k8s.io/utils/pointer/pointer.go b/vendor/k8s.io/utils/pointer/pointer.go new file mode 100644 index 0000000000..a11a540f46 --- /dev/null +++ b/vendor/k8s.io/utils/pointer/pointer.go @@ -0,0 +1,86 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pointer + +import ( + "fmt" + "reflect" +) + +// AllPtrFieldsNil tests whether all pointer fields in a struct are nil. This is useful when, +// for example, an API struct is handled by plugins which need to distinguish +// "no plugin accepted this spec" from "this spec is empty". +// +// This function is only valid for structs and pointers to structs. Any other +// type will cause a panic. Passing a typed nil pointer will return true. +func AllPtrFieldsNil(obj interface{}) bool { + v := reflect.ValueOf(obj) + if !v.IsValid() { + panic(fmt.Sprintf("reflect.ValueOf() produced a non-valid Value for %#v", obj)) + } + if v.Kind() == reflect.Ptr { + if v.IsNil() { + return true + } + v = v.Elem() + } + for i := 0; i < v.NumField(); i++ { + if v.Field(i).Kind() == reflect.Ptr && !v.Field(i).IsNil() { + return false + } + } + return true +} + +// Int32Ptr returns a pointer to an int32 +func Int32Ptr(i int32) *int32 { + return &i +} + +// Int64Ptr returns a pointer to an int64 +func Int64Ptr(i int64) *int64 { + return &i +} + +// Int32PtrDerefOr dereference the int32 ptr and returns it i not nil, +// else returns def. +func Int32PtrDerefOr(ptr *int32, def int32) int32 { + if ptr != nil { + return *ptr + } + return def +} + +// BoolPtr returns a pointer to a bool +func BoolPtr(b bool) *bool { + return &b +} + +// StringPtr returns a pointer to the passed string. +func StringPtr(s string) *string { + return &s +} + +// Float32Ptr returns a pointer to the passed float32. +func Float32Ptr(i float32) *float32 { + return &i +} + +// Float64Ptr returns a pointer to the passed float64. +func Float64Ptr(i float64) *float64 { + return &i +}