Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add attack to Patroni PostgreSQL cluster (#229) #230

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/attack/attack.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func NewAttackCommand() *cobra.Command {
NewHTTPAttackCommand(&uid),
NewVMAttackCommand(&uid),
NewUserDefinedCommand(&uid),
NewPatroniAttackCommand(&uid),
)

return cmd
Expand Down
99 changes: 99 additions & 0 deletions cmd/attack/patroni.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright 2020 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package attack

import (
"fmt"
"time"

"github.com/spf13/cobra"
"go.uber.org/fx"

"github.com/chaos-mesh/chaosd/cmd/server"
"github.com/chaos-mesh/chaosd/pkg/core"
"github.com/chaos-mesh/chaosd/pkg/server/chaosd"
"github.com/chaos-mesh/chaosd/pkg/utils"
)

func NewPatroniAttackCommand(uid *string) *cobra.Command {
options := core.NewPatroniCommand()
dep := fx.Options(
server.Module,
fx.Provide(func() *core.PatroniCommand {
options.UID = *uid
return options
}),
)

cmd := &cobra.Command{
Use: "patroni <subcommand>",
Short: "Patroni attack related commands",
}

cmd.AddCommand(
NewPatroniSwitchoverCommand(dep, options),
NewPatroniFailoverCommand(dep, options),
)

cmd.PersistentFlags().StringVarP(&options.User, "user", "u", "patroni", "patroni cluster user")
cmd.PersistentFlags().StringVar(&options.Password, "password", "p", "patroni cluster password")
cmd.PersistentFlags().StringVarP(&options.Address, "address", "a", "", "patroni cluster address, any of available hosts")
cmd.PersistentFlags().BoolVarP(&options.LocalMode, "local-mode", "l", false, "execute patronictl on host with chaosd. User with privileges required.")
cmd.PersistentFlags().BoolVarP(&options.RemoteMode, "remote-mode", "r", false, "execute patroni command by REST API")

return cmd
}

func NewPatroniSwitchoverCommand(dep fx.Option, options *core.PatroniCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "switchover",
Short: "exec switchover command. Warning! Command is not recover!",
Run: func(*cobra.Command, []string) {
options.Action = core.SwitchoverAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(PatroniAttackF)).Run()
},
}
cmd.Flags().StringVarP(&options.Candidate, "candidate", "c", "", "switchover candidate, default random unit for replicas")
cmd.Flags().StringVarP(&options.Scheduled_at, "scheduled_at", "d", fmt.Sprint(time.Now().Add(time.Second*60).Format(time.RFC3339)), `scheduled switchover,
default now()+1 minute by remote mode`)

return cmd
}

func NewPatroniFailoverCommand(dep fx.Option, options *core.PatroniCommand) *cobra.Command {
cmd := &cobra.Command{
Use: "failover",
Short: "Exec failover command. Warning! Command is not recover!",
Run: func(*cobra.Command, []string) {
options.Action = core.FailoverAction
utils.FxNewAppWithoutLog(dep, fx.Invoke(PatroniAttackF)).Run()
},
}

cmd.Flags().StringVarP(&options.Candidate, "leader", "c", "", "failover new leader, default random unit for replicas")
return cmd
}

func PatroniAttackF(options *core.PatroniCommand, chaos *chaosd.Server) {
if err := options.Validate(); err != nil {
utils.ExitWithError(utils.ExitBadArgs, err)
}

uid, err := chaos.ExecuteAttack(chaosd.PatroniAttack, options, core.CommandMode)
if err != nil {
utils.ExitWithError(utils.ExitError, err)
}

utils.NormalExit(fmt.Sprintf("Attack %s successfully to patroni address %s, uid: %s", options.Action, options.Address, uid))
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ require (
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe
github.com/swaggo/gin-swagger v1.5.0
github.com/swaggo/swag v1.8.3
github.com/tidwall/gjson v1.14.4
go.uber.org/fx v1.17.1
go.uber.org/zap v1.21.0
google.golang.org/grpc v1.40.0
Expand Down Expand Up @@ -125,6 +126,8 @@ require (
github.com/romana/ipset v1.0.0 // indirect
github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,12 @@ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
Expand Down
3 changes: 3 additions & 0 deletions pkg/core/experiment.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const (
FileAttack = "file"
HTTPAttack = "http"
VMAttack = "vm"
PatroniAttack = "patroni"
UserDefinedAttack = "userDefined"
)

Expand Down Expand Up @@ -128,6 +129,8 @@ func GetAttackByKind(kind string) *AttackConfig {
attackConfig = &VMOption{}
case UserDefinedAttack:
attackConfig = &UserDefinedOption{}
case PatroniAttack:
attackConfig = &PatroniCommand{}
default:
return nil
}
Expand Down
82 changes: 82 additions & 0 deletions pkg/core/patroni.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2020 Chaos Mesh Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package core

import (
"encoding/json"

"github.com/pingcap/errors"
)

const (
SwitchoverAction = "switchover"
FailoverAction = "failover"
)

var _ AttackConfig = &PatroniCommand{}

type PatroniCommand struct {
CommonAttackConfig

Address string `json:"address,omitempty"`
Candidate string `json:"candidate,omitempty"`
Leader string `json:"leader,omitempty"`
User string `json:"user,omitempty"`
Password string `json:"password,omitempty"`
Scheduled_at string `json:"scheduled_at,omitempty"`
LocalMode bool `json:"local_mode,omitempty"`
RemoteMode bool `json:"remote_mode,omitempty"`
RecoverCmd string `json:"recoverCmd,omitempty"`
}

func (p *PatroniCommand) Validate() error {
if err := p.CommonAttackConfig.Validate(); err != nil {
return err
}

if !p.RemoteMode && !p.LocalMode {
return errors.New("local or remote mode required")
}

if p.RemoteMode {

if len(p.Address) == 0 {
return errors.New("address not provided")
}

if len(p.User) == 0 {
return errors.New("patroni user not provided")
}

if len(p.Password) == 0 {
return errors.New("patroni password not provided")
}
}

return nil
}

func (p PatroniCommand) RecoverData() string {
data, _ := json.Marshal(p)

return string(data)
}

func NewPatroniCommand() *PatroniCommand {
return &PatroniCommand{
CommonAttackConfig: CommonAttackConfig{
Kind: PatroniAttack,
},
}
}
Loading