Esempio n. 1
0
    def jumpstart(self, name, network_directory, block):
        self.app.log.info(f'Attempting to jumpstart {name} to block: {block}.')

        url = f'{self.app.config["hydra"]["channel_url"]}/jumpstart/{name}/jumps.json'

        # Get the published jumpstart data
        try:
            jumps_json = json.loads(requests.get(url).content)
        except Exception as exc:  # pylint: disable=broad-except
            self.app.log.debug(f'Jumpstart metadata retrieval failed with: {exc}')
            self.app.log.warning(f'No jumpstart data found for network {name}.  Continuing without jumpstart')
            return

        if block not in jumps_json:
            raise HydraError(f'Network {name} does not have jumpstart for block {block}')

        # Next operations will all occur within the node directory for this network
        os.chdir(network_directory)

        # Get the jumpstart gzipped tar file
        url = f'{self.app.config["hydra"]["channel_url"]}/jumpstart/{name}/{jumps_json[block]}'
        jumpstart_tarfile = jumps_json[block]

        try:
            self.app.utils.download_file_stream(jumpstart_tarfile, url)
        except Exception as exc:
            raise HydraError(f'Unable to download jumpstart file {jumpstart_tarfile}: {exc}')

        # Cleanup existing data that we're overwriting from jumpstart
        for delete_dir in ['app.db', 'receipts_db', 'chaindata/data']:
            try:
                rmtree(os.path.join(network_directory, delete_dir))
            except FileNotFoundError as exc:
                self.app.log.debug(f'{exc}')
            except IOError as exc:
                raise HydraError(f'Cleanup of existing data failed: {exc}')

        # Extract jumpstart over network directory
        self.app.log.info(f'Extracting jumpstart contents')
        try:
            with tarfile.open(jumpstart_tarfile) as tar:
                tar_members = tar.getmembers()
                for member in tqdm(iterable=tar_members, total=(len(tar_members) - 1)):
                    tar.extract(member=member)
        except tarfile.TarError as exc:
            raise HydraError(f'Unable to extract jumpstart file {jumpstart_tarfile}: {exc}')

        # Cleanup jumpstart gzipped tar file
        try:
            os.remove(jumpstart_tarfile)
        except OSError as exc:
            self.app.log.warning(f'Unable to cleanup jumpstart tar. {exc}')

        self.app.log.info(f'Jumpstarting network complete!')
Esempio n. 2
0
    def env_or_arg(self, arg_name, env_name, or_path=None, required=False):
        # pargs has default value of None if argument not provided
        value = getattr(self.app.pargs, arg_name)

        if value is None:
            if env_name in os.environ:
                value = os.environ[env_name]
                self.app.log.info(
                    f'--{arg_name} not specified, using environ[{env_name}]: {value}'
                )

            elif or_path and os.path.exists(self.app.utils.path(or_path)):
                with open(self.app.utils.path(or_path), 'r') as variable_file:
                    value = variable_file.read().strip()
                    self.app.log.info(
                        f'--{arg_name} not specified, using file {or_path}: {value}'
                    )

        if required and value is None:
            self.app.log.error(
                f'You must specify either --{arg_name} or set {env_name} in your environment'
            )
            raise HydraError(
                f'You must specify either --{arg_name} or set {env_name} in your environment'
            )

        return value
Esempio n. 3
0
    def pull_registry(self):
        name = self.app.utils.env_or_arg('name',
                                         'HYDRA_NETWORK',
                                         or_path='.hydra_network')

        try:
            url = f'{self.app.config["hydra"]["channel_url"]}/networks/{name}/hydra.json'
            network_registry = json.loads(requests.get(url).content)
            self.app.network.register(name, network_registry)
        except Exception as exc:
            raise HydraError(
                f'Unable to pull updated registry information: {exc}')
Esempio n. 4
0
    def uninstall_systemd(self, name, binary='shipchain'):
        service_name = f'{name}{"" if binary is "shipchain" else f".{binary}"}.service'
        systemd_service = f'/etc/systemd/system/{service_name}'

        self.app.log.info(f'Uninstalling {service_name}')

        if not os.path.exists(systemd_service):
            raise HydraError(f'Systemd file {systemd_service} not found')

        self.app.utils.binary_exec('sudo', 'systemctl', 'stop', service_name)
        self.app.utils.binary_exec('sudo', 'systemctl', 'disable', service_name)
        self.app.utils.binary_exec('sudo', 'systemctl', 'reset-failed', service_name, ignore_error=True)
        self.app.utils.binary_exec('sudo', 'systemctl', 'daemon-reload')
        self.app.utils.binary_exec('sudo', 'rm', systemd_service)
Esempio n. 5
0
    def leave_network(self):
        name = self.app.utils.env_or_arg('name', 'HYDRA_NETWORK', or_path='.hydra_network', required=True)
        destination = self.app.pargs.destination or self.app.utils.path(name)

        # Verify network directory exists before we start removing everything
        if not os.path.exists(destination):
            raise HydraError(f'Network directory {destination} does not exist')

        # Stop and remove service before removing network directory
        try:
            self.app.client.uninstall_systemd(name)
        except HydraError as exc:
            self.app.log.warning(exc)
            self.app.log.info(f'Service not installed.  Attempting to stop executable manually.')
            self.app.client.find_and_kill_executable(destination)

        # Remove network directory
        rmtree(destination)
        self.app.log.info(f'Removed network directory {destination}')

        # Remove .hydra_network if this is the default network
        default_network_file = self.app.utils.path('.hydra_network')

        if os.path.exists(default_network_file):
            remove_default_network = False

            with open(default_network_file, 'r') as network_file:
                default_network = network_file.readline().strip()
                if default_network == name:
                    remove_default_network = True

            if remove_default_network:
                os.remove(default_network_file)
                self.app.log.info(f'Removed default network setting')

        self.app.log.info(f'Successfully left network {name}')
Esempio n. 6
0
 def get(stub):
     url = f'http://{host}:{port}{stub}'
     try:
         return json.loads(requests.get(url).content)
     except requests.exceptions.ConnectionError:
         raise HydraError(f'Error accessing {url}.  Is your node running?')
Esempio n. 7
0
    def generate_jumpstart(self):
        networks = self.app.network.read_networks_file()
        name = self.app.utils.env_or_arg('name',
                                         'HYDRA_NETWORK',
                                         or_path='.hydra_network')

        if name not in networks:
            self.app.log.error(
                f'You must choose a valid network name: {networks.keys()}')
            return

        if len(networks[name]['ips']) <= 1:
            self.app.log.error(
                f'Jumpstart loom.yaml would contain Oracle specific settings.')
            raise HydraError(f'Not enough nodes in network')

        ip = networks[name]['ips'][-1]

        # We want to include current block height in tarfile name
        self.app.log.info(f'Getting client status on {ip}')
        block_height = json.loads(
            self.app.network.run_command(
                ip, 'hydra -o json client status'))["node_block_height"]
        self.app.log.info(f'Current block height {block_height}')

        # We don't want to package live databases
        self.app.log.info(f'Stopping node before packaging jumpstart')
        self.app.network.run_command(
            ip, f'hydra client stop-service --name {name} 2>&1')

        try:
            tarfile = f'{datetime.today().strftime("%Y-%m-%d")}_{block_height}_{name}.tar.gz'
            s3_destination = f's3://{self.app.release.dist_bucket}/jumpstart/{name}/{tarfile}'

            jumpstart_include = [
                'genesis.json',
                'app.db',
                'evm.db',
                'receipts_db',
                'chaindata/config/genesis.json',
                'chaindata/data/blockstore.db',
                'chaindata/data/evidence.db',
                'chaindata/data/fnConsensus.db',
                'chaindata/data/state.db',
                'chaindata/data/tx_index.db',
            ]

            self.app.log.info(f'Building {tarfile}')
            self.app.network.run_command(
                ip, f"cd /data/{name}; "
                f"tar -zcf {tarfile} "
                f"{' '.join(jumpstart_include)}")

            # The AMI does not include awscli by default
            self.app.log.info(f'Ensuring AWS CLI is available')
            self.app.network.run_command(
                ip, f"sudo apt-get -y install awscli 2>&1")

            self.app.log.info(f'Uploading {tarfile} to {s3_destination}')
            self.app.network.run_command(
                ip,
                f"cd /data/{name}; aws s3 cp {tarfile} {s3_destination} --acl public-read"
            )

            self.app.log.info(f'Cleaning up {tarfile}')
            self.app.network.run_command(ip,
                                         f"cd /data/{name}; rm -f {tarfile}")

            s3 = self.app.release.get_boto().resource('s3')
            try:
                jumps_obj = s3.Object(self.app.release.dist_bucket,
                                      f'jumpstart/{name}/jumps.json').get()
                jumps_json = json.loads(
                    jumps_obj['Body'].read().decode('utf-8'))
            except s3.meta.client.exceptions.NoSuchKey:
                jumps_json = {}

            jumps_json[block_height] = tarfile
            jumps_json['latest'] = tarfile

            s3.Object(self.app.release.dist_bucket,
                      f'jumpstart/{name}/jumps.json').put(
                          ACL='public-read',
                          Body=json.dumps(jumps_json).encode('utf-8'),
                          ContentType='application/json',
                      )

            self.app.log.info(f'Jumpstart generation complete!')

        except Exception as exc:
            self.app.log.error(f'Jumpstart generation failed {exc}')

        finally:
            self.app.log.info(f'Restarting node service')
            self.app.network.run_command(
                ip, f'hydra client start-service --name {name} 2>&1')
Esempio n. 8
0
    def monitor_cloudformation_stack(self, stack, node_count, name):
        stack.reload()
        registry = {
            'bootstrapped': datetime.utcnow().strftime('%c'),
            'size': node_count,
            'status': stack.stack_status,
            'outputs': {},
            'ips': [],
            'node_data': {}
        }
        self.app.network.register(name, registry)
        while stack.stack_status == 'CREATE_IN_PROGRESS':
            self.app.log.info(f'Status: {stack.stack_status}')
            time.sleep(10)
            stack.reload()
            registry['status'] = stack.stack_status
            self.app.network.register(name, registry)
        self.app.log.info(f'Status: {stack.stack_status}')

        if stack.stack_status != 'CREATE_COMPLETE':
            user_response = shell.Prompt(
                'Error deploying cloudformation, what do you want to do?',
                options=['Delete It', 'Leave It'],
                numbered=True)
            if user_response.prompt() == 'Delete It':
                stack.delete()
            return

        if self.app.pargs.default:
            with open(self.app.utils.path('.hydra_network'),
                      'w+') as network_file:
                network_file.write(name)

        registry['outputs'] = {
            o['OutputKey']: o['OutputValue']
            for o in stack.outputs
        }
        for node in range(node_count):
            ip = registry['outputs'][f'IP{node}']
            registry['ips'].append(ip)
            self.app.log.info(f'Node IP: {ip}')

        self.app.network.register(name, registry)

        self.app.log.info(
            'Creation complete, pausing for a minute while the software installs...'
        )

        bootstrapped_a_node = False
        for ip in registry['ips']:
            for attempt in range(1, 11):
                try:
                    self.app.log.info(
                        f'Bootstrapping node {ip} attempt {attempt}...')
                    registry['node_data'][ip] = self.get_bootstrap_data(
                        ip, name)
                    bootstrapped_a_node = True
                    break
                except Exception:  # pylint: disable=broad-except
                    if attempt >= 10:
                        self.app.log.error(
                            f'Timed out waiting for node {ip} to bootstrap.')
                        break
                    time.sleep(30)
                    continue

        if not bootstrapped_a_node:
            raise HydraError(f'Bootstrapping failed for all nodes')

        registry['bootstrapped'] = datetime.utcnow().strftime('%c')
        self.app.network.register(name, registry)

        self.app.log.info('Stack launch success!')