commit 7663106d25d272ffc7ff2208d95cddef3b685f62 Author: Arturo Filastò <art...@filasto.net> Date: Thu Aug 4 19:46:26 2016 +0200
Add support for deck lifecycle in the web UI * Fix a series of bugs found while testing * Bump the version number up one --- MANIFEST.in | 1 + data/decks/web-full.yaml | 23 ++++++ data/decks/web-no-invalid.yaml | 18 +++++ data/decks/web.yaml | 23 ------ ooni/__init__.py | 2 +- ooni/agent/agent.py | 9 ++- ooni/agent/scheduler.py | 64 ++++++++++++++--- ooni/deck/deck.py | 9 ++- ooni/deck/store.py | 4 +- ooni/resources.py | 5 ++ ooni/scripts/oonideckgen.py | 20 ++---- ooni/scripts/ooniprobe.py | 3 +- ooni/scripts/oonireport.py | 7 +- ooni/settings.py | 154 +++++++++++++++++++---------------------- ooni/ui/cli.py | 37 ++++++++-- ooni/ui/web/client/index.html | 2 +- ooni/ui/web/server.py | 28 +++++++- ooni/ui/web/web.py | 5 +- ooni/utils/__init__.py | 4 +- setup.py | 29 +++----- 20 files changed, 271 insertions(+), 176 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 485e834..60d2ef9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -9,3 +9,4 @@ include data/ooniprobe.conf.sample include data/configs/lepidopter-ooniprobe.conf include data/configs/lepidopter-oonireport.conf include ooni/settings.ini +include ooni/ui/consent-form.md diff --git a/data/decks/web-full.yaml b/data/decks/web-full.yaml new file mode 100644 index 0000000..7812505 --- /dev/null +++ b/data/decks/web-full.yaml @@ -0,0 +1,23 @@ +--- +name: Full Web test deck +description: This deck runs HTTP Header Field Manipulation, HTTP Invalid + Request and the Web Connectivity test +schedule: "@daily" +tasks: +- name: Runs the HTTP Header Field Manipulation test + ooni: + test_name: http_header_field_manipulation + +- name: Runs the HTTP Invalid Request Line test + ooni: + test_name: http_invalid_request_line + +- name: Runs the Web Connectivity Test + ooni: + test_name: web_connectivity + file: $citizenlab_global_urls + +- name: Runs the Web Connectivity Test + ooni: + test_name: web_connectivity + file: $citizenlab_${probe_cc}_urls diff --git a/data/decks/web-no-invalid.yaml b/data/decks/web-no-invalid.yaml new file mode 100644 index 0000000..ea93488 --- /dev/null +++ b/data/decks/web-no-invalid.yaml @@ -0,0 +1,18 @@ +--- +name: Web test deck without HTTP Invalid Request Line +description: This deck runs HTTP Header Field Manipulation, and the Web Connectivity test +schedule: "@daily" +tasks: +- name: Runs the HTTP Header Field Manipulation test + ooni: + test_name: http_header_field_manipulation + +- name: Runs the Web Connectivity Test + ooni: + test_name: web_connectivity + file: $citizenlab_global_urls + +- name: Runs the Web Connectivity Test + ooni: + test_name: web_connectivity + file: $citizenlab_${probe_cc}_urls diff --git a/data/decks/web.yaml b/data/decks/web.yaml deleted file mode 100644 index c7b9bdc..0000000 --- a/data/decks/web.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Web related ooniprobe tests -description: This deck runs HTTP Header Field Manipulation, HTTP Invalid - Request and the Web Connectivity test -schedule: "@daily" -tasks: -- name: Runs the HTTP Header Field Manipulation test - ooni: - test_name: http_header_field_manipulation - -- name: Runs the HTTP Invalid Request Line test - ooni: - test_name: http_invalid_request_line - -- name: Runs the Web Connectivity Test - ooni: - test_name: web_connectivity - file: $citizenlab_global_urls - -- name: Runs the Web Connectivity Test - ooni: - test_name: web_connectivity - file: $citizenlab_${probe_cc}_urls diff --git a/ooni/__init__.py b/ooni/__init__.py index 653a636..1a31608 100644 --- a/ooni/__init__.py +++ b/ooni/__init__.py @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- __author__ = "Open Observatory of Network Interference" -__version__ = "2.0.0a0" +__version__ = "2.0.0a1" __all__ = [ 'agent', diff --git a/ooni/agent/agent.py b/ooni/agent/agent.py index c2e7e26..0311cef 100644 --- a/ooni/agent/agent.py +++ b/ooni/agent/agent.py @@ -11,12 +11,15 @@ class AgentService(service.MultiService): director = Director() - self.web_ui_service = WebUIService(director, web_ui_port) - self.web_ui_service.setServiceParent(self) - self.scheduler_service = SchedulerService(director) self.scheduler_service.setServiceParent(self) + self.web_ui_service = WebUIService(director, + self.scheduler_service, + web_ui_port) + self.web_ui_service.setServiceParent(self) + + def startService(self): service.MultiService.startService(self) diff --git a/ooni/agent/scheduler.py b/ooni/agent/scheduler.py index 1f51bd4..74a1688 100644 --- a/ooni/agent/scheduler.py +++ b/ooni/agent/scheduler.py @@ -14,6 +14,29 @@ from ooni.contrib import croniter from ooni.geoip import probe_ip from ooni.measurements import list_measurements +class FileSystemlockAndMutex(object): + """ + This is a lock that is both a mutex lock and also on filesystem. + When you acquire it, it will first block on the mutex lock and then + once that is released it will attempt to acquire the lock on the + filesystem. + + It's a way to support concurrent usage of the DeferredFilesystemLock + without races. + """ + def __init__(self, file_path): + self._fs_lock = defer.DeferredFilesystemLock(file_path) + self._mutex = defer.DeferredLock() + + @defer.inlineCallbacks + def acquire(self): + yield self._mutex.acquire() + yield self._fs_lock.deferUntilLocked() + + def release(self): + self._fs_lock.unlock() + self._mutex.release() + class DidNotRun(Exception): pass @@ -33,7 +56,7 @@ class ScheduledTask(object): scheduler_directory = config.scheduler_directory self._last_run = FilePath(scheduler_directory).child(self.identifier) - self._last_run_lock = defer.DeferredFilesystemLock( + self._last_run_lock = FileSystemlockAndMutex( FilePath(scheduler_directory).child(self.identifier + ".lock").path ) @@ -63,9 +86,9 @@ class ScheduledTask(object): @defer.inlineCallbacks def run(self): - yield self._last_run_lock.deferUntilLocked() + yield self._last_run_lock.acquire() if not self.should_run: - self._last_run_lock.unlock() + self._last_run_lock.release() raise DidNotRun try: yield self.task() @@ -73,7 +96,7 @@ class ScheduledTask(object): except: raise finally: - self._last_run_lock.unlock() + self._last_run_lock.release() class UpdateInputsAndResources(ScheduledTask): @@ -140,6 +163,21 @@ class RunDeck(ScheduledTask): yield deck.setup() yield deck.run(self.director) + +class RefreshDeckList(ScheduledTask): + """ + This task is configured to refresh the list of decks that are enabled. + """ + identifier = 'refresh-deck-list' + schedule = '@hourly' + + def __init__(self, scheduler, schedule=None, identifier=None): + self.scheduler = scheduler + super(RefreshDeckList, self).__init__(schedule, identifier) + + def task(self): + self.scheduler.refresh_deck_list() + class SendHeartBeat(ScheduledTask): """ This task is used to send a heartbeat that the probe is still alive and @@ -188,6 +226,18 @@ class SchedulerService(service.MultiService): def schedule(self, task): self._scheduled_tasks.append(task) + def refresh_deck_list(self): + # Deletes all the RunDeck tasks and reschedules only the ones that + # are enabled. + for scheduled_task in self._scheduled_tasks[:]: + if isinstance(scheduled_task, RunDeck): + self._scheduled_tasks.remove(scheduled_task) + + for deck_id, deck in deck_store.list_enabled(): + if deck.schedule is None: + continue + self.schedule(RunDeck(self.director, deck_id, deck.schedule)) + def _task_did_not_run(self, failure, task): failure.trap(DidNotRun) log.debug("Did not run {0}".format(task.identifier)) @@ -214,13 +264,11 @@ class SchedulerService(service.MultiService): def startService(self): service.MultiService.startService(self) + self.refresh_deck_list() self.schedule(UpdateInputsAndResources()) self.schedule(UploadReports()) self.schedule(DeleteOldReports()) - for deck_id, deck in deck_store.list_enabled(): - if deck.schedule is None: - continue - self.schedule(RunDeck(self.director, deck_id, deck.schedule)) + self.schedule(RefreshDeckList(self)) self._looping_call.start(self.interval) diff --git a/ooni/deck/deck.py b/ooni/deck/deck.py index 1b2300a..75d6366 100644 --- a/ooni/deck/deck.py +++ b/ooni/deck/deck.py @@ -225,7 +225,12 @@ class NGDeck(object): measurement_id = task.id measurement_dir = self._measurement_path.child(measurement_id) - measurement_dir.createDirectory() + try: + measurement_dir.createDirectory() + except OSError as ose: + # Ignore 'File Exists' + if ose.errno != 17: + raise report_filename = measurement_dir.child("measurements.njson.progress").path pid_file = measurement_dir.child("running.pid") @@ -338,6 +343,8 @@ class DeckTask(object): if task_data.get('no-collector', False): collector_address = None + elif config.reports.upload is False: + collector_address = None net_test_loader = NetTestLoader( options_to_args(task_data), diff --git a/ooni/deck/store.py b/ooni/deck/store.py index 695f97d..2d24f29 100644 --- a/ooni/deck/store.py +++ b/ooni/deck/store.py @@ -140,7 +140,7 @@ class DeckStore(object): def list_enabled(self): decks = [] for deck_id, deck in self._list(): - if self.is_enabled(deck_id): + if not self.is_enabled(deck_id): continue decks.append((deck_id, deck)) return decks @@ -153,7 +153,7 @@ class DeckStore(object): if not deck_path.exists(): raise DeckNotFound(deck_id) deck_enabled_path = self.enabled_directory.child(deck_id + '.yaml') - deck_enabled_path.linkTo(deck_path) + deck_path.linkTo(deck_enabled_path) def disable(self, deck_id): deck_enabled_path = self.enabled_directory.child(deck_id + '.yaml') diff --git a/ooni/resources.py b/ooni/resources.py index aef0f13..9615c53 100644 --- a/ooni/resources.py +++ b/ooni/resources.py @@ -4,6 +4,11 @@ from twisted.python.filepath import FilePath from twisted.internet import defer from twisted.web.client import downloadPage, getPage, HTTPClientFactory +# WARNING: this script is being run as part of the post install procedure. +# Be sure to not import either in this module or in the imported modules +# dependencies other than twisted. If you end up including something that is +# not twisted, then you will need to add it to the setup_requires in setup.py. + from ooni.utils import log, gunzip, rename from ooni.settings import config diff --git a/ooni/scripts/oonideckgen.py b/ooni/scripts/oonideckgen.py index 9b087f9..6c2882c 100644 --- a/ooni/scripts/oonideckgen.py +++ b/ooni/scripts/oonideckgen.py @@ -2,7 +2,6 @@ from __future__ import print_function import errno import os -import shutil import sys from twisted.internet import defer, task @@ -29,7 +28,7 @@ class Options(usage.Options): "submitting reports"], ["bouncer", None, None, "Specify a custom bouncer to use"], ["output", "o", None, - "Specify the directory where to write output."] + "Specify the path where we should be writing the deck to."] ] def opt_version(self): @@ -119,24 +118,13 @@ def oonideckgen(reactor): print("%s: --country-code must be 2 characters" % sys.argv[0]) sys.exit(2) - if not os.path.isdir(options['output']): - print("%s: %s is not a directory" % (sys.argv[0], - options['output'])) - sys.exit(3) + if os.path.isdir(options['output']): + options['output'] = os.path.join(options['output'], 'web-full.yaml') options['country-code'] = options['country-code'].lower() - output_dir = os.path.abspath(options['output']) - output_dir = os.path.join(output_dir, "deck") - - if os.path.isdir(output_dir): - print("Found previous deck deleting content of it") - shutil.rmtree(output_dir) - - options['output'] = output_dir - try: - os.makedirs(options['output']) + os.makedirs(os.path.dirname(options['output'])) except OSError as exception: if exception.errno != errno.EEXIST: raise diff --git a/ooni/scripts/ooniprobe.py b/ooni/scripts/ooniprobe.py index 430252a..d67dd80 100644 --- a/ooni/scripts/ooniprobe.py +++ b/ooni/scripts/ooniprobe.py @@ -13,7 +13,8 @@ def ooniprobe(reactor): if global_options['queue']: return runWithDaemonDirector(global_options) elif global_options['initialize']: - return initializeOoniprobe(global_options) + initializeOoniprobe(global_options) + return defer.succeed(None) elif global_options['web-ui']: from ooni.scripts.ooniprobe_agent import WEB_UI_URL from ooni.scripts.ooniprobe_agent import status_agent, start_agent diff --git a/ooni/scripts/oonireport.py b/ooni/scripts/oonireport.py index 13d8473..cd9b244 100644 --- a/ooni/scripts/oonireport.py +++ b/ooni/scripts/oonireport.py @@ -85,8 +85,7 @@ def upload(report_file, collector=None, bouncer=None, measurement_id=None): elif isinstance(collector_settings, str): collector_client = CollectorClient(address=collector_settings) else: - log.msg("Could not find %s in reporting.yaml. Looking up " - "collector with canonical bouncer." % report_file) + log.msg("Looking up collector with canonical bouncer." % report_file) collector_client = yield lookup_collector_client(report.header, CANONICAL_BOUNCER_ONION) @@ -267,8 +266,8 @@ class Options(usage.Options): def tor_check(): if not config.tor.socks_port: - print("Currently oonireport requires that you start Tor yourself " - "and set the socks_port inside of ooniprobe.conf") + log.err("Currently oonireport requires that you start Tor yourself " + "and set the socks_port inside of ooniprobe.conf") sys.exit(1) diff --git a/ooni/settings.py b/ooni/settings.py index 8bb3340..e7174e2 100644 --- a/ooni/settings.py +++ b/ooni/settings.py @@ -13,7 +13,6 @@ from ooni.utils.net import ConnectAndCloseProtocol, connectProtocol from ooni.utils import Storage, log, get_ooni_root from ooni import errors - CONFIG_FILE_TEMPLATE = """\ # This is the configuration file for OONIProbe # This file follows the YAML markup format: http://yaml.org/spec/1.2/spec.html @@ -127,36 +126,81 @@ defaults = { "preferred_backend": "onion" }, "tor": { + "socks_port": None, + "control_port": None, + "bridges": None, + "data_dir": None, "timeout": 200, "torrc": {} } } +# This is the root of the ooniprobe source code tree +OONIPROBE_ROOT = get_ooni_root() + +IS_VIRTUALENV = False +if hasattr(sys, 'real_prefix'): + IS_VIRTUALENV = True + +# These are the the embedded settings +_SETTINGS_INI = os.path.join(OONIPROBE_ROOT, 'settings.ini') + +USR_SHARE_PATH = '/var/lib/ooni' +VAR_LIB_PATH = '/usr/share/ooni' +ETC_PATH = '/etc' + +if IS_VIRTUALENV: + _PREFIX = os.path.abspath(sys.prefix) + VAR_LIB_PATH = os.path.join( + _PREFIX, + 'var', 'lib', 'ooni' + ) + USR_SHARE_PATH = os.path.join( + _PREFIX, + 'usr', 'share', 'ooni' + ) + ETC_PATH = os.path.join( + _PREFIX, + 'etc' + ) +elif os.path.isfile(_SETTINGS_INI): + settings = SafeConfigParser() + with open(_SETTINGS_INI) as fp: + settings.readfp(fp) + + _USR_SHARE_PATH = settings.get('directories', 'usr_share') + if _USR_SHARE_PATH is not None: + USR_SHARE_PATH = _USR_SHARE_PATH + + _VAR_LIB_PATH = settings.get('directories', 'var_lib') + if _VAR_LIB_PATH is not None: + VAR_LIB_PATH = _VAR_LIB_PATH + + _ETC_PATH = settings.get('directories', 'etc') + if _ETC_PATH is not None: + ETC_PATH = _ETC_PATH + class OConfig(object): _custom_home = None def __init__(self): self.current_user = getpass.getuser() + self.global_options = {} - self.reports = Storage() + self.scapyFactory = None self.tor_state = None self.logging = True + + # These are the configuration options self.basic = Storage() self.advanced = Storage() + self.reports = Storage() self.tor = Storage() self.privacy = Storage() - self.set_paths() - def embedded_settings(self, category, option): - embedded_settings = os.path.join(get_ooni_root(), 'settings.ini') - if os.path.isfile(embedded_settings): - settings = SafeConfigParser() - with open(embedded_settings) as fp: - settings.readfp(fp) - return settings.get(category, option) - return None + self.set_paths() def is_initialized(self): # When this is false it means that the user has not gone @@ -170,64 +214,23 @@ class OConfig(object): with open(initialized_path, 'w+'): pass @property - def var_lib_path(self): - if hasattr(sys, 'real_prefix'): - # We are in a virtualenv use the /usr/share in the virtualenv - return os.path.join( - os.path.abspath(sys.prefix), - 'var', 'lib', 'ooni' - ) - var_lib_path = self.embedded_settings("directories", "var_lib") - if var_lib_path: - return os.path.abspath(var_lib_path) - return "/var/lib/ooni" - - @property def running_path(self): """ This is the directory used to store state application data. It defaults to /var/lib/ooni, but if that is not writeable we will use the ooni_home. """ - var_lib_path = self.var_lib_path - if os.access(var_lib_path, os.W_OK): - return var_lib_path + if os.access(VAR_LIB_PATH, os.W_OK): + return VAR_LIB_PATH return self.ooni_home @property - def usr_share_path(self): - if hasattr(sys, 'real_prefix'): - # We are in a virtualenv use the /usr/share in the virtualenv - return os.path.join( - os.path.abspath(sys.prefix), - 'usr', 'share', 'ooni' - ) - usr_share_path = self.embedded_settings("directories", "usr_share") - if usr_share_path: - return os.path.abspath(usr_share_path) - return "/usr/share/ooni" - - - @property - def etc_path(self): - if hasattr(sys, 'real_prefix'): - # We are in a virtualenv use the /usr/share in the virtualenv - return os.path.join( - os.path.abspath(sys.prefix), - 'usr', 'share', 'ooni' - ) - etc_path = self.embedded_settings("directories", "etc") - if etc_path: - return os.path.abspath(etc_path) - return "/etc" - - @property def data_directory_candidates(self): dirs = [ self.ooni_home, - self.var_lib_path, - self.usr_share_path, - os.path.join(get_ooni_root(), '..', 'data'), + VAR_LIB_PATH, + USR_SHARE_PATH, + os.path.join(OONIPROBE_ROOT, '..', 'data'), '/usr/share/' ] if os.getenv("OONI_DATA_DIR"): @@ -241,7 +244,7 @@ class OConfig(object): for target_dir in self.data_directory_candidates: if os.path.isdir(target_dir): return target_dir - return self.var_lib_path + return VAR_LIB_PATH @property def ooni_home(self): @@ -260,14 +263,14 @@ class OConfig(object): return file_path def set_paths(self): - self.nettest_directory = os.path.join(get_ooni_root(), 'nettests') - self.web_ui_directory = os.path.join(get_ooni_root(), 'ui', 'web', 'client') + self.nettest_directory = os.path.join(OONIPROBE_ROOT, 'nettests') + self.web_ui_directory = os.path.join(OONIPROBE_ROOT, 'ui', 'web','client') self.inputs_directory = os.path.join(self.running_path, 'inputs') self.scheduler_directory = os.path.join(self.running_path, 'scheduler') self.resources_directory = os.path.join(self.running_path, 'resources') - self.decks_available_directory = os.path.join(self.running_path, + self.decks_available_directory = os.path.join(USR_SHARE_PATH, 'decks-available') self.decks_enabled_directory = os.path.join(self.running_path, 'decks-enabled') @@ -317,6 +320,7 @@ class OConfig(object): def create_config_file(self, include_ip=False, include_asn=True, include_country=True, should_upload=True, preferred_backend="onion"): + self.initialize_ooni_home() def _bool_to_yaml(value): if value is True: return 'true' @@ -342,39 +346,23 @@ class OConfig(object): ) self.read_config_file() - def _create_config_file(self): - target_config_file = self.config_file - print "Creating it for you in '%s'." % target_config_file - sample_config_file = self.get_data_file_path('ooniprobe.conf.sample') - - with open(sample_config_file) as f: - with open(target_config_file, 'w+') as w: - for line in f: - if line.startswith(' logfile: '): - w.write(' logfile: %s\n' % ( - os.path.join(self.ooni_home, 'ooniprobe.log')) - ) - else: - w.write(line) - def read_config_file(self, check_incoherences=False): - #if not os.path.isfile(self.config_file): - # print "Configuration file does not exist." - # self._create_config_file() - # self.read_config_file() - configuration = {} + config_file = {} + log.debug("Reading config file from %s" % self.config_file) if os.path.isfile(self.config_file): with open(self.config_file) as f: config_file_contents = '\n'.join(f.readlines()) - configuration = yaml.safe_load(config_file_contents) + config_file = yaml.safe_load(config_file_contents) for category in defaults.keys(): + configuration[category] = {} for k, v in defaults[category].items(): try: - value = configuration.get(category, {})[k] + value = config_file.get(category, {})[k] except KeyError: value = v + configuration[category][k] = value getattr(self, category)[k] = value self.set_paths() diff --git a/ooni/ui/cli.py b/ooni/ui/cli.py index 8cd3358..65e4a0f 100644 --- a/ooni/ui/cli.py +++ b/ooni/ui/cli.py @@ -10,7 +10,7 @@ from twisted.python import usage from twisted.internet import defer from ooni import errors, __version__ -from ooni.settings import config +from ooni.settings import config, OONIPROBE_ROOT from ooni.utils import log class LifetimeExceeded(Exception): pass @@ -182,7 +182,30 @@ def director_startup_other_failures(failure): def initializeOoniprobe(global_options): - # XXX print here the informed consent documentation. + print(""" + _ _ _ + __ _ _ _ ___ ___| |_(_)_ _ __ _ __| | + / _` | '_/ -_) -_) _| | ' \/ _` (_-<_| + \__, |_| \___\___|\__|_|_||_\__, /__(_) + |___/ |___/ ) + """) + print("It looks like this is the first time you are running ooniprobe") + print("Please take a minute to read through the informed consent documentation and " + "understand what are the risks associated with running ooniprobe.") + print("Press enter to continue...") + raw_input() + with open(os.path.join(OONIPROBE_ROOT, 'ui', 'consent-form.md')) as f: + consent_form_text = ''.join(f.readlines()) + from pydoc import pager + pager(consent_form_text) + + answer = "" + while answer.lower() != "yes": + print('Type "yes" if you are fully aware of the risks associated with using ooniprobe and you wish to proceed') + answer = raw_input("> ") + + print("") + print("Now help us configure some things!") answer = raw_input('Should we upload measurements to a collector? (Y/n) ') should_upload = True if answer.lower().startswith("n"): @@ -195,15 +218,15 @@ def initializeOoniprobe(global_options): answer = raw_input('Should we include your ASN (your network) in ' 'measurements? (Y/n) ') - include_asn = False + include_asn = True if answer.lower().startswith("n"): - include_asn = True + include_asn = False answer = raw_input('Should we include your Country in ' 'measurements? (Y/n) ') - include_country = False + include_country = True if answer.lower().startswith("n"): - include_country = True + include_country = False answer = raw_input('How would you like reports to be uploaded? (onion, ' 'https, cloudfronted) ') @@ -230,7 +253,7 @@ def setupGlobalOptions(logging, start_tor, check_incoherences): log.err("You first need to agree to the informed consent and setup " "ooniprobe to run it.") global_options['initialize'] = True - return + return global_options config.set_paths() config.initialize_ooni_home() diff --git a/ooni/ui/web/client/index.html b/ooni/ui/web/client/index.html index 6a7c149..ad2dc50 100644 --- a/ooni/ui/web/client/index.html +++ b/ooni/ui/web/client/index.html @@ -13,5 +13,5 @@ <app> Loading... </app> - <script type="text/javascript" src="app.bundle.js?9c4ed560c98eaf61a836"></script></body> + <script type="text/javascript" src="app.bundle.js?f06164bd3b339e781c75"></script></body> </html> diff --git a/ooni/ui/web/server.py b/ooni/ui/web/server.py index ed2193e..bdafe75 100644 --- a/ooni/ui/web/server.py +++ b/ooni/ui/web/server.py @@ -149,8 +149,10 @@ class WebUIAPI(object): _reactor = reactor _enable_xsrf_protection = True - def __init__(self, config, director, _reactor=reactor): + def __init__(self, config, director, scheduler, _reactor=reactor): self.director = director + self.scheduler = scheduler + self.config = config self.measurement_path = FilePath(config.measurements_directory) @@ -301,17 +303,34 @@ class WebUIAPI(object): for deck_id, deck in self.director.deck_store.list(): deck_list['available'][deck_id] = { 'name': deck.name, - 'description': deck.description + 'description': deck.description, + 'schedule': deck.schedule, + 'enabled': self.director.deck_store.is_enabled(deck_id) } for deck_id, deck in self.director.deck_store.list_enabled(): deck_list['enabled'][deck_id] = { 'name': deck.name, - 'description': deck.description + 'description': deck.description, + 'schedule': deck.schedule, + 'enabled': True } return self.render_json(deck_list, request) + @app.route('/api/deck/<string:deck_id>/run', methods=["POST"]) + @xsrf_protect(check=True) + @requires_true(attrs=['_director_started', '_is_initialized']) + def api_deck_run(self, request, deck_id): + try: + deck = self.director.deck_store.get(deck_id) + except DeckNotFound: + raise WebUIError(404, "Deck not found") + + self.run_deck(deck) + + return self.render_json({"status": "starting"}, request) + @app.route('/api/deck/<string:deck_id>/enable', methods=["POST"]) @xsrf_protect(check=True) @requires_true(attrs=['_director_started', '_is_initialized']) @@ -321,6 +340,8 @@ class WebUIAPI(object): except DeckNotFound: raise WebUIError(404, "Deck not found") + self.scheduler.refresh_deck_list() + return self.render_json({"status": "enabled"}, request) @app.route('/api/deck/<string:deck_id>/disable', methods=["POST"]) @@ -331,6 +352,7 @@ class WebUIAPI(object): self.director.deck_store.disable(deck_id) except DeckNotFound: raise WebUIError(404, "Deck not found") + self.scheduler.refresh_deck_list() return self.render_json({"status": "disabled"}, request) diff --git a/ooni/ui/web/web.py b/ooni/ui/web/web.py index eca75cb..10bbef1 100644 --- a/ooni/ui/web/web.py +++ b/ooni/ui/web/web.py @@ -6,16 +6,17 @@ from ooni.ui.web.server import WebUIAPI from ooni.settings import config class WebUIService(service.MultiService): - def __init__(self, director, port_number=8842): + def __init__(self, director, scheduler, port_number=8842): service.MultiService.__init__(self) self.director = director + self.scheduler = scheduler self.port_number = port_number def startService(self): service.MultiService.startService(self) - web_ui_api = WebUIAPI(config, self.director) + web_ui_api = WebUIAPI(config, self.director, self.scheduler) self._port = reactor.listenTCP( self.port_number, server.Site(web_ui_api.app.resource()) diff --git a/ooni/utils/__init__.py b/ooni/utils/__init__.py index d672ca8..247758f 100644 --- a/ooni/utils/__init__.py +++ b/ooni/utils/__init__.py @@ -10,9 +10,6 @@ from zipfile import ZipFile from twisted.python.filepath import FilePath from twisted.python.runtime import platform -from ooni import errors - - class Storage(dict): """ A Storage object is like a dictionary except `obj.foo` can be used @@ -57,6 +54,7 @@ class Storage(dict): self[k] = v def checkForRoot(): + from ooni import errors if os.getuid() != 0: raise errors.InsufficientPrivileges diff --git a/setup.py b/setup.py index da48323..abbbf21 100644 --- a/setup.py +++ b/setup.py @@ -92,6 +92,8 @@ import os import shutil import tempfile import subprocess +from glob import glob + from ConfigParser import SafeConfigParser from os.path import join as pj @@ -138,30 +140,20 @@ class OoniInstall(install): else: var_path = pj(prefix, 'var', 'lib') - for root, dirs, file_names in os.walk('data/'): - files = [] - for file_name in file_names: - if file_name.endswith('.pyc'): - continue - elif file_name.endswith('.dat') and \ - file_name.startswith('Geo'): - continue - elif file_name == "ooniprobe.conf.sample": - files.append(self.gen_config(share_path)) - continue - files.append(pj(root, file_name)) - self.distribution.data_files.append( - [ - pj(share_path, 'ooni', root.replace('data/', '')), - files - ] + self.distribution.data_files.append( + ( + pj(share_path, 'ooni', 'decks-available'), + glob('data/decks/*') ) + ) settings = SafeConfigParser() settings.add_section("directories") settings.set("directories", "usr_share", os.path.join(share_path, "ooni")) settings.set("directories", "var_lib", os.path.join(var_path, "ooni")) + settings.set("directories", "etc", + os.path.join(var_path, "ooni")) with open("ooni/settings.ini", "w+") as fp: settings.write(fp) @@ -196,7 +188,7 @@ class OoniInstall(install): if is_lepidopter(): self.update_lepidopter_config() - +setup_requires = ['twisted'] install_requires = [] dependency_links = [] data_files = [] @@ -248,6 +240,7 @@ setup( include_package_data=True, dependency_links=dependency_links, install_requires=install_requires, + setup_requires=setup_requires, zip_safe=False, entry_points={ 'console_scripts': [
_______________________________________________ tor-commits mailing list tor-commits@lists.torproject.org https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits