14
14
package chaosd
15
15
16
16
import (
17
- "bytes"
18
17
"encoding/json"
19
18
"fmt"
20
- "io"
21
19
"math/rand"
22
20
"net/http"
21
+ "os/exec"
22
+ "strings"
23
23
24
- "github.com/chaos-mesh/chaosd/pkg/core"
25
- "github.com/chaos-mesh/chaosd/pkg/server/utils"
26
24
"github.com/pingcap/errors"
27
25
"github.com/pingcap/log"
26
+
27
+ "github.com/chaos-mesh/chaosd/pkg/core"
28
+ "github.com/chaos-mesh/chaosd/pkg/server/utils"
28
29
)
29
30
30
31
type patroniAttack struct {}
@@ -34,96 +35,157 @@ var PatroniAttack AttackType = patroniAttack{}
34
35
func (patroniAttack ) Attack (options core.AttackConfig , _ Environment ) error {
35
36
attack := options .(* core.PatroniCommand )
36
37
37
- candidate := attack .Candidate
38
-
39
- leader := attack .Leader
38
+ var responce []byte
40
39
41
- var scheduled_at string
40
+ var address string
42
41
43
- var url string
42
+ var err error
44
43
45
44
values := make (map [string ]string )
46
45
47
- patroniInfo , err := utils .GetPatroniInfo (attack .Address )
46
+ if attack .RemoteMode {
47
+ address = attack .Address
48
+ } else if attack .LocalMode {
49
+ address , err = utils .GetLocalHostname ()
50
+ if err != nil {
51
+ return errors .WithStack (err )
52
+ }
53
+ }
54
+
55
+ patroniInfo , err := utils .GetPatroniInfo (address )
48
56
if err != nil {
49
57
err = errors .Errorf ("failed to get patroni info for %v: %v" , options .String (), err )
50
58
return errors .WithStack (err )
51
59
}
52
60
53
- if len (patroniInfo .Replicas ) == 0 {
54
- err = errors .Errorf ("failed to get available replicas. Please, check your cluster" )
61
+ if len (patroniInfo .Replicas ) == 0 && len (patroniInfo .SyncStandby ) == 0 {
62
+ err = errors .Errorf ("failed to get available candidates. Please, check your cluster" )
63
+ return errors .WithStack (err )
64
+ }
65
+
66
+ sync_mode_check , err := isSynchronousClusterMode (address , attack .User , attack .Password )
67
+ if err != nil {
68
+ err = errors .Errorf ("failed to check cluster synchronous mode for %v: %v" , options .String (), err )
55
69
return errors .WithStack (err )
56
70
}
57
71
58
- if candidate == "" {
59
- candidate = patroniInfo .Replicas [rand .Intn (len (patroniInfo .Replicas ))]
72
+ if attack .Candidate == "" {
73
+ if sync_mode_check {
74
+ values ["candidate" ] = patroniInfo .SyncStandby [rand .Intn (len (patroniInfo .SyncStandby ))]
75
+ } else {
76
+ values ["candidate" ] = patroniInfo .Replicas [rand .Intn (len (patroniInfo .Replicas ))]
77
+ }
78
+
60
79
}
61
80
62
- if leader == "" {
63
- leader = patroniInfo .Master
81
+ if attack . Leader == "" {
82
+ values [ " leader" ] = patroniInfo .Master
64
83
}
65
84
66
- switch options .String () {
67
- case "switchover" :
85
+ values ["scheduled_at" ] = attack .Scheduled_at
68
86
69
- scheduled_at = attack . Scheduled_at
87
+ cmd := options . String ()
70
88
71
- values = map [string ]string {"leader" : leader , "scheduled_at" : scheduled_at }
89
+ switch cmd {
90
+ case "switchover" :
72
91
73
- log .Info (fmt .Sprintf ("Switchover will be done from %v to another available replica in %v" , patroniInfo . Master , scheduled_at ))
92
+ log .Info (fmt .Sprintf ("Switchover will be done from %v to %v in %v" , values [ "leader" ], values [ "candidate" ], values [ " scheduled_at" ] ))
74
93
75
94
case "failover" :
76
95
77
- values = map [ string ] string { "candidate" : candidate }
96
+ log . Info ( fmt . Sprintf ( "Failover will be done from %v to %v" , values [ "leader" ], values [ " candidate" ]))
78
97
79
- log . Info ( fmt . Sprintf ( "Failover will be done from %v to %v" , patroniInfo . Master , candidate ))
98
+ }
80
99
100
+ if attack .RemoteMode {
101
+ responce , err = execPatroniAttackByRemoteMode (address , attack .User , attack .Password , cmd , values )
102
+ if err != nil {
103
+ return err
104
+ }
105
+ } else if attack .LocalMode {
106
+ responce , err = execPatroniAttackByLocalMode (cmd , values )
107
+ if err != nil {
108
+ return err
109
+ }
81
110
}
82
111
83
- patroniAddr := attack .Address
112
+ if attack .RemoteMode {
113
+ log .S ().Infof ("Execute %v successfully: %v" , cmd , string (responce ))
114
+ }
84
115
85
- cmd := options .String ()
116
+ if attack .LocalMode {
117
+ log .S ().Infof ("Execute %v successfully" , cmd )
118
+ fmt .Println (string (responce ))
119
+ }
120
+
121
+ return nil
122
+ }
123
+
124
+ func execPatroniAttackByRemoteMode (patroniAddr string , user string , password string , cmd string , values map [string ]string ) ([]byte , error ) {
86
125
87
126
data , err := json .Marshal (values )
88
127
if err != nil {
89
128
err = errors .Errorf ("failed to marshal data: %v" , values )
90
- return errors .WithStack (err )
129
+ return nil , errors .WithStack (err )
91
130
}
92
131
93
- url = fmt .Sprintf ("http://%v:8008/%v" , patroniAddr , cmd )
132
+ buf , err := utils .MakeHTTPRequest (http .MethodPost , patroniAddr , 8008 , cmd , data , user , password )
133
+ if err != nil {
134
+ return nil , errors .WithStack (err )
135
+ }
94
136
95
- request , err := http .NewRequest ("POST" , url , bytes .NewBuffer (data ))
137
+ return buf , nil
138
+ }
139
+
140
+ func execPatroniAttackByLocalMode (cmd string , values map [string ]string ) ([]byte , error ) {
141
+ var cmdTemplate string
142
+
143
+ if cmd == "failover" {
144
+ cmdTemplate = fmt .Sprintf ("patronictl %v --master %v --candidate %v --force" , cmd , values ["leader" ], values ["candidate" ])
145
+ } else if cmd == "switchover" {
146
+ cmdTemplate = fmt .Sprintf ("patronictl %v --master %v --candidate %v --scheduled %v --force" , cmd , values ["leader" ], values ["candidate" ], values ["scheduled_at" ])
147
+ }
148
+
149
+ execCmd := exec .Command ("bash" , "-c" , cmdTemplate )
150
+ output , err := execCmd .CombinedOutput ()
96
151
if err != nil {
97
- err = errors . Errorf ("failed to %v: %v" , cmd , err )
98
- return errors . WithStack ( err )
152
+ log . S (). Errorf (fmt . Sprintf ( "failed to %v: %v" , cmdTemplate , string ( output )) )
153
+ return nil , err
99
154
}
100
155
101
- request .Header .Set ("Content-Type" , "application/json" )
102
- request .SetBasicAuth (attack .User , attack .Password )
156
+ if strings .Contains (string (output ), "failed" ) {
157
+ err = errors .New (string (output ))
158
+ return nil , err
159
+ }
103
160
104
- client := & http.Client {}
105
- resp , error := client .Do (request )
106
- if error != nil {
107
- err = errors .Errorf ("failed to %v: %v" , cmd , err )
108
- return errors .WithStack (err )
161
+ return output , nil
162
+ }
163
+
164
+ func isSynchronousClusterMode (patroniAddr string , user string , password string ) (bool , error ) {
165
+
166
+ buf , err := utils .MakeHTTPRequest (http .MethodGet , patroniAddr , 8008 , "config" , []byte {}, user , password )
167
+ if err != nil {
168
+ return false , err
109
169
}
110
170
111
- defer resp . Body . Close ( )
171
+ patroni_responce := make ( map [ string ] interface {} )
112
172
113
- buf , err := io . ReadAll ( resp . Body )
173
+ err = json . Unmarshal ( buf , & patroni_responce )
114
174
if err != nil {
115
- err = errors .Errorf ("failed to read %v responce: %v" , cmd , err )
116
- return errors .WithStack (err )
175
+ return false , fmt .Errorf ("bad request %v %v" , err .Error (), http .StatusBadRequest )
117
176
}
118
177
119
- if resp . StatusCode != 200 && resp . StatusCode != 202 {
120
- err = errors . Errorf ( "failed to %v: status code %v, responce %v" , cmd , resp . StatusCode , string ( buf ))
121
- return errors . WithStack ( err )
178
+ mode_check , ok := patroni_responce [ "synchronous_mode" ].( bool )
179
+ if ! ok {
180
+ return false , fmt . Errorf ( "failed to cast synchronous_mode field from patroni responce" )
122
181
}
123
182
124
- log .S ().Infof ("Execute %v successfully: %v" , cmd , string (buf ))
183
+ if mode_check {
184
+ return true , nil
185
+ }
186
+
187
+ return false , nil
125
188
126
- return nil
127
189
}
128
190
129
191
func (patroniAttack ) Recover (exp core.Experiment , _ Environment ) error {
0 commit comments