From 53d190b3afbd17b1cf3ded6805ed5f4eb2e87a8c Mon Sep 17 00:00:00 2001 From: Enxebre Date: Thu, 21 Feb 2019 11:02:34 +0100 Subject: [PATCH 1/2] Revendor for cluster-api-actuator-pkg --- Gopkg.lock | 24 +- Gopkg.toml | 5 + .../cluster-api-actuator-pkg/LICENSE | 201 ++++ .../pkg/e2e/openshift/main.go | 15 + .../pkg/e2e/openshift/suites/e2e.go | 76 ++ .../pkg/e2e/openshift/suites/smoke_suite.go | 941 ++++++++++++++++++ .../pkg/e2e/openshift/suites/utils.go | 169 ++++ .../v1alpha1/clusterautoscaler_types.go | 1 + .../v1alpha1/machineautoscaler_types.go | 2 +- .../v1alpha1/zz_generated.deepcopy.go | 7 +- 10 files changed, 1428 insertions(+), 13 deletions(-) create mode 100644 vendor/github.com/openshift/cluster-api-actuator-pkg/LICENSE create mode 100644 vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/main.go create mode 100644 vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/suites/e2e.go create mode 100644 vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/suites/smoke_suite.go create mode 100644 vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/suites/utils.go diff --git a/Gopkg.lock b/Gopkg.lock index bef313641..79ad98dd1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -375,15 +375,24 @@ revision = "91fca585a85b163ddfd119fd09c128c9feadddca" [[projects]] - digest = "1:88a77d15e6ccd75e18697c7b7c1a4956f601e3481ea26ddc67d78d25518a535e" + digest = "1:9fd11fe137591af5321a6e33c48e37eb1f320aeb3cb5bc92bf4e071a90198258" + name = "github.com/openshift/cluster-api-actuator-pkg" + packages = [ + "pkg/e2e/openshift", + "pkg/e2e/openshift/suites", + ] + pruneopts = "NUT" + revision = "2620fd63480ea1155c71de87a022f1f564f09ff2" + +[[projects]] + digest = "1:34e5ed50e072e876fd27372bec3cd4d22ff790153798bade2df855f0a603ae29" name = "github.com/openshift/cluster-autoscaler-operator" packages = [ "pkg/apis", "pkg/apis/autoscaling/v1alpha1", ] pruneopts = "NUT" - revision = "55d8a9017618d3ea22105068095813993b830df8" - version = "v0.0.0" + revision = "4bcabb30b24b7289ea2b935f5a9cf0b89784835d" [[projects]] branch = "master" @@ -1006,16 +1015,13 @@ "github.com/golang/glog", "github.com/openshift/api/config/v1", "github.com/openshift/client-go/config/clientset/versioned", - "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1", - "github.com/openshift/cluster-api/pkg/apis/cluster/v1alpha1", + "github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift", "github.com/openshift/cluster-api/pkg/apis/machine/v1beta1", "github.com/openshift/cluster-api/pkg/client/clientset_generated/clientset", "github.com/openshift/cluster-api/pkg/client/clientset_generated/clientset/scheme", "github.com/openshift/cluster-api/pkg/client/informers_generated/externalversions", "github.com/openshift/cluster-api/pkg/client/informers_generated/externalversions/machine/v1beta1", "github.com/openshift/cluster-api/pkg/client/listers_generated/machine/v1beta1", - "github.com/openshift/cluster-autoscaler-operator/pkg/apis", - "github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1", "github.com/openshift/cluster-version-operator/lib/resourceapply", "github.com/openshift/cluster-version-operator/lib/resourcemerge", "github.com/openshift/cluster-version-operator/lib/resourceread", @@ -1025,21 +1031,18 @@ "github.com/spf13/pflag", "k8s.io/api/apps/v1", "k8s.io/api/apps/v1beta2", - "k8s.io/api/batch/v1", "k8s.io/api/core/v1", "k8s.io/api/rbac/v1beta1", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1", "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset", "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme", "k8s.io/apimachinery/pkg/api/errors", - "k8s.io/apimachinery/pkg/api/resource", "k8s.io/apimachinery/pkg/apis/meta/v1", "k8s.io/apimachinery/pkg/labels", "k8s.io/apimachinery/pkg/runtime", "k8s.io/apimachinery/pkg/runtime/schema", "k8s.io/apimachinery/pkg/types", "k8s.io/apimachinery/pkg/util/runtime", - "k8s.io/apimachinery/pkg/util/sets", "k8s.io/apimachinery/pkg/util/uuid", "k8s.io/apimachinery/pkg/util/wait", "k8s.io/client-go/informers", @@ -1067,7 +1070,6 @@ "k8s.io/code-generator/cmd/lister-gen", "k8s.io/code-generator/cmd/openapi-gen", "k8s.io/gengo/args", - "k8s.io/utils/pointer", "sigs.k8s.io/controller-runtime/pkg/client", "sigs.k8s.io/controller-runtime/pkg/client/config", "sigs.k8s.io/controller-runtime/pkg/client/fake", diff --git a/Gopkg.toml b/Gopkg.toml index c12fc3166..f7b1b246e 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -8,6 +8,7 @@ required = [ "k8s.io/code-generator/cmd/informer-gen", "k8s.io/code-generator/cmd/openapi-gen", "k8s.io/gengo/args", + "github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift" ] [[constraint]] @@ -85,3 +86,7 @@ required = [ [[prune.project]] name = "k8s.io/code-generator" non-go = false + +[[constraint]] + name = "github.com/openshift/cluster-api-actuator-pkg" + revision = "2620fd63480ea1155c71de87a022f1f564f09ff2" diff --git a/vendor/github.com/openshift/cluster-api-actuator-pkg/LICENSE b/vendor/github.com/openshift/cluster-api-actuator-pkg/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/openshift/cluster-api-actuator-pkg/LICENSE @@ -0,0 +1,201 @@ + 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/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/main.go b/vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/main.go new file mode 100644 index 000000000..a3832beb2 --- /dev/null +++ b/vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "flag" + + "github.com/golang/glog" + "github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/suites" +) + +func main() { + flag.Parse() + if err := suites.Run(); err != nil { + glog.Fatal(err) + } +} diff --git a/vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/suites/e2e.go b/vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/suites/e2e.go new file mode 100644 index 000000000..f3c6777cf --- /dev/null +++ b/vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/suites/e2e.go @@ -0,0 +1,76 @@ +package suites + +import ( + "context" + "flag" + + "github.com/golang/glog" + osconfigv1 "github.com/openshift/api/config/v1" + mapiv1beta1 "github.com/openshift/cluster-api/pkg/apis/machine/v1beta1" + caov1alpha1 "github.com/openshift/cluster-autoscaler-operator/pkg/apis" + + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/runtime/signals" +) + +type testConfig struct { + client client.Client +} + +func newClient() (client.Client, error) { + // Get a config to talk to the apiserver + cfg, err := config.GetConfig() + if err != nil { + return nil, err + } + + return client.New(cfg, client.Options{}) +} + +func Run() error { + flag.Parse() + + if err := mapiv1beta1.AddToScheme(scheme.Scheme); err != nil { + glog.Fatal(err) + } + + if err := caov1alpha1.AddToScheme(scheme.Scheme); err != nil { + glog.Fatal(err) + } + + if err := osconfigv1.AddToScheme(scheme.Scheme); err != nil { + glog.Fatal(err) + } + + return runAllSuites(signals.SetupSignalHandler()) +} + +func runAllSuites(stopCh <-chan struct{}) error { + client, err := newClient() + if err != nil { + return err + } + testConfig := &testConfig{ + client: client, + } + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + select { + case <-stopCh: + cancel() + case <-ctx.Done(): + } + }() + + for _, tc := range smokeTestingSuite(testConfig) { + if err := tc.expect(ctx); err != nil { + glog.Errorf("FAIL: %v: %v", tc.desc, err) + return err + } + glog.Infof("PASS: %v", tc.desc) + } + return nil +} diff --git a/vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/suites/smoke_suite.go b/vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/suites/smoke_suite.go new file mode 100644 index 000000000..a407ada9c --- /dev/null +++ b/vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/suites/smoke_suite.go @@ -0,0 +1,941 @@ +package suites + +import ( + "errors" + "fmt" + "strings" + "time" + + "sigs.k8s.io/controller-runtime/pkg/client" + + "context" + + "github.com/golang/glog" + osconfigv1 "github.com/openshift/api/config/v1" + mapiv1beta1 "github.com/openshift/cluster-api/pkg/apis/machine/v1beta1" + caov1alpha1 "github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1" + cvoresourcemerge "github.com/openshift/cluster-version-operator/lib/resourcemerge" + kappsapi "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" +) + +const ( + waitShort = 1 * time.Minute + waitMedium = 3 * time.Minute + waitLong = 10 * time.Minute + namespace = "openshift-machine-api" + workerRoleLabel = "node-role.kubernetes.io/worker" +) + +func smokeTestingSuite(tc *testConfig) []struct { + expect func(ctx context.Context) error + desc string +} { + return []struct { + expect func(ctx context.Context) error + desc string + }{ + { + expect: tc.ExpectMachineAPIOperatorAvailable, + desc: "[openshift] Expect Machine API operator to be available", + }, + { + expect: tc.ExpectMachineAPIClusterOperatorStatusAvailable, + desc: "[openshift] Expect Machine API ClusterOperator status to be available", + }, + { + expect: tc.ExpectMachineAPIOperatorReconcileControllersDeployment, + desc: "[openshift] Expect Machine API Operator to reconcile controllers", + }, + { + expect: tc.ExpectClusterAutoscalerOperatorAvailable, + desc: "[openshift] Expect Cluster Autoscaler operator to be available", + }, + { + expect: tc.ExpectClusterAutoscalerClusterOperatorStatusAvailable, + desc: "[openshift] Expect Cluster Autoscaler ClusterOperator status to be available", + }, + { + expect: tc.ExpectAllMachinesLinkedToANode, + desc: "[openshift] Expect each machines linked to a node", + }, + { + expect: tc.ExpectAdditiveReconcileMachineTaints, + desc: "[openshift] Expect machines to propagate taints to node in an additive manner", + }, + { + expect: tc.ExpectNewNodeWhenDeletingMachine, + desc: "[openshift] Expect a new node to come up after deleting a machine from a MachineSet", + }, + { + expect: tc.ExpectAutoscalerScalesOut, + desc: "[openshift] Expect cluster to scale out when workload increases", + }, + { + expect: tc.ExpectNodeToBeDrainedBeforeMachineIsDeleted, + desc: "[openshift] Expect node to be drained before its machine is deleted", + }, + } +} + +func (tc *testConfig) ExpectMachineAPIOperatorAvailable(ctx context.Context) error { + name := "machine-api-operator" + key := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + d := &kappsapi.Deployment{} + + err := wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { + if err := tc.client.Get(context.TODO(), key, d); err != nil { + glog.Errorf("error querying api for Deployment object: %v, retrying...", err) + return false, nil + } + if d.Status.ReadyReplicas < 1 { + return false, nil + } + return true, nil + }) + return err +} + +func (tc *testConfig) ExpectMachineAPIClusterOperatorStatusAvailable(ctx context.Context) error { + name := "machine-api" + key := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + clusterOperator := &osconfigv1.ClusterOperator{} + + err := wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { + if err := tc.client.Get(context.TODO(), key, clusterOperator); err != nil { + glog.Errorf("error querying api for OperatorStatus object: %v, retrying...", err) + return false, nil + } + if available := cvoresourcemerge.FindOperatorStatusCondition(clusterOperator.Status.Conditions, osconfigv1.OperatorAvailable); available != nil { + if available.Status == osconfigv1.ConditionTrue { + return true, nil + } + } + return false, nil + }) + return err +} + +func (tc *testConfig) ExpectMachineAPIOperatorReconcileControllersDeployment(ctx context.Context) error { + key := types.NamespacedName{ + Namespace: namespace, + Name: "clusterapi-manager-controllers", + } + d := &kappsapi.Deployment{} + + glog.Info("Get deployment") + err := wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { + if err := tc.client.Get(context.TODO(), key, d); err != nil { + glog.Errorf("error querying api for Deployment object: %v, retrying...", err) + return false, nil + } + return true, nil + }) + if err != nil { + return err + } + + glog.Info("Delete deployment") + err = wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { + if err := tc.client.Delete(context.TODO(), d); err != nil { + glog.Errorf("error querying api for Deployment object: %v, retrying...", err) + return false, nil + } + return true, nil + }) + if err != nil { + return err + } + + glog.Info("Verify deployment is recreated") + err = wait.PollImmediate(1*time.Second, waitLong, func() (bool, error) { + if err := tc.client.Get(context.TODO(), key, d); err != nil { + glog.Errorf("error querying api for Deployment object: %v, retrying...", err) + return false, nil + } + if d.Status.ReadyReplicas < 1 || !d.DeletionTimestamp.IsZero() { + return false, nil + } + return true, nil + }) + return err +} + +func (tc *testConfig) ExpectClusterAutoscalerOperatorAvailable(ctx context.Context) error { + name := "cluster-autoscaler-operator" + key := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + d := &kappsapi.Deployment{} + + err := wait.PollImmediate(1*time.Second, 1*time.Minute, func() (bool, error) { + if err := tc.client.Get(context.TODO(), key, d); err != nil { + glog.Errorf("error querying api for Deployment object: %v, retrying...", err) + return false, nil + } + if d.Status.ReadyReplicas < 1 { + return false, nil + } + return true, nil + }) + return err +} + +func (tc *testConfig) ExpectClusterAutoscalerClusterOperatorStatusAvailable(ctx context.Context) error { + name := "cluster-autoscaler" + key := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + clusterOperator := &osconfigv1.ClusterOperator{} + + err := wait.PollImmediate(1*time.Second, 1*time.Minute, func() (bool, error) { + if err := tc.client.Get(context.TODO(), key, clusterOperator); err != nil { + glog.Errorf("error querying api for OperatorStatus object: %v, retrying...", err) + return false, nil + } + if cvoresourcemerge.IsOperatorStatusConditionTrue(clusterOperator.Status.Conditions, osconfigv1.OperatorAvailable) { + return true, nil + } + return false, nil + }) + return err +} + +func (tc *testConfig) ExpectAllMachinesLinkedToANode(ctx context.Context) error { + machineAnnotationKey := "machine.openshift.io/machine" + listOptions := client.ListOptions{ + Namespace: namespace, + } + machineList := mapiv1beta1.MachineList{} + nodeList := corev1.NodeList{} + + err := wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { + if err := tc.client.List(context.TODO(), &listOptions, &machineList); err != nil { + glog.Errorf("error querying api for machineList object: %v, retrying...", err) + return false, nil + } + if err := tc.client.List(context.TODO(), &listOptions, &nodeList); err != nil { + glog.Errorf("error querying api for nodeList object: %v, retrying...", err) + return false, nil + } + glog.Infof("Waiting for %d machines to become nodes", len(machineList.Items)) + return len(machineList.Items) == len(nodeList.Items), nil + }) + if err != nil { + return err + } + + return wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { + nodeNameToMachineAnnotation := make(map[string]string) + for _, node := range nodeList.Items { + nodeNameToMachineAnnotation[node.Name] = node.Annotations[machineAnnotationKey] + } + for _, machine := range machineList.Items { + if machine.Status.NodeRef == nil { + glog.Errorf("machine %s has no NodeRef, retrying...", machine.Name) + return false, nil + } + nodeName := machine.Status.NodeRef.Name + if nodeNameToMachineAnnotation[nodeName] != fmt.Sprintf("%s/%s", namespace, machine.Name) { + glog.Errorf("node name %s does not match expected machine name %s, retrying...", nodeName, machine.Name) + return false, nil + } + } + return true, nil + }) +} + +func (tc *testConfig) ExpectAdditiveReconcileMachineTaints(ctx context.Context) error { + glog.Info("Verify machine taints are getting applied to node") + listOptions := client.ListOptions{ + Namespace: namespace, + } + machineList := mapiv1beta1.MachineList{} + + if err := tc.client.List(context.TODO(), &listOptions, &machineList); err != nil { + return fmt.Errorf("error querying api for machineList object: %v", err) + + } + glog.Info("Got the machine list") + machine := machineList.Items[0] + if machine.Status.NodeRef == nil { + return fmt.Errorf("machine %s has no NodeRef", machine.Name) + } + glog.Infof("Got the machine, %s", machine.Name) + nodeName := machine.Status.NodeRef.Name + nodeKey := types.NamespacedName{ + Namespace: namespace, + Name: nodeName, + } + node := &corev1.Node{} + + if err := tc.client.Get(context.TODO(), nodeKey, node); err != nil { + return fmt.Errorf("error querying api for node object: %v", err) + } + glog.Infof("Got the node, %s, from machine, %s", node.Name, machine.Name) + nodeTaint := corev1.Taint{ + Key: "not-from-machine", + Value: "true", + Effect: corev1.TaintEffectNoSchedule, + } + node.Spec.Taints = []corev1.Taint{nodeTaint} + if err := tc.client.Update(context.TODO(), node); err != nil { + return fmt.Errorf("error updating node object with non-machine taint: %v", err) + } + glog.Info("Updated node object with taint") + machineTaint := corev1.Taint{ + Key: "from-machine", + Value: "true", + Effect: corev1.TaintEffectNoSchedule, + } + machine.Spec.Taints = []corev1.Taint{machineTaint} + if err := tc.client.Update(context.TODO(), &machine); err != nil { + return fmt.Errorf("error updating machine object with taint: %v", err) + } + glog.Info("Updated machine object with taint") + var expectedTaints = sets.NewString("not-from-machine", "from-machine") + err := wait.PollImmediate(1*time.Second, waitLong, func() (bool, error) { + if err := tc.client.Get(context.TODO(), nodeKey, node); err != nil { + glog.Errorf("error querying api for node object: %v", err) + return false, nil + } + glog.Info("Got the node again for verification of taints") + var observedTaints = sets.NewString() + for _, taint := range node.Spec.Taints { + observedTaints.Insert(taint.Key) + } + if expectedTaints.Difference(observedTaints).HasAny("not-from-machine", "from-machine") == false { + glog.Infof("expected : %v, observed %v , difference %v, ", expectedTaints, observedTaints, expectedTaints.Difference(observedTaints)) + return true, nil + } + glog.Infof("All expected taints not found on node. Missing: %v", expectedTaints.Difference(observedTaints)) + return false, nil + }) + return err +} + +func (tc *testConfig) ExpectNewNodeWhenDeletingMachine(ctx context.Context) error { + listOptions := client.ListOptions{ + Namespace: namespace, + } + machineList := mapiv1beta1.MachineList{} + nodeList := corev1.NodeList{} + + glog.Info("Get machineList") + err := wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { + if err := tc.client.List(context.TODO(), &listOptions, &machineList); err != nil { + glog.Errorf("error querying api for machineList object: %v, retrying...", err) + return false, nil + } + return true, nil + }) + if err != nil { + return err + } + + glog.Info("Get nodeList") + err = wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { + if err := tc.client.List(context.TODO(), &listOptions, &nodeList); err != nil { + glog.Errorf("error querying api for nodeList object: %v, retrying...", err) + return false, nil + } + return true, nil + }) + if err != nil { + return err + } + + clusterInitialTotalNodes := len(nodeList.Items) + clusterInitialTotalMachines := len(machineList.Items) + var triagedWorkerMachine mapiv1beta1.Machine + var triagedWorkerNode corev1.Node +MachineLoop: + for _, m := range machineList.Items { + if m.Labels["sigs.k8s.io/cluster-api-machine-role"] == "worker" { + for _, n := range nodeList.Items { + if m.Status.NodeRef == nil { + glog.Errorf("no NodeRef found in machine %v", m.Name) + return errors.New("no NodeRef found in machine") + } + if n.Name == m.Status.NodeRef.Name { + triagedWorkerMachine = m + triagedWorkerNode = n + break MachineLoop + } + } + } + } + + glog.Info("Delete machine") + err = wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { + if err := tc.client.Delete(context.TODO(), &triagedWorkerMachine); err != nil { + glog.Errorf("error querying api for Deployment object: %v, retrying...", err) + return false, nil + } + return true, nil + }) + if err != nil { + return err + } + + err = wait.PollImmediate(1*time.Second, waitMedium, func() (bool, error) { + if err := tc.client.List(context.TODO(), &listOptions, &machineList); err != nil { + glog.Errorf("error querying api for machineList object: %v, retrying...", err) + return false, nil + } + glog.Info("Expect new machine to come up") + return len(machineList.Items) == clusterInitialTotalMachines, nil + }) + if err != nil { + return err + } + + err = wait.PollImmediate(1*time.Second, waitLong, func() (bool, error) { + if err := tc.client.List(context.TODO(), &listOptions, &nodeList); err != nil { + glog.Errorf("error querying api for nodeList object: %v, retrying...", err) + return false, nil + } + glog.Info("Expect deleted machine node to go away") + for _, n := range nodeList.Items { + if n.Name == triagedWorkerNode.Name { + return false, nil + } + } + glog.Info("Expect new node to come up") + return len(nodeList.Items) == clusterInitialTotalNodes, nil + }) + if err != nil { + return err + } + return nil +} + +// ExpectAutoscalerScalesOut is an smoke test for the autoscaling feature +// Create a clusterAutoscaler object +// Create a machineAutoscaler object +// Create a workLoad to force autoscaling +// Validate the targeted machineSet scales out the field for the expected number of replicas +// Validate the number of nodes in the cluster is growing +// Delete the workLoad and so provoke scale down +// Validate the targeted machineSet scales down its replica count +// Validate the number of nodes scales down to the initial number before scale out +// Delete the machineAutoscaler object +// Delete the clusterAutoscaler object +// ExpectAutoscalerScalesOut is an smoke test for the autoscaling feature +// Create a clusterAutoscaler object +// Create a machineAutoscaler object +// Create a workLoad to force autoscaling +// Validate the targeted machineSet scales out the field for the expected number of replicas +// Validate the number of nodes in the cluster is growing +// Delete the workLoad and so provoke scale down +// Validate the targeted machineSet scales down its replica count +// Validate the number of nodes scales down to the initial number before scale out +// Delete the machineAutoscaler object +// Delete the clusterAutoscaler object +func (tc *testConfig) ExpectAutoscalerScalesOut(ctx context.Context) error { + listOptions := client.ListOptions{ + Namespace: namespace, + } + glog.Info("Get one machineSet") + machineSetList := mapiv1beta1.MachineSetList{} + if err := wait.PollImmediate(1*time.Second, waitMedium, func() (bool, error) { + if err := tc.client.List(ctx, &listOptions, &machineSetList); err != nil { + glog.Errorf("error querying api for nodeList object: %v, retrying...", err) + return false, ctx.Err() + } + return len(machineSetList.Items) > 0, nil + }); err != nil { + return err + } + + // When we add support for machineDeployments on the installer, cluster-autoscaler and cluster-autoscaler-operator + // we need to test against deployments instead so we skip this test. + targetMachineSet := machineSetList.Items[0] + if ownerReferences := targetMachineSet.GetOwnerReferences(); len(ownerReferences) > 0 { + glog.Infof("MachineSet %s is owned by a machineDeployment. Please run tests against machineDeployment instead", targetMachineSet.Name) + return nil + } + + glog.Infof("Create ClusterAutoscaler and MachineAutoscaler objects. Targeting machineSet %s", targetMachineSet.Name) + initialNumberOfReplicas := targetMachineSet.Spec.Replicas + clusterAutoscaler := caov1alpha1.ClusterAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + Namespace: namespace, + }, + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterAutoscaler", + APIVersion: "autoscaling.openshift.io/v1alpha1", + }, + Spec: caov1alpha1.ClusterAutoscalerSpec{ + ScaleDown: &caov1alpha1.ScaleDownConfig{ + Enabled: true, + DelayAfterAdd: "10s", + DelayAfterDelete: "10s", + DelayAfterFailure: "10s", + UnneededTime: "10s", + }, + }, + } + machineAutoscaler := caov1alpha1.MachineAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: fmt.Sprintf("autoscale-%s", targetMachineSet.Name), + Namespace: namespace, + }, + TypeMeta: metav1.TypeMeta{ + Kind: "MachineAutoscaler", + APIVersion: "autoscaling.openshift.io/v1alpha1", + }, + Spec: caov1alpha1.MachineAutoscalerSpec{ + MaxReplicas: 2, + MinReplicas: 1, + ScaleTargetRef: caov1alpha1.CrossVersionObjectReference{ + Name: targetMachineSet.Name, + Kind: "MachineSet", + APIVersion: "machine.openshift.io/v1beta1", + }, + }, + } + if err := wait.PollImmediate(1*time.Second, waitMedium, func() (bool, error) { + if err := tc.client.Create(ctx, &clusterAutoscaler); err != nil { + if !strings.Contains(err.Error(), "already exists") { + glog.Errorf("error querying api for clusterAutoscaler object: %v, retrying...", err) + return false, ctx.Err() + } + } + if err := tc.client.Create(ctx, &machineAutoscaler); err != nil { + if !strings.Contains(err.Error(), "already exists") { + glog.Errorf("error querying api for machineAutoscaler object: %v, retrying...", err) + return false, ctx.Err() + } + } + return true, nil + }); err != nil { + return err + } + + workLoad := newWorkLoad() + + // We want to clean up these objects on any subsequent error. + + defer func() { + if workLoad != nil { + cascadeDelete := metav1.DeletePropagationForeground + wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { + if err := tc.client.Delete(context.TODO(), workLoad, func(opt *client.DeleteOptions) { + opt.PropagationPolicy = &cascadeDelete + }); err != nil { + glog.Errorf("error querying api for workLoad object: %v, retrying...", err) + return false, nil + } + return true, nil + }) + glog.Info("Deleted workload object") + } + + wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { + if err := tc.client.Delete(context.TODO(), &machineAutoscaler); err != nil { + glog.Errorf("error querying api for machineAutoscaler object: %v, retrying...", err) + return false, nil + } + return true, nil + }) + glog.Info("Deleted machineAutoscaler object") + + wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { + if err := tc.client.Delete(context.TODO(), &clusterAutoscaler); err != nil { + glog.Errorf("error querying api for clusterAutoscaler object: %v, retrying...", err) + return false, nil + } + return true, nil + }) + glog.Info("Deleted clusterAutoscaler object") + }() + + glog.Info("Get nodeList") + nodeList := corev1.NodeList{} + if err := wait.PollImmediate(1*time.Second, waitMedium, func() (bool, error) { + if err := tc.client.List(ctx, &listOptions, &nodeList); err != nil { + glog.Errorf("error querying api for nodeList object: %v, retrying...", err) + return false, ctx.Err() + } + return true, nil + }); err != nil { + return err + } + + clusterInitialTotalNodes := len(nodeList.Items) + glog.Infof("Cluster initial number of nodes is %d", clusterInitialTotalNodes) + + glog.Info("Create workload") + + if err := wait.PollImmediate(1*time.Second, waitMedium, func() (bool, error) { + if err := tc.client.Create(ctx, workLoad); err != nil { + glog.Errorf("error querying api for workLoad object: %v, retrying...", err) + return false, ctx.Err() + } + return true, nil + }); err != nil { + return err + } + + glog.Info("Wait for cluster to scale out number of replicas") + if err := wait.PollImmediate(1*time.Second, waitLong, func() (bool, error) { + msKey := types.NamespacedName{ + Namespace: namespace, + Name: targetMachineSet.Name, + } + ms := &mapiv1beta1.MachineSet{} + if err := tc.client.Get(ctx, msKey, ms); err != nil { + glog.Errorf("error querying api for clusterAutoscaler object: %v, retrying...", err) + return false, ctx.Err() + } + glog.Infof("MachineSet %s. Initial number of replicas: %d. New number of replicas: %d", targetMachineSet.Name, *initialNumberOfReplicas, *ms.Spec.Replicas) + return *ms.Spec.Replicas > *initialNumberOfReplicas, nil + }); err != nil { + return err + } + + glog.Info("Wait for cluster to scale out nodes") + if err := wait.PollImmediate(1*time.Second, waitLong, func() (bool, error) { + nodeList := corev1.NodeList{} + if err := tc.client.List(ctx, &listOptions, &nodeList); err != nil { + glog.Errorf("error querying api for nodeList object: %v, retrying...", err) + return false, ctx.Err() + } + glog.Info("Expect at least a new node to come up") + glog.Infof("Initial number of nodes: %d. New number of nodes: %d", clusterInitialTotalNodes, len(nodeList.Items)) + return len(nodeList.Items) > clusterInitialTotalNodes, nil + }); err != nil { + return err + } + + glog.Info("Delete workload") + if err := wait.PollImmediate(1*time.Second, waitMedium, func() (bool, error) { + cascadeDelete := metav1.DeletePropagationForeground + if err := tc.client.Delete(ctx, workLoad, func(opt *client.DeleteOptions) { + opt.PropagationPolicy = &cascadeDelete + }); err != nil { + glog.Errorf("error querying api for workLoad object: %v, retrying...", err) + return false, ctx.Err() + } + workLoad = nil + return true, nil + }); err != nil { + return err + } + + // As we have just deleted the workload the autoscaler will + // start to scale down the unneeded nodes. We wait for that + // condition; if successful we assert that (a smoke test of) + // scale down is functional. + + glog.Info("Wait for cluster to match initial number of replicas") + if err := wait.PollImmediate(1*time.Second, waitLong, func() (bool, error) { + msKey := types.NamespacedName{ + Namespace: namespace, + Name: targetMachineSet.Name, + } + ms := &mapiv1beta1.MachineSet{} + if err := tc.client.Get(ctx, msKey, ms); err != nil { + glog.Errorf("error querying api for machineSet object: %v, retrying...", err) + return false, nil + } + glog.Infof("Initial number of replicas: %d. Current number of replicas: %d", *initialNumberOfReplicas, *ms.Spec.Replicas) + return *ms.Spec.Replicas == *initialNumberOfReplicas, nil + }); err != nil { + return err + } + + glog.Info("Wait for cluster to match initial number of nodes") + return wait.PollImmediate(1*time.Second, waitLong, func() (bool, error) { + nodeList := corev1.NodeList{} + if err := tc.client.List(ctx, &listOptions, &nodeList); err != nil { + glog.Errorf("error querying api for nodeList object: %v, retrying...", err) + return false, ctx.Err() + } + glog.Infof("Initial number of nodes: %d. Current number of nodes: %d", clusterInitialTotalNodes, len(nodeList.Items)) + return len(nodeList.Items) == clusterInitialTotalNodes, nil + }) +} + +func (tc *testConfig) ExpectNodeToBeDrainedBeforeMachineIsDeleted(ctx context.Context) error { + delObjects := make(map[string]runtime.Object) + + defer func() { + // Remove resources + for key := range delObjects { + glog.Infof("Deleting object %q", key) + if err := tc.client.Delete(context.TODO(), delObjects[key]); err != nil { + glog.Errorf("Unable to delete object %q: %v", key, err) + } + } + }() + + // Take the first worker machineset (assuming only worker machines are backed by machinesets) + machinesets := mapiv1beta1.MachineSetList{} + if err := wait.PollImmediate(1*time.Second, 1*time.Minute, func() (bool, error) { + if err := tc.client.List(context.TODO(), &client.ListOptions{}, &machinesets); err != nil { + glog.Errorf("Error querying api for machineset object: %v, retrying...", err) + return false, nil + } + if len(machinesets.Items) < 1 { + glog.Errorf("Expected at least one machineset, have none") + return false, nil + } + return true, nil + }); err != nil { + return err + } + + // Create two machines + machine1 := machineFromMachineset(&machinesets.Items[0]) + machine1.Name = "machine1" + + if err := tc.client.Create(context.TODO(), machine1); err != nil { + return fmt.Errorf("unable to create machine %q: %v", machine1.Name, err) + } + + delObjects["machine1"] = machine1 + + machine2 := machineFromMachineset(&machinesets.Items[0]) + machine2.Name = "machine2" + + if err := tc.client.Create(context.TODO(), machine2); err != nil { + return fmt.Errorf("unable to create machine %q: %v", machine2.Name, err) + } + + delObjects["machine2"] = machine2 + + // Wait until both new nodes are ready + if err := wait.PollImmediate(1*time.Second, 10*time.Minute, func() (bool, error) { + nodes := corev1.NodeList{} + listOpt := &client.ListOptions{} + listOpt.MatchingLabels(nodeDrainLabels) + if err := tc.client.List(context.TODO(), listOpt, &nodes); err != nil { + glog.Errorf("Error querying api for Node object: %v, retrying...", err) + return false, nil + } + // expecting nodeGroupSize nodes + nodeCounter := 0 + for _, node := range nodes.Items { + if _, exists := node.Labels[workerRoleLabel]; !exists { + continue + } + + if !isNodeReady(&node) { + continue + } + + nodeCounter++ + } + + if nodeCounter < 2 { + glog.Errorf("Expecting 2 nodes with %#v labels in Ready state, got %v", nodeDrainLabels, nodeCounter) + return false, nil + } + + glog.Infof("Expected number (2) of nodes with %v label in Ready state found", nodeDrainLabels) + return true, nil + }); err != nil { + return err + } + + rc := replicationControllerWorkload(namespace) + if err := tc.client.Create(context.TODO(), rc); err != nil { + return fmt.Errorf("unable to create RC %q: %v", rc.Name, err) + } + + delObjects["rc"] = rc + + pdb := podDisruptionBudget(namespace) + if err := tc.client.Create(context.TODO(), pdb); err != nil { + return fmt.Errorf("unable to create PDB %q: %v", pdb.Name, err) + } + + delObjects["pdb"] = pdb + + // Wait until all replicas are ready + if err := wait.PollImmediate(1*time.Second, 10*time.Minute, func() (bool, error) { + rcObj := corev1.ReplicationController{} + key := types.NamespacedName{ + Namespace: rc.Namespace, + Name: rc.Name, + } + if err := tc.client.Get(context.TODO(), key, &rcObj); err != nil { + glog.Errorf("Error querying api RC %q object: %v, retrying...", rc.Name, err) + return false, nil + } + if rcObj.Status.ReadyReplicas == 0 { + glog.Infof("Waiting for at least one RC ready replica (%v/%v)", rcObj.Status.ReadyReplicas, rcObj.Status.Replicas) + return false, nil + } + glog.Infof("Waiting for RC ready replicas (%v/%v)", rcObj.Status.ReadyReplicas, rcObj.Status.Replicas) + if rcObj.Status.Replicas != rcObj.Status.ReadyReplicas { + return false, nil + } + return true, nil + }); err != nil { + return err + } + + // All pods are distributed evenly among all nodes so it's fine to drain + // random node and observe reconciliation of pods on the other one. + if err := tc.client.Delete(context.TODO(), machine1); err != nil { + return fmt.Errorf("unable to delete machine %q: %v", machine1.Name, err) + } + + delete(delObjects, "machine1") + + // We still should be able to list the machine as until rc.replicas-1 are running on the other node + var drainedNodeName string + if err := wait.PollImmediate(1*time.Second, 10*time.Minute, func() (bool, error) { + machine := mapiv1beta1.Machine{} + + key := types.NamespacedName{ + Namespace: machine1.Namespace, + Name: machine1.Name, + } + if err := tc.client.Get(context.TODO(), key, &machine); err != nil { + glog.Errorf("Error querying api machine %q object: %v, retrying...", machine1.Name, err) + return false, nil + } + if machine.Status.NodeRef == nil || machine.Status.NodeRef.Kind != "Node" { + glog.Error("Machine %q not linked to a node", machine.Name) + } + + drainedNodeName = machine.Status.NodeRef.Name + node := corev1.Node{} + + if err := tc.client.Get(context.TODO(), types.NamespacedName{Name: drainedNodeName}, &node); err != nil { + glog.Errorf("Error querying api node %q object: %v, retrying...", drainedNodeName, err) + return false, nil + } + + if !node.Spec.Unschedulable { + glog.Errorf("Node %q is expected to be marked as unschedulable, it is not", node.Name) + return false, nil + } + + glog.Infof("Node %q is mark unschedulable as expected", node.Name) + + pods := corev1.PodList{} + listOpt := &client.ListOptions{} + listOpt.MatchingLabels(rc.Spec.Selector) + if err := tc.client.List(context.TODO(), listOpt, &pods); err != nil { + glog.Errorf("Error querying api for Pods object: %v, retrying...", err) + return false, nil + } + + // expecting nodeGroupSize nodes + podCounter := 0 + for _, pod := range pods.Items { + if pod.Spec.NodeName != machine.Status.NodeRef.Name { + continue + } + if !pod.DeletionTimestamp.IsZero() { + continue + } + podCounter++ + } + + glog.Infof("Have %v pods scheduled to node %q", podCounter, machine.Status.NodeRef.Name) + + // Verify we have enough pods running as well + rcObj := corev1.ReplicationController{} + key = types.NamespacedName{ + Namespace: rc.Namespace, + Name: rc.Name, + } + if err := tc.client.Get(context.TODO(), key, &rcObj); err != nil { + glog.Errorf("Error querying api RC %q object: %v, retrying...", rc.Name, err) + return false, nil + } + + // The point of the test is to make sure majority of the pods is rescheduled + // to other nodes. Pod disruption budget makes sure at most one pod + // owned by the RC is not Ready. So no need to test it. Though, usefull to have it printed. + glog.Infof("RC ReadyReplicas/Replicas: %v/%v", rcObj.Status.ReadyReplicas, rcObj.Status.Replicas) + + // This makes sure at most one replica is not ready + if rcObj.Status.Replicas-rcObj.Status.ReadyReplicas > 1 { + return false, fmt.Errorf("pod disruption budget not respecpted, node was not properly drained") + } + + // Depends on timing though a machine can be deleted even before there is only + // one pod left on the node (that is being evicted). + if podCounter > 2 { + glog.Infof("Expecting at most 2 pods to be scheduled to drained node %v, got %v", machine.Status.NodeRef.Name, podCounter) + return false, nil + } + + glog.Info("Expected result: all pods from the RC up to last one or two got scheduled to a different node while respecting PDB") + return true, nil + }); err != nil { + return err + } + + // Validate the machine is deleted + if err := wait.PollImmediate(5*time.Second, 1*time.Minute, func() (bool, error) { + machine := mapiv1beta1.Machine{} + + key := types.NamespacedName{ + Namespace: machine1.Namespace, + Name: machine1.Name, + } + err := tc.client.Get(context.TODO(), key, &machine) + if err == nil { + glog.Errorf("Machine %q not yet deleted", machine1.Name) + return false, nil + } + + if !strings.Contains(err.Error(), "not found") { + glog.Errorf("Error querying api machine %q object: %v, retrying...", machine1.Name, err) + return false, nil + } + + glog.Infof("Machine %q successfully deleted", machine1.Name) + return true, nil + }); err != nil { + return err + } + + // Validate underlying node is removed as well + if err := wait.PollImmediate(5*time.Second, waitLong, func() (bool, error) { + node := corev1.Node{} + + key := types.NamespacedName{ + Name: drainedNodeName, + } + err := tc.client.Get(context.TODO(), key, &node) + if err == nil { + glog.Errorf("Node %q not yet deleted", drainedNodeName) + return false, nil + } + + if !strings.Contains(err.Error(), "not found") { + glog.Errorf("Error querying api node %q object: %v, retrying...", drainedNodeName, err) + return false, nil + } + + glog.Infof("Node %q successfully deleted", drainedNodeName) + return true, nil + }); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/suites/utils.go b/vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/suites/utils.go new file mode 100644 index 000000000..94ede54d9 --- /dev/null +++ b/vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/suites/utils.go @@ -0,0 +1,169 @@ +package suites + +import ( + mapiv1beta1 "github.com/openshift/cluster-api/pkg/apis/machine/v1beta1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + kpolicyapi "k8s.io/api/policy/v1beta1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/uuid" +) + +var nodeDrainLabels = map[string]string{ + workerRoleLabel: "", + "node-draining-test": "", +} + +func newWorkLoad() *batchv1.Job { + backoffLimit := int32(4) + completions := int32(50) + parallelism := int32(50) + activeDeadlineSeconds := int64(100) + return &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "workload", + Namespace: namespace, + }, + TypeMeta: metav1.TypeMeta{ + Kind: "Job", + APIVersion: "batch/v1", + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "workload", + Image: "busybox", + Command: []string{ + "sleep", + "300", + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + "memory": resource.MustParse("500Mi"), + "cpu": resource.MustParse("500m"), + }, + }, + }, + }, + RestartPolicy: corev1.RestartPolicy("Never"), + NodeSelector: map[string]string{ + "node-role.kubernetes.io/worker": "", + }, + }, + }, + ActiveDeadlineSeconds: &activeDeadlineSeconds, + BackoffLimit: &backoffLimit, + Completions: &completions, + Parallelism: ¶llelism, + }, + } +} + +func machineFromMachineset(machineset *mapiv1beta1.MachineSet) *mapiv1beta1.Machine { + randomUUID := string(uuid.NewUUID()) + + machine := &mapiv1beta1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: machineset.Namespace, + Name: "machine-" + randomUUID[:6], + Labels: machineset.Labels, + }, + Spec: machineset.Spec.Template.Spec, + } + if machine.Spec.ObjectMeta.Labels == nil { + machine.Spec.ObjectMeta.Labels = map[string]string{} + } + for key := range nodeDrainLabels { + if _, exists := machine.Spec.ObjectMeta.Labels[key]; exists { + continue + } + machine.Spec.ObjectMeta.Labels[key] = nodeDrainLabels[key] + } + return machine +} + +func replicationControllerWorkload(namespace string) *corev1.ReplicationController { + var replicas int32 = 20 + return &corev1.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pdb-workload", + Namespace: namespace, + }, + Spec: corev1.ReplicationControllerSpec{ + Replicas: &replicas, + Selector: map[string]string{ + "app": "nginx", + }, + Template: &corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nginx", + Labels: map[string]string{ + "app": "nginx", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "work", + Image: "busybox", + Command: []string{"sleep", "10h"}, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + "cpu": resource.MustParse("50m"), + "memory": resource.MustParse("50Mi"), + }, + }, + }, + }, + NodeSelector: nodeDrainLabels, + Tolerations: []corev1.Toleration{ + { + Key: "kubemark", + Operator: corev1.TolerationOpExists, + }, + }, + }, + }, + }, + } +} + +func podDisruptionBudget(namespace string) *kpolicyapi.PodDisruptionBudget { + maxUnavailable := intstr.FromInt(1) + return &kpolicyapi.PodDisruptionBudget{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nginx-pdb", + Namespace: namespace, + }, + Spec: kpolicyapi.PodDisruptionBudgetSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "nginx", + }, + }, + MaxUnavailable: &maxUnavailable, + }, + } +} + +// 1. create two machines (without machineset) and wait until nodes are registered and ready +// 1. create rc +// 1. create pdb +// 1. pick a node that has at least half of the rc pods +// 1. drain node +// 1. observe the machine object is not deleted before a node is drained, +// i.e. as long as there is at least one pod running on the drained node, +// the machine object can not be deleted + +func isNodeReady(node *corev1.Node) bool { + for _, c := range node.Status.Conditions { + if c.Type == corev1.NodeReady { + return c.Status == corev1.ConditionTrue + } + } + return false +} diff --git a/vendor/github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1/clusterautoscaler_types.go b/vendor/github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1/clusterautoscaler_types.go index 08ccbd51f..633859e5d 100644 --- a/vendor/github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1/clusterautoscaler_types.go +++ b/vendor/github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1/clusterautoscaler_types.go @@ -64,4 +64,5 @@ type ScaleDownConfig struct { DelayAfterAdd string `json:"delayAfterAdd"` DelayAfterDelete string `json:"delayAfterDelete"` DelayAfterFailure string `json:"delayAfterFailure"` + UnneededTime string `json:"unneededTime,omitempty"` } diff --git a/vendor/github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1/machineautoscaler_types.go b/vendor/github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1/machineautoscaler_types.go index 2fd2b37c1..1fe7bbc1d 100644 --- a/vendor/github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1/machineautoscaler_types.go +++ b/vendor/github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1/machineautoscaler_types.go @@ -17,7 +17,7 @@ type MachineAutoscalerSpec struct { // MachineAutoscalerStatus defines the observed state of MachineAutoscaler type MachineAutoscalerStatus struct { - // TODO: Add status fields. + LastTargetRef *CrossVersionObjectReference `json:"lastTargetRef,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/vendor/github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1/zz_generated.deepcopy.go b/vendor/github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1/zz_generated.deepcopy.go index 9f71bbfdb..4ee19fff1 100644 --- a/vendor/github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1/zz_generated.deepcopy.go +++ b/vendor/github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1/zz_generated.deepcopy.go @@ -176,7 +176,7 @@ func (in *MachineAutoscaler) DeepCopyInto(out *MachineAutoscaler) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) out.Spec = in.Spec - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) return } @@ -251,6 +251,11 @@ func (in *MachineAutoscalerSpec) DeepCopy() *MachineAutoscalerSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MachineAutoscalerStatus) DeepCopyInto(out *MachineAutoscalerStatus) { *out = *in + if in.LastTargetRef != nil { + in, out := &in.LastTargetRef, &out.LastTargetRef + *out = new(CrossVersionObjectReference) + **out = **in + } return } From 93d62ea4a2ca753667cfc87ca1cefafe8a185c94 Mon Sep 17 00:00:00 2001 From: Enxebre Date: Thu, 21 Feb 2019 11:02:47 +0100 Subject: [PATCH 2/2] Run e2e tests from cluster-api-actuator-pkg --- Makefile | 2 +- test/e2e/main.go | 120 ---- test/e2e/operator_expectations.go | 1074 ----------------------------- 3 files changed, 1 insertion(+), 1195 deletions(-) delete mode 100644 test/e2e/main.go delete mode 100644 test/e2e/operator_expectations.go diff --git a/Makefile b/Makefile index c59235c85..350f54be4 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ build-integration: ## Build integration test binary $(DOCKER_CMD) go build $(GOGCFLAGS) -o bin/integration github.com/openshift/machine-api-operator/test/integration test-e2e: - go run ./test/e2e/*.go -alsologtostderr + go run ./vendor/github.com/openshift/cluster-api-actuator-pkg/pkg/e2e/openshift/*.go -alsologtostderr .PHONY: test test: ## Run tests diff --git a/test/e2e/main.go b/test/e2e/main.go deleted file mode 100644 index 5ffaa7fd7..000000000 --- a/test/e2e/main.go +++ /dev/null @@ -1,120 +0,0 @@ -package main - -import ( - "flag" - - "github.com/golang/glog" - osconfigv1 "github.com/openshift/api/config/v1" - mapiv1beta1 "github.com/openshift/cluster-api/pkg/apis/machine/v1beta1" - caov1alpha1 "github.com/openshift/cluster-autoscaler-operator/pkg/apis" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/config" -) - -const ( - namespace = "openshift-machine-api" -) - -func init() { - if err := mapiv1beta1.AddToScheme(scheme.Scheme); err != nil { - glog.Fatal(err) - } - - if err := osconfigv1.AddToScheme(scheme.Scheme); err != nil { - glog.Fatal(err) - } - - if err := caov1alpha1.AddToScheme(scheme.Scheme); err != nil { - glog.Fatal(err) - } -} - -type testConfig struct { - client client.Client -} - -func newClient() (client.Client, error) { - // Get a config to talk to the apiserver - cfg, err := config.GetConfig() - if err != nil { - return nil, err - } - - return client.New(cfg, client.Options{}) - -} - -func main() { - flag.Parse() - if err := runSuite(); err != nil { - glog.Fatal(err) - } -} - -func runSuite() error { - - client, err := newClient() - if err != nil { - return err - } - testConfig := &testConfig{ - client: client, - } - glog.Info("RUN: ExpectOperatorAvailable") - if err := testConfig.ExpectOperatorAvailable(); err != nil { - glog.Errorf("FAIL: ExpectOperatorAvailable: %v", err) - return err - } - glog.Info("PASS: ExpectOperatorAvailable") - - glog.Info("RUN: ExpectClusterOperatorStatusAvailable") - if err := testConfig.ExpectClusterOperatorStatusAvailable(); err != nil { - glog.Errorf("FAIL: ExpectClusterOperatorStatusAvailable: %v", err) - return err - } - glog.Info("PASS: ExpectClusterOperatorStatusAvailable") - - glog.Info("RUN: ExpectAllMachinesLinkedToANode") - if err := testConfig.ExpectAllMachinesLinkedToANode(); err != nil { - glog.Errorf("FAIL: ExpectAllMachinesLinkedToANode: %v", err) - return err - } - glog.Info("PASS: ExpectAllMachinesLinkedToANode") - - glog.Info("RUN: ExpectReconcileControllersDeployment") - if err := testConfig.ExpectReconcileControllersDeployment(); err != nil { - glog.Errorf("FAIL: ExpectReconcileControllersDeployment: %v", err) - return err - } - glog.Info("PASS: ExpectReconcileControllersDeployment") - - if err := testConfig.ExpectAdditiveReconcileMachineTaints(); err != nil { - glog.Errorf("FAIL: ExpectAdditiveReconcileMachineTaints: %v", err) - return err - } - glog.Info("PASS: ExpectAdditiveReconcileMachineTaints") - - glog.Info("RUN: ExpectNewNodeWhenDeletingMachine") - if err := testConfig.ExpectNewNodeWhenDeletingMachine(); err != nil { - glog.Errorf("FAIL: ExpectNewNodeWhenDeletingMachine: %v", err) - return err - } - glog.Info("PASS: ExpectNewNodeWhenDeletingMachine") - - glog.Info("RUN: ExpectAutoscalerScalesOut") - if err := testConfig.ExpectAutoscalerScalesOut(); err != nil { - glog.Errorf("FAIL: ExpectAutoscalerScalesOut: %v", err) - return err - } - glog.Info("PASS: ExpectAutoscalerScalesOut") - - glog.Info("RUN: ExpectNodeToBeDrainedBeforeMachineIsDeleted") - if err := testConfig.ExpectNodeToBeDrainedBeforeMachineIsDeleted(); err != nil { - glog.Errorf("FAIL: ExpectNodeToBeDrainedBeforeMachineIsDeleted: %v", err) - return err - } - glog.Info("PASS: ExpectNodeToBeDrainedBeforeMachineIsDeleted") - - return nil -} diff --git a/test/e2e/operator_expectations.go b/test/e2e/operator_expectations.go deleted file mode 100644 index 82d326333..000000000 --- a/test/e2e/operator_expectations.go +++ /dev/null @@ -1,1074 +0,0 @@ -package main - -import ( - "strings" - "time" - - "context" - "errors" - "fmt" - - "github.com/golang/glog" - osconfigv1 "github.com/openshift/api/config/v1" - mapiv1beta1 "github.com/openshift/cluster-api/pkg/apis/machine/v1beta1" - caov1alpha1 "github.com/openshift/cluster-autoscaler-operator/pkg/apis/autoscaling/v1alpha1" - cvoresourcemerge "github.com/openshift/cluster-version-operator/lib/resourcemerge" - kappsapi "k8s.io/api/apps/v1" - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - kpolicyapi "k8s.io/api/policy/v1beta1" - resource "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/uuid" - "k8s.io/apimachinery/pkg/util/wait" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - waitShort = 1 * time.Minute - waitMedium = 3 * time.Minute - waitLong = 15 * time.Minute - workerRoleLabel = "node-role.kubernetes.io/worker" -) - -func (tc *testConfig) ExpectOperatorAvailable() error { - name := "machine-api-operator" - key := types.NamespacedName{ - Namespace: namespace, - Name: name, - } - d := &kappsapi.Deployment{} - - err := wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { - if err := tc.client.Get(context.TODO(), key, d); err != nil { - glog.Errorf("error querying api for Deployment object: %v, retrying...", err) - return false, nil - } - if d.Status.ReadyReplicas < 1 { - return false, nil - } - return true, nil - }) - return err -} - -func (tc *testConfig) ExpectClusterOperatorStatusAvailable() error { - name := "machine-api" - key := types.NamespacedName{ - Namespace: namespace, - Name: name, - } - clusterOperator := &osconfigv1.ClusterOperator{} - - err := wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { - if err := tc.client.Get(context.TODO(), key, clusterOperator); err != nil { - glog.Errorf("error querying api for OperatorStatus object: %v, retrying...", err) - return false, nil - } - if available := cvoresourcemerge.FindOperatorStatusCondition(clusterOperator.Status.Conditions, osconfigv1.OperatorAvailable); available != nil { - if available.Status == osconfigv1.ConditionTrue { - return true, nil - } - } - return false, nil - }) - return err -} - -func (tc *testConfig) ExpectAllMachinesLinkedToANode() error { - machineAnnotationKey := "machine.openshift.io/machine" - listOptions := client.ListOptions{ - Namespace: namespace, - } - machineList := mapiv1beta1.MachineList{} - nodeList := corev1.NodeList{} - - err := wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { - if err := tc.client.List(context.TODO(), &listOptions, &machineList); err != nil { - glog.Errorf("error querying api for machineList object: %v, retrying...", err) - return false, nil - } - if err := tc.client.List(context.TODO(), &listOptions, &nodeList); err != nil { - glog.Errorf("error querying api for nodeList object: %v, retrying...", err) - return false, nil - } - glog.Infof("Expecting the same number of nodes and machines, have %v nodes and %v machines", len(nodeList.Items), len(machineList.Items)) - return len(machineList.Items) == len(nodeList.Items), nil - }) - if err != nil { - return err - } - - return wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { - nodeNameToMachineAnnotation := make(map[string]string) - for _, node := range nodeList.Items { - nodeNameToMachineAnnotation[node.Name] = node.Annotations[machineAnnotationKey] - } - for _, machine := range machineList.Items { - if machine.Status.NodeRef == nil { - glog.Errorf("machine %s has no NodeRef, retrying...", machine.Name) - return false, nil - } - nodeName := machine.Status.NodeRef.Name - if nodeNameToMachineAnnotation[nodeName] != fmt.Sprintf("%s/%s", namespace, machine.Name) { - glog.Errorf("node name %s does not match expected machine name %s, retrying...", nodeName, machine.Name) - return false, nil - } - } - return true, nil - }) -} - -func (tc *testConfig) ExpectReconcileControllersDeployment() error { - key := types.NamespacedName{ - Namespace: namespace, - Name: "clusterapi-manager-controllers", - } - d := &kappsapi.Deployment{} - - glog.Info("Get deployment") - err := wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { - if err := tc.client.Get(context.TODO(), key, d); err != nil { - glog.Errorf("error querying api for Deployment object: %v, retrying...", err) - return false, nil - } - return true, nil - }) - if err != nil { - return err - } - - glog.Info("Delete deployment") - err = wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { - if err := tc.client.Delete(context.TODO(), d); err != nil { - glog.Errorf("error querying api for Deployment object: %v, retrying...", err) - return false, nil - } - return true, nil - }) - if err != nil { - return err - } - - glog.Info("Verify deployment is recreated") - err = wait.PollImmediate(1*time.Second, waitLong, func() (bool, error) { - if err := tc.client.Get(context.TODO(), key, d); err != nil { - glog.Errorf("error querying api for Deployment object: %v, retrying...", err) - return false, nil - } - if d.Status.ReadyReplicas < 1 || !d.DeletionTimestamp.IsZero() { - return false, nil - } - return true, nil - }) - return err -} - -func (tc *testConfig) ExpectAdditiveReconcileMachineTaints() error { - glog.Info("Verify machine taints are getting applied to node") - listOptions := client.ListOptions{ - Namespace: namespace, - } - machineList := mapiv1beta1.MachineList{} - - if err := tc.client.List(context.TODO(), &listOptions, &machineList); err != nil { - return fmt.Errorf("error querying api for machineList object: %v", err) - - } - glog.Info("Got the machine list") - machine := machineList.Items[0] - if machine.Status.NodeRef == nil { - return fmt.Errorf("machine %s has no NodeRef", machine.Name) - } - glog.Infof("Got the machine, %s", machine.Name) - nodeName := machine.Status.NodeRef.Name - nodeKey := types.NamespacedName{ - Namespace: namespace, - Name: nodeName, - } - node := &corev1.Node{} - - if err := tc.client.Get(context.TODO(), nodeKey, node); err != nil { - return fmt.Errorf("error querying api for node object: %v", err) - } - glog.Infof("Got the node, %s, from machine, %s", node.Name, machine.Name) - nodeTaint := corev1.Taint{ - Key: "not-from-machine", - Value: "true", - Effect: corev1.TaintEffectNoSchedule, - } - node.Spec.Taints = []corev1.Taint{nodeTaint} - if err := tc.client.Update(context.TODO(), node); err != nil { - return fmt.Errorf("error updating node object with non-machine taint: %v", err) - } - glog.Info("Updated node object with taint") - machineTaint := corev1.Taint{ - Key: "from-machine", - Value: "true", - Effect: corev1.TaintEffectNoSchedule, - } - machine.Spec.Taints = []corev1.Taint{machineTaint} - if err := tc.client.Update(context.TODO(), &machine); err != nil { - return fmt.Errorf("error updating machine object with taint: %v", err) - } - glog.Info("Updated machine object with taint") - var expectedTaints = sets.NewString("not-from-machine", "from-machine") - err := wait.PollImmediate(1*time.Second, waitLong, func() (bool, error) { - if err := tc.client.Get(context.TODO(), nodeKey, node); err != nil { - glog.Errorf("error querying api for node object: %v", err) - return false, nil - } - glog.Info("Got the node again for verification of taints") - var observedTaints = sets.NewString() - for _, taint := range node.Spec.Taints { - observedTaints.Insert(taint.Key) - } - if expectedTaints.Difference(observedTaints).HasAny("not-from-machine", "from-machine") == false { - glog.Infof("expected : %v, observed %v , difference %v, ", expectedTaints, observedTaints, expectedTaints.Difference(observedTaints)) - return true, nil - } - glog.Infof("All expected taints not found on node. Missing: %v", expectedTaints.Difference(observedTaints)) - return false, nil - }) - return err -} - -func (tc *testConfig) ExpectNewNodeWhenDeletingMachine() error { - listOptions := client.ListOptions{ - Namespace: namespace, - } - machineList := mapiv1beta1.MachineList{} - nodeList := corev1.NodeList{} - - glog.Info("Get machineList") - err := wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { - if err := tc.client.List(context.TODO(), &listOptions, &machineList); err != nil { - glog.Errorf("error querying api for machineList object: %v, retrying...", err) - return false, nil - } - return true, nil - }) - if err != nil { - return err - } - - glog.Info("Get nodeList") - err = wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { - if err := tc.client.List(context.TODO(), &listOptions, &nodeList); err != nil { - glog.Errorf("error querying api for nodeList object: %v, retrying...", err) - return false, nil - } - return true, nil - }) - if err != nil { - return err - } - - clusterInitialTotalNodes := len(nodeList.Items) - clusterInitialTotalMachines := len(machineList.Items) - var triagedWorkerMachine mapiv1beta1.Machine - var triagedWorkerNode corev1.Node -MachineLoop: - for _, m := range machineList.Items { - if m.Labels["sigs.k8s.io/cluster-api-machine-role"] == "worker" { - for _, n := range nodeList.Items { - if m.Status.NodeRef == nil { - glog.Errorf("no NodeRef found in machine %v", m.Name) - return errors.New("no NodeRef found in machine") - } - if n.Name == m.Status.NodeRef.Name { - triagedWorkerMachine = m - triagedWorkerNode = n - break MachineLoop - } - } - } - } - - glog.Info("Delete machine") - err = wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { - if err := tc.client.Delete(context.TODO(), &triagedWorkerMachine); err != nil { - glog.Errorf("error querying api for Deployment object: %v, retrying...", err) - return false, nil - } - return true, nil - }) - if err != nil { - return err - } - - err = wait.PollImmediate(1*time.Second, waitMedium, func() (bool, error) { - if err := tc.client.List(context.TODO(), &listOptions, &machineList); err != nil { - glog.Errorf("error querying api for machineList object: %v, retrying...", err) - return false, nil - } - glog.Info("Expect new machine to come up") - return len(machineList.Items) == clusterInitialTotalMachines, nil - }) - if err != nil { - return err - } - - err = wait.PollImmediate(1*time.Second, waitLong, func() (bool, error) { - if err := tc.client.List(context.TODO(), &listOptions, &nodeList); err != nil { - glog.Errorf("error querying api for nodeList object: %v, retrying...", err) - return false, nil - } - glog.Info("Expect deleted machine node to go away") - for _, n := range nodeList.Items { - if n.Name == triagedWorkerNode.Name { - return false, nil - } - } - glog.Info("Expect new node to come up") - return len(nodeList.Items) == clusterInitialTotalNodes, nil - }) - if err != nil { - return err - } - return nil -} - -// ExpectAutoscalerScalesOut is an smoke test for the autoscaling feature -// Create a clusterAutoscaler object -// Create a machineAutoscaler object -// Create a workLoad to force autoscaling -// Validate the targeted machineSet scales out the field for the expected number of replicas -// Validate the number of nodes in the cluster is growing -// Delete the workLoad -// Delete the autoscaler object -// Ensure initial number of replicas and nodes -func (tc *testConfig) ExpectAutoscalerScalesOut() error { - listOptions := client.ListOptions{ - Namespace: namespace, - } - glog.Info("Get one machineSet") - machineSetList := mapiv1beta1.MachineSetList{} - err := wait.PollImmediate(1*time.Second, waitMedium, func() (bool, error) { - if err := tc.client.List(context.TODO(), &listOptions, &machineSetList); err != nil { - glog.Errorf("error querying api for nodeList object: %v, retrying...", err) - return false, nil - } - return len(machineSetList.Items) > 0, nil - }) - if err != nil { - return err - } - - // When we add support for machineDeployments on the installer, cluster-autoscaler and cluster-autoscaler-operator - // we need to test against deployments instead so we skip this test. - targetMachineSet := machineSetList.Items[0] - if ownerReferences := targetMachineSet.GetOwnerReferences(); len(ownerReferences) > 0 { - glog.Infof("MachineSet %s is owned by a machineDeployment. Please run tests agains machineDeployment instead", targetMachineSet.Name) - return nil - } - - glog.Infof("Create ClusterAutoscaler and MachineAutoscaler objects. Targeting machineSet %s", targetMachineSet.Name) - initialNumberOfReplicas := targetMachineSet.Spec.Replicas - clusterAutoscaler := caov1alpha1.ClusterAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default", - Namespace: namespace, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "ClusterAutoscaler", - APIVersion: "autoscaling.openshift.io/v1alpha1", - }, - } - machineAutoscaler := caov1alpha1.MachineAutoscaler{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: fmt.Sprintf("autoscale-%s", targetMachineSet.Name), - Namespace: namespace, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "MachineAutoscaler", - APIVersion: "autoscaling.openshift.io/v1alpha1", - }, - Spec: caov1alpha1.MachineAutoscalerSpec{ - MaxReplicas: 12, - MinReplicas: 1, - ScaleTargetRef: caov1alpha1.CrossVersionObjectReference{ - Name: targetMachineSet.Name, - Kind: "MachineSet", - APIVersion: "machine.openshift.io/v1beta1", - }, - }, - } - err = wait.PollImmediate(1*time.Second, waitMedium, func() (bool, error) { - if err := tc.client.Create(context.TODO(), &clusterAutoscaler); err != nil { - if !strings.Contains(err.Error(), "already exists") { - glog.Errorf("error querying api for clusterAutoscaler object: %v, retrying...", err) - return false, nil - } - } - if err := tc.client.Create(context.TODO(), &machineAutoscaler); err != nil { - if !strings.Contains(err.Error(), "already exists") { - glog.Errorf("error querying api for machineAutoscaler object: %v, retrying...", err) - return false, nil - } - } - return true, nil - }) - if err != nil { - return err - } - - glog.Info("Get nodeList") - nodeList := corev1.NodeList{} - err = wait.PollImmediate(1*time.Second, waitMedium, func() (bool, error) { - if err := tc.client.List(context.TODO(), &listOptions, &nodeList); err != nil { - glog.Errorf("error querying api for nodeList object: %v, retrying...", err) - return false, nil - } - return true, nil - }) - if err != nil { - return err - } - clusterInitialTotalNodes := len(nodeList.Items) - glog.Infof("Cluster initial number of nodes is %d", clusterInitialTotalNodes) - - glog.Info("Create workload") - mem, err := resource.ParseQuantity("500Mi") - if err != nil { - glog.Fatalf("failed to ParseQuantity %v", err) - } - cpu, err := resource.ParseQuantity("500m") - if err != nil { - glog.Fatalf("failed to ParseQuantity %v", err) - } - backoffLimit := int32(4) - completions := int32(50) - parallelism := int32(50) - activeDeadlineSeconds := int64(100) - workLoad := batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: "workload", - Namespace: namespace, - }, - TypeMeta: metav1.TypeMeta{ - Kind: "Job", - APIVersion: "batch/v1", - }, - Spec: batchv1.JobSpec{ - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "workload", - Image: "busybox", - Command: []string{ - "sleep", - "300", - }, - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - "memory": mem, - "cpu": cpu, - }, - }, - }, - }, - RestartPolicy: corev1.RestartPolicy("Never"), - }, - }, - ActiveDeadlineSeconds: &activeDeadlineSeconds, - BackoffLimit: &backoffLimit, - Completions: &completions, - Parallelism: ¶llelism, - }, - } - err = wait.PollImmediate(1*time.Second, waitMedium, func() (bool, error) { - if err := tc.client.Create(context.TODO(), &workLoad); err != nil { - glog.Errorf("error querying api for workLoad object: %v, retrying...", err) - return false, nil - } - return true, nil - }) - if err != nil { - return err - } - - glog.Info("Wait for cluster to scale out number of replicas") - err = wait.PollImmediate(1*time.Second, waitLong, func() (bool, error) { - msKey := types.NamespacedName{ - Namespace: namespace, - Name: targetMachineSet.Name, - } - ms := &mapiv1beta1.MachineSet{} - if err := tc.client.Get(context.TODO(), msKey, ms); err != nil { - glog.Errorf("error querying api for clusterAutoscaler object: %v, retrying...", err) - return false, nil - } - glog.Infof("MachineSet %s. Initial number of replicas: %d. New number of replicas: %d", targetMachineSet.Name, *initialNumberOfReplicas, *ms.Spec.Replicas) - return *ms.Spec.Replicas > *initialNumberOfReplicas, nil - }) - if err != nil { - return err - } - - glog.Info("Wait for cluster to scale out nodes") - err = wait.PollImmediate(1*time.Second, waitLong, func() (bool, error) { - nodeList := corev1.NodeList{} - if err := tc.client.List(context.TODO(), &listOptions, &nodeList); err != nil { - glog.Errorf("error querying api for nodeList object: %v, retrying...", err) - return false, nil - } - glog.Info("Expect at least a new node to come up") - glog.Infof("Initial number of nodes: %d. New number of nodes: %d", clusterInitialTotalNodes, len(nodeList.Items)) - return len(nodeList.Items) > clusterInitialTotalNodes, nil - }) - - glog.Info("Delete workload") - err = wait.PollImmediate(1*time.Second, waitMedium, func() (bool, error) { - if err := tc.client.Delete(context.TODO(), &workLoad); err != nil { - glog.Errorf("error querying api for workLoad object: %v, retrying...", err) - return false, nil - } - return true, nil - }) - if err != nil { - return err - } - - // We delete the clusterAutoscaler and ensure the initial number of replicas to get the cluster to the initial number of nodes - // TODO: validate the autoscaler to scale down - glog.Info("Delete clusterAutoscaler object") - err = wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { - if err := tc.client.Delete(context.TODO(), &clusterAutoscaler); err != nil { - glog.Errorf("error querying api for clusterAutoscaler object: %v, retrying...", err) - return false, nil - } - return true, nil - }) - if err != nil { - return err - } - - glog.Info("Delete machineAutoscaler object") - err = wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { - if err := tc.client.Delete(context.TODO(), &machineAutoscaler); err != nil { - glog.Errorf("error querying api for machineAutoscaler object: %v, retrying...", err) - return false, nil - } - return true, nil - }) - if err != nil { - return err - } - - glog.Infof("Ensure initial number of replicas: %d", initialNumberOfReplicas) - err = wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { - msKey := types.NamespacedName{ - Namespace: namespace, - Name: targetMachineSet.Name, - } - ms := &mapiv1beta1.MachineSet{} - if err := tc.client.Get(context.TODO(), msKey, ms); err != nil { - glog.Errorf("error querying api for machineSet object: %v, retrying...", err) - return false, nil - } - ms.Spec.Replicas = initialNumberOfReplicas - if err := tc.client.Update(context.TODO(), ms); err != nil { - glog.Errorf("error querying api for machineSet object: %v, retrying...", err) - return false, nil - } - return true, nil - }) - if err != nil { - return err - } - - glog.Info("Wait for cluster to match initial number of nodes") - return wait.PollImmediate(1*time.Second, waitLong, func() (bool, error) { - nodeList := corev1.NodeList{} - if err := tc.client.List(context.TODO(), &listOptions, &nodeList); err != nil { - glog.Errorf("error querying api for nodeList object: %v, retrying...", err) - return false, nil - } - glog.Infof("Initial number of nodes: %d. Current number of nodes: %d", clusterInitialTotalNodes, len(nodeList.Items)) - return len(nodeList.Items) == clusterInitialTotalNodes, nil - }) -} - -func (tc *testConfig) ExpectNodeToBeDrainedBeforeDeletingMachine() error { - listOptions := client.ListOptions{ - Namespace: namespace, - } - - var machine mapiv1beta1.Machine - var nodeName string - var node *corev1.Node - - glog.Info("Get machineList with at least one machine with NodeRef set") - if err := wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { - machineList := mapiv1beta1.MachineList{} - if err := tc.client.List(context.TODO(), &listOptions, &machineList); err != nil { - glog.Errorf("error querying api for machineList object: %v, retrying...", err) - return false, nil - } - for _, machineItem := range machineList.Items { - // empty or non-worker role skipped - if machineItem.Labels["sigs.k8s.io/cluster-api-machine-role"] == "worker" { - if machineItem.Status.NodeRef != nil && machineItem.Status.NodeRef.Name != "" { - machine = machineItem - nodeName = machineItem.Status.NodeRef.Name - return true, nil - } - } - } - return false, fmt.Errorf("no machine found with NodeRef not set") - }); err != nil { - return err - } - - glog.Info("Get nodeList") - if err := wait.PollImmediate(1*time.Second, waitShort, func() (bool, error) { - nodeList := corev1.NodeList{} - if err := tc.client.List(context.TODO(), &listOptions, &nodeList); err != nil { - glog.Errorf("error querying api for nodeList object: %v, retrying...", err) - return false, nil - } - for _, nodeItem := range nodeList.Items { - if nodeItem.Name == nodeName { - node = &nodeItem - break - } - } - if node == nil { - return false, fmt.Errorf("node %q not found", nodeName) - } - return true, nil - }); err != nil { - return err - } - - glog.Info("Delete machine and observe node draining") - if err := tc.client.Delete(context.TODO(), &machine); err != nil { - return fmt.Errorf("unable to delete machine %q", machine.Name) - } - - return wait.PollImmediate(time.Second, waitShort, func() (bool, error) { - eventList := corev1.EventList{} - if err := tc.client.List(context.TODO(), &listOptions, &eventList); err != nil { - glog.Errorf("error querying api for eventList object: %v, retrying...", err) - return false, nil - } - - glog.Infof("Fetching delete machine and node drained events") - var nodeDrainedEvent *corev1.Event - var machineDeletedEvent *corev1.Event - for _, eventItem := range eventList.Items { - if eventItem.Reason == "Deleted" && eventItem.Message == fmt.Sprintf("Node %q drained", nodeName) { - nodeDrainedEvent = &eventItem - continue - } - // always take the newest 'machine deleted' event - if eventItem.Reason == "Deleted" && eventItem.Message == fmt.Sprintf("Deleted machine %v", machine.Name) { - machineDeletedEvent = &eventItem - } - } - - if nodeDrainedEvent == nil { - glog.Infof("Unable to find %q node drained event", nodeName) - return false, nil - } - - if machineDeletedEvent == nil { - glog.Infof("Unable to find %q machine deleted event", machine.Name) - return false, nil - } - - glog.Infof("Node %q drained event recorded: %#v", nodeName, *nodeDrainedEvent) - - if machineDeletedEvent.FirstTimestamp.Before(&nodeDrainedEvent.FirstTimestamp) { - err := fmt.Errorf("machine %q deleted before node %q got drained", machine.Name, nodeName) - glog.Error(err) - return true, err - } - - return true, nil - }) -} - -var nodeDrainLabels = map[string]string{ - workerRoleLabel: "", - "node-draining-test": "", -} - -func machineFromMachineset(machineset *mapiv1beta1.MachineSet) *mapiv1beta1.Machine { - randomUUID := string(uuid.NewUUID()) - - machine := &mapiv1beta1.Machine{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: machineset.Namespace, - Name: "machine-" + randomUUID[:6], - Labels: machineset.Labels, - }, - Spec: machineset.Spec.Template.Spec, - } - if machine.Spec.ObjectMeta.Labels == nil { - machine.Spec.ObjectMeta.Labels = map[string]string{} - } - for key := range nodeDrainLabels { - if _, exists := machine.Spec.ObjectMeta.Labels[key]; exists { - continue - } - machine.Spec.ObjectMeta.Labels[key] = nodeDrainLabels[key] - } - return machine -} - -func replicationControllerWorkload(namespace string) *corev1.ReplicationController { - var replicas int32 = 20 - return &corev1.ReplicationController{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pdb-workload", - Namespace: namespace, - }, - Spec: corev1.ReplicationControllerSpec{ - Replicas: &replicas, - Selector: map[string]string{ - "app": "nginx", - }, - Template: &corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nginx", - Labels: map[string]string{ - "app": "nginx", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "work", - Image: "busybox", - Command: []string{"sleep", "10h"}, - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - "cpu": resource.MustParse("50m"), - "memory": resource.MustParse("50Mi"), - }, - }, - }, - }, - NodeSelector: nodeDrainLabels, - Tolerations: []corev1.Toleration{ - { - Key: "kubemark", - Operator: corev1.TolerationOpExists, - }, - }, - }, - }, - }, - } -} - -func podDisruptionBudget(namespace string) *kpolicyapi.PodDisruptionBudget { - maxUnavailable := intstr.FromInt(1) - return &kpolicyapi.PodDisruptionBudget{ - ObjectMeta: metav1.ObjectMeta{ - Name: "nginx-pdb", - Namespace: namespace, - }, - Spec: kpolicyapi.PodDisruptionBudgetSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": "nginx", - }, - }, - MaxUnavailable: &maxUnavailable, - }, - } -} - -// 1. create two machines (without machineset) and wait until nodes are registered and ready -// 1. create rc -// 1. create pdb -// 1. pick a node that has at least half of the rc pods -// 1. drain node -// 1. observe the machine object is not deleted before a node is drained, -// i.e. as long as there is at least one pod running on the drained node, -// the machine object can not be deleted - -func isNodeReady(node *corev1.Node) bool { - for _, c := range node.Status.Conditions { - if c.Type == corev1.NodeReady { - return c.Status == corev1.ConditionTrue - } - } - return false -} - -func (tc *testConfig) ExpectNodeToBeDrainedBeforeMachineIsDeleted() error { - delObjects := make(map[string]runtime.Object) - - defer func() { - // Remove resources - for key := range delObjects { - glog.Infof("Deleting object %q", key) - if err := tc.client.Delete(context.TODO(), delObjects[key]); err != nil { - glog.Errorf("Unable to delete object %q: %v", key, err) - } - } - }() - - // Take the first worker machineset (assuming only worker machines are backed by machinesets) - machinesets := mapiv1beta1.MachineSetList{} - if err := wait.PollImmediate(1*time.Second, 1*time.Minute, func() (bool, error) { - if err := tc.client.List(context.TODO(), &client.ListOptions{}, &machinesets); err != nil { - glog.Errorf("Error querying api for machineset object: %v, retrying...", err) - return false, nil - } - if len(machinesets.Items) < 1 { - glog.Errorf("Expected at least one machineset, have none") - return false, nil - } - return true, nil - }); err != nil { - return err - } - - // Create two machines - machine1 := machineFromMachineset(&machinesets.Items[0]) - machine1.Name = "machine1" - - if err := tc.client.Create(context.TODO(), machine1); err != nil { - return fmt.Errorf("unable to create machine %q: %v", machine1.Name, err) - } - - delObjects["machine1"] = machine1 - - machine2 := machineFromMachineset(&machinesets.Items[0]) - machine2.Name = "machine2" - - if err := tc.client.Create(context.TODO(), machine2); err != nil { - return fmt.Errorf("unable to create machine %q: %v", machine2.Name, err) - } - - delObjects["machine2"] = machine2 - - // Wait until both new nodes are ready - if err := wait.PollImmediate(1*time.Second, 10*time.Minute, func() (bool, error) { - nodes := corev1.NodeList{} - listOpt := &client.ListOptions{} - listOpt.MatchingLabels(nodeDrainLabels) - if err := tc.client.List(context.TODO(), listOpt, &nodes); err != nil { - glog.Errorf("Error querying api for Node object: %v, retrying...", err) - return false, nil - } - // expecting nodeGroupSize nodes - nodeCounter := 0 - for _, node := range nodes.Items { - if _, exists := node.Labels[workerRoleLabel]; !exists { - continue - } - - if !isNodeReady(&node) { - continue - } - - nodeCounter++ - } - - if nodeCounter < 2 { - glog.Errorf("Expecting 2 nodes with %#v labels in Ready state, got %v", nodeDrainLabels, nodeCounter) - return false, nil - } - - glog.Infof("Expected number (2) of nodes with %v label in Ready state found", nodeDrainLabels) - return true, nil - }); err != nil { - return err - } - - rc := replicationControllerWorkload(namespace) - if err := tc.client.Create(context.TODO(), rc); err != nil { - return fmt.Errorf("unable to create RC %q: %v", rc.Name, err) - } - - delObjects["rc"] = rc - - pdb := podDisruptionBudget(namespace) - if err := tc.client.Create(context.TODO(), pdb); err != nil { - return fmt.Errorf("unable to create PDB %q: %v", pdb.Name, err) - } - - delObjects["pdb"] = pdb - - // Wait until all replicas are ready - if err := wait.PollImmediate(1*time.Second, 10*time.Minute, func() (bool, error) { - rcObj := corev1.ReplicationController{} - key := types.NamespacedName{ - Namespace: rc.Namespace, - Name: rc.Name, - } - if err := tc.client.Get(context.TODO(), key, &rcObj); err != nil { - glog.Errorf("Error querying api RC %q object: %v, retrying...", rc.Name, err) - return false, nil - } - if rcObj.Status.ReadyReplicas == 0 { - glog.Infof("Waiting for at least one RC ready replica (%v/%v)", rcObj.Status.ReadyReplicas, rcObj.Status.Replicas) - return false, nil - } - glog.Infof("Waiting for RC ready replicas (%v/%v)", rcObj.Status.ReadyReplicas, rcObj.Status.Replicas) - if rcObj.Status.Replicas != rcObj.Status.ReadyReplicas { - return false, nil - } - return true, nil - }); err != nil { - return err - } - - // All pods are distributed evenly among all nodes so it's fine to drain - // random node and observe reconciliation of pods on the other one. - if err := tc.client.Delete(context.TODO(), machine1); err != nil { - return fmt.Errorf("unable to delete machine %q: %v", machine1.Name, err) - } - - delete(delObjects, "machine1") - - // We still should be able to list the machine as until rc.replicas-1 are running on the other node - var drainedNodeName string - if err := wait.PollImmediate(1*time.Second, 10*time.Minute, func() (bool, error) { - machine := mapiv1beta1.Machine{} - - key := types.NamespacedName{ - Namespace: machine1.Namespace, - Name: machine1.Name, - } - if err := tc.client.Get(context.TODO(), key, &machine); err != nil { - glog.Errorf("Error querying api machine %q object: %v, retrying...", machine1.Name, err) - return false, nil - } - if machine.Status.NodeRef == nil || machine.Status.NodeRef.Kind != "Node" { - glog.Error("Machine %q not linked to a node", machine.Name) - } - - drainedNodeName = machine.Status.NodeRef.Name - node := corev1.Node{} - - if err := tc.client.Get(context.TODO(), types.NamespacedName{Name: drainedNodeName}, &node); err != nil { - glog.Errorf("Error querying api node %q object: %v, retrying...", drainedNodeName, err) - return false, nil - } - - if !node.Spec.Unschedulable { - glog.Errorf("Node %q is expected to be marked as unschedulable, it is not", node.Name) - return false, nil - } - - glog.Infof("Node %q is mark unschedulable as expected", node.Name) - - pods := corev1.PodList{} - listOpt := &client.ListOptions{} - listOpt.MatchingLabels(rc.Spec.Selector) - if err := tc.client.List(context.TODO(), listOpt, &pods); err != nil { - glog.Errorf("Error querying api for Pods object: %v, retrying...", err) - return false, nil - } - - // expecting nodeGroupSize nodes - podCounter := 0 - for _, pod := range pods.Items { - if pod.Spec.NodeName != machine.Status.NodeRef.Name { - continue - } - if !pod.DeletionTimestamp.IsZero() { - continue - } - podCounter++ - } - - glog.Infof("Have %v pods scheduled to node %q", podCounter, machine.Status.NodeRef.Name) - - // Verify we have enough pods running as well - rcObj := corev1.ReplicationController{} - key = types.NamespacedName{ - Namespace: rc.Namespace, - Name: rc.Name, - } - if err := tc.client.Get(context.TODO(), key, &rcObj); err != nil { - glog.Errorf("Error querying api RC %q object: %v, retrying...", rc.Name, err) - return false, nil - } - - // The point of the test is to make sure majority of the pods is rescheduled - // to other nodes. Pod disruption budget makes sure at most one pod - // owned by the RC is not Ready. So no need to test it. Though, usefull to have it printed. - glog.Infof("RC ReadyReplicas/Replicas: %v/%v", rcObj.Status.ReadyReplicas, rcObj.Status.Replicas) - - // This makes sure at most one replica is not ready - if rcObj.Status.Replicas-rcObj.Status.ReadyReplicas > 1 { - return false, fmt.Errorf("pod disruption budget not respecpted, node was not properly drained") - } - - // Depends on timing though a machine can be deleted even before there is only - // one pod left on the node (that is being evicted). - if podCounter > 2 { - glog.Infof("Expecting at most 2 pods to be scheduled to drained node %v, got %v", machine.Status.NodeRef.Name, podCounter) - return false, nil - } - - glog.Info("Expected result: all pods from the RC up to last one or two got scheduled to a different node while respecting PDB") - return true, nil - }); err != nil { - return err - } - - // Validate the machine is deleted - if err := wait.PollImmediate(5*time.Second, 1*time.Minute, func() (bool, error) { - machine := mapiv1beta1.Machine{} - - key := types.NamespacedName{ - Namespace: machine1.Namespace, - Name: machine1.Name, - } - err := tc.client.Get(context.TODO(), key, &machine) - if err == nil { - glog.Errorf("Machine %q not yet deleted", machine1.Name) - return false, nil - } - - if !strings.Contains(err.Error(), "not found") { - glog.Errorf("Error querying api machine %q object: %v, retrying...", machine1.Name, err) - return false, nil - } - - glog.Infof("Machine %q successfully deleted", machine1.Name) - return true, nil - }); err != nil { - return err - } - - // Validate underlying node is removed as well - if err := wait.PollImmediate(5*time.Second, waitLong, func() (bool, error) { - node := corev1.Node{} - - key := types.NamespacedName{ - Name: drainedNodeName, - } - err := tc.client.Get(context.TODO(), key, &node) - if err == nil { - glog.Errorf("Node %q not yet deleted", drainedNodeName) - return false, nil - } - - if !strings.Contains(err.Error(), "not found") { - glog.Errorf("Error querying api node %q object: %v, retrying...", drainedNodeName, err) - return false, nil - } - - glog.Infof("Node %q successfully deleted", drainedNodeName) - return true, nil - }); err != nil { - return err - } - - return nil -}