mirror of
https://github.com/NationalSecurityAgency/PACE-python.git
synced 2026-11-07 22:57:21 -10:00
203 lines
6.6 KiB
Python
203 lines
6.6 KiB
Python
## **************
|
|
## Copyright 2015 MIT Lincoln Laboratory
|
|
## Project: PACE
|
|
## Authors: ATLH, SS
|
|
## Description: Common utility functions for testing the code
|
|
## Modifications:
|
|
## Date Name Modification
|
|
## ---- ---- ------------
|
|
## 20 Jan 2015 ATLH Original file - based on sign_utils
|
|
## 15 Dec 2015 SS Added functionality of data generation
|
|
## **************
|
|
""" Utility functions for testing PACE software with rows written to
|
|
Accumulo.
|
|
Any randomized test should use the same seed so that they are equivalent.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
this_dir = os.path.dirname(os.path.dirname(__file__))
|
|
base_dir = os.path.join(this_dir, '../..')
|
|
sys.path.append(base_dir)
|
|
|
|
import random
|
|
import copy
|
|
import time, datetime
|
|
import string
|
|
|
|
from pyaccumulo import Range
|
|
|
|
class Timer(object):
|
|
"""
|
|
Timer that works with python's 'with' syntax
|
|
measures the time taken in seconds and milliseconds
|
|
"""
|
|
#modified from http://www.huyng.com/posts/python-performance-analysis/
|
|
def __enter__(self):
|
|
self.start = time.clock()
|
|
return self
|
|
|
|
def __exit__(self, *args):
|
|
self.end = time.clock()
|
|
self.secs = self.end - self.start
|
|
self.msecs = self.secs * 1000 # millisecs
|
|
|
|
def generate_data(filename,
|
|
seed,
|
|
vis=False,
|
|
default_vis="default",
|
|
vis_in_value=False,
|
|
num_entries=50000,
|
|
num_rows=1000,
|
|
vis_length=9,
|
|
row_length=9):
|
|
""" Generate a tab-delimited text file with five columns:
|
|
row, col fam, col qual, col vis, value
|
|
|
|
keyword arguments:
|
|
filename - name of file to which the data will be written
|
|
seed - a string to be used as the seed for the random number generator
|
|
visibility - if True, random column visibilities are added. If False,
|
|
the default visibility string is added. (default False)
|
|
default_vis - the default visibility string to use if 'visibiilty' is
|
|
set to False. (default "default")
|
|
vis_in_value - if True, a random "column visibility" is generated, but
|
|
this is appended to the value rather than column visibility. If
|
|
True, the param vis is treated as False.
|
|
num_entries - Number of entries written.
|
|
num_rows - Number of rows in the data written.
|
|
vis_length - The length (in chars) of the random col visibility added
|
|
(true length will be this + 2)
|
|
row_length - Number of chars in each row ID. Numbers are zero padded to
|
|
reach this length.
|
|
"""
|
|
|
|
# seed RNG
|
|
random.seed(seed)
|
|
|
|
rows = map(lambda x: str(x).zfill(row_length), \
|
|
random.sample(xrange(10 ** row_length), num_rows))
|
|
unused_rows = copy.deepcopy(rows)
|
|
|
|
def generate_entry():
|
|
""" Helper function for generate_data that returns a single entry
|
|
which will form a single line of the data file
|
|
"""
|
|
|
|
def generate_field(length=row_length):
|
|
""" Generates a single field (row, col fam, or col qual) randomly
|
|
"""
|
|
return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(length))
|
|
|
|
def generate_row():
|
|
""" Generates a single rowID from the list of rows. On successive
|
|
calls, ensures each rowID is used at least once, but makes no
|
|
other guarantees about how many times each rowID is used
|
|
"""
|
|
return unused_rows.pop() if unused_rows else random.choice(rows)
|
|
|
|
def generate_colvis():
|
|
""" Generates a single column visibility (either the default or a
|
|
random value)
|
|
"""
|
|
return default_vis if (vis_in_value or not vis) \
|
|
else generate_field(vis_length).zfill(vis_length)
|
|
|
|
def generate_value():
|
|
""" Generates a random value, accounting for vis_in_value """
|
|
val = generate_field()
|
|
return val + generate_field(vis_length).zfill(vis_length) if vis_in_value else val
|
|
|
|
return generate_row() + "\t" + \
|
|
generate_field() + "\t" + \
|
|
generate_field() + "\t" + \
|
|
generate_colvis() + "\t" + \
|
|
generate_value() + "\n"
|
|
|
|
# Open file and generate data
|
|
with open(filename, "w") as f:
|
|
for i in range(num_entries):
|
|
f.write(generate_entry())
|
|
|
|
def sanitize(string):
|
|
""" Turns '-' into '_' for accumulo table names
|
|
"""
|
|
return string.replace('-', '_')
|
|
|
|
def get_single_entry(conn, table, row=None, cf=None, cq=None):
|
|
""" Get the single entry with the given key fields (row, column
|
|
family, and column qualifier) from an Accumulo database.
|
|
|
|
Returns:
|
|
|
|
None if no entries were found
|
|
The found entry if exactly one was found
|
|
Raises an assertion error if more than one entry was found
|
|
"""
|
|
|
|
if cf is None:
|
|
assert cq is None
|
|
cols = None
|
|
elif cq is None:
|
|
cols = [[cf]]
|
|
else:
|
|
cols = [[cf, cq]]
|
|
|
|
candidates = conn.scan(table, Range(srow=row, erow=row), cols=cols)
|
|
|
|
try:
|
|
first = next(candidates)
|
|
except StopIteration:
|
|
# No candidates; return None
|
|
return None
|
|
|
|
try:
|
|
more = next(candidates)
|
|
assert False # only supposed to have one result
|
|
except StopIteration:
|
|
return first
|
|
|
|
def entry_exists(conn, table, row, cf=None, cq=None):
|
|
""" Check to see if at least one entry with the given information
|
|
exists in the given Accumulo table. Takes at least a row,
|
|
and possibly a column family & qualifier as well.
|
|
|
|
Arguments:
|
|
|
|
conn : Accumulo - a connection to the Accumulo instance to check
|
|
table : string - the name of the table to check
|
|
row : string - the name of the row to look for within the given table
|
|
cf : optional string - the column family to look for, if desired
|
|
cq : optional string - the column qualifier to look for, if desired
|
|
|
|
Returns:
|
|
|
|
bool - whether there is at least one entry in the given connection with
|
|
the given fields
|
|
|
|
Assumptions:
|
|
|
|
- The given table exists on the connection
|
|
- If cf is not specified/None, then neither is cq
|
|
"""
|
|
|
|
if cf is None:
|
|
assert cq is None
|
|
cols = None
|
|
elif cq is None:
|
|
cols = [[cf]]
|
|
else:
|
|
cols = [[cf, cq]]
|
|
|
|
candidates = conn.scan(table, Range(srow=row, erow=row), cols=cols)
|
|
|
|
try:
|
|
first = next(candidates)
|
|
except StopIteration:
|
|
# Nothing found
|
|
return False
|
|
else:
|
|
# Something found
|
|
return True
|
|
|