Merge pull request #442 from KPrasch/cli-revamp

--dev flag for `run_ursula`
pull/455/head
K Prasch 2018-09-22 15:49:44 -07:00 committed by GitHub
commit af249f91b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 283 additions and 162 deletions

View File

@ -92,7 +92,8 @@ class NucypherClickConfig:
"""Initialize contract agency and set them on config"""
if simulation is True:
self.blockchain.interface._registry._swap_registry(filepath=self.sim_registry_filepath) # TODO: Public API for mirroring existing registry
# TODO: Public API for mirroring existing registry
self.blockchain.interface._registry._swap_registry(filepath=self.sim_registry_filepath)
self.token_agent = NucypherTokenAgent(blockchain=self.blockchain)
self.miner_agent = MinerAgent(token_agent=self.token_agent)
@ -133,15 +134,15 @@ def cli(config, verbose, version, config_file):
def configure(config, action, config_file, config_root, temp):
"""Manage the nucypher .ini configuration file"""
if temp:
node_configuration = NodeConfiguration(temp=temp, auto_initialize=True)
elif config_root:
node_configuration = NodeConfiguration(config_root=config_root)
else:
if config_root:
node_configuration = NodeConfiguration(config_root=config_root, auto_initialize=False)
elif temp:
node_configuration = NodeConfiguration(temp=temp, auto_initialize=False)
elif config_file:
click.echo("Using configuration file at: {}".format(config_file))
node_configuration = NodeConfiguration.from_configuration_file(filepath=config_file)
click.echo("Using configuration directory: {}".format(node_configuration.config_root))
else:
node_configuration = NodeConfiguration() # Fully Default
def __destroy():
click.confirm("Permanently destroy all nucypher configurations, known nodes, certificates and keys?", abort=True)
@ -149,25 +150,24 @@ def configure(config, action, config_file, config_root, temp):
click.echo("Deleted configuration files at {}".format(node_configuration.config_root))
def __initialize():
# TODO: temp config message?
click.confirm("Initialize new nucypher configuration?", abort=True)
node_configuration.initialize_configuration()
click.echo("Created configuration files at {}".format(node_configuration.config_root))
if action == "validate":
is_valid = validate_configuration_file(config_file)
result = 'Valid' if is_valid else 'Invalid'
click.echo('{} is {}'.format(config_file, result))
elif action == "init":
if action == "init":
__initialize()
elif action == "destroy":
__destroy()
elif action == "reset":
__destroy()
__initialize()
elif action == "validate":
is_valid = validate_configuration_file(config_file)
result = 'Valid' if is_valid else 'Invalid'
click.echo('{} is {}'.format(config_file, result))
@cli.command()
@click.argument('action', default='list', required=False)
@ -665,15 +665,15 @@ def status(config, provider, contracts, network):
@cli.command()
@click.option('--config-file', type=click.Path())
@click.option('--federated-only', is_flag=True, default=False)
@click.option('--dev', is_flag=True, default=False)
@click.option('--rest-host', type=str, default='localhost')
@click.option('--rest-port', type=int, default=UrsulaConfiguration.DEFAULT_REST_PORT)
@click.option('--dev', is_flag=True, default=True)
@click.option('--federated-only', is_flag=True)
@click.option('--rest-host', type=str)
@click.option('--rest-port', type=int)
@click.option('--db-name', type=str)
@click.option('--checksum-address', type=str)
@click.option('--teacher-uri', type=str)
@click.option('--metadata-dir', type=click.Path())
@click.option('--config-file', type=click.Path())
def run_ursula(rest_port,
rest_host,
db_name,
@ -697,13 +697,16 @@ def run_ursula(rest_port,
but can be overridden (mostly for testing purposes) with inline cli options.
"""
if not dev:
click.echo("WARNING: Development mode is disabled")
temp = True if dev else False
if config_file:
ursula_config = UrsulaConfiguration.from_configuration_file(filepath=config_file)
else:
ursula_config = UrsulaConfiguration(temp=temp,
auto_initialize=temp,
auto_initialize=dev,
rest_host=rest_host,
rest_port=rest_port,
db_name=db_name,
@ -719,27 +722,33 @@ def run_ursula(rest_port,
ursula = ursula_config.produce()
if teacher_uri:
# TODO: Validate and handle teacher URI paring here
if 'http' not in teacher_uri:
teacher_uri = 'https://'+teacher_uri
url = urlparse(url=teacher_uri)
host, port = url.hostname, url.port
teacher_config = UrsulaConfiguration(temp=True,
auto_initialize=True,
rest_host=host,
rest_port=port,
is_me=False,
federated_only=federated_only,
known_nodes=(ursula, ))
teacher = teacher_config.produce()
# Know each other
# ursula.remember_node(teacher)
teacher_ursula = teacher_config.produce()
ursula.start_learning_loop() # Enter learning loop
ursula.get_deployer().run() # Run TLS Deployer
if not federated_only:
ursula.stake() # Start staking daemon
try:
ursula.start_learning_loop() # Enter learning loop
ursula.get_deployer().run() # Run TLS Deployer (Reactor)
if not federated_only: # TODO: Resume / Init
ursula.stake() # Start staking daemon
finally:
# Cleanup
if ursula_config.temp:
click.echo("Cleaning up temporary runtime files and directories")
ursula_config.cleanup()
click.echo("Exited gracefully") # TODO: Integrate with other graceful shutdown functionality
if __name__ == "__main__":

View File

@ -394,9 +394,9 @@ class Ursula(Character, VerifiableNode, Miner):
# Ursula
rest_host,
rest_port,
certificate=None,
certificate=None, # TODO: from_certificate classmethod instead, use only filepath..?
certificate_filepath: str = None,
db_name=None,
db_name=None, # TODO: deprecate db_name, use only filepath.?
db_filepath: str = None,
is_me=True,
interface_signature=None,
@ -422,7 +422,8 @@ class Ursula(Character, VerifiableNode, Miner):
self._work_orders = []
Character.__init__(self, is_me=is_me,
Character.__init__(self,
is_me=is_me,
checksum_address=checksum_address,
always_be_learning=always_be_learning,
federated_only=federated_only,
@ -492,7 +493,7 @@ class Ursula(Character, VerifiableNode, Miner):
# Unless the caller passed a crypto power, we'll make our own TLSHostingPower for this stranger.
rest_server = ProxyRESTServer(
rest_host=rest_host,
rest_port=rest_port,
rest_port=rest_port
)
if certificate or certificate_filepath:
tls_hosting_power = TLSHostingPower(rest_server=rest_server,

View File

@ -5,10 +5,12 @@ from os.path import abspath
from constant_sorrow import constants
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve
from cryptography.x509 import Certificate
from nucypher.blockchain.eth.agents import EthereumContractAgent
from nucypher.config.constants import DEFAULT_CONFIG_FILE_LOCATION
from nucypher.config.node import NodeConfiguration
from nucypher.crypto.powers import CryptoPower
class UrsulaConfiguration(NodeConfiguration):
@ -16,22 +18,24 @@ class UrsulaConfiguration(NodeConfiguration):
DEFAULT_TLS_CURVE = ec.SECP384R1
DEFAULT_REST_HOST = 'localhost'
DEFAULT_REST_PORT = 9151
DEFAULT_DB_TEMPLATE = "ursula.{port}.db"
__REGISTRY_NAME = 'contract_registry.json'
def __init__(self,
rest_host: str = DEFAULT_REST_HOST,
rest_port: int = DEFAULT_REST_PORT,
rest_host: str = None,
rest_port: int = None,
# TLS
tls_curve: EllipticCurve = DEFAULT_TLS_CURVE,
tls_private_key=None,
certificate: bytes = None,
tls_curve: EllipticCurve = None,
tls_private_key: bytes = None,
certificate: Certificate = None,
certificate_filepath: str = None,
# Ursula
db_name: str = None,
db_filepath: str = None,
interface_signature=None,
crypto_power=None,
crypto_power: CryptoPower = None,
# Blockchain
miner_agent: EthereumContractAgent = None,
@ -42,20 +46,17 @@ class UrsulaConfiguration(NodeConfiguration):
) -> None:
# REST
self.rest_host = rest_host
self.rest_port = rest_port
self.db_name = db_name or "ursula.{port}.db".format(port=self.rest_port)
self.rest_host = rest_host or self.DEFAULT_REST_HOST
self.rest_port = rest_port or self. DEFAULT_REST_PORT
self.db_name = db_name or self.DEFAULT_DB_TEMPLATE.format(port=self.rest_port)
self.db_filepath = db_filepath or constants.UNINITIALIZED_CONFIGURATION
#
# TLS
#
self.tls_curve = tls_curve
self.tls_curve = tls_curve or self.DEFAULT_TLS_CURVE
self.tls_private_key = tls_private_key
self.certificate: bytes = certificate
# if certificate_filepath is None:
# certificate_filepath = certificate_filepath or os.path.join(self.known_certificates_dir, 'ursula.pem')
self.certificate = certificate
self.certificate_filepath = certificate_filepath
# Ursula
@ -79,10 +80,18 @@ class UrsulaConfiguration(NodeConfiguration):
instance = cls(**{**payload, **overrides})
return instance
def generate_runtime_filepaths(self):
super().generate_runtime_filepaths()
self.db_filepath = os.path.join(self.config_root, self.db_name)
self.registry_filepath = os.path.join(self.config_root, 'contract_registry.json')
def _generate_runtime_filepaths(self, commit=True) -> dict:
base_filepaths = super()._generate_runtime_filepaths(commit=commit)
# TODO: Handle pre-existing certificates, injecting the path
# if not self.certificate_filepath:
# certificate_filepath = certificate_filepath or os.path.join(self.known_certificates_dir, 'ursula.pem')
filepaths = dict(db_filepath=os.path.join(self.config_root, self.db_name),
registry_filepath=os.path.join(self.config_root, self.__REGISTRY_NAME))
if commit:
for field, filepath in filepaths.items():
setattr(self, field, filepath)
base_filepaths.update(filepaths)
return filepaths
@property
def payload(self) -> dict:
@ -99,7 +108,7 @@ class UrsulaConfiguration(NodeConfiguration):
tls_curve=self.tls_curve,
tls_private_key=self.tls_private_key,
certificate=self.certificate,
# certificate_filepath=self.certificate_filepath, # TODO
# certificate_filepath=self.certificate_filepath, # TODO: Handle existing certificates, injecting the path
# Ursula
interface_signature=self.interface_signature,
@ -121,7 +130,7 @@ class UrsulaConfiguration(NodeConfiguration):
ursula = Ursula(**merged_parameters)
if self.temp:
class MockDatastoreThreadPool(object):
class MockDatastoreThreadPool(object): # TODO: Does this belong here..?
def callInThread(self, f, *args, **kwargs):
return f(*args, **kwargs)
ursula.datastore_threadpool = MockDatastoreThreadPool()
@ -144,7 +153,7 @@ class UrsulaConfiguration(NodeConfiguration):
class AliceConfiguration(NodeConfiguration):
def __init__(self,
policy_agent=None,
policy_agent: EthereumContractAgent = None,
*args, **kwargs
) -> None:
super().__init__(*args, **kwargs)

View File

@ -8,11 +8,14 @@ from constant_sorrow import constants
from itertools import islice
from nucypher.config.constants import DEFAULT_CONFIG_ROOT, DEFAULT_CONFIG_FILE_LOCATION, TEMPLATE_CONFIG_FILE_LOCATION
from nucypher.network.middleware import RestMiddleware
class NodeConfiguration:
DEFAULT_OPERATING_MODE = 'decentralized'
TEMP_CONFIGURATION_DIR_PREFIX = "nucypher-tmp-cli-"
DEFAULT_NETWORK_MIDDLEWARE_CLASS = RestMiddleware
class ConfigurationError(RuntimeError):
pass
@ -28,12 +31,12 @@ class NodeConfiguration:
checksum_address: str = None,
is_me: bool = True,
federated_only: bool = False,
network_middleware=None,
federated_only: bool = None,
network_middleware: RestMiddleware = None,
# Informant
known_metadata_dir: str = None,
start_learning_on_same_thread: bool = True,
start_learning_on_same_thread: bool = False,
abort_on_learning_error: bool = False,
always_be_learning: bool = True,
known_nodes: Iterable = None,
@ -41,30 +44,50 @@ class NodeConfiguration:
) -> None:
self.__temp_dir = constants.NO_TEMPORARY_CONFIGURATION
#
# Configuration root
#
self.temp = temp
self.__temp_dir = constants.LIVE_CONFIGURATION
if temp and not config_root:
self.__temp_dir = constants.UNINITIALIZED_TEMPORARY_CONFIGURATION
config_root = constants.UNINITIALIZED_TEMPORARY_CONFIGURATION
# Create a temp dir and set it as the config root if no config root was specified
self.__temp_dir = constants.UNINITIALIZED_CONFIGURATION
config_root = constants.UNINITIALIZED_CONFIGURATION
elif not config_root:
config_root = DEFAULT_CONFIG_ROOT
self.temp = temp
self.config_root = config_root
self.config_file_location = config_file_location
#
# Node Filepaths (Configuration root files and subdirectories)
#
self.config_file_location = config_file_location
self.keyring_dir = keyring_dir or constants.UNINITIALIZED_CONFIGURATION
self.known_nodes_dir = constants.UNINITIALIZED_CONFIGURATION
self.known_certificates_dir = known_metadata_dir or constants.UNINITIALIZED_CONFIGURATION
self.known_metadata_dir = known_metadata_dir or constants.UNINITIALIZED_CONFIGURATION
if auto_initialize:
self.initialize_configuration()
self.initialize_configuration() # <<< Write runtime files and dirs
self.checksum_address = checksum_address
self.is_me = is_me
#
# Node
#
if not federated_only: # TODO: get_config function?
federated_only = True if self.DEFAULT_OPERATING_MODE is 'federated' else False
self.federated_only = federated_only
if is_me:
network_middleware = network_middleware or self.DEFAULT_NETWORK_MIDDLEWARE_CLASS()
self.network_middleware = network_middleware
#
# Identity
#
self.is_me = is_me
self.checksum_address = checksum_address
# Learning
self.known_nodes = known_nodes
self.network_middleare = network_middleware
self.start_learning_on_same_thread = start_learning_on_same_thread
self.abort_on_learning_error = abort_on_learning_error
self.always_be_learning = always_be_learning
@ -75,23 +98,21 @@ class NodeConfiguration:
template_file = stack.enter_context(open(TEMPLATE_CONFIG_FILE_LOCATION, 'r'))
new_file = stack.enter_context(open(filepath, 'w+'))
if new_file.read() != '':
raise self.ConfigurationError("{} is not a blank file. Do you have an existing configuration?")
for line in islice(template_file, 12, None):
raise self.ConfigurationError("{} is not a blank file. Do you have an existing configuration file?")
for line in islice(template_file, 12, None): # chop the warning header
new_file.writelines(line.lstrip(';')) # TODO Copy Default Sections, Perhaps interactively
def check_config_tree(self, configuration_dir: str = None) -> bool:
def check_config_tree(self, configuration_dir: str = None) -> bool: # TODO: more filesystem validation
path = configuration_dir if configuration_dir else self.config_root
if not os.path.exists(path):
raise self.ConfigurationError(
'No Nucypher configuration directory found at {}.'.format(configuration_dir))
'No configuration directory found at {}.'.format(configuration_dir))
return True
def generate_runtime_filepaths(self) -> None:
"""Dynamically generate paths based on configuration root directory"""
self.keyring_dir = os.path.join(self.config_root, 'keyring')
self.known_nodes_dir = os.path.join(self.config_root, 'known_nodes')
self.known_certificates_dir = os.path.join(self.config_root, 'certificates')
self.known_metadata_dir = os.path.join(self.config_root, 'metadata')
@property
def runtime_filepaths(self):
return self._generate_runtime_filepaths(commit=False)
@property
def payload(self):
@ -107,7 +128,7 @@ class NodeConfiguration:
start_learning_on_same_thread=self.start_learning_on_same_thread,
abort_on_learning_error=self.abort_on_learning_error,
always_be_learning=self.always_be_learning,
network_middleware=self.network_middleare,
network_middleware=self.network_middleware,
# Knowledge
known_nodes=self.known_nodes,
@ -116,6 +137,24 @@ class NodeConfiguration:
)
return base_payload
def _generate_runtime_filepaths(self, commit=True) -> dict:
"""Dynamically generate paths based on configuration root directory"""
if self.temp and commit and self.config_root is constants.UNINITIALIZED_CONFIGURATION:
raise self.ConfigurationError("Cannot pre-generate filepaths for temporary node configurations.")
filepaths = dict(config_root=self.config_root,
keyring_dir=os.path.join(self.config_root, 'keyring'),
known_nodes_dir=os.path.join(self.config_root, 'known_nodes'),
known_certificates_dir=os.path.join(self.config_root, 'certificates'),
known_metadata_dir=os.path.join(self.config_root, 'metadata'))
if commit:
for field, filepath in filepaths.items():
setattr(self, field, filepath)
return filepaths
def cleanup(self):
if self.temp:
self.__temp_dir.cleanup()
def initialize_configuration(self) -> str:
"""Create the configuration and runtime directory tree starting with thr config root directory."""
@ -123,11 +162,11 @@ class NodeConfiguration:
# Create Config Root
#
if self.temp:
if self.temp and self.config_root is constants.UNINITIALIZED_CONFIGURATION:
self.__temp_dir = TemporaryDirectory(prefix=self.TEMP_CONFIGURATION_DIR_PREFIX)
self.config_root = self.__temp_dir.name
else:
if not self.temp and self.config_root is not constants.UNINITIALIZED_CONFIGURATION:
try:
os.mkdir(self.config_root, mode=0o755)
except FileExistsError:
@ -141,14 +180,18 @@ class NodeConfiguration:
# Create Config Subdirectories
#
self.generate_runtime_filepaths()
self._generate_runtime_filepaths(commit=True)
try:
os.mkdir(self.keyring_dir, mode=0o700) # keyring
os.mkdir(self.known_nodes_dir, mode=0o755) # known_nodes
os.mkdir(self.known_certificates_dir, mode=0o755) # known_certs
os.mkdir(self.known_metadata_dir, mode=0o755) # known_metadata
except FileExistsError:
raise # TODO
# TODO: beef up the error message
# existing_paths = [os.path.join(self.config_root, f) for f in os.listdir(self.config_root)]
# NodeConfiguration.ConfigurationError("There are existing files at {}".format())
message = "There are pre-existing nucypher installation files at {}".format(self.config_root)
raise NodeConfiguration.ConfigurationError(message)
self.check_config_tree(configuration_dir=self.config_root)
return self.config_root

View File

@ -1,21 +1,20 @@
import os
import datetime
import os
from random import SystemRandom
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.x509 import Certificate
from typing import Union
from typing import Union, Tuple
import sha3
from constant_sorrow import constants
from cryptography import x509
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.backends.openssl.ec import _EllipticCurvePrivateKey
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.x509 import Certificate
from cryptography.x509.oid import NameOID
from umbral import pre
from umbral.keys import UmbralPrivateKey, UmbralPublicKey
@ -72,7 +71,9 @@ def keccak_digest(*messages: bytes) -> bytes:
return hash.digest()
def ecdsa_sign(message: bytes, privkey: UmbralPrivateKey) -> bytes:
def ecdsa_sign(message: bytes,
privkey: UmbralPrivateKey
) -> bytes:
"""
Accepts a hashed message and signs it with the private key given.
@ -86,11 +87,10 @@ def ecdsa_sign(message: bytes, privkey: UmbralPrivateKey) -> bytes:
return signature_der_bytes
def ecdsa_verify(
message: bytes,
signature: bytes,
pubkey: UmbralPublicKey
) -> bool:
def ecdsa_verify(message: bytes,
signature: bytes,
pubkey: UmbralPublicKey
) -> bool:
"""
Accepts a message and signature and verifies it with the
provided public key.
@ -132,19 +132,24 @@ def _save_tls_certificate(certificate: Certificate,
return certificate_filepath
def load_tls_certificate(filepath):
with open(filepath, 'r') as certificate_file:
cert = x509.load_pem_x509_certificate(certificate_file.read(),
backend=default_backend())
return cert
def load_tls_certificate(filepath: str) -> Certificate:
"""Deserialize an X509 certificate from a filepath"""
try:
with open(filepath, 'r') as certificate_file:
cert = x509.load_pem_x509_certificate(certificate_file.read(),
backend=default_backend())
return cert
except FileNotFoundError:
raise # TODO: Better error message here
def generate_self_signed_certificate(common_name,
curve,
host,
certificate_dir,
private_key=None,
days_valid=365):
def generate_self_signed_certificate(common_name: str,
curve: EllipticCurve,
host: str,
certificate_dir: str,
private_key: _EllipticCurvePrivateKey = None,
days_valid: int = 365
) -> Tuple[Certificate, _EllipticCurvePrivateKey, str]:
if not private_key:
private_key = ec.generate_private_key(curve, default_backend())
@ -165,19 +170,19 @@ def generate_self_signed_certificate(common_name,
cert = cert.add_extension(x509.SubjectAlternativeName([x509.DNSName(host)]), critical=False)
cert = cert.sign(private_key, hashes.SHA512(), default_backend())
# if certificate_dir:
tls_certificate_filepath = _save_tls_certificate(cert, directory=certificate_dir, common_name=common_name)
# else:
# tls_certificate_filepath = constants.CERTIFICATE_NOT_SAVED
if certificate_dir: # TODO: Make this more configurable?
tls_certificate_filepath = _save_tls_certificate(cert, directory=certificate_dir, common_name=common_name)
else:
tls_certificate_filepath = constants.CERTIFICATE_NOT_SAVED
return cert, private_key, tls_certificate_filepath
def encrypt_and_sign(recipient_pubkey_enc: UmbralPublicKey,
plaintext: bytes,
signer: Union["SignatureStamp", "_Constant"],
sign_plaintext=True,
) -> tuple:
plaintext: bytes,
signer: 'SignatureStamp',
sign_plaintext: bool = True
) -> Tuple[UmbralMessageKit, 'SignatureStamp']:
if signer is not constants.DO_NOT_SIGN:
# The caller didn't expressly tell us not to sign; we'll sign.
@ -197,7 +202,7 @@ def encrypt_and_sign(recipient_pubkey_enc: UmbralPublicKey,
else:
# Don't sign.
signature = sig_header = constants.NOT_SIGNED
alice_pubkey = None
alice_pubkey = None # TODO: ..eh?
ciphertext, capsule = pre.encrypt(recipient_pubkey_enc, sig_header + plaintext)
message_kit = UmbralMessageKit(ciphertext=ciphertext, capsule=capsule)

View File

@ -21,19 +21,16 @@ class ProxyRESTServer:
log = getLogger("characters")
def __init__(self,
rest_host,
rest_port,
routes=None,
rest_host: str,
rest_port: int,
routes: 'ProxyRESTRoutes' = None,
) -> None:
self.rest_interface = InterfaceInfo(host=rest_host, port=rest_port)
if routes:
if routes: # if is me
self.rest_app = routes.rest_app
db_filepath = routes.db_filepath
self.db_filepath = routes.db_filepath
else:
self.rest_app = constants.PUBLIC_ONLY
db_filepath = constants.NO_DATABASE_CONNECTION
self.db_filepath = db_filepath
def rest_url(self):
return "{}:{}".format(self.rest_interface.host, self.rest_interface.port)

View File

@ -1,4 +1,6 @@
import contextlib
import os
import shutil
from click.testing import CliRunner
@ -9,28 +11,50 @@ import pytest
from nucypher.config.node import NodeConfiguration
def test_initialize_temp_configuration_directory(temp_config_root):
def test_initialize_configuration_directory():
runner = CliRunner()
# Use the system temporary storage area
result = runner.invoke(cli, ['configure', 'init', '--temp'], input='Y', catch_exceptions=False)
assert '/tmp' in result.output, "Configuration not in system temporary directory"
assert NodeConfiguration.TEMP_CONFIGURATION_DIR_PREFIX in result.output
assert result.exit_code == 0
result = runner.invoke(cli, ['configure', 'destroy', '--temp'], input='Y', catch_exceptions=False)
assert NodeConfiguration.TEMP_CONFIGURATION_DIR_PREFIX in result.output
assert result.exit_code == 0
# Use an custom configuration directory
custom_filepath = '/tmp/nucypher-tmp-test-custom'
args = ['configure', 'init', '--config-root', temp_config_root]
result = runner.invoke(cli, args, input='Y', catch_exceptions=False)
assert '/tmp' in result.output, "Configuration not in system temporary directory"
assert temp_config_root in result.output
assert result.exit_code == 0
try:
os.mkdir(custom_filepath) # TODO: Create a top-level installation directory, instead of writing only subdirs
args = ['configure', 'destroy', '--config-root', temp_config_root]
result = runner.invoke(cli, args, input='Y', catch_exceptions=False)
assert temp_config_root in result.output
assert result.exit_code == 0
args = ['configure', 'init', '--config-root', custom_filepath]
result = runner.invoke(cli, args, input='Y', catch_exceptions=False)
assert '[y/N]' in result.output
assert '/tmp' in result.output, "Configuration not in system temporary directory"
assert 'Created' in result.output
assert custom_filepath in result.output
assert result.exit_code == 0
assert os.path.isdir(custom_filepath)
assert True
# Ensure that there are not pre-existing configuration files at config_root
with pytest.raises(NodeConfiguration.ConfigurationError):
_result = runner.invoke(cli, args, input='Y', catch_exceptions=False)
args = ['configure', 'destroy', '--config-root', custom_filepath]
result = runner.invoke(cli, args, input='Y', catch_exceptions=False)
assert '[y/N]' in result.output
assert '/tmp' in result.output, "Configuration not in system temporary directory"
assert 'Deleted' in result.output
assert custom_filepath in result.output
assert result.exit_code == 0
assert not os.path.isdir(custom_filepath)
finally:
with contextlib.suppress(FileNotFoundError):
shutil.rmtree(custom_filepath)
# # TODO: Integrate with run ursula
@pytest.mark.skip("To be implemented")

View File

@ -1,13 +1,48 @@
import pytest_twisted
import time
import pytest
from click.testing import CliRunner
from twisted.internet import threads
from twisted.internet.error import CannotListenError
from cli.main import cli
from nucypher.characters.base import Learner
from nucypher.config.characters import UrsulaConfiguration
def test_run_lone_federated_ursula():
runner = CliRunner()
@pytest.mark.skip(reason="Handle second call to reactor.run, or multiproc")
@pytest_twisted.inlineCallbacks
def test_run_lone_federated_default_ursula():
args = ['run_ursula',
'--temp',
'--dev',
'--federated-only',
'--teacher-uri', 'localhost:5556']
'--rest-port', '9999', # TODO: use different port to avoid premature ConnectionError with many test runs?
]
runner = CliRunner()
result = yield threads.deferToThread(runner.invoke(cli, args, catch_exceptions=False))
# result = runner.invoke(cli, args, catch_exceptions=False) # TODO: Handle second call to reactor.run
alone = "WARNING - Can't learn right now: Need some nodes to start learning from."
time.sleep(Learner._SHORT_LEARNING_DELAY)
assert alone in result.output
assert result.exit_code == 0
# Cannot start another Ursula on the same REST port
with pytest.raises(CannotListenError):
_result = runner.invoke(cli, args, catch_exceptions=False)
@pytest.mark.skip(reason="Handle second call to reactor.run, or multiproc")
def test_federated_ursula_with_manual_teacher_uri():
args = ['run_ursula',
'--dev',
'--federated-only',
'--rest-port', '9091', # TODO: Test Constant?
'--teacher-uri', 'localhost:{}'.format(UrsulaConfiguration.DEFAULT_REST_PORT)]
# TODO: Handle second call to reactor.run
runner = CliRunner()
result_with_teacher = runner.invoke(cli, args, catch_exceptions=False)
pass
assert result_with_teacher.exit_code == 0

View File

@ -12,7 +12,7 @@ def pytest_addoption(parser):
parser.addoption("--runslow",
action="store_true",
default=False,
help="run test even if they are marked as slow")
help="run tests even if they are marked as slow")
def pytest_collection_modifyitems(config, items):

View File

@ -39,22 +39,23 @@ def tempfile_path():
os.remove(path)
@pytest.fixture(scope="session")
@pytest.fixture(scope="module")
def temp_dir_path():
temp_dir = tempfile.TemporaryDirectory(prefix='nucypher-test-')
yield temp_dir.name
temp_dir.cleanup()
@pytest.fixture(scope="session")
@pytest.fixture(scope="module")
def temp_config_root(temp_dir_path):
"""
User is responsible for closing the file given at the path.
"""
default_node_config = NodeConfiguration(temp=True,
auto_initialize=True,
auto_initialize=False,
config_root=temp_dir_path)
yield default_node_config.config_root
default_node_config.cleanup()
@pytest.fixture(scope="module")
@ -70,40 +71,36 @@ def test_keystore():
#
@pytest.fixture(scope="module")
def ursula_federated_test_config(temp_config_root):
def ursula_federated_test_config():
ursula_config = UrsulaConfiguration(temp=True,
auto_initialize=True,
is_me=True,
config_root=temp_config_root,
rest_host="localhost",
always_be_learning=False,
abort_on_learning_error=True,
federated_only=True)
yield ursula_config
@pytest.fixture(scope="module")
def ursula_decentralized_test_config(temp_config_root, three_agents):
def ursula_decentralized_test_config(three_agents):
token_agent, miner_agent, policy_agent = three_agents
ursula_config = UrsulaConfiguration(temp=True,
auto_initialize=True,
config_root=temp_config_root,
is_me=True,
rest_host="localhost",
always_be_learning=False,
abort_on_learning_error=True,
miner_agent=miner_agent,
federated_only=False)
yield ursula_config
@pytest.fixture(scope="module")
def alice_federated_test_config(federated_ursulas, temp_config_root):
def alice_federated_test_config(federated_ursulas):
config = AliceConfiguration(temp=True,
is_me=True,
auto_initialize=True,
config_root=temp_config_root,
is_me=True,
network_middleware=MockRestMiddleware(),
known_nodes=federated_ursulas,
federated_only=True,
@ -112,20 +109,18 @@ def alice_federated_test_config(federated_ursulas, temp_config_root):
@pytest.fixture(scope="module")
def alice_blockchain_test_config(blockchain_ursulas, three_agents, temp_config_root):
def alice_blockchain_test_config(blockchain_ursulas, three_agents):
token_agent, miner_agent, policy_agent = three_agents
etherbase, alice_address, bob_address, *everyone_else = token_agent.blockchain.interface.w3.eth.accounts
config = AliceConfiguration(temp=True,
is_me=True,
auto_initialize=True,
config_root=temp_config_root,
network_middleware=MockRestMiddleware(),
policy_agent=policy_agent,
known_nodes=blockchain_ursulas,
abort_on_learning_error=True,
checksum_address=alice_address)
yield config

View File

@ -1,6 +1,6 @@
import os
import shutil
import pytest
import pytest_twisted
import requests
from cryptography.hazmat.primitives import serialization
@ -45,5 +45,8 @@ def test_federated_nodes_connect_via_tls_and_verify(ursula_federated_test_config
yield threads.deferToThread(check_node_with_cert, node, "test-cert")
finally:
os.remove("test-cert")
#
# def test_node_metadata_contains_proper_cert():
@pytest.mark.skip(reason="To be implemented")
def test_node_metadata_contains_proper_cert():
pass