Skip to content

Commit

Permalink
example/cmd/microctl: Implement cluster recovery
Browse files Browse the repository at this point in the history
Signed-off-by: Wesley Hershberger <[email protected]>
  • Loading branch information
MggMuggins committed Jun 14, 2024
1 parent 5ff92a6 commit bd6829a
Showing 1 changed file with 104 additions and 0 deletions.
104 changes: 104 additions & 0 deletions example/cmd/microctl/cluster_members.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,46 @@
package main

import (
"bufio"
"fmt"
"io"
"os"
"sort"
"strings"

"github.com/canonical/lxd/shared"
cli "github.com/canonical/lxd/shared/cmd"
"github.com/canonical/lxd/shared/termios"
"github.com/spf13/cobra"
"golang.org/x/sys/unix"
"gopkg.in/yaml.v2"

"github.com/canonical/microcluster/client"
"github.com/canonical/microcluster/cluster"
"github.com/canonical/microcluster/microcluster"
)

const recoveryConfirmation = `You should only run this command if:
- A quorum of cluster members is permanently lost
- You are *absolutely* sure all microd instances are stopped
- This instance has the most up to date database
Do you want to proceed? (yes/no): `

const recoveryYamlComment = `# Member roles can be modified. Unrecoverable nodes should be given the role "spare".
#
# "voter" - Voting member of the database. A majority of voters is a quorum.
# "stand-by" - Non-voting member of the database; can be promoted to voter.
# "spare" - Not a member of the database.
#
# The edit is aborted if:
# - the number of members changes
# - the name of any member changes
# - the ID of any member changes
# - the address of any member changes
# - no changes are made
`

type cmdClusterMembers struct {
common *CmdControl
}
Expand All @@ -27,6 +58,9 @@ func (c *cmdClusterMembers) command() *cobra.Command {
var cmdList = cmdClusterMembersList{common: c.common}
cmd.AddCommand(cmdList.command())

var cmdRestore = cmdClusterEdit{common: c.common}
cmd.AddCommand(cmdRestore.command())

return cmd
}

Expand Down Expand Up @@ -130,3 +164,73 @@ func (c *cmdClusterMemberRemove) run(cmd *cobra.Command, args []string) error {

return nil
}

type cmdClusterEdit struct {
common *CmdControl
}

func (c *cmdClusterEdit) command() *cobra.Command {
cmd := &cobra.Command{
Use: "edit",
Short: "Recover the cluster from this node if quorum is lost",
RunE: c.run,
}

return cmd
}

func (c *cmdClusterEdit) run(cmd *cobra.Command, args []string) error {
m, err := microcluster.App(microcluster.Args{StateDir: c.common.FlagStateDir, Verbose: c.common.FlagLogVerbose, Debug: c.common.FlagLogDebug})
if err != nil {
return err
}

members, err := m.GetLocalClusterMembers()
if err != nil {
return err
}

membersYaml, err := yaml.Marshal(members)
if err != nil {
return err
}

var content []byte
if !termios.IsTerminal(unix.Stdin) {
content, err = io.ReadAll(os.Stdin)
if err != nil {
return err
}
} else {
reader := bufio.NewReader(os.Stdin)
fmt.Print(recoveryConfirmation)

input, _ := reader.ReadString('\n')
input = strings.TrimSuffix(input, "\n")

if strings.ToLower(input) != "yes" {
fmt.Println("Cluster edit aborted; no changes made")
return nil
}

content, err = shared.TextEditor("", append([]byte(recoveryYamlComment), membersYaml...))
if err != nil {
return err
}
}

newMembers := []cluster.LocalMember{}
err = yaml.Unmarshal(content, &newMembers)
if err != nil {
return err
}

err = m.RecoverFromQuorumLoss(newMembers)
if err != nil {
return fmt.Errorf("cluster edit: %w", err)
}

fmt.Println("Cluster reconfigured successfully")

return nil
}

0 comments on commit bd6829a

Please sign in to comment.