From 02693d1aaeffbc8b769e4f4bac062e48103bfdbb Mon Sep 17 00:00:00 2001 From: John Strunk Date: Mon, 10 Jan 2022 09:38:31 -0500 Subject: [PATCH 1/5] Update go.mod Signed-off-by: John Strunk --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 4e2a7c5b1..9636154e3 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/spf13/cobra v1.2.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.8.1 + gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.22.1 k8s.io/apimachinery v0.22.1 k8s.io/cli-runtime v0.22.1 @@ -105,7 +106,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect k8s.io/apiextensions-apiserver v0.22.1 // indirect k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect From dc8531b87c738c9ba976750526fae1ebf58c05da Mon Sep 17 00:00:00 2001 From: John Strunk Date: Mon, 10 Jan 2022 10:41:40 -0500 Subject: [PATCH 2/5] CLI: Helper to add UUID label Signed-off-by: John Strunk --- kubectl-volsync/cmd/relationship.go | 17 +++++++++++++++++ kubectl-volsync/cmd/relationship_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/kubectl-volsync/cmd/relationship.go b/kubectl-volsync/cmd/relationship.go index fb73d7d7c..444118b6f 100644 --- a/kubectl-volsync/cmd/relationship.go +++ b/kubectl-volsync/cmd/relationship.go @@ -29,6 +29,14 @@ import ( "gopkg.in/yaml.v2" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + // RelationshipLabelKey is a label applied to objects that are created as a + // part of a given relationship. The value of the key is the UUID of the + // relationship. + RelationshipLabelKey = "volsync.backube/relationship" ) // Each relationship type (e.g., replication, migration, backup, etc.) should @@ -180,3 +188,12 @@ func (r *Relationship) GetData(data interface{}) error { return resource.ParseQuantity(data.(string)) })) } + +func (r *Relationship) AddIDLabel(object client.Object) { + labels := object.GetLabels() + if labels == nil { + labels = map[string]string{} + } + labels[RelationshipLabelKey] = r.ID().String() + object.SetLabels(labels) +} diff --git a/kubectl-volsync/cmd/relationship_test.go b/kubectl-volsync/cmd/relationship_test.go index 3344bc6f3..3fd65bd24 100644 --- a/kubectl-volsync/cmd/relationship_test.go +++ b/kubectl-volsync/cmd/relationship_test.go @@ -24,7 +24,9 @@ import ( "github.com/google/uuid" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilrand "k8s.io/apimachinery/pkg/util/rand" ) @@ -165,5 +167,27 @@ var _ = Describe("Relationships", func() { Expect(initial.RdataPtr.RdataPtr).To(BeNil()) Expect(loaded.RdataPtr.RdataPtr).To(BeNil()) }) + It("can set the ID label on an object", func() { + pvcNoLabels := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "bar", + }, + } + rel.AddIDLabel(pvcNoLabels) + Expect(pvcNoLabels.Labels).To(HaveKeyWithValue(RelationshipLabelKey, rel.ID().String())) + pvcLabels := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "bar", + Labels: map[string]string{ + "one": "two", + "three": "four", + }, + }, + } + rel.AddIDLabel(pvcLabels) + Expect(pvcLabels.Labels).To(HaveKeyWithValue(RelationshipLabelKey, rel.ID().String())) + }) }) }) From 6ab7c8905d3fafd5f44404e0d95fc909c2551832 Mon Sep 17 00:00:00 2001 From: John Strunk Date: Wed, 12 Jan 2022 09:54:48 -0500 Subject: [PATCH 3/5] CLI: replication create/delete Signed-off-by: John Strunk --- kubectl-volsync/cmd/replication.go | 222 ++++++++++++++++++++ kubectl-volsync/cmd/replication_create.go | 64 ++++++ kubectl-volsync/cmd/replication_delete.go | 80 ++++++++ kubectl-volsync/cmd/replication_test.go | 236 ++++++++++++++++++++++ 4 files changed, 602 insertions(+) create mode 100644 kubectl-volsync/cmd/replication.go create mode 100644 kubectl-volsync/cmd/replication_create.go create mode 100644 kubectl-volsync/cmd/replication_delete.go create mode 100644 kubectl-volsync/cmd/replication_test.go diff --git a/kubectl-volsync/cmd/replication.go b/kubectl-volsync/cmd/replication.go new file mode 100644 index 000000000..403fd4f86 --- /dev/null +++ b/kubectl-volsync/cmd/replication.go @@ -0,0 +1,222 @@ +/* +Copyright © 2021 The VolSync authors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ +package cmd + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + errorsutil "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/klog/v2" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" + "sigs.k8s.io/controller-runtime/pkg/client" + + volsyncv1alpha1 "github.com/backube/volsync/api/v1alpha1" +) + +const ReplicationRelationshipType RelationshipType = "replication" + +// replicationRelationship holds the config state for replication-type +// relationships +type replicationRelationship struct { + Relationship + data replicationRelationshipData +} + +// replicationRelationshipData is the state that will be saved to the +// relationship config file +type replicationRelationshipData struct { + // Config file/struct version used so we know how to decode when parsing + // from disk + Version int + // Config info for the source side of the relationship + Source *replicationRelationshipSource + // Config info for the destination side of the relationship + Destination *replicationRelationshipDestination +} + +type replicationRelationshipSource struct { + // Cluster context name + Cluster string + // Namespace on source cluster + Namespace string + // Name of PVC being replicated + PVCName string + // Name of ReplicationSource object + RSName string + // Parameters for the ReplicationSource + Source volsyncv1alpha1.ReplicationSourceRsyncSpec +} + +type replicationRelationshipDestination struct { + // Cluster context name + Cluster string + // Namespace on destination cluster + Namespace string + // Name of the ReplicationDestination object + RDName string + // Parameters for the ReplicationDestination + Destination volsyncv1alpha1.ReplicationDestinationRsyncSpec +} + +// replicationCmd represents the replication command +var replicationCmd = &cobra.Command{ + Use: "replication", + Short: i18n.T("Replicate data between two PersistentVolumes"), + Long: templates.LongDesc(i18n.T(` + Replicate the contents of one PersistentVolume to another. + + This set of commands is designed to set up and manage a replication + relationship between two different PVCs in the same Namespace, across + Namespaces, or in different clusters. The contents of the volume can be + replicated either on-demand or based on a provided schedule. + `)), +} + +func init() { + rootCmd.AddCommand(replicationCmd) + + replicationCmd.PersistentFlags().StringP("relationship", "r", "", "relationship name") + cobra.CheckErr(replicationCmd.MarkPersistentFlagRequired("relationship")) +} + +func newReplicationRelationship(cmd *cobra.Command) (*replicationRelationship, error) { + r, err := CreateRelationshipFromCommand(cmd, ReplicationRelationshipType) + if err != nil { + return nil, err + } + + return &replicationRelationship{ + Relationship: *r, + data: replicationRelationshipData{ + Version: 1, + }, + }, nil +} + +func loadReplicationRelationship(cmd *cobra.Command) (*replicationRelationship, error) { + r, err := LoadRelationshipFromCommand(cmd, ReplicationRelationshipType) + if err != nil { + return nil, err + } + + rr := &replicationRelationship{ + Relationship: *r, + } + // Decode according to the file version + version := rr.GetInt("data.version") + switch version { + case 1: + if err := rr.GetData(&rr.data); err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("unsupported config file version %d", version) + } + return rr, nil +} + +func (rr *replicationRelationship) Save() error { + if err := rr.SetData(rr.data); err != nil { + return err + } + // resource.Quantity doesn't properly encode, so we need to do it manually + if rr.data.Source != nil && rr.data.Source.Source.Capacity != nil { + rr.Set("data.source.source.replicationsourcevolumeoptions.capacity", + rr.data.Source.Source.Capacity.String()) + } + if rr.data.Destination != nil && rr.data.Destination.Destination.Capacity != nil { + rr.Set("data.destination.destination.replicationdestinationvolumeoptions.capacity", + rr.data.Destination.Destination.Capacity.String()) + } + return rr.Relationship.Save() +} + +// GetClients returns clients to access the src & dst clusters (srcClient, +// dstClient, error) +func (rr *replicationRelationship) GetClients() (client.Client, client.Client, error) { + var srcClient, dstClient client.Client + var err error + errList := []error{} + if rr.data.Source != nil { + if srcClient, err = newClient(rr.data.Source.Cluster); err != nil { + klog.Errorf("unable to create client for source cluster: %w", err) + errList = append(errList, err) + } + } + if rr.data.Destination != nil { + if dstClient, err = newClient(rr.data.Destination.Cluster); err != nil { + klog.Errorf("unable to create client for destination cluster: %w", err) + errList = append(errList, err) + } + } + return srcClient, dstClient, errorsutil.NewAggregate(errList) +} + +// DeleteSource removes the resources we've created on the source cluster +func (rr *replicationRelationship) DeleteSource(ctx context.Context, + srcClient client.Client) error { + src := rr.data.Source + if srcClient == nil || src == nil { + // Nothing to do because we don't have a client or the source isn't + // defined + return nil + } + + errList := []error{} + for _, o := range []client.Object{ + // cleaning up requires deleting both RS and the Secret we copied + &volsyncv1alpha1.ReplicationSource{}, + &corev1.Secret{}, + } { + err := srcClient.DeleteAllOf(ctx, o, + client.InNamespace(src.Namespace), + client.MatchingLabels{RelationshipLabelKey: rr.ID().String()}, + client.PropagationPolicy(metav1.DeletePropagationBackground)) + if client.IgnoreNotFound(err) != nil { + klog.Errorf("unable to remove previous Source objects: %w", err) + errList = append(errList, err) + } + } + return errorsutil.NewAggregate(errList) +} + +// DeleteDestination removes the resources we've created on the destination +// cluster +func (rr *replicationRelationship) DeleteDestination(ctx context.Context, + dstClient client.Client) error { + dst := rr.data.Destination + if dstClient == nil || dst == nil { + // Nothing to do because we don't have a client or the destination isn't + // defined + return nil + } + + err := dstClient.DeleteAllOf(ctx, &volsyncv1alpha1.ReplicationDestination{}, + client.InNamespace(dst.Namespace), + client.MatchingLabels{RelationshipLabelKey: rr.ID().String()}, + client.PropagationPolicy(metav1.DeletePropagationBackground)) + err = client.IgnoreNotFound(err) + if err != nil { + klog.Errorf("unable to remove previous Destination objects: %w", err) + } + return err +} diff --git a/kubectl-volsync/cmd/replication_create.go b/kubectl-volsync/cmd/replication_create.go new file mode 100644 index 000000000..63a41f2ef --- /dev/null +++ b/kubectl-volsync/cmd/replication_create.go @@ -0,0 +1,64 @@ +/* +Copyright © 2021 The VolSync authors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +type replicationCreate struct { + cobra.Command +} + +// replicationCreateCmd represents the create command +var replicationCreateCmd = &cobra.Command{ + Use: "create", + Short: i18n.T("Create a new replication relationship"), + Long: templates.LongDesc(i18n.T(` + This command creates a new, empty replication relationship. + + Once created, both a source (set-source) and a destination (set-destination) + must be added. + `)), + RunE: func(cmd *cobra.Command, args []string) error { + r := &replicationCreate{ + Command: *cmd, + } + return r.Run() + }, +} + +func init() { + replicationCmd.AddCommand(replicationCreateCmd) +} + +func (cmd *replicationCreate) Run() error { + r, err := newReplicationRelationship(&cmd.Command) + if err != nil { + return err + } + + if err = r.Save(); err != nil { + return fmt.Errorf("unable to save relationship configuration: %w", err) + } + + return nil +} diff --git a/kubectl-volsync/cmd/replication_delete.go b/kubectl-volsync/cmd/replication_delete.go new file mode 100644 index 000000000..b71b1aa9e --- /dev/null +++ b/kubectl-volsync/cmd/replication_delete.go @@ -0,0 +1,80 @@ +/* +Copyright © 2021 The VolSync authors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ +package cmd + +import ( + "context" + + "github.com/spf13/cobra" + errorsutil "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +type replicationDelete struct { + rel *replicationRelationship +} + +// replicationDeleteCmd represents the delete command +var replicationDeleteCmd = &cobra.Command{ + Use: "delete", + Short: i18n.T("Delete an existing replication relationship"), + Long: templates.LongDesc(i18n.T(` + This command deletes a replication relationship and removes the associated + objects from the cluster(s). + + The delete command removes the VolSync CRs for this relationship as well as + any associated VolumeSnapshot objects. In order to preserve the replicated + data, the destination should be "promote"-ed prior to deleting the + relationship. + `)), + RunE: func(cmd *cobra.Command, args []string) error { + r, err := newReplicationDelete(cmd) + if err != nil { + return err + } + r.rel, err = loadReplicationRelationship(cmd) + if err != nil { + return err + } + return r.Run(cmd.Context()) + }, +} + +func init() { + replicationCmd.AddCommand(replicationDeleteCmd) +} + +func newReplicationDelete(cmd *cobra.Command) (*replicationDelete, error) { + rdel := &replicationDelete{} + return rdel, nil +} + +func (rdel *replicationDelete) Run(ctx context.Context) error { + srcClient, dstClient, _ := rdel.rel.GetClients() + errList := []error{} + if err := rdel.rel.DeleteSource(ctx, srcClient); err != nil { + errList = append(errList, err) + } + if err := rdel.rel.DeleteDestination(ctx, dstClient); err != nil { + errList = append(errList, err) + } + if err := rdel.rel.Delete(); err != nil { + errList = append(errList, err) + } + return errorsutil.NewAggregate(errList) +} diff --git a/kubectl-volsync/cmd/replication_test.go b/kubectl-volsync/cmd/replication_test.go new file mode 100644 index 000000000..c70ec6135 --- /dev/null +++ b/kubectl-volsync/cmd/replication_test.go @@ -0,0 +1,236 @@ +/* +Copyright © 2021 The VolSync authors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ +package cmd + +import ( + "context" + "io/ioutil" + "os" + "reflect" + + volsyncv1alpha1 "github.com/backube/volsync/api/v1alpha1" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe("Replication relationships can create/save/load", func() { + var dirname string + var cmd *cobra.Command + BeforeEach(func() { + var err error + // Create temp directory for relationship files + dirname, err = ioutil.TempDir("", "relation") + Expect(err).NotTo(HaveOccurred()) + + cmd = &cobra.Command{} + cmd.Flags().StringP("relationship", "r", "test-name", "") + cmd.Flags().String("config-dir", dirname, "") + }) + AfterEach(func() { + os.RemoveAll(dirname) + }) + It("can be round-triped", func() { + By("creating a new relationship") + rr, err := newReplicationRelationship(cmd) + Expect(err).NotTo(HaveOccurred()) + Expect(rr.data.Version).To(Equal(1)) + Expect(rr.data.Destination).To(BeNil()) + Expect(rr.data.Source).To(BeNil()) + + By("saving the relationship") + // Assign some values to test round-trip + caps := resource.MustParse("1Gi") + rr.data.Source = &replicationRelationshipSource{ + Cluster: "cluster", + Namespace: "the-ns", + PVCName: "a-pvc", + RSName: "an-rs", + Source: volsyncv1alpha1.ReplicationSourceRsyncSpec{ + ReplicationSourceVolumeOptions: volsyncv1alpha1.ReplicationSourceVolumeOptions{ + CopyMethod: volsyncv1alpha1.CopyMethodClone, + Capacity: &caps, + StorageClassName: pointer.String("scn"), + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + VolumeSnapshotClassName: pointer.String("vscn"), + }, + }, + } + capd := resource.MustParse("99Gi") + rr.data.Destination = &replicationRelationshipDestination{ + Cluster: "c2", + Namespace: "n2", + RDName: "rd2", + Destination: volsyncv1alpha1.ReplicationDestinationRsyncSpec{ + ReplicationDestinationVolumeOptions: volsyncv1alpha1.ReplicationDestinationVolumeOptions{ + CopyMethod: volsyncv1alpha1.CopyMethodSnapshot, + Capacity: &capd, + StorageClassName: pointer.String("scn2"), + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteMany}, + VolumeSnapshotClassName: pointer.String("vscn2"), + DestinationPVC: pointer.String("dpvc"), + }, + ServiceType: (*corev1.ServiceType)(pointer.String(string(corev1.ServiceTypeClusterIP))), + }, + } + Expect(rr.Save()).To(Succeed()) + + By("loading it back in, they should match") + rr2, err := loadReplicationRelationship(cmd) + Expect(err).NotTo(HaveOccurred()) + Expect(reflect.DeepEqual(rr2.data, rr.data)).To(BeTrue()) + }) +}) + +var _ = Describe("Replication relationships", func() { + var ctx context.Context + var dirname string + var repRel *replicationRelationship + BeforeEach(func() { + ctx = context.TODO() + var err error + // Create temp directory for relationship files + dirname, err = ioutil.TempDir("", "relation") + Expect(err).NotTo(HaveOccurred()) + // Create a new generic relationship + rel, err := createRelationship(dirname, "test", ReplicationRelationshipType) + Expect(err).NotTo(HaveOccurred()) + repRel = &replicationRelationship{ + Relationship: *rel, + data: replicationRelationshipData{ + Version: 1, + Source: nil, + Destination: nil, + }, + } + }) + AfterEach(func() { + os.RemoveAll(dirname) + }) + + When("the cluster is empty", func() { + When("trying to delete", func() { + It("succeeds if no resources are defined", func() { + Expect(repRel.DeleteSource(ctx, k8sClient)).To(Succeed()) + Expect(repRel.DeleteDestination(ctx, k8sClient)).To(Succeed()) + }) + It("succeeds if the cluster is empty", func() { + repRel.data.Source = &replicationRelationshipSource{ + RSName: "xxx", + Namespace: "zzz", + } + repRel.data.Destination = &replicationRelationshipDestination{ + RDName: "xxx", + Namespace: "zzz", + } + Expect(repRel.DeleteSource(ctx, k8sClient)).To(Succeed()) + Expect(repRel.DeleteDestination(ctx, k8sClient)).To(Succeed()) + }) + }) + }) + When("there are existing resources in the cluster", func() { + var srcNs, dstNs *corev1.Namespace + var rs *volsyncv1alpha1.ReplicationSource + var rd *volsyncv1alpha1.ReplicationDestination + BeforeEach(func() { + // Create Namespaces + srcNs = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "src-", + }, + } + Expect(k8sClient.Create(ctx, srcNs)).To(Succeed()) + Expect(srcNs.Name).NotTo(BeEmpty()) + dstNs = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "dst-", + }, + } + Expect(k8sClient.Create(ctx, dstNs)).To(Succeed()) + Expect(dstNs.Name).NotTo(BeEmpty()) + + rs = &volsyncv1alpha1.ReplicationSource{ + ObjectMeta: metav1.ObjectMeta{ + Name: "thesource", + Namespace: srcNs.Name, + }, + Spec: volsyncv1alpha1.ReplicationSourceSpec{ + Rsync: &volsyncv1alpha1.ReplicationSourceRsyncSpec{}, + }, + } + repRel.AddIDLabel(rs) + Expect(k8sClient.Create(ctx, rs)).To(Succeed()) + rd = &volsyncv1alpha1.ReplicationDestination{ + ObjectMeta: metav1.ObjectMeta{ + Name: "thedestination", + Namespace: dstNs.Name, + Labels: map[string]string{ + "some": "label", + }, + }, + Spec: volsyncv1alpha1.ReplicationDestinationSpec{ + Rsync: &volsyncv1alpha1.ReplicationDestinationRsyncSpec{}, + }, + } + repRel.AddIDLabel(rd) + Expect(k8sClient.Create(ctx, rd)).To(Succeed()) + repRel.data.Source = &replicationRelationshipSource{ + RSName: rs.Name, + Namespace: srcNs.Name, + } + repRel.data.Destination = &replicationRelationshipDestination{ + RDName: rd.Name, + Namespace: dstNs.Name, + } + }) + AfterEach(func() { + Expect(k8sClient.Delete(ctx, srcNs)).To(Succeed()) + Expect(k8sClient.Delete(ctx, dstNs)).To(Succeed()) + }) + It("cleans them up when trying to delete", func() { + rd2 := &volsyncv1alpha1.ReplicationDestination{ + ObjectMeta: metav1.ObjectMeta{ + Name: "thedestination2", + Namespace: dstNs.Name, + }, + Spec: volsyncv1alpha1.ReplicationDestinationSpec{ + Rsync: &volsyncv1alpha1.ReplicationDestinationRsyncSpec{}, + }, + } + // Note: we didn't add the relationship label, therefore it + // won't get deleted + Expect(k8sClient.Create(ctx, rd2)).To(Succeed()) + // Set the relationship such that the Replication objs should be deleted + repRel.data.Source.RSName = "" + repRel.data.Destination.RDName = "" + Expect(repRel.DeleteSource(ctx, k8sClient)).To(Succeed()) + Expect(repRel.DeleteDestination(ctx, k8sClient)).To(Succeed()) + + newRs := volsyncv1alpha1.ReplicationSource{} + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(rs), &newRs)).NotTo(Succeed()) + newRd := volsyncv1alpha1.ReplicationDestination{} + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(rd), &newRd)).NotTo(Succeed()) + // extra one should still be there since it doesn't have the relationship ID label + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(rd2), &newRd)).To(Succeed()) + }) + }) +}) From 3966fd868537e0f7f6b3eff1bf28034a614a580e Mon Sep 17 00:00:00 2001 From: John Strunk Date: Tue, 1 Feb 2022 16:07:39 -0500 Subject: [PATCH 4/5] Capture klog logging in ginkgo tests Signed-off-by: John Strunk --- kubectl-volsync/cmd/suite_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kubectl-volsync/cmd/suite_test.go b/kubectl-volsync/cmd/suite_test.go index 25cf7216f..e7335ed78 100644 --- a/kubectl-volsync/cmd/suite_test.go +++ b/kubectl-volsync/cmd/suite_test.go @@ -8,6 +8,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "k8s.io/client-go/rest" + "k8s.io/klog/v2" "k8s.io/kubectl/pkg/scheme" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" @@ -28,7 +29,9 @@ func TestCmd(t *testing.T) { } var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter))) + logger := zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)) + logf.SetLogger(logger) + klog.SetLogger(logger) By("bootstrapping test environment") testEnv = &envtest.Environment{ From 9b3b61190d43b844bb66851d09047ab1d873b29d Mon Sep 17 00:00:00 2001 From: John Strunk Date: Wed, 2 Feb 2022 16:34:57 -0500 Subject: [PATCH 5/5] Remove old coverage file before running tests Signed-off-by: John Strunk --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 9d7de760e..8384d7d3f 100644 --- a/Makefile +++ b/Makefile @@ -105,6 +105,7 @@ helm-lint: helm ## Lint Helm chart TEST_ARGS ?= -progress -randomizeAllSpecs -randomizeSuites -slowSpecThreshold 30 -p -cover -coverprofile cover.out -outputdir . TEST_PACKAGES ?= ./... test: manifests generate lint envtest helm-lint ginkgo ## Run tests. + -rm -f cover.out KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" $(GINKGO) $(TEST_ARGS) $(TEST_PACKAGES) .PHONY: test-e2e