From 95a2422b3c57b4d762424c2b6f852a1dcd20ced3 Mon Sep 17 00:00:00 2001 From: Kyle Squizzato Date: Wed, 31 Jul 2024 08:46:16 -0700 Subject: [PATCH] [ENGDTR-4307] Allow msr2 and msr3 products to coexist (#488) * Seperate out MSR inlined config into two seperate products * Allow both MSR2 and MSR3 to coexist * Refactor describe to iterate through both product types and remove uses of switch. * Refactor GatherFacts and ValidateFacts phases to validate both products. * Refactor phase to just check for config non-nil and remove uses of switch. * Rename MSR to MSR2 and rename files, packages and struct entries. Signed-off-by: Kyle Squizzato Co-authored-by: James Nesbitt --- cmd/exec.go | 3 +- pkg/config/config.go | 2 + pkg/config/migration/v15/v15.go | 28 +++ pkg/config/migration/v15/v15_test.go | 73 ++++++++ pkg/constant/constant.go | 2 - pkg/kubeclient/msr.go | 38 +---- pkg/kubeclient/msr_test.go | 31 ---- pkg/msr/msr2/msr2.go | 38 ++--- pkg/msr/msr2/{msr_test.go => msr2_test.go} | 20 +-- pkg/msr/msr3/msr3.go | 32 ++-- pkg/msr/msr3/msr3_test.go | 8 +- pkg/product/mke/api/cluster.go | 10 +- pkg/product/mke/api/cluster_spec.go | 88 +++++----- pkg/product/mke/api/cluster_spec_test.go | 48 +++--- pkg/product/mke/api/cluster_test.go | 52 +++--- pkg/product/mke/api/host.go | 66 +++++--- pkg/product/mke/api/hosts_test.go | 6 +- pkg/product/mke/api/msr_config.go | 159 +++++++++--------- pkg/product/mke/api/msr_config_test.go | 42 ++--- pkg/product/mke/apply.go | 25 +-- pkg/product/mke/exec.go | 2 +- pkg/product/mke/phase/configure_deps_msr3.go | 2 +- .../mke/phase/configure_provisioner_msr3.go | 8 +- pkg/product/mke/phase/describe.go | 43 +++-- pkg/product/mke/phase/gather_facts.go | 71 ++------ pkg/product/mke/phase/info.go | 27 ++- .../phase/{install_msr.go => install_msr2.go} | 48 +++--- pkg/product/mke/phase/install_msr3.go | 107 ++---------- ..._msr_replicas.go => join_msr2_replicas.go} | 32 ++-- pkg/product/mke/phase/prepare_host.go | 2 +- ...pull_msr_images.go => pull_msr2_images.go} | 22 +-- pkg/product/mke/phase/remove_nodes.go | 34 ++-- .../{uninstall_msr.go => uninstall_msr2.go} | 6 +- pkg/product/mke/phase/uninstall_msr3.go | 9 +- pkg/product/mke/phase/upgrade_check.go | 4 +- pkg/product/mke/phase/upgrade_mcr.go | 26 +-- .../phase/{upgrade_msr.go => upgrade_msr2.go} | 40 ++--- pkg/product/mke/phase/validate_facts.go | 130 ++++++++++---- pkg/product/mke/phase/validate_facts_test.go | 43 +++-- pkg/product/mke/reset.go | 5 +- pkg/product/product.go | 4 +- 41 files changed, 728 insertions(+), 708 deletions(-) create mode 100644 pkg/config/migration/v15/v15.go create mode 100644 pkg/config/migration/v15/v15_test.go rename pkg/msr/msr2/{msr_test.go => msr2_test.go} (84%) rename pkg/product/mke/phase/{install_msr.go => install_msr2.go} (70%) rename pkg/product/mke/phase/{join_msr_replicas.go => join_msr2_replicas.go} (75%) rename pkg/product/mke/phase/{pull_msr_images.go => pull_msr2_images.go} (73%) rename pkg/product/mke/phase/{uninstall_msr.go => uninstall_msr2.go} (86%) rename pkg/product/mke/phase/{upgrade_msr.go => upgrade_msr2.go} (68%) diff --git a/cmd/exec.go b/cmd/exec.go index 937115252..b029cb35c 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/Mirantis/mcc/pkg/config" + "github.com/Mirantis/mcc/pkg/product/mke/api" "github.com/kballard/go-shellquote" "github.com/urfave/cli/v2" ) @@ -63,7 +64,7 @@ func NewExecCommand() *cli.Command { args := ctx.Args().Slice() - err = product.Exec(ctx.StringSlice("target"), ctx.Bool("interactive"), ctx.Bool("first"), ctx.Bool("all"), ctx.Bool("parallel"), ctx.String("role"), ctx.String("os"), shellquote.Join(args...)) + err = product.Exec(ctx.StringSlice("target"), ctx.Bool("interactive"), ctx.Bool("first"), ctx.Bool("all"), ctx.Bool("parallel"), api.RoleType(ctx.String("role")), ctx.String("os"), shellquote.Join(args...)) if err != nil { return fmt.Errorf("failed to execute command: %w", err) } diff --git a/pkg/config/config.go b/pkg/config/config.go index 51d0f7f86..4769aafd5 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -21,6 +21,8 @@ import ( // needed to load the migrators. _ "github.com/Mirantis/mcc/pkg/config/migration/v14" // needed to load the migrators. + _ "github.com/Mirantis/mcc/pkg/config/migration/v15" + // needed to load the migrators. _ "github.com/Mirantis/mcc/pkg/config/migration/v1beta1" // needed to load the migrators. _ "github.com/Mirantis/mcc/pkg/config/migration/v1beta2" diff --git a/pkg/config/migration/v15/v15.go b/pkg/config/migration/v15/v15.go new file mode 100644 index 000000000..f81898b3a --- /dev/null +++ b/pkg/config/migration/v15/v15.go @@ -0,0 +1,28 @@ +package v15 + +import ( + "github.com/Mirantis/mcc/pkg/config/migration" + log "github.com/sirupsen/logrus" +) + +// Migrate migrates a v1.5 format configuration into the v1.6 api format and +// replaces the contents of the supplied data byte slice. +func Migrate(plain map[string]interface{}) error { + plain["apiVersion"] = "launchpad.mirantis.com/mke/v1.6" + if spec, ok := plain["spec"].(map[interface{}]interface{}); ok { + if msr, ok := spec["msr"].(map[interface{}]interface{}); ok { + // Convert msr key to msr2. + spec["msr2"] = msr + delete(spec, "msr") + } + } + + log.Debugf("migrated configuration from launchpad.mirantis.com/v1.5 to launchpad.mirantis.com/mke/v1.6") + log.Infof("Note: The configuration has been migrated from a previous version") + log.Infof(" to see the migrated configuration use: launchpad describe config") + return nil +} + +func init() { + migration.Register("launchpad.mirantis.com/mke/v1.5", Migrate) +} diff --git a/pkg/config/migration/v15/v15_test.go b/pkg/config/migration/v15/v15_test.go new file mode 100644 index 000000000..27c5387a8 --- /dev/null +++ b/pkg/config/migration/v15/v15_test.go @@ -0,0 +1,73 @@ +package v15 + +import ( + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" +) + +func TestVersionMigration(t *testing.T) { + v15 := []byte(`apiVersion: launchpad.mirantis.com/mke/v1.5 +kind: mke +spec: + mcr: + channel: stable + installURLLinux: https://get.mirantis.com/ + installURLWindows: https://get.mirantis.com/install.ps1 + repoURL: https://repos.mirantis.com + version: 23.0.8 + mke: + adminPassword: miradmin + adminUsername: admin + imageRepo: docker.io/mirantis + installFlags: + - --san=pgedaray-mke-lb-1477b84d031720d6.elb.us-west-1.amazonaws.com + - --nodeport-range=32768-35535 + upgradeFlags: + - --force-recent-backup + - --force-minimums + version: 3.7.3 + msr: + imageRepo: docker.io/mirantis + installFlags: + - --nfs-options nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport + - --nfs-storage-url nfs://nfs.example.com/ + replicaIDs: sequential + version: 2.9.15 +`) + v16 := []byte(`apiVersion: launchpad.mirantis.com/mke/v1.6 +kind: mke +spec: + mcr: + channel: stable + installURLLinux: https://get.mirantis.com/ + installURLWindows: https://get.mirantis.com/install.ps1 + repoURL: https://repos.mirantis.com + version: 23.0.8 + mke: + adminPassword: miradmin + adminUsername: admin + imageRepo: docker.io/mirantis + installFlags: + - --san=pgedaray-mke-lb-1477b84d031720d6.elb.us-west-1.amazonaws.com + - --nodeport-range=32768-35535 + upgradeFlags: + - --force-recent-backup + - --force-minimums + version: 3.7.3 + msr2: + imageRepo: docker.io/mirantis + installFlags: + - --nfs-options nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport + - --nfs-storage-url nfs://nfs.example.com/ + replicaIDs: sequential + version: 2.9.15 +`) + in := make(map[string]interface{}) + require.NoError(t, yaml.Unmarshal(v15, in)) + require.NoError(t, Migrate(in)) + out, err := yaml.Marshal(in) + require.NoError(t, err) + require.Equal(t, string(v16), string(out)) +} diff --git a/pkg/constant/constant.go b/pkg/constant/constant.go index e70e8679b..81d6b73a8 100644 --- a/pkg/constant/constant.go +++ b/pkg/constant/constant.go @@ -43,8 +43,6 @@ const ( MSROperatorDeploymentLabels = "app.kubernetes.io/name=msr-operator" // KubeConfigFile is the name of the kubeconfig file. KubeConfigFile = "kube.yml" - // MSRNodeSelector is the node selector for MSR nodes. - MSRNodeSelector = "node-role.kubernetes.io/msr" // DefaultStorageClassAnnotation is the annotation to set a StorageClass to the default. DefaultStorageClassAnnotation = "storageclass.kubernetes.io/is-default-class" ) diff --git a/pkg/kubeclient/msr.go b/pkg/kubeclient/msr.go index 64467e373..1c7e0e201 100644 --- a/pkg/kubeclient/msr.go +++ b/pkg/kubeclient/msr.go @@ -19,6 +19,7 @@ import ( "k8s.io/client-go/dynamic" ) +// GetMSRCR get the MSR Custom Resource instance. func (kc *KubeClient) GetMSRCR(ctx context.Context, name string, rc dynamic.ResourceInterface) (*unstructured.Unstructured, error) { unstructured, err := rc.Get(ctx, name, metav1.GetOptions{}) if err != nil { @@ -153,39 +154,6 @@ func (kc *KubeClient) ApplyMSRCR(ctx context.Context, obj *unstructured.Unstruct return nil } -// PrepareNodeForMSR updates the given node name setting the MSRNodeSelector -// on the node and removing any found Kubernetes NoExecute taints added by MKE. -func (kc *KubeClient) PrepareNodeForMSR(ctx context.Context, name string) error { - node, err := kc.client.CoreV1().Nodes().Get(ctx, name, metav1.GetOptions{}) - if err != nil { - return fmt.Errorf("failed to get node %q: %w", name, err) - } - - if node.Labels == nil { - node.Labels = make(map[string]string) - } - - node.Labels[constant.MSRNodeSelector] = "true" - - // Rebuild the taints list without the NoExecute taint if found. - taints := []corev1.Taint{} - for _, t := range node.Spec.Taints { - if t.Key == constant.KubernetesOrchestratorTaint && t.Value == "NoExecute" { - continue - } - taints = append(taints, t) - } - - node.Spec.Taints = taints - - _, err = kc.client.CoreV1().Nodes().Update(ctx, node, metav1.UpdateOptions{}) - if err != nil { - return fmt.Errorf("failed to update node %q: %w", name, err) - } - - return nil -} - // GetMSRResourceClient returns a dynamic client for the MSR custom resource. // //nolint:ireturn @@ -261,9 +229,7 @@ func (kc *KubeClient) MSRURL(ctx context.Context, name string, rc dynamic.Resour switch serviceType { case string(corev1.ServiceTypeNodePort): - nodes, err := kc.client.CoreV1().Nodes().List(ctx, metav1.ListOptions{ - LabelSelector: constant.MSRNodeSelector + "=true", - }) + nodes, err := kc.client.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) if err != nil { return nil, fmt.Errorf("failed to list nodes: %w", err) } diff --git a/pkg/kubeclient/msr_test.go b/pkg/kubeclient/msr_test.go index 432839cd8..73442844f 100644 --- a/pkg/kubeclient/msr_test.go +++ b/pkg/kubeclient/msr_test.go @@ -5,7 +5,6 @@ import ( "fmt" "testing" - "github.com/Mirantis/mcc/pkg/constant" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" @@ -99,33 +98,6 @@ func TestApplyMSRCR(t *testing.T) { }) } -func TestPrepareNodeForMSR(t *testing.T) { - kc := NewTestClient(t) - - kc.client.CoreV1().Nodes().Create(context.Background(), &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: "node1", - }, - Spec: corev1.NodeSpec{ - Taints: []corev1.Taint{ - { - Key: constant.KubernetesOrchestratorTaint, - Value: "NoExecute", - }, - }, - }, - }, metav1.CreateOptions{}) - - err := kc.PrepareNodeForMSR(context.Background(), "node1") - assert.NoError(t, err) - - actualNode, err := kc.client.CoreV1().Nodes().Get(context.Background(), "node1", metav1.GetOptions{}) - require.NoError(t, err) - - assert.Equal(t, "true", actualNode.Labels[constant.MSRNodeSelector]) - assert.Empty(t, actualNode.Spec.Taints) -} - func TestMSRURL(t *testing.T) { t.Run("no spec.service.externalHTTPSPort", func(t *testing.T) { kc := NewTestClient(t) @@ -258,9 +230,6 @@ func TestMSRURL(t *testing.T) { kc.client.CoreV1().Nodes().Create(context.Background(), &corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: "msr-node1", - Labels: map[string]string{ - constant.MSRNodeSelector: "true", - }, }, Status: corev1.NodeStatus{ Addresses: []corev1.NodeAddress{ diff --git a/pkg/msr/msr2/msr2.go b/pkg/msr/msr2/msr2.go index e0ad7e050..96b43173c 100644 --- a/pkg/msr/msr2/msr2.go +++ b/pkg/msr/msr2/msr2.go @@ -14,13 +14,13 @@ import ( ) // CollectFacts gathers the current status of the installed MSR setup. -func CollectFacts(h *api.Host) (*api.MSRMetadata, error) { +func CollectFacts(h *api.Host) (*api.MSR2Metadata, error) { rethinkdbContainerID, err := h.ExecOutput(h.Configurer.DockerCommandf(`ps -aq --filter name=dtr-rethinkdb`)) if err != nil { return nil, fmt.Errorf("failed to get MSR container ID: %w", err) } if rethinkdbContainerID == "" { - return &api.MSRMetadata{Installed: false}, nil + return &api.MSR2Metadata{Installed: false}, nil } version, err := h.ExecOutput(h.Configurer.DockerCommandf(`inspect %s --format '{{ index .Config.Labels "com.docker.dtr.version"}}'`, rethinkdbContainerID)) @@ -55,13 +55,11 @@ func CollectFacts(h *api.Host) (*api.MSRMetadata, error) { bootstrapimage = fmt.Sprintf("%s/dtr:%s", repo, version) } - msrMeta := &api.MSRMetadata{ - Installed: true, - InstalledVersion: version, - MSR2: api.MSR2Metadata{ - InstalledBootstrapImage: bootstrapimage, - ReplicaID: replicaID, - }, + msrMeta := &api.MSR2Metadata{ + Installed: true, + InstalledVersion: version, + InstalledBootstrapImage: bootstrapimage, + ReplicaID: replicaID, } return msrMeta, nil @@ -115,8 +113,8 @@ func FormatReplicaID(num uint64) string { // BuildMKEFlags builds the mkeFlags []string consisting of mke installFlags // that are shared with MSR. func BuildMKEFlags(config *api.ClusterConfig) common.Flags { - mkeUser := config.Spec.MSR.V2.InstallFlags.GetValue("--ucp-username") - mkePass := config.Spec.MSR.V2.InstallFlags.GetValue("--ucp-password") + mkeUser := config.Spec.MSR2.InstallFlags.GetValue("--ucp-username") + mkePass := config.Spec.MSR2.InstallFlags.GetValue("--ucp-password") if mkeUser == "" { mkeUser = config.Spec.MKE.AdminUsername @@ -149,7 +147,7 @@ func mkeURLHost(config *api.ClusterConfig) string { // installer if it fails. func Destroy(h *api.Host, config *api.ClusterConfig) error { mkeFlags := BuildMKEFlags(config) - cmd := fmt.Sprintf("run -it --rm mirantis/dtr:%s destroy --ucp-insecure-tls --ucp-url %s --ucp-username %s --ucp-password %s --replica-id %s", h.MSRMetadata.InstalledVersion, mkeFlags.GetValue("--ucp-url"), mkeFlags.GetValue("--ucp-username"), mkeFlags.GetValue("--ucp-password"), h.MSRMetadata.MSR2.ReplicaID) + cmd := fmt.Sprintf("run -it --rm mirantis/dtr:%s destroy --ucp-insecure-tls --ucp-url %s --ucp-username %s --ucp-password %s --replica-id %s", h.MSR2Metadata.InstalledVersion, mkeFlags.GetValue("--ucp-url"), mkeFlags.GetValue("--ucp-username"), mkeFlags.GetValue("--ucp-password"), h.MSR2Metadata.ReplicaID) if err := h.Exec(h.Configurer.DockerCommandf(cmd)); err != nil { return fmt.Errorf("failed to run MSR destroy: %w", err) } @@ -203,18 +201,18 @@ var errMaxReplicaID = fmt.Errorf("max sequential msr replica id exceeded") // AssignSequentialReplicaIDs goes through all the MSR hosts, finds the highest replica id and assigns sequential ones starting from that to all the hosts without replica ids. func AssignSequentialReplicaIDs(c *api.ClusterConfig) error { - msrHosts := c.Spec.MSRs() + msrHosts := c.Spec.MSR2s() // find the largest replica id var maxReplicaID uint64 err := msrHosts.Each(func(h *api.Host) error { - if h.MSRMetadata == nil { - h.MSRMetadata = &api.MSRMetadata{} + if h.MSR2Metadata == nil { + h.MSR2Metadata = &api.MSR2Metadata{} } - if h.MSRMetadata.MSR2.ReplicaID != "" { - ri, err := strconv.ParseUint(h.MSRMetadata.MSR2.ReplicaID, 16, 48) + if h.MSR2Metadata.ReplicaID != "" { + ri, err := strconv.ParseUint(h.MSR2Metadata.ReplicaID, 16, 48) if err != nil { - return fmt.Errorf("%s: invalid MSR replicaID '%s': %w", h, h.MSRMetadata.MSR2.ReplicaID, err) + return fmt.Errorf("%s: invalid MSR replicaID '%s': %w", h, h.MSR2Metadata.ReplicaID, err) } if maxReplicaID < ri { maxReplicaID = ri @@ -230,9 +228,9 @@ func AssignSequentialReplicaIDs(c *api.ClusterConfig) error { } err = msrHosts.Each(func(h *api.Host) error { - if h.MSRMetadata.MSR2.ReplicaID == "" { + if h.MSR2Metadata.ReplicaID == "" { maxReplicaID++ - h.MSRMetadata.MSR2.ReplicaID = FormatReplicaID(maxReplicaID) + h.MSR2Metadata.ReplicaID = FormatReplicaID(maxReplicaID) } return nil }) diff --git a/pkg/msr/msr2/msr_test.go b/pkg/msr/msr2/msr2_test.go similarity index 84% rename from pkg/msr/msr2/msr_test.go rename to pkg/msr/msr2/msr2_test.go index e0622ce0d..c5ea6271b 100644 --- a/pkg/msr/msr2/msr_test.go +++ b/pkg/msr/msr2/msr2_test.go @@ -60,7 +60,7 @@ func TestBuildMKEFlags(t *testing.T) { "--san ucp.acme.com", }, }, - MSR: &api.MSRConfig{}, + MSR2: &api.MSR2Config{}, }, } @@ -89,19 +89,17 @@ func TestSequentialReplicaIDs(t *testing.T) { config := &api.ClusterConfig{ Spec: &api.ClusterSpec{ Hosts: []*api.Host{ - {Role: "msr"}, - {Role: "msr", MSRMetadata: &api.MSRMetadata{MSR2: api.MSR2Metadata{ + {Role: "msr2"}, + {Role: "msr2", MSR2Metadata: &api.MSR2Metadata{ ReplicaID: "00000000001f", - }}}, - {Role: "msr"}, - }, - MSR: &api.MSRConfig{ - V2: api.MSR2Config{ReplicaIDs: "sequential"}, + }}, + {Role: "msr2"}, }, + MSR2: &api.MSR2Config{ReplicaIDs: "sequential"}, }, } require.NoError(t, AssignSequentialReplicaIDs(config)) - require.Equal(t, "000000000020", config.Spec.Hosts[0].MSRMetadata.MSR2.ReplicaID) - require.Equal(t, "00000000001f", config.Spec.Hosts[1].MSRMetadata.MSR2.ReplicaID) - require.Equal(t, "000000000021", config.Spec.Hosts[2].MSRMetadata.MSR2.ReplicaID) + require.Equal(t, "000000000020", config.Spec.Hosts[0].MSR2Metadata.ReplicaID) + require.Equal(t, "00000000001f", config.Spec.Hosts[1].MSR2Metadata.ReplicaID) + require.Equal(t, "000000000021", config.Spec.Hosts[2].MSR2Metadata.ReplicaID) } diff --git a/pkg/msr/msr3/msr3.go b/pkg/msr/msr3/msr3.go index c87732630..9b6c0e013 100644 --- a/pkg/msr/msr3/msr3.go +++ b/pkg/msr/msr3/msr3.go @@ -23,14 +23,14 @@ var ( ) // CollectFacts gathers the current status of the installed MSR3 setup. -func CollectFacts(ctx context.Context, msrName string, kubeClient *kubeclient.KubeClient, resourceClient dynamic.ResourceInterface, helmClient *helm.Helm, options ...kubeclient.WaitOption) (*api.MSRMetadata, error) { +func CollectFacts(ctx context.Context, msrName string, kubeClient *kubeclient.KubeClient, resourceClient dynamic.ResourceInterface, helmClient *helm.Helm, options ...kubeclient.WaitOption) (api.MSR3Metadata, error) { obj, err := kubeClient.GetMSRCR(ctx, msrName, resourceClient) if err != nil { if apierrors.IsNotFound(err) { log.Infof("MSR CR: %s not found: %s", msrName, err) - return &api.MSRMetadata{Installed: false}, nil + return api.MSR3Metadata{Installed: false}, nil } - return nil, fmt.Errorf("failed to get MSR CR: %w", err) + return api.MSR3Metadata{}, fmt.Errorf("failed to get MSR CR: %w", err) } // Check to see if the MSR CR exists and is ready for up to 30 seconds @@ -41,24 +41,24 @@ func CollectFacts(ctx context.Context, msrName string, kubeClient *kubeclient.Ku // reliably determine whether it is installed or not, so mark it as // not installed. log.Infof("Failed to determine if MSR CR: %s is ready: %s", msrName, err) - return &api.MSRMetadata{Installed: false}, nil + return api.MSR3Metadata{Installed: false}, nil } version, found, err := unstructured.NestedString(obj.Object, "spec", "image", "tag") if !found || version == "" { - return nil, errSpecImageTagNotPopulated + return api.MSR3Metadata{}, errSpecImageTagNotPopulated } if err != nil { - return nil, fmt.Errorf("unable to determine version from found MSR: %w", err) + return api.MSR3Metadata{}, fmt.Errorf("unable to determine version from found MSR: %w", err) } releases, err := helmClient.List(constant.InstalledDependenciesFilter) if err != nil { - return nil, fmt.Errorf("failed to list helm releases: %w", err) + return api.MSR3Metadata{}, fmt.Errorf("failed to list helm releases: %w", err) } if len(releases) == 0 { - return nil, errNoHelmDependenciesInstalled + return api.MSR3Metadata{}, errNoHelmDependenciesInstalled } installedDeps := make(map[string]helm.ReleaseDetails) @@ -74,12 +74,10 @@ func CollectFacts(ctx context.Context, msrName string, kubeClient *kubeclient.Ku } } - return &api.MSRMetadata{ - Installed: true, - InstalledVersion: version, - MSR3: api.MSR3Metadata{ - InstalledDependencies: installedDeps, - }, + return api.MSR3Metadata{ + Installed: true, + InstalledVersion: version, + InstalledDependencies: installedDeps, }, nil } @@ -103,8 +101,8 @@ func ApplyCRD(ctx context.Context, msr *unstructured.Unstructured, kc *kubeclien // GetMSRURL returns the URL for the MSR admin UI. func GetMSRURL(config *api.ClusterConfig) (string, error) { - if config.Spec.MSR.V3.LoadBalancerURL != "" { - return "https://" + config.Spec.MSR.V3.LoadBalancerURL + "/", nil + if config.Spec.MSR3.LoadBalancerURL != "" { + return "https://" + config.Spec.MSR3.LoadBalancerURL + "/", nil } kubeClient, _, err := mke.KubeAndHelmFromConfig(config) @@ -117,7 +115,7 @@ func GetMSRURL(config *api.ClusterConfig) (string, error) { return "", fmt.Errorf("failed to get resource client for MSR CR: %w", err) } - msrName := config.Spec.MSR.V3.CRD.GetName() + msrName := config.Spec.MSR3.CRD.GetName() url, err := kubeClient.MSRURL(context.Background(), msrName, rc) if err != nil { diff --git a/pkg/msr/msr3/msr3_test.go b/pkg/msr/msr3/msr3_test.go index 767c8ec4f..caf43c908 100644 --- a/pkg/msr/msr3/msr3_test.go +++ b/pkg/msr/msr3/msr3_test.go @@ -95,13 +95,11 @@ func TestCollectFacts(t *testing.T) { // shouldn't expect the InstalledDependencies map to contain this. rd.RepoURL = "" - assert.Equal(t, &api.MSRMetadata{ + assert.Equal(t, api.MSR3Metadata{ Installed: true, InstalledVersion: "3.1.1", - MSR3: api.MSR3Metadata{ - InstalledDependencies: map[string]helm.ReleaseDetails{ - "rethinkdb-operator": rd, - }, + InstalledDependencies: map[string]helm.ReleaseDetails{ + "rethinkdb-operator": rd, }}, actual) }) } diff --git a/pkg/product/mke/api/cluster.go b/pkg/product/mke/api/cluster.go index 06d5f31a4..84a22ede2 100644 --- a/pkg/product/mke/api/cluster.go +++ b/pkg/product/mke/api/cluster.go @@ -17,7 +17,7 @@ type ClusterMeta struct { // ClusterConfig describes launchpad.yaml configuration. type ClusterConfig struct { - APIVersion string `yaml:"apiVersion" validate:"eq=launchpad.mirantis.com/mke/v1.5"` + APIVersion string `yaml:"apiVersion" validate:"eq=launchpad.mirantis.com/mke/v1.6"` Kind string `yaml:"kind" validate:"oneof=mke mke+msr"` Metadata *ClusterMeta `yaml:"metadata"` Spec *ClusterSpec `yaml:"spec"` @@ -70,7 +70,7 @@ func Init(kind string) *ClusterConfig { } config := &ClusterConfig{ - APIVersion: "launchpad.mirantis.com/mke/v1.5", + APIVersion: "launchpad.mirantis.com/mke/v1.6", Kind: kind, Metadata: &ClusterMeta{ Name: "my-mke-cluster", @@ -111,9 +111,9 @@ func Init(kind string) *ClusterConfig { if err != nil { msrV = "required" } - config.Spec.MSR = &MSRConfig{ - Version: msrV, - V2: MSR2Config{ReplicaIDs: "sequential"}, + config.Spec.MSR2 = &MSR2Config{ + Version: msrV, + ReplicaIDs: "sequential", } config.Spec.Hosts = append(config.Spec.Hosts, diff --git a/pkg/product/mke/api/cluster_spec.go b/pkg/product/mke/api/cluster_spec.go index 6017089b0..0f6b7276f 100644 --- a/pkg/product/mke/api/cluster_spec.go +++ b/pkg/product/mke/api/cluster_spec.go @@ -26,7 +26,8 @@ type Cluster struct { type ClusterSpec struct { Hosts Hosts `yaml:"hosts" validate:"required,min=1,dive"` MKE MKEConfig `yaml:"mke,omitempty"` - MSR *MSRConfig `yaml:"msr,omitempty"` + MSR2 *MSR2Config `yaml:"msr2,omitempty"` + MSR3 *MSR3Config `yaml:"msr3,omitempty"` MCR common.MCRConfig `yaml:"mcr,omitempty"` Cluster Cluster `yaml:"cluster"` // Namespace is the Kubernetes namespace to use for installing products @@ -37,23 +38,24 @@ type ClusterSpec struct { // Workers filters only the workers from the cluster config. func (c *ClusterSpec) Workers() Hosts { - return c.Hosts.Filter(func(h *Host) bool { return h.Role == "worker" }) + return c.Hosts.Filter(func(h *Host) bool { return h.Role == RoleWorker }) } // Managers filters only the manager nodes from the cluster config. func (c *ClusterSpec) Managers() Hosts { - return c.Hosts.Filter(func(h *Host) bool { return h.Role == "manager" }) + return c.Hosts.Filter(func(h *Host) bool { return h.Role == RoleManager }) } -// MSRs filters only the MSR nodes from the cluster config. -func (c *ClusterSpec) MSRs() Hosts { - return c.Hosts.Filter(func(h *Host) bool { return h.Role == "msr" }) +// MSR2s filters only the MSR2 nodes from the cluster config. +func (c *ClusterSpec) MSR2s() Hosts { + return c.Hosts.Filter(func(h *Host) bool { return h.Role == RoleMSR2 }) } -// WorkersAndMSRs filters both worker and MSR roles from the cluster config. +// WorkersAndMSRs filters both worker and MSR roles from the cluster config; +// so anything that is not a manager. func (c *ClusterSpec) WorkersAndMSRs() Hosts { return c.Hosts.Filter(func(h *Host) bool { - return h.Role == "msr" || h.Role == "worker" + return h.Role != RoleManager }) } @@ -75,8 +77,8 @@ var errGenerateURL = errors.New("unable to generate url") // MKEURL returns a URL for MKE or an error if one can not be generated. func (c *ClusterSpec) MKEURL() (*url.URL, error) { // Easy route, user has provided one in MSR --ucp-url - if c.MSR != nil { - if f := c.MSR.V2.InstallFlags.GetValue("--ucp-url"); f != "" { + if c.MSR2 != nil { + if f := c.MSR2.InstallFlags.GetValue("--ucp-url"); f != "" { if !strings.Contains(f, "://") { f = "https://" + f } @@ -123,9 +125,9 @@ func (c *ClusterSpec) MKEURL() (*url.URL, error) { func (c *ClusterSpec) MSR2URL() (*url.URL, error) { var msrAddr string - if c.MSR != nil { + if c.MSR2 != nil { // Default to using the --dtr-external-url if it's set - if f := c.MSR.V2.InstallFlags.GetValue("--dtr-external-url"); f != "" { + if f := c.MSR2.InstallFlags.GetValue("--dtr-external-url"); f != "" { if !strings.Contains(f, "://") { f = "https://" + f } @@ -143,18 +145,18 @@ func (c *ClusterSpec) MSR2URL() (*url.URL, error) { } } - // Otherwise, use MSRLeaderAddress - msrLeader := c.MSRLeader() + // Otherwise, use MSR2LeaderAddress + msrLeader := c.MSR2Leader() if msrLeader == nil { - return nil, fmt.Errorf("%w: no MSR nodes found", errGenerateURL) + return nil, fmt.Errorf("%w: no MSR2 nodes found", errGenerateURL) } msrAddr = msrLeader.Address() - if c.MSR != nil { - if portstr := c.MSR.V2.InstallFlags.GetValue("--replica-https-port"); portstr != "" { + if c.MSR2 != nil { + if portstr := c.MSR2.InstallFlags.GetValue("--replica-https-port"); portstr != "" { p, err := strconv.Atoi(portstr) if err != nil { - return nil, fmt.Errorf("invalid MSR --replica-https-port value '%s': %w", portstr, err) + return nil, fmt.Errorf("invalid MSR2 --replica-https-port value '%s': %w", portstr, err) } msrAddr = fmt.Sprintf("%s:%d", msrAddr, p) } @@ -175,6 +177,14 @@ func (e *invalidConfigError) Error() string { return fmt.Sprintf("invalid configuration: %s", e.reason) } +func (c *ClusterSpec) validateMSRHosts() error { + if c.Hosts.Count(func(h *Host) bool { return h.Role == RoleMSR2 }) > 0 && c.MSR2 == nil { + return &invalidConfigError{fmt.Sprintf("hosts with %q role present, but no spec.%s defined", RoleMSR2, RoleMSR2)} + } + + return nil +} + // UnmarshalYAML sets in some sane defaults when unmarshaling the data from yaml. func (c *ClusterSpec) UnmarshalYAML(unmarshal func(interface{}) error) error { type spec ClusterSpec @@ -186,16 +196,10 @@ func (c *ClusterSpec) UnmarshalYAML(unmarshal func(interface{}) error) error { return fmt.Errorf("failed to unmarshal cluster spec: %w", err) } - if c.Hosts.Count(func(h *Host) bool { return h.Role == "msr" }) > 0 { - if specAlias.MSR == nil { - return &invalidConfigError{"hosts with 'msr' role present, but no spec.msr defined"} - } - if err := defaults.Set(specAlias.MSR); err != nil { - return fmt.Errorf("failed to set defaults for spec.msr: %w", err) - } - } else { - specAlias.MSR = nil - log.Debugf("ignoring spec.msr configuration as there are no hosts having the 'msr' role") + if err := c.validateMSRHosts(); err != nil { + return err + } else if c.MSR2 == nil { + log.Debugf("ignoring spec.msr2 configurations as there are no hosts having %q role", RoleMSR2) } bastionHosts := c.Hosts.Filter(func(h *Host) bool { @@ -243,22 +247,22 @@ func isSwarmLeader(h *Host) bool { // IsMSRInstalled checks to see if MSR is installed on the given host. func IsMSRInstalled(h *Host) bool { - return h.MSRMetadata != nil && h.MSRMetadata.Installed + return h.MSR2Metadata != nil && h.MSR2Metadata.Installed } -// MSRLeader returns the current MSRLeader host. -func (c *ClusterSpec) MSRLeader() *Host { - // MSR doesn't have the concept of leaders during the installation phase, +// MSR2Leader returns the current MSR2Leader host. +func (c *ClusterSpec) MSR2Leader() *Host { + // MSR2 doesn't have the concept of leaders during the installation phase, // but we need to make sure we have a Host to reference during our other // bootstrap operations: Upgrade and Join - msrs := c.MSRs() + msrs := c.MSR2s() h := msrs.Find(IsMSRInstalled) if h != nil { - log.Debugf("%s: found MSR installed, using as leader", h) + log.Debugf("%s: found MSR2 installed, using as leader", h) return h } - log.Debugf("did not find a MSR installation, falling back to the first MSR host") + log.Debugf("did not find a MSR2 installation, falling back to the first MSR2 host") return msrs.First() } @@ -335,15 +339,21 @@ func (c *ClusterSpec) CheckMKEHealthLocal(hosts []*Host) error { return nil } -// ContainsMSR returns true when the config has msr hosts. +func (c *ClusterSpec) containsRole(role RoleType) bool { + return c.Hosts.Find(func(h *Host) bool { return h.Role == role }) != nil +} + +// ContainsMSR checks if the cluster spec contains any version of MSR. func (c *ClusterSpec) ContainsMSR() bool { - return c.Hosts.Find(func(h *Host) bool { return h.Role == "msr" }) != nil + return c.ContainsMSR2() || c.ContainsMSR3() } +// ContainsMSR2 checks if the cluster spec contains MSR2. func (c *ClusterSpec) ContainsMSR2() bool { - return c.ContainsMSR() && c.MSR.MajorVersion() == 2 + return c.containsRole(RoleMSR2) && c.MSR2 != nil } +// ContainsMSR3 checks if the cluster spec contains MSR3. func (c *ClusterSpec) ContainsMSR3() bool { - return c.ContainsMSR() && c.MSR.MajorVersion() == 3 + return c.MSR3 != nil } diff --git a/pkg/product/mke/api/cluster_spec_test.go b/pkg/product/mke/api/cluster_spec_test.go index 8cfad02d2..199f4b169 100644 --- a/pkg/product/mke/api/cluster_spec_test.go +++ b/pkg/product/mke/api/cluster_spec_test.go @@ -16,20 +16,20 @@ var manager = &Host{ Role: "manager", } -var msr = &Host{ +var msr2 = &Host{ Connection: rig.Connection{ SSH: &rig.SSH{ Address: "192.168.1.3", }, }, - Role: "msr", + Role: "msr2", } func TestMKEClusterSpecMKEURLWithoutSan(t *testing.T) { spec := ClusterSpec{ Hosts: []*Host{manager}, MKE: MKEConfig{}, - MSR: &MSRConfig{}, + MSR2: &MSR2Config{}, } url, err := spec.MKEURL() require.NoError(t, err) @@ -42,7 +42,7 @@ func TestMKEClusterSpecMKEURLWithSan(t *testing.T) { MKE: MKEConfig{ InstallFlags: []string{"--san=mke.acme.com"}, }, - MSR: &MSRConfig{}, + MSR2: &MSR2Config{}, } url, err := spec.MKEURL() @@ -66,10 +66,10 @@ func TestMKEClusterSpecMKEURLWithNoMSRMetadata(t *testing.T) { spec := ClusterSpec{ Hosts: []*Host{ manager, - msr, + msr2, }, - MKE: MKEConfig{}, - MSR: &MSRConfig{}, + MKE: MKEConfig{}, + MSR2: &MSR2Config{}, } url, err := spec.MKEURL() @@ -81,10 +81,10 @@ func TestMKEClusterSpecMSR2URLWithNoMSRMetadata(t *testing.T) { spec := ClusterSpec{ Hosts: []*Host{ manager, - msr, + msr2, }, - MKE: MKEConfig{}, - MSR: &MSRConfig{}, + MKE: MKEConfig{}, + MSR2: &MSR2Config{}, } url, err := spec.MSR2URL() @@ -96,7 +96,7 @@ func TestMKEClusterSpecMSR2URLWithNoMSRHostRoleButConfig(t *testing.T) { spec := ClusterSpec{ Hosts: []*Host{manager}, MKE: MKEConfig{}, - MSR: &MSRConfig{}, + MSR2: &MSR2Config{}, } _, err := spec.MSR2URL() require.Error(t, err) @@ -112,12 +112,12 @@ func TestMKEClusterSpecMSR2URLWithoutExternalURL(t *testing.T) { Address: "192.168.1.3", }, }, - Role: "msr", - MSRMetadata: &MSRMetadata{Installed: true}, + Role: "msr2", + MSR2Metadata: &MSR2Metadata{Installed: true}, }, }, - MKE: MKEConfig{}, - MSR: &MSRConfig{}, + MKE: MKEConfig{}, + MSR2: &MSR2Config{}, } url, err := spec.MSR2URL() require.NoError(t, err) @@ -128,11 +128,11 @@ func TestMKEClusterSpecMSR2URLWithExternalURL(t *testing.T) { spec := ClusterSpec{ Hosts: []*Host{ manager, - msr, + msr2, }, MKE: MKEConfig{}, - MSR: &MSRConfig{ - V2: MSR2Config{InstallFlags: []string{"--dtr-external-url msr.acme.com"}}, + MSR2: &MSR2Config{ + InstallFlags: []string{"--dtr-external-url msr.acme.com"}, }, } url, err := spec.MSR2URL() @@ -144,11 +144,11 @@ func TestMKEClusterSpecMSRURLWithPort(t *testing.T) { spec := ClusterSpec{ Hosts: []*Host{ manager, - msr, + msr2, }, MKE: MKEConfig{}, - MSR: &MSRConfig{ - V2: MSR2Config{InstallFlags: []string{"--replica-https-port 999"}}, + MSR2: &MSR2Config{ + InstallFlags: []string{"--replica-https-port 999"}, }, } url, err := spec.MSR2URL() @@ -172,11 +172,11 @@ func TestMKEClusterSpecMKEURLFromMSRMKEUrl(t *testing.T) { spec := ClusterSpec{ Hosts: []*Host{ manager, - msr, + msr2, }, MKE: MKEConfig{}, - MSR: &MSRConfig{ - V2: MSR2Config{InstallFlags: []string{"--ucp-url mke.acme.com:5555"}}, + MSR2: &MSR2Config{ + InstallFlags: []string{"--ucp-url mke.acme.com:5555"}, }, } url, err := spec.MKEURL() diff --git a/pkg/product/mke/api/cluster_test.go b/pkg/product/mke/api/cluster_test.go index 1558e2c68..dea07377c 100644 --- a/pkg/product/mke/api/cluster_test.go +++ b/pkg/product/mke/api/cluster_test.go @@ -7,6 +7,8 @@ import ( "strings" "testing" + "github.com/sirupsen/logrus" + "github.com/Mirantis/mcc/pkg/config/migration" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -21,6 +23,8 @@ import ( // needed to load the migrators. _ "github.com/Mirantis/mcc/pkg/config/migration/v14" // needed to load the migrators. + _ "github.com/Mirantis/mcc/pkg/config/migration/v15" + // needed to load the migrators. _ "github.com/Mirantis/mcc/pkg/config/migration/v1beta1" // needed to load the migrators. _ "github.com/Mirantis/mcc/pkg/config/migration/v1beta2" @@ -36,7 +40,7 @@ func TestHostRequireManagerValidationPass(t *testing.T) { kf, _ := os.CreateTemp("", "testkey") defer kf.Close() data := ` -apiVersion: "launchpad.mirantis.com/mke/v1.5" +apiVersion: "launchpad.mirantis.com/mke/v1.6" kind: mke spec: hosts: @@ -235,6 +239,8 @@ spec: } func TestMigrateFromV1Beta1(t *testing.T) { + logrus.SetLevel(logrus.DebugLevel) + data := ` apiVersion: launchpad.mirantis.com/v1beta1 kind: mke @@ -253,7 +259,7 @@ spec: c := loadAndMigrateYaml(t, data) err := c.Validate() require.NoError(t, err) - require.Equal(t, "launchpad.mirantis.com/mke/v1.5", c.APIVersion) + require.Equal(t, "launchpad.mirantis.com/mke/v1.6", c.APIVersion) require.Equal(t, c.Spec.MCR.InstallURLLinux, "http://example.com/") require.Equal(t, c.Spec.Hosts[0].SSH.Port, 9022) @@ -278,7 +284,7 @@ spec: ` c := loadAndMigrateYaml(t, data) require.NoError(t, c.Validate()) - require.Equal(t, "launchpad.mirantis.com/mke/v1.5", c.APIVersion) + require.Equal(t, "launchpad.mirantis.com/mke/v1.6", c.APIVersion) } func TestMigrateFromV1Beta1WithoutInstallURL(t *testing.T) { @@ -300,7 +306,7 @@ spec: c := loadAndMigrateYaml(t, data) err := c.Validate() require.NoError(t, err) - require.Equal(t, "launchpad.mirantis.com/mke/v1.5", c.APIVersion) + require.Equal(t, "launchpad.mirantis.com/mke/v1.6", c.APIVersion) require.Equal(t, constant.MCRInstallURLLinux, c.Spec.MCR.InstallURLLinux) require.Equal(t, 9022, c.Spec.Hosts[0].SSH.Port) @@ -386,7 +392,7 @@ spec: func TestHostWinRMDefaults(t *testing.T) { data := ` -apiVersion: launchpad.mirantis.com/mke/v1.5 +apiVersion: launchpad.mirantis.com/mke/v1.6 kind: mke spec: mke: @@ -408,10 +414,10 @@ spec: require.Equal(t, c.Spec.Hosts[0].WinRM.Insecure, false) } -func TestValidationWithMSRRole(t *testing.T) { +func TestValidationWithMSR2Role(t *testing.T) { kf, _ := os.CreateTemp("", "testkey") defer kf.Close() - t.Run("the role is not ucp, worker or msr", func(t *testing.T) { + t.Run("the role is not ucp, worker or msr2", func(t *testing.T) { data := ` apiVersion: launchpad.mirantis.com/mke/v1.4 kind: mke @@ -433,20 +439,20 @@ spec: require.Error(t, c.Validate()) }) - t.Run("the role is msr", func(t *testing.T) { + t.Run("the role is msr2", func(t *testing.T) { data := ` -apiVersion: launchpad.mirantis.com/mke/v1.5 +apiVersion: launchpad.mirantis.com/mke/v1.6 kind: mke+msr spec: mke: version: 3.3.7 - msr: + msr2: version: 2.8.5 hosts: - ssh: address: "10.0.0.1" keyPath: ` + kf.Name() + ` - role: msr + role: msr2 - ssh: address: "10.0.0.2" keyPath: ` + kf.Name() + ` @@ -467,12 +473,12 @@ func TestValidationWithMSR3(t *testing.T) { // section. The resulting data is valid // yaml. data := ` -apiVersion: launchpad.mirantis.com/mke/v1.5 +apiVersion: launchpad.mirantis.com/mke/v1.6 kind: mke+msr spec: mke: version: 3.3.7 - msr: + msr3: version: 3.1.4 storageURL: "https://example.com" storageClassType: "nfs" @@ -484,19 +490,19 @@ spec: hosts: - ssh: address: "10.0.0.1" - role: msr + role: msr3 - ssh: address: "10.0.0.2" role: manager ` c := loadYaml(t, data) - require.Equal(t, c.Spec.MSR.V3.StorageURL, "https://example.com") - require.Equal(t, c.Spec.MSR.V3.StorageClassType, "nfs") - require.Equal(t, c.Spec.MSR.V3.CRD.GetAPIVersion(), "msr.mirantis.com/v1") - require.Equal(t, c.Spec.MSR.V3.CRD.GetKind(), "MSR") + require.Equal(t, c.Spec.MSR3.StorageURL, "https://example.com") + require.Equal(t, c.Spec.MSR3.StorageClassType, "nfs") + require.Equal(t, c.Spec.MSR3.CRD.GetAPIVersion(), "msr.mirantis.com/v1") + require.Equal(t, c.Spec.MSR3.CRD.GetKind(), "MSR") - actual, found, err := unstructured.NestedString(c.Spec.MSR.V3.CRD.Object, "spec", "logLevel") + actual, found, err := unstructured.NestedString(c.Spec.MSR3.CRD.Object, "spec", "logLevel") require.True(t, found) require.NoError(t, err) require.Equal(t, actual, "debug") @@ -511,13 +517,13 @@ kind: mke+msr spec: mke: version: 3.3.7 - msr: + msr3: version: 3.1.4 storageClassType: "nfs" hosts: - ssh: address: "10.0.0.1" - role: msr + role: msr3 - ssh: address: "10.0.0.2" role: manager @@ -535,14 +541,14 @@ kind: mke+msr spec: mke: version: 3.3.7 - msr: + msr3: version: 3.1.4 storageURL: "https://example.com" storageClassType: "not-supported" hosts: - ssh: address: "10.0.0.1" - role: msr + role: msr3 - ssh: address: "10.0.0.2" role: manager diff --git a/pkg/product/mke/api/host.go b/pkg/product/mke/api/host.go index 3e6c40fb7..61abd0d92 100644 --- a/pkg/product/mke/api/host.go +++ b/pkg/product/mke/api/host.go @@ -10,7 +10,6 @@ import ( "strings" "time" - "github.com/Mirantis/mcc/pkg/helm" common "github.com/Mirantis/mcc/pkg/product/common/api" "github.com/Mirantis/mcc/pkg/util/byteutil" retry "github.com/avast/retry-go" @@ -35,27 +34,13 @@ type HostMetadata struct { MCRInstalled bool } -// MSRMetadata is metadata needed by MSR for configuration and is gathered at -// the GatherFacts phase and at the end of each configuration phase. -type MSRMetadata struct { - Installed bool - InstalledVersion string - - MSR2 MSR2Metadata - MSR3 MSR3Metadata -} - type MSR2Metadata struct { + Installed bool + InstalledVersion string InstalledBootstrapImage string ReplicaID string } -type MSR3Metadata struct { - // InstalledDependencies is a map of dependencies needed for MSR3 and their - // versions. - InstalledDependencies map[string]helm.ReleaseDetails -} - type errs struct { errors []string } @@ -80,11 +65,48 @@ func (e *errs) String() string { return "- " + strings.Join(e.errors, "\n- ") } +// RoleType represents the types of host roles we accept. +type RoleType string + +const ( + RoleManager RoleType = "manager" + RoleWorker RoleType = "worker" + RoleMSR2 RoleType = "msr2" +) + +var AcceptedRoleTypes = []string{ + string(RoleManager), + string(RoleWorker), + string(RoleMSR2), +} + +type UnknownRoleTypeError struct { + RoleType string +} + +func (e UnknownRoleTypeError) Error() string { + return fmt.Sprintf("unknown role type: %s, must be one of: %s", e.RoleType, strings.Join(AcceptedRoleTypes, ", ")) +} + +// ParseRoleType takes a string and returns the RoleType associated with the +// given string if one exists. +func ParseRoleType(value string) (RoleType, error) { + for _, rt := range AcceptedRoleTypes { + if value == rt { + return RoleType(rt), nil + } + } + + // We should never get here because we do validation on the incoming + // yaml, but just in case. + return "", UnknownRoleTypeError{RoleType: value} +} + // Host contains all the needed details to work with hosts. type Host struct { rig.Connection `yaml:",inline"` - Role string `yaml:"role" validate:"oneof=manager worker msr"` + Role RoleType `yaml:"role" validate:"oneof=manager worker msr2 msr3"` PrivateInterface string `yaml:"privateInterface,omitempty" validate:"omitempty,gt=2"` DaemonConfig dig.Mapping `yaml:"mcrConfig,flow,omitempty" default:"{}"` Environment map[string]string `yaml:"environment,flow,omitempty" default:"{}"` @@ -92,10 +114,10 @@ type Host struct { ImageDir string `yaml:"imageDir,omitempty"` SudoDocker bool `yaml:"sudodocker"` - Metadata *HostMetadata `yaml:"-"` - MSRMetadata *MSRMetadata `yaml:"-"` - Configurer HostConfigurer `yaml:"-"` - Errors errs `yaml:"-"` + Metadata *HostMetadata `yaml:"-"` + MSR2Metadata *MSR2Metadata `yaml:"-"` + Configurer HostConfigurer `yaml:"-"` + Errors errs `yaml:"-"` } // UnmarshalYAML sets in some sane defaults when unmarshaling the data from yaml. diff --git a/pkg/product/mke/api/hosts_test.go b/pkg/product/mke/api/hosts_test.go index b27fee10b..4bfef16cc 100644 --- a/pkg/product/mke/api/hosts_test.go +++ b/pkg/product/mke/api/hosts_test.go @@ -45,11 +45,11 @@ func TestFilter(t *testing.T) { }) require.Len(t, managers, 2) - require.Equal(t, managers[0].Role, "manager") - require.Equal(t, managers[1].Role, "manager") + require.Equal(t, managers[0].Role, RoleType("manager")) + require.Equal(t, managers[1].Role, RoleType("manager")) require.Len(t, workers, 1) - require.Equal(t, workers[0].Role, "worker") + require.Equal(t, workers[0].Role, RoleType("worker")) } func TestFind(t *testing.T) { diff --git a/pkg/product/mke/api/msr_config.go b/pkg/product/mke/api/msr_config.go index 14675fad5..21a93da8a 100644 --- a/pkg/product/mke/api/msr_config.go +++ b/pkg/product/mke/api/msr_config.go @@ -12,25 +12,13 @@ import ( "github.com/Mirantis/mcc/pkg/util/maputil" "github.com/creasty/defaults" "github.com/hashicorp/go-version" + log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -// MSRConfig defines the configuration for MSR, each product version has it's -// own set of configuration options, but the options are inlined into the parent -// 'msr' key for ease of use when crafting the yaml configuration. Due to this -// constraint, ensure that different version configurations do not have -// overlapping struct tags. -type MSRConfig struct { - Version string `yaml:"version" validate:"required"` - - // V2 defines the configuration for MSR V2. - V2 MSR2Config `yaml:",inline"` - // V3 defines the configuration for MSR V3. - V3 MSR3Config `yaml:",inline"` -} - -// MSRConfig has all the bits needed to configure MSR (V2) during installation. +// MSR2Config has all the bits needed to configure MSR (V2) during installation. type MSR2Config struct { + Version string `yaml:"version,omitempty"` ImageRepo string `yaml:"imageRepo,omitempty"` InstallFlags common.Flags `yaml:"installFlags,flow,omitempty"` UpgradeFlags common.Flags `yaml:"upgradeFlags,flow,omitempty"` @@ -46,6 +34,8 @@ type MSR2Config struct { // MSR3Config defines the configuration for both the MSR3 CR and the // dependencies needed to run it. type MSR3Config struct { + // Version is the MSR3 version to install. + Version string `yaml:"version,omitempty"` // Dependencies define strict dependencies that MSR3 needs to function. Dependencies `yaml:"dependencies,omitempty"` // StorageClassType allows users to have launchpad configure a StorageClass @@ -59,6 +49,16 @@ type MSR3Config struct { LoadBalancerURL string `yaml:"loadBalancerURL,omitempty"` // CRD is the MSR custom resource definition which configures the MSR CR. CRD *unstructured.Unstructured `yaml:"crd,omitempty"` + + Metadata MSR3Metadata `yaml:"-"` +} + +type MSR3Metadata struct { + Installed bool + InstalledVersion string + // InstalledDependencies is a map of dependencies needed for MSR3 and their + // versions. + InstalledDependencies map[string]helm.ReleaseDetails } var errStorageURLRequired = fmt.Errorf("spec.msr.storageURL is required when spec.msr.storageClassType is populated") @@ -183,9 +183,9 @@ func (d *Dependencies) SetDefaults() { var errUnexpectedTypeForCRD = errors.New("unexpected type for CRD: expected map") // UnmarshalYAML sets in some sane defaults when unmarshaling the data from yaml. -func (c *MSRConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { - type msr MSRConfig - yc := (*msr)(c) +func (c *MSR3Config) UnmarshalYAML(unmarshal func(interface{}) error) error { + type msr3 MSR3Config + yc := (*msr3)(c) if err := unmarshal(yc); err != nil { return fmt.Errorf("failed to unmarshal MSR configuration: %w", err) } @@ -209,96 +209,76 @@ func (c *MSRConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { } // Convert the map[interface{}]interface{} to map[string]interface{}. - c.V3.CRD = &unstructured.Unstructured{Object: maputil.ConvertInterfaceMap(mapObj)} + c.CRD = &unstructured.Unstructured{Object: maputil.ConvertInterfaceMap(mapObj)} } if err := defaults.Set(c); err != nil { return fmt.Errorf("failed to set defaults for MSR configuration: %w", err) } - return c.setConfigForVersion() -} + if err := c.Validate(); err != nil { + return fmt.Errorf("failed to validate MSR configuration: %w", err) + } -var errUnsupportedMSRVersion = errors.New("unsupported MSR major version: must be either 2 or 3") + c.Dependencies.SetDefaults() -func (c *MSRConfig) setConfigForVersion() error { - switch c.MajorVersion() { - case 2: - if c.V2.CACertPath != "" { - caCertData, err := fileutil.LoadExternalFile(c.V2.CACertPath) - if err != nil { - return fmt.Errorf("failed to load CA cert data: %w", err) - } - c.V2.CACertData = string(caCertData) - } + return nil +} - if c.V2.CertPath != "" { - certData, err := fileutil.LoadExternalFile(c.V2.CertPath) - if err != nil { - return fmt.Errorf("failed to load cert data: %w", err) - } - c.V2.CertData = string(certData) +// SetDefaults sets default values. +func (c *MSR2Config) SetDefaults() error { + if c.CACertPath != "" { + caCertData, err := fileutil.LoadExternalFile(c.CACertPath) + if err != nil { + return fmt.Errorf("failed to load CA cert data: %w", err) } + c.CACertData = string(caCertData) + } - if c.V2.KeyPath != "" { - keyData, err := fileutil.LoadExternalFile(c.V2.KeyPath) - if err != nil { - return fmt.Errorf("failed to load key data: %w", err) - } - c.V2.KeyData = string(keyData) + if c.CertPath != "" { + certData, err := fileutil.LoadExternalFile(c.CertPath) + if err != nil { + return fmt.Errorf("failed to load cert data: %w", err) } + c.CertData = string(certData) + } - case 3: - if err := c.V3.Validate(); err != nil { - return fmt.Errorf("failed to validate MSR configuration: %w", err) + if c.KeyPath != "" { + keyData, err := fileutil.LoadExternalFile(c.KeyPath) + if err != nil { + return fmt.Errorf("failed to load key data: %w", err) } - - c.V3.Dependencies.SetDefaults() - - default: - return errUnsupportedMSRVersion + c.KeyData = string(keyData) } - return nil -} + if c.ImageRepo == "" { + c.ImageRepo = constant.ImageRepo -// MajorVersion returns the major version of MSR, or 0 if the version is invalid. -func (c *MSRConfig) MajorVersion() int { - if c == nil { - return 0 + fmt.Println("ImageRepo is empty") } v, err := version.NewVersion(c.Version) if err != nil { - return 0 + log.Debugf("Failed to parse version: %s, will fallback to using imageRepo: %s", c.Version, constant.ImageRepo) + // If we encounter an error here just default to using + // constant.ImageRepo. + return nil } - return v.Segments()[0] -} - -// SetDefaults sets default values. -func (c *MSRConfig) SetDefaults() { - if c.V2.ImageRepo == "" { - c.V2.ImageRepo = constant.ImageRepo - } - - v, err := version.NewVersion(c.Version) - if err != nil { - return + if c.ImageRepo == constant.ImageRepo && c.UseLegacyImageRepo(v) { + c.ImageRepo = constant.ImageRepoLegacy } - if c.V2.ImageRepo == constant.ImageRepo && c.UseLegacyImageRepo(v) { - c.V2.ImageRepo = constant.ImageRepoLegacy - } + return nil } // GetBootstrapperImage combines the bootstrapper image name based on user given config. -func (c *MSRConfig) GetBootstrapperImage() string { - return fmt.Sprintf("%s/dtr:%s", c.V2.ImageRepo, c.Version) +func (c *MSR2Config) GetBootstrapperImage() string { + return fmt.Sprintf("%s/dtr:%s", c.ImageRepo, c.Version) } // UseLegacyImageRepo returns true if the version number does not satisfy >= 2.8.2 || >= 2.7.8 || >= 2.6.15. -func (c *MSRConfig) UseLegacyImageRepo(v *version.Version) bool { +func (c *MSR2Config) UseLegacyImageRepo(v *version.Version) bool { // Strip out anything after -, seems like go-version thinks vs := v.String() var v2 *version.Version @@ -313,3 +293,28 @@ func (c *MSRConfig) UseLegacyImageRepo(v *version.Version) bool { c3, _ := version.NewConstraint("< 2.7, >= 2.6.15") return !(c1.Check(v2) || c2.Check(v2) || c3.Check(v2)) } + +var errInvalidMSR2Config = fmt.Errorf("invalid MSR2 config") + +// UnmarshalYAML sets in some sane defaults when unmarshaling the data from yaml. +func (c *MSR2Config) UnmarshalYAML(unmarshal func(interface{}) error) error { + type msr2 MSR2Config + yc := (*msr2)(c) + if err := unmarshal(yc); err != nil { + return err + } + + if c.Version == "" { + return fmt.Errorf("%w: missing spec.msr.version", errInvalidMSR2Config) + } + + if _, err := version.NewVersion(c.Version); err != nil { + return fmt.Errorf("%w: error in field spec.msr.version: %w", errInvalidMSR2Config, err) + } + + if err := defaults.Set(c); err != nil { + return fmt.Errorf("set MSR2 defaults: %w", err) + } + + return c.SetDefaults() +} diff --git a/pkg/product/mke/api/msr_config_test.go b/pkg/product/mke/api/msr_config_test.go index 131285383..608d38239 100644 --- a/pkg/product/mke/api/msr_config_test.go +++ b/pkg/product/mke/api/msr_config_test.go @@ -6,16 +6,14 @@ import ( "testing" "github.com/hashicorp/go-version" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/yaml.v2" "github.com/Mirantis/mcc/pkg/constant" ) -func TestMSRConfig_UseLegacyImageRepo(t *testing.T) { - cfg := MSRConfig{} - // >=3.1.15 || >=3.2.8 || >=3.3.2 is "mirantis" +func TestMSR2Config_UseLegacyImageRepo(t *testing.T) { + cfg := MSR2Config{} legacyVersions := []string{ "2.8.1", "2.7.7", @@ -44,43 +42,29 @@ func TestMSRConfig_UseLegacyImageRepo(t *testing.T) { } } -func TestMSRConfig_LegacyDefaultVersionRepo(t *testing.T) { - cfg := MSRConfig{} +func TestMSR2Config_LegacyDefaultVersionRepo(t *testing.T) { + cfg := MSR2Config{} err := yaml.Unmarshal([]byte("version: 2.8.1"), &cfg) require.NoError(t, err) - require.Equal(t, constant.ImageRepoLegacy, cfg.V2.ImageRepo) + require.Equal(t, constant.ImageRepoLegacy, cfg.ImageRepo) } -func TestMSRConfig_ModernDefaultVersionRepo(t *testing.T) { - cfg := MSRConfig{} +func TestMSR2Config_ModernDefaultVersionRepo(t *testing.T) { + cfg := MSR2Config{} err := yaml.Unmarshal([]byte("version: 2.8.2"), &cfg) require.NoError(t, err) - require.Equal(t, constant.ImageRepo, cfg.V2.ImageRepo) + require.Equal(t, constant.ImageRepo, cfg.ImageRepo) } -func TestMSRConfig_CustomRepo(t *testing.T) { - cfg := MSRConfig{} +func TestMSR2Config_CustomRepo(t *testing.T) { + cfg := MSR2Config{} err := yaml.Unmarshal([]byte("version: 2.8.2\nimageRepo: foo.foo/foo"), &cfg) require.NoError(t, err) - require.Equal(t, "foo.foo/foo", cfg.V2.ImageRepo) - cfg = MSRConfig{} + require.Equal(t, "foo.foo/foo", cfg.ImageRepo) + cfg = MSR2Config{} err = yaml.Unmarshal([]byte("version: 2.8.1\nimageRepo: foo.foo/foo"), &cfg) require.NoError(t, err) - require.Equal(t, "foo.foo/foo", cfg.V2.ImageRepo) -} - -// TestMSRConfig_YAMLKeysDoNotOverlap tests that the yaml keys in MSR2Config and -// MSR3Config do not overlap. This is important as the MSR2 and MSR3 configs -// are inlined under the 'msr' parent key. During unmarshaling, the yaml -// keys should be unique to ensure that the correct version structs are -// appropriately populated. -func TestMSRConfig_YAMLKeysDoNotOverlap(t *testing.T) { - a := extractYAMLTags(t, MSR2Config{}) - b := extractYAMLTags(t, MSR3Config{}) - - for _, key := range a { - assert.NotContainsf(t, b, key, "yaml tag: %q should not exist in both MSR2Config and MSR3Config types", key) - } + require.Equal(t, "foo.foo/foo", cfg.ImageRepo) } // extractYAML tags iterates v's struct fields and returns a sorted slice of diff --git a/pkg/product/mke/apply.go b/pkg/product/mke/apply.go index 94e4da59f..cd3a9e4fa 100644 --- a/pkg/product/mke/apply.go +++ b/pkg/product/mke/apply.go @@ -41,28 +41,29 @@ func (p *MKE) Apply(disableCleanup, force bool, concurrency int, forceUpgrade bo &mke.UpgradeMKE{}, &mke.JoinManagers{}, &mke.JoinWorkers{}, + &mke.ValidateMKEHealth{}, ) - // Determine which MSR phases to run based on the MSR version. - switch p.ClusterConfig.Spec.MSR.MajorVersion() { - case 2: + // Determine which MSR phases to run based on the MSR spec provided in the + // config. + if p.ClusterConfig.Spec.ContainsMSR2() { phaseManager.AddPhases( - &mke.PullMSRImages{}, - &mke.ValidateMKEHealth{}, - &mke.InstallMSR{}, - &mke.UpgradeMSR{}, - &mke.JoinMSRReplicas{}, + &mke.PullMSR2Images{}, + &mke.InstallMSR2{}, + &mke.UpgradeMSR2{}, + &mke.JoinMSR2Replicas{}, ) - case 3: + } + + if p.ClusterConfig.Spec.ContainsMSR3() { phaseManager.AddPhases( - &mke.ValidateMKEHealth{}, &mke.ConfigureDepsMSR3{}, &mke.ConfigureStorageProvisioner{}, &mke.InstallOrUpgradeMSR3{}, ) } - // Add the remaining phases that run regardless of MSR version. + // Add the remaining phases that run after the MKE and MSR phases. phaseManager.AddPhases( &mke.LabelNodes{}, &mke.RemoveNodes{}, @@ -90,7 +91,7 @@ func (p *MKE) Apply(disableCleanup, force bool, concurrency int, forceUpgrade bo "api_version": p.ClusterConfig.APIVersion, "hosts": len(p.ClusterConfig.Spec.Hosts), "managers": len(p.ClusterConfig.Spec.Managers()), - "dtrs": len(p.ClusterConfig.Spec.MSRs()), + "dtrs": len(p.ClusterConfig.Spec.MSR2s()), "linux_workers": linuxWorkersCount, "windows_workers": windowsWorkersCount, "engine_version": p.ClusterConfig.Spec.MCR.Version, diff --git a/pkg/product/mke/exec.go b/pkg/product/mke/exec.go index 8b688b207..e81e055a5 100644 --- a/pkg/product/mke/exec.go +++ b/pkg/product/mke/exec.go @@ -18,7 +18,7 @@ import ( var errInvalidTarget = errors.New("invalid target") // Exec runs commands or shell sessions on a configuration host. -func (p *MKE) Exec(targets []string, interactive, first, all, parallel bool, role, hostos, cmd string) error { //nolint:maintidx +func (p *MKE) Exec(targets []string, interactive, first, all, parallel bool, role api.RoleType, hostos, cmd string) error { //nolint:maintidx var hosts api.Hosts for _, target := range targets { diff --git a/pkg/product/mke/phase/configure_deps_msr3.go b/pkg/product/mke/phase/configure_deps_msr3.go index e10844f8a..cd4318501 100644 --- a/pkg/product/mke/phase/configure_deps_msr3.go +++ b/pkg/product/mke/phase/configure_deps_msr3.go @@ -40,7 +40,7 @@ func (p *ConfigureDepsMSR3) Prepare(config interface{}) error { return fmt.Errorf("failed to get kube and helm clients: %w", err) } - for _, releaseDetails := range p.Config.Spec.MSR.V3.Dependencies.List() { + for _, releaseDetails := range p.Config.Spec.MSR3.Dependencies.List() { vers, err := version.NewSemver(releaseDetails.Version) if err != nil { // We should never get here, we should be parsing the version prior diff --git a/pkg/product/mke/phase/configure_provisioner_msr3.go b/pkg/product/mke/phase/configure_provisioner_msr3.go index ac8cb32d5..27f60da05 100644 --- a/pkg/product/mke/phase/configure_provisioner_msr3.go +++ b/pkg/product/mke/phase/configure_provisioner_msr3.go @@ -35,7 +35,7 @@ func (p *ConfigureStorageProvisioner) Prepare(config interface{}) error { return err } - p.leader = p.Config.Spec.MSRLeader() + p.leader = p.Config.Spec.MSR2Leader() p.Kube, p.Helm, err = mke.KubeAndHelmFromConfig(p.Config) if err != nil { @@ -75,7 +75,7 @@ func (p *ConfigureStorageProvisioner) Prepare(config interface{}) error { func (p *ConfigureStorageProvisioner) ShouldRun() bool { return p.Config.Spec.ContainsMSR3() && - p.Config.Spec.MSR.V3.ShouldConfigureStorageClass() && + p.Config.Spec.MSR3.ShouldConfigureStorageClass() && p.storageProvisioner != nil } @@ -110,7 +110,7 @@ type storageProvisioner struct { func storageProvisionerChart(config *api.ClusterConfig) *storageProvisioner { // TODO: Currently we only support "nfs" as a configured StorageClassType, // we should add some more. - scType := config.Spec.MSR.V3.StorageClassType + scType := config.Spec.MSR3.StorageClassType if scType == "" { log.Debugf("no storage class type configured, not configuring default storage class") @@ -129,7 +129,7 @@ func storageProvisionerChart(config *api.ClusterConfig) *storageProvisioner { RepoURL: "https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/", Values: map[string]interface{}{ "nfs": map[string]string{ - "server": config.Spec.MSR.V3.StorageURL, + "server": config.Spec.MSR3.StorageURL, "path": "/", "volumeName": "nfs-subdir-external-provisioner-root", }, diff --git a/pkg/product/mke/phase/describe.go b/pkg/product/mke/phase/describe.go index 7fa4c125d..a121f2db9 100644 --- a/pkg/product/mke/phase/describe.go +++ b/pkg/product/mke/phase/describe.go @@ -62,38 +62,47 @@ func (p *Describe) mkeReport() { } func (p *Describe) msrReport() { - msrLeader := p.Config.Spec.MSRLeader() - if msrLeader == nil || msrLeader.MSRMetadata == nil || !msrLeader.MSRMetadata.Installed { - fmt.Println("Not installed") - return - } - + // Configure the columns to start. tabWriter := new(tabwriter.Writer) - // minwidth, tabwidth, padding, padchar, flags tabWriter.Init(os.Stdout, 8, 8, 1, '\t', 0) - fmt.Fprintf(tabWriter, "%s\t%s\t\n", "VERSION", "ADMIN_UI") - installedVersion := msrLeader.MSRMetadata.InstalledVersion - msrURL := "n/a" - var err error + // Populate potential MSR2 entries. + msr2Leader := p.Config.Spec.MSR2Leader() + msr2URL := "n/a" - switch p.Config.Spec.MSR.MajorVersion() { - case 2: + if msr2Leader == nil || msr2Leader.MSR2Metadata == nil || !msr2Leader.MSR2Metadata.Installed { + // If no MSR2 is installed just don't add anything to the writer. + log.Debugf("No MSR2 products found") + } else { if url, err := p.Config.Spec.MSR2URL(); err != nil { log.Infof("failed to get MSR URL: %s", err) } else { - msrURL = url.String() + msr2URL = url.String() } - case 3: - msrURL, err = msr3.GetMSRURL(p.Config) + + msr2InstalledVersion := msr2Leader.MSR2Metadata.InstalledVersion + + fmt.Fprintf(tabWriter, "%s\t%s\t\n", msr2InstalledVersion, msr2URL) + } + + if p.Config.Spec.MSR3 == nil || !p.Config.Spec.MSR3.Metadata.Installed { + // If no MSR3 is installed just don't add anything to the writer. + log.Debugf("No MSR3 products found") + } else { + // Populate potential MSR3 entries. + msr3URL, err := msr3.GetMSRURL(p.Config) if err != nil { log.Infof("failed to get MSR URL: %s", err) } + + msr3InstalledVersion := p.Config.Spec.MSR3.Metadata.InstalledVersion + + fmt.Fprintf(tabWriter, "%s\t%s\t\n", msr3InstalledVersion, msr3URL) } - fmt.Fprintf(tabWriter, "%s\t%s\t\n", installedVersion, msrURL) + // Flush the added entries to the writer. tabWriter.Flush() } diff --git a/pkg/product/mke/phase/gather_facts.go b/pkg/product/mke/phase/gather_facts.go index 984f2e749..1df1a456e 100644 --- a/pkg/product/mke/phase/gather_facts.go +++ b/pkg/product/mke/phase/gather_facts.go @@ -31,11 +31,6 @@ import ( log "github.com/sirupsen/logrus" ) -var ( - errCannotInstallMSR3WithMSR2 = errors.New("cannot install MSR v2 when MSR v3 is already installed, please uninstall MSR v3 first or modify the 'spec.msr.version' field in the cluster configuration to a 3.x version") - errCannotInstallMSR2WithMSR3 = errors.New("cannot install MSR v3 when MSR v2 is already installed, if you wish to migrate to MSR v3 please use the Mirantis Migration Tool (mmt) or modify the 'spec.msr.version' field in the cluster configuration to a 2.x version") -) - // GatherFacts phase implementation to collect facts (OS, version etc.) from hosts. type GatherFacts struct { phase.Analytics @@ -70,50 +65,30 @@ func (p *GatherFacts) Run() error { p.Config.Spec.MKE.Metadata.ClusterID = swarm.ClusterID(swarmLeader) } - if p.Config.Spec.ContainsMSR() { - // If we intend to configure MSR as well, gather facts for MSR - if p.Config.Spec.MSR == nil { - p.Config.Spec.MSR = &api.MSRConfig{} - } - - // Collect both sets of facts to understand versions of MSR on the - // target hosts, this is to ensure that if the user has MSR2 already - // installed but is trying to install MSR3 we can tell them that they - // need to use 'mmt' to migrate the versions. The same goes for MSR3 - // to MSR2, if they have MSR3 installed and are trying to install MSR2 - // we can tell them they need to manually uninstall MSR3 first. - // TODO: Perhaps we can call mmt for them in the future. - msr2Installed := p.collectMSR2Facts() - msr3Installed := p.collectMSR3Facts() - - if msr3Installed && p.Config.Spec.MSR.MajorVersion() == 2 { - return errCannotInstallMSR2WithMSR3 - } + if p.Config.Spec.MSR2 != nil { + p.collectMSR2Facts() + } - if msr2Installed && p.Config.Spec.MSR.MajorVersion() == 3 { - return errCannotInstallMSR3WithMSR2 - } + if p.Config.Spec.MSR3 != nil { + p.collectMSR3Facts() } return nil } // collectMSR2Facts collects MSR2 facts from the hosts populating the host -// metadata struct, returning true if MSR2 is installed. -func (p *GatherFacts) collectMSR2Facts() bool { - var installed bool - - msrHosts := p.Config.Spec.MSRs() +// metadata struct. +func (p *GatherFacts) collectMSR2Facts() { + msrHosts := p.Config.Spec.MSR2s() err := msrHosts.ParallelEach(func(h *api.Host) error { if h.Metadata != nil && h.Metadata.MCRVersion != "" { - msrMeta, err := msr2.CollectFacts(h) + msr2Meta, err := msr2.CollectFacts(h) if err != nil { log.Debugf("%s: failed to collect existing MSR details: %s", h, err.Error()) } - h.MSRMetadata = msrMeta - if msrMeta.Installed { - log.Infof("%s: MSR has version %s", h, msrMeta.InstalledVersion) - installed = true + h.MSR2Metadata = msr2Meta + if msr2Meta.Installed { + log.Infof("%s: MSR has version %s", h, msr2Meta.InstalledVersion) } else { log.Infof("%s: MSR is not installed", h) } @@ -123,8 +98,6 @@ func (p *GatherFacts) collectMSR2Facts() bool { if err != nil { log.Debugf("failed to collect existing MSR details across MSR hosts: %s", err.Error()) } - - return installed } // collectMSR3Facts collects MSR3 facts from the hosts populating the host @@ -147,31 +120,21 @@ func (p *GatherFacts) collectMSR3Facts() bool { return false } - msrMeta, err := msr3.CollectFacts(context.Background(), p.Config.Spec.MSR.V3.CRD.GetName(), kubeClient, rc, helmClient, kubeclient.WithCustomWait(1, time.Second*30)) + msr3Meta, err := msr3.CollectFacts(context.Background(), p.Config.Spec.MSR3.CRD.GetName(), kubeClient, rc, helmClient, kubeclient.WithCustomWait(1, time.Second*30)) if err != nil { log.Debugf("failed to collect existing MSR details: %s", err.Error()) return false } - if msrMeta.Installed { - log.Infof("MSR has version %s", msrMeta.InstalledVersion) + if msr3Meta.Installed { + log.Infof("MSR has version %s", msr3Meta.InstalledVersion) } else { log.Info("MSR is not installed") } - // Write the msrMeta to each of the hosts so it can be queried - // independently and function like legacy MSR. - msrHosts := p.Config.Spec.MSRs() - err = msrHosts.ParallelEach(func(h *api.Host) error { - h.MSRMetadata = msrMeta - return nil - }) - if err != nil { - log.Debugf("failed to write MSR metadata to MSR hosts: %s", err.Error()) - return false - } + p.Config.Spec.MSR3.Metadata = msr3Meta - return msrMeta.Installed + return msr3Meta.Installed } var errInvalidIP = errors.New("invalid IP address") diff --git a/pkg/product/mke/phase/info.go b/pkg/product/mke/phase/info.go index c24214e84..5696817df 100644 --- a/pkg/product/mke/phase/info.go +++ b/pkg/product/mke/phase/info.go @@ -25,20 +25,19 @@ func (p *Info) Run() error { log.Infof("MKE cluster admin UI: %s", mkeurl) } - if p.Config.Spec.MSR != nil { - switch p.Config.Spec.MSR.MajorVersion() { - case 2: - msrURL, err := p.Config.Spec.MSR2URL() - if err == nil { - log.Infof("MSR cluster admin UI: %s", msrURL.String()) - } - case 3: - msrURL, err := msr3.GetMSRURL(p.Config) - if err != nil { - log.Infof("failed to get MSR URL: %s", err) - } else { - log.Infof("MSR cluster admin UI: %s", msrURL) - } + if p.Config.Spec.MSR2 != nil { + msrURL, err := p.Config.Spec.MSR2URL() + if err == nil { + log.Infof("MSR cluster admin UI: %s", msrURL.String()) + } + } + + if p.Config.Spec.MSR3 != nil { + msrURL, err := msr3.GetMSRURL(p.Config) + if err != nil { + log.Infof("failed to get MSR URL: %s", err) + } else { + log.Infof("MSR cluster admin UI: %s", msrURL) } } diff --git a/pkg/product/mke/phase/install_msr.go b/pkg/product/mke/phase/install_msr2.go similarity index 70% rename from pkg/product/mke/phase/install_msr.go rename to pkg/product/mke/phase/install_msr2.go index f2350ab4b..98ce8bd94 100644 --- a/pkg/product/mke/phase/install_msr.go +++ b/pkg/product/mke/phase/install_msr2.go @@ -14,7 +14,7 @@ import ( // InstallMSR is the phase implementation for running the actual MSR installer // bootstrap. -type InstallMSR struct { +type InstallMSR2 struct { phase.Analytics phase.CleanupDisabling phase.BasicPhase @@ -23,23 +23,23 @@ type InstallMSR struct { } // Title prints the phase title. -func (p *InstallMSR) Title() string { +func (p *InstallMSR2) Title() string { return "Install MSR components" } // ShouldRun should return true only when there is an installation to be // performed. -func (p *InstallMSR) ShouldRun() bool { - p.leader = p.Config.Spec.MSRLeader() - return p.Config.Spec.ContainsMSR() && (p.leader.MSRMetadata == nil || !p.leader.MSRMetadata.Installed) +func (p *InstallMSR2) ShouldRun() bool { + p.leader = p.Config.Spec.MSR2Leader() + return p.Config.Spec.ContainsMSR2() && (p.leader.MSR2Metadata == nil || !p.leader.MSR2Metadata.Installed) } // Run the installer container. -func (p *InstallMSR) Run() error { +func (p *InstallMSR2) Run() error { h := p.leader - if h.MSRMetadata == nil { - h.MSRMetadata = &api.MSRMetadata{} + if h.MSR2Metadata == nil { + h.MSR2Metadata = &api.MSR2Metadata{} } managers := p.Config.Spec.Managers() @@ -50,10 +50,10 @@ func (p *InstallMSR) Run() error { } p.EventProperties = map[string]interface{}{ - "msr_version": p.Config.Spec.MSR.Version, + "msr2_version": p.Config.Spec.MSR2.Version, } - image := p.Config.Spec.MSR.GetBootstrapperImage() + image := p.Config.Spec.MSR2.GetBootstrapperImage() runFlags := common.Flags{"-i"} if !p.CleanupDisabled() { @@ -63,7 +63,7 @@ func (p *InstallMSR) Run() error { if h.Configurer.SELinuxEnabled(h) { runFlags.Add("--security-opt label=disable") } - installFlags := p.Config.Spec.MSR.V2.InstallFlags + installFlags := p.Config.Spec.MSR2.InstallFlags redacts := []string{installFlags.GetValue("--ucp-username"), installFlags.GetValue("--ucp-password")} // Configure the mkeFlags from existing MKEConfig @@ -74,20 +74,20 @@ func (p *InstallMSR) Run() error { installFlags.Merge(mkeFlags) - if p.Config.Spec.MSR.V2.CACertData != "" { - escaped := shellescape.Quote(p.Config.Spec.MSR.V2.CACertData) + if p.Config.Spec.MSR2.CACertData != "" { + escaped := shellescape.Quote(p.Config.Spec.MSR2.CACertData) installFlags.AddOrReplace(fmt.Sprintf("--dtr-ca %s", escaped)) redacts = append(redacts, escaped) } - if p.Config.Spec.MSR.V2.CertData != "" { - escaped := shellescape.Quote(p.Config.Spec.MSR.V2.CertData) + if p.Config.Spec.MSR2.CertData != "" { + escaped := shellescape.Quote(p.Config.Spec.MSR2.CertData) installFlags.AddOrReplace(fmt.Sprintf("--dtr-cert %s", escaped)) redacts = append(redacts, escaped) } - if p.Config.Spec.MSR.V2.KeyData != "" { - escaped := shellescape.Quote(p.Config.Spec.MSR.V2.KeyData) + if p.Config.Spec.MSR2.KeyData != "" { + escaped := shellescape.Quote(p.Config.Spec.MSR2.KeyData) installFlags.AddOrReplace(fmt.Sprintf("--dtr-key %s", escaped)) redacts = append(redacts, escaped) } @@ -98,11 +98,11 @@ func (p *InstallMSR) Run() error { redacts = append(redacts, escaped) } - if h.MSRMetadata.MSR2.ReplicaID != "" { - log.Infof("%s: installing MSR with replica id %s", h, h.MSRMetadata.MSR2.ReplicaID) - installFlags.AddOrReplace(fmt.Sprintf("--replica-id %s", h.MSRMetadata.MSR2.ReplicaID)) + if h.MSR2Metadata.ReplicaID != "" { + log.Infof("%s: installing MSR2 with replica id %s", h, h.MSR2Metadata.ReplicaID) + installFlags.AddOrReplace(fmt.Sprintf("--replica-id %s", h.MSR2Metadata.ReplicaID)) } else { - log.Infof("%s: installing MSR version %s", h, p.Config.Spec.MSR.Version) + log.Infof("%s: installing MSR2 version %s", h, p.Config.Spec.MSR2.Version) } installCmd := h.Configurer.DockerCommandf("run %s %s install %s", runFlags.Join(), image, installFlags.Join()) @@ -115,12 +115,12 @@ func (p *InstallMSR) Run() error { if err != nil { return fmt.Errorf("%s: failed to collect existing MSR details: %w", h, err) } - h.MSRMetadata = msrMeta + h.MSR2Metadata = msrMeta return nil } -// CleanUp removes remnants of MSR after a failed installation. -func (p *InstallMSR) CleanUp() { +// CleanUp removes remnants of MSR2 after a failed installation. +func (p *InstallMSR2) CleanUp() { log.Infof("Cleaning up for '%s'", p.Title()) if err := msr.Destroy(p.leader, p.Config); err != nil { log.Warnf("Error while cleaning-up resources for '%s': %s", p.Title(), err.Error()) diff --git a/pkg/product/mke/phase/install_msr3.go b/pkg/product/mke/phase/install_msr3.go index 2a14193b0..d1a40c4e2 100644 --- a/pkg/product/mke/phase/install_msr3.go +++ b/pkg/product/mke/phase/install_msr3.go @@ -4,12 +4,9 @@ import ( "context" "fmt" - "github.com/Mirantis/mcc/pkg/constant" "github.com/Mirantis/mcc/pkg/mke" "github.com/Mirantis/mcc/pkg/msr/msr3" "github.com/Mirantis/mcc/pkg/phase" - "github.com/Mirantis/mcc/pkg/product/mke/api" - "github.com/Mirantis/mcc/pkg/swarm" log "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) @@ -20,8 +17,6 @@ type InstallOrUpgradeMSR3 struct { phase.Analytics phase.CleanupDisabling phase.KubernetesPhase - - leader *api.Host } // Title prints the phase title. @@ -31,16 +26,14 @@ func (p *InstallOrUpgradeMSR3) Title() string { // Prepare collects the hosts and labels them with the MSR role via the // Kubernetes client so that they can be used as NodeSelector in the MSR CR. -func (p *InstallOrUpgradeMSR3) Prepare(config interface{}) error { - var err error +func (p *InstallOrUpgradeMSR3) Prepare(_ interface{}) error { + managers := p.Config.Spec.Managers() - p.Config, err = convertConfigToClusterConfig(config) - if err != nil { - return err + if err := p.Config.Spec.CheckMKEHealthRemote(managers); err != nil { + return fmt.Errorf("failed to health check mke, try to set `--ucp-url` installation flag and check connectivity: %w", err) } - p.leader = p.Config.Spec.MSRLeader() - + var err error p.Kube, p.Helm, err = mke.KubeAndHelmFromConfig(p.Config) if err != nil { return fmt.Errorf("failed to get kube and helm clients: %w", err) @@ -54,85 +47,15 @@ func (p *InstallOrUpgradeMSR3) ShouldRun() bool { return p.Config.Spec.ContainsMSR3() } -// Run deploys an MSR CR to the cluster, setting NodeSelector to nodes the user -// has specified as MSR hosts in the config. +// Run deploys an MSR CR to the cluster. func (p *InstallOrUpgradeMSR3) Run() error { ctx := context.Background() - h := p.leader - - if h.MSRMetadata == nil { - h.MSRMetadata = &api.MSRMetadata{} - } - - var msrHosts []*api.Host - - for _, h := range p.Config.Spec.Hosts { - if h.Role == "msr" { - msrHosts = append(msrHosts, h) - } - } - - for _, msrH := range msrHosts { - hostname := msrH.Metadata.Hostname - - // If MKE is the target Kubernetes cluster, set the orchestrator - // type to Kubernetes for the node. - swarmLeader := p.Config.Spec.SwarmLeader() - nodeID, err := swarm.NodeID(msrH) - if err != nil { - return fmt.Errorf("%s: failed to get node ID: %w", msrH, err) - } - - kubeOrchestratorLabelCmd := swarmLeader.Configurer.DockerCommandf("%s %s", constant.KubernetesOrchestratorLabelCmd, nodeID) - err = swarmLeader.Exec(kubeOrchestratorLabelCmd) - if err != nil { - return fmt.Errorf("failed to label node %s (%s) for kube orchestration: %w", hostname, nodeID, err) - } - - swarmOrchestratorCheckLabelCmd := swarmLeader.Configurer.DockerCommandf("%s %s", constant.SwarmOrchestratorCheckLabelCmd, nodeID) - output, err := swarmLeader.ExecOutput(swarmOrchestratorCheckLabelCmd) - if err != nil { - log.Warnf("failed to check swarm orchestrator label on node %s (%s): %s", hostname, nodeID, err) - } - - if output != "" { - swarmOrchestratorRemoveLabelCmd := swarmLeader.Configurer.DockerCommandf("%s %s", constant.SwarmOrchestratorRemoveLabelCmd, nodeID) - err = swarmLeader.Exec(swarmOrchestratorRemoveLabelCmd) - if err != nil { - log.Warnf("failed to remove swarm orchestrator label from node %s (%s): %s", hostname, nodeID, err) - } - } - - err = p.Kube.PrepareNodeForMSR(context.Background(), hostname) - if err != nil { - return fmt.Errorf("%s: failed to label node: %w", msrH, err) - } - } - - if err := p.Config.Spec.CheckMKEHealthRemote([]*api.Host{h}); err != nil { - return fmt.Errorf("%s: failed to health check mke, try to set `--ucp-url` installation flag and check connectivity: %w", h, err) - } - if err := p.Kube.ValidateMSROperatorReady(ctx); err != nil { return fmt.Errorf("failed to validate msr-operator is ready: %w", err) } - msr := p.Config.Spec.MSR.V3.CRD - - // Append the NodeSelector for the MSR hosts if not already present. - nodeSelector, found, err := unstructured.NestedMap(msr.Object, "spec", "nodeSelector") - if err != nil { - return fmt.Errorf("failed to get MSR spec.nodeSelector: %w", err) - } - - if !found || nodeSelector == nil { - nodeSelector = make(map[string]interface{}) - } - - if _, ok := nodeSelector[constant.MSRNodeSelector]; !ok { - nodeSelector[constant.MSRNodeSelector] = "true" - } + msr := p.Config.Spec.MSR3.CRD // Ensure the postgresql.spec.volume.size field is sane, postgres-operator // doesn't default the Size field and is picky about the format. @@ -148,14 +71,14 @@ func (p *InstallOrUpgradeMSR3) Run() error { } // Set the version tag to the desired MSR version specified in config. - if err := unstructured.SetNestedField(msr.Object, p.Config.Spec.MSR.Version, "spec", "image", "tag"); err != nil { + if err := unstructured.SetNestedField(msr.Object, p.Config.Spec.MSR3.Version, "spec", "image", "tag"); err != nil { return fmt.Errorf("failed to set MSR spec.image.tag: %w", err) } // Configure Nginx.DNSNames if a LoadBalancerURL is specified. - if p.Config.Spec.MSR.V3.ShouldConfigureLB() { - if err := unstructured.SetNestedStringSlice(msr.Object, []string{"nginx", "localhost", p.Config.Spec.MSR.V3.LoadBalancerURL}, "spec", "nginx", "dnsNames"); err != nil { - return fmt.Errorf("failed to set MSR spec.nginx.dnsNames to include LoadBalancerURL: %q: %w", p.Config.Spec.MSR.V3.LoadBalancerURL, err) + if p.Config.Spec.MSR3.ShouldConfigureLB() { + if err := unstructured.SetNestedStringSlice(msr.Object, []string{"nginx", "localhost", p.Config.Spec.MSR3.LoadBalancerURL}, "spec", "nginx", "dnsNames"); err != nil { + return fmt.Errorf("failed to set MSR spec.nginx.dnsNames to include LoadBalancerURL: %q: %w", p.Config.Spec.MSR3.LoadBalancerURL, err) } } @@ -165,8 +88,8 @@ func (p *InstallOrUpgradeMSR3) Run() error { return fmt.Errorf("failed to apply MSR CRD: %w", err) } - if p.Config.Spec.MSR.V3.ShouldConfigureLB() { - if err := p.Kube.ExposeLoadBalancer(ctx, p.Config.Spec.MSR.V3.LoadBalancerURL); err != nil { + if p.Config.Spec.MSR3.ShouldConfigureLB() { + if err := p.Kube.ExposeLoadBalancer(ctx, p.Config.Spec.MSR3.LoadBalancerURL); err != nil { log.Warnf("failed to expose MSR via LoadBalancer: %s", err) } } @@ -176,12 +99,12 @@ func (p *InstallOrUpgradeMSR3) Run() error { return fmt.Errorf("failed to get MSR resource client: %w", err) } - msrMeta, err := msr3.CollectFacts(ctx, p.Config.Spec.MSR.V3.CRD.GetName(), p.Kube, rc, p.Helm) + msrMeta, err := msr3.CollectFacts(ctx, p.Config.Spec.MSR3.CRD.GetName(), p.Kube, rc, p.Helm) if err != nil { return fmt.Errorf("failed to collect MSR details: %w", err) } - h.MSRMetadata = msrMeta + p.Config.Spec.MSR3.Metadata = msrMeta return nil } diff --git a/pkg/product/mke/phase/join_msr_replicas.go b/pkg/product/mke/phase/join_msr2_replicas.go similarity index 75% rename from pkg/product/mke/phase/join_msr_replicas.go rename to pkg/product/mke/phase/join_msr2_replicas.go index 7120f523d..b1303be42 100644 --- a/pkg/product/mke/phase/join_msr_replicas.go +++ b/pkg/product/mke/phase/join_msr2_replicas.go @@ -13,19 +13,19 @@ import ( ) // JoinMSRReplicas phase implementation. -type JoinMSRReplicas struct { +type JoinMSR2Replicas struct { phase.Analytics phase.HostSelectPhase phase.CleanupDisabling } // HostFilterFunc returns true for hosts that don't have MSR configured. -func (p *JoinMSRReplicas) HostFilterFunc(h *api.Host) bool { - return h.MSRMetadata == nil || !h.MSRMetadata.Installed +func (p *JoinMSR2Replicas) HostFilterFunc(h *api.Host) bool { + return h.MSR2Metadata == nil || !h.MSR2Metadata.Installed } // Prepare collects the hosts. -func (p *JoinMSRReplicas) Prepare(config interface{}) error { +func (p *JoinMSR2Replicas) Prepare(config interface{}) error { cfg, ok := config.(*api.ClusterConfig) if !ok { return errInvalidConfig @@ -35,7 +35,7 @@ func (p *JoinMSRReplicas) Prepare(config interface{}) error { return nil } log.Debugf("collecting hosts for phase %s", p.Title()) - msrHosts := p.Config.Spec.MSRs() + msrHosts := p.Config.Spec.MSR2s() hosts := msrHosts.Filter(p.HostFilterFunc) log.Debugf("found %d hosts for phase %s", len(hosts), p.Title()) p.Hosts = hosts @@ -43,20 +43,20 @@ func (p *JoinMSRReplicas) Prepare(config interface{}) error { } // Title for the phase. -func (p *JoinMSRReplicas) Title() string { - return "Join MSR Replicas" +func (p *JoinMSR2Replicas) Title() string { + return "Join MSR2 Replicas" } // Run joins all the workers nodes to swarm if not already part of it. -func (p *JoinMSRReplicas) Run() error { - msrLeader := p.Config.Spec.MSRLeader() +func (p *JoinMSR2Replicas) Run() error { + msrLeader := p.Config.Spec.MSR2Leader() mkeFlags := msr2.BuildMKEFlags(p.Config) for _, h := range p.Hosts { // Iterate through the msrs and determine which have MSR installed // on them, if one is found which is not yet in the cluster, perform // a join against msrLeader - if h.MSRMetadata != nil && h.MSRMetadata.Installed { + if h.MSR2Metadata != nil && h.MSR2Metadata.Installed { log.Infof("%s: already a MSR node", h) continue } @@ -73,11 +73,11 @@ func (p *JoinMSRReplicas) Run() error { joinFlags := common.Flags{} redacts := []string{} joinFlags.Add(fmt.Sprintf("--ucp-node %s", h.Metadata.Hostname)) - joinFlags.Add(fmt.Sprintf("--existing-replica-id %s", msrLeader.MSRMetadata.MSR2.ReplicaID)) + joinFlags.Add(fmt.Sprintf("--existing-replica-id %s", msrLeader.MSR2Metadata.ReplicaID)) joinFlags.MergeOverwrite(mkeFlags) // We can't just append the installFlags to joinFlags because they // differ, so we have to selectively pluck the ones that are shared - for _, f := range msr2.PluckSharedInstallFlags(p.Config.Spec.MSR.V2.InstallFlags, msr2.SharedInstallJoinFlags) { + for _, f := range msr2.PluckSharedInstallFlags(p.Config.Spec.MSR2.InstallFlags, msr2.SharedInstallJoinFlags) { joinFlags.AddOrReplace(f) } if p.Config.Spec.MKE.CACertData != "" { @@ -85,14 +85,14 @@ func (p *JoinMSRReplicas) Run() error { joinFlags.AddOrReplace(fmt.Sprintf("--ucp-ca %s", escaped)) redacts = append(redacts, escaped) } - if h.MSRMetadata != nil && h.MSRMetadata.MSR2.ReplicaID != "" { - log.Infof("%s: joining MSR replica to cluster with replica id: %s", h, h.MSRMetadata.MSR2.ReplicaID) - joinFlags.AddOrReplace(fmt.Sprintf("--replica-id %s", h.MSRMetadata.MSR2.ReplicaID)) + if h.MSR2Metadata != nil && h.MSR2Metadata.ReplicaID != "" { + log.Infof("%s: joining MSR replica to cluster with replica id: %s", h, h.MSR2Metadata.ReplicaID) + joinFlags.AddOrReplace(fmt.Sprintf("--replica-id %s", h.MSR2Metadata.ReplicaID)) } else { log.Infof("%s: joining MSR replica to cluster", h) } - joinCmd := msrLeader.Configurer.DockerCommandf("run %s %s join %s", runFlags.Join(), msrLeader.MSRMetadata.MSR2.InstalledBootstrapImage, joinFlags.Join()) + joinCmd := msrLeader.Configurer.DockerCommandf("run %s %s join %s", runFlags.Join(), msrLeader.MSR2Metadata.InstalledBootstrapImage, joinFlags.Join()) err := msrLeader.Exec(joinCmd, exec.StreamOutput(), exec.RedactString(redacts...)) if err != nil { return fmt.Errorf("%s: failed to run MSR join: %w", h, err) diff --git a/pkg/product/mke/phase/prepare_host.go b/pkg/product/mke/phase/prepare_host.go index 496c93405..c41fd2ecf 100644 --- a/pkg/product/mke/phase/prepare_host.go +++ b/pkg/product/mke/phase/prepare_host.go @@ -43,7 +43,7 @@ func (p *PrepareHost) Run() error { return fmt.Errorf("failed to authorize docker: %w", err) } - if p.Config.Spec.ContainsMSR() && p.Config.Spec.MSR.V2.ReplicaIDs == "sequential" { + if p.Config.Spec.ContainsMSR() && p.Config.Spec.MSR2.ReplicaIDs == "sequential" { err = msr2.AssignSequentialReplicaIDs(p.Config) if err != nil { return fmt.Errorf("failed to assign sequential MSR replica IDs: %w", err) diff --git a/pkg/product/mke/phase/pull_msr_images.go b/pkg/product/mke/phase/pull_msr2_images.go similarity index 73% rename from pkg/product/mke/phase/pull_msr_images.go rename to pkg/product/mke/phase/pull_msr2_images.go index 2c04f8668..cea63b41d 100644 --- a/pkg/product/mke/phase/pull_msr_images.go +++ b/pkg/product/mke/phase/pull_msr2_images.go @@ -11,29 +11,29 @@ import ( // PullMSRImages phase implementation is responsible for pulling the MSR // bootstrap image. -type PullMSRImages struct { +type PullMSR2Images struct { phase.Analytics phase.BasicPhase } // Title for the phase. -func (p *PullMSRImages) Title() string { - return "Pull MSR images" +func (p *PullMSR2Images) Title() string { + return "Pull MSR2 images" } // Run pulls images in parallel across nodes via a workerpool of 5. -func (p *PullMSRImages) Run() error { +func (p *PullMSR2Images) Run() error { images, err := p.ListImages() if err != nil { return fmt.Errorf("failed to get MSR images list: %w", err) } log.Debugf("loaded MSR images list: %v", images) - imageRepo := p.Config.Spec.MSR.V2.ImageRepo + imageRepo := p.Config.Spec.MSR2.ImageRepo if api.IsCustomImageRepo(imageRepo) { pullList := docker.AllToRepository(images, imageRepo) // In case of custom image repo, we need to pull and retag all the images on all MSR hosts - err := phase.RunParallelOnHosts(p.Config.Spec.MSRs(), p.Config, func(h *api.Host, _ *api.ClusterConfig) error { + err := phase.RunParallelOnHosts(p.Config.Spec.MSR2s(), p.Config, func(h *api.Host, _ *api.ClusterConfig) error { if err := docker.PullImages(h, pullList); err != nil { return fmt.Errorf("failed to pull MSR images: %w", err) } @@ -47,16 +47,16 @@ func (p *PullMSRImages) Run() error { } } - if err := docker.PullImages(p.Config.Spec.MSRLeader(), images); err != nil { + if err := docker.PullImages(p.Config.Spec.MSR2Leader(), images); err != nil { return fmt.Errorf("failed to pull MSR images: %w", err) } return nil } -// ListImages obtains a list of images from MSR. -func (p *PullMSRImages) ListImages() ([]*docker.Image, error) { - msrLeader := p.Config.Spec.MSRLeader() - bootstrap := docker.NewImage(p.Config.Spec.MSR.GetBootstrapperImage()) +// ListImages obtains a list of images from MSR2. +func (p *PullMSR2Images) ListImages() ([]*docker.Image, error) { + msrLeader := p.Config.Spec.MSR2Leader() + bootstrap := docker.NewImage(p.Config.Spec.MSR2.GetBootstrapperImage()) if !bootstrap.Exist(msrLeader) { if err := bootstrap.Pull(msrLeader); err != nil { diff --git a/pkg/product/mke/phase/remove_nodes.go b/pkg/product/mke/phase/remove_nodes.go index 813e5e50e..5fb339010 100644 --- a/pkg/product/mke/phase/remove_nodes.go +++ b/pkg/product/mke/phase/remove_nodes.go @@ -95,14 +95,14 @@ func (p *RemoveNodes) Prepare(config interface{}) error { // If the node is a managed msr node in addition to a managed // launchpad node, first remove MSR if managed.msr { - // Check to see if the config contains any left over MSR nodes, + // Check to see if the config contains any left over MSR2 nodes, // if it doesn't just call msr2.Cleanup to remove - msrs := p.Config.Spec.MSRs() - if len(msrs) == 0 { - // All of the MSRs were removed from config, just remove + msr2s := p.Config.Spec.MSR2s() + if len(msr2s) == 0 { + // All of the MSR2s were removed from config, just remove // them forcefully since we don't care about sustaining - // quorum - p.cleanupMSRs = msrs + // quorum. + p.cleanupMSRs = msr2s } // Get the hostname from the nodeID inspect hostname, err := swarmLeader.ExecOutput(swarmLeader.Configurer.DockerCommandf(`node inspect %s --format {{.Description.Hostname}}`, nodeID)) @@ -139,7 +139,7 @@ func (p *RemoveNodes) Run() error { if len(p.msrReplicaIDs) > 0 { for _, replicaID := range p.msrReplicaIDs { - err := p.removemsrNode(p.Config, replicaID) + err := p.removeMSR2Node(p.Config, replicaID) if err != nil { return err } @@ -213,8 +213,8 @@ func (p *RemoveNodes) removeNode(h *api.Host, nodeID string) error { return nil } -func (p *RemoveNodes) removemsrNode(config *api.ClusterConfig, replicaID string) error { - msrLeader := config.Spec.MSRLeader() +func (p *RemoveNodes) removeMSR2Node(config *api.ClusterConfig, replicaID string) error { + msr2Leader := config.Spec.MSR2Leader() mkeFlags := msr2.BuildMKEFlags(config) runFlags := common.Flags{"-i"} @@ -223,24 +223,24 @@ func (p *RemoveNodes) removemsrNode(config *api.ClusterConfig, replicaID string) runFlags.Add("--rm") } - if msrLeader.Configurer.SELinuxEnabled(msrLeader) { + if msr2Leader.Configurer.SELinuxEnabled(msr2Leader) { runFlags.Add("--security-opt label=disable") } removeFlags := common.Flags{ fmt.Sprintf("--replica-ids %s", replicaID), - fmt.Sprintf("--existing-replica-id %s", msrLeader.MSRMetadata.MSR2.ReplicaID), + fmt.Sprintf("--existing-replica-id %s", msr2Leader.MSR2Metadata.ReplicaID), } removeFlags.MergeOverwrite(mkeFlags) - for _, f := range msr2.PluckSharedInstallFlags(config.Spec.MSR.V2.InstallFlags, msr2.SharedInstallRemoveFlags) { + for _, f := range msr2.PluckSharedInstallFlags(config.Spec.MSR2.InstallFlags, msr2.SharedInstallRemoveFlags) { removeFlags.AddOrReplace(f) } - removeCmd := msrLeader.Configurer.DockerCommandf("run %s %s remove %s", runFlags.Join(), msrLeader.MSRMetadata.MSR2.InstalledBootstrapImage, removeFlags.Join()) - log.Debugf("%s: Removing MSR replica %s from cluster", msrLeader, replicaID) - err := msrLeader.Exec(removeCmd, exec.StreamOutput()) + removeCmd := msr2Leader.Configurer.DockerCommandf("run %s %s remove %s", runFlags.Join(), msr2Leader.MSR2Metadata.InstalledBootstrapImage, removeFlags.Join()) + log.Debugf("%s: Removing MSR replica %s from cluster", msr2Leader, replicaID) + err := msr2Leader.Exec(removeCmd, exec.StreamOutput()) if err != nil { - return fmt.Errorf("%s: failed to run MSR remove: %w", msrLeader, err) + return fmt.Errorf("%s: failed to run MSR remove: %w", msr2Leader, err) } return nil } @@ -288,7 +288,7 @@ func (p *RemoveNodes) getReplicaIDFromHostname(config *api.ClusterConfig, h *api mkeURL.Path = "/containers/json" q := mkeURL.Query() - q.Add("filters", fmt.Sprintf(`{"ancestor": ["dtr-nginx:%s"]}`, config.Spec.MSR.Version)) + q.Add("filters", fmt.Sprintf(`{"ancestor": ["dtr-nginx:%s"]}`, config.Spec.MSR2.Version)) q.Add("size", "false") mkeURL.RawQuery = q.Encode() diff --git a/pkg/product/mke/phase/uninstall_msr.go b/pkg/product/mke/phase/uninstall_msr2.go similarity index 86% rename from pkg/product/mke/phase/uninstall_msr.go rename to pkg/product/mke/phase/uninstall_msr2.go index 64b2c448d..88bf881cb 100644 --- a/pkg/product/mke/phase/uninstall_msr.go +++ b/pkg/product/mke/phase/uninstall_msr2.go @@ -17,14 +17,14 @@ type UninstallMSR struct { // Title prints the phase title. func (p *UninstallMSR) Title() string { - return "Uninstall MSR components" + return "Uninstall MSR2 components" } // Run an uninstall via msr.Cleanup. func (p *UninstallMSR) Run() error { swarmLeader := p.Config.Spec.SwarmLeader() - msrLeader := p.Config.Spec.MSRLeader() - if msrLeader == nil || !msrLeader.MSRMetadata.Installed { + msrLeader := p.Config.Spec.MSR2Leader() + if msrLeader == nil || !msrLeader.MSR2Metadata.Installed { log.Infof("%s: MSR is not installed", swarmLeader) return nil } diff --git a/pkg/product/mke/phase/uninstall_msr3.go b/pkg/product/mke/phase/uninstall_msr3.go index b54ba1304..14706111b 100644 --- a/pkg/product/mke/phase/uninstall_msr3.go +++ b/pkg/product/mke/phase/uninstall_msr3.go @@ -24,8 +24,7 @@ func (p *UninstallMSR3) Title() string { } func (p *UninstallMSR3) ShouldRun() bool { - leader := p.Config.Spec.MSRLeader() - return p.Config.Spec.ContainsMSR3() && leader.MSRMetadata.Installed + return p.Config.Spec.ContainsMSR3() && p.Config.Spec.MSR3.Metadata.Installed } func (p *UninstallMSR3) Prepare(config interface{}) error { @@ -49,18 +48,18 @@ func (p *UninstallMSR3) Run() error { ctx := context.Background() // Remove the LB service if it's being used, ignoring if it's not found. - if p.Config.Spec.MSR.V3.ShouldConfigureLB() { + if p.Config.Spec.MSR3.ShouldConfigureLB() { err := p.Kube.DeleteService(ctx, constant.ExposedLBServiceName) if err != nil && !apierrors.IsNotFound(err) { return fmt.Errorf("failed to delete LoadBalancer service: %w", err) } } - chartsToUninstall := p.Config.Spec.MSR.V3.Dependencies.List() + chartsToUninstall := p.Config.Spec.MSR3.Dependencies.List() // Add the storage provisioner chart to the list of charts to uninstall // if it's configured. - if p.Config.Spec.MSR.V3.ShouldConfigureStorageClass() { + if p.Config.Spec.MSR3.ShouldConfigureStorageClass() { sp := storageProvisionerChart(p.Config) if sp != nil { diff --git a/pkg/product/mke/phase/upgrade_check.go b/pkg/product/mke/phase/upgrade_check.go index 9a08a90eb..348e2fd0f 100644 --- a/pkg/product/mke/phase/upgrade_check.go +++ b/pkg/product/mke/phase/upgrade_check.go @@ -57,7 +57,7 @@ func (p *UpgradeCheck) Run() (err error) { return nil } - msrv, err := hub.LatestTag("mirantis", "dtr", strings.Contains(p.Config.Spec.MSR.Version, "-")) + msrv, err := hub.LatestTag("mirantis", "dtr", strings.Contains(p.Config.Spec.MSR2.Version, "-")) if err != nil { log.Errorf("failed to check for MSR upgrade: %s", err.Error()) return nil @@ -69,7 +69,7 @@ func (p *UpgradeCheck) Run() (err error) { return nil } - msrTargetV, err := version.NewVersion(p.Config.Spec.MSR.Version) + msrTargetV, err := version.NewVersion(p.Config.Spec.MSR2.Version) if err != nil { log.Errorf("invalid MSR version in configuration: %s", err.Error()) return fmt.Errorf("invalid MSR version in configuration: %w", err) diff --git a/pkg/product/mke/phase/upgrade_mcr.go b/pkg/product/mke/phase/upgrade_mcr.go index c8875399d..4ca83ffaf 100644 --- a/pkg/product/mke/phase/upgrade_mcr.go +++ b/pkg/product/mke/phase/upgrade_mcr.go @@ -102,8 +102,8 @@ func (p *UpgradeMCR) upgradeMCRs() error { } port := 443 - if p.Config.Spec.MSR != nil { - if flagport := p.Config.Spec.MSR.V2.InstallFlags.GetValue("--replica-https-port"); flagport != "" { + if p.Config.Spec.MSR2 != nil { + if flagport := p.Config.Spec.MSR2.InstallFlags.GetValue("--replica-https-port"); flagport != "" { if fp, err := strconv.Atoi(flagport); err == nil { port = fp } @@ -112,23 +112,24 @@ func (p *UpgradeMCR) upgradeMCRs() error { // Upgrade MSR hosts individually for _, h := range msrs { - if h.MSRMetadata.Installed { - if err := validateMSRReady(p.Config, h, port); err != nil { - return err - } + if err := validateMSRReady(p.Config, h, port); err != nil { + return err } + if err := p.upgradeMCR(h); err != nil { return err } - if h.MSRMetadata.Installed { + + if h.MSR2Metadata.Installed { if err := validateMSRReady(p.Config, h, port); err != nil { return err } err := retry.Do( func() error { if _, err := msr2.CollectFacts(h); err != nil { - return fmt.Errorf("%s: failed to collect MSR facts: %w", h, err) + return fmt.Errorf("%s: failed to collect MSR2 facts: %w", h, err) } + return nil }, retry.DelayType(retry.CombineDelay(retry.FixedDelay, retry.RandomDelay)), @@ -192,12 +193,13 @@ func (p *UpgradeMCR) upgradeMCR(h *api.Host) error { func validateMSRReady(config *api.ClusterConfig, h *api.Host, port int) error { ctx := context.Background() - switch config.Spec.MSR.MajorVersion() { - case 2: + if config.Spec.MSR2 != nil { if err := msr2.WaitMSRNodeReady(h, port); err != nil { return fmt.Errorf("%s: failed to wait for MSR node to be ready: %w", h, err) } - case 3: + } + + if config.Spec.MSR3 != nil { kubeClient, _, err := mke.KubeAndHelmFromConfig(config) if err != nil { return fmt.Errorf("failed to create Kubernetes and Helm clients from config: %w", err) @@ -208,7 +210,7 @@ func validateMSRReady(config *api.ClusterConfig, h *api.Host, port int) error { return fmt.Errorf("failed to get resource client for MSR CR: %w", err) } - obj, err := kubeClient.GetMSRCR(ctx, config.Spec.MSR.V3.CRD.GetName(), rc) + obj, err := kubeClient.GetMSRCR(ctx, config.Spec.MSR3.CRD.GetName(), rc) if err != nil { return fmt.Errorf("failed to get MSR CR: %w", err) } diff --git a/pkg/product/mke/phase/upgrade_msr.go b/pkg/product/mke/phase/upgrade_msr2.go similarity index 68% rename from pkg/product/mke/phase/upgrade_msr.go rename to pkg/product/mke/phase/upgrade_msr2.go index f744eee33..b55fe849c 100644 --- a/pkg/product/mke/phase/upgrade_msr.go +++ b/pkg/product/mke/phase/upgrade_msr2.go @@ -12,7 +12,7 @@ import ( ) // UpgradeMSR is the phase implementation for running the actual msr upgrade container. -type UpgradeMSR struct { +type UpgradeMSR2 struct { phase.Analytics phase.CleanupDisabling phase.BasicPhase @@ -25,23 +25,23 @@ type msrUpgradeVersionNotExpectedError struct { } func (e *msrUpgradeVersionNotExpectedError) Error() string { - return fmt.Sprintf("%s: MSR upgrade version %s does not match expected version %s", e.host, e.installedVersion, e.expectedVersion) + return fmt.Sprintf("%s: MSR2 upgrade version %s does not match expected version %s", e.host, e.installedVersion, e.expectedVersion) } // Title prints the phase title. -func (p *UpgradeMSR) Title() string { - return "Upgrade MSR components" +func (p *UpgradeMSR2) Title() string { + return "Upgrade MSR2 components" } // ShouldRun should return true only when there is an upgrade to be performed. -func (p *UpgradeMSR) ShouldRun() bool { - h := p.Config.Spec.MSRLeader() - return p.Config.Spec.ContainsMSR() && h.MSRMetadata != nil && h.MSRMetadata.InstalledVersion != p.Config.Spec.MSR.Version +func (p *UpgradeMSR2) ShouldRun() bool { + h := p.Config.Spec.MSR2Leader() + return p.Config.Spec.ContainsMSR() && h.MSR2Metadata != nil && h.MSR2Metadata.InstalledVersion != p.Config.Spec.MSR2.Version } // Run the upgrade container. -func (p *UpgradeMSR) Run() error { - h := p.Config.Spec.MSRLeader() +func (p *UpgradeMSR2) Run() error { + h := p.Config.Spec.MSR2Leader() managers := p.Config.Spec.Managers() @@ -54,8 +54,8 @@ func (p *UpgradeMSR) Run() error { "msr_upgraded": false, } - if p.Config.Spec.MSR.Version == h.MSRMetadata.InstalledVersion { - log.Infof("%s: MSR cluster already at version %s, not running upgrade", h, p.Config.Spec.MSR.Version) + if p.Config.Spec.MSR2.Version == h.MSR2Metadata.InstalledVersion { + log.Infof("%s: MSR cluster already at version %s, not running upgrade", h, p.Config.Spec.MSR2.Version) return nil } @@ -67,15 +67,15 @@ func (p *UpgradeMSR) Run() error { if h.Configurer.SELinuxEnabled(h) { runFlags.Add("--security-opt label=disable") } - upgradeFlags := common.Flags{fmt.Sprintf("--existing-replica-id %s", h.MSRMetadata.MSR2.ReplicaID)} + upgradeFlags := common.Flags{fmt.Sprintf("--existing-replica-id %s", h.MSR2Metadata.ReplicaID)} upgradeFlags.MergeOverwrite(msr2.BuildMKEFlags(p.Config)) - for _, f := range msr2.PluckSharedInstallFlags(p.Config.Spec.MSR.V2.InstallFlags, msr2.SharedInstallUpgradeFlags) { + for _, f := range msr2.PluckSharedInstallFlags(p.Config.Spec.MSR2.InstallFlags, msr2.SharedInstallUpgradeFlags) { upgradeFlags.AddOrReplace(f) } - upgradeFlags.MergeOverwrite(p.Config.Spec.MSR.V2.UpgradeFlags) + upgradeFlags.MergeOverwrite(p.Config.Spec.MSR2.UpgradeFlags) - upgradeCmd := h.Configurer.DockerCommandf("run %s %s upgrade %s", runFlags.Join(), p.Config.Spec.MSR.GetBootstrapperImage(), upgradeFlags.Join()) + upgradeCmd := h.Configurer.DockerCommandf("run %s %s upgrade %s", runFlags.Join(), p.Config.Spec.MSR2.GetBootstrapperImage(), upgradeFlags.Join()) log.Debugf("%s: Running MSR upgrade via bootstrapper", h) if err := h.Exec(upgradeCmd, exec.StreamOutput()); err != nil { return fmt.Errorf("%s: failed to run MSR upgrade: %w", h, err) @@ -87,16 +87,16 @@ func (p *UpgradeMSR) Run() error { } // Check to make sure installedversion matches bootstrapperVersion - if msrMeta.InstalledVersion != p.Config.Spec.MSR.Version { + if msrMeta.InstalledVersion != p.Config.Spec.MSR2.Version { // If our newly collected facts do not match the version we upgraded to // then the upgrade has failed - return &msrUpgradeVersionNotExpectedError{host: h, installedVersion: msrMeta.InstalledVersion, expectedVersion: p.Config.Spec.MSR.Version} + return &msrUpgradeVersionNotExpectedError{host: h, installedVersion: msrMeta.InstalledVersion, expectedVersion: p.Config.Spec.MSR2.Version} } p.EventProperties["msr_upgraded"] = true - p.EventProperties["msr_installed_version"] = h.MSRMetadata.InstalledVersion - p.EventProperties["msr_upgraded_version"] = p.Config.Spec.MSR.Version - h.MSRMetadata = msrMeta + p.EventProperties["msr_installed_version"] = h.MSR2Metadata.InstalledVersion + p.EventProperties["msr_upgraded_version"] = p.Config.Spec.MSR2.Version + h.MSR2Metadata = msrMeta return nil } diff --git a/pkg/product/mke/phase/validate_facts.go b/pkg/product/mke/phase/validate_facts.go index b2fc2fd64..25cab2410 100644 --- a/pkg/product/mke/phase/validate_facts.go +++ b/pkg/product/mke/phase/validate_facts.go @@ -55,6 +55,14 @@ func (p *ValidateFacts) Run() error { } } + if err := p.validateMSRCannotDowngrade(); err != nil { + if p.Force { + log.Warnf("%s: continuing anyway because --force given", err.Error()) + } else { + return err + } + } + if err := p.validateDataPlane(); err != nil { if p.Force { log.Warnf("%s: continuing anyway because --force given", err.Error()) @@ -89,57 +97,107 @@ func (e cannotDowngradeProductError) Error() string { return fmt.Sprintf("can't downgrade %s %s to %s", e.product, e.installedVersion.String(), e.targetVersion.String()) } -// validateMSRVersionJump validates MKE upgrade path. +// validateMKEVersionJump validates MKE upgrade path. func (p *ValidateFacts) validateMKEVersionJump() error { if p.Config.Spec.MKE.Metadata.Installed && p.Config.Spec.MKE.Metadata.InstalledVersion != "" { - installedMKE, err := version.NewVersion(p.Config.Spec.MKE.Metadata.InstalledVersion) - if err != nil { - return fmt.Errorf("can't parse installed MKE version: %w", err) - } - targetMKE, err := version.NewVersion(p.Config.Spec.MKE.Version) - if err != nil { - return fmt.Errorf("can't parse target MKE version: %w", err) + return validateVersionJump("MKE", p.Config.Spec.MKE.Metadata.InstalledVersion, p.Config.Spec.MKE.Version) + } + + return nil +} + +// validateMSRVersionJump validates MSR upgrade path. +func (p *ValidateFacts) validateMSRVersionJump() error { + if p.Config.Spec.MSR2 != nil { + msr2Leader := p.Config.Spec.MSR2Leader() + + if msr2Leader.MSR2Metadata != nil && msr2Leader.MSR2Metadata.Installed && msr2Leader.MSR2Metadata.InstalledVersion != "" { + if err := validateVersionJump("MSR2", msr2Leader.MSR2Metadata.InstalledVersion, p.Config.Spec.MSR2.Version); err != nil { + return fmt.Errorf("MSR2 version validation failed: %w", err) + } } + } - if mccversion.GreaterThan(installedMKE, targetMKE) { - return cannotDowngradeProductError{product: "MKE", installedVersion: installedMKE, targetVersion: targetMKE} + if p.Config.Spec.MSR3 != nil { + if p.Config.Spec.MSR3.Metadata.Installed && p.Config.Spec.MSR3.Metadata.InstalledVersion != "" { + if err := validateVersionJump("MSR3", p.Config.Spec.MSR3.Metadata.InstalledVersion, p.Config.Spec.MSR3.Version); err != nil { + return fmt.Errorf("MSR3 version validation failed: %w", err) + } } + } - installedSegments := installedMKE.Segments() - targetSegments := targetMKE.Segments() + return nil +} - // This will fail if there's something like 2.x => 3.x or 3.x => 4.x. - if installedSegments[0] == targetSegments[0] && targetSegments[1]-installedSegments[1] > 1 { - return fmt.Errorf("%w: can't upgrade MKE directly from %s to %s - need to upgrade to %d.%d first", errInvalidUpgradePath, installedMKE, targetMKE, installedSegments[0], installedSegments[1]+1) - } +// validateVersionJump validates a version jump for a given product. +func validateVersionJump(product, installedVersion, targetVersion string) error { + installed, err := version.NewVersion(installedVersion) + if err != nil { + return fmt.Errorf("can't parse installed version: %w", err) + } + target, err := version.NewVersion(targetVersion) + if err != nil { + return fmt.Errorf("can't parse target version: %w", err) + } + + if mccversion.GreaterThan(installed, target) { + return cannotDowngradeProductError{product: product, installedVersion: installed, targetVersion: target} + } + + installedSegments := installed.Segments() + targetSegments := target.Segments() + + // This will fail if there's something like 2.x => 3.x or 3.x => 4.x. + if installedSegments[0] == targetSegments[0] && targetSegments[1]-installedSegments[1] > 1 { + return fmt.Errorf("%w: can't upgrade %s directly from %s to %s - need to upgrade to %d.%d first", errInvalidUpgradePath, product, installed, target, installedSegments[0], installedSegments[1]+1) } return nil } -// validateMSRVersionJump validates MSR upgrade path. -func (p *ValidateFacts) validateMSRVersionJump() error { - msrLeader := p.Config.Spec.MSRLeader() - if p.Config.Spec.MSR != nil && msrLeader.MSRMetadata != nil && msrLeader.MSRMetadata.Installed && msrLeader.MSRMetadata.InstalledVersion != "" { - installedMSR, err := version.NewVersion(msrLeader.MSRMetadata.InstalledVersion) - if err != nil { - return fmt.Errorf("can't parse installed MSR version: %w", err) - } - targetMSR, err := version.NewVersion(p.Config.Spec.MSR.Version) - if err != nil { - return fmt.Errorf("can't parse target MSR version: %w", err) - } +// validateVersionDowngrade validates a version downgrade of a given product. +func validateVersionDowngrade(product, installedVersion, targetVersion string) error { + installed, err := version.NewVersion(installedVersion) + if err != nil { + return fmt.Errorf("can't parse installed MSR version: %w", err) + } + target, err := version.NewVersion(targetVersion) + if err != nil { + return fmt.Errorf("can't parse target MSR version: %w", err) + } - if mccversion.GreaterThan(installedMSR, targetMSR) { - return cannotDowngradeProductError{product: "MSR", installedVersion: installedMSR, targetVersion: targetMSR} - } + if mccversion.GreaterThan(installed, target) { + return cannotDowngradeProductError{product: product, installedVersion: installed, targetVersion: target} + } + + installedSegments := installed.Segments() + targetSegments := target.Segments() + + // This will fail if there's something like 2.x => 3.x or 3.x => 4.x. + if installedSegments[0] == targetSegments[0] && targetSegments[1]-installedSegments[1] > 1 { + return fmt.Errorf("%w: can't upgrade %s directly from %s to %s - need to upgrade to %d.%d first", errInvalidUpgradePath, product, installed, target, installedSegments[0], installedSegments[1]+1) + } - installedSegments := installedMSR.Segments() - targetSegments := targetMSR.Segments() + return nil +} + +// validateMSRCannotDowngrade validates that MSR can't be downgraded. +func (p *ValidateFacts) validateMSRCannotDowngrade() error { + if p.Config.Spec.MSR2 != nil { + msr2Leader := p.Config.Spec.MSR2Leader() + + if msr2Leader.MSR2Metadata != nil && msr2Leader.MSR2Metadata.Installed && msr2Leader.MSR2Metadata.InstalledVersion != "" { + if err := validateVersionDowngrade("MSR2", msr2Leader.MSR2Metadata.InstalledVersion, p.Config.Spec.MSR2.Version); err != nil { + return fmt.Errorf("MSR2 version validation failed: %w", err) + } + } + } - // This will fail if there's something like 2.x => 3.x or 3.x => 4.x. - if installedSegments[0] == targetSegments[0] && targetSegments[1]-installedSegments[1] > 1 { - return fmt.Errorf("%w: can't upgrade MSR directly from %s to %s - need to upgrade to %d.%d first", errInvalidUpgradePath, installedMSR, targetMSR, installedSegments[0], installedSegments[1]+1) + if p.Config.Spec.MSR3 != nil { + if p.Config.Spec.MSR3.Metadata.Installed && p.Config.Spec.MSR3.Metadata.InstalledVersion != "" { + if err := validateVersionDowngrade("MSR3", p.Config.Spec.MSR3.Metadata.InstalledVersion, p.Config.Spec.MSR3.Version); err != nil { + return fmt.Errorf("MSR3 version validation failed: %w", err) + } } } diff --git a/pkg/product/mke/phase/validate_facts_test.go b/pkg/product/mke/phase/validate_facts_test.go index 3ff90e4b0..26c616ad4 100644 --- a/pkg/product/mke/phase/validate_facts_test.go +++ b/pkg/product/mke/phase/validate_facts_test.go @@ -63,34 +63,40 @@ func TestValidateFactsMSRVersionJumpFail(t *testing.T) { phase.Config = &api.ClusterConfig{ Spec: &api.ClusterSpec{ Hosts: []*api.Host{ - {Role: "msr", MSRMetadata: &api.MSRMetadata{ - Installed: true, - InstalledVersion: "2.6.4", - }}, + { + Role: api.RoleMSR2, + MSR2Metadata: &api.MSR2Metadata{ + Installed: true, + InstalledVersion: "2.6.4", + }, + }, }, - MSR: &api.MSRConfig{ + MSR2: &api.MSR2Config{ Version: "2.8.4", }, }, } - require.ErrorContains(t, phase.validateMSRVersionJump(), "can't upgrade MSR directly from 2.6.4 to 2.8.4 - need to upgrade to 2.7 first") + require.ErrorContains(t, phase.validateMSRVersionJump(), "can't upgrade MSR2 directly from 2.6.4 to 2.8.4 - need to upgrade to 2.7 first") } func TestValidateFactsMSRVersionJumpDowngradeFail(t *testing.T) { phase := ValidateFacts{} phase.Config = &api.ClusterConfig{ Spec: &api.ClusterSpec{ Hosts: []*api.Host{ - {Role: "msr", MSRMetadata: &api.MSRMetadata{ - Installed: true, - InstalledVersion: "2.8.4", - }}, + { + Role: api.RoleMSR2, + MSR2Metadata: &api.MSR2Metadata{ + Installed: true, + InstalledVersion: "2.8.4", + }, + }, }, - MSR: &api.MSRConfig{ + MSR2: &api.MSR2Config{ Version: "2.7.6", }, }, } - require.ErrorContains(t, phase.validateMSRVersionJump(), "can't downgrade MSR 2.8.4 to 2.7.6") + require.ErrorContains(t, phase.validateMSRVersionJump(), "can't downgrade MSR2 2.8.4 to 2.7.6") } func TestValidateFactsMSRVersionJumpSuccess(t *testing.T) { @@ -98,12 +104,15 @@ func TestValidateFactsMSRVersionJumpSuccess(t *testing.T) { phase.Config = &api.ClusterConfig{ Spec: &api.ClusterSpec{ Hosts: []*api.Host{ - {Role: "msr", MSRMetadata: &api.MSRMetadata{ - Installed: true, - InstalledVersion: "2.6.8", - }}, + { + Role: api.RoleMSR2, + MSR2Metadata: &api.MSR2Metadata{ + Installed: true, + InstalledVersion: "2.6.8", + }, + }, }, - MSR: &api.MSRConfig{ + MSR2: &api.MSR2Config{ Version: "2.7.1", }, }, diff --git a/pkg/product/mke/reset.go b/pkg/product/mke/reset.go index 1712018e5..d28e87d3e 100644 --- a/pkg/product/mke/reset.go +++ b/pkg/product/mke/reset.go @@ -21,10 +21,9 @@ func (p *MKE) Reset() error { ) // begin MSR phases - switch p.ClusterConfig.Spec.MSR.MajorVersion() { - case 2: + if p.ClusterConfig.Spec.ContainsMSR2() { phaseManager.AddPhase(&mke.UninstallMSR{}) - case 3: + } else if p.ClusterConfig.Spec.ContainsMSR3() { phaseManager.AddPhase(&mke.UninstallMSR3{}) } diff --git a/pkg/product/product.go b/pkg/product/product.go index 32ea22fa7..db26bdfa5 100644 --- a/pkg/product/product.go +++ b/pkg/product/product.go @@ -1,11 +1,13 @@ package product +import "github.com/Mirantis/mcc/pkg/product/mke/api" + // Product is an interface that represents a product that launchpad can manage. type Product interface { Apply(disableCleanup, force bool, concurrency int, forceUpgrade bool) error Reset() error Describe(reportName string) error ClientConfig() error - Exec(target []string, interactive, first, all, parallel bool, role, os, cmd string) error + Exec(target []string, interactive, first, all, parallel bool, role api.RoleType, os, cmd string) error ClusterName() string }