-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtrainticket_probes.py
373 lines (308 loc) · 14.9 KB
/
trainticket_probes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
#!/usr/bin/python3
import os
import subprocess
import time
import random
import yaml
import requests
import datetime
import pandas as pd
# If you know your ticket booking system like you know yourself,
# you need not fear the result of a hundred train trips.
# -- Sun Tzu, probably
def steady_state_load():
os.system(f"{os.getcwd()}/steady-state.sh")
print("Execution of shell script finished. Killing generators and evaluating responses.")
kill_load_generators()
csv_df = pd.read_csv("steady-log.csv")
csv_df["Avg Response Time"].replace(0, float('nan')) # Remove zeroes, as they make the average meaningless
mean_avg_response_time = csv_df["Avg Response Time"].mean()
new_name = time.strftime("steady-state_%Y-%m-%d_%H-%M.csv")
os.system(f"mv steady-log.csv {new_name}")
print(f"Average response time is: {mean_avg_response_time}")
response_time_ok = mean_avg_response_time < 1.0
return bool(response_time_ok) # Workaround to really stupid numpy behaviour (FIXME)
def food_service_overload_action():
os.system(f"{os.getcwd()}/overload.sh")
print("Finished overloading the food service, CSV is ready.")
def food_service_overload_probe():
csv_df = pd.read_csv("overload-log.csv")
csv_df["Avg Response Time"].replace(0, float('nan'))
mean_avg_response_time = csv_df["Avg Response Time"].mean()
print(f"Average time during overload is: {mean_avg_response_time}")
response_time_ok = mean_avg_response_time < 2.0 # Workaround due to numpy being dumb, same as above: FIXME
kill_load_generators()
return bool(response_time_ok)
# Kills all loadgenerator processes
def kill_load_generators():
os.system("pkill -f loadgenerator")
def scenario_two_steady_state_probe():
# Start the Load Generator to create background load reflecting user interaction with the system
#os.system(f"{os.getcwd()}/steady-state.sh")
#print("Loadgenerators started, beginning steady state check")
# Log in to TrainTicket to be authorized for subsequent requests
login_response = requests.post(f"http://localhost:8080/api/v1/users/login", json = {"username": "admin", "password": "222222"})
login_response_parsed = login_response.json()
auth_header = {'Authorization': f"Bearer {login_response_parsed['data']['token']}"}
user_id = login_response_parsed['data']['userId']
# Get a contact id, we need that for reservations
contacts_response = requests.get("http://localhost:12347/api/v1/contactservice/contacts", headers=auth_header)
contact = contacts_response.json()["data"][0]
contact_id = contact["id"]
account_id = contact["accountId"]
# Reserve a ticket, we do however not get the order ID back from the reservation ("preserve") service
trip_id = "D1345"
tomorrow = datetime.date.today() + datetime.timedelta(days=1)
reserve_json = {
"accountId": account_id,
"contactsId": contact_id,
"tripId":trip_id,
"seatType":"2",
"date": tomorrow.strftime("%Y-%m-%d"),
"from":"Shang Hai",
"to":"Su Zhou",
"assurance":"0",
"foodType":1,
"foodName":"Bone Soup",
"foodPrice":2.5,
"stationName":"",
"storeName":""
}
reserve_response = requests.post("http://localhost:14568/api/v1/preserveservice/preserve", json=reserve_json, headers=auth_header)
#print(f"Reservation response: {reserve_response.json()}")
if not reserve_response.json()["status"] == 1:
print("Reservation failed, failing this probe.")
#kill_load_generators()
return False
# Fetch the newest order, which _should_ be the one we just created
all_orders = requests.get("http://localhost:12031/api/v1/orderservice/order", headers=auth_header)
all_orders_list = all_orders.json()["data"]
newest_order = sorted(all_orders_list, key=lambda order: order["boughtDate"], reverse=True)[0]
# Pay for the order using inside_payment service
price = "50.0"
order_id = newest_order["id"]
payment_json = {
"orderId": order_id,
"price": price,
"tripId": trip_id,
"userId": user_id
}
requests.post("http://localhost:18673/api/v1/inside_pay_service/inside_payment", json=payment_json, headers=auth_header)
#print(f"INFO: Order ID is {order_id}")
# Query payments/orders and check whether payment has actually worked (meaning that a payment entity exists for the order)
payment_query_result = requests.get("http://localhost:18673/api/v1/inside_pay_service/inside_payment/payment", headers=auth_header)
paid_order_ids = [payment["orderId"] for payment in payment_query_result.json()["data"]]
if order_id not in paid_order_ids:
print("No payment exists for this order - failing the probe.")
#kill_load_generators()
return False
print("Everything successful, ending experiment with success.")
#kill_load_generators()
return True
def scenario_two_switch_bad_config():
print("Switching database configs to bad one")
os.system(f"{os.getcwd()}/switch-mongo-configs.sh")
def scenario_two_rollback_config():
print("Switching back to good config")
os.system(f"{os.getcwd()}/rollback-mongo-config.sh")
def scenario_two_broken_probe():
# Start the Load Generator to create background load reflecting user interaction with the system
#os.system(f"{os.getcwd()}/steady-state.sh")
#print("Loadgenerators started, beginning steady state check")
# Log in to TrainTicket to be authorized for subsequent requests
login_response = requests.post(f"http://localhost:8080/api/v1/users/login", json = {"username": "admin", "password": "222222"})
login_response_parsed = login_response.json()
auth_header = {'Authorization': f"Bearer {login_response_parsed['data']['token']}"}
user_id = login_response_parsed['data']['userId']
# Get a contact id, we need that for reservations
contacts_response = requests.get("http://localhost:12347/api/v1/contactservice/contacts", headers=auth_header)
contact = contacts_response.json()["data"][0]
contact_id = contact["id"]
account_id = contact["accountId"]
# Reserve a ticket, we do however not get the order ID back from the reservation ("preserve") service
trip_id = "D1345"
tomorrow = datetime.date.today() + datetime.timedelta(days=1)
reserve_json = {
"accountId": account_id,
"contactsId": contact_id,
"tripId":trip_id,
"seatType":"2",
"date": tomorrow.strftime("%Y-%m-%d"),
"from":"Shang Hai",
"to":"Su Zhou",
"assurance":"0",
"foodType":1,
"foodName":"Bone Soup",
"foodPrice":2.5,
"stationName":"",
"storeName":""
}
reserve_response = requests.post("http://localhost:14568/api/v1/preserveservice/preserve", json=reserve_json, headers=auth_header)
#print(f"Reservation response: {reserve_response.json()}")
if not reserve_response.json()["status"] == 1:
print("Reservation failed, failing this probe.")
#kill_load_generators()
return False
# Fetch the newest order, which _should_ be the one we just created
all_orders = requests.get("http://localhost:12031/api/v1/orderservice/order", headers=auth_header)
all_orders_list = all_orders.json()["data"]
newest_order = sorted(all_orders_list, key=lambda order: order["boughtDate"], reverse=True)[0]
# Pay for the order using inside_payment service
price = "50.0"
order_id = newest_order["id"]
payment_json = {
"orderId": order_id,
"price": price,
"tripId": trip_id,
"userId": user_id
}
requests.post("http://localhost:18673/api/v1/inside_pay_service/inside_payment", json=payment_json, headers=auth_header)
#print(f"INFO: Order ID is {order_id}")
# Query payments/orders and check whether payment has actually worked (meaning that a payment entity exists for the order)
payment_query_result = requests.get("http://localhost:18673/api/v1/inside_pay_service/inside_payment/payment", headers=auth_header)
paid_order_ids = [payment["orderId"] for payment in payment_query_result.json()["data"]]
if order_id not in paid_order_ids:
print("No payment exists for this order - fault is happening.")
#kill_load_generators()
return True
print("The order has been paid, indicating that the fault injection failed.")
#kill_load_generators()
return False
def scenario_three_steady_state_probe():
# TODO: Move this login/auth part into a new function to avoid duplicate code
# Start the Load Generator to create background load reflecting user interaction with the system
#os.system(f"{os.getcwd()}/steady-state.sh")
#print("Loadgenerators started, beginning steady state check")
# Log in to TrainTicket to be authorized for subsequent requests
login_response = requests.post(f"http://localhost:8080/api/v1/users/login", json = {"username": "admin", "password": "222222"})
login_response_parsed = login_response.json()
auth_header = {'Authorization': f"Bearer {login_response_parsed['data']['token']}"}
user_id = login_response_parsed['data']['userId']
# Get a contact id, we need that for reservations
contacts_response = requests.get("http://localhost:12347/api/v1/contactservice/contacts", headers=auth_header)
contact = contacts_response.json()["data"][0]
contact_id = contact["id"]
account_id = contact["accountId"]
# TODO: Move ticket reservation part into a new function to avoid duplicate code
# Reserve a ticket, we do however not get the order ID back from the reservation ("preserve") service
trip_id = "D1345"
tomorrow = datetime.date.today() + datetime.timedelta(days=1)
reserve_json = {
"accountId": account_id,
"contactsId": contact_id,
"tripId":trip_id,
"seatType":"2",
"date": tomorrow.strftime("%Y-%m-%d"),
"from":"Shang Hai",
"to":"Su Zhou",
"assurance":"0",
"foodType":1,
"foodName":"Bone Soup",
"foodPrice":2.5,
"stationName":"",
"storeName":""
}
reserve_response = requests.post("http://localhost:14568/api/v1/preserveservice/preserve", json=reserve_json, headers=auth_header)
#print(f"Reservation response: {reserve_response.json()}")
if not reserve_response.json()["status"] == 1:
print("Reservation failed, failing this probe.")
kill_load_generators()
return False
print("Everything successful, ending experiment with success.")
kill_load_generators()
return True
def scenario_three_switch_faulty_preserve_controller():
print("Switching database configs to bad one")
os.system(f"{os.getcwd()}/switch-preserve-controller.sh")
print("Waiting 10 seconds for the replaced service to boot.")
time.sleep(10)
def scenario_three_rollback_preserve_controller():
print("Switching back to good config")
os.system(f"{os.getcwd()}/rollback-preserve-controller.sh")
def scenario_three_broken_probe():
# Start the Load Generator to create background load reflecting user interaction with the system
#os.system(f"{os.getcwd()}/steady-state.sh")
#print("Loadgenerators started, beginning steady state check")
# Log in to TrainTicket to be authorized for subsequent requests
login_response = requests.post(f"http://localhost:8080/api/v1/users/login", json = {"username": "admin", "password": "222222"})
login_response_parsed = login_response.json()
auth_header = {'Authorization': f"Bearer {login_response_parsed['data']['token']}"}
user_id = login_response_parsed['data']['userId']
# Get a contact id, we need that for reservations
contacts_response = requests.get("http://localhost:12347/api/v1/contactservice/contacts", headers=auth_header)
contact = contacts_response.json()["data"][0]
contact_id = contact["id"]
account_id = contact["accountId"]
# Reserve a ticket, we do however not get the order ID back from the reservation ("preserve") service
trip_id = "D1345"
tomorrow = datetime.date.today() + datetime.timedelta(days=1)
reserve_json = {
"accountId": account_id,
"contactsId": contact_id,
"tripId":trip_id,
"seatType":"2",
"date": tomorrow.strftime("%Y-%m-%d"),
"from":"Shang Hai",
"to":"Su Zhou",
"assurance":"0",
"foodType":1,
"foodName":"Bone Soup",
"foodPrice":2.5,
"stationName":"",
"storeName":""
}
reserve_response = requests.post("http://localhost:14568/api/v1/preserveservice/preserve", json=reserve_json, headers=auth_header)
#print(f"Reservation response: {reserve_response.json()}")
if reserve_response.headers['Content-Type'] != "application/json":
print("Response type is not JSON - fault is happening.")
kill_load_generators()
return True
print("The response type is still JSON, indicating that the fault injection failed.")
kill_load_generators()
return False
# Returns after detecting that TrainTicket has bootet
def probe_wait_trainticket_running_k8s():
trainticket_booted = False
while not trainticket_booted:
status = subprocess.getoutput("kubectl get pods | grep 0/1")
trainticket_booted = not status
print(f"Waiting 30 seconds. TrainTicket status is: {status}")
time.sleep(30)
return
# Waits until a pod error has occured
def probe_wait_poderror_trainticket_k8s():
error_occured = False
while not error_occured:
pods = subprocess.getoutput("kubectl get pods | grep 0/1")
number_of_lines = pods.count('\n')
error_occured = ("Error" in pods or "CrashLoopBackOff" in pods) and number_of_lines < 10
print(f"Waiting 15 seconds. Pod status is: {pods}")
time.sleep(15)
return
def twentyfive_percent_chance():
nonce = random.randint(1, 4)
return nonce == 1
def action_scramble_and_redeploy():
invalid_dns_config = {'dnsPolicy': 'None', 'dnsConfig': {'nameservers': ['192.168.1.213']}}
with open('quickstart-ts-deployment-part2.yml') as file:
docs = yaml.load_all(file, Loader=yaml.FullLoader)
scrambled_docs = []
for doc in docs:
if doc['kind'] == 'Deployment' and twentyfive_percent_chance():
doc["spec"]["template"]["spec"].update(invalid_dns_config)
scrambled_docs.append(doc)
# This should overwrite the file in place, as according to: https://stackoverflow.com/a/53607914
with open("scrambled-yaml.yaml", "w") as outfile:
yaml.dump_all(scrambled_docs, outfile, default_flow_style=False)
print("Finished scrambling, redeploying next.")
os.system(f"{os.getcwd()}/replace-and-redeploy-2.sh")
def probe_all_pods_ok():
pods = subprocess.getoutput("kubectl get pods")
pod_rows = pods.splitlines()
del pod_rows[0]
faulty_pods_exist = False
for row in pod_rows:
faulty_pods_exist = not ("1/1" in row and "Running" in row)
if faulty_pods_exist:
break
return not faulty_pods_exist