def fetch_keyset_from_source( # noqa: C901 client, source, token, wait_timeout=180.0, allow_exists=False ): # noqa """fetch_keyset_from_source Put keyset by providing source controller to download keyset. This contains logic that handles whether or not fetching from the source fails, typically due to a firewall or routing issue in the underlay network (e.g. security groups and route tables). Pseudo-logic: PUT new keyset request to fetch from remote controller if keyset exists or already in progress, fail immediately as its unexpected if PUT succees: wait: if a new successful put returns: that indicates failure to download from source. return 400 if timeout: that indicates controller is rebooting, return wait for keyset if keyset already exists, wait to ensure keyset exists, then return keyset details Arguments: source {str} - host controller to fetch keyset from token {str} - secret token used when generating keyset wait_timeout {float} - timeout for waiting for keyset and while polling for download failure (default: 1 min) allow_exists {bool} - If true and keyset already exists, DONT throw exception Raises: e: ApiException or CohesiveSDKException Returns: KeysetDetail """ sleep_time = 2.0 failure_error_str = ( "Failed to fetch keyset for source. This typically due to a misconfigured " "firewall or routing issue between source and client controllers." ) try: put_response = client.config.put_keyset(**{"source": source, "token": token}) except ApiException as e: if allow_exists and ("keyset already exists" in e.get_error_message().lower()): Logger.info("Keyset already exists.", host=client.host_uri) return client.config.try_get_keyset() Logger.info( "Failed to fetch keyset: %s" % e.get_error_message(), host=client.host_uri, ) raise e except UrlLib3ConnExceptions: raise ApiException( status=HTTPStatus.SERVICE_UNAVAILABLE, reason="Controller unavailable. It is likely rebooting. Try client.sys_admin.wait_for_api().", ) if not put_response.response: keyset_data = client.config.get_keyset() if keyset_data.response and keyset_data.response.keyset_present: raise ApiException(status=400, reason="Keyset already exists.") raise ApiException(status=500, reason="Put keyset returned None.") start_time = put_response.response.started_at_i Logger.info(message="Keyset downloading from source.", start_time=start_time) polling_start = time.time() while time.time() - polling_start <= wait_timeout: try: duplicate_call_resp = client.config.put_keyset( **{"source": source, "token": token} ) except UrlLib3ConnExceptions: Logger.info( "API call timeout. Controller is likely rebooting. Waiting for keyset.", wait_timeout=wait_timeout, source=source, ) client.sys_admin.wait_for_api(timeout=wait_timeout, wait_for_reboot=True) return client.config.wait_for_keyset(timeout=wait_timeout) except ApiException as e: duplicate_call_error = e.get_error_message() if duplicate_call_error == "Keyset already exists.": keyset_data = client.config.try_get_keyset() if not keyset_data: Logger.info( "Keyset exists. Waiting for reboot.", wait_timeout=wait_timeout, source=source, ) client.sys_admin.wait_for_api( timeout=wait_timeout, wait_for_reboot=True ) return client.config.wait_for_keyset() return keyset_data if duplicate_call_error == "Keyset setup in progress.": # this means download is in progress, but might fail. Wait and retry time.sleep(sleep_time) continue # Unexpected ApiException raise e # If there is a new start time for keyset generation, that indicates a failure new_start_resp = duplicate_call_resp.response new_start = new_start_resp.started_at_i if new_start_resp else None if (new_start and start_time) and (new_start != start_time): Logger.error(failure_error_str, source=source) raise ApiException(status=HTTPStatus.BAD_REQUEST, reason=failure_error_str) time.sleep(sleep_time) raise CohesiveSDKException("Timeout while waiting for keyset.")
def peer_mesh( clients, peer_address_map=None, address_type=VNS3Attr.primary_private_ip, delay_configure=False, mtu=None, ): """peer_mesh Create a peering mesh by adding each client as peer for other clients. The order of the list of clients is the assumed peering id, i.e. client at clients[0] has peering id of 1, clients[1] has peering id of 2. Each TLS connection between peers is then automatically negotiated. Arguments: clients {List[VNS3Client]} Keyword Arguments: peer_address_map {Dict} - Optional map for peering addresses { [from_peer_id: str]: { [to_peer_id_1: str]: [peer_address_1: str], [to_peer_id_2: str]: [peer_address_2: str], ... } } address_type {str} - which address to use. Options: primary_private_ip, secondary_private_ip, public_ip or public_dns delay_configure {bool} -- delay automatic negotiation of peer (default: False) mtu {int} -- Override MTU for the peering TLS connection. VNS3 defaults to 1500. (default: {None}) Raises: CohesiveSDKException Returns: data_types.BulkOperationResult """ # fetch peer ids and set on clients ensure_peer_ids_result = fetch_state_attribute(clients, VNS3Attr.peer_id) if api_ops.bulk_operation_failed(ensure_peer_ids_result): errors_str = api_ops.stringify_bulk_result_exception( ensure_peer_ids_result) Logger.error("Failed to fetch peering Ids for all clients", errors=errors_str) raise CohesiveSDKException( "Failed to fetch peering Ids for all clients: %s" % errors_str) # constructu peer address mapping if peer_address_map is not None: Logger.debug("Using address map passed for peering mesh.") peer_id_to_client = { c.query_state(VNS3Attr.peer_id): c for c in clients } peer_address_mapping_tuples = [ (peer_id_to_client[from_peer_id], to_peers_map) for from_peer_id, to_peers_map in peer_address_map.items() ] else: Logger.debug("Constructing peering mesh") peer_address_mapping_tuples = _construct_peer_address_mapping( clients, address_type) common_peer_kwargs = {} if delay_configure: common_peer_kwargs.update(force=False) if mtu: common_peer_kwargs.update(overlay_mtu=mtu) def create_all_peers_for_client(client, post_peer_kwargs): return [ client.peering.post_create_peer( **dict(peering_request, **common_peer_kwargs)) for peering_request in post_peer_kwargs ] run_peering_funcs = [] # bind api function calls for peer creations for vns3_client, peer_mapping in peer_address_mapping_tuples: run_peering_funcs.append( bind( create_all_peers_for_client, vns3_client, [{ "id": peer_id, "name": peer_address } for peer_id, peer_address in peer_mapping.items()], )) Logger.debug("Creating %d-way peering mesh." % len(clients)) return api_ops.__bulk_call_api(run_peering_funcs, parallelize=True)