def add_clarity(self, config: DockerConfig): if self.node_count < 1: raise Exception("There must be at lease one casperlabs node") with self._lock: node_name = self.cl_nodes[0].node.container_name self.grpc_web_proxy_node = DockerGrpcWebProxy(config, node_name) self.clarity_node = DockerClarity( config, self.grpc_web_proxy_node.container_name) self.selenium_node = DockerSelenium(config) wait_for_clarity_started(self.clarity_node, config.command_timeout, 1) # Since we need pull selenium images from docker hub, it will take more time wait_for_selenium_started(self.selenium_node, 5 * 60, 1) if self.in_docker: # If these integration tests are running in a docker container, then we need connect the docker container # to the network of selenium network = self.selenium_node.network_from_name(config.network) # Gets name of container name of integration_testing integration_test_node = os.getenv("HOSTNAME") network.connect(integration_test_node) remote_drive = f"http://{self.selenium_node.name}:4444/wd/hub" else: remote_drive = f"http://127.0.0.1:4444/wd/hub" chrome_options = Options() prefs = { "profile.default_content_setting_values.automatic_downloads": 1 } chrome_options.add_experimental_option("prefs", prefs) self.selenium_driver = webdriver.Remote(remote_drive, DesiredCapabilities.CHROME, options=chrome_options) self.selenium_driver.implicitly_wait(30)
class CasperLabsNetwork: """ CasperLabsNetwork is the base object for a network of 0-many CasperLabNodes. A subclass should implement `_create_cl_network` to stand up the type of network it needs. """ grpc_encryption = False behind_proxy = False initial_motes = INITIAL_MOTES_AMOUNT def __init__(self, docker_client: DockerClient, extra_docker_params: Dict = None): self.extra_docker_params = extra_docker_params or {} self._next_key_number = FIRST_VALIDATOR_ACCOUNT self.docker_client = docker_client self.cl_nodes: List[CasperLabsNode] = [] self.clarity_node: DockerClarity = None self.selenium_node: DockerSelenium = None self.selenium_driver: WebDriver = None self.grpc_web_proxy_node: DockerGrpcWebProxy = None self._created_networks: List[str] = [] self._lock = (threading.RLock() ) # protect self.cl_nodes and self._created_networks self._accounts_lock = threading.Lock() self.test_accounts = {} self.next_key = 1 @property def node_count(self) -> int: return len(self.cl_nodes) @property def docker_nodes(self) -> List[DockerNode]: with self._lock: return [cl_node.node for cl_node in self.cl_nodes] @property def execution_engines(self) -> List[DockerExecutionEngine]: with self._lock: return [cl_node.execution_engine for cl_node in self.cl_nodes] @property def genesis_account(self): """ Genesis Account Address """ return GENESIS_ACCOUNT @property def in_docker(self) -> bool: return os.getenv("IN_DOCKER") == "true" def lookup_node(self, node_id): m = {node.node_id: node for node in self.docker_nodes} return m[node_id] def test_account(self, node, amount=TEST_ACCOUNT_INITIAL_BALANCE) -> Account: name = test_name() if not name: # This happens when a thread tries to deploy. # Name of the test that spawned the thread does not appear on the inspect.stack. # Threads that don't want to use genesis account # should pass from_address, public_key and private_key to deploy explicitly. return self.genesis_account elif name not in self.test_accounts: with self._accounts_lock: self.test_accounts[name] = Account(self.next_key) logging.info( f"=== Creating test account #{self.next_key} {self.test_accounts[name].public_key_hex} for {name} " ) block_hash = node.transfer_to_account(self.next_key, amount) # Waiting for the block with transaction that created new account to propagate to all nodes. # Expensive, but some tests may rely on it. wait_for_block_hash_propagated_to_all_nodes( node.cl_network.docker_nodes, block_hash) for deploy in node.client.show_deploys(block_hash): assert (deploy.is_error is False), f"Account creation failed: {deploy}" self.next_key += 1 return self.test_accounts[name] def from_address(self, node) -> str: return self.test_account(node).public_key_hex def get_key(self): key_pair = Account(self._next_key_number) self._next_key_number += 1 return key_pair def create_cl_network(self, **kwargs): """ Should be implemented with each network class to setup custom nodes and networks. """ raise NotImplementedError( "Must implement 'create_cl_network' in subclass.") def create_docker_network(self) -> str: with self._lock: tag_name = os.environ.get("TAG_NAME") or "latest" tag_name += f"-{self.docker_client.cl_unique_run_num}" network_name = f"casperlabs_{random_string(5)}_{tag_name}" self._created_networks.append(network_name) self.docker_client.networks.create(network_name, driver="bridge") logging.info(f"Docker network {network_name} created.") return network_name def _add_cl_node(self, config: DockerConfig) -> None: with self._lock: config.number = self.node_count cl_node = CasperLabsNode(self, config) self.cl_nodes.append(cl_node) def add_new_node_to_network(self, generate_config=None, account: Account = None) -> Account: if account is None: account = self.get_key() if generate_config is not None: config = generate_config(account) else: config = DockerConfig( self.docker_client, node_private_key=account.private_key, node_account=account, grpc_encryption=self.grpc_encryption, behind_proxy=self.behind_proxy, ) self.add_cl_node(config) self.wait_method(wait_for_approved_block_received_handler_state, 1) self.wait_for_peers() return account def add_bootstrap(self, config: DockerConfig, bootstrap_address: str = None) -> None: config.is_bootstrap = True config.bootstrap_address = bootstrap_address self._add_cl_node(config) self.wait_method(wait_for_node_started, 0) wait_for_genesis_block(self.docker_nodes[0]) def add_clarity(self, config: DockerConfig): if self.node_count < 1: raise Exception("There must be at lease one casperlabs node") with self._lock: node_name = self.cl_nodes[0].node.container_name self.grpc_web_proxy_node = DockerGrpcWebProxy(config, node_name) self.clarity_node = DockerClarity( config, self.grpc_web_proxy_node.container_name) self.selenium_node = DockerSelenium(config) wait_for_clarity_started(self.clarity_node, config.command_timeout, 1) # Since we need pull selenium images from docker hub, it will take more time wait_for_selenium_started(self.selenium_node, 5 * 60, 1) if self.in_docker: # If these integration tests are running in a docker container, then we need connect the docker container # to the network of selenium network = self.selenium_node.network_from_name(config.network) # Gets name of container name of integration_testing integration_test_node = os.getenv("HOSTNAME") network.connect(integration_test_node) remote_drive = f"http://{self.selenium_node.name}:4444/wd/hub" else: remote_drive = f"http://127.0.0.1:4444/wd/hub" chrome_options = Options() prefs = { "profile.default_content_setting_values.automatic_downloads": 1 } chrome_options.add_experimental_option("prefs", prefs) self.selenium_driver = webdriver.Remote(remote_drive, DesiredCapabilities.CHROME, options=chrome_options) self.selenium_driver.implicitly_wait(30) def add_cl_node( self, config: DockerConfig, network_with_bootstrap: bool = True, bootstrap_address: str = None, ) -> None: with self._lock: if self.node_count == 0: raise Exception("Must create bootstrap first") config.bootstrap_address = (bootstrap_address or self.cl_nodes[0].node.address) if network_with_bootstrap: config.network = self.cl_nodes[0].node.config.network self._add_cl_node(config) def stop_cl_node(self, node_number: int) -> None: with self._lock: cl_node = self.cl_nodes[node_number] cl_node.execution_engine.stop() node = cl_node.node with wait_for_log_watcher(GoodbyeInLogLine(node.container)): node.stop() def start_cl_node(self, node_number: int) -> None: with self._lock: self.cl_nodes[node_number].execution_engine.start() node = self.cl_nodes[node_number].node node.truncate_logs() node.start() wait_for_approved_block_received_handler_state( node, node.config.command_timeout) wait_for_genesis_block(self.docker_nodes[node_number]) def wait_for_peers(self) -> None: if self.node_count < 2: return for cl_node in self.cl_nodes: wait_for_peers_count_at_least(cl_node.node, self.node_count - 1, cl_node.config.command_timeout) def wait_method(self, method: Callable, node_number: int) -> None: """ Calls a wait method with the node and timeout from internal objects Blocks until satisfied or timeout. :param method: wait method to call :param node_number: index of self.cl_nodes to use as node :return: None """ cl_node = self.cl_nodes[node_number] method(cl_node.node, cl_node.config.command_timeout) def __enter__(self): return self def __exit__(self, exception_type, exception_value, traceback=None): if exception_type is not None: import traceback as tb logging.error( f"Python Exception Occurred: {exception_type} {exception_value} {tb.format_exc()}" ) with self._lock: if self.clarity_node: self.clarity_node.cleanup() if self.grpc_web_proxy_node: self.grpc_web_proxy_node.cleanup() if self.selenium_node: self.selenium_node.cleanup() for node in self.cl_nodes: node.cleanup() if self.in_docker and self.selenium_node: # If these integration tests are running in a docker container, # then we need disconnect the docker container from the network of selenium network = self.selenium_node.network_from_name( self.selenium_node.config.network) # Gets name of container name of integration_testing integration_test_node = os.getenv("HOSTNAME") network.disconnect(integration_test_node) self.cleanup() return True def cleanup(self): with self._lock: for network_name in self._created_networks: try: self.docker_client.networks.get(network_name).remove() except (NotFound, Exception) as e: logging.warning( f"Exception in cleanup while trying to remove network {network_name}: {str(e)}" )