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
24
"github.com/chaos-mesh/chaosd/pkg/core"
25
25
"github.com/chaos-mesh/chaosd/pkg/server/utils"
@@ -34,17 +34,24 @@ var PatroniAttack AttackType = patroniAttack{}
34
34
func (patroniAttack ) Attack (options core.AttackConfig , _ Environment ) error {
35
35
attack := options .(* core.PatroniCommand )
36
36
37
- candidate := attack . Candidate
37
+ var responce [] byte
38
38
39
- leader := attack . Leader
39
+ var address string
40
40
41
- var scheduled_at string
42
-
43
- var url string
41
+ var err error
44
42
45
43
values := make (map [string ]string )
46
44
47
- patroniInfo , err := utils .GetPatroniInfo (attack .Address )
45
+ if attack .RemoteMode {
46
+ address = attack .Address
47
+ } else if attack .LocalMode {
48
+ address , err = utils .GetLocalHostname ()
49
+ if err != nil {
50
+ return errors .WithStack (err )
51
+ }
52
+ }
53
+
54
+ patroniInfo , err := utils .GetPatroniInfo (address )
48
55
if err != nil {
49
56
err = errors .Errorf ("failed to get patroni info for %v: %v" , options .String (), err )
50
57
return errors .WithStack (err )
@@ -55,75 +62,130 @@ func (patroniAttack) Attack(options core.AttackConfig, _ Environment) error {
55
62
return errors .WithStack (err )
56
63
}
57
64
58
- if candidate == "" {
59
- candidate = patroniInfo .Replicas [rand .Intn (len (patroniInfo .Replicas ))]
65
+ sync_mode_check , err := isSynchronousClusterMode (attack .Address , attack .User , attack .Password )
66
+ if err != nil {
67
+ err = errors .Errorf ("failed to check cluster synchronous mode for %v: %v" , options .String (), err )
68
+ return errors .WithStack (err )
60
69
}
61
70
62
- if leader == "" {
63
- leader = patroniInfo .Master
71
+ if attack .Candidate == "" {
72
+ if sync_mode_check {
73
+ values ["candidate" ] = patroniInfo .SyncStandby [rand .Intn (len (patroniInfo .SyncStandby ))]
74
+ } else {
75
+ values ["candidate" ] = patroniInfo .Replicas [rand .Intn (len (patroniInfo .Replicas ))]
76
+ }
77
+
64
78
}
65
79
66
- switch options .String () {
67
- case "switchover" :
80
+ if attack .Leader == "" {
81
+ values ["leader" ] = patroniInfo .Master
82
+ }
68
83
69
- scheduled_at = attack .Scheduled_at
84
+ values [ " scheduled_at" ] = attack .Scheduled_at
70
85
71
- values = map [string ]string {"leader" : leader , "scheduled_at" : scheduled_at }
86
+ cmd := options .String ()
87
+
88
+ switch cmd {
89
+ case "switchover" :
72
90
73
- log .Info (fmt .Sprintf ("Switchover will be done from %v to another available replica in %v" , patroniInfo . Master , scheduled_at ))
91
+ log .Info (fmt .Sprintf ("Switchover will be done from %v to %v in %v" , values [ "leader" ], values [ "candidate" ], values [ " scheduled_at" ] ))
74
92
75
93
case "failover" :
76
94
77
- values = map [ string ] string { "candidate" : candidate }
95
+ log . Info ( fmt . Sprintf ( "Failover will be done from %v to %v" , values [ "leader" ], values [ " candidate" ]))
78
96
79
- log . Info ( fmt . Sprintf ( "Failover will be done from %v to %v" , patroniInfo . Master , candidate ))
97
+ }
80
98
99
+ if attack .RemoteMode {
100
+ responce , err = execPatroniAttackByRemoteMode (attack , cmd , values )
101
+ if err != nil {
102
+ return err
103
+ }
104
+ } else if attack .LocalMode {
105
+ responce , err = execPatroniAttackByLocalMode (attack , cmd , values )
106
+ if err != nil {
107
+ return err
108
+ }
81
109
}
82
110
83
- patroniAddr := attack .Address
111
+ if attack .RemoteMode {
112
+ log .S ().Infof ("Execute %v successfully: %v" , cmd , string (responce ))
113
+ }
84
114
85
- cmd := options .String ()
115
+ if attack .LocalMode {
116
+ log .S ().Infof ("Execute %v successfully" , cmd )
117
+ fmt .Println (string (responce ))
118
+ }
119
+
120
+ return nil
121
+ }
122
+
123
+ func execPatroniAttackByRemoteMode (attack * core.PatroniCommand , cmd string , values map [string ]string ) ([]byte , error ) {
124
+ patroniAddr := attack .Address
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 , attack .User , attack .Password )
133
+ if err != nil {
134
+ return nil , errors .WithStack (err )
135
+ }
136
+
137
+ return buf , nil
138
+ }
139
+
140
+ func execPatroniAttackByLocalMode (attack * core.PatroniCommand , 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
+ }
94
148
95
- request , err := http .NewRequest ("POST" , url , bytes .NewBuffer (data ))
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
154
+ }
155
+
156
+ if strings .Contains (string (output ), "failed" ) {
157
+ err = errors .New (string (output ))
158
+ return nil , err
99
159
}
100
160
101
- request . Header . Set ( "Content-Type" , "application/json" )
102
- request . SetBasicAuth ( attack . User , attack . Password )
161
+ return output , nil
162
+ }
103
163
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 )
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