def set_version_library(client, api, library):
    """Set the API library functions based on the current clients version

    Arguments:
        client {APIClient}
        api {Object} -
        library {Dict} - {
            func_name: str -> dict[version:str -> func]
        }

    Returns: None
    """
    client_version = client.dot_version
    if not client_version:
        client_version = client.latest_version()

    def in_range(val, min_v, max_v):
        return val >= min_v and val <= max_v

    version_library = {}
    vns3_version_int = parse_version_to_int(client_version)
    for function_name, versions_funcs in library.items():
        for version, func in versions_funcs.items():
            if "-" in version:
                min_v, max_v = [parse_version_to_int(i) for i in version.split("-")]
                if in_range(vns3_version_int, min_v, max_v):
                    version_library[function_name] = func
            elif version == client_version:
                version_library[function_name] = func

    Logger.debug(
        "Setting v%s API functions %s" % (client_version, api.__class__.__name__)
    )
    for name, func in version_library.items():
        setattr(api, name, functools.partial(func, client))
def set_peer_ids(clients, ids=None) -> data_types.BulkOperationResult:
    """Set peer ids for all clients. Assume order of clients passed as ids

    Arguments:
        clients {List[VNS3Client]}

    Returns:
        BulkOperationResult
    """
    def _set_peer_id(_client, i):
        get_resp = _client.peering.get_peering_status()
        if get_resp.response.id == i:
            _client.add_to_state(VNS3Attr.peer_id, i)
            return get_resp

        resp = _client.peering.put_self_peering_id(id=i)
        _client.add_to_state(VNS3Attr.peer_id, i)
        return resp

    ordered_ids = ids or range(1, len(clients) + 1)

    bound_api_calls = [
        bind(_set_peer_id, client, ordered_ids[index])
        for index, client in enumerate(clients)
    ]

    Logger.debug("Setting peer IDS: %s" % ordered_ids)
    return api_ops.__bulk_call_api(bound_api_calls, parallelize=True)
def create_firewall_policy(client: VNS3Client, firewall_rules, state={}):
    """Create group of firewall rules

    Arguments:
        client {VNS3Client}
        firewall_rules {List[CreateFirewallRuleRequest]} - [{
            'position': int,
            'rule': str
        }, ...]

    Keyword Arguments:
        state {dict} - State to format rules with. (can call client.state)

    Returns:
        Tuple[List[str], List[str]] - success, errors
    """
    successes = []
    errors = []
    Logger.debug(
        "Creating firewall policy.",
        host=client.host_uri,
        rule_count=len(firewall_rules),
    )
    for i, rule_args in enumerate(firewall_rules):
        rule, err = util.format_string(rule_args["rule"], state)
        if err:
            errors.append(err)
            continue

        rule_args.update(rule=rule)
        client.firewall.post_create_firewall_rule(**rule_args)
        successes.append('Rule "%s" inserted at position %d' %
                         (rule, rule_args["position"]))
    return successes, errors
Beispiel #4
0
def wait_for_tunnel_connected(api_client,
                              endpoint_id=None,
                              tunnel_id=None,
                              retry_timeout=2,
                              timeout=60,
                              sleep_time=1.0,
                              **kwargs):
    """Wait for tunnel to have status connected. If ONLY endpoint id provided, assumes only 1 tunnel exists.

    Arguments:
        api_client {VNS3Client} -- [description]

    Keyword Arguments:
        endpoint_id {int} - endpoint to check tunnel status for
        tunnel_id {int} - Tunnel for status
        retry_timeout {int}
        timeout {int}
        sleep_time {float}

    Returns:
        [dict] - tunnel status dict
    """
    import time

    if not (tunnel_id or endpoint_id):
        raise RuntimeError("tunnel_id or endpoint_id must be provided")

    if not tunnel_id:
        endpoint = get_ipsec_endpoint(api_client, endpoint_id).response
        endpoint_tunnels = endpoint["tunnels"]
        total_tunnels = len(endpoint_tunnels)
        if total_tunnels != 1:
            raise RuntimeError(
                "Can't determine tunnel for endpoint. Expected 1 tunnel, found %s"
                % str(total_tunnels))
        tunnel_id = next(iter(endpoint_tunnels.values()))["id"]

    tunnel_id = str(tunnel_id)
    start_time = time.time()
    while time.time() - start_time < timeout:
        ipsec_status = get_ipsec_status(api_client,
                                        _request_timeout=retry_timeout)
        if tunnel_id not in ipsec_status.response:
            raise CohesiveSDKException("Tunnel ID %s does not exist." %
                                       tunnel_id)

        tunnel = ipsec_status.response[tunnel_id]
        if tunnel.get("connected") is True:
            Logger.debug("Tunnel connected",
                         host=api_client.host_uri,
                         tunnel_id=tunnel_id)
            return tunnel

        Logger.debug("Tunnel not up yet. Waiting.",
                     host=api_client.host_uri,
                     tunnel_id=tunnel_id)
        time.sleep(sleep_time)
    raise CohesiveSDKException("Polling timeout [timeout=%sseconds]" % timeout)
Beispiel #5
0
def wait_for_api(api_client,
                 retry_timeout=2,
                 timeout=60,
                 wait_for_reboot=False,
                 assert_config=None,
                 healthy_ping_count=10,
                 sleep_time=1.5,
                 **kwargs):  # noqa: E501
    """wait_for_api  # noqa: E501

    Wait for api availability. This method makes a synchronous HTTP request for API status
    :param _request_timeout: timeout setting for this request. If one
                                number provided, it will be total request
                                timeout. It can also be a pair (tuple) of
                                (connection, read) timeouts.
    :return: {}
    """
    import time

    start_time = time.time()

    if wait_for_reboot:
        _wait_for_down(api_client, sleep_time=1, timeout=timeout)

    successful_pings = 0
    target_host = api_client.host_uri
    while time.time() - start_time < timeout:
        try:
            config_detail = config_api.get_config(
                api_client, _request_timeout=retry_timeout)
            if (config_detail and config_detail.response
                    and config_detail.response.vns3_version):
                if not assert_config or (assert_config(
                        config_detail.response)):
                    successful_pings = successful_pings + 1
                    if successful_pings >= healthy_ping_count:
                        return config_detail.response
        except (
                urllib3.exceptions.ConnectTimeoutError,
                urllib3.exceptions.NewConnectionError,
                urllib3.exceptions.MaxRetryError,
        ):
            Logger.debug(
                "API connection error on API ping. Retrying in %ds." %
                sleep_time,
                host=target_host,
            )
        except ApiException as e:
            if e.status == 502:
                time.sleep(sleep_time)
            else:
                raise e

        time.sleep(sleep_time)
    raise ApiException("API timeout [timeout=%sseconds]" % timeout)
def create_route_table(client: VNS3Client, routes, state={}):
    """Create routing policy

    Arguments:
        client {VNS3Client}
        routes {List[Route]} - [{
            "cidr": "str",
            "description": "str",
            "interface": "str",
            "gateway": "str",
            "tunnel": "int",
            "advertise": "bool",
            "metric": "int",
        }, ...]

    Keyword Arguments:
        state {dict} - State to format routes with. (can call client.state)

    Returns:
        Tuple[List[str], List[str]] - success, errors
    """
    successes = []
    errors = []
    Logger.debug(
        "Setting controller route table.",
        host=client.host_uri,
        route_count=len(routes),
    )

    _sub_vars = state or client.state
    for i, route_kwargs in enumerate(routes):
        skip = False
        for key, value in route_kwargs.items():
            _value, err = util.format_string(value, _sub_vars)
            if err:
                errors.append("Route key %s not formattable." % key)
                skip = True
            else:
                route_kwargs.update(**{key: _value})

        if skip:
            continue

        client.routing.post_create_route_if_not_exists(route_kwargs)
        successes.append("Route created: route=%s" % str(route_kwargs))

    if errors:
        raise CohesiveSDKException(",".join(errors))

    return successes, errors
Beispiel #7
0
def post_create_route_if_not_exists(
    api_client, route_request, comparison_keys=RouteConstants.RouteComparisonKeys
):
    """Create route if it doesn not exist for client. Compare based on keys.

    Arguments:
        client {VNS3Client}
        route {dict} - Route dictionary

    Keyword Arguments:
        comparison_keys {List[str]} - list of Route attributes to compare when checking for existence.
                                        default: ["cidr", "interface", "gateway"]

    Returns:
        [RoutesListResponse] -  dictionary of routes keyed by route Ids (ints). id -> dict
    """

    def __to_route_tuple(route, keys):
        t = ()
        for key in keys:
            _val = route.get(key)
            if _val is not None:
                t += (_val,)
            elif key in ("interface", "gateway"):
                t += ("_notset",)
            else:
                t += (None,)
        return t

    routes_response = api_client.routing.get_routes().response
    route_tuples = (
        {
            __to_route_tuple(current_route, comparison_keys): current_route
            for current_route in routes_response.values()
        }
        if routes_response
        else {}
    )
    new_route_tuple = __to_route_tuple(route_request, comparison_keys)
    if new_route_tuple in route_tuples:
        Logger.debug(
            "Route already exists. Skipping creation.",
            host=api_client.host_uri,
        )
        return routes_response

    return api_client.routing.post_create_route(**route_request).response
def wait_for_backup(api_client, job_uuid, retry_timeout=2.0, timeout=60):
    """Wait for backup job to complete

    :param VNS3Client api_client: (required)
    :param str job_uuid: (required)

    Keyword Arguments:
        retry_timeout {float} - time to sleep between retries
        timeout {int} - max time to wait

    Raises:
        ApiException

    Returns:
        dict job data {
            'uuid': str,
            'status': str,
            'filename': str
        }
    """
    import time

    start_time = time.time()

    target_host = api_client.host_uri
    while time.time() - start_time < timeout:
        try:
            backup_resp = get_backup_job(api_client, job_uuid)
            backup_data = backup_resp.response
            if backup_data and backup_data.status.lower() == "completed":
                return backup_data

            Logger.debug("Backup still prcessing. Waiting.", host=target_host)
            time.sleep(retry_timeout)
        except UrlLib3ConnExceptions:
            Logger.debug(
                "API connection error fetching backup data. Retrying.", host=target_host
            )
            time.sleep(retry_timeout)
            continue
    raise ApiException(
        reason="Backup processing never finished. Timeout %s seconds." % timeout
    )
import os

from cohesivenet import Logger
from cohesivenet.macros import connect, config, admin, peering, routing

Logger.silence_urllib3()


def take_keys(keys, data_dict):
    """Take keys from dict

    Arguments:
        keys {List[str]} -- Keys it include in output dict
        data_dict {Dict}

    Returns:
        [Dict]
    """
    return {k: v for k, v in data_dict.items() if k in keys}


def setup_clients(host_password_dicts):
    """setup_clients Connect to clients

    Arguments:
        args: List[Dict] - {
            host: str,
            password: str
        }

    Returns:
import os
from datetime import datetime

from cohesivenet import Logger
from cohesivenet.macros import connect, config, admin, peering, routing, state

Logger.silence_urllib3()
Logger.set_stream_handler(os.getenv("COHESIVE_LOG_LEVEL", "info").lower())

TOPOLOGY = "VNS3 Peering Mesh Example"


def print_log(message, level="info", module="peering_mesh"):
    print(
        "[%s] [%s] [%s] [%s]" % (str(datetime.utcnow()), module, level.upper(), message)
    )


def get_env_config():
    """Fetch variables from environment:

    CONTROLLER_HOSTS_CSV: CSV of VNS3 hosts
    CONTROLLER_PASSWORDS_CSV: CSV of VNS3 host passwords
    MASTER_PASSWORD: master password to be used for API
    ROOT_CONTROLLER: (Optional, defaults to first in HOSTS list) select root controller by host. Root will be licensed first
    ROOT_CONTROLLER_PASSWORD: (Optional) Password for root controller
    CONTROLLER_SUBNETS_CSV: specific subnet for controllers, e.g. 10.0.1.0/25,10.0.1.128/25
    LICENSE: path to license file
    KEYSET_TOKEN: secret token to be used for keyset

    example env file:
def assert_rule_policy(client: VNS3Client, rules, should_fix=False):
    """Assert rule policy contains expected rules

    Arguments:
        client {VNS3Client}
        rules {List[dict]}

    Keyword Arguments:
        should_fix {bool} - if false, raise Error, else, update firewall

    Raises:
        CohesiveSDKException - raised if invalid firewall rules provided
        AssertionError - raised if should_fix=False and provided rules dont match VNS3

    Returns:
        List[str] - ordered list of firewall rules
    """
    current_firewall = __firewall_resp_to_list(
        client.firewall.get_firewall_rules())
    new_firewall, errors = __construct_proposed_firewall_list(
        rules, state=client.state)
    if errors:
        raise CohesiveSDKException(
            "Invalid firewall rules provided. Errors=%s" % (errors))

    if current_firewall == new_firewall:
        Logger.info("Current firewall is correct. No-op.",
                    host=client.host_uri)
        return current_firewall

    Logger.info(
        "Firewall configuration drift. Expected: %s != %s." %
        (new_firewall, current_firewall),
        host=client.host_uri,
    )

    if not should_fix:
        raise AssertionError(
            "Firewalls did not match for VNS3 @ %s. Current firewall %s != %s."
            % (client.host_uri, current_firewall, new_firewall))

    # operations: insert, delete
    OP_INS = "insert"
    OP_DEL = "delete"
    firewall_edits = []
    for i, rule in enumerate(new_firewall):
        if len(current_firewall) <= i:
            operation = OP_INS
        elif current_firewall[i] == rule:
            continue
        else:
            # current firewall rule is incorrect.
            # now, minimize operations to get correct
            # if can insert OR delete, prefer delete
            # ie. if next rule is the correct rule, del this rule
            operation = (OP_DEL if len(current_firewall) > i + 1
                         and current_firewall[i + 1] == rule else OP_INS)

        firewall_edits.append("%s:%s" % (operation, i))
        if operation == OP_INS:
            client.firewall.post_create_firewall_rule(**{
                "rule": rule,
                "position": i
            })
            current_firewall.insert(i, rule)
        else:  # operation == OP_DEL:
            client.firewall.delete_firewall_rule_by_position(i)
            del current_firewall[i]

    Logger.debug(
        "%s network operations required to fix firewall: %s" %
        (len(firewall_edits), firewall_edits),
        host=client.host_uri,
    )

    return __firewall_resp_to_list(client.firewall.get_firewall_rules())
Beispiel #12
0
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.")
Beispiel #13
0
def setup_controller(
    client: VNS3Client,
    topology_name: str,
    license_file: str,
    license_parameters: Dict,
    keyset_parameters: Dict,
    controller_name: str = None,
    peering_id: int = 1,
    reboot_timeout=120,
    keyset_timeout=120,
):
    """setup_controller Set the topology name, controller license, keyset and peering ID if provided

    Arguments:
        client {VNS3Client}
        topology_name {str}
        controller_name {str}
        keyset_parameters {Dict} -- UpdateKeysetRequest {
            'source': 'str',
            'token': 'str',
            'topology_name': 'str',
            'sealed_network': 'bool'
        }
        peering_id {int} -- ID for this controller in peering mesh

    Returns:
        OperationResult
    """
    current_config = client.config.get_config().response
    Logger.info("Setting topology name", name=topology_name)
    config_update = {}
    if topology_name and current_config.topology_name != topology_name:
        config_update.update({"topology_name": topology_name})
    if controller_name and current_config.controller_name != controller_name:
        config_update.update({"controller_name": controller_name})
    if config_update:
        client.config.put_config(**config_update)

    if not current_config.licensed:
        if not os.path.isfile(license_file):
            raise CohesiveSDKException("License file does not exist")

        with open(license_file) as f:
            license_file_data = f.read().strip()
        Logger.info("Uploading license file", path=license_file)
        client.licensing.upload_license(license_file_data)

    accept_license = False
    try:
        current_license = client.licensing.get_license().response
        accept_license = not current_license or not current_license.finalized
    except ApiException as e:
        if e.get_error_message() == "Must be licensed first.":
            accept_license = True
        else:
            raise e

    if accept_license:
        Logger.info("Accepting license", parameters=license_parameters)
        client.licensing.put_set_license_parameters(**license_parameters)
        Logger.info("Waiting for server reboot.")
        client.sys_admin.wait_for_api(timeout=reboot_timeout, wait_for_reboot=True)

    current_keyset = api_operations.retry_call(
        client.config.get_keyset, max_attempts=20
    ).response
    if not current_keyset.keyset_present and not current_keyset.in_progress:
        Logger.info("Generating keyset", parameters=keyset_parameters)
        api_operations.retry_call(client.config.put_keyset, kwargs=keyset_parameters)
        Logger.info("Waiting for keyset ready")
        client.config.wait_for_keyset(timeout=keyset_timeout)
    elif current_keyset.in_progress:
        client.config.wait_for_keyset(timeout=keyset_timeout)

    current_peering_status = client.peering.get_peering_status().response
    if not current_peering_status.id and peering_id:
        Logger.info("Setting peering id", id=peering_id)
        client.peering.put_self_peering_id(**{"id": peering_id})
    return client
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)