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.
- cavisson_pythonagent-0.0.1.dist-info/METADATA +32 -0
- cavisson_pythonagent-0.0.1.dist-info/RECORD +180 -0
- cavisson_pythonagent-0.0.1.dist-info/WHEEL +5 -0
- cavisson_pythonagent-0.0.1.dist-info/licenses/LICENSE +19 -0
- cavisson_pythonagent-0.0.1.dist-info/top_level.txt +1 -0
- pythonagent/__init__.py +22 -0
- pythonagent/agent/__init__.py +219 -0
- pythonagent/agent/internal/__init__.py +1 -0
- pythonagent/agent/internal/agent.py +1651 -0
- pythonagent/agent/internal/framesinfo.py +152 -0
- pythonagent/agent/internal/heap_dump.py +39 -0
- pythonagent/agent/internal/intercept_module.py +70 -0
- pythonagent/agent/internal/logs.py +122 -0
- pythonagent/agent/internal/metadata/__init__.py +0 -0
- pythonagent/agent/internal/metadata/agent_meta_data.py +276 -0
- pythonagent/agent/internal/proc_compat.py +75 -0
- pythonagent/agent/internal/profile.py +47 -0
- pythonagent/agent/internal/provider.py +83 -0
- pythonagent/agent/internal/thread_dump.py +85 -0
- pythonagent/agent/internal/udp.py +245 -0
- pythonagent/agent/internal/udp_message.py +800 -0
- pythonagent/agent/probes/Instrumentation/__init__.py +340 -0
- pythonagent/agent/probes/Instrumentation/find.py +243 -0
- pythonagent/agent/probes/Instrumentation/module_version_resolver.py +155 -0
- pythonagent/agent/probes/Instrumentation/new_parser.py +64 -0
- pythonagent/agent/probes/Instrumentation/parser.py +129 -0
- pythonagent/agent/probes/__init__.py +219 -0
- pythonagent/agent/probes/base.py +303 -0
- pythonagent/agent/probes/cache/__init__.py +51 -0
- pythonagent/agent/probes/cache/redis.py +119 -0
- pythonagent/agent/probes/cache/redis_asyncio.py +83 -0
- pythonagent/agent/probes/coroutines/__init__.py +1 -0
- pythonagent/agent/probes/coroutines/asyncio.py +63 -0
- pythonagent/agent/probes/elasticdb/__init__.py +7 -0
- pythonagent/agent/probes/elasticdb/aelastic.py +54 -0
- pythonagent/agent/probes/frameworks/__init__.py +27 -0
- pythonagent/agent/probes/frameworks/agentprofiler.py +105 -0
- pythonagent/agent/probes/frameworks/aiohttp_web.py +155 -0
- pythonagent/agent/probes/frameworks/aisess.py +151 -0
- pythonagent/agent/probes/frameworks/asgi.py +340 -0
- pythonagent/agent/probes/frameworks/bottle.py +27 -0
- pythonagent/agent/probes/frameworks/cherry.py +25 -0
- pythonagent/agent/probes/frameworks/django.py +128 -0
- pythonagent/agent/probes/frameworks/falcon.py +21 -0
- pythonagent/agent/probes/frameworks/fastapi.py +35 -0
- pythonagent/agent/probes/frameworks/flask.py +30 -0
- pythonagent/agent/probes/frameworks/pyramid.py +56 -0
- pythonagent/agent/probes/frameworks/test.py +108 -0
- pythonagent/agent/probes/frameworks/tornado_async_web.py +117 -0
- pythonagent/agent/probes/frameworks/tornado_web.py +133 -0
- pythonagent/agent/probes/frameworks/wsgi.py +353 -0
- pythonagent/agent/probes/grpc/__init__.py +76 -0
- pythonagent/agent/probes/grpc/client_interceptor.py +132 -0
- pythonagent/agent/probes/grpc/server_interceptor.py +129 -0
- pythonagent/agent/probes/havoc/__init__.py +0 -0
- pythonagent/agent/probes/havoc/custom_memory_stress.py +186 -0
- pythonagent/agent/probes/havoc/custom_thread_stress.py +187 -0
- pythonagent/agent/probes/havoc/havoc_constants.py +218 -0
- pythonagent/agent/probes/havoc/havoc_manager.py +981 -0
- pythonagent/agent/probes/http/__init__.py +49 -0
- pythonagent/agent/probes/http/aiohttp_client.py +59 -0
- pythonagent/agent/probes/http/boto.py +12 -0
- pythonagent/agent/probes/http/httplib.py +110 -0
- pythonagent/agent/probes/http/httpx_client.py +116 -0
- pythonagent/agent/probes/http/requests.py +15 -0
- pythonagent/agent/probes/http/tornado_httpclient.py +85 -0
- pythonagent/agent/probes/http/urllib3.py +16 -0
- pythonagent/agent/probes/langchain/__init__.py +21 -0
- pythonagent/agent/probes/langchain/base_tool.py +95 -0
- pythonagent/agent/probes/langchain/langchain_community.py +136 -0
- pythonagent/agent/probes/langchain/langchain_core.py +32 -0
- pythonagent/agent/probes/langchain/langchain_openai.py +110 -0
- pythonagent/agent/probes/logging/__init__.py +106 -0
- pythonagent/agent/probes/message_brokers/__init__.py +4 -0
- pythonagent/agent/probes/message_brokers/pika.py +126 -0
- pythonagent/agent/probes/mongodb/__init__.py +6 -0
- pythonagent/agent/probes/mongodb/pymongo.py +286 -0
- pythonagent/agent/probes/openai/__init__.py +3 -0
- pythonagent/agent/probes/openai/openai.py +797 -0
- pythonagent/agent/probes/span.py +101 -0
- pythonagent/agent/probes/sql/__init__.py +13 -0
- pythonagent/agent/probes/sql/botocores3.py +51 -0
- pythonagent/agent/probes/sql/dbapi.py +285 -0
- pythonagent/agent/probes/sql/dynamodb.py +90 -0
- pythonagent/agent/probes/sql/mysql_connector.py +24 -0
- pythonagent/agent/probes/sql/mysql_connector_cext.py +24 -0
- pythonagent/agent/probes/sql/mysqldb.py +43 -0
- pythonagent/agent/probes/sql/psycopg2.py +174 -0
- pythonagent/agent/probes/sql/pymysql.py +25 -0
- pythonagent/bootstrap/__init__.py +0 -0
- pythonagent/bootstrap/cav_gunicorn.py +26 -0
- pythonagent/bootstrap/cavagent_lambda_wrapper.py +291 -0
- pythonagent/bootstrap/run.py +47 -0
- pythonagent/bootstrap/sitecustomize.py +287 -0
- pythonagent/cavisson/netdiagnostics/CavAgent/instrumentationprofile.json +26 -0
- pythonagent/cavisson/netdiagnostics/CavAgent/interceptor_points.txt +29 -0
- pythonagent/cavisson/netdiagnostics/python/CavAgent/instrumentationprofile.json +42 -0
- pythonagent/cavisson/netdiagnostics/python/CavAgent/interceptor_points.txt +29 -0
- pythonagent/cavisson/netdiagnostics/python/config/ndsettings.conf +6 -0
- pythonagent/config.py +279 -0
- pythonagent/find.py +72 -0
- pythonagent/find_mod_cls_name.py +54 -0
- pythonagent/lang.py +131 -0
- pythonagent/lib.py +91 -0
- pythonagent/main/__init__.py +0 -0
- pythonagent/main/pytrace/__init__.py +79 -0
- pythonagent/main/pytrace/commands/__init__.py +0 -0
- pythonagent/main/pytrace/commands/auto_discovery.py +25 -0
- pythonagent/main/pytrace/commands/run.py +401 -0
- pythonagent/main/pytrace/pytrace.py +133 -0
- pythonagent/main/wsgi.py +6 -0
- pythonagent/main.py +27 -0
- pythonagent/run.py +46 -0
- pythonagent/sqins.py +8 -0
- pythonagent/test.py +73 -0
- pythonagent/utils.py +168 -0
- pythonagent/vendor/__init__.py +0 -0
- pythonagent/vendor/pympler/__init__.py +1 -0
- pythonagent/vendor/pympler/asizeof.py +2810 -0
- pythonagent/vendor/pympler/charts.py +62 -0
- pythonagent/vendor/pympler/classtracker.py +590 -0
- pythonagent/vendor/pympler/classtracker_stats.py +780 -0
- pythonagent/vendor/pympler/garbagegraph.py +80 -0
- pythonagent/vendor/pympler/mprofile.py +97 -0
- pythonagent/vendor/pympler/muppy.py +275 -0
- pythonagent/vendor/pympler/panels.py +115 -0
- pythonagent/vendor/pympler/process.py +238 -0
- pythonagent/vendor/pympler/py.typed +0 -0
- pythonagent/vendor/pympler/refbrowser.py +451 -0
- pythonagent/vendor/pympler/refgraph.py +350 -0
- pythonagent/vendor/pympler/summary.py +321 -0
- pythonagent/vendor/pympler/tracker.py +267 -0
- pythonagent/vendor/pympler/util/__init__.py +0 -0
- pythonagent/vendor/pympler/util/bottle.py +3809 -0
- pythonagent/vendor/pympler/util/compat.py +23 -0
- pythonagent/vendor/pympler/util/stringutils.py +77 -0
- pythonagent/vendor/pympler/web.py +346 -0
- pythonagent/vendor/werkzeug/__init__.py +20 -0
- pythonagent/vendor/werkzeug/_compat.py +228 -0
- pythonagent/vendor/werkzeug/_internal.py +473 -0
- pythonagent/vendor/werkzeug/_reloader.py +341 -0
- pythonagent/vendor/werkzeug/datastructures.py +3120 -0
- pythonagent/vendor/werkzeug/debug/__init__.py +498 -0
- pythonagent/vendor/werkzeug/debug/console.py +218 -0
- pythonagent/vendor/werkzeug/debug/repr.py +297 -0
- pythonagent/vendor/werkzeug/debug/tbtools.py +628 -0
- pythonagent/vendor/werkzeug/exceptions.py +829 -0
- pythonagent/vendor/werkzeug/filesystem.py +64 -0
- pythonagent/vendor/werkzeug/formparser.py +584 -0
- pythonagent/vendor/werkzeug/http.py +1307 -0
- pythonagent/vendor/werkzeug/local.py +420 -0
- pythonagent/vendor/werkzeug/middleware/__init__.py +25 -0
- pythonagent/vendor/werkzeug/middleware/dispatcher.py +66 -0
- pythonagent/vendor/werkzeug/middleware/http_proxy.py +219 -0
- pythonagent/vendor/werkzeug/middleware/lint.py +408 -0
- pythonagent/vendor/werkzeug/middleware/profiler.py +132 -0
- pythonagent/vendor/werkzeug/middleware/proxy_fix.py +169 -0
- pythonagent/vendor/werkzeug/middleware/shared_data.py +293 -0
- pythonagent/vendor/werkzeug/posixemulation.py +117 -0
- pythonagent/vendor/werkzeug/routing.py +2210 -0
- pythonagent/vendor/werkzeug/security.py +249 -0
- pythonagent/vendor/werkzeug/serving.py +1117 -0
- pythonagent/vendor/werkzeug/test.py +1123 -0
- pythonagent/vendor/werkzeug/testapp.py +241 -0
- pythonagent/vendor/werkzeug/urls.py +1138 -0
- pythonagent/vendor/werkzeug/useragents.py +202 -0
- pythonagent/vendor/werkzeug/utils.py +778 -0
- pythonagent/vendor/werkzeug/wrappers/__init__.py +36 -0
- pythonagent/vendor/werkzeug/wrappers/accept.py +50 -0
- pythonagent/vendor/werkzeug/wrappers/auth.py +33 -0
- pythonagent/vendor/werkzeug/wrappers/base_request.py +673 -0
- pythonagent/vendor/werkzeug/wrappers/base_response.py +700 -0
- pythonagent/vendor/werkzeug/wrappers/common_descriptors.py +341 -0
- pythonagent/vendor/werkzeug/wrappers/cors.py +100 -0
- pythonagent/vendor/werkzeug/wrappers/etag.py +304 -0
- pythonagent/vendor/werkzeug/wrappers/json.py +145 -0
- pythonagent/vendor/werkzeug/wrappers/request.py +49 -0
- pythonagent/vendor/werkzeug/wrappers/response.py +84 -0
- pythonagent/vendor/werkzeug/wrappers/user_agent.py +14 -0
- 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))
|