Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also .

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also .
base repository: gsathya/pyonionoo
base: master
head repository: gsathya/pyonionoo
compare: bug_6708
Checking mergeability… Don’t worry, you can still create the pull request.
  • 1 commit
  • 3 files changed
  • 0 comments
  • 1 contributor
Commits on Sep 22, 2012
The various util methods found in utils.py have been
modified from stem/utils.py
Showing with 139 additions and 47 deletions.
  1. +48 −30 pyonionoo/database.py
  2. +1 −1 pyonionoo/handlers/arguments.py
  3. +90 −16 pyonionoo/utils.py
@@ -5,6 +5,7 @@
import threading
import time

import pyonionoo.utils as utils
from pyonionoo.parser import Router

# Name of the SQLite database. This should be defined in a configuration file
@@ -45,29 +46,29 @@
# will be made into an alias for rowid.
summary_tbl_name = 'summary'
summary_schema = """
id INTEGER PRIMARY KEY,
type CHARACTER,
nickname STRING,
fingerprint STRING,
running BOOLEAN,
time_published STRING,
OR_port STRING,
dir_port STRING,
consensus_weight INTEGER,
country_code STRING,
hostname STRING,
id INTEGER PRIMARY KEY,
type CHARACTER,
nickname STRING,
fingerprint STRING,
running BOOLEAN,
time_published STRING,
OR_port STRING,
dir_port STRING,
consensus_weight INTEGER,
country_code STRING,
hostname STRING,
time_lookup STRING
"""

addresses_tbl_name = 'addresses'
addresses_schema = """
id_of_row INTEGER,
id_of_row INTEGER,
address STRING
"""

flags_tbl_name = 'flags'
flags_schema = """
id_of_row INTEGER,
id_of_row INTEGER,
flag STRING
"""

@@ -122,13 +123,16 @@ def bootstrap_database(metrics_out, summary_file):

def update_databases(summary_file=None):
"""
Updates all three databases information.
Updates all three databases.
This operation operates as a single transaction. Therefore,
the database can be read by other requests while it is being
performed, and those reads will correspond to the "un-updated"
database. Once this function completes, all future reads will be
from the "updated" database.
@type summary_file: string
@param summary_file: full path to the summary file
"""
global DB_CREATION_TIME, TABLE_ROWS

@@ -216,37 +220,51 @@ def update_databases(summary_file=None):
FRESHEN_TIMER.start()

def cancel_freshen():
"""
Stop updating the database.
"""

FRESHEN_TIMER.cancel()

def get_database_conn():
conn = sqlite3.connect(DBNAME)
def get_database_conn(dbname=DBNAME):
"""
Connect to a database.
@param type: string
@param dbname: name of the database
@rtype: sqlite3.Connection
@return: a Connection object
"""

conn = sqlite3.connect(dbname)
return conn

def query_summary_tbl(running_filter=None, type_filter=None, hex_fingerprint_filter=None,
country_filter=None, search_filter=None, order_field=None,
order_asc=True, offset_value=None, limit_value=None,
fields=('fingerprint',)):
conn = get_database_conn()
cursor = conn.cursor()

# Build up a WHERE clause based on the request parameters. We only
# consider the case in which the client specifies 'search' or
# some subset (possibly empty) of {'running', 'type', 'lookup', 'country'}.
clauses = []
if search_filter:
# We have to do some heuristics here, because the search filters
# do not come with anything to identify which field they correspond
# to. E.g., the request search=ffa means any relay with nickname
# starting with 'ffa' or fingerprint starting with 'ffa' or '$ffa'.

# Actually, this is a moderately painful parameter to implement.
# Testing for an IP address probably means using regular expressions.
# SQLite doesn't support them without a user-defined function.
# Matching against a Python RE is easy to do, but then we have
# to have a where clause that matches against the beginning of a
# field value, and SQLite doesn't appear to support such a search
# (unless, of course, you want to write a user-defined match()
# function).
pass
if utils.is_valid_ip_address(search_filter) or \
utils.is_valid_ipv6_address(search_filter):
logging.info("Search based on ip address")
cursor.execute('SELECT %s FROM summary, addresses WHERE summary.id=addresses.id_of_row AND addresses.address="%s"' % (','.join(fields), search_filter))
return cursor.fetchall()
elif utils.is_valid_fingerprint(search_filter):
logging.info("Search based on fingerprint")
if '$' in search_filter:
search_filter = search_filter.strip('$')
clauses.append('fingerprint like "%s%%"' % search_filter)
elif utils.is_valid_nickname(search_filter):
logging.info("Search based on nickname")
clauses.append('nickname like "%s%%"' % search_filter)
else:
if running_filter:
clauses.append("running = %s" % int(running_filter))
@@ -74,7 +74,7 @@ def parse(arguments):
# the search parameter, which is then given to us as a single
# (space-separated) string.
if key == "search":
search_filter = values.split()
search_filter = values[0]

# TODO: Handle list of ordering fields.
if key == "order":
@@ -1,30 +1,104 @@
import re

import cyclone.escape
import cyclone.redis
import cyclone.sqlite
import cyclone.web

from twisted.enterprise import adbapi

def is_valid_ip_address(address):
"""
Checks if a string is a valid IPv4 address.
:param str address: string to be checked
:returns: True if input is a valid IPv4 address, False otherwise
"""

if not isinstance(address, str): return False

# checks if theres four period separated values
if address.count(".") != 3: return False

# checks that each value in the octet are decimal values between 0-255
for entry in address.split("."):
if not entry.isdigit() or int(entry) < 0 or int(entry) > 255:
return False
elif entry[0] == "0" and len(entry) > 1:
return False # leading zeros, for instance in "1.2.3.001"

return True

def is_valid_ipv6_address(address, allow_brackets=False):
"""
Checks if a string is a valid IPv6 address.
:param str address: string to be checked
:param bool allow_brackets: ignore brackets which form '[address]'
:returns: True if input is a valid IPv6 address, False otherwise
"""

if allow_brackets:
if address.startswith("[") and address.endswith("]"):
address = address[1:-1]

# addresses are made up of eight colon separated groups of four hex digits
# with leading zeros being optional
# https://en.wikipedia.org/wiki/IPv6#Address_format

colon_count = address.count(":")

if colon_count > 7:
return False # too many groups
elif colon_count != 7 and not "::" in address:
return False # not enough groups and none are collapsed
elif address.count("::") > 1 or ":::" in address:
return False # multiple groupings of zeros can't be collapsed

for entry in address.split(":"):
if not re.match("^[0-9a-fA-f]{0,4}$", entry):
return False

return True

def is_valid_fingerprint(entry, check_prefix=False):
"""
Checks if a string is a properly formatted relay fingerprint. This checks for
a '$' prefix if check_prefix is true, otherwise this only validates the hex
digits.
:param str entry: string to be checked
:param bool check_prefix: checks for a '$' prefix
:returns: True if the string could be a relay fingerprint, False otherwise.
"""
hex_digit = "[0-9a-fA-F]"
fingerprint_pattern = re.compile("^%s{1,40}$" % hex_digit)

if check_prefix:
if not entry or entry[0] != "$": return False
entry = entry[1:]

return bool(fingerprint_pattern.match(entry))

def is_valid_nickname(entry):
"""
Checks if a string is a valid format for being a nickname.
:param str entry: string to be checked
:returns: True if the string could be a nickname, False otherwise.
"""

nickname_pattern = re.compile("^[a-zA-Z0-9]{1,19}$")
return bool(nickname_pattern.match(entry))

class DatabaseMixin(object):
mysql = None
redis = None
sqlite = None

@classmethod
def setup(self, settings):
conf = settings.get("sqlite_settings")
if conf:
DatabaseMixin.sqlite = cyclone.sqlite.InlineSQLite(conf.database)

conf = settings.get("redis_settings")
if conf:
DatabaseMixin.redis = cyclone.redis.lazyConnectionPool(
conf.host, conf.port, conf.dbid, conf.poolsize)

conf = settings.get("mysql_settings")
if conf:
DatabaseMixin.mysql = adbapi.ConnectionPool("MySQLdb",
host=conf.host, port=conf.port, db=conf.database,
user=conf.username, passwd=conf.password,
cp_min=1, cp_max=conf.poolsize,
cp_reconnect=True, cp_noisy=conf.debug)

No commit comments for this range