Skip to content

Commit

Permalink
Fix 5.0 early patch version restore issue (#1880)
Browse files Browse the repository at this point in the history
Signed-off-by: Arnob Kumar Saha <arnob@appscode.com>
  • Loading branch information
ArnobKumarSaha committed Aug 14, 2023
1 parent f1b4d3e commit 0ae911f
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 10 deletions.
29 changes: 21 additions & 8 deletions pkg/backup.go
Expand Up @@ -539,6 +539,13 @@ func (opt *mongoOptions) backupMongoDB(targetRef api_v1beta1.TargetRef) (*restic
klog.Errorf("error while creating user for %v. error: %v", host, err)
return nil, err
}
} else {
// We need to create a dummy role while backup to resolve `BSON field '_mergeAuthzCollections.tempRolesCollection' is missing but a required field` issue.
// https://jira.mongodb.org/browse/TOOLS-2946?focusedCommentId=4022387&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-4022387
err = createStashBackupRole(host)
if err != nil {
return nil, err
}
}
// do the task
primary, secondary, err := getPrimaryNSecondaryMember(host)
Expand Down Expand Up @@ -570,6 +577,12 @@ func (opt *mongoOptions) backupMongoDB(targetRef api_v1beta1.TargetRef) (*restic
// if parameters.ReplicaSets is nil, then the mongodb database doesn't have replicasets or sharded replicasets.
// In this case, perform normal backup with clientconfig.Service.Name mongo dsn
if parameters.ReplicaSets == nil {
// We need to create a dummy role while backup to resolve `BSON field '_mergeAuthzCollections.tempRolesCollection' is missing but a required field` issue.
// https://jira.mongodb.org/browse/TOOLS-2946?focusedCommentId=4022387&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-4022387
err = createStashBackupRole(hostname)
if err != nil {
return nil, err
}
opt.backupOptions = append(opt.backupOptions, getBackupOpt(hostname, restic.DefaultHost, true))
}

Expand Down Expand Up @@ -838,38 +851,38 @@ func unlockSecondaryMember(mongohost string) error {
return nil
}

func checkRoleExists(mongoDSN string) (bool, error) {
func checkRoleExists(mongoDSN string, roleName string) (bool, error) {
v := make(map[string]interface{})
args := append([]interface{}{
"admin",
"--host", mongoDSN,
"--quiet",
"--eval", `JSON.stringify(db.getRole("` + StashRoleName + `"))`,
"--eval", `JSON.stringify(db.getRole("` + roleName + `"))`,
}, mongoCreds...)
if err := sh.Command(MongoCMD, args...).Command("/usr/bin/tail", "-1").UnmarshalJSON(&v); err != nil {
return false, err
}

if val, ok := v["role"].(string); ok && string(val) == StashRoleName {
if val, ok := v["role"].(string); ok && val == roleName {
return true, nil
}

return false, nil
}

func checkUserExists(mongoDSN string) (bool, error) {
func checkUserExists(mongoDSN string, userName string) (bool, error) {
v := make(map[string]interface{})
args := append([]interface{}{
"admin",
"--host", mongoDSN,
"--quiet",
"--eval", `JSON.stringify(db.getUser("` + StashUserName + `"))`,
"--eval", `JSON.stringify(db.getUser("` + userName + `"))`,
}, mongoCreds...)
if err := sh.Command(MongoCMD, args...).Command("/usr/bin/tail", "-1").UnmarshalJSON(&v); err != nil {
return false, err
}

if val, ok := v["user"].(string); ok && string(val) == StashUserName {
if val, ok := v["user"].(string); ok && val == userName {
return true, nil
}

Expand All @@ -886,7 +899,7 @@ func createStashRoleAndUser(mongoDSN string, pass string) error {
}

func createStashBackupRole(mongoDSN string) error {
exists, err := checkRoleExists(mongoDSN)
exists, err := checkRoleExists(mongoDSN, StashRoleName)
if err != nil {
return err
}
Expand Down Expand Up @@ -914,7 +927,7 @@ func createStashBackupRole(mongoDSN string) error {
}

func createStashBackupUser(mongoDSN string, pass string) error {
exists, err := checkUserExists(mongoDSN)
exists, err := checkUserExists(mongoDSN, StashUserName)
if err != nil {
return err
}
Expand Down
156 changes: 154 additions & 2 deletions pkg/restore.go
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/spf13/cobra"
license "go.bytebuilders.dev/license-verifier/kubernetes"
"gomodules.xyz/flags"
"gomodules.xyz/go-sh"
"gomodules.xyz/pointer"
core "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -48,6 +49,8 @@ import (
"kubedb.dev/apimachinery/apis/config/v1alpha1"
)

var cleanupRestoreFuncs []func() error

func NewCmdRestore() *cobra.Command {
var (
masterURL string
Expand All @@ -73,6 +76,7 @@ func NewCmdRestore() *cobra.Command {
return checkCommandExists()
},
RunE: func(cmd *cobra.Command, args []string) error {
defer cleanupRestore()
flags.EnsureRequiredFlags(cmd, "appbinding", "provider", "storage-secret-name", "storage-secret-namespace")

// prepare client
Expand Down Expand Up @@ -153,6 +157,14 @@ func NewCmdRestore() *cobra.Command {
return cmd
}

func cleanupRestore() {
for _, f := range cleanupRestoreFuncs {
if err := f(); err != nil {
klog.Errorln(err)
}
}
}

func (opt *mongoOptions) restoreMongoDB(targetRef api_v1beta1.TargetRef) (*restic.RestoreOutput, error) {
var err error
err = license.CheckLicenseEndpoint(opt.config, licenseApiService, SupportedProducts)
Expand Down Expand Up @@ -293,7 +305,6 @@ func (opt *mongoOptions) restoreMongoDB(targetRef api_v1beta1.TargetRef) (*resti
"--authenticationDatabase", "$external",
}
mongoCreds = append(mongoCreds, userAuth...)
dumpCreds = append(dumpCreds, userAuth...)

} else {
userAuth := []interface{}{
Expand All @@ -302,7 +313,11 @@ func (opt *mongoOptions) restoreMongoDB(targetRef api_v1beta1.TargetRef) (*resti
"--authenticationDatabase", opt.authenticationDatabase,
}
mongoCreds = append(mongoCreds, userAuth...)
dumpCreds = append(dumpCreds, userAuth...)
}

err = opt.workOnSuperUser(parameters, hostname)
if err != nil {
return nil, err
}

getDumpOpts := func(mongoDSN, hostKey string, isStandalone bool) restic.DumpOptions {
Expand Down Expand Up @@ -392,6 +407,8 @@ func (opt *mongoOptions) restoreMongoDB(targetRef api_v1beta1.TargetRef) (*resti
// hide password, don't print cmd
resticWrapper.HideCMD()

klog.Infof("==> %+v \n %+v \n", opt.dumpOptions, targetRef)

// Run dump
out, err := resticWrapper.ParallelDump(opt.dumpOptions, targetRef, opt.maxConcurrency)
if err != nil {
Expand All @@ -401,6 +418,141 @@ func (opt *mongoOptions) restoreMongoDB(targetRef api_v1beta1.TargetRef) (*resti
return out, nil
}

const (
SuperRoleName = "interalUseOnlyOplogRestore"
SuperUserName = "superoplogger"
)

func (opt *mongoOptions) workOnSuperUser(parameters v1alpha1.MongoDBConfiguration, hostNameForStandalone string) error {
pass, err := genPassword(12)
if err != nil {
return err
}

if parameters.ConfigServer != "" {
dsn := parameters.ConfigServer
err := createSuperRoleAndUser(dsn, pass)
if err != nil {
return err
}
}
for key := range parameters.ReplicaSets {
dsn := parameters.ReplicaSets[key]
err := createSuperRoleAndUser(dsn, pass)
if err != nil {
return err
}
}

if parameters.ReplicaSets == nil {
err := createSuperRoleAndUser(hostNameForStandalone, pass)
if err != nil {
return err
}
}

superUserAuth := []interface{}{
fmt.Sprintf("--username=%s", SuperUserName),
fmt.Sprintf("--password=%s", pass),
"--authenticationDatabase", opt.authenticationDatabase,
}
dumpCreds = append(dumpCreds, superUserAuth...)
return nil
}

func createSuperRoleAndUser(mongoDSN, pass string) error {
klog.Infof("creating SuperRole & User for %s \n", mongoDSN)
err := createSuperRole(mongoDSN)
if err != nil {
return err
}

err = createSuperUser(mongoDSN, pass)
cleanupRestoreFuncs = append(cleanupRestoreFuncs, func() error {
return deleteSuperUser(mongoDSN)
})
return err
}

func createSuperRole(mongoDSN string) error {
exists, err := checkRoleExists(mongoDSN, SuperRoleName)
if err != nil {
return err
}
if !exists {
klog.Infoln("creating role " + SuperRoleName)
v := make(map[string]interface{})

args := append([]interface{}{
"admin",
"--host", mongoDSN,
"--quiet",
"--eval", `JSON.stringify(db.runCommand({createRole: "` + SuperRoleName + `",privileges:[{resource:{anyResource:true},actions:["anyAction"]}],roles: []}))`,
}, mongoCreds...)

if err := sh.Command(MongoCMD, args...).Command("/usr/bin/tail", "-1").UnmarshalJSON(&v); err != nil {
return err
}

if val, ok := v["ok"].(float64); !ok || int(val) != 1 {
return fmt.Errorf("unable to create role %v. got response: %v", SuperRoleName, v)
}
}
return nil
}

func createSuperUser(mongoDSN, pass string) error {
exists, err := checkUserExists(mongoDSN, SuperUserName)
if err != nil {
return err
}
if !exists {
klog.Infoln("creating user " + SuperUserName)
v := make(map[string]interface{})

args := append([]interface{}{
"admin",
"--host", mongoDSN,
"--quiet",
"--eval", `JSON.stringify(db.runCommand({createUser: "` + SuperUserName + `" ,pwd: "` + pass + `", roles:[{role:"root", db:"admin"},"__system","interalUseOnlyOplogRestore","backup"]}))`,
}, mongoCreds...)
if err := sh.Command(MongoCMD, args...).Command("/usr/bin/tail", "-1").UnmarshalJSON(&v); err != nil {
return err
}

if val, ok := v["ok"].(float64); !ok || int(val) != 1 {
return fmt.Errorf("unable to create user %v. got response: %v", SuperUserName, v)
}
}
return nil
}

func deleteSuperUser(mongoDSN string) error {
exists, err := checkUserExists(mongoDSN, SuperUserName)
if err != nil {
return err
}
if exists {
klog.Infoln("deleting user " + SuperUserName)
v := make(map[string]interface{})

args := append([]interface{}{
"admin",
"--host", mongoDSN,
"--quiet",
"--eval", `JSON.stringify(db.runCommand({dropUser: "` + SuperUserName + `"}))`,
}, mongoCreds...)
if err := sh.Command(MongoCMD, args...).Command("/usr/bin/tail", "-1").UnmarshalJSON(&v); err != nil {
return err
}

if val, ok := v["ok"].(float64); !ok || int(val) != 1 {
return fmt.Errorf("unable to delete user %v. got response: %v", SuperUserName, v)
}
}
return nil
}

func (opt *mongoOptions) getHostRestoreStats(err error) []api_v1beta1.HostRestoreStats {
var restoreStats []api_v1beta1.HostRestoreStats

Expand Down
16 changes: 16 additions & 0 deletions pkg/utils.go
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package pkg

import (
"crypto/rand"
"fmt"
"os/exec"
"strings"
Expand Down Expand Up @@ -94,3 +95,18 @@ func containsArg(args []string, checklist sets.String) bool {
}
return false
}

var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-"

func genPassword(length int) (string, error) {
ll := len(chars)
b := make([]byte, length)
_, err := rand.Read(b) // generates len(b) random bytes
if err != nil {
return "", err
}
for i := 0; i < length; i++ {
b[i] = chars[int(b[i])%ll]
}
return string(b), nil
}

0 comments on commit 0ae911f

Please sign in to comment.