Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some Updates to worker management #2494

Merged
merged 8 commits into from
Jan 12, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 5 additions & 5 deletions deploy/ansible/worker/include/run_ursula.yml
Expand Up @@ -76,7 +76,7 @@
max-file: "5"
image: "{{ nucypher_image | default('nucypher/nucypher:latest') }}"
restart_policy: "unless-stopped"
command: "nucypher ursula run {{nucypher_ursula_run_options | default('')}} --lonely {{prometheus | default('')}} {{gas_strategy | default('')}} --network {{network_name}}"
command: "nucypher ursula run {{nucypher_ursula_run_options}} --lonely"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 much needed cleanup

volumes:
- /home/nucypher:/root/.local/share/
ports:
Expand All @@ -85,14 +85,14 @@
env: "{{runtime_envvars}}"

- name: "wait a few seconds for the seed node to become available"
when: SEED_NODE_URI is not undefined
when: SEED_NODE_URI is not undefined and SEED_NODE_URI
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the extra check for ensuring the SEED_NODE_URI value is non-empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah... it errors if SEED_NODE_URI is undefined but it can still be None.
So the first check is to avoid an error if it's undefined, the 2nd one is the actual logic of detecting if it has a non None value.

pause:
seconds: 15

- name: "Run Staked Ursula (non-seed)"
become: yes
become_user: nucypher
when: SEED_NODE_URI is undefined or inventory_hostname != SEED_NODE_URI
when: inventory_hostname != SEED_NODE_URI
docker_container:
recreate: yes
name: ursula
Expand All @@ -104,7 +104,7 @@
max-file: "5"
image: "{{ nucypher_image | default('nucypher/nucypher:latest') }}"
restart_policy: "unless-stopped"
command: "nucypher ursula run {{nucypher_ursula_run_options | default('')}} --disable-availability-check {{teacher_options}} {{prometheus | default('')}} {{gas_strategy | default('')}} --network {{network_name}}"
command: "nucypher ursula run {{nucypher_ursula_run_options}} {{teacher_options}}"
volumes:
- /home/nucypher:/root/.local/share/
ports:
Expand Down Expand Up @@ -133,5 +133,5 @@
become: yes
wait_for:
path: "/var/lib/docker/containers/{{ursula_container_name['stdout']}}/{{ursula_container_name['stdout']}}-json.log"
search_regex: "Checking worker settings:"
search_regex: "Awaiting worker qualification"
timeout: 30
44 changes: 16 additions & 28 deletions nucypher/cli/commands/cloudworkers.py
Expand Up @@ -18,8 +18,6 @@
import click
import os

from nucypher.cli.options import option_gas_strategy, option_max_gas_price

try:
from nucypher.utilities.clouddeploy import CloudDeployers
except ImportError:
Expand Down Expand Up @@ -60,11 +58,11 @@ def cloudworkers():
@click.option('--seed-network', help="Do you want the 1st node to be --lonely and act as a seed node for this network", default=False, is_flag=True)
@click.option('--include-stakeholder', 'stakes', help="limit worker to specified stakeholder addresses", multiple=True)
@click.option('--wipe', help="Clear nucypher configs on existing nodes and start a fresh node with new keys.", default=False, is_flag=True)
@click.option('--prometheus', help="Run Prometheus on workers.", default=False, is_flag=True)
@click.option('--namespace', help="Namespace for these operations. Used to address hosts and data locally and name hosts on cloud platforms.", type=click.STRING, default='local-stakeholders')
@click.option('--env', '-e', 'envvars', help="environment variables (ENVVAR=VALUE)", multiple=True, type=click.STRING, default=[])
@click.option('--cli', '-c', 'cliargs', help="cli arguments for 'nucypher run': eg.'--max-gas-price 50'/'--c max-gas-price=50'", multiple=True, type=click.STRING, default=[])
@group_general_config
def up(general_config, staker_options, config_file, cloudprovider, aws_profile, remote_provider, nucypher_image, seed_network, stakes, wipe, prometheus, namespace, envvars):
def up(general_config, staker_options, config_file, cloudprovider, aws_profile, remote_provider, nucypher_image, seed_network, stakes, wipe, prometheus, namespace, envvars, cliargs):
vepkenez marked this conversation as resolved.
Show resolved Hide resolved
"""Creates workers for all stakes owned by the user for the given network."""

emitter = setup_emitter(general_config)
Expand All @@ -84,7 +82,7 @@ def up(general_config, staker_options, config_file, cloudprovider, aws_profile,
config_file = config_file or StakeHolderConfiguration.default_filepath()

deployer = CloudDeployers.get_deployer(cloudprovider)(emitter, STAKEHOLDER, config_file, remote_provider,
nucypher_image, seed_network, aws_profile, prometheus, namespace=namespace, network=STAKEHOLDER.network, envvars=envvars)
nucypher_image, seed_network, aws_profile, prometheus, namespace=namespace, network=STAKEHOLDER.network, envvars=envvars, cliargs=cliargs)
if staker_addresses:
config = deployer.create_nodes(staker_addresses)

Expand All @@ -99,13 +97,13 @@ def up(general_config, staker_options, config_file, cloudprovider, aws_profile,
@click.option('--remote-provider', help="The blockchain provider for the remote node, if not provided, nodes will run geth.", default=None)
@click.option('--nucypher-image', help="The docker image containing the nucypher code to run on the remote nodes. (default is nucypher/nucypher:latest)", default=None)
@click.option('--seed-network', help="Do you want the 1st node to be --lonely and act as a seed node for this network", default=False, is_flag=True)
@click.option('--prometheus', help="Run Prometheus on workers.", default=False, is_flag=True)
@click.option('--count', help="Create this many nodes.", type=click.INT, default=1)
@click.option('--namespace', help="Namespace for these operations. Used to address hosts and data locally and name hosts on cloud platforms.", type=click.STRING, default='local-stakeholders')
@click.option('--network', help="The Nucypher network name these hosts will run on.", type=click.STRING, default='mainnet')
@click.option('--env', '-e', 'envvars', help="environment variables (ENVVAR=VALUE)", multiple=True, type=click.STRING, default=[])
@click.option('--cli', '-c', 'cliargs', help="cli arguments for 'nucypher run': eg.'--max-gas-price 50'/'--c max-gas-price=50'", multiple=True, type=click.STRING, default=[])
@group_general_config
def create(general_config, cloudprovider, aws_profile, remote_provider, nucypher_image, seed_network, prometheus, count, namespace, network, envvars):
def create(general_config, cloudprovider, aws_profile, remote_provider, nucypher_image, seed_network, prometheus, count, namespace, network, envvars, cliargs):
vepkenez marked this conversation as resolved.
Show resolved Hide resolved
"""Creates the required number of workers to be staked later under a namespace"""

emitter = setup_emitter(general_config)
Expand All @@ -115,7 +113,7 @@ def create(general_config, cloudprovider, aws_profile, remote_provider, nucypher
return

deployer = CloudDeployers.get_deployer(cloudprovider)(emitter, None, None, remote_provider, nucypher_image, seed_network,
aws_profile, prometheus, namespace=namespace, network=network, envvars=envvars)
aws_profile, prometheus, namespace=namespace, network=network, envvars=envvars, cliargs=cliargs)

names = []
i = 1
Expand Down Expand Up @@ -190,16 +188,14 @@ def add_for_stake(general_config, staker_options, config_file, staker_address, h
@click.option('--nucypher-image', help="The docker image containing the nucypher code to run on the remote nodes.", default=None)
@click.option('--seed-network', help="Do you want the 1st node to be --lonely and act as a seed node for this network", default=False, is_flag=True)
@click.option('--wipe', help="Clear your nucypher config and start a fresh node with new keys", default=False, is_flag=True)
@click.option('--prometheus', help="Run Prometheus on workers.", default=False, is_flag=True)
@click.option('--namespace', help="Namespace for these operations. Used to address hosts and data locally and name hosts on cloud platforms.", type=click.STRING, default='local-stakeholders')
@click.option('--network', help="The Nucypher network name these hosts will run on.", type=click.STRING, default='mainnet')
@option_gas_strategy
@option_max_gas_price
@click.option('--include-host', 'include_hosts', help="specify hosts to update", multiple=True, type=click.STRING)
@click.option('--env', '-e', 'envvars', help="environment variables (ENVVAR=VALUE)", multiple=True, type=click.STRING, default=[])
@click.option('--cli', '-c', 'cliargs', help="cli arguments for 'nucypher run': eg.'--max-gas-price 50'/'--c max-gas-price=50'", multiple=True, type=click.STRING, default=[])
@group_general_config
def deploy(general_config, remote_provider, nucypher_image, seed_network, sentry_dsn, wipe, prometheus,
namespace, network, gas_strategy, max_gas_price, include_hosts, envvars):
def deploy(general_config, remote_provider, nucypher_image, seed_network, wipe,
namespace, network, include_hosts, envvars, cliargs):
"""Deploys NuCypher on managed hosts."""

emitter = setup_emitter(general_config)
Expand All @@ -214,13 +210,10 @@ def deploy(general_config, remote_provider, nucypher_image, seed_network, sentry
remote_provider,
nucypher_image,
seed_network,
sentry_dsn,
prometheus=prometheus,
namespace=namespace,
network=network,
gas_strategy=gas_strategy,
max_gas_price=max_gas_price,
envvars=envvars)
envvars=envvars,
cliargs=cliargs)

hostnames = deployer.config['instances'].keys()
if include_hosts:
Expand All @@ -235,16 +228,14 @@ def deploy(general_config, remote_provider, nucypher_image, seed_network, sentry
@click.option('--nucypher-image', help="The docker image containing the nucypher code to run on the remote nodes.", default=None)
@click.option('--seed-network', help="Do you want the 1st node to be --lonely and act as a seed node for this network", default=False, is_flag=True)
@click.option('--wipe', help="Clear your nucypher config and start a fresh node with new keys", default=False, is_flag=True)
@click.option('--prometheus', help="Run Prometheus on workers.", default=False, is_flag=True)
@click.option('--namespace', help="Namespace for these operations. Used to address hosts and data locally and name hosts on cloud platforms.", type=click.STRING, default='local-stakeholders')
@click.option('--network', help="The Nucypher network name these hosts will run on.", type=click.STRING, default='mainnet')
@click.option('--include-host', 'include_hosts', help="specify hosts to update", multiple=True, type=click.STRING)
@click.option('--env', '-e', 'envvars', help="environment variables (ENVVAR=VALUE)", multiple=True, type=click.STRING, default=[])
@option_gas_strategy
@option_max_gas_price
@click.option('--cli', '-c', 'cliargs', help="cli arguments for 'nucypher run': eg.'--max-gas-price 50'/'--c max-gas-price=50'", multiple=True, type=click.STRING, default=[])
@group_general_config
def update(general_config, remote_provider, nucypher_image, seed_network, sentry_dsn, wipe, prometheus,
namespace, network, gas_strategy, max_gas_price, include_hosts, envvars):
def update(general_config, remote_provider, nucypher_image, seed_network, wipe,
namespace, network, include_hosts, envvars, cliargs):
"""Updates existing installations of Nucypher on existing managed remote hosts."""

emitter = setup_emitter(general_config)
Expand All @@ -260,13 +251,10 @@ def update(general_config, remote_provider, nucypher_image, seed_network, sentry
remote_provider,
nucypher_image,
seed_network,
sentry_dsn,
prometheus=prometheus,
namespace=namespace,
network=network,
gas_strategy=gas_strategy,
max_gas_price=max_gas_price,
envvars=envvars
envvars=envvars,
cliargs=cliargs,
)

emitter.echo(f"updating the following existing hosts:")
Expand Down
73 changes: 47 additions & 26 deletions nucypher/utilities/clouddeploy.py
Expand Up @@ -174,27 +174,37 @@ def __init__(self, # TODO: Add type annotations
seed_network=False,
sentry_dsn=None,
profile=None,
prometheus=False,
pre_config=False,
network=None,
namespace=None,
gas_strategy=None,
max_gas_price=None,
action=None,
envvars=None
envvars=None,
cliargs=None,
):

self.emitter = emitter
self.stakeholder = stakeholder
self.network = network
self.namespace = namespace or 'local-stakeholders'
self.action = action

self.envvars = envvars or []
if self.envvars:
if not all([ (len(v.split('=')) == 2) for v in self.envvars]):
raise ValueError("Improperly specified environment variables: --env variables must be specified in pairs as `<name>=<value>`")
self.envvars = [v.split('=') for v in (self.envvars)]

cliargs = cliargs or []
self.cliargs = []
if cliargs:
for arg in cliargs:
if '=' in arg:
self.cliargs.append(arg.split('='))
else:
self.cliargs.append((arg, '')) # allow for --flags like '--prometheus'

self.config_filename = f'{self.network}-{self.namespace}.json'

self.created_new_nodes = False
Expand Down Expand Up @@ -249,7 +259,6 @@ def __init__(self, # TODO: Add type annotations
self.config.pop('seed_node', None)
self.nodes_are_decentralized = 'geth.ipc' in self.config['blockchain_provider']
self.config['stakeholder_config_file'] = stakeholder_config_path
self.config['use-prometheus'] = prometheus

# add instance key as host_nickname for use in inventory
if self.config.get('instances'):
Expand Down Expand Up @@ -280,6 +289,9 @@ def _configure_provider_params(self, provider_profile):
def _do_setup_for_instance_creation(self):
pass

def _format_runtime_options(self, node_options):
return ' '.join([f'--{name} {value}' for name, value in node_options.items()])

@property
def chain_id(self):
return NetworksInventory.get_ethereum_chain_id(self.network)
Expand All @@ -302,37 +314,46 @@ def update_generate_inventory(self, node_names, **kwargs):
if not nodes:
raise KeyError(f"No hosts matched the supplied names: {node_names}. Try `nucypher cloudworkers list-hosts`")

default_envvars = [
(NUCYPHER_ENVVAR_KEYRING_PASSWORD, self.config['keyringpassword']),
(NUCYPHER_ENVVAR_WORKER_ETH_PASSWORD, self.config['ethpassword']),
]
defaults = {
'envvars':
[
(NUCYPHER_ENVVAR_KEYRING_PASSWORD, self.config['keyringpassword']),
(NUCYPHER_ENVVAR_WORKER_ETH_PASSWORD, self.config['ethpassword']),
],
'cliargs': [
]
}

for datatype in ['envvars', 'cliargs']:

data_key = f'runtime_{datatype}'

input_envvars = [(k, v) for k, v in self.envvars]
input_data = [(k, v) for k, v in getattr(self, datatype)]

# populate the specified environment variables as well as the
# defaults that are only used in the inventory
for key, node in nodes.items():
node_vars = nodes[key].get('runtime_envvars', {})
for k, v in input_envvars:
node_vars.update({k: v})
nodes[key]['runtime_envvars'] = node_vars
# populate the specified environment variables as well as the
# defaults that are only used in the inventory
for key, node in nodes.items():
node_vars = nodes[key].get(data_key, {})
for k, v in input_data:
node_vars.update({k: v})
nodes[key][data_key] = node_vars

# we want to update the config with the specified envvars
# so they will persist in future invocations
self.config['instances'][key] = copy.deepcopy(nodes[key])
# we want to update the config with the specified values
# so they will persist in future invocations
self.config['instances'][key] = copy.deepcopy(nodes[key])

# we don't want to save the default_envvars to the config file
# but we do want them to be specified to the inventory template
# but overridden on a per node basis if previously specified
for key, node in nodes.items():
for k, v in default_envvars:
if not k in nodes[key]['runtime_envvars']:
nodes[key]['runtime_envvars'][k] = v
# we don't want to save the default_envvars to the config file
# but we do want them to be specified to the inventory template
# but overridden on a per node basis if previously specified
for key, node in nodes.items():
for k, v in defaults[datatype]:
if not k in nodes[key][data_key]:
nodes[key][data_key][k] = v

inventory_content = self._inventory_template.render(
deployer=self,
nodes=nodes.values(),
extra=kwargs
extra=kwargs,
)

with open(self.inventory_path, 'w') as outfile:
Expand Down
Expand Up @@ -15,14 +15,8 @@ all:
ansible_python_interpreter: /usr/bin/python3
ansible_connection: ssh
nucypher_image: ${deployer.config['nucypher_image']}
gas_strategy: ${deployer.config['gas_strategy']}
blockchain_provider: ${deployer.config['blockchain_provider']}
node_is_decentralized: ${deployer.nodes_are_decentralized}
%if deployer.config.get('use-prometheus'):
prometheus: --prometheus --metrics-port ${deployer.PROMETHEUS_PORT}
%else:
prometheus:
%endif
%if deployer.config.get('seed_node'):
SEED_NODE_URI: ${deployer.config['seed_node']}
teacher_options: --teacher ${deployer.config['seed_node']}
Expand All @@ -46,11 +40,9 @@ all:
%if node.get('nucypher_image'):
nucypher_image: ${node['nucypher_image']}
%endif
%if node.get('gas_strategy'):
gas_strategy: ${node['gas_strategy']}
%endif
runtime_envvars:
%for key, val in node['runtime_envvars'].items():
${key}: "${val}"
%endfor
nucypher_ursula_run_options: ${deployer._format_runtime_options(node['runtime_cliargs'])}
%endfor