cavisson-pythonagent 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. cavisson_pythonagent-0.0.1.dist-info/METADATA +32 -0
  2. cavisson_pythonagent-0.0.1.dist-info/RECORD +180 -0
  3. cavisson_pythonagent-0.0.1.dist-info/WHEEL +5 -0
  4. cavisson_pythonagent-0.0.1.dist-info/licenses/LICENSE +19 -0
  5. cavisson_pythonagent-0.0.1.dist-info/top_level.txt +1 -0
  6. pythonagent/__init__.py +22 -0
  7. pythonagent/agent/__init__.py +219 -0
  8. pythonagent/agent/internal/__init__.py +1 -0
  9. pythonagent/agent/internal/agent.py +1651 -0
  10. pythonagent/agent/internal/framesinfo.py +152 -0
  11. pythonagent/agent/internal/heap_dump.py +39 -0
  12. pythonagent/agent/internal/intercept_module.py +70 -0
  13. pythonagent/agent/internal/logs.py +122 -0
  14. pythonagent/agent/internal/metadata/__init__.py +0 -0
  15. pythonagent/agent/internal/metadata/agent_meta_data.py +276 -0
  16. pythonagent/agent/internal/proc_compat.py +75 -0
  17. pythonagent/agent/internal/profile.py +47 -0
  18. pythonagent/agent/internal/provider.py +83 -0
  19. pythonagent/agent/internal/thread_dump.py +85 -0
  20. pythonagent/agent/internal/udp.py +245 -0
  21. pythonagent/agent/internal/udp_message.py +800 -0
  22. pythonagent/agent/probes/Instrumentation/__init__.py +340 -0
  23. pythonagent/agent/probes/Instrumentation/find.py +243 -0
  24. pythonagent/agent/probes/Instrumentation/module_version_resolver.py +155 -0
  25. pythonagent/agent/probes/Instrumentation/new_parser.py +64 -0
  26. pythonagent/agent/probes/Instrumentation/parser.py +129 -0
  27. pythonagent/agent/probes/__init__.py +219 -0
  28. pythonagent/agent/probes/base.py +303 -0
  29. pythonagent/agent/probes/cache/__init__.py +51 -0
  30. pythonagent/agent/probes/cache/redis.py +119 -0
  31. pythonagent/agent/probes/cache/redis_asyncio.py +83 -0
  32. pythonagent/agent/probes/coroutines/__init__.py +1 -0
  33. pythonagent/agent/probes/coroutines/asyncio.py +63 -0
  34. pythonagent/agent/probes/elasticdb/__init__.py +7 -0
  35. pythonagent/agent/probes/elasticdb/aelastic.py +54 -0
  36. pythonagent/agent/probes/frameworks/__init__.py +27 -0
  37. pythonagent/agent/probes/frameworks/agentprofiler.py +105 -0
  38. pythonagent/agent/probes/frameworks/aiohttp_web.py +155 -0
  39. pythonagent/agent/probes/frameworks/aisess.py +151 -0
  40. pythonagent/agent/probes/frameworks/asgi.py +340 -0
  41. pythonagent/agent/probes/frameworks/bottle.py +27 -0
  42. pythonagent/agent/probes/frameworks/cherry.py +25 -0
  43. pythonagent/agent/probes/frameworks/django.py +128 -0
  44. pythonagent/agent/probes/frameworks/falcon.py +21 -0
  45. pythonagent/agent/probes/frameworks/fastapi.py +35 -0
  46. pythonagent/agent/probes/frameworks/flask.py +30 -0
  47. pythonagent/agent/probes/frameworks/pyramid.py +56 -0
  48. pythonagent/agent/probes/frameworks/test.py +108 -0
  49. pythonagent/agent/probes/frameworks/tornado_async_web.py +117 -0
  50. pythonagent/agent/probes/frameworks/tornado_web.py +133 -0
  51. pythonagent/agent/probes/frameworks/wsgi.py +353 -0
  52. pythonagent/agent/probes/grpc/__init__.py +76 -0
  53. pythonagent/agent/probes/grpc/client_interceptor.py +132 -0
  54. pythonagent/agent/probes/grpc/server_interceptor.py +129 -0
  55. pythonagent/agent/probes/havoc/__init__.py +0 -0
  56. pythonagent/agent/probes/havoc/custom_memory_stress.py +186 -0
  57. pythonagent/agent/probes/havoc/custom_thread_stress.py +187 -0
  58. pythonagent/agent/probes/havoc/havoc_constants.py +218 -0
  59. pythonagent/agent/probes/havoc/havoc_manager.py +981 -0
  60. pythonagent/agent/probes/http/__init__.py +49 -0
  61. pythonagent/agent/probes/http/aiohttp_client.py +59 -0
  62. pythonagent/agent/probes/http/boto.py +12 -0
  63. pythonagent/agent/probes/http/httplib.py +110 -0
  64. pythonagent/agent/probes/http/httpx_client.py +116 -0
  65. pythonagent/agent/probes/http/requests.py +15 -0
  66. pythonagent/agent/probes/http/tornado_httpclient.py +85 -0
  67. pythonagent/agent/probes/http/urllib3.py +16 -0
  68. pythonagent/agent/probes/langchain/__init__.py +21 -0
  69. pythonagent/agent/probes/langchain/base_tool.py +95 -0
  70. pythonagent/agent/probes/langchain/langchain_community.py +136 -0
  71. pythonagent/agent/probes/langchain/langchain_core.py +32 -0
  72. pythonagent/agent/probes/langchain/langchain_openai.py +110 -0
  73. pythonagent/agent/probes/logging/__init__.py +106 -0
  74. pythonagent/agent/probes/message_brokers/__init__.py +4 -0
  75. pythonagent/agent/probes/message_brokers/pika.py +126 -0
  76. pythonagent/agent/probes/mongodb/__init__.py +6 -0
  77. pythonagent/agent/probes/mongodb/pymongo.py +286 -0
  78. pythonagent/agent/probes/openai/__init__.py +3 -0
  79. pythonagent/agent/probes/openai/openai.py +797 -0
  80. pythonagent/agent/probes/span.py +101 -0
  81. pythonagent/agent/probes/sql/__init__.py +13 -0
  82. pythonagent/agent/probes/sql/botocores3.py +51 -0
  83. pythonagent/agent/probes/sql/dbapi.py +285 -0
  84. pythonagent/agent/probes/sql/dynamodb.py +90 -0
  85. pythonagent/agent/probes/sql/mysql_connector.py +24 -0
  86. pythonagent/agent/probes/sql/mysql_connector_cext.py +24 -0
  87. pythonagent/agent/probes/sql/mysqldb.py +43 -0
  88. pythonagent/agent/probes/sql/psycopg2.py +174 -0
  89. pythonagent/agent/probes/sql/pymysql.py +25 -0
  90. pythonagent/bootstrap/__init__.py +0 -0
  91. pythonagent/bootstrap/cav_gunicorn.py +26 -0
  92. pythonagent/bootstrap/cavagent_lambda_wrapper.py +291 -0
  93. pythonagent/bootstrap/run.py +47 -0
  94. pythonagent/bootstrap/sitecustomize.py +287 -0
  95. pythonagent/cavisson/netdiagnostics/CavAgent/instrumentationprofile.json +26 -0
  96. pythonagent/cavisson/netdiagnostics/CavAgent/interceptor_points.txt +29 -0
  97. pythonagent/cavisson/netdiagnostics/python/CavAgent/instrumentationprofile.json +42 -0
  98. pythonagent/cavisson/netdiagnostics/python/CavAgent/interceptor_points.txt +29 -0
  99. pythonagent/cavisson/netdiagnostics/python/config/ndsettings.conf +6 -0
  100. pythonagent/config.py +279 -0
  101. pythonagent/find.py +72 -0
  102. pythonagent/find_mod_cls_name.py +54 -0
  103. pythonagent/lang.py +131 -0
  104. pythonagent/lib.py +91 -0
  105. pythonagent/main/__init__.py +0 -0
  106. pythonagent/main/pytrace/__init__.py +79 -0
  107. pythonagent/main/pytrace/commands/__init__.py +0 -0
  108. pythonagent/main/pytrace/commands/auto_discovery.py +25 -0
  109. pythonagent/main/pytrace/commands/run.py +401 -0
  110. pythonagent/main/pytrace/pytrace.py +133 -0
  111. pythonagent/main/wsgi.py +6 -0
  112. pythonagent/main.py +27 -0
  113. pythonagent/run.py +46 -0
  114. pythonagent/sqins.py +8 -0
  115. pythonagent/test.py +73 -0
  116. pythonagent/utils.py +168 -0
  117. pythonagent/vendor/__init__.py +0 -0
  118. pythonagent/vendor/pympler/__init__.py +1 -0
  119. pythonagent/vendor/pympler/asizeof.py +2810 -0
  120. pythonagent/vendor/pympler/charts.py +62 -0
  121. pythonagent/vendor/pympler/classtracker.py +590 -0
  122. pythonagent/vendor/pympler/classtracker_stats.py +780 -0
  123. pythonagent/vendor/pympler/garbagegraph.py +80 -0
  124. pythonagent/vendor/pympler/mprofile.py +97 -0
  125. pythonagent/vendor/pympler/muppy.py +275 -0
  126. pythonagent/vendor/pympler/panels.py +115 -0
  127. pythonagent/vendor/pympler/process.py +238 -0
  128. pythonagent/vendor/pympler/py.typed +0 -0
  129. pythonagent/vendor/pympler/refbrowser.py +451 -0
  130. pythonagent/vendor/pympler/refgraph.py +350 -0
  131. pythonagent/vendor/pympler/summary.py +321 -0
  132. pythonagent/vendor/pympler/tracker.py +267 -0
  133. pythonagent/vendor/pympler/util/__init__.py +0 -0
  134. pythonagent/vendor/pympler/util/bottle.py +3809 -0
  135. pythonagent/vendor/pympler/util/compat.py +23 -0
  136. pythonagent/vendor/pympler/util/stringutils.py +77 -0
  137. pythonagent/vendor/pympler/web.py +346 -0
  138. pythonagent/vendor/werkzeug/__init__.py +20 -0
  139. pythonagent/vendor/werkzeug/_compat.py +228 -0
  140. pythonagent/vendor/werkzeug/_internal.py +473 -0
  141. pythonagent/vendor/werkzeug/_reloader.py +341 -0
  142. pythonagent/vendor/werkzeug/datastructures.py +3120 -0
  143. pythonagent/vendor/werkzeug/debug/__init__.py +498 -0
  144. pythonagent/vendor/werkzeug/debug/console.py +218 -0
  145. pythonagent/vendor/werkzeug/debug/repr.py +297 -0
  146. pythonagent/vendor/werkzeug/debug/tbtools.py +628 -0
  147. pythonagent/vendor/werkzeug/exceptions.py +829 -0
  148. pythonagent/vendor/werkzeug/filesystem.py +64 -0
  149. pythonagent/vendor/werkzeug/formparser.py +584 -0
  150. pythonagent/vendor/werkzeug/http.py +1307 -0
  151. pythonagent/vendor/werkzeug/local.py +420 -0
  152. pythonagent/vendor/werkzeug/middleware/__init__.py +25 -0
  153. pythonagent/vendor/werkzeug/middleware/dispatcher.py +66 -0
  154. pythonagent/vendor/werkzeug/middleware/http_proxy.py +219 -0
  155. pythonagent/vendor/werkzeug/middleware/lint.py +408 -0
  156. pythonagent/vendor/werkzeug/middleware/profiler.py +132 -0
  157. pythonagent/vendor/werkzeug/middleware/proxy_fix.py +169 -0
  158. pythonagent/vendor/werkzeug/middleware/shared_data.py +293 -0
  159. pythonagent/vendor/werkzeug/posixemulation.py +117 -0
  160. pythonagent/vendor/werkzeug/routing.py +2210 -0
  161. pythonagent/vendor/werkzeug/security.py +249 -0
  162. pythonagent/vendor/werkzeug/serving.py +1117 -0
  163. pythonagent/vendor/werkzeug/test.py +1123 -0
  164. pythonagent/vendor/werkzeug/testapp.py +241 -0
  165. pythonagent/vendor/werkzeug/urls.py +1138 -0
  166. pythonagent/vendor/werkzeug/useragents.py +202 -0
  167. pythonagent/vendor/werkzeug/utils.py +778 -0
  168. pythonagent/vendor/werkzeug/wrappers/__init__.py +36 -0
  169. pythonagent/vendor/werkzeug/wrappers/accept.py +50 -0
  170. pythonagent/vendor/werkzeug/wrappers/auth.py +33 -0
  171. pythonagent/vendor/werkzeug/wrappers/base_request.py +673 -0
  172. pythonagent/vendor/werkzeug/wrappers/base_response.py +700 -0
  173. pythonagent/vendor/werkzeug/wrappers/common_descriptors.py +341 -0
  174. pythonagent/vendor/werkzeug/wrappers/cors.py +100 -0
  175. pythonagent/vendor/werkzeug/wrappers/etag.py +304 -0
  176. pythonagent/vendor/werkzeug/wrappers/json.py +145 -0
  177. pythonagent/vendor/werkzeug/wrappers/request.py +49 -0
  178. pythonagent/vendor/werkzeug/wrappers/response.py +84 -0
  179. pythonagent/vendor/werkzeug/wrappers/user_agent.py +14 -0
  180. pythonagent/vendor/werkzeug/wsgi.py +1000 -0
@@ -0,0 +1,981 @@
1
+ import json
2
+ from collections import namedtuple
3
+ import random
4
+ import time
5
+ import logging
6
+ import os
7
+ import threading
8
+
9
+ from pythonagent.agent.internal import udp
10
+ from pythonagent.agent.probes.havoc.havoc_constants import HavocType, HAVOC_TYPES, HAVOC_TYPE_NAMES, EXPIRE_DURATION_MS, SIZE_OF_OBJECTS_BYTES, HAVOC_PROFILES_JSON
11
+ from pythonagent.agent.probes.havoc.custom_memory_stress import apply_memory_leak, stop_memory_leak
12
+ from pythonagent.agent.probes.havoc.custom_thread_stress import apply_thread_leak, stop_thread_leak, ThreadAllocationError
13
+ from pythonagent.utils import get_current_timestamp_in_ms
14
+ import pythonagent.agent as agent
15
+
16
+
17
+
18
+ class NDHavocException(Exception):
19
+ def __init__(self, message):
20
+ self.message = message
21
+ super().__init__(self.message)
22
+
23
+
24
+ class NDNetHavocRequest(object):
25
+ def __init__(self, havoc_conf, header_dict):
26
+
27
+ self.logger = logging.getLogger('pythonagent.agent')
28
+ self.startTime = get_current_timestamp_in_ms()
29
+
30
+ self.header_dict = header_dict
31
+ self.nhmid = header_dict["NHMID"]
32
+
33
+ self.havocType = HavocType(int(havoc_conf.havocType))
34
+ self.totalDurationInSec = getattr(havoc_conf, "totalDurationInSec", 0)
35
+
36
+ if hasattr(havoc_conf, 'delayInSec'):
37
+ self.delayInSec = havoc_conf.delayInSec
38
+
39
+ if self.havocType in (HavocType.INBOUND_SERVICE_DELAY, HavocType.OUTBOUND_SERVICE_DELAY, HavocType.METHOD_CALL_DELAY):
40
+ self.havocStarttime = self.startTime
41
+ self.shootUpDuration = getattr(havoc_conf, 'shootUpDuration', 0)
42
+ self.stableDuration = getattr(havoc_conf, 'stableDuration', 0)
43
+ self.tumbleDuration = getattr(havoc_conf, 'tumbleDuration', 0)
44
+ self.stableStartTime = self.shootUpDuration
45
+ self.stableDurationEndTime = self.shootUpDuration + self.stableDuration
46
+ self.tumbleDownStartTime = self.totalDurationInSec * 1000 - self.tumbleDuration
47
+
48
+ if self.havocType in (HavocType.INBOUND_SERVICE_DELAY, HavocType.INBOUND_SERVICE_FAILURE):
49
+ self.bTNameMode = havoc_conf.bTNameMode
50
+ if hasattr(havoc_conf, 'uRL'):
51
+ self.uRL = havoc_conf.uRL
52
+ if hasattr(havoc_conf, 'bTName'):
53
+ self.bTName = havoc_conf.bTName
54
+
55
+ if self.havocType in (HavocType.OUTBOUND_SERVICE_DELAY, HavocType.OUTBOUND_SERVICE_FAILURE):
56
+ self.backendNameMod = havoc_conf.backendNameMod
57
+ if hasattr(havoc_conf, 'bTNameMode'):
58
+ self.bTNameMode = havoc_conf.bTNameMode
59
+ if hasattr(havoc_conf, 'uRL'):
60
+ self.uRL = havoc_conf.uRL
61
+ if hasattr(havoc_conf, 'bTName'):
62
+ self.bTName = havoc_conf.bTName
63
+ if hasattr(havoc_conf, 'hostname'):
64
+ self.hostname = havoc_conf.hostname
65
+ if hasattr(havoc_conf, 'backendName'):
66
+ self.backendName = havoc_conf.backendName
67
+
68
+ if self.havocType in (HavocType.METHOD_CALL_DELAY, HavocType.METHOD_CALL_FAILURE):
69
+ self.methodMod = havoc_conf.methodMod
70
+ if hasattr(havoc_conf, 'methodFQM'):
71
+ self.methodFQM = havoc_conf.methodFQM
72
+ if hasattr(havoc_conf, 'bTNameMode'):
73
+ self.bTNameMode = havoc_conf.bTNameMode
74
+ if hasattr(havoc_conf, 'uRL'):
75
+ self.uRL = havoc_conf.uRL
76
+ if hasattr(havoc_conf, 'bTName'):
77
+ self.bTName = havoc_conf.bTName
78
+
79
+ if self.havocType == HavocType.CUSTOM_MEMORY_LEAK:
80
+ self.leaksINMB = havoc_conf.leaksINMB
81
+ self.objectSize = havoc_conf.objectSize
82
+ self.shootUpDuration = getattr(havoc_conf, "shootUpDuration", 0)
83
+
84
+ if self.havocType == HavocType.APPLICATION_KILL:
85
+ self.methodMod = havoc_conf.methodMod
86
+ self.methodFQM = havoc_conf.methodFQM
87
+ self.customExitCode = havoc_conf.customExitCode
88
+ self.methodEntryOrExit = havoc_conf.methodEntryOrExit
89
+ self.killApplication = havoc_conf.killApplication
90
+ self.latencyInSec = havoc_conf.latencyInSec
91
+
92
+ if self.havocType == HavocType.CUSTOM_THREAD_LEAK:
93
+ self.numberOfThreads = havoc_conf.numberOfThreads
94
+ self.threadSleepTimeInMS = havoc_conf.threadSleepTimeInMS
95
+
96
+ self.shootUpDuration = getattr(havoc_conf, "shootUpDuration", 0)
97
+ self.stableDuration = getattr(havoc_conf, "stableDuration", 0)
98
+ self.tumbleDuration = getattr(havoc_conf, "tumbleDuration", 0)
99
+
100
+ try:
101
+
102
+ self.protocol = getattr(havoc_conf, 'protocol', None)
103
+ self.bTName = havoc_conf.bTName
104
+ self.isHavocEnable = False
105
+ self.threshOld = havoc_conf.threshOld
106
+ self.backendName = havoc_conf.backendName
107
+
108
+ except Exception as e:
109
+ self.logger.warning("Optional parameters not found {}".format(e))
110
+
111
+
112
+ class NDNetHavocMonitor(object):
113
+
114
+ __instance = None
115
+
116
+ @staticmethod
117
+ def get_instance():
118
+ if NDNetHavocMonitor.__instance == None:
119
+ NDNetHavocMonitor()
120
+ return NDNetHavocMonitor.__instance
121
+
122
+ def __init__(self):
123
+ """ Virtually private constructor. """
124
+ if NDNetHavocMonitor.__instance != None:
125
+ raise Exception("This class is a singleton! Use get_instance()")
126
+ else:
127
+ NDNetHavocMonitor.__instance = self
128
+
129
+ self.enableNetHavoc = False
130
+
131
+ self.netHavocConfigMap = {
132
+ "InBoundService_Delay": [],
133
+ "InBoundService_Failure": [],
134
+ "OutBoundService_Delay": [],
135
+ "OutBoundService_Failure": [],
136
+ "MethodCall_Delay": [],
137
+ "MethodCall_Failure": [],
138
+ "Custom_Memory_Leak": [],
139
+ "Custom_Thread_Leak": [],
140
+ "Application_Kill": []
141
+ }
142
+
143
+ self.logger = logging.getLogger('pythonagent.agent')
144
+
145
+ def set_enable_net_havoc(self, enable_logs_net_havoc):
146
+ try:
147
+ if enable_logs_net_havoc == 1:
148
+ self.enableNetHavoc = True
149
+ else:
150
+ self.enableNetHavoc = False
151
+ except Exception as e:
152
+ raise e
153
+
154
+ def validate_config(self, havoc_conf):
155
+ self.logger.info("Validating config")
156
+ is_valid = False
157
+
158
+ # `enable` field is no longer present in incoming JSON, so this validation is disabled to keep inbound Havoc working.
159
+ '''
160
+ # Validate enable
161
+ if not hasattr(havoc_conf, 'enable'):
162
+ return_message = "enable not found"
163
+ return is_valid, return_message
164
+
165
+ else:
166
+ if havoc_conf.enable not in [0, 1]:
167
+ return_message = "invalid enable not found"
168
+ return is_valid, return_message
169
+ else:
170
+ logging.debug("enable validated")
171
+ '''
172
+ # Validate havocType
173
+ if not hasattr(havoc_conf, 'havocType'):
174
+ return_message = "havocType not found"
175
+ return is_valid, return_message
176
+ else:
177
+ try:
178
+ havoc_type = HavocType(int(havoc_conf.havocType))
179
+ except ValueError:
180
+ return_message = "invalid havocType"
181
+ return is_valid, return_message
182
+ logging.debug("havocType validated")
183
+
184
+ if havoc_type == HavocType.APPLICATION_KILL:
185
+ is_valid, return_message = self.validate_application_kill_config(havoc_conf)
186
+ return is_valid, return_message
187
+
188
+ if havoc_type == HavocType.CUSTOM_THREAD_LEAK:
189
+ is_valid, return_message = self.validate_thread_leak_config(havoc_conf)
190
+ return is_valid, return_message
191
+
192
+ # Validate totalDurationInSec
193
+ if not hasattr(havoc_conf, 'totalDurationInSec'):
194
+ return_message = "totalDurationInSec not found"
195
+ return is_valid, return_message
196
+ else:
197
+ if not isinstance(havoc_conf.totalDurationInSec, int):
198
+ return_message = "invalid totalDurationInSec"
199
+ return is_valid, return_message
200
+ else:
201
+ logging.debug("totalDurationInSec validated")
202
+
203
+ # Validate threshOld
204
+ if not hasattr(havoc_conf, 'threshOld'):
205
+ return_message = "threshOld not found"
206
+ return is_valid, return_message
207
+ else:
208
+ if not hasattr(havoc_conf.threshOld, 'percentage'):
209
+ return_message = "invalid threshOld"
210
+ return is_valid, return_message
211
+ else:
212
+ self.logger.debug("threshOld validated")
213
+
214
+ # Validate uRL for Havoc Type 1 and 2
215
+ if havoc_type in (HavocType.INBOUND_SERVICE_DELAY, HavocType.INBOUND_SERVICE_FAILURE):
216
+ self.logger.debug("bTNameMode: {}".format(havoc_conf.bTNameMode))
217
+ if havoc_conf.bTNameMode == 0:
218
+ pass
219
+ elif havoc_conf.bTNameMode in [1, 2]:
220
+ if not hasattr(havoc_conf, 'bTName'):
221
+ return_message = "bTName not found for inbound service"
222
+ return is_valid, return_message
223
+ elif len(havoc_conf.bTName.strip()) == 0:
224
+ return_message = "invalid bTName for inbound service"
225
+ return is_valid, return_message
226
+ else:
227
+ self.logger.debug("bTName validated")
228
+ elif havoc_conf.bTNameMode == 3:
229
+
230
+ if not hasattr(havoc_conf, 'uRL'):
231
+ return_message = "uRL not found for inbound service"
232
+ return is_valid, return_message
233
+
234
+ else:
235
+ if len(havoc_conf.uRL) == 0:
236
+ return_message = "invalid uRL for inbound service"
237
+ return is_valid, return_message
238
+ else:
239
+ self.logger.debug("uRL validated")
240
+
241
+ else:
242
+ return_message = "invalid bTNameMode"
243
+ return is_valid, return_message
244
+ else:
245
+ self.logger.debug("uRL not needed")
246
+
247
+ # Validate hostname and protocol for Havoc Type 3 and 4
248
+ if havoc_type in (HavocType.OUTBOUND_SERVICE_DELAY, HavocType.OUTBOUND_SERVICE_FAILURE):
249
+ if hasattr(havoc_conf, 'bTNameMode'):
250
+ if havoc_conf.bTNameMode not in [0, 1, 2, 3]:
251
+ return_message = "invalid bTNameMode for outbound service"
252
+ return is_valid, return_message
253
+ if havoc_conf.bTNameMode in [1, 2]:
254
+ if not hasattr(havoc_conf, 'bTName'):
255
+ return_message = "bTName not found for outbound service"
256
+ return is_valid, return_message
257
+ if len(havoc_conf.bTName.strip()) == 0:
258
+ return_message = "invalid bTName for outbound service"
259
+ return is_valid, return_message
260
+ if havoc_conf.bTNameMode == 3:
261
+ if not hasattr(havoc_conf, 'uRL'):
262
+ return_message = "uRL not found for outbound service"
263
+ return is_valid, return_message
264
+ if len(havoc_conf.uRL.strip()) == 0:
265
+ return_message = "invalid uRL for outbound service"
266
+ return is_valid, return_message
267
+
268
+ if havoc_conf.backendNameMod == 0:
269
+ if hasattr(havoc_conf, 'backendName') and len(havoc_conf.backendName.strip()) == 0:
270
+ return_message = "invalid backendName for outbound service"
271
+ return is_valid, return_message
272
+ elif havoc_conf.backendNameMod in [1, 2]:
273
+ if not hasattr(havoc_conf, 'backendName'):
274
+ return_message = "backendName not found for outbound service"
275
+ return is_valid, return_message
276
+ elif len(havoc_conf.backendName.strip()) == 0:
277
+ return_message = "invalid backendName for outbound service"
278
+ return is_valid, return_message
279
+ else:
280
+ self.logger.debug("backendName validated")
281
+ elif havoc_conf.backendNameMod == 3:
282
+ if not hasattr(havoc_conf, 'hostname'):
283
+ return_message = "hostname not found for outbound service"
284
+ return is_valid, return_message
285
+ else:
286
+ if len(havoc_conf.hostname) == 0:
287
+ return_message = "invalid hostname for outbound service"
288
+ return is_valid, return_message
289
+ else:
290
+ self.logger.debug("hostname validated")
291
+
292
+ if not hasattr(havoc_conf, 'protocol'):
293
+ if havoc_conf.protocol not in ["Http", "Non Http"]:
294
+ return_message = "invalid protocol for outbound service"
295
+ return is_valid, return_message
296
+ else:
297
+ self.logger.debug("protocol validated")
298
+ else:
299
+ self.logger.debug("protocol not found")
300
+ elif havoc_conf.backendNameMod == 4:
301
+ if not hasattr(havoc_conf, 'hostname'):
302
+ return_message = "hostname not found for outbound service"
303
+ return is_valid, return_message
304
+ elif len(havoc_conf.hostname) == 0:
305
+ return_message = "invalid hostname for outbound service"
306
+ return is_valid, return_message
307
+ else:
308
+ self.logger.debug("hostname validated")
309
+ else:
310
+ return_message = "invalid backendNameMod"
311
+ return is_valid, return_message
312
+
313
+ else:
314
+ self.logger.debug("hostname not needed")
315
+
316
+ # Validate methodFQM
317
+ if havoc_type in (HavocType.METHOD_CALL_DELAY, HavocType.METHOD_CALL_FAILURE):
318
+ if not hasattr(havoc_conf, 'methodFQM'):
319
+ return_message = "methodFQM not found for method"
320
+ return is_valid, return_message
321
+ else:
322
+ if len(havoc_conf.methodFQM) == 0:
323
+ return_message = "invalid methodFQM for method"
324
+
325
+ return is_valid, return_message
326
+ else:
327
+ self.logger.debug("methodFQM validated")
328
+
329
+ if hasattr(havoc_conf, 'bTNameMode'):
330
+ if havoc_conf.bTNameMode not in [0, 1, 2, 3]:
331
+ return_message = "invalid bTNameMode for method"
332
+ return is_valid, return_message
333
+ if havoc_conf.bTNameMode in [1, 2]:
334
+ if not hasattr(havoc_conf, 'bTName'):
335
+ return_message = "bTName not found for method"
336
+ return is_valid, return_message
337
+ if len(havoc_conf.bTName.strip()) == 0:
338
+ return_message = "invalid bTName for method"
339
+ return is_valid, return_message
340
+ if havoc_conf.bTNameMode == 3:
341
+ if not hasattr(havoc_conf, 'uRL'):
342
+ return_message = "uRL not found for method"
343
+ return is_valid, return_message
344
+ if len(havoc_conf.uRL.strip()) == 0:
345
+ return_message = "invalid uRL for method"
346
+ return is_valid, return_message
347
+ else:
348
+ self.logger.debug("methodFQM not needed")
349
+
350
+ if havoc_type in (HavocType.INBOUND_SERVICE_DELAY, HavocType.OUTBOUND_SERVICE_DELAY, HavocType.METHOD_CALL_DELAY):
351
+ if not hasattr(havoc_conf, 'delayInSec'):
352
+ return_message = "delayInSec not found for delay"
353
+ return is_valid, return_message
354
+ else:
355
+ if not isinstance(havoc_conf.delayInSec, int):
356
+ return_message = "invalid delayInSec for delay"
357
+ return is_valid, return_message
358
+ else:
359
+ self.logger.debug("delayInSec validated")
360
+ else:
361
+ self.logger.debug("delayInSec not needed")
362
+
363
+ if havoc_type == HavocType.CUSTOM_MEMORY_LEAK:
364
+ if not hasattr(havoc_conf, "leaksINMB"):
365
+ return_message = "leaksINMB not found for custom_memory_leak"
366
+ return is_valid, return_message
367
+ else:
368
+ if not hasattr(havoc_conf, "objectSize"):
369
+ return_message = "objectSize not found for custom_memory_leak"
370
+ return is_valid, return_message
371
+ else:
372
+ self.logger.debug("objectSize validated")
373
+ else:
374
+ self.logger.debug("leaksINMB, objectsize not needed")
375
+
376
+ if havoc_type == HavocType.CUSTOM_THREAD_LEAK:
377
+ if not hasattr(havoc_conf, "numberOfThreads"):
378
+ return_message = "numberOfThreads not found for thread_leak"
379
+ return is_valid, return_message
380
+
381
+ if not hasattr(havoc_conf, "threadSleepTimeInMS"):
382
+ return_message = "threadSleepTimeInMS not found for thread_leak"
383
+ return is_valid, return_message
384
+
385
+ is_valid = True
386
+ return_message = "Config Validated"
387
+ return is_valid, return_message
388
+
389
+ def validate_application_kill_config(self, havoc_conf):
390
+ int_fields = ["methodMod", "customExitCode", "methodEntryOrExit", "killApplication", "latencyInSec"]
391
+ for field in int_fields:
392
+ if not hasattr(havoc_conf, field):
393
+ return False, "{} not found for application kill".format(field)
394
+ if not isinstance(getattr(havoc_conf, field), int):
395
+ return False, "invalid {} for application kill".format(field)
396
+
397
+ if not hasattr(havoc_conf, "methodFQM"):
398
+ return False, "methodFQM not found for application kill"
399
+
400
+ if havoc_conf.methodMod != 1 or havoc_conf.methodFQM != "":
401
+ return False, "only direct application kill is supported"
402
+
403
+ if havoc_conf.killApplication not in [1, 2, 3, 4]:
404
+ return False, "invalid killApplication for application kill"
405
+
406
+ if havoc_conf.latencyInSec < 0:
407
+ return False, "invalid latencyInSec for application kill"
408
+
409
+ return True, "Config Validated"
410
+
411
+ def validate_thread_leak_config(self, havoc_conf):
412
+ if not hasattr(havoc_conf, "numberOfThreads"):
413
+ return False, "numberOfThreads not found for thread_leak"
414
+ if not isinstance(havoc_conf.numberOfThreads, int):
415
+ return False, "invalid numberOfThreads for thread_leak"
416
+ if havoc_conf.numberOfThreads < 0:
417
+ return False, "invalid numberOfThreads for thread_leak"
418
+ if not hasattr(havoc_conf, "threadSleepTimeInMS"):
419
+ return False, "threadSleepTimeInMS not found for thread_leak"
420
+ if not isinstance(havoc_conf.threadSleepTimeInMS, int):
421
+ return False, "invalid threadSleepTimeInMS for thread_leak"
422
+ if havoc_conf.threadSleepTimeInMS < 0:
423
+ return False, "invalid threadSleepTimeInMS for thread_leak"
424
+ if not hasattr(havoc_conf, "totalDurationInSec"):
425
+ return False, "totalDurationInSec not found for thread_leak"
426
+ if not isinstance(havoc_conf.totalDurationInSec, int):
427
+ return False, "invalid totalDurationInSec for thread_leak"
428
+ return True, "Config Validated"
429
+
430
+ def parse_nethavoc_config(self, json_conf, header_dict):
431
+ self.logger.info("Parsing config")
432
+
433
+ try:
434
+ havoc_conf = json.loads(json_conf, object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))
435
+ havoc_type = HavocType(int(havoc_conf.havocType))
436
+ self.logger.info("Havoc type: {}".format(havoc_type))
437
+ self.logger.debug("header_dict: {}".format(header_dict))
438
+ nhmid = header_dict["NHMID"]
439
+ self.logger.debug("nhmid: {}".format(nhmid))
440
+ self.logger.info("checking if havoc already exists")
441
+
442
+ havoc_type_full_str = HAVOC_TYPE_NAMES[havoc_type]
443
+ config_list = self.netHavocConfigMap[havoc_type_full_str]
444
+ self.logger.info("config_list: {}".format(config_list))
445
+
446
+ if config_list:
447
+ for config in config_list:
448
+ if nhmid == config.nhmid:
449
+ # enableHavoc is sent in the message HEADER, not the JSON
450
+ # body - confirmed from live traffic. enableHavoc == 0 is
451
+ # a stop request for this already-running havoc.
452
+ if int(header_dict.get("enableHavoc", 1)) == 0:
453
+ self.logger.info("Stop requested for existing havoc {}".format(nhmid))
454
+ config_list.remove(config)
455
+ if havoc_type == HavocType.CUSTOM_THREAD_LEAK:
456
+ stop_thread_leak()
457
+ if havoc_type == HavocType.CUSTOM_MEMORY_LEAK:
458
+ stop_memory_leak()
459
+ return True
460
+ self.logger.info("havoc already exists skipping")
461
+ return
462
+
463
+ is_valid, ret_message = self.validate_config(havoc_conf)
464
+
465
+ if is_valid:
466
+ self.logger.info(ret_message)
467
+ req_obj = NDNetHavocRequest(havoc_conf, header_dict)
468
+ self.logger.debug("Request Object: ".format(req_obj))
469
+
470
+ havoc_type_full_str = HAVOC_TYPE_NAMES[havoc_type]
471
+ self.netHavocConfigMap[havoc_type_full_str].append(req_obj)
472
+
473
+ if havoc_type == HavocType.APPLICATION_KILL:
474
+ self.apply_application_kill(req_obj)
475
+ return True
476
+
477
+ if havoc_type == HavocType.CUSTOM_THREAD_LEAK:
478
+ # Launch the custom thread-leak worker in a background thread.
479
+ self.logger.info("ThreadLeakHavoc: Starting custom thread leak worker thread for NHMID %s", nhmid)
480
+ thread_leak_havoc = threading.Thread(target=self.increase_thread_count,
481
+ args=(),
482
+ daemon=True)
483
+ thread_leak_havoc.start()
484
+
485
+ if havoc_type == HavocType.CUSTOM_MEMORY_LEAK:
486
+ # Launch the custom memory-leak worker in a background thread.
487
+ self.logger.info("MemoryLeakHavoc: Starting custom memory leak worker thread for NHMID %s", nhmid)
488
+ havoc_thread = threading.Thread(target=self.increase_memory_usage,
489
+ args=(),
490
+ daemon=True)
491
+
492
+ havoc_thread.start()
493
+
494
+ else:
495
+ self.logger.info("Invalid config: {}".format(ret_message))
496
+
497
+
498
+
499
+ except Exception as e:
500
+ self.logger.error("havoc parsing exception: {}".format(e))
501
+ raise e
502
+
503
+ @staticmethod
504
+ def get_havoc_completion_header(header_dict, status="Success", message="HavocCompleted"):
505
+ response_header = dict(header_dict)
506
+ response_header["Status"] = status
507
+ response_header["Message"] = message
508
+ response_header["MsgType"] = "1"
509
+ response_header.pop("SubOperation", None)
510
+ response_header["Ack"] = "0"
511
+
512
+ header_str = "NetDiagnosticMessage 2.0;"
513
+ for key, value in response_header.items():
514
+ header_str += "{}:{};".format(key, value)
515
+ return header_str + "\n\n"
516
+
517
+ def send_havoc_status_response(self, config, status, message):
518
+ try:
519
+ header_str = self.get_havoc_completion_header(config.header_dict, status=status, message=message)
520
+ agent_obj = agent.get_agent_instance()
521
+ agent_obj.send_havoc_response(header_str)
522
+ except Exception:
523
+ self.logger.exception("ThreadLeakHavoc: failed to send havoc status response")
524
+
525
+
526
+ def apply_application_kill(self, config):
527
+ self.logger.info("Applying direct application kill havoc")
528
+ self.send_havoc_status_response(config, "Success", "HavocCompleted")
529
+ self._application_kill_after_latency(config.killApplication, config.customExitCode, config.latencyInSec)
530
+
531
+ def _application_kill_after_latency(self, kill_application, custom_exit_code, latency_in_sec):
532
+ if latency_in_sec > 0:
533
+ self.logger.info("Application kill scheduled after {} seconds".format(latency_in_sec))
534
+ time.sleep(latency_in_sec)
535
+ self.kill_application(kill_application, custom_exit_code)
536
+
537
+ def kill_application(self, kill_application, custom_exit_code=0):
538
+ self.logger.warning("Killing application using mode: {}".format(kill_application))
539
+ logging.shutdown()
540
+ if kill_application == 1:
541
+ os._exit(0)
542
+ elif kill_application == 2:
543
+ os._exit(1)
544
+ elif kill_application == 3:
545
+ os.abort()
546
+ elif kill_application == 4:
547
+ os._exit(custom_exit_code)
548
+ else:
549
+ self.logger.warning("Unsupported application kill mode: {}".format(kill_application))
550
+
551
+ def should_apply_havoc(self, percentage):
552
+ try:
553
+ percentage = int(percentage)
554
+ except (TypeError, ValueError):
555
+ self.logger.warning("Invalid havoc percentage '%s'; defaulting to 100", percentage)
556
+ return True # If percentage is invalid, apply havoc by default
557
+
558
+ if percentage <= 0:
559
+ return False
560
+ if percentage >= 100:
561
+ return True
562
+
563
+ selected_value = 100
564
+ try:
565
+ selected_value = random.randint(1, 100)
566
+ self.logger.debug("Havoc decision: percentage=%s selected_value=%s result=%s", percentage, selected_value, selected_value <= percentage)
567
+ except Exception:
568
+ pass
569
+ return selected_value <= percentage
570
+
571
+ def _get_havoc_percentage(self, config):
572
+ try:
573
+ if hasattr(config, "threshOld") and config.threshOld:
574
+ return int(getattr(config.threshOld, "percentage", 100))
575
+ except (AttributeError, TypeError, ValueError):
576
+ pass
577
+
578
+ return 100
579
+
580
+ def common_delay_in_response(self, config):
581
+ percentage = self._get_havoc_percentage(config)
582
+
583
+ if not self.should_apply_havoc(percentage):
584
+
585
+ self.logger.debug("Skipping havoc. Percentage=%s", percentage)
586
+ return
587
+
588
+ self.logger.info("Applying delay duration(ms): %s", config.delayInSec)
589
+
590
+ try:
591
+ _agent = agent.get_agent_instance()
592
+ _agent.common_sleep(config.havocStarttime, config.shootUpDuration, config.stableDuration,
593
+ config.tumbleDuration, config.delayInSec)
594
+ except Exception as e:
595
+ raise e
596
+
597
+ def common_failure(self, config, exception_message):
598
+ percentage = self._get_havoc_percentage(config)
599
+
600
+ if not self.should_apply_havoc(percentage):
601
+ self.logger.debug("Skipping failure havoc. Percentage=%s", percentage)
602
+ return
603
+
604
+ self.logger.info("Applying failure message: {}".format(exception_message))
605
+ try:
606
+ _agent = agent.get_agent_instance()
607
+ ctx = _agent.get_transaction_context()
608
+ if ctx:
609
+ ctx.havoc_applied = True
610
+ except Exception as e:
611
+ self.logger.exception("common_failure exception: {}".format(e))
612
+ raise NDHavocException(exception_message)
613
+
614
+ @staticmethod
615
+ def create_dummy_object(obj_size):
616
+ int_list_size = obj_size // 4 # Size of int = 4
617
+ obj = []
618
+ for i in range(int_list_size):
619
+ obj.append(i)
620
+ return obj
621
+
622
+ def memory_leak(self, leaksINMB, memoryLeaksInSec):
623
+
624
+ total_leak_bytes = leaksINMB * 1024 * 1024
625
+ num_of_obj = total_leak_bytes // SIZE_OF_OBJECTS_BYTES
626
+ time_for_each_obj = memoryLeaksInSec // num_of_obj
627
+
628
+ obj_created = 0
629
+ while obj_created < num_of_obj:
630
+ self.create_dummy_object(SIZE_OF_OBJECTS_BYTES)
631
+ time.sleep(time_for_each_obj)
632
+ obj_created += 0
633
+
634
+ def apply_inbound_service_delay(self, req_url, btname=None):
635
+ self.logger.info("Trying to apply inbound service delay")
636
+ config_list = self.netHavocConfigMap["InBoundService_Delay"]
637
+
638
+ if config_list:
639
+ for config in config_list:
640
+ self.logger.debug("Trying to apply havoc: {}".format(config.nhmid))
641
+
642
+ if config.bTNameMode == 0:
643
+ self.logger.debug("bTNameMode = 0, proceeding")
644
+ self.common_delay_in_response(config)
645
+ elif config.bTNameMode == 1:
646
+ config_bt_name = getattr(config, "bTName", None)
647
+ bt_name_list = [name.strip() for name in config_bt_name.split(":")] if config_bt_name else []
648
+ self.logger.debug("bTNameMode = 1, bt_name_list: {}, btname: {}".format(bt_name_list, btname))
649
+ if btname and btname in bt_name_list:
650
+ self.logger.debug("Config matched for BT name list {}".format(btname))
651
+ self.common_delay_in_response(config)
652
+ else:
653
+ self.logger.debug("Config not matched for BT name list {}".format(btname))
654
+ elif config.bTNameMode == 2:
655
+ config_bt_name = getattr(config, "bTName", None)
656
+ config_bt_name = config_bt_name.strip() if config_bt_name else None
657
+ self.logger.debug("bTNameMode = 2, config_bt_name: {}, btname: {}".format(config_bt_name, btname))
658
+ if config_bt_name and btname and (config_bt_name.upper() == "ALL" or config_bt_name in btname):
659
+ self.logger.debug("Config matched for BT name {}".format(btname))
660
+ self.common_delay_in_response(config)
661
+ else:
662
+ self.logger.debug("Config not matched for BT name {}".format(btname))
663
+ else:
664
+ url = config.uRL if hasattr(config, "uRL") else None
665
+ self.logger.debug("url: {} in req_url: {}, btname: {}".format(url, req_url, btname))
666
+ if url and url in req_url:
667
+ self.logger.debug("Config matched for Request URL {}".format(req_url))
668
+ self.common_delay_in_response(config)
669
+ else:
670
+ self.logger.debug("Config not matched for Request URL {}".format(req_url))
671
+
672
+ else:
673
+ self.logger.debug("config not set for apply_inbound_service_delay")
674
+
675
+ def apply_inbound_service_failure(self, req_url, btname=None):
676
+ self.logger.info("Trying to apply inbound service failure")
677
+ config_list = self.netHavocConfigMap["InBoundService_Failure"]
678
+
679
+ if config_list:
680
+ for config in config_list:
681
+ self.logger.debug("Trying to apply havoc: {}".format(config.nhmid))
682
+ if config.bTNameMode == 0:
683
+ self.logger.debug("bTNameMode = 0, proceeding")
684
+ self.common_failure(config, "BT:ALL")
685
+ elif config.bTNameMode == 1:
686
+ config_bt_name = getattr(config, "bTName", None)
687
+ bt_name_list = [name.strip() for name in config_bt_name.split(":")] if config_bt_name else []
688
+ self.logger.debug("bTNameMode = 1, bt_name_list: {}, btname: {}".format(bt_name_list, btname))
689
+ if btname and btname in bt_name_list:
690
+ self.logger.debug("Config matched for BT name list {}".format(btname))
691
+ self.common_failure(config, btname)
692
+ else:
693
+ self.logger.debug("Config not matched for BT name list {}".format(btname))
694
+ elif config.bTNameMode == 2:
695
+ config_bt_name = getattr(config, "bTName", None)
696
+ config_bt_name = config_bt_name.strip() if config_bt_name else None
697
+ self.logger.debug("bTNameMode = 2, config_bt_name: {}, btname: {}".format(config_bt_name, btname))
698
+ if config_bt_name and btname and (config_bt_name.upper() == "ALL" or config_bt_name in btname):
699
+ self.logger.debug("Config matched for BT name {}".format(btname))
700
+ self.common_failure(config, config_bt_name)
701
+ else:
702
+ self.logger.debug("Config not matched for BT name {}".format(btname))
703
+ else:
704
+ url = config.uRL if hasattr(config, "uRL") else None
705
+ self.logger.debug("url: {} in req_url: {}".format(url, req_url))
706
+ if url and url in req_url:
707
+ self.logger.debug("Config matched for Request URL {}".format(req_url))
708
+ self.common_failure(config, url)
709
+ else:
710
+ self.logger.debug("Config not matched for Request URL {}".format(req_url))
711
+ else:
712
+ self.logger.debug("config not set for apply_inbound_service_failure")
713
+ # raise Exception("config not set for apply_inbound_service_failure")
714
+
715
+ def apply_outbound_service_delay(self, req_hostname, backend_name=None, req_url=None, btname=None):
716
+ self.logger.info("Trying to apply outbound service delay")
717
+ config_list = self.netHavocConfigMap["OutBoundService_Delay"]
718
+
719
+ if config_list:
720
+ for config in config_list:
721
+ self.logger.debug("Trying to apply havoc: {}".format(config.nhmid))
722
+ if not self.is_matched_bt_name(config, req_url, btname):
723
+ self.logger.debug("Config not matched for outbound BT/url. btname: {} req_url: {}".format(btname, req_url))
724
+ continue
725
+
726
+ if config.backendNameMod == 0:
727
+ self.logger.debug("backendNameMod = 0, proceeding")
728
+ self.common_delay_in_response(config)
729
+ elif config.backendNameMod == 1:
730
+ config_backend_name = getattr(config, "backendName", None)
731
+ backend_name_list = [name.strip() for name in config_backend_name.split(":")] if config_backend_name else []
732
+ self.logger.debug("backendNameMod = 1, backend_name_list: {}, backend_name: {}".format(backend_name_list, backend_name))
733
+ if backend_name and any(name == backend_name or name in backend_name or backend_name in name for name in backend_name_list):
734
+ self.logger.debug("Config matched for Backend name list {}".format(backend_name))
735
+ self.common_delay_in_response(config)
736
+ else:
737
+ self.logger.debug("Config not matched for Backend name list {}".format(backend_name))
738
+ elif config.backendNameMod == 2:
739
+ config_backend_name = getattr(config, "backendName", None)
740
+ config_backend_name = config_backend_name.strip() if config_backend_name else None
741
+ self.logger.debug("backendNameMod = 2, config_backend_name: {}, backend_name: {}".format(config_backend_name, backend_name))
742
+ if config_backend_name and backend_name and (config_backend_name.upper() == "ALL" or config_backend_name in backend_name or backend_name in config_backend_name):
743
+ self.logger.debug("Config matched for Backend name {}".format(backend_name))
744
+ self.common_delay_in_response(config)
745
+ else:
746
+ self.logger.debug("Config not matched for Backend name {}".format(backend_name))
747
+ else:
748
+ hostname = config.hostname
749
+ self.logger.debug("hostname: {} in req_hostname: {}, backend_name: {}".format(hostname, req_hostname, backend_name))
750
+
751
+ if hostname.upper() == "ALL" or hostname in req_hostname:
752
+ self.logger.debug("Config matched for Request Hostname {}".format(req_hostname))
753
+ self.common_delay_in_response(config)
754
+ else:
755
+ self.logger.debug("Config not matched for Request Hostname {}".format(req_hostname))
756
+ else:
757
+ self.logger.debug("config not set for apply_outbound_service_delay")
758
+
759
+ def apply_outbound_service_failure(self, req_hostname, backend_name=None, req_url=None, btname=None):
760
+ self.logger.info("Trying to apply outbound service failure")
761
+ config_list = self.netHavocConfigMap["OutBoundService_Failure"]
762
+
763
+ if config_list:
764
+ for config in config_list:
765
+ self.logger.debug("Trying to apply havoc: {}".format(config.nhmid))
766
+ if not self.is_matched_bt_name(config, req_url, btname):
767
+ self.logger.debug("Config not matched for outbound BT/url. btname: {} req_url: {}".format(btname, req_url))
768
+ continue
769
+
770
+ if config.backendNameMod == 0:
771
+ self.logger.debug("backendNameMod = 0, proceeding")
772
+ self.common_failure(config, "Integration Point: ALL")
773
+ elif config.backendNameMod == 1:
774
+ config_backend_name = getattr(config, "backendName", None)
775
+ backend_name_list = [name.strip() for name in config_backend_name.split(":")] if config_backend_name else []
776
+ self.logger.debug("backendNameMod = 1, backend_name_list: {}, backend_name: {}".format(backend_name_list, backend_name))
777
+ if backend_name and any(name == backend_name or name in backend_name or backend_name in name for name in backend_name_list):
778
+ self.logger.debug("Config matched for Backend name list {}".format(backend_name))
779
+ self.common_failure(config, backend_name)
780
+ else:
781
+ self.logger.debug("Config not matched for Backend name list {}".format(backend_name))
782
+ elif config.backendNameMod == 2:
783
+ config_backend_name = getattr(config, "backendName", None)
784
+ config_backend_name = config_backend_name.strip() if config_backend_name else None
785
+ self.logger.debug("backendNameMod = 2, config_backend_name: {}, backend_name: {}".format(config_backend_name, backend_name))
786
+ if config_backend_name and backend_name and (config_backend_name.upper() == "ALL" or config_backend_name in backend_name or backend_name in config_backend_name):
787
+ self.logger.debug("Config matched for Backend name {}".format(backend_name))
788
+ self.common_failure(config, backend_name)
789
+ else:
790
+ self.logger.debug("Config not matched for Backend name {}".format(backend_name))
791
+ else:
792
+ hostname = getattr(config, "hostname", None)
793
+ self.logger.debug("hostname: {} in req_hostname: {}, backend_name: {}".format(hostname, req_hostname, backend_name))
794
+
795
+ if hostname and (hostname.upper() == "ALL" or hostname in req_hostname):
796
+ self.logger.debug("Config matched for Request Hostname {}".format(req_hostname))
797
+ self.common_failure(config, hostname)
798
+ else:
799
+ self.logger.debug("Config not matched for Request Hostname {}".format(req_hostname))
800
+ else:
801
+ self.logger.debug("config not set for apply_outbound_service_failure")
802
+
803
+ def is_matched_bt_name(self, config, req_url=None, btname=None):
804
+ bt_name_mode = getattr(config, "bTNameMode", None)
805
+ if bt_name_mode is None:
806
+ return True
807
+
808
+ if bt_name_mode == 0:
809
+ self.logger.debug("bTNameMode = 0, proceeding")
810
+ return True
811
+
812
+ if bt_name_mode == 1:
813
+ config_bt_name = getattr(config, "bTName", None)
814
+ bt_name_list = [name.strip() for name in config_bt_name.split(":")] if config_bt_name else []
815
+ self.logger.debug("bTNameMode = 1, bt_name_list: {}, btname: {}".format(bt_name_list, btname))
816
+ return bool(btname and btname in bt_name_list)
817
+
818
+ if bt_name_mode == 2:
819
+ config_bt_name = getattr(config, "bTName", None)
820
+ config_bt_name = config_bt_name.strip() if config_bt_name else None
821
+ self.logger.debug("bTNameMode = 2, config_bt_name: {}, btname: {}".format(config_bt_name, btname))
822
+ return bool(config_bt_name and (config_bt_name.upper() == "ALL" or (btname and config_bt_name in btname)))
823
+
824
+ url = getattr(config, "uRL", None)
825
+ self.logger.debug("bTNameMode = 3, url: {} in req_url: {}, btname: {}".format(url, req_url, btname))
826
+ return bool(url and (url.upper() == "ALL" or (req_url and url in req_url)))
827
+
828
+ def is_matched_method_name(self, config, req_method_fqm):
829
+ method_fqm = config.methodFQM
830
+ method_fqm_list = [m.strip() for m in method_fqm.split(':')]
831
+ self.logger.debug("method_fqm_list: {} req_method_fqm: {}".format(method_fqm_list, req_method_fqm))
832
+ return [m for m in method_fqm_list if m in req_method_fqm]
833
+
834
+ def apply_method_call_delay(self, req_method_fqm, req_url=None, btname=None):
835
+ self.logger.info("Trying to apply method call delay")
836
+ config_list = self.netHavocConfigMap["MethodCall_Delay"]
837
+
838
+ if config_list:
839
+ for config in config_list:
840
+ self.logger.debug("Trying to apply havoc: {}".format(config.nhmid))
841
+ matched_methods = self.is_matched_method_name(config, req_method_fqm)
842
+ matched_bt = self.is_matched_bt_name(config, req_url, btname)
843
+
844
+ if matched_methods and matched_bt:
845
+ self.logger.debug("Config matched for Request FQM {} BT {}".format(req_method_fqm, btname))
846
+ self.common_delay_in_response(config)
847
+ else:
848
+ self.logger.debug("Config not matched for Request FQM {} BT {}".format(req_method_fqm, btname))
849
+ else:
850
+ self.logger.debug("config not set for apply_method_call_delay")
851
+
852
+ def apply_method_invocation_failure(self, req_method_fqm, req_url=None, btname=None):
853
+ self.logger.info("Trying to apply method invocation failure")
854
+ config_list = self.netHavocConfigMap["MethodCall_Failure"]
855
+
856
+ if config_list:
857
+ for config in config_list:
858
+ self.logger.debug("Trying to apply havoc: {}".format(config.nhmid))
859
+ matched_methods = self.is_matched_method_name(config, req_method_fqm)
860
+ matched_bt = self.is_matched_bt_name(config, req_url, btname)
861
+
862
+ if matched_methods and matched_bt:
863
+ self.logger.debug("Config matched for Request FQM {} BT {}".format(req_method_fqm, btname))
864
+ _agent = agent.get_agent_instance()
865
+ start_time = round(time.time() * 1000)
866
+ throwing_method = req_method_fqm
867
+ dummy_bt = 0
868
+ dummy_line_number = 0
869
+ encodedstktrc = "DummyStackTrace"
870
+ exception_class = "HavocException"
871
+ exception_message = "HavocMessage"
872
+ exception_cause = "HavocMethodFailure"
873
+ throwing_class = "None"
874
+ file_name = "havoc_manager.py"
875
+ _agent.exceptiondump(dummy_bt, None, start_time, exception_class, exception_message, throwing_class,
876
+ throwing_method, exception_cause, dummy_line_number, encodedstktrc, file_name)
877
+
878
+ self.common_failure(config, config.methodFQM)
879
+
880
+ else:
881
+ self.logger.debug("Config not matched for Request FQM {} BT {}".format(req_method_fqm, btname))
882
+ else:
883
+ self.logger.debug("config not set for apply_method_call_failure")
884
+
885
+ def increase_memory_usage(self):
886
+ self.logger.info("MemoryLeakHavoc: Trying to apply memory leak")
887
+ config_list = self.netHavocConfigMap["Custom_Memory_Leak"]
888
+ if config_list:
889
+ for config in config_list:
890
+ self.logger.debug("Trying to apply havoc: {}".format(config.nhmid))
891
+ leaksINMB = config.leaksINMB
892
+ totalDurationInSec = config.totalDurationInSec
893
+ objectSize = config.objectSize
894
+ shootUpDuration = config.shootUpDuration
895
+ apply_memory_leak(leaksINMB, shootUpDuration, totalDurationInSec, objectSize)
896
+ _agent = agent.get_agent_instance()
897
+ if _agent.get_transaction_context():
898
+ _agent.get_transaction_context().havoc_applied = True
899
+
900
+
901
+
902
+ else:
903
+ self.logger.debug("config not set for apply_method_call_failure")
904
+
905
+ def increase_thread_count(self):
906
+ self.logger.info("ThreadLeakHavoc: Trying to apply thread leak")
907
+ config_list = self.netHavocConfigMap["Custom_Thread_Leak"]
908
+ if config_list:
909
+ for config in list(config_list):
910
+ self.logger.debug("Trying to apply havoc: {}".format(config.nhmid))
911
+ try:
912
+ apply_thread_leak(config.numberOfThreads, config.threadSleepTimeInMS, config.shootUpDuration,
913
+ config.stableDuration, config.tumbleDuration)
914
+ except ThreadAllocationError as er:
915
+ self.logger.error("ThreadLeakHavoc: failed to allocate new thread for NHMID %s", config.nhmid)
916
+ self.send_havoc_status_response(
917
+ config,
918
+ status="Failed",
919
+ message=er
920
+ )
921
+ if config in config_list:
922
+ config_list.remove(config)
923
+ continue
924
+
925
+ _agent = agent.get_agent_instance()
926
+ if _agent.get_transaction_context():
927
+ _agent.get_transaction_context().havoc_applied = True
928
+ else:
929
+ self.logger.debug("config not set for increase_thread_count")
930
+
931
+ def refresh_configs(self, agent_obj):
932
+ self.logger.info("Refreshing Configs")
933
+ current_time_ms = get_current_timestamp_in_ms()
934
+
935
+ for havoc_type in HAVOC_TYPES:
936
+ self.logger.info("Checking havoc type: {}".format(havoc_type))
937
+
938
+ config_list = self.netHavocConfigMap[havoc_type]
939
+ if config_list:
940
+ self.logger.info("Config found for havoc type: {}".format(havoc_type))
941
+ for config in list(config_list):
942
+ self.logger.debug("Checking Config {} for type {}".format(id(config), config.havocType))
943
+ total_duration_in_ms = (config.totalDurationInSec - 5) * 1000
944
+ self.logger.debug("total_duration_in_ms: {}".format(total_duration_in_ms))
945
+
946
+ start_time_ms = config.startTime
947
+ duration = current_time_ms - start_time_ms
948
+ self.logger.debug("duration: {}".format(duration))
949
+
950
+ # if duration >= EXPIRE_DURATION_MS:
951
+ if duration >= total_duration_in_ms:
952
+ self.logger.debug("Config {} for type {} expired, removing".format(id(config), config.havocType))
953
+ config_list.remove(config)
954
+
955
+ header_str = "NetDiagnosticMessage 2.0;"
956
+ for key, value in list(config.header_dict.items()): # Iterate over a copy
957
+ if key == "Status":
958
+ config.header_dict[key] = "Success"
959
+ elif key == "Message":
960
+ config.header_dict[key] = "HavocCompleted"
961
+ elif key == "MsgType":
962
+ config.header_dict[key] = "1"
963
+ elif key == "SubOperation":
964
+ config.header_dict.pop(key, None) # Safely remove "SubOperation"
965
+
966
+ # Add "Ack" key after updates
967
+ config.header_dict["Ack"] = "0"
968
+
969
+ # Construct the header string
970
+ for key, value in config.header_dict.items():
971
+ header_str += f"{key}:{value};"
972
+ header_str = header_str + "\n\n"
973
+
974
+
975
+ agent_obj.send_havoc_response(header_str)
976
+
977
+
978
+ else:
979
+ self.logger.debug("Config {} for type {} not expired, continuing".format(id(config), config.havocType))
980
+ else:
981
+ self.logger.debug("config not found for havoc type {}".format(havoc_type))