2013-05-27 13:06:28 +00:00
|
|
|
'''
|
2013-07-16 10:22:51 +00:00
|
|
|
Missile object and generators
|
2013-07-16 12:02:56 +00:00
|
|
|
|
2013-07-16 13:21:55 +00:00
|
|
|
You should update Stepper.status.ammo_count and Stepper.status.loop_count in your custom generators!
|
2013-05-27 13:06:28 +00:00
|
|
|
'''
|
2013-08-27 11:31:51 +00:00
|
|
|
import gzip
|
2013-05-27 13:06:28 +00:00
|
|
|
from itertools import cycle
|
2013-07-15 11:51:50 +00:00
|
|
|
from module_exceptions import AmmoFileError
|
2013-07-23 14:22:37 +00:00
|
|
|
import os.path
|
2013-07-16 13:21:55 +00:00
|
|
|
import info
|
2013-08-12 16:25:05 +00:00
|
|
|
import logging
|
2013-05-27 13:06:28 +00:00
|
|
|
|
2013-09-05 12:09:18 +00:00
|
|
|
def get_opener(f_path):
|
|
|
|
""" Returns opener function according to file extensions:
|
|
|
|
bouth open and gzip.open calls return fileobj.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
f_path: str, ammo file path.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
function, to call for file open.
|
|
|
|
"""
|
|
|
|
if f_path.endswith('.gz'):
|
|
|
|
return gzip.open
|
|
|
|
return open
|
2013-05-27 13:06:28 +00:00
|
|
|
|
|
|
|
class HttpAmmo(object):
|
2013-07-16 10:22:51 +00:00
|
|
|
'''
|
|
|
|
Represents HTTP missile
|
2013-09-17 13:16:07 +00:00
|
|
|
|
|
|
|
>>> print HttpAmmo('/', []).to_s() # doctest: +NORMALIZE_WHITESPACE
|
|
|
|
GET / HTTP/1.1
|
|
|
|
|
|
|
|
>>> print HttpAmmo('/', ['Connection: Close', 'Content-Type: Application/JSON']).to_s() # doctest: +NORMALIZE_WHITESPACE
|
|
|
|
GET / HTTP/1.1
|
|
|
|
Connection: Close
|
|
|
|
Content-Type: Application/JSON
|
|
|
|
|
|
|
|
>>> print HttpAmmo('/', ['Connection: Close'], method='POST', body='hello!').to_s() # doctest: +NORMALIZE_WHITESPACE
|
|
|
|
POST / HTTP/1.1
|
|
|
|
Connection: Close
|
|
|
|
Content-Length: 6
|
|
|
|
<BLANKLINE>
|
|
|
|
hello!
|
2013-07-16 10:22:51 +00:00
|
|
|
'''
|
|
|
|
|
2013-09-17 13:16:07 +00:00
|
|
|
def __init__(self, uri, headers, method='GET', http_ver='1.1', body=''):
|
2013-05-27 13:06:28 +00:00
|
|
|
self.method = method
|
|
|
|
self.uri = uri
|
|
|
|
self.proto = 'HTTP/%s' % http_ver
|
2013-09-17 13:16:07 +00:00
|
|
|
self.headers = set(headers)
|
|
|
|
self.body = body
|
|
|
|
if len(body):
|
|
|
|
self.headers.add("Content-Length: %s" % len(body))
|
2013-05-27 13:06:28 +00:00
|
|
|
|
|
|
|
def to_s(self):
|
2013-06-25 13:18:36 +00:00
|
|
|
if self.headers:
|
|
|
|
headers = '\r\n'.join(self.headers) + '\r\n'
|
2013-06-26 12:52:03 +00:00
|
|
|
else:
|
|
|
|
headers = ''
|
2013-09-17 13:16:07 +00:00
|
|
|
return "%s %s %s\r\n%s\r\n%s" % (self.method, self.uri, self.proto, headers, self.body)
|
2013-05-27 13:06:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SimpleGenerator(object):
|
2013-06-26 09:28:34 +00:00
|
|
|
|
2013-07-16 10:22:51 +00:00
|
|
|
'''
|
|
|
|
Generates ammo based on a given sample.
|
|
|
|
'''
|
2013-06-26 09:28:34 +00:00
|
|
|
|
2013-05-27 13:06:28 +00:00
|
|
|
def __init__(self, missile_sample):
|
2013-07-16 10:22:51 +00:00
|
|
|
'''
|
|
|
|
Missile sample is any object that has to_s method which
|
|
|
|
returns its string representation.
|
|
|
|
'''
|
2013-05-27 13:06:28 +00:00
|
|
|
self.missiles = cycle([(missile_sample.to_s(), None)])
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
for m in self.missiles:
|
2013-07-16 13:21:55 +00:00
|
|
|
info.status.inc_loop_count()
|
2013-05-27 13:06:28 +00:00
|
|
|
yield m
|
|
|
|
|
|
|
|
|
2013-08-12 09:57:48 +00:00
|
|
|
class UriStyleGenerator(object):
|
2013-06-26 09:28:34 +00:00
|
|
|
|
2013-07-16 10:22:51 +00:00
|
|
|
'''
|
|
|
|
Generates GET ammo based on given URI list.
|
|
|
|
'''
|
2013-06-26 09:28:34 +00:00
|
|
|
|
2013-07-16 12:18:46 +00:00
|
|
|
def __init__(self, uris, headers, http_ver='1.1'):
|
2013-07-16 10:22:51 +00:00
|
|
|
'''
|
|
|
|
uris - a list of URIs as strings.
|
|
|
|
'''
|
2013-05-27 13:06:28 +00:00
|
|
|
self.uri_count = len(uris)
|
2013-06-26 09:28:34 +00:00
|
|
|
self.missiles = cycle(
|
|
|
|
[(HttpAmmo(uri, headers, http_ver=http_ver).to_s(), None) for uri in uris])
|
2013-05-27 13:06:28 +00:00
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
for m in self.missiles:
|
2013-08-29 13:50:46 +00:00
|
|
|
yield m
|
2013-07-16 13:21:55 +00:00
|
|
|
info.status.loop_count = info.status.ammo_count / self.uri_count
|
2013-05-27 13:06:28 +00:00
|
|
|
|
|
|
|
|
2013-08-12 09:57:48 +00:00
|
|
|
class AmmoFileReader(object):
|
2013-06-26 09:28:34 +00:00
|
|
|
|
2013-05-27 13:06:28 +00:00
|
|
|
'''Read missiles from ammo file'''
|
2013-06-26 09:28:34 +00:00
|
|
|
|
2013-09-05 12:41:16 +00:00
|
|
|
def __init__(self, filename, **kwargs):
|
2013-05-27 13:06:28 +00:00
|
|
|
self.filename = filename
|
2013-08-12 16:25:05 +00:00
|
|
|
self.log = logging.getLogger(__name__)
|
|
|
|
self.log.info("Loading ammo from '%s'" % filename)
|
2013-05-27 13:06:28 +00:00
|
|
|
|
|
|
|
def __iter__(self):
|
2013-08-12 16:25:05 +00:00
|
|
|
def read_chunk_header(ammo_file):
|
|
|
|
chunk_header = ''
|
|
|
|
while chunk_header is '':
|
|
|
|
line = ammo_file.readline()
|
|
|
|
if line is '':
|
|
|
|
return line
|
|
|
|
chunk_header = line.strip('\r\n')
|
|
|
|
return chunk_header
|
2013-09-05 12:09:18 +00:00
|
|
|
with get_opener(self.filename)(self.filename, 'rb') as ammo_file:
|
2013-07-30 14:18:15 +00:00
|
|
|
info.status.af_size = os.path.getsize(self.filename)
|
2013-08-12 16:25:05 +00:00
|
|
|
chunk_header = read_chunk_header(ammo_file) # if we got StopIteration here, the file is empty
|
2013-05-27 13:06:28 +00:00
|
|
|
while chunk_header:
|
2013-07-23 14:22:37 +00:00
|
|
|
if chunk_header is not '':
|
2013-06-26 09:28:34 +00:00
|
|
|
try:
|
|
|
|
fields = chunk_header.split()
|
|
|
|
chunk_size = int(fields[0])
|
2013-09-10 16:31:57 +00:00
|
|
|
if chunk_size == 0:
|
|
|
|
break
|
2013-06-26 09:28:34 +00:00
|
|
|
marker = fields[1] if len(fields) > 1 else None
|
|
|
|
missile = ammo_file.read(chunk_size)
|
|
|
|
if len(missile) < chunk_size:
|
|
|
|
raise AmmoFileError(
|
|
|
|
"Unexpected end of file: read %s bytes instead of %s" % (len(missile), chunk_size))
|
|
|
|
yield (missile, marker)
|
2013-08-12 16:25:05 +00:00
|
|
|
except (IndexError, ValueError) as e:
|
2013-06-26 09:28:34 +00:00
|
|
|
raise AmmoFileError(
|
2013-08-12 16:25:05 +00:00
|
|
|
"Error while reading ammo file. Position: %s, header: '%s', original exception: %s" % (ammo_file.tell(), chunk_header, e))
|
|
|
|
chunk_header = read_chunk_header(ammo_file)
|
|
|
|
if chunk_header == '':
|
|
|
|
self.log.debug('Reached the end of ammo file. Starting over.')
|
2013-07-15 16:00:27 +00:00
|
|
|
ammo_file.seek(0)
|
2013-07-30 11:47:54 +00:00
|
|
|
info.status.inc_loop_count()
|
2013-08-12 16:25:05 +00:00
|
|
|
chunk_header = read_chunk_header(ammo_file)
|
|
|
|
info.status.af_position = ammo_file.tell()
|
2013-08-12 10:04:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SlowLogReader(object):
|
|
|
|
|
|
|
|
'''Read missiles from SQL slow log. Not usable with Phantom'''
|
|
|
|
|
2013-09-05 12:41:16 +00:00
|
|
|
def __init__(self, filename, **kwargs):
|
2013-08-12 10:04:00 +00:00
|
|
|
self.filename = filename
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
with open(self.filename, 'rb') as ammo_file:
|
|
|
|
info.status.af_size = os.path.getsize(self.filename)
|
2013-08-22 12:23:49 +00:00
|
|
|
request = ""
|
2013-08-12 10:04:00 +00:00
|
|
|
while True:
|
|
|
|
for line in ammo_file:
|
|
|
|
info.status.af_position = ammo_file.tell()
|
2013-08-22 13:09:05 +00:00
|
|
|
if line.startswith('#'):
|
2013-08-22 13:14:19 +00:00
|
|
|
if request != "":
|
|
|
|
yield (request, None)
|
|
|
|
request = ""
|
2013-08-12 10:04:00 +00:00
|
|
|
else:
|
2013-08-22 13:09:05 +00:00
|
|
|
request += line
|
2013-08-26 11:57:27 +00:00
|
|
|
ammo_file.seek(0)
|
|
|
|
info.status.af_position = 0
|
|
|
|
info.status.inc_loop_count()
|
|
|
|
|
|
|
|
class LineReader(object):
|
|
|
|
|
|
|
|
'''One line -- one missile'''
|
|
|
|
|
2013-09-05 12:41:16 +00:00
|
|
|
def __init__(self, filename, **kwargs):
|
2013-08-26 11:57:27 +00:00
|
|
|
self.filename = filename
|
|
|
|
|
2013-09-05 12:09:18 +00:00
|
|
|
def __iter__(self):
|
|
|
|
with get_opener(self.filename)(self.filename, 'rb') as ammo_file:
|
|
|
|
while True:
|
|
|
|
for line in ammo_file:
|
|
|
|
info.status.af_position = ammo_file.tell()
|
|
|
|
yield (line.rstrip('\r\n'), None)
|
|
|
|
ammo_file.seek(0)
|
|
|
|
info.status.af_position = 0
|
|
|
|
info.status.inc_loop_count()
|
2013-08-27 11:31:51 +00:00
|
|
|
|
|
|
|
|
2013-09-05 12:09:18 +00:00
|
|
|
class UriReader(object):
|
2013-09-05 12:41:16 +00:00
|
|
|
def __init__(self, filename, headers=[], **kwargs):
|
2013-09-05 12:09:18 +00:00
|
|
|
self.filename = filename
|
|
|
|
self.headers = set(headers)
|
2013-09-17 13:16:07 +00:00
|
|
|
self.log = logging.getLogger(__name__)
|
|
|
|
self.log.info("Loading ammo from '%s' using URI format." % filename)
|
2013-08-27 11:31:51 +00:00
|
|
|
|
2013-08-26 11:57:27 +00:00
|
|
|
def __iter__(self):
|
2013-09-05 12:09:18 +00:00
|
|
|
with get_opener(self.filename)(self.filename, 'rb') as ammo_file:
|
2013-08-26 11:57:27 +00:00
|
|
|
while True:
|
|
|
|
for line in ammo_file:
|
|
|
|
info.status.af_position = ammo_file.tell()
|
2013-09-05 12:09:18 +00:00
|
|
|
if line.startswith('['):
|
2013-09-06 10:25:25 +00:00
|
|
|
self.headers.add(line.strip('\r\n[]\t '))
|
2013-09-05 12:09:18 +00:00
|
|
|
elif len(line.rstrip('\r\n')):
|
|
|
|
yield (HttpAmmo(line.rstrip('\r\n'), headers=self.headers).to_s(), None)
|
2013-08-12 10:04:00 +00:00
|
|
|
ammo_file.seek(0)
|
|
|
|
info.status.af_position = 0
|
2013-08-27 11:31:51 +00:00
|
|
|
info.status.inc_loop_count()
|
2013-09-17 13:16:07 +00:00
|
|
|
|
|
|
|
class UriPostReader(object):
|
|
|
|
|
|
|
|
'''Read POST missiles from ammo file'''
|
|
|
|
|
|
|
|
def __init__(self, filename, headers=[], **kwargs):
|
|
|
|
self.filename = filename
|
|
|
|
self.headers = set(headers)
|
|
|
|
self.log = logging.getLogger(__name__)
|
|
|
|
self.log.info("Loading ammo from '%s' using URI+POST format" % filename)
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
def read_chunk_header(ammo_file):
|
|
|
|
chunk_header = ''
|
|
|
|
while chunk_header is '':
|
|
|
|
line = ammo_file.readline()
|
|
|
|
if line.startswith('['):
|
|
|
|
self.headers.add(line.strip('\r\n[]\t '))
|
|
|
|
elif line is '':
|
|
|
|
return line
|
|
|
|
else:
|
|
|
|
chunk_header = line.strip('\r\n')
|
|
|
|
return chunk_header
|
|
|
|
with get_opener(self.filename)(self.filename, 'rb') as ammo_file:
|
|
|
|
info.status.af_size = os.path.getsize(self.filename)
|
|
|
|
chunk_header = read_chunk_header(ammo_file) # if we got StopIteration here, the file is empty
|
|
|
|
while chunk_header:
|
|
|
|
if chunk_header is not '':
|
|
|
|
try:
|
|
|
|
self.log.info("Headers: %s" % self.headers)
|
|
|
|
self.log.info("chunk_header: %s" % chunk_header)
|
|
|
|
fields = chunk_header.split()
|
|
|
|
chunk_size = int(fields[0])
|
|
|
|
if chunk_size == 0:
|
|
|
|
break
|
|
|
|
uri = fields[1]
|
|
|
|
marker = fields[2] if len(fields) > 2 else None
|
|
|
|
missile = ammo_file.read(chunk_size)
|
|
|
|
if len(missile) < chunk_size:
|
|
|
|
raise AmmoFileError(
|
|
|
|
"Unexpected end of file: read %s bytes instead of %s" % (len(missile), chunk_size))
|
|
|
|
yield (
|
|
|
|
HttpAmmo(
|
|
|
|
uri=uri,
|
|
|
|
headers=self.headers,
|
|
|
|
method='POST',
|
|
|
|
body=missile,
|
|
|
|
).to_s(),
|
|
|
|
marker
|
|
|
|
)
|
|
|
|
except (IndexError, ValueError) as e:
|
|
|
|
raise AmmoFileError(
|
|
|
|
"Error while reading ammo file. Position: %s, header: '%s', original exception: %s" % (ammo_file.tell(), chunk_header, e))
|
|
|
|
chunk_header = read_chunk_header(ammo_file)
|
|
|
|
if chunk_header == '':
|
|
|
|
self.log.debug('Reached the end of ammo file. Starting over.')
|
|
|
|
ammo_file.seek(0)
|
|
|
|
info.status.inc_loop_count()
|
|
|
|
chunk_header = read_chunk_header(ammo_file)
|
|
|
|
info.status.af_position = ammo_file.tell()
|